diff --git a/content/treehouse/dev/chats.tree b/content/treehouse/dev/chats.tree new file mode 100644 index 0000000..30507ab --- /dev/null +++ b/content/treehouse/dev/chats.tree @@ -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 %} diff --git a/crates/treehouse/src/cli/generate.rs b/crates/treehouse/src/cli/generate.rs index de3001e..17c3887 100644 --- a/crates/treehouse/src/cli/generate.rs +++ b/crates/treehouse/src/cli/generate.rs @@ -291,6 +291,7 @@ impl Generator { treehouse, config, &mut config_derived_data, + paths, parsed_tree.file_id, &roots.branches, ); diff --git a/crates/treehouse/src/html/tree.rs b/crates/treehouse/src/html/tree.rs index 988b642..7961037 100644 --- a/crates/treehouse/src/html/tree.rs +++ b/crates/treehouse/src/html/tree.rs @@ -1,9 +1,10 @@ -use std::fmt::Write; +use std::{borrow::Cow, fmt::Write}; use pulldown_cmark::{BrokenLink, LinkType}; use treehouse_format::pull::BranchKind; use crate::{ + cli::Paths, config::{Config, ConfigDerivedData}, html::EscapeAttribute, state::{FileId, Treehouse}, @@ -20,6 +21,7 @@ pub fn branch_to_html( treehouse: &mut Treehouse, config: &Config, config_derived_data: &mut ConfigDerivedData, + paths: &Paths<'_>, file_id: FileId, branch_id: SemaBranchId, ) { @@ -49,6 +51,11 @@ pub fn branch_to_html( } else { "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 { 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!( s, - "
  • ", + "
  • ", EscapeAttribute(&branch.html_id) ) .unwrap(); @@ -136,7 +153,7 @@ pub fn branch_to_html( } }; 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( &final_markdown, @@ -204,7 +221,15 @@ pub fn branch_to_html( let num_children = branch.children.len(); for i in 0..num_children { 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(""); } @@ -221,12 +246,21 @@ pub fn branches_to_html( treehouse: &mut Treehouse, config: &Config, config_derived_data: &mut ConfigDerivedData, + paths: &Paths<'_>, file_id: FileId, branches: &[SemaBranchId], ) { s.push_str(""); } diff --git a/crates/treehouse/src/tree/attributes.rs b/crates/treehouse/src/tree/attributes.rs index 6a317ca..025195c 100644 --- a/crates/treehouse/src/tree/attributes.rs +++ b/crates/treehouse/src/tree/attributes.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; /// Top-level `%%` root attributes. @@ -79,6 +81,14 @@ pub struct Attributes { /// debug mode. #[serde(default)] 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, } /// Controls for block content presentation. diff --git a/crates/treehouse/src/tree/mini_template.rs b/crates/treehouse/src/tree/mini_template.rs index 947613e..3a1ae86 100644 --- a/crates/treehouse/src/tree/mini_template.rs +++ b/crates/treehouse/src/tree/mini_template.rs @@ -8,7 +8,7 @@ use std::ops::Range; use pulldown_cmark::escape::escape_html; -use crate::{config::Config, state::Treehouse}; +use crate::{cli::Paths, config::Config, state::Treehouse}; struct Lexer<'a> { input: &'a str, @@ -149,7 +149,7 @@ impl<'a> Renderer<'a> { 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; while let Some(token) = self.lexer.next() { @@ -166,6 +166,7 @@ impl<'a> Renderer<'a> { match Self::render_template( config, treehouse, + paths, self.lexer.input[inside.as_ref().unwrap().range.clone()].trim(), ) { Ok(s) => match escaping { @@ -192,22 +193,24 @@ impl<'a> Renderer<'a> { fn render_template( config: &Config, _treehouse: &Treehouse, + paths: &Paths<'_>, template: &str, ) -> Result { let (function, arguments) = template.split_once(' ').unwrap_or((template, "")); match function { "pic" => Ok(config.pic_url(arguments)), - "c++" => Ok("".into()), + "include_static" => std::fs::read_to_string(paths.static_dir.join(arguments)) + .map_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 { lexer: Lexer::new(input), output: String::new(), }; - renderer.render(config, treehouse); + renderer.render(config, treehouse, paths); renderer.output } diff --git a/static/chat/treehouse/dev/chats/test.json b/static/chat/treehouse/dev/chats/test.json new file mode 100644 index 0000000..94bff5a --- /dev/null +++ b/static/chat/treehouse/dev/chats/test.json @@ -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" } + } +} diff --git a/static/css/components/chat.css b/static/css/components/chat.css new file mode 100644 index 0000000..c988c8d --- /dev/null +++ b/static/css/components/chat.css @@ -0,0 +1,83 @@ +: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