commit
4ebb3b86d0
11
content/treehouse/dev/chats.tree
Normal file
11
content/treehouse/dev/chats.tree
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
%% title = "testing ground for (chit)chats"
|
||||||
|
scripts = ["components/chat.js"]
|
||||||
|
styles = ["components/chat.css"]
|
||||||
|
|
||||||
|
% id = "01HSR695VNDXRGPC3XCHS3A61V"
|
||||||
|
- this is a test page for the treehouse's chat system.
|
||||||
|
|
||||||
|
% cast = "chat js"
|
||||||
|
id = "01HSR695VNDYS0GDB527B9NPJS"
|
||||||
|
template = true
|
||||||
|
- {% include_static chat/treehouse/dev/chats/test.json %}
|
|
@ -291,6 +291,7 @@ impl Generator {
|
||||||
treehouse,
|
treehouse,
|
||||||
config,
|
config,
|
||||||
&mut config_derived_data,
|
&mut config_derived_data,
|
||||||
|
paths,
|
||||||
parsed_tree.file_id,
|
parsed_tree.file_id,
|
||||||
&roots.branches,
|
&roots.branches,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::fmt::Write;
|
use std::{borrow::Cow, fmt::Write};
|
||||||
|
|
||||||
use pulldown_cmark::{BrokenLink, LinkType};
|
use pulldown_cmark::{BrokenLink, LinkType};
|
||||||
use treehouse_format::pull::BranchKind;
|
use treehouse_format::pull::BranchKind;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cli::Paths,
|
||||||
config::{Config, ConfigDerivedData},
|
config::{Config, ConfigDerivedData},
|
||||||
html::EscapeAttribute,
|
html::EscapeAttribute,
|
||||||
state::{FileId, Treehouse},
|
state::{FileId, Treehouse},
|
||||||
|
@ -20,6 +21,7 @@ pub fn branch_to_html(
|
||||||
treehouse: &mut Treehouse,
|
treehouse: &mut Treehouse,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
config_derived_data: &mut ConfigDerivedData,
|
config_derived_data: &mut ConfigDerivedData,
|
||||||
|
paths: &Paths<'_>,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
branch_id: SemaBranchId,
|
branch_id: SemaBranchId,
|
||||||
) {
|
) {
|
||||||
|
@ -49,6 +51,11 @@ pub fn branch_to_html(
|
||||||
} else {
|
} else {
|
||||||
"b"
|
"b"
|
||||||
};
|
};
|
||||||
|
let component = if !branch.attributes.cast.is_empty() {
|
||||||
|
Cow::Owned(format!("{component} {}", branch.attributes.cast))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(component)
|
||||||
|
};
|
||||||
|
|
||||||
let linked_branch = if let Content::Link(link) = &branch.attributes.content {
|
let linked_branch = if let Content::Link(link) = &branch.attributes.content {
|
||||||
format!(" data-th-link=\"{}\"", EscapeHtml(link))
|
format!(" data-th-link=\"{}\"", EscapeHtml(link))
|
||||||
|
@ -62,9 +69,19 @@ pub fn branch_to_html(
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut data_attributes = String::new();
|
||||||
|
for (key, value) in &branch.attributes.data {
|
||||||
|
write!(
|
||||||
|
data_attributes,
|
||||||
|
" data-{key}=\"{}\"",
|
||||||
|
EscapeAttribute(value)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
s,
|
s,
|
||||||
"<li data-cast=\"{component}\" class=\"{class}\" id=\"{}\"{linked_branch}{do_not_persist}>",
|
"<li data-cast=\"{component}\" class=\"{class}\" id=\"{}\"{linked_branch}{do_not_persist}{data_attributes}>",
|
||||||
EscapeAttribute(&branch.html_id)
|
EscapeAttribute(&branch.html_id)
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -136,7 +153,7 @@ pub fn branch_to_html(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if branch.attributes.template {
|
if branch.attributes.template {
|
||||||
final_markdown = mini_template::render(config, treehouse, &final_markdown);
|
final_markdown = mini_template::render(config, treehouse, paths, &final_markdown);
|
||||||
}
|
}
|
||||||
let markdown_parser = pulldown_cmark::Parser::new_with_broken_link_callback(
|
let markdown_parser = pulldown_cmark::Parser::new_with_broken_link_callback(
|
||||||
&final_markdown,
|
&final_markdown,
|
||||||
|
@ -204,7 +221,15 @@ pub fn branch_to_html(
|
||||||
let num_children = branch.children.len();
|
let num_children = branch.children.len();
|
||||||
for i in 0..num_children {
|
for i in 0..num_children {
|
||||||
let child_id = treehouse.tree.branch(branch_id).children[i];
|
let child_id = treehouse.tree.branch(branch_id).children[i];
|
||||||
branch_to_html(s, treehouse, config, config_derived_data, file_id, child_id);
|
branch_to_html(
|
||||||
|
s,
|
||||||
|
treehouse,
|
||||||
|
config,
|
||||||
|
config_derived_data,
|
||||||
|
paths,
|
||||||
|
file_id,
|
||||||
|
child_id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
s.push_str("</ul>");
|
s.push_str("</ul>");
|
||||||
}
|
}
|
||||||
|
@ -221,12 +246,21 @@ pub fn branches_to_html(
|
||||||
treehouse: &mut Treehouse,
|
treehouse: &mut Treehouse,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
config_derived_data: &mut ConfigDerivedData,
|
config_derived_data: &mut ConfigDerivedData,
|
||||||
|
paths: &Paths<'_>,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
branches: &[SemaBranchId],
|
branches: &[SemaBranchId],
|
||||||
) {
|
) {
|
||||||
s.push_str("<ul>");
|
s.push_str("<ul>");
|
||||||
for &child in branches {
|
for &child in branches {
|
||||||
branch_to_html(s, treehouse, config, config_derived_data, file_id, child);
|
branch_to_html(
|
||||||
|
s,
|
||||||
|
treehouse,
|
||||||
|
config,
|
||||||
|
config_derived_data,
|
||||||
|
paths,
|
||||||
|
file_id,
|
||||||
|
child,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
s.push_str("</ul>");
|
s.push_str("</ul>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Top-level `%%` root attributes.
|
/// Top-level `%%` root attributes.
|
||||||
|
@ -79,6 +81,14 @@ pub struct Attributes {
|
||||||
/// debug mode.
|
/// debug mode.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub stage: Stage,
|
pub stage: Stage,
|
||||||
|
|
||||||
|
/// List of extra spells to cast on the branch.
|
||||||
|
#[serde(default)]
|
||||||
|
pub cast: String,
|
||||||
|
|
||||||
|
/// List of extra `data` attributes to add to the block.
|
||||||
|
#[serde(default)]
|
||||||
|
pub data: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls for block content presentation.
|
/// Controls for block content presentation.
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::ops::Range;
|
||||||
|
|
||||||
use pulldown_cmark::escape::escape_html;
|
use pulldown_cmark::escape::escape_html;
|
||||||
|
|
||||||
use crate::{config::Config, state::Treehouse};
|
use crate::{cli::Paths, config::Config, state::Treehouse};
|
||||||
|
|
||||||
struct Lexer<'a> {
|
struct Lexer<'a> {
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
@ -149,7 +149,7 @@ impl<'a> Renderer<'a> {
|
||||||
self.output.push_str(&self.lexer.input[token.range.clone()]);
|
self.output.push_str(&self.lexer.input[token.range.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, config: &Config, treehouse: &Treehouse) {
|
fn render(&mut self, config: &Config, treehouse: &Treehouse, paths: &Paths<'_>) {
|
||||||
let kind_of = |token: &Token| token.kind;
|
let kind_of = |token: &Token| token.kind;
|
||||||
|
|
||||||
while let Some(token) = self.lexer.next() {
|
while let Some(token) = self.lexer.next() {
|
||||||
|
@ -166,6 +166,7 @@ impl<'a> Renderer<'a> {
|
||||||
match Self::render_template(
|
match Self::render_template(
|
||||||
config,
|
config,
|
||||||
treehouse,
|
treehouse,
|
||||||
|
paths,
|
||||||
self.lexer.input[inside.as_ref().unwrap().range.clone()].trim(),
|
self.lexer.input[inside.as_ref().unwrap().range.clone()].trim(),
|
||||||
) {
|
) {
|
||||||
Ok(s) => match escaping {
|
Ok(s) => match escaping {
|
||||||
|
@ -192,22 +193,24 @@ impl<'a> Renderer<'a> {
|
||||||
fn render_template(
|
fn render_template(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
_treehouse: &Treehouse,
|
_treehouse: &Treehouse,
|
||||||
|
paths: &Paths<'_>,
|
||||||
template: &str,
|
template: &str,
|
||||||
) -> Result<String, InvalidTemplate> {
|
) -> Result<String, InvalidTemplate> {
|
||||||
let (function, arguments) = template.split_once(' ').unwrap_or((template, ""));
|
let (function, arguments) = template.split_once(' ').unwrap_or((template, ""));
|
||||||
match function {
|
match function {
|
||||||
"pic" => Ok(config.pic_url(arguments)),
|
"pic" => Ok(config.pic_url(arguments)),
|
||||||
"c++" => Ok("<script>alert(1)</script>".into()),
|
"include_static" => std::fs::read_to_string(paths.static_dir.join(arguments))
|
||||||
|
.map_err(|_| InvalidTemplate),
|
||||||
_ => Err(InvalidTemplate),
|
_ => Err(InvalidTemplate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(config: &Config, treehouse: &Treehouse, input: &str) -> String {
|
pub fn render(config: &Config, treehouse: &Treehouse, paths: &Paths<'_>, input: &str) -> String {
|
||||||
let mut renderer = Renderer {
|
let mut renderer = Renderer {
|
||||||
lexer: Lexer::new(input),
|
lexer: Lexer::new(input),
|
||||||
output: String::new(),
|
output: String::new(),
|
||||||
};
|
};
|
||||||
renderer.render(config, treehouse);
|
renderer.render(config, treehouse, paths);
|
||||||
renderer.output
|
renderer.output
|
||||||
}
|
}
|
||||||
|
|
42
static/chat/treehouse/dev/chats/test.json
Normal file
42
static/chat/treehouse/dev/chats/test.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"init": {
|
||||||
|
"kind": "say",
|
||||||
|
"content": "hello! nice to meet you.",
|
||||||
|
"then": "question1"
|
||||||
|
},
|
||||||
|
"question1": {
|
||||||
|
"kind": "ask",
|
||||||
|
"questions": [
|
||||||
|
{ "content": "Who are you?", "then": "whoAreYou" },
|
||||||
|
{ "content": "What is this?", "then": "whatIsThis" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"whoAreYou": {
|
||||||
|
"kind": "say",
|
||||||
|
"content": "I'm liquidex. y'know, the guy running this place.",
|
||||||
|
"then": "question1"
|
||||||
|
},
|
||||||
|
"whatIsThis": {
|
||||||
|
"kind": "say",
|
||||||
|
"content": "this is a test of the treehouse dialogue system. basically, I made myself a little framework for writing conversations between characters, and this is a test page for it.",
|
||||||
|
"then": "whatIsThisQuestion"
|
||||||
|
},
|
||||||
|
"whatIsThisQuestion": {
|
||||||
|
"kind": "ask",
|
||||||
|
"questions": [
|
||||||
|
{ "content": "Right.", "then": "question1" },
|
||||||
|
{
|
||||||
|
"content": "\"Dialogues\", seriously? What, are you British?",
|
||||||
|
"then": "british"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"british": {
|
||||||
|
"kind": "say",
|
||||||
|
"content": "no, but I think British is pretty damn funny.",
|
||||||
|
"then": "question1"
|
||||||
|
},
|
||||||
|
"end": { "kind": "end" }
|
||||||
|
}
|
||||||
|
}
|
85
static/css/components/chat.css
Normal file
85
static/css/components/chat.css
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
:root {
|
||||||
|
--icon-choose: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgMTJMMTAgOEw2IDQiIHN0cm9rZT0iIzU1NDIzZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--icon-choose: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgMTJMMTAgOEw2IDQiIHN0cm9rZT0iI2Q3Y2RiZiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th-chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
th-chat-said {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
border: 1px solid var(--border-1);
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th-chat-asked {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
&>button {
|
||||||
|
/* Reset <button> */
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
user-select: auto;
|
||||||
|
|
||||||
|
padding: 0.5em 0;
|
||||||
|
margin-right: 2rem;
|
||||||
|
|
||||||
|
--recursive-wght: 500;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-align: right;
|
||||||
|
opacity: 80%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity var(--transition-duration);
|
||||||
|
|
||||||
|
&.asked {
|
||||||
|
display: none;
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
--recursive-wght: 600;
|
||||||
|
cursor: default;
|
||||||
|
opacity: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
background-image: var(--icon-choose);
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: 0%;
|
||||||
|
vertical-align: middle;
|
||||||
|
translate: -1em 0;
|
||||||
|
transition: opacity var(--transition-duration), translate var(--transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::before, &[disabled]::before {
|
||||||
|
opacity: 50%;
|
||||||
|
translate: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,7 +101,8 @@ body::selection {
|
||||||
body,
|
body,
|
||||||
pre,
|
pre,
|
||||||
code,
|
code,
|
||||||
kbd {
|
kbd,
|
||||||
|
button {
|
||||||
font-family: 'RecVar', sans-serif;
|
font-family: 'RecVar', sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
165
static/js/components/chat.js
Normal file
165
static/js/components/chat.js
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import { addSpell, spell } from "treehouse/spells.js";
|
||||||
|
import { Branch } from "treehouse/tree.js";
|
||||||
|
|
||||||
|
const persistenceKey = "treehouse.chats";
|
||||||
|
let persistentState = JSON.parse(localStorage.getItem(persistenceKey)) || {};
|
||||||
|
|
||||||
|
persistentState.log ??= {};
|
||||||
|
savePersistentState();
|
||||||
|
|
||||||
|
function savePersistentState() {
|
||||||
|
localStorage.setItem(persistenceKey, JSON.stringify(persistentState));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chat extends HTMLElement {
|
||||||
|
constructor(branch) {
|
||||||
|
super();
|
||||||
|
this.branch = branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.id = spell(this.branch, Branch).namedID;
|
||||||
|
this.model = JSON.parse(spell(this.branch, Branch).branchContent.textContent);
|
||||||
|
|
||||||
|
this.state = new ChatState(this, this.model);
|
||||||
|
this.state.onInteract = () => {
|
||||||
|
persistentState.log[this.id] = this.state.log;
|
||||||
|
savePersistentState();
|
||||||
|
};
|
||||||
|
this.state.exec("init");
|
||||||
|
|
||||||
|
let log = persistentState.log[this.id];
|
||||||
|
if (log != null) {
|
||||||
|
this.state.replay(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("th-chat", Chat);
|
||||||
|
|
||||||
|
class Said extends HTMLElement {
|
||||||
|
constructor({ content }) {
|
||||||
|
super();
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = this.content;
|
||||||
|
this.dispatchEvent(new Event(".animationsComplete"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("th-chat-said", Said);
|
||||||
|
|
||||||
|
class Asked extends HTMLElement {
|
||||||
|
constructor({ content, alreadyAsked }) {
|
||||||
|
super();
|
||||||
|
this.content = content;
|
||||||
|
this.alreadyAsked = alreadyAsked;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.button = document.createElement("button");
|
||||||
|
this.button.innerHTML = this.content;
|
||||||
|
this.button.addEventListener("click", _ => {
|
||||||
|
this.dispatchEvent(new Event(".click"));
|
||||||
|
});
|
||||||
|
if (this.alreadyAsked) {
|
||||||
|
this.button.classList.add("asked");
|
||||||
|
}
|
||||||
|
this.appendChild(this.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
interactionFinished() {
|
||||||
|
this.button.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("th-chat-asked", Asked);
|
||||||
|
|
||||||
|
class ChatState {
|
||||||
|
constructor(container, model) {
|
||||||
|
this.container = container;
|
||||||
|
this.model = model;
|
||||||
|
this.log = [];
|
||||||
|
this.results = {};
|
||||||
|
this.wereAsked = new Set();
|
||||||
|
this.onInteract = _ => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
replay(log) {
|
||||||
|
for (let entry of log) {
|
||||||
|
this.interact(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(name) {
|
||||||
|
let node = this.model.nodes[name];
|
||||||
|
let results = this.results[name];
|
||||||
|
this.results[name] = this[node.kind](name, node, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
say(_, node) {
|
||||||
|
let said = new Said({ content: node.content });
|
||||||
|
said.addEventListener(".animationsComplete", _ => this.exec(node.then));
|
||||||
|
this.container.appendChild(said);
|
||||||
|
}
|
||||||
|
|
||||||
|
ask(name, node) {
|
||||||
|
let questions = [];
|
||||||
|
for (let i_ = 0; i_ < node.questions.length; ++i_) {
|
||||||
|
let i = i_;
|
||||||
|
let key = `${name}[${i}]`;
|
||||||
|
|
||||||
|
let question = node.questions[i];
|
||||||
|
let asked = new Asked({ content: question.content, alreadyAsked: this.wereAsked.has(key) });
|
||||||
|
asked.addEventListener(".click", _ => {
|
||||||
|
this.interact({
|
||||||
|
kind: "ask.choose",
|
||||||
|
name,
|
||||||
|
option: i,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.container.appendChild(asked);
|
||||||
|
questions[i] = asked;
|
||||||
|
}
|
||||||
|
return questions;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {}
|
||||||
|
|
||||||
|
interact(interaction) {
|
||||||
|
let node = this.model.nodes[interaction.name];
|
||||||
|
|
||||||
|
this.log.push(interaction);
|
||||||
|
this.onInteract();
|
||||||
|
|
||||||
|
switch (interaction.kind) {
|
||||||
|
case "ask.choose": {
|
||||||
|
if (this.wereAsked.has(interaction.key)) {
|
||||||
|
this.log.pop();
|
||||||
|
}
|
||||||
|
this.wereAsked.add(interaction.key);
|
||||||
|
|
||||||
|
let questions = this.results[interaction.name];
|
||||||
|
let question = node.questions[interaction.option];
|
||||||
|
let asked = questions[interaction.option];
|
||||||
|
asked.interactionFinished();
|
||||||
|
this.exec(question.then);
|
||||||
|
for (let q of questions) {
|
||||||
|
if (q != asked) {
|
||||||
|
q.parentNode.removeChild(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpell("chat", class {
|
||||||
|
constructor(branch) {
|
||||||
|
branch.replaceWith(new Chat(branch));
|
||||||
|
}
|
||||||
|
});
|
|
@ -70,10 +70,12 @@ let mutationObserver = new MutationObserver(records => {
|
||||||
// NOTE: Added nodes may contain children which also need to be processed.
|
// NOTE: Added nodes may contain children which also need to be processed.
|
||||||
// Collect those that have [data-cast] on them and apply spells to them.
|
// Collect those that have [data-cast] on them and apply spells to them.
|
||||||
for (let addedNode of record.addedNodes) {
|
for (let addedNode of record.addedNodes) {
|
||||||
if (addedNode.getAttribute("data-cast") != null) {
|
if (!(addedNode instanceof Text)) {
|
||||||
mutatedNodes.add(addedNode);
|
if (addedNode.getAttribute("data-cast") != null) {
|
||||||
|
mutatedNodes.add(addedNode);
|
||||||
|
}
|
||||||
|
addedNode.querySelectorAll("[data-cast]").forEach(element => mutatedNodes.add(element));
|
||||||
}
|
}
|
||||||
addedNode.querySelectorAll("[data-cast]").forEach(element => mutatedNodes.add(element));
|
|
||||||
}
|
}
|
||||||
applySpells(mutatedNodes);
|
applySpells(mutatedNodes);
|
||||||
}
|
}
|
||||||
|
|
3
static/svg/dark/choose.svg
Normal file
3
static/svg/dark/choose.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 12L10 8L6 4" stroke="#d7cdbf" stroke-width="2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 164 B |
3
static/svg/light/choose.svg
Normal file
3
static/svg/light/choose.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 12L10 8L6 4" stroke="#55423e" stroke-width="2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 164 B |
Loading…
Reference in a new issue