diff --git a/Cargo.lock b/Cargo.lock index e89722b..5f8e6a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -724,7 +724,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1409,7 +1409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror", "ucd-trie", ] @@ -1599,7 +1599,7 @@ dependencies = [ "rand_chacha", "simd_helpers", "system-deps", - "thiserror 1.0.69", + "thiserror", "v_frame", "wasm-bindgen", ] @@ -1931,16 +1931,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl", ] [[package]] @@ -1954,17 +1945,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.8" @@ -2196,17 +2176,24 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror 2.0.12", "tokio", "toml_edit 0.19.15", "tracing", "tracing-chrome", "tracing-subscriber", + "treehouse-format", "ulid", "webp", "xmlparser", ] +[[package]] +name = "treehouse-format" +version = "0.1.0" +dependencies = [ + "thiserror", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 97f0b1b..9c407e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,11 @@ -[package] -name = "treehouse" -version = "0.1.0" -edition = "2021" +[workspace] +members = ["crates/*"] +resolver = "2" -[dependencies] -anyhow = "1.0.75" -axum = { version = "0.7.9", features = ["macros"] } -axum-macros = "0.4.2" -base64 = "0.21.7" -blake3 = "1.5.3" -chrono = { version = "0.4.35", features = ["serde"] } -clap = { version = "4.3.22", features = ["derive"] } -codespan-reporting = "0.11.1" -dashmap = "6.1.0" -git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] } -handlebars = "4.3.7" -image = "0.25.5" -indexmap = { version = "2.2.6", features = ["serde"] } -jotdown = { version = "0.4.1", default-features = false } -rand = "0.8.5" -rayon = "1.10.0" -regex = "1.10.3" -serde = { version = "1.0.183", features = ["derive"] } -serde_json = "1.0.105" -thiserror = "2.0.12" -tokio = { version = "1.32.0", features = ["full"] } -toml_edit = { version = "0.19.14", features = ["serde"] } +[workspace.dependencies] tracing = "0.1.40" -tracing-chrome = "0.7.2" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -ulid = "1.0.0" -webp = "0.3.0" -xmlparser = "0.13.6" + +treehouse-format = { path = "crates/treehouse-format" } [profile.dev] package.webp.opt-level = 3 diff --git a/Justfile b/Justfile index e9915ae..7cc2557 100644 --- a/Justfile +++ b/Justfile @@ -1,7 +1,7 @@ port := "8080" serve: - RUST_BACKTRACE=1 cargo watch -- cargo run -- serve --port {{port}} + cargo watch -- cargo run -- serve --port {{port}} fix: cargo run -- fix-all --apply diff --git a/content/about/v2.tree b/content/about.tree similarity index 100% rename from content/about/v2.tree rename to content/about.tree diff --git a/content/index.dj b/content/index.dj deleted file mode 100644 index 2fdb873..0000000 --- a/content/index.dj +++ /dev/null @@ -1,37 +0,0 @@ -title = "riki's house" -include_feed = { name = "new", title = "Blog" } - -+++ - -My name's *riki moe*, or *リキ萌*! - -I'm a _he/him_-type cat [furry][page:philosophy/furry] doing various things with computers. - -I work on game optimization tools at [[CD PROJEKT RED](https://cdprojektred.com) :rarog:]{.nowrap} but that's just the tip of the iceberg! -After hours, I program [websites](/), [compilers][def:stitchkit/repo], [audio][def:dawd3/repo], and other fun things. - -Such as [*video games.*][page:games] -Like, I probably wouldn't be in the industry if I didn't like them.\ -My personal favourites are [:nap: [DELTARUNE](https://deltarune.com)]{.nowrap}, [:bean: [Animal Well](https://www.animalwell.net/)]{.nowrap}, [:fox: [TUNIC](https://tunicgame.com/)]{.nowrap}, [:hueh: [A Hat in Time](https://hatintime.com)]{.nowrap}, and [:propane: [Noita](https://noitagame.com/)]{.nowrap}. -But also many many more, because I'm really indecisive. - -Or [*music.*][page:music] -I [listen to a lot of it.][def:social/bandcamp] -And I mean, [_a lot_.][def:social/listenbrainz] -A metric fuck tonne.\ -I'm a huge fan of electronic genres, but also jazz and (alternative) rock from time to time.\ -I listen to [_Songs About My Cats_](https://venetiansnares.bandcamp.com/album/songs-about-my-cats) while coding.\ -My favourite artists are [C418](https://c418.bandcamp.com/album/excursions), [The Flashbulb](https://theflashbulb.bandcamp.com/album/kirlian-selections), [Aphex Twin](https://aphextwin.bandcamp.com/album/drukqs), [Squarepusher](https://squarepusher.bandcamp.com/album/ultravisitor), and [False Noise](https://upscalehq.bandcamp.com/album/floral-strobe). - -I kinda also [make music sometimes](https://daknus.bandcamp.com) when I feel like it. - -I also drew a bit of dawing (...do a bit of drawing), as evidenced by the floofee on this page. - -If all that sounds like an interesting bunch of words... - -- feel free to email me: `hi` at this domain!! -- or add me on Discord---the nickname's *rikimoe*. - -I like to think I'm pretty amicable in person but I'm uh, also really socially awkward...!\ -~Please excuse any social awkwardness that may ensue from you contacting me.\ -Or me contacting you.~ diff --git a/content/index.tree b/content/index.tree index 5e7245f..7f437f2 100644 --- a/content/index.tree +++ b/content/index.tree @@ -73,6 +73,10 @@ visibility = "Private" - I'd like to make some new friends! if you wanna meet me, email `hi` at this domain. +% id = "about" + content.link = "about" ++ ## [``{=html}][page:kuroneko]{.secret}me + % id = "programming" content.link = "programming" + ## ``{=html}programming diff --git a/content/requiem.dj b/content/requiem.dj deleted file mode 100644 index 0a32dfc..0000000 --- a/content/requiem.dj +++ /dev/null @@ -1,130 +0,0 @@ -title = "Requiem for a Fractal Forest" - -+++ - - -It's been almost 2 years since the treehouse's inception, and there've been a lot of changes in the project throughout. -It all started on my holiday in August 2023, when I was bored without a computer. -Reading [Lobsters](https://lobste.rs), I stumbled upon someone's website, and it made me think a lot about how I could make a website I would love as an outlet for writing. -A website that would truly be a reflection of myself, my own values, and my own quirkiness. - -I opened the A5 notepad I had on me during that trip, and started sketching. - -A website made out of bullet points. -A tree of paragraphs, nesting forever and ever, where in every collapsed branch you could find something more. -And it would grow into an ever-more-deeply-nesting fractal forest, a rabbit hole to suck you in for hours. -Imagine [Vsauce](https://www.youtube.com/@Vsauce), but on a blog, and if all the tangents were optional. - -I was really enjoying [Logseq](https://logseq.com/) at the time, and it was my primary source of inspiration for the treehouse. -It just felt like a natural way to organise thoughts, so I wanted to create something like it, but without the lagginess and chugginess coming with a bloated frontend app written in Clojure. - -And that, my friends, is how the treehouse was born. - - -## The implementation - -When I came back home, I jumped straight to my code editor. -First a data format. Then an HTML generator. And then some CSS to style it. - -The first version of the treehouse was up and running. - -It wasn't much, but it worked. -It proved the concept, so I started fleshing it out. - -- I added some JavaScript to make the navigation more friendly. -- Fixed some UX details of the `
` element that bugged me. -- I made it so that branches could expand into lazily-loaded trees, so that you wouldn't have to navigate away from the main page. -- I made it possible to permalink to branches of the tree, so that you could link parts of it to your friends. -- I wrote a script that would generate unique IDs for branches for me automatically, so that I could just run `treehouse fix` before a commit, and everything would be linkable. -- I added dates, so that you could keep track of when something was updated. -- [And so many more things.][page:treehouse/changelog] - -And of course, alongside all those technicalities, I was writing. - -The treehouse is single-handedly what taught me to write regularly about my thoughts, observations, insights, and pet peeves. -And through that, I taught myself to _Write_. \ -Text. Essays. Prose. - -The treehouse has been on my mind as my main project ever since I started it. - -It just resonated with me so strongly.\ -I cherished it like a beloved friend. - -Everything I did revolved around the treehouse. - -Most side projects I did were features, improvements, and weird experiments. -Like that one time I wanted to add a sort-of-visual-novel-type-of-thing to the website, so I started banging out a whole [Twine](https://twinery.org/)-like story graph editor. - -It was all incredibly fun. - - -## The limits - -But throughout this whole process, I was constantly running into problems with the tree format. -You see, the UX just plain _sucked_. - -By that I mean, the basic UX of reading pages was pretty bad. -It felt more like reading a braindump than a polished post, even when I spent hours on structuring, proofreading, and everything. - -The nesting was distracting. -The more of it was there, the worse it would get. -I pretty soon learned you gotta dial it back down to the absolute minimum. 1--3 levels is enough. -Root for introduction and headings, 2 for heading content, and 3 for tangents. - -But it never felt _right_. -As you expanded branches on the main page, they would drift rightwards. -The indentation would quickly get out of hand, eating away all your precious screen space. -I added indent guides to help combat it, but they didn't help. -It was like browsing an overly nested folder. -Probably because it was _precisely that_. - -The UI around it just felt like a huge distraction. -Hovering over a branch always caused something to fade into your view to signal it could be interacted with, and I feel like it resulted in a lack of sense of stability to the pages. -It was detracting you from the actual content of the website. - -It was a fun quirk, but not much more than that. - -I was also reaching the limits of the structure. -A tree can only have one parent, but what if a post fits more than one category? -Where do I put it then? - -And what if I wanna edit posts on the web, or add a section for short tweet-like braindumps, or something? - -It was all incredibly limiting. - - -## The breakpoint - -The moment I decided it was time to let go, was the moment I needed to do a layout change that'd be impossible with a tree. - -I wanted to add my fursona to the right side of the screen. - -Whatever I did, it would eat away too much space, and result in a feeling of imbalance. -I had to eat away all the padding from the left side, but then the text felt too cramped. -It was much too close to the edge of the screen. - -The only real solution I could see was to limit the page to a smaller width, but that didn't work with the amount of UI elements tree branches had. - -So I abandoned them! - -Welcome to the new treehouse. -Or, rather, just house, because there's no longer a tree. -You can call it a _meoooow~_house if you insist. :ahyes: - ---- - -But! - -Even if it's not a tree in structure, it still has to support the tree format for backwards compatibility. -I wouldn't wanna have to rewrite all those pages I've accumulated throughout the past two years. - -But it's the end of an era. -I'll slowly be rewriting _some parts_ of the site to this document-oriented format, just like this page. -But maybe that isn't such a bad thing? - -There are lots of ways you can make a really fun blog post. -[This one comes to mind immediately](https://modem.io/blog/blog-monetization/), and honestly---would you be able to make something that amazing with a tree structure? - -Because I wouldn't. - -So... welcome to the new treehouse :3 diff --git a/content/treehouse/new.tree b/content/treehouse/new.tree index 5232f89..371207f 100644 --- a/content/treehouse/new.tree +++ b/content/treehouse/new.tree @@ -26,13 +26,6 @@ if you've been wondering what I've been up to, you've come to the right place. if you want to read any of the posts, follow the links. it's like that by design. -% tags = ["design", "treehouse"] - id = "01K02XZTW3VYKX0Q5NZ17NRVTF" -- ### [Requiem for a Fractal Forest][page:requiem.dj] - - % id = "01K02XZTW3W08E195CRHJ4XATD" - - A retrospect on the treehouse's form factor, and why I'm getting rid of it. (at least partially) - % tags = ["programming"] id = "01JX0GYB1D4W3A6FRPBG738N4F" - ### [on changing the Firefox New Tab, and software freedom][page:programming/new-tab] diff --git a/crates/treehouse-format/Cargo.toml b/crates/treehouse-format/Cargo.toml new file mode 100644 index 0000000..7db6b26 --- /dev/null +++ b/crates/treehouse-format/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "treehouse-format" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror = "1.0.47" diff --git a/src/tree/ast.rs b/crates/treehouse-format/src/ast.rs similarity index 99% rename from src/tree/ast.rs rename to crates/treehouse-format/src/ast.rs index 5c97879..e8e4915 100644 --- a/src/tree/ast.rs +++ b/crates/treehouse-format/src/ast.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use super::{ +use crate::{ pull::{Attributes, BranchEvent, BranchKind, Parser}, ParseError, ParseErrorKind, }; diff --git a/crates/treehouse-format/src/lib.rs b/crates/treehouse-format/src/lib.rs new file mode 100644 index 0000000..6afdf89 --- /dev/null +++ b/crates/treehouse-format/src/lib.rs @@ -0,0 +1,32 @@ +use std::ops::Range; + +pub mod ast; +pub mod pull; + +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] +pub enum ParseErrorKind { + #[error("branch kind (`+` or `-`) expected")] + BranchKindExpected, + + #[error("root branches must not be indented")] + RootIndentLevel, + + #[error("at least {expected} spaces of indentation were expected, but got {got}")] + InconsistentIndentation { got: usize, expected: usize }, + + #[error("unterminated code block")] + UnterminatedCodeBlock, +} + +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[error("{range:?}: {kind}")] +pub struct ParseError { + pub kind: ParseErrorKind, + pub range: Range, +} + +impl ParseErrorKind { + pub fn at(self, range: Range) -> ParseError { + ParseError { kind: self, range } + } +} diff --git a/src/tree/pull.rs b/crates/treehouse-format/src/pull.rs similarity index 99% rename from src/tree/pull.rs rename to crates/treehouse-format/src/pull.rs index c1e4fd6..964ca4e 100644 --- a/src/tree/pull.rs +++ b/crates/treehouse-format/src/pull.rs @@ -1,6 +1,6 @@ use std::{convert::identity, ops::Range}; -use super::{ParseError, ParseErrorKind}; +use crate::{ParseError, ParseErrorKind}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BranchKind { diff --git a/crates/treehouse/Cargo.toml b/crates/treehouse/Cargo.toml new file mode 100644 index 0000000..2198fdf --- /dev/null +++ b/crates/treehouse/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "treehouse" +version = "0.1.0" +edition = "2021" + +[dependencies] + +treehouse-format = { workspace = true } + +anyhow = "1.0.75" +axum = { version = "0.7.9", features = ["macros"] } +axum-macros = "0.4.2" +base64 = "0.21.7" +blake3 = "1.5.3" +chrono = { version = "0.4.35", features = ["serde"] } +clap = { version = "4.3.22", features = ["derive"] } +codespan-reporting = "0.11.1" +dashmap = "6.1.0" +git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] } +handlebars = "4.3.7" +image = "0.25.5" +indexmap = { version = "2.2.6", features = ["serde"] } +jotdown = { version = "0.4.1", default-features = false } +rand = "0.8.5" +rayon = "1.10.0" +regex = "1.10.3" +serde = { version = "1.0.183", features = ["derive"] } +serde_json = "1.0.105" +tokio = { version = "1.32.0", features = ["full"] } +toml_edit = { version = "0.19.14", features = ["serde"] } +tracing.workspace = true +tracing-chrome = "0.7.2" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +ulid = "1.0.0" +webp = "0.3.0" +xmlparser = "0.13.6" diff --git a/src/cli.rs b/crates/treehouse/src/cli.rs similarity index 100% rename from src/cli.rs rename to crates/treehouse/src/cli.rs diff --git a/src/cli/fix.rs b/crates/treehouse/src/cli/fix.rs similarity index 99% rename from src/cli/fix.rs rename to crates/treehouse/src/cli/fix.rs index 6e64cc0..b5e1342 100644 --- a/src/cli/fix.rs +++ b/crates/treehouse/src/cli/fix.rs @@ -3,11 +3,11 @@ use std::ops::{ControlFlow, Range}; use anyhow::{anyhow, Context}; use codespan_reporting::diagnostic::Diagnostic; use tracing::{error, info}; +use treehouse_format::ast::Branch; use crate::{ parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics}, state::{report_diagnostics, FileId, Source, Treehouse}, - tree::ast::Branch, vfs::{self, Content, Dir, Edit, EditPath, VPath}, }; diff --git a/src/cli/serve.rs b/crates/treehouse/src/cli/serve.rs similarity index 100% rename from src/cli/serve.rs rename to crates/treehouse/src/cli/serve.rs diff --git a/src/cli/serve/live_reload.rs b/crates/treehouse/src/cli/serve/live_reload.rs similarity index 100% rename from src/cli/serve/live_reload.rs rename to crates/treehouse/src/cli/serve/live_reload.rs diff --git a/src/cli/serve/picture_upload.rs b/crates/treehouse/src/cli/serve/picture_upload.rs similarity index 100% rename from src/cli/serve/picture_upload.rs rename to crates/treehouse/src/cli/serve/picture_upload.rs diff --git a/src/cli/wc.rs b/crates/treehouse/src/cli/wc.rs similarity index 97% rename from src/cli/wc.rs rename to crates/treehouse/src/cli/wc.rs index 7028dc3..b210225 100644 --- a/src/cli/wc.rs +++ b/crates/treehouse/src/cli/wc.rs @@ -1,9 +1,10 @@ use std::ops::ControlFlow; +use treehouse_format::ast::{Branch, Roots}; + use crate::{ parse::parse_tree_with_diagnostics, state::{report_diagnostics, Source, Treehouse}, - tree::ast::{Branch, Roots}, vfs::{self, Content, Dir, VPath}, }; diff --git a/src/config.rs b/crates/treehouse/src/config.rs similarity index 97% rename from src/config.rs rename to crates/treehouse/src/config.rs index c55cba2..48fe361 100644 --- a/src/config.rs +++ b/crates/treehouse/src/config.rs @@ -152,8 +152,6 @@ impl Config { } pub fn page_url(&self, page: &str) -> String { - // We don't want .dj appearing in URLs, though it exists as a disambiguator in [page:] links. - let page = page.strip_suffix(".dj").unwrap_or(page); format!("{}/{}", self.site, page) } diff --git a/src/dirs.rs b/crates/treehouse/src/dirs.rs similarity index 100% rename from src/dirs.rs rename to crates/treehouse/src/dirs.rs diff --git a/src/fun.rs b/crates/treehouse/src/fun.rs similarity index 100% rename from src/fun.rs rename to crates/treehouse/src/fun.rs diff --git a/src/fun/seasons.rs b/crates/treehouse/src/fun/seasons.rs similarity index 100% rename from src/fun/seasons.rs rename to crates/treehouse/src/fun/seasons.rs diff --git a/crates/treehouse/src/generate.rs b/crates/treehouse/src/generate.rs new file mode 100644 index 0000000..ec43d98 --- /dev/null +++ b/crates/treehouse/src/generate.rs @@ -0,0 +1,238 @@ +mod atom; +mod dir_helper; +mod include_static_helper; +mod simple_template; +mod tree; + +use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc}; + +use atom::FeedDir; +use dir_helper::DirHelper; +use handlebars::{handlebars_helper, Handlebars}; +use include_static_helper::IncludeStaticHelper; +use serde::Serialize; +use tracing::{error, info_span, instrument}; + +use crate::{ + config::Config, + dirs::Dirs, + fun::seasons::Season, + sources::Sources, + vfs::{ + self, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, Entries, HtmlCanonicalize, + MemDir, Overlay, ToDynDir, VPath, VPathBuf, + }, +}; + +#[derive(Serialize)] +struct BaseTemplateData<'a> { + config: &'a Config, + import_map: String, + season: Option, + dev: bool, + feeds: Vec, +} + +impl<'a> BaseTemplateData<'a> { + fn new(sources: &'a Sources) -> Self { + Self { + config: &sources.config, + import_map: serde_json::to_string_pretty(&sources.import_map) + .expect("import map should be serializable to JSON"), + season: Season::current(), + dev: cfg!(debug_assertions), + feeds: sources.treehouse.feeds_by_name.keys().cloned().collect(), + } + } +} + +fn create_handlebars(site: &str, static_: DynDir) -> Handlebars<'static> { + let mut handlebars = Handlebars::new(); + + handlebars_helper!(cat: |a: String, b: String| a + &b); + + handlebars.register_helper("cat", Box::new(cat)); + handlebars.register_helper("asset", Box::new(DirHelper::new(site, static_.clone()))); + handlebars.register_helper( + "include_static", + Box::new(IncludeStaticHelper::new(static_)), + ); + + handlebars +} + +#[instrument(skip(handlebars))] +fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) { + vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| { + if path.extension() == Some("hbs") { + if let Some(content) = vfs::query::(dir, path).and_then(|c| c.string().ok()) { + let _span = info_span!("register_template", ?path).entered(); + if let Err(err) = handlebars.register_template_string(path.as_str(), content) { + error!("in template: {err}"); + } + } + } + ControlFlow::Continue(()) + }); +} + +struct TreehouseDir { + dirs: Arc, + sources: Arc, + handlebars: Arc>, + dir_index: DirIndex, +} + +impl TreehouseDir { + fn new( + dirs: Arc, + sources: Arc, + handlebars: Arc>, + dir_index: DirIndex, + ) -> Self { + Self { + dirs, + sources, + handlebars, + dir_index, + } + } + + #[instrument("TreehouseDir::dir", skip(self))] + fn dir(&self, path: &VPath) -> Vec { + // NOTE: This does not include simple templates, because that's not really needed right now. + + let mut index = &self.dir_index; + for component in path.segments() { + if let Some(child) = index.children.get(component) { + index = child; + } else { + // There cannot possibly be any entries under an invalid path. + // Bail early. + return vec![]; + } + } + + index + .children + .values() + .map(|child| child.full_path.clone()) + .collect() + } + + #[instrument("TreehouseDir::content", skip(self))] + fn content(&self, path: &VPath) -> Option { + let path = if path.is_root() { + VPath::new_const("index") + } else { + path + }; + + self.sources + .treehouse + .files_by_tree_path + .get(path) + .map(|&file_id| { + Content::new( + "text/html", + tree::generate_or_error(&self.sources, &self.dirs, &self.handlebars, file_id) + .into(), + ) + }) + .or_else(|| { + if path.file_name().is_some_and(|s| !s.starts_with('_')) { + let template_name = path.with_extension("hbs"); + if self.handlebars.has_template(template_name.as_str()) { + return Some(Content::new( + "text/html", + simple_template::generate_or_error( + &self.sources, + &self.handlebars, + template_name.as_str(), + ) + .into(), + )); + } + } + None + }) + } +} + +impl Dir for TreehouseDir { + fn query(&self, path: &VPath, query: &mut vfs::Query) { + query.provide(|| Entries(self.dir(path))); + query.try_provide(|| self.content(path)); + } +} + +impl fmt::Debug for TreehouseDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TreehouseDir") + } +} + +/// Acceleration structure for `dir` operations on [`TreehouseDir`]s. +#[derive(Debug, Default)] +struct DirIndex { + full_path: VPathBuf, + children: HashMap, +} + +impl DirIndex { + #[instrument(name = "DirIndex::new", skip(paths))] + pub fn new<'a>(paths: impl Iterator) -> Self { + let mut root = DirIndex::default(); + + for path in paths { + let mut parent = &mut root; + let mut full_path = VPath::ROOT.to_owned(); + for segment in path.segments() { + full_path.push(segment); + let child = parent + .children + .entry(segment.to_owned()) + .or_insert_with(|| DirIndex { + full_path: full_path.clone(), + children: HashMap::new(), + }); + parent = child; + } + } + + root + } +} + +pub fn target(dirs: Arc, sources: Arc) -> DynDir { + let mut handlebars = create_handlebars(&sources.config.site, dirs.static_.clone()); + load_templates(&mut handlebars, &dirs.template); + let handlebars = Arc::new(handlebars); + + let mut root = MemDir::new(); + root.add( + VPath::new("feed"), + ContentCache::new(FeedDir::new( + dirs.clone(), + sources.clone(), + handlebars.clone(), + )) + .to_dyn(), + ); + root.add(VPath::new("static"), dirs.static_.clone()); + root.add( + VPath::new("robots.txt"), + Cd::new(dirs.static_.clone(), VPathBuf::new("robots.txt")).to_dyn(), + ); + + let dir_index = DirIndex::new(sources.treehouse.files_by_tree_path.keys().map(|x| &**x)); + let tree_view = TreehouseDir::new(dirs, sources, handlebars, dir_index); + + let tree_view = ContentCache::new(tree_view); + tree_view.warm_up(); + let tree_view = HtmlCanonicalize::new(tree_view); + + Overlay::new(tree_view.to_dyn(), root.to_dyn()) + .anchored_at(VPath::ROOT.to_owned()) + .to_dyn() +} diff --git a/src/generate/atom.rs b/crates/treehouse/src/generate/atom.rs similarity index 64% rename from src/generate/atom.rs rename to crates/treehouse/src/generate/atom.rs index 1c042eb..9a39f7c 100644 --- a/src/generate/atom.rs +++ b/crates/treehouse/src/generate/atom.rs @@ -4,14 +4,15 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use handlebars::Handlebars; use serde::Serialize; -use tracing::{info_span, instrument}; +use tracing::{info, info_span, instrument}; +use ulid::Ulid; use crate::{ dirs::Dirs, html::djot::{self, resolve_link}, sources::Sources, state::FileId, - tree::{feed, SemaBranchId}, + tree::SemaBranchId, vfs::{self, Content, Dir, Entries, VPath, VPathBuf}, }; @@ -155,7 +156,7 @@ fn extract_entries(sources: &Sources, dirs: &Dirs, file_id: FileId) -> Vec Vec, + link: Option, +} + +fn parse_entry( + sources: &Sources, + dirs: &Dirs, + file_id: FileId, + parser: jotdown::Parser, +) -> ParsedEntry { + let mut parser = parser.into_offset_iter(); + while let Some((event, span)) = parser.next() { + if let jotdown::Event::Start(jotdown::Container::Heading { .. }, _attrs) = &event { + let mut events = vec![(event, span)]; + for (event, span) in parser.by_ref() { + // To my knowledge headings cannot nest, so it's okay not keeping a stack here. + let is_heading = matches!( + event, + jotdown::Event::End(jotdown::Container::Heading { .. }) + ); + events.push((event, span)); + if is_heading { + break; + } + } + + let title_events: Vec<_> = events + .iter() + .filter(|(event, _)| { + !matches!( + event, + // A little repetitive, but I don't mind. + // The point of this is not to include extra

and in the link text, + // but preserve other formatting such as bold, italic, code, etc. + jotdown::Event::Start( + jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. }, + _ + ) | jotdown::Event::End( + jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. } + ) + ) + }) + .cloned() + .collect(); + let mut title = String::new(); + let _render_diagnostics = djot::Renderer { + config: &sources.config, + dirs, + treehouse: &sources.treehouse, + file_id, + + // How. Just, stop. + page_id: "liquidex-you-reeeeeal-dummy".into(), + } + .render(&title_events, &mut title); + + let link = events.iter().find_map(|(event, _)| { + if let jotdown::Event::Start(jotdown::Container::Link(link, link_type), _) = event { + Some(link_url(sources, dirs, link, *link_type)) + } else { + None + } + }); + + return ParsedEntry { + title: (!title.is_empty()).then_some(title), + link, + }; + } + } + + ParsedEntry { + title: None, + link: None, + } +} + +fn link_url(sources: &Sources, dirs: &Dirs, url: &str, link_type: jotdown::LinkType) -> String { + if let jotdown::LinkType::Span(jotdown::SpanLinkType::Unresolved) = link_type { + if let Some(url) = resolve_link(&sources.config, &sources.treehouse, dirs, url) { + return url; + } + } + url.to_owned() +} + /// Extremely simple HTML renderer without the treehouse's fancy branch folding and linking features. fn branches_to_html_simple( s: &mut String, diff --git a/src/generate/dir_helper.rs b/crates/treehouse/src/generate/dir_helper.rs similarity index 100% rename from src/generate/dir_helper.rs rename to crates/treehouse/src/generate/dir_helper.rs diff --git a/src/generate/include_static_helper.rs b/crates/treehouse/src/generate/include_static_helper.rs similarity index 100% rename from src/generate/include_static_helper.rs rename to crates/treehouse/src/generate/include_static_helper.rs diff --git a/crates/treehouse/src/generate/simple_template.rs b/crates/treehouse/src/generate/simple_template.rs new file mode 100644 index 0000000..6ed88e6 --- /dev/null +++ b/crates/treehouse/src/generate/simple_template.rs @@ -0,0 +1,30 @@ +use anyhow::Context; +use handlebars::Handlebars; +use tracing::instrument; + +use crate::sources::Sources; + +use super::BaseTemplateData; + +#[instrument(name = "simple_template::generate", skip(sources, handlebars))] +pub fn generate( + sources: &Sources, + handlebars: &Handlebars, + template_name: &str, +) -> anyhow::Result { + let base_template_data = BaseTemplateData::new(sources); + handlebars + .render(template_name, &base_template_data) + .context("failed to render template") +} + +pub fn generate_or_error( + sources: &Sources, + handlebars: &Handlebars, + template_name: &str, +) -> String { + match generate(sources, handlebars, template_name) { + Ok(html) => html, + Err(error) => format!("error: {error:?}"), + } +} diff --git a/crates/treehouse/src/generate/tree.rs b/crates/treehouse/src/generate/tree.rs new file mode 100644 index 0000000..9fc77aa --- /dev/null +++ b/crates/treehouse/src/generate/tree.rs @@ -0,0 +1,111 @@ +use anyhow::{ensure, Context}; +use handlebars::Handlebars; +use serde::Serialize; +use tracing::{info_span, instrument}; + +use crate::{ + dirs::Dirs, + generate::BaseTemplateData, + html::{breadcrumbs::breadcrumbs_to_html, tree}, + sources::Sources, + state::FileId, +}; + +#[derive(Serialize)] +struct Page { + title: String, + thumbnail: Option, + scripts: Vec, + styles: Vec, + breadcrumbs: String, + tree_path: Option, + tree: String, +} + +#[derive(Serialize)] +struct Thumbnail { + url: String, + alt: Option, +} + +#[derive(Serialize)] +struct PageTemplateData<'a> { + #[serde(flatten)] + base: &'a BaseTemplateData<'a>, + page: Page, +} + +#[instrument(skip(sources, dirs, handlebars))] +pub fn generate( + sources: &Sources, + dirs: &Dirs, + handlebars: &Handlebars, + file_id: FileId, +) -> anyhow::Result { + let breadcrumbs = breadcrumbs_to_html(&sources.config, &sources.navigation_map, file_id); + + let roots = sources + .treehouse + .roots + .get(&file_id) + .expect("tree should have been added to the treehouse"); + + let tree = { + let _span = info_span!("generate_tree::root_to_html").entered(); + let renderer = tree::Renderer { + sources, + dirs, + file_id, + }; + let mut tree = String::new(); + renderer.root(&mut tree); + tree + }; + + let template_data = PageTemplateData { + base: &BaseTemplateData::new(sources), + page: Page { + title: roots.attributes.title.clone(), + thumbnail: roots + .attributes + .thumbnail + .as_ref() + .map(|thumbnail| Thumbnail { + url: sources.config.pic_url(&*dirs.pic, &thumbnail.id), + alt: thumbnail.alt.clone(), + }), + scripts: roots.attributes.scripts.clone(), + styles: roots.attributes.styles.clone(), + breadcrumbs, + tree_path: sources.treehouse.tree_path(file_id).map(|s| s.to_string()), + tree, + }, + }; + let template_name = roots + .attributes + .template + .clone() + .unwrap_or_else(|| "_tree.hbs".into()); + + ensure!( + handlebars.has_template(&template_name), + "template {template_name} does not exist" + ); + + let _span = info_span!("handlebars::render").entered(); + handlebars + .render(&template_name, &template_data) + .context("template rendering failed") +} + +pub fn generate_or_error( + sources: &Sources, + dirs: &Dirs, + handlebars: &Handlebars, + file_id: FileId, +) -> String { + match generate(sources, dirs, handlebars, file_id) { + Ok(html) => html, + Err(error) => format!("error: {error:?}"), + } +} diff --git a/src/history.rs b/crates/treehouse/src/history.rs similarity index 100% rename from src/history.rs rename to crates/treehouse/src/history.rs diff --git a/src/html.rs b/crates/treehouse/src/html.rs similarity index 100% rename from src/html.rs rename to crates/treehouse/src/html.rs diff --git a/src/html/breadcrumbs.rs b/crates/treehouse/src/html/breadcrumbs.rs similarity index 100% rename from src/html/breadcrumbs.rs rename to crates/treehouse/src/html/breadcrumbs.rs diff --git a/src/html/djot.rs b/crates/treehouse/src/html/djot.rs similarity index 94% rename from src/html/djot.rs rename to crates/treehouse/src/html/djot.rs index 8ce3b11..582aae4 100644 --- a/src/html/djot.rs +++ b/crates/treehouse/src/html/djot.rs @@ -106,6 +106,21 @@ impl<'a> Writer<'a> { range: Range, out: &mut String, ) -> std::fmt::Result { + if let Event::Start(Container::Footnote { label: _ }, ..) = e { + self.diagnostics.push(Diagnostic { + severity: Severity::Error, + code: Some("djot".into()), + message: "Djot footnotes are not supported".into(), + labels: vec![Label { + style: LabelStyle::Primary, + file_id: self.renderer.file_id, + range: range.clone(), + message: "".into(), + }], + notes: vec![], + }) + } + if matches!(&e, Event::Start(Container::LinkDefinition { .. }, ..)) { self.ignore_next_event = true; return Ok(()); @@ -148,7 +163,7 @@ impl<'a> Writer<'a> { } => { out.push_str(" 1 { - write!(out, r#" start="{start}""#)?; + write!(out, r#" start="{}""#, start)?; } if let Some(ty) = match numbering { Decimal => None, @@ -157,7 +172,7 @@ impl<'a> Writer<'a> { RomanLower => Some('i'), RomanUpper => Some('I'), } { - write!(out, r#" type="{ty}""#)?; + write!(out, r#" type="{}""#, ty)?; } } } @@ -167,7 +182,7 @@ impl<'a> Writer<'a> { } Container::DescriptionList => out.push_str(" out.push_str(" out.push_str(label), + Container::Footnote { .. } => unreachable!(), Container::Table => out.push_str(" out.push_str(" {} @@ -178,7 +193,7 @@ impl<'a> Writer<'a> { } out.push_str(" write!(out, " write!(out, " out.push_str(" out.push_str(" out.push_str(" Writer<'a> { .into_iter() .filter(|(a, _)| !(*a == "class" || a.starts_with(':'))) { - write!(out, r#" {key}=""#)?; + write!(out, r#" {}=""#, key)?; value.parts().for_each(|part| write_attr(part, out)); out.push('"'); } @@ -323,7 +338,7 @@ impl<'a> Writer<'a> { Alignment::Center => "center", Alignment::Right => "right", }; - write!(out, r#" style="text-align: {a};">"#)?; + write!(out, r#" style="text-align: {};">"#, a)?; } Container::CodeBlock { language } => { if language.is_empty() { @@ -429,7 +444,7 @@ impl<'a> Writer<'a> { } Container::DescriptionList => out.push_str(""), Container::DescriptionDetails => out.push_str(""), - Container::Footnote { label } => out.push_str(label), + Container::Footnote { .. } => unreachable!(), Container::Table => out.push_str(""), Container::TableRow { .. } => out.push_str(""), Container::Section { .. } => {} @@ -440,7 +455,7 @@ impl<'a> Writer<'a> { } out.push_str("

"); } - Container::Heading { level, .. } => write!(out, "")?, + Container::Heading { level, .. } => write!(out, "", level)?, Container::TableCell { head: false, .. } => out.push_str(""), Container::TableCell { head: true, .. } => out.push_str(""), Container::Caption => out.push_str(""), @@ -522,8 +537,19 @@ impl<'a> Writer<'a> { Raw::Html => out.push_str(s), Raw::Other => {} }, - Event::FootnoteReference(label) => { - out.push_str(label); + Event::FootnoteReference(_label) => { + self.diagnostics.push(Diagnostic { + severity: Severity::Error, + code: Some("djot".into()), + message: "Djot footnotes are unsupported".into(), + labels: vec![Label { + style: LabelStyle::Primary, + file_id: self.renderer.file_id, + range, + message: "".into(), + }], + notes: vec![], + }); } Event::Symbol(sym) => { if let Some(vpath) = self.renderer.config.emoji.get(sym.as_ref()) { @@ -598,7 +624,7 @@ impl<'a> Writer<'a> { } out.push_str(" Some("""), _ => None, } - .is_some_and(|s| { + .map_or(false, |s| { ent = s; true }) diff --git a/src/html/highlight.rs b/crates/treehouse/src/html/highlight.rs similarity index 100% rename from src/html/highlight.rs rename to crates/treehouse/src/html/highlight.rs diff --git a/src/html/highlight/compiled.rs b/crates/treehouse/src/html/highlight/compiled.rs similarity index 100% rename from src/html/highlight/compiled.rs rename to crates/treehouse/src/html/highlight/compiled.rs diff --git a/src/html/highlight/tokenize.rs b/crates/treehouse/src/html/highlight/tokenize.rs similarity index 100% rename from src/html/highlight/tokenize.rs rename to crates/treehouse/src/html/highlight/tokenize.rs diff --git a/src/html/navmap.rs b/crates/treehouse/src/html/navmap.rs similarity index 100% rename from src/html/navmap.rs rename to crates/treehouse/src/html/navmap.rs diff --git a/src/html/tree.rs b/crates/treehouse/src/html/tree.rs similarity index 99% rename from src/html/tree.rs rename to crates/treehouse/src/html/tree.rs index a574e39..7453bb5 100644 --- a/src/html/tree.rs +++ b/crates/treehouse/src/html/tree.rs @@ -1,6 +1,7 @@ use std::fmt::Write; use chrono::{DateTime, Utc}; +use treehouse_format::pull::BranchKind; use crate::{ config::Config, @@ -10,9 +11,7 @@ use crate::{ state::{FileId, Treehouse}, tree::{ attributes::{Content, Stage, Visibility}, - mini_template, - pull::BranchKind, - SemaBranchId, + mini_template, SemaBranchId, }, vfs::{self, VPath, VPathBuf}, }; diff --git a/src/import_map.rs b/crates/treehouse/src/import_map.rs similarity index 100% rename from src/import_map.rs rename to crates/treehouse/src/import_map.rs diff --git a/src/lib.rs b/crates/treehouse/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/treehouse/src/lib.rs diff --git a/src/main.rs b/crates/treehouse/src/main.rs similarity index 100% rename from src/main.rs rename to crates/treehouse/src/main.rs diff --git a/src/parse.rs b/crates/treehouse/src/parse.rs similarity index 85% rename from src/parse.rs rename to crates/treehouse/src/parse.rs index e24bcbe..56fe2f4 100644 --- a/src/parse.rs +++ b/crates/treehouse/src/parse.rs @@ -2,11 +2,9 @@ use std::{ops::Range, str::FromStr}; use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity}; use tracing::instrument; +use treehouse_format::ast::Roots; -use crate::{ - state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse}, - tree::{self, ast::Roots}, -}; +use crate::state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse}; pub struct ErrorsEmitted; @@ -15,7 +13,7 @@ pub fn parse_tree_with_diagnostics( file_id: FileId, input: &str, ) -> Result>> { - Roots::parse(&mut tree::pull::Parser { input, position: 0 }).map_err(|error| { + Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| { vec![Diagnostic { severity: Severity::Error, code: Some("tree".into()), diff --git a/src/paths.rs b/crates/treehouse/src/paths.rs similarity index 100% rename from src/paths.rs rename to crates/treehouse/src/paths.rs diff --git a/src/sources.rs b/crates/treehouse/src/sources.rs similarity index 85% rename from src/sources.rs rename to crates/treehouse/src/sources.rs index 9e0938a..56feac4 100644 --- a/src/sources.rs +++ b/crates/treehouse/src/sources.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::ControlFlow}; use anyhow::{anyhow, Context}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -use tracing::{error, info_span, instrument}; +use tracing::{info_span, instrument}; use crate::{ config::Config, @@ -66,19 +66,14 @@ fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result { let mut parsed_trees = HashMap::new(); let mut paths = vec![]; - let mut doc_paths = vec![]; vfs::walk_dir_rec(&*dirs.content, VPath::ROOT, &mut |path| { - match path.extension() { - Some("tree") => paths.push(path.to_owned()), - Some("dj") => doc_paths.push(path.to_owned()), - _ => (), + if path.extension() == Some("tree") { + paths.push(path.to_owned()); } ControlFlow::Continue(()) }); - // Trees - // NOTE: Sources are filled in later; they can be left out until a call to report_diagnostics. let file_ids: Vec<_> = paths .iter() @@ -137,18 +132,5 @@ fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result { report_diagnostics(&treehouse, &diagnostics)?; - // Docs - - for path in doc_paths { - if let Some(input) = - vfs::query::(&dirs.content, &path).and_then(|c| c.string().ok()) - { - let file_id = treehouse.add_file(path.clone(), Source::Other(input)); - treehouse.files_by_doc_path.insert(path, file_id); - } else { - error!("doc {path} does not exist in content directory even though it was enumerated via walk_dir_rec"); - } - } - Ok(treehouse) } diff --git a/src/state.rs b/crates/treehouse/src/state.rs similarity index 97% rename from src/state.rs rename to crates/treehouse/src/state.rs index ab98670..9a592b9 100644 --- a/src/state.rs +++ b/crates/treehouse/src/state.rs @@ -66,8 +66,7 @@ pub struct FileId(usize); /// Treehouse compilation context. pub struct Treehouse { pub files: Vec, - pub files_by_tree_path: HashMap, // trees only - pub files_by_doc_path: HashMap, // docs only + pub files_by_tree_path: HashMap, pub feeds_by_name: HashMap, pub tree: SemaTree, @@ -84,7 +83,6 @@ impl Treehouse { Self { files: vec![], files_by_tree_path: HashMap::new(), - files_by_doc_path: HashMap::new(), feeds_by_name: HashMap::new(), tree: SemaTree::default(), diff --git a/src/tree.rs b/crates/treehouse/src/tree.rs similarity index 94% rename from src/tree.rs rename to crates/treehouse/src/tree.rs index c5a62e1..6c3bd9d 100644 --- a/src/tree.rs +++ b/crates/treehouse/src/tree.rs @@ -1,23 +1,20 @@ -pub mod ast; pub mod attributes; -pub mod feed; pub mod mini_template; -pub mod pull; use std::ops::Range; use attributes::Timestamps; use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity}; use tracing::instrument; +use treehouse_format::{ + ast::{Branch, Roots}, + pull::BranchKind, +}; use crate::{ config::Config, state::{toml_error_to_diagnostic, FileId, Source, TomlError, Treehouse}, - tree::{ - ast::{Branch, Roots}, - attributes::{Attributes, Content}, - pull::BranchKind, - }, + tree::attributes::{Attributes, Content}, }; use self::attributes::RootAttributes; @@ -412,31 +409,3 @@ impl SemaBranch { attributes } } - -#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] -pub enum ParseErrorKind { - #[error("branch kind (`+` or `-`) expected")] - BranchKindExpected, - - #[error("root branches must not be indented")] - RootIndentLevel, - - #[error("at least {expected} spaces of indentation were expected, but got {got}")] - InconsistentIndentation { got: usize, expected: usize }, - - #[error("unterminated code block")] - UnterminatedCodeBlock, -} - -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -#[error("{range:?}: {kind}")] -pub struct ParseError { - pub kind: ParseErrorKind, - pub range: Range, -} - -impl ParseErrorKind { - pub fn at(self, range: Range) -> ParseError { - ParseError { kind: self, range } - } -} diff --git a/src/tree/attributes.rs b/crates/treehouse/src/tree/attributes.rs similarity index 97% rename from src/tree/attributes.rs rename to crates/treehouse/src/tree/attributes.rs index c18fa3e..60fed8b 100644 --- a/src/tree/attributes.rs +++ b/crates/treehouse/src/tree/attributes.rs @@ -23,6 +23,10 @@ pub struct RootAttributes { #[serde(default = "default_icon")] pub icon: String, + /// Summary of the generated .html page. + #[serde(default)] + pub description: Option, + /// ID of picture attached to the page, to be used as a thumbnail. #[serde(default)] pub thumbnail: Option, @@ -46,7 +50,7 @@ pub struct RootAttributes { #[serde(default)] pub timestamps: Option, - /// When specified, this page will have a corresponding Atom feed under `feed/{feed}.atom`. + /// When specified, this page will have a corresponding Atom feed under `rss/{feed}.xml`. /// /// In feeds, top-level branches are expected to have a single heading containing the post title. /// Their children are turned into the post description diff --git a/src/tree/mini_template.rs b/crates/treehouse/src/tree/mini_template.rs similarity index 100% rename from src/tree/mini_template.rs rename to crates/treehouse/src/tree/mini_template.rs diff --git a/src/vfs.rs b/crates/treehouse/src/vfs.rs similarity index 98% rename from src/vfs.rs rename to crates/treehouse/src/vfs.rs index 772592e..7d9c6b4 100644 --- a/src/vfs.rs +++ b/crates/treehouse/src/vfs.rs @@ -168,12 +168,6 @@ impl<'a> dyn Erased<'a> + 'a { } } -impl Dir for () { - fn query(&self, _path: &VPath, _query: &mut Query) { - // Noop implementation. - } -} - impl Dir for &T where T: Dir, diff --git a/src/vfs/anchored.rs b/crates/treehouse/src/vfs/anchored.rs similarity index 100% rename from src/vfs/anchored.rs rename to crates/treehouse/src/vfs/anchored.rs diff --git a/src/vfs/asynch.rs b/crates/treehouse/src/vfs/asynch.rs similarity index 100% rename from src/vfs/asynch.rs rename to crates/treehouse/src/vfs/asynch.rs diff --git a/src/vfs/cd.rs b/crates/treehouse/src/vfs/cd.rs similarity index 100% rename from src/vfs/cd.rs rename to crates/treehouse/src/vfs/cd.rs diff --git a/src/vfs/content_cache.rs b/crates/treehouse/src/vfs/content_cache.rs similarity index 100% rename from src/vfs/content_cache.rs rename to crates/treehouse/src/vfs/content_cache.rs diff --git a/src/vfs/content_version_cache.rs b/crates/treehouse/src/vfs/content_version_cache.rs similarity index 100% rename from src/vfs/content_version_cache.rs rename to crates/treehouse/src/vfs/content_version_cache.rs diff --git a/src/vfs/edit.rs b/crates/treehouse/src/vfs/edit.rs similarity index 100% rename from src/vfs/edit.rs rename to crates/treehouse/src/vfs/edit.rs diff --git a/src/vfs/file.rs b/crates/treehouse/src/vfs/file.rs similarity index 100% rename from src/vfs/file.rs rename to crates/treehouse/src/vfs/file.rs diff --git a/src/vfs/html_canonicalize.rs b/crates/treehouse/src/vfs/html_canonicalize.rs similarity index 87% rename from src/vfs/html_canonicalize.rs rename to crates/treehouse/src/vfs/html_canonicalize.rs index bd3e772..fb3ee54 100644 --- a/src/vfs/html_canonicalize.rs +++ b/crates/treehouse/src/vfs/html_canonicalize.rs @@ -2,7 +2,6 @@ use core::fmt; use super::{Dir, Query, VPath}; -/// This Dir exists to serve as a compatibility layer for very old links that end with .html. pub struct HtmlCanonicalize { inner: T, } diff --git a/src/vfs/image_size_cache.rs b/crates/treehouse/src/vfs/image_size_cache.rs similarity index 100% rename from src/vfs/image_size_cache.rs rename to crates/treehouse/src/vfs/image_size_cache.rs diff --git a/src/vfs/mem_dir.rs b/crates/treehouse/src/vfs/mem_dir.rs similarity index 100% rename from src/vfs/mem_dir.rs rename to crates/treehouse/src/vfs/mem_dir.rs diff --git a/src/vfs/overlay.rs b/crates/treehouse/src/vfs/overlay.rs similarity index 64% rename from src/vfs/overlay.rs rename to crates/treehouse/src/vfs/overlay.rs index 470a076..13907ed 100644 --- a/src/vfs/overlay.rs +++ b/crates/treehouse/src/vfs/overlay.rs @@ -2,8 +2,6 @@ use std::fmt; use tracing::instrument; -use crate::vfs::ToDynDir; - use super::{entries, Dir, DynDir, Entries, Query, VPath, VPathBuf}; pub struct Overlay { @@ -40,18 +38,3 @@ impl fmt::Debug for Overlay { write!(f, "Overlay({:?}, {:?})", self.base, self.overlay) } } - -pub fn layered_dir(layers: &[DynDir]) -> DynDir { - match layers { - [] => ().to_dyn(), - [dir] => dir.clone(), - [left, right] => Overlay::new(left.clone(), right.clone()).to_dyn(), - [left, right, rest @ ..] => { - let mut overlay = Overlay::new(left.clone(), right.clone()); - for dir in rest { - overlay = Overlay::new(overlay.to_dyn(), dir.clone()); - } - overlay.to_dyn() - } - } -} diff --git a/src/vfs/path.rs b/crates/treehouse/src/vfs/path.rs similarity index 100% rename from src/vfs/path.rs rename to crates/treehouse/src/vfs/path.rs diff --git a/src/vfs/physical.rs b/crates/treehouse/src/vfs/physical.rs similarity index 100% rename from src/vfs/physical.rs rename to crates/treehouse/src/vfs/physical.rs diff --git a/tests/it/main.rs b/crates/treehouse/tests/it/main.rs similarity index 100% rename from tests/it/main.rs rename to crates/treehouse/tests/it/main.rs diff --git a/tests/it/vfs.rs b/crates/treehouse/tests/it/vfs.rs similarity index 100% rename from tests/it/vfs.rs rename to crates/treehouse/tests/it/vfs.rs diff --git a/tests/it/vfs/cd.rs b/crates/treehouse/tests/it/vfs/cd.rs similarity index 100% rename from tests/it/vfs/cd.rs rename to crates/treehouse/tests/it/vfs/cd.rs diff --git a/tests/it/vfs/file.rs b/crates/treehouse/tests/it/vfs/file.rs similarity index 100% rename from tests/it/vfs/file.rs rename to crates/treehouse/tests/it/vfs/file.rs diff --git a/tests/it/vfs/mount_points.rs b/crates/treehouse/tests/it/vfs/mount_points.rs similarity index 100% rename from tests/it/vfs/mount_points.rs rename to crates/treehouse/tests/it/vfs/mount_points.rs diff --git a/tests/it/vfs/physical.rs b/crates/treehouse/tests/it/vfs/physical.rs similarity index 100% rename from tests/it/vfs/physical.rs rename to crates/treehouse/tests/it/vfs/physical.rs diff --git a/tests/it/vfs_physical/test.txt b/crates/treehouse/tests/it/vfs_physical/test.txt similarity index 100% rename from tests/it/vfs_physical/test.txt rename to crates/treehouse/tests/it/vfs_physical/test.txt diff --git a/src/generate.rs b/src/generate.rs deleted file mode 100644 index 3caa73e..0000000 --- a/src/generate.rs +++ /dev/null @@ -1,128 +0,0 @@ -mod atom; -mod dir_helper; -mod doc; -mod include_static_helper; -mod simple_template; -mod tree; - -use std::{ops::ControlFlow, sync::Arc}; - -use atom::FeedDir; -use chrono::{DateTime, Utc}; -use dir_helper::DirHelper; -use handlebars::{handlebars_helper, Handlebars}; -use include_static_helper::IncludeStaticHelper; -use serde::Serialize; -use tracing::{error, info_span, instrument}; - -use crate::{ - config::Config, - dirs::Dirs, - fun::seasons::Season, - generate::{ - doc::DocDir, - simple_template::SimpleTemplateDir, - tree::{DirIndex, TreehouseDir}, - }, - sources::Sources, - vfs::{ - self, layered_dir, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, HtmlCanonicalize, - MemDir, ToDynDir, VPath, VPathBuf, - }, -}; - -#[derive(Serialize)] -struct BaseTemplateData<'a> { - config: &'a Config, - import_map: String, - season: Option, - dev: bool, - feeds: Vec, -} - -impl<'a> BaseTemplateData<'a> { - fn new(sources: &'a Sources) -> Self { - Self { - config: &sources.config, - import_map: serde_json::to_string_pretty(&sources.import_map) - .expect("import map should be serializable to JSON"), - season: Season::current(), - dev: cfg!(debug_assertions), - feeds: sources.treehouse.feeds_by_name.keys().cloned().collect(), - } - } -} - -fn create_handlebars(site: &str, static_: DynDir) -> Handlebars<'static> { - let mut handlebars = Handlebars::new(); - - handlebars_helper!(cat: |a: String, b: String| a + &b); - handlebars_helper!(iso_date: |d: DateTime| d.format("%F").to_string()); - - handlebars.register_helper("cat", Box::new(cat)); - handlebars.register_helper("iso_date", Box::new(iso_date)); - handlebars.register_helper("asset", Box::new(DirHelper::new(site, static_.clone()))); - handlebars.register_helper( - "include_static", - Box::new(IncludeStaticHelper::new(static_)), - ); - - handlebars -} - -#[instrument(skip(handlebars))] -fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) { - vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| { - if path.extension() == Some("hbs") { - if let Some(content) = vfs::query::(dir, path).and_then(|c| c.string().ok()) { - let _span = info_span!("register_template", ?path).entered(); - if let Err(err) = handlebars.register_template_string(path.as_str(), content) { - error!("in template: {err}"); - } - } - } - ControlFlow::Continue(()) - }); -} - -pub fn target(dirs: Arc, sources: Arc) -> DynDir { - let mut handlebars = create_handlebars(&sources.config.site, dirs.static_.clone()); - load_templates(&mut handlebars, &dirs.template); - let handlebars = Arc::new(handlebars); - - let mut root = MemDir::new(); - root.add( - VPath::new("feed"), - ContentCache::new(FeedDir::new( - dirs.clone(), - sources.clone(), - handlebars.clone(), - )) - .to_dyn(), - ); - root.add(VPath::new("static"), dirs.static_.clone()); - root.add( - VPath::new("robots.txt"), - Cd::new(dirs.static_.clone(), VPathBuf::new("robots.txt")).to_dyn(), - ); - - let dir_index = DirIndex::new(sources.treehouse.files_by_tree_path.keys().map(|x| &**x)); - let treehouse_dir = layered_dir(&[ - TreehouseDir::new(dirs.clone(), sources.clone(), handlebars.clone(), dir_index).to_dyn(), - DocDir { - sources: sources.clone(), - dirs, - handlebars: handlebars.clone(), - } - .to_dyn(), - SimpleTemplateDir::new(sources.clone(), handlebars.clone()).to_dyn(), - ]); - - let tree_view = ContentCache::new(treehouse_dir); - tree_view.warm_up(); - let tree_view = HtmlCanonicalize::new(tree_view); - - layered_dir(&[tree_view.to_dyn(), root.to_dyn()]) - .anchored_at(VPath::ROOT.to_owned()) - .to_dyn() -} diff --git a/src/generate/doc.rs b/src/generate/doc.rs deleted file mode 100644 index eda596f..0000000 --- a/src/generate/doc.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::{ - fmt::{self}, - sync::Arc, -}; - -use anyhow::Context; -use chrono::{DateTime, Utc}; -use handlebars::Handlebars; -use serde::{Deserialize, Serialize}; -use tracing::{error, instrument}; - -use crate::{ - dirs::Dirs, - generate::BaseTemplateData, - html::djot, - sources::Sources, - state::{report_diagnostics, toml_error_to_diagnostic, FileId, TomlError}, - tree::{attributes::Picture, feed}, - vfs::{Content, Dir, Query, VPath}, -}; - -#[derive(Default, Deserialize)] -struct Attributes { - /// Template to use for generating the page. - /// Defaults to `_tree.hbs`. - #[serde(default)] - template: Option, - - /// Title of the page. - /// The only necessary field. - /// Unlike tree pages, doc pages always have titles. - title: String, - - /// ID of picture attached to the page, to be used as a thumbnail. - #[serde(default)] - thumbnail: Option, - - /// Additional scripts to load into to the page. - /// These are relative to the /static/js directory. - #[serde(default)] - scripts: Vec, - - /// Additional styles to load into to the page. - /// These are relative to the /static/css directory. - #[serde(default)] - styles: Vec, - - /// If not `None`, the page will get an additional 'feed' field in template data, containing - /// updates from the news feed of the specified name. - #[serde(default)] - include_feed: Option, -} - -#[derive(Deserialize)] -struct IncludeFeed { - /// The name of the feed (within the treehouse database.) - name: String, - - /// The title of the feed shown on the page. - title: String, -} - -#[derive(Serialize)] -struct Page { - title: String, - thumbnail: Option, - scripts: Vec, - styles: Vec, - tree_path: String, - doc: String, - feed: Option, -} - -#[derive(Serialize)] -struct Thumbnail { - url: String, - alt: Option, -} - -#[derive(Serialize)] -struct Feed { - title: String, - entries: Vec, -} - -#[derive(Serialize)] -struct Entry { - title: String, - url: String, - updated: DateTime, - categories: Vec, -} - -#[derive(Serialize)] -struct PageTemplateData<'a> { - #[serde(flatten)] - base: &'a BaseTemplateData<'a>, - page: Page, -} - -pub struct DocDir { - pub sources: Arc, - pub dirs: Arc, - - pub handlebars: Arc>, -} - -impl DocDir { - #[instrument("DocDir::content", skip(self))] - pub fn content(&self, path: &VPath) -> Option { - if let Some(file_id) = self - .sources - .treehouse - .files_by_doc_path - .get(&path.with_extension("dj")) - { - let source = self.sources.treehouse.source(*file_id).input(); - return Some(Content::new( - "text/html", - self.generate(*file_id, path, source).into_bytes(), - )); - } - - None - } - - fn generate(&self, file_id: FileId, path: &VPath, source: &str) -> String { - let (front_matter, text) = source.split_once("+++").unwrap_or(("", source)); - let attributes: Attributes = - toml_edit::de::from_str(front_matter).unwrap_or_else(|error| { - _ = report_diagnostics( - &self.sources.treehouse, - &[toml_error_to_diagnostic(TomlError { - message: error.message().to_owned(), - span: error.span(), - file_id, - input_range: 0..front_matter.len(), - })], - ); - Attributes::default() - }); - - let events: Vec<_> = jotdown::Parser::new(text).into_offset_iter().collect(); - let mut rendered_markup = String::new(); - let render_diagnostics = djot::Renderer { - config: &self.sources.config, - dirs: &self.dirs, - treehouse: &self.sources.treehouse, - file_id, - page_id: path.to_string(), - } - .render(&events, &mut rendered_markup); - - let template_name = attributes.template.as_deref().unwrap_or("_doc.hbs"); - - let render_result = self - .handlebars - .render( - template_name, - &PageTemplateData { - base: &BaseTemplateData::new(&self.sources), - page: Page { - title: attributes.title, - thumbnail: attributes.thumbnail.map(|pic| Thumbnail { - url: self.sources.config.pic_url(&*self.dirs.pic, &pic.id), - alt: pic.alt, - }), - scripts: attributes.scripts, - styles: attributes.styles, - tree_path: path.to_string(), - doc: rendered_markup, - feed: attributes.include_feed.and_then(|feed| { - Some(Feed { - title: feed.title, - entries: self - .generate_feed(&feed.name) - .inspect_err(|e| { - error!("generating feed for {path} failed: {e}") - }) - .ok()?, - }) - }), - }, - }, - ) - .context("template rendering failed"); - match render_result { - Ok(rendered) => rendered, - Err(error) => format!("{error:#?}"), - } - } - - fn generate_feed(&self, name: &str) -> anyhow::Result> { - let file_id = *self - .sources - .treehouse - .feeds_by_name - .get(name) - .context("no feed with the given name")?; - let roots = &self.sources.treehouse.roots[&file_id]; - - Ok(roots - .branches - .iter() - .flat_map(|&branch_id| { - let branch = self.sources.treehouse.tree.branch(branch_id); - - let text = &self.sources.treehouse.source(file_id).input()[branch.content.clone()]; - let parsed = feed::parse_entry( - &self.sources, - &self.dirs, - file_id, - jotdown::Parser::new(text), - ); - - let updated = branch - .attributes - .timestamp() - .unwrap_or(DateTime::UNIX_EPOCH); // if you see the Unix epoch... oops - - parsed.link.map(|url| Entry { - updated, - url, - title: parsed.title.unwrap_or_else(|| "untitled".into()), - categories: branch.attributes.tags.clone(), - }) - }) - .collect()) - } -} - -impl Dir for DocDir { - fn query(&self, path: &VPath, query: &mut Query) { - query.try_provide(|| self.content(path)); - } -} - -impl fmt::Debug for DocDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("DocDir") - } -} diff --git a/src/generate/simple_template.rs b/src/generate/simple_template.rs deleted file mode 100644 index 879485b..0000000 --- a/src/generate/simple_template.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{fmt, sync::Arc}; - -use anyhow::Context; -use handlebars::Handlebars; -use tracing::instrument; - -use crate::{ - sources::Sources, - vfs::{Content, Dir, Query, VPath}, -}; - -use super::BaseTemplateData; - -pub struct SimpleTemplateDir { - sources: Arc, - handlebars: Arc>, -} - -impl SimpleTemplateDir { - pub fn new(sources: Arc, handlebars: Arc>) -> Self { - Self { - sources, - handlebars, - } - } - - #[instrument(name = "simple_template::generate", skip(self))] - fn generate(&self, template_name: &str) -> anyhow::Result { - let base_template_data = BaseTemplateData::new(&self.sources); - self.handlebars - .render(template_name, &base_template_data) - .context("failed to render template") - } - - fn generate_or_error(&self, template_name: &str) -> String { - match self.generate(template_name) { - Ok(html) => html, - Err(error) => format!("error: {error:?}"), - } - } - - #[instrument("TreehouseDir::content", skip(self))] - fn content(&self, path: &VPath) -> Option { - if path.file_name().is_some_and(|s| !s.starts_with('_')) { - let template_name = path.with_extension("hbs"); - if self.handlebars.has_template(template_name.as_str()) { - return Some(Content::new( - "text/html", - self.generate_or_error(template_name.as_str()).into(), - )); - } - } - - None - } -} - -impl Dir for SimpleTemplateDir { - fn query(&self, path: &VPath, query: &mut Query) { - // NOTE: An implementation of Entries is not currently provided, because SimpleTemplateDir - // isn't used enough to need one. - query.try_provide(|| self.content(path)); - } -} - -impl fmt::Debug for SimpleTemplateDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("SimpleTemplateDir") - } -} diff --git a/src/generate/tree.rs b/src/generate/tree.rs deleted file mode 100644 index 9ecd14c..0000000 --- a/src/generate/tree.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::{collections::HashMap, fmt, sync::Arc}; - -use anyhow::{ensure, Context}; -use handlebars::Handlebars; -use serde::Serialize; -use tracing::{info_span, instrument}; - -use crate::{ - dirs::Dirs, - generate::{simple_template, BaseTemplateData}, - html::{breadcrumbs::breadcrumbs_to_html, tree}, - sources::Sources, - state::FileId, - vfs::{self, Content, Dir, Entries, VPath, VPathBuf}, -}; - -#[derive(Serialize)] -struct Page { - title: String, - thumbnail: Option, - scripts: Vec, - styles: Vec, - breadcrumbs: String, - tree_path: Option, - tree: String, -} - -#[derive(Serialize)] -struct Thumbnail { - url: String, - alt: Option, -} - -#[derive(Serialize)] -struct PageTemplateData<'a> { - #[serde(flatten)] - base: &'a BaseTemplateData<'a>, - page: Page, -} - -#[instrument(skip(sources, dirs, handlebars))] -pub fn generate( - sources: &Sources, - dirs: &Dirs, - handlebars: &Handlebars, - file_id: FileId, -) -> anyhow::Result { - let breadcrumbs = breadcrumbs_to_html(&sources.config, &sources.navigation_map, file_id); - - let roots = sources - .treehouse - .roots - .get(&file_id) - .expect("tree should have been added to the treehouse"); - - let tree = { - let _span = info_span!("generate_tree::root_to_html").entered(); - let renderer = tree::Renderer { - sources, - dirs, - file_id, - }; - let mut tree = String::new(); - renderer.root(&mut tree); - tree - }; - - let template_data = PageTemplateData { - base: &BaseTemplateData::new(sources), - page: Page { - title: roots.attributes.title.clone(), - thumbnail: roots - .attributes - .thumbnail - .as_ref() - .map(|thumbnail| Thumbnail { - url: sources.config.pic_url(&*dirs.pic, &thumbnail.id), - alt: thumbnail.alt.clone(), - }), - scripts: roots.attributes.scripts.clone(), - styles: roots.attributes.styles.clone(), - breadcrumbs, - tree_path: sources.treehouse.tree_path(file_id).map(|s| s.to_string()), - tree, - }, - }; - let template_name = roots - .attributes - .template - .clone() - .unwrap_or_else(|| "_tree.hbs".into()); - - ensure!( - handlebars.has_template(&template_name), - "template {template_name} does not exist" - ); - - let _span = info_span!("handlebars::render").entered(); - handlebars - .render(&template_name, &template_data) - .context("template rendering failed") -} - -pub fn generate_or_error( - sources: &Sources, - dirs: &Dirs, - handlebars: &Handlebars, - file_id: FileId, -) -> String { - match generate(sources, dirs, handlebars, file_id) { - Ok(html) => html, - Err(error) => format!("error: {error:?}"), - } -} - -pub struct TreehouseDir { - dirs: Arc, - sources: Arc, - handlebars: Arc>, - dir_index: DirIndex, -} - -impl TreehouseDir { - pub fn new( - dirs: Arc, - sources: Arc, - handlebars: Arc>, - dir_index: DirIndex, - ) -> Self { - Self { - dirs, - sources, - handlebars, - dir_index, - } - } - - #[instrument("TreehouseDir::dir", skip(self))] - fn dir(&self, path: &VPath) -> Vec { - // NOTE: This does not include simple templates, because that's not really needed right now. - - let mut index = &self.dir_index; - for component in path.segments() { - if let Some(child) = index.children.get(component) { - index = child; - } else { - // There cannot possibly be any entries under an invalid path. - // Bail early. - return vec![]; - } - } - - index - .children - .values() - .map(|child| child.full_path.clone()) - .collect() - } - - #[instrument("TreehouseDir::content", skip(self))] - fn content(&self, path: &VPath) -> Option { - let path = if path.is_root() { - VPath::new_const("index") - } else { - path - }; - - self.sources - .treehouse - .files_by_tree_path - .get(path) - .map(|&file_id| { - Content::new( - "text/html", - generate_or_error(&self.sources, &self.dirs, &self.handlebars, file_id).into(), - ) - }) - } -} - -impl Dir for TreehouseDir { - fn query(&self, path: &VPath, query: &mut vfs::Query) { - query.provide(|| Entries(self.dir(path))); - query.try_provide(|| self.content(path)); - } -} - -impl fmt::Debug for TreehouseDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TreehouseDir") - } -} - -/// Acceleration structure for `dir` operations on [`TreehouseDir`]s. -#[derive(Debug, Default)] -pub struct DirIndex { - full_path: VPathBuf, - children: HashMap, -} - -impl DirIndex { - #[instrument(name = "DirIndex::new", skip(paths))] - pub fn new<'a>(paths: impl Iterator) -> Self { - let mut root = DirIndex::default(); - - for path in paths { - let mut parent = &mut root; - let mut full_path = VPath::ROOT.to_owned(); - for segment in path.segments() { - full_path.push(segment); - let child = parent - .children - .entry(segment.to_owned()) - .or_insert_with(|| DirIndex { - full_path: full_path.clone(), - children: HashMap::new(), - }); - parent = child; - } - } - - root - } -} diff --git a/src/tree/feed.rs b/src/tree/feed.rs deleted file mode 100644 index b8c0d77..0000000 --- a/src/tree/feed.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{ - dirs::Dirs, - html::djot::{self, resolve_link}, - sources::Sources, - state::FileId, -}; - -#[derive(Debug, Clone)] -pub struct ParsedEntry { - pub title: Option, - pub link: Option, -} - -pub fn parse_entry( - sources: &Sources, - dirs: &Dirs, - file_id: FileId, - parser: jotdown::Parser, -) -> ParsedEntry { - let mut parser = parser.into_offset_iter(); - while let Some((event, span)) = parser.next() { - if let jotdown::Event::Start(jotdown::Container::Heading { .. }, _attrs) = &event { - let mut events = vec![(event, span)]; - for (event, span) in parser.by_ref() { - // To my knowledge headings cannot nest, so it's okay not keeping a stack here. - let is_heading = matches!( - event, - jotdown::Event::End(jotdown::Container::Heading { .. }) - ); - events.push((event, span)); - if is_heading { - break; - } - } - - let title_events: Vec<_> = events - .iter() - .filter(|(event, _)| { - !matches!( - event, - // A little repetitive, but I don't mind. - // The point of this is not to include extra

and in the link text, - // but preserve other formatting such as bold, italic, code, etc. - jotdown::Event::Start( - jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. }, - _ - ) | jotdown::Event::End( - jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. } - ) - ) - }) - .cloned() - .collect(); - let mut title = String::new(); - let _render_diagnostics = djot::Renderer { - config: &sources.config, - dirs, - treehouse: &sources.treehouse, - file_id, - - // How. Just, stop. - page_id: "liquidex-you-reeeeeal-dummy".into(), - } - .render(&title_events, &mut title); - - let link = events.iter().find_map(|(event, _)| { - if let jotdown::Event::Start(jotdown::Container::Link(link, link_type), _) = event { - Some(link_url(sources, dirs, link, *link_type)) - } else { - None - } - }); - - return ParsedEntry { - title: (!title.is_empty()).then_some(title), - link, - }; - } - } - - ParsedEntry { - title: None, - link: None, - } -} - -fn link_url(sources: &Sources, dirs: &Dirs, url: &str, link_type: jotdown::LinkType) -> String { - if let jotdown::LinkType::Span(jotdown::SpanLinkType::Unresolved) = link_type { - if let Some(url) = resolve_link(&sources.config, &sources.treehouse, dirs, url) { - return url; - } - } - url.to_owned() -} diff --git a/src/tree/lib.rs b/src/tree/lib.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/tree/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/static/character/riki/sitting.png b/static/character/riki/sitting.png deleted file mode 100644 index 316229e..0000000 Binary files a/static/character/riki/sitting.png and /dev/null differ diff --git a/static/css/base.css b/static/css/base.css index 7f32d49..0ef71d0 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -1,4 +1,4 @@ -/* Color scheme */ +/* Color scheme. */ :root { --accent-red: #fb4c9e; @@ -36,18 +36,6 @@ } } -/* Animations */ - -:root { - --transition-duration: 0.15s; -} - -@media (prefers-reduced-motion: reduce) { - :root { - --transition-duration: 0; - } -} - /* Reset things to more sensible sizing rules */ * { diff --git a/static/css/doc.css b/static/css/doc.css deleted file mode 100644 index 2832972..0000000 --- a/static/css/doc.css +++ /dev/null @@ -1,84 +0,0 @@ -main.doc { - --doc-text-width: 80ch; - - display: flex; - flex-direction: row; - align-items: start; - - & .vertical-center { - min-height: 100vh; - flex-grow: 1; - - display: flex; - flex-direction: column; - - align-items: center; - justify-content: center; - } - - & .doc-text { - padding: 1.6rem; - max-width: var(--doc-text-width); - - line-height: 1.6; - - /* I was thinking a bunch about whether documents should be justified, and it honestly - causes more awkwardness than it solves. Web pages aren't quite books, unfortunately. - - A cool feature that would help is text-wrap: pretty; but only Safari implements a nice - text layout algorithm for it. (Chrome prevents short last lines, Firefox doesn't - implement it at all) */ - - /* text-align: justify; - hyphens: auto; */ - - & p { - padding-top: 0.5lh; - padding-bottom: 0.5lh; - } - - & h2 { - margin: 0; - padding-top: 1lh; - padding-bottom: 0.5lh; - } - - & ul, - & ol { - /* Is there a better way to add spacing to the marker, other than adding whitespace? */ - list-style: "– "; - margin-top: 0; - margin-bottom: 0; - padding-bottom: 0.5lh; - } - } - - & section.feed { - max-width: 40ch; - padding: 0.8rem; - padding-top: 3.2rem; - } -} - -@media (max-width: 1500px) { - main.doc { - flex-direction: column; - align-items: center; - - & .vertical-center { - min-height: 0; - } - - & footer { - padding: 0.8rem; - } - - & section.feed { - max-width: var(--doc-text-width); - - margin-top: 2.4em; - padding: 1.6rem; - border-top: 1px solid var(--border-1); - } - } -} diff --git a/static/css/main.css b/static/css/main.css index df1456d..79a0d53 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -1,46 +1,73 @@ -/* Main layout */ +/* Lay out the main containers. */ body { - --main-min-size: 100vh; + --top-min-spacing: 40px; margin: 0; display: grid; grid-template-columns: - [left] 1fr - [right] auto; + [left] minmax( + 0, + clamp(136px, calc(100vw - (1920px - 360px - 160px)), 160px) + ) + [center] minmax(0, auto) + [right] minmax(0, calc(100vw - (1920px - 360px))); grid-template-rows: - [nav] auto - [main] minmax(var(--main-min-size), auto) - [virtual] 100vh; + [top] minmax( + clamp( + var(--top-min-spacing), + calc(100vw - (1920px - 360px - 160px)), + 128px + ), + min-content + ) + [title] minmax(9.6rem, min-content) + [main] 1fr + [bottom] min-content; } html { + /* Try to always leave a bunch of empty space at the bottom, but don't overdo it. + It's kind of awkward when you scroll to the bottom and your page just turns blank. */ + --virtual-space-ratio: 1.75; + + height: calc(100% * var(--virtual-space-ratio)); + /* Leave a bunch of space at the top when scrolling to elements. I'm honestly not sure why this is needed on and not the scrolled-to element... */ scroll-padding-top: 10vh; } -.sidebar-sticky { - grid-column: right; - grid-row: main; +body { + min-height: calc(100% / var(--virtual-space-ratio)); } -aside.sidebar { - position: sticky; - top: 0px; +.noscript { + grid-row: top; + grid-column: center; +} - max-width: 50rem; - height: 100vh; - padding: 0.8rem; +#nav-logo { + grid-row: title; + grid-column: left; - display: flex; + align-self: center; + justify-self: end; +} + +section.page-header { + grid-row: title; + grid-column: center; + + align-self: center; } main { - grid-column: left; grid-row: main; - min-width: 0; + grid-column: center / center; + + margin-right: 0.8rem; } footer { @@ -48,25 +75,27 @@ footer { grid-column: center / center; } -/* Narrower layout: sidebar is pushed to the top */ +@media (max-width: 1200px) { + main { + grid-column: left / -1; + } -@media (max-width: 1280px) { + footer { + grid-column: 1 / -1; + } +} + +@media (max-width: 450px) { body { - --main-min-size: 0; + --top-min-spacing: 0px; } - .sidebar-sticky { - grid-column: left; - grid-row: nav; - - display: flex; - justify-content: center; + section.page-header { + grid-column: 1 / -1; } - aside.sidebar { - position: relative; - height: auto; - padding: 0; + nav#nav-logo { + display: none; } } @@ -90,6 +119,16 @@ body { /* Set up typography */ +@font-face { + font-family: "RecVar"; + /* NOTE: I put the hash in here manually instead of adding the complexity of piping CSS through + Handlebars because I don't really think it's worth it for this single asset. + Other assets are referenced rarely enough that caching probably isn't gonna make too much of + an impact. + It's unlikely I'll ever update the font anyways, so eh, whatever. */ + src: url("../font/Recursive_VF_1.085.woff2?v=b3-41236e2f"); +} + body, pre, code, @@ -108,7 +147,7 @@ html { } body { - font-size: 1.6rem; + font-size: 1.4rem; } pre, @@ -144,16 +183,15 @@ input { "slnt" var(--recursive-slnt), "CRSV" var(--recursive-crsv); - font-feature-settings: - var(--recursive-simplified-f), var(--recursive-simplified-g), - var(--recursive-simplified-l), var(--recursive-simplified-r), - var(--recursive-no-serif-L-Z); + font-feature-settings: var(--recursive-simplified-f), + var(--recursive-simplified-g), var(--recursive-simplified-l), + var(--recursive-simplified-r), var(--recursive-no-serif-L-Z); } h1 { --recursive-wght: 900; - font-size: 4.8rem; + font-size: 5.6rem; font-feature-settings: var(--recursive-simplified-r) 0; } @@ -207,12 +245,6 @@ h6 { text-wrap: balance; } -/* Other classes for controlling typography */ - -.nowrap { - white-space: nowrap; -} - /* Lay out elements a bit more compactly */ p, @@ -287,21 +319,21 @@ th-literate-program { overflow: auto; } -/* Images */ +/* Also don't let images get out of hand */ img { - /* Prevent images from causing horizontal scrolling */ max-width: 100%; - height: auto; } +/* Also regarding images - make them look a bit more pretty by default */ + img.pic { border-radius: 0.6rem; margin: 0.8rem 0; } +/* Image hints for tweaking rendering */ img { - /* Hints for tweaking rendering */ &[src*="+pixel"] { image-rendering: pixelated; border-radius: 0; @@ -345,6 +377,8 @@ a:visited { color: var(--link-color-visited); } +/* Allow for some secret links */ + a.secret { color: var(--text-color); text-decoration: none; @@ -425,224 +459,32 @@ hr { color: #6c2380; } -/* Feeds */ +/* Navigation button */ -section.feed { - display: flex; - flex-direction: column; - - /* Titles */ - - & a, - & a:visited { - color: var(--text-color); - } - - & a:visited { - color: color-mix( - in srgb, - var(--background-color), - var(--text-color) 60% - ); - } - - & h1 { - --recursive-wght: 800; - font-size: 125%; - padding-top: 1.2rem; - padding-bottom: 1.2rem; - } - - & h2 { - --recursive-wght: 600; - font-size: 100%; - padding: 0; - } - - /* Articles */ - - & article { - display: flex; - flex-direction: column; - - padding-bottom: 1.2rem; - - line-height: 1.4; - - & .info { - display: flex; - flex-direction: row; - flex-wrap: wrap; - font-size: 87.5%; - - & > *:not(:first-child)::before { - content: "·"; - padding: 0 0.4rem; - } - } - - & .categories { - display: flex; - flex-direction: row; - flex-wrap: wrap; - - list-style: none; - margin: 0; - padding: 0; - - & > *::before { - content: "#"; - } - - & > *:not(:first-child)::before { - padding-left: 0.4rem; - } - } - } +#nav-logo { + width: min-content; + height: min-content; } -/* Page sidebar */ +#nav-logo .logo { + /* NOTE: Measurements in px for pixel perfection */ + width: 120px; + height: 120px; -aside.sidebar { - overflow: clip; - - & > a { - display: block; - height: min-content; - margin-top: auto; - } -} - -header.floof { - margin-top: auto; - - position: relative; - - & > img { - display: block; - min-width: 0; - object-fit: cover; - object-position: 33% 0; - } - - & > h1 { - position: absolute; - top: 3rem; - left: 3rem; - - display: flex; - flex-direction: column; - line-height: 1; - width: min-content; - - --recursive-wght: 900; - font-size: 5.6rem; - text-align: right; - - transform: skew(-5deg, -5deg); - - & .rikis { - width: max-content; - background-color: var(--text-color); - color: var(--background-color); - padding: 0.1em; - - --shadow-color: var(--accent-pink); - box-shadow: - 0.5px 0.5px 0 var(--shadow-color), - 1px 1px 0 var(--shadow-color), - 1.5px 1.5px 0 var(--shadow-color), - 2px 2px 0 var(--shadow-color), - 2.5px 2.5px 0 var(--shadow-color), - 3px 3px 0 var(--shadow-color), - 3.5px 3.5px 0 var(--shadow-color), - 4px 4px 0 var(--shadow-color); - - /* - import math - - print("box-shadow:") - x = 0 - max_x = 16 - while x < max_x: - print(f"{x}px {x}px {math.pow(x / max_x, 2) * 16}px rgba(from var(--shadow-color) r g b / {math.pow(1 - x / max_x, 3)}),") - x += 0.5 - */ - /* prettier-ignore */ - box-shadow: - 0px 0px 0.0px rgba(from var(--shadow-color) r g b / 1.0), - 0.5px 0.5px 0.015625px rgba(from var(--shadow-color) r g b / 0.909149169921875), - 1.0px 1.0px 0.0625px rgba(from var(--shadow-color) r g b / 0.823974609375), - 1.5px 1.5px 0.140625px rgba(from var(--shadow-color) r g b / 0.744293212890625), - 2.0px 2.0px 0.25px rgba(from var(--shadow-color) r g b / 0.669921875), - 2.5px 2.5px 0.390625px rgba(from var(--shadow-color) r g b / 0.600677490234375), - 3.0px 3.0px 0.5625px rgba(from var(--shadow-color) r g b / 0.536376953125), - 3.5px 3.5px 0.765625px rgba(from var(--shadow-color) r g b / 0.476837158203125), - 4.0px 4.0px 1.0px rgba(from var(--shadow-color) r g b / 0.421875), - 4.5px 4.5px 1.265625px rgba(from var(--shadow-color) r g b / 0.371307373046875), - 5.0px 5.0px 1.5625px rgba(from var(--shadow-color) r g b / 0.324951171875), - 5.5px 5.5px 1.890625px rgba(from var(--shadow-color) r g b / 0.282623291015625), - 6.0px 6.0px 2.25px rgba(from var(--shadow-color) r g b / 0.244140625), - 6.5px 6.5px 2.640625px rgba(from var(--shadow-color) r g b / 0.209320068359375), - 7.0px 7.0px 3.0625px rgba(from var(--shadow-color) r g b / 0.177978515625), - 7.5px 7.5px 3.515625px rgba(from var(--shadow-color) r g b / 0.149932861328125), - 8.0px 8.0px 4.0px rgba(from var(--shadow-color) r g b / 0.125), - 8.5px 8.5px 4.515625px rgba(from var(--shadow-color) r g b / 0.102996826171875), - 9.0px 9.0px 5.0625px rgba(from var(--shadow-color) r g b / 0.083740234375), - 9.5px 9.5px 5.640625px rgba(from var(--shadow-color) r g b / 0.067047119140625), - 10.0px 10.0px 6.25px rgba(from var(--shadow-color) r g b / 0.052734375), - 10.5px 10.5px 6.890625px rgba(from var(--shadow-color) r g b / 0.040618896484375), - 11.0px 11.0px 7.5625px rgba(from var(--shadow-color) r g b / 0.030517578125), - 11.5px 11.5px 8.265625px rgba(from var(--shadow-color) r g b / 0.022247314453125), - 12.0px 12.0px 9.0px rgba(from var(--shadow-color) r g b / 0.015625), - 12.5px 12.5px 9.765625px rgba(from var(--shadow-color) r g b / 0.010467529296875), - 13.0px 13.0px 10.5625px rgba(from var(--shadow-color) r g b / 0.006591796875), - 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625), - 14.0px 14.0px 12.25px rgba(from var(--shadow-color) r g b / 0.001953125), - 14.5px 14.5px 13.140625px rgba(from var(--shadow-color) r g b / 0.000823974609375), - 15.0px 15.0px 14.0625px rgba(from var(--shadow-color) r g b / 0.000244140625), - 15.5px 15.5px 15.015625px rgba(from var(--shadow-color) r g b / 3.0517578125e-05) - ; - } - - & .fluffy-little-house { - display: flex; - flex-direction: column; - - background-color: var(--background-color); - width: min-content; - align-self: end; - - padding-left: 0.8rem; - padding-right: 0.8rem; - padding-top: 0.4rem; - - z-index: -1; - - color: var(--text-color); - - & .adjectives { - --recursive-wght: 800; - font-size: 1.6rem; - padding-top: 0.6rem; - } - - & .house { - margin-top: -0.2em; - font-size: 3.6rem; - } - } - } + display: block; + opacity: 100%; + color: var(--text-color); } /* Navigation header (contains page title & breadcrumbs) */ h1.page-title { - --recursive-wght: 900; + --recursive-wght: 850; - line-height: 1.2; - padding-top: 3lh; - padding-bottom: 0.5lh; + margin-top: 0.32rem; + margin-bottom: 0.32rem; + margin-left: 3.6rem; + font-size: 4rem; & a { color: var(--text-color); @@ -663,18 +505,7 @@ h1.page-title { } } -@media (max-width: 1280px) { - h1.page-title { - padding-top: 0.25lh; - } -} - -@media (max-width: 700px) { - h1.page-title { - font-size: 4rem; - } -} - +/* Style badges */ span.badge { --recursive-wght: 800; --recursive-mono: 1; @@ -701,9 +532,11 @@ span.badge { /* Style the footer */ footer { - width: 100%; - max-width: 90ch; - padding: 1.6rem 0.8rem; + padding-left: 1.6rem; + padding-right: 1.6rem; + + margin-top: 6.4rem; + padding-bottom: 6.4rem; display: flex; flex-direction: row; @@ -776,9 +609,9 @@ dialog[open] { /* Style emojis to be readable */ img[data-cast~="emoji"] { - max-width: 1.3125em; - max-height: 1.3125em; - vertical-align: text-bottom; + max-width: 1.5em; + max-height: 1.5em; + vertical-align: bottom; object-fit: contain; } diff --git a/static/css/page/index.css b/static/css/page/index.css index a6efd39..ac95a5a 100644 --- a/static/css/page/index.css +++ b/static/css/page/index.css @@ -1,3 +1,98 @@ +h1.page-title { + --recursive-wght: 900; + font-size: 5.6rem; + + display: flex; + flex-direction: column; + line-height: 1; + width: min-content; + + transform: skew(-5deg, -5deg); + + & .rikis { + width: max-content; + background-color: var(--text-color); + color: var(--background-color); + padding: 0.1em; + + --shadow-color: var(--accent-pink); + box-shadow: + 0.5px 0.5px 0 var(--shadow-color), + 1px 1px 0 var(--shadow-color), + 1.5px 1.5px 0 var(--shadow-color), + 2px 2px 0 var(--shadow-color), + 2.5px 2.5px 0 var(--shadow-color), + 3px 3px 0 var(--shadow-color), + 3.5px 3.5px 0 var(--shadow-color), + 4px 4px 0 var(--shadow-color); + + /* + import math + + print("box-shadow:") + x = 0 + max_x = 16 + while x < max_x: + print(f"{x}px {x}px {math.pow(x / max_x, 2) * 16}px rgba(from var(--shadow-color) r g b / {math.pow(1 - x / max_x, 3)}),") + x += 0.5 + */ + /* prettier-ignore */ + box-shadow: + 0px 0px 0.0px rgba(from var(--shadow-color) r g b / 1.0), + 0.5px 0.5px 0.015625px rgba(from var(--shadow-color) r g b / 0.909149169921875), + 1.0px 1.0px 0.0625px rgba(from var(--shadow-color) r g b / 0.823974609375), + 1.5px 1.5px 0.140625px rgba(from var(--shadow-color) r g b / 0.744293212890625), + 2.0px 2.0px 0.25px rgba(from var(--shadow-color) r g b / 0.669921875), + 2.5px 2.5px 0.390625px rgba(from var(--shadow-color) r g b / 0.600677490234375), + 3.0px 3.0px 0.5625px rgba(from var(--shadow-color) r g b / 0.536376953125), + 3.5px 3.5px 0.765625px rgba(from var(--shadow-color) r g b / 0.476837158203125), + 4.0px 4.0px 1.0px rgba(from var(--shadow-color) r g b / 0.421875), + 4.5px 4.5px 1.265625px rgba(from var(--shadow-color) r g b / 0.371307373046875), + 5.0px 5.0px 1.5625px rgba(from var(--shadow-color) r g b / 0.324951171875), + 5.5px 5.5px 1.890625px rgba(from var(--shadow-color) r g b / 0.282623291015625), + 6.0px 6.0px 2.25px rgba(from var(--shadow-color) r g b / 0.244140625), + 6.5px 6.5px 2.640625px rgba(from var(--shadow-color) r g b / 0.209320068359375), + 7.0px 7.0px 3.0625px rgba(from var(--shadow-color) r g b / 0.177978515625), + 7.5px 7.5px 3.515625px rgba(from var(--shadow-color) r g b / 0.149932861328125), + 8.0px 8.0px 4.0px rgba(from var(--shadow-color) r g b / 0.125), + 8.5px 8.5px 4.515625px rgba(from var(--shadow-color) r g b / 0.102996826171875), + 9.0px 9.0px 5.0625px rgba(from var(--shadow-color) r g b / 0.083740234375), + 9.5px 9.5px 5.640625px rgba(from var(--shadow-color) r g b / 0.067047119140625), + 10.0px 10.0px 6.25px rgba(from var(--shadow-color) r g b / 0.052734375), + 10.5px 10.5px 6.890625px rgba(from var(--shadow-color) r g b / 0.040618896484375), + 11.0px 11.0px 7.5625px rgba(from var(--shadow-color) r g b / 0.030517578125), + 11.5px 11.5px 8.265625px rgba(from var(--shadow-color) r g b / 0.022247314453125), + 12.0px 12.0px 9.0px rgba(from var(--shadow-color) r g b / 0.015625), + 12.5px 12.5px 9.765625px rgba(from var(--shadow-color) r g b / 0.010467529296875), + 13.0px 13.0px 10.5625px rgba(from var(--shadow-color) r g b / 0.006591796875), + 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625), + 14.0px 14.0px 12.25px rgba(from var(--shadow-color) r g b / 0.001953125), + 14.5px 14.5px 13.140625px rgba(from var(--shadow-color) r g b / 0.000823974609375), + 15.0px 15.0px 14.0625px rgba(from var(--shadow-color) r g b / 0.000244140625), + 15.5px 15.5px 15.015625px rgba(from var(--shadow-color) r g b / 3.0517578125e-05) + ; + } + + & .adjectives { + --recursive-wght: 800; + font-size: 2rem; + vertical-align: 50%; + } + + & .house { + width: max-content; + font-size: 4rem; + padding-left: 1em; + padding-top: 0.1em; + } +} + +@media (hover: none) { + h1.page-title a { + text-decoration: none; + } +} + @media (max-width: 450px) { body { --top-min-spacing: 40px; diff --git a/static/css/tree.css b/static/css/tree.css index ae73ba2..6aaed5e 100644 --- a/static/css/tree.css +++ b/static/css/tree.css @@ -2,6 +2,7 @@ :root { --tree-indent-width: 3.2rem; + --transition-duration: 0.15s; --button-bar-icon-size: 2.8rem; } @@ -81,21 +82,6 @@ .tree { --tree-indent-guide-dim: transparent; --tree-indent-guide-highlighted: transparent; - - display: flex; - flex-direction: column; - - align-self: start; - align-items: center; - - & > article { - padding: 1.6rem; - width: 100%; - } - - & > footer { - max-width: none; - } } .tree:has(.branch-container:hover) { @@ -130,9 +116,8 @@ } /* Top level should not have an indent or a border. */ -.tree article > ul { +.tree > ul { padding-left: 0; - margin-left: 0; border-left: none; } diff --git a/template/_doc.hbs b/template/_doc.hbs deleted file mode 100644 index 2c68496..0000000 --- a/template/_doc.hbs +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - {{> components/_head.hbs }} - - - - {{#each page.styles}} - - {{/each}} - - - - - - {{> components/_sidebar.hbs }} - - {{~> components/_doc.hbs }} - - - - - - - -{{~> components/_jar.hbs }} diff --git a/template/_tree.hbs b/template/_tree.hbs index 233f511..f395225 100644 --- a/template/_tree.hbs +++ b/template/_tree.hbs @@ -4,30 +4,31 @@ {{> components/_head.hbs }} - - - - {{#each page.styles}} - - {{/each}} - - - {{> components/_sidebar.hbs }} + + + {{> components/_noscript.hbs }} + {{> components/_nav.hbs }} + {{> components/_header.hbs }} + + {{!-- + NOTE: ~ because components/_tree.hbs must not include any extra indentation, because it may + contain pre elements which shouldn't be indented. + --}} {{~> components/_tree.hbs }} + {{!-- For all pages except the one linked from the footer, include the footer icon. --}} + {{#if (ne page.tree_path "treehouse")}} + {{> components/_footer.hbs }} + {{/if}} + - -{{~> components/_jar.hbs }} diff --git a/template/components/_doc.hbs b/template/components/_doc.hbs deleted file mode 100644 index 0afa630..0000000 --- a/template/components/_doc.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
-
- - - {{> components/_footer.hbs }} -
- - {{> components/_feed.hbs }} -
diff --git a/template/components/_feed.hbs b/template/components/_feed.hbs deleted file mode 100644 index 780ef75..0000000 --- a/template/components/_feed.hbs +++ /dev/null @@ -1,20 +0,0 @@ -{{#if page.feed}} -
-

{{ page.feed.title }}

- - {{#each page.feed.entries}} -
-

{{{ title }}}

-
- -
    - {{#each categories as |category|}} -
  • {{ category }}
  • - {{/each}} -
-
-
- {{/each}} -
-{{/if}} - diff --git a/template/components/_footer.hbs b/template/components/_footer.hbs index 064b07e..e216374 100644 --- a/template/components/_footer.hbs +++ b/template/components/_footer.hbs @@ -9,8 +9,6 @@ - {{!-- For all pages except the one linked from the footer, include the footer icon. --}} - {{#if (ne page.tree_path "treehouse")}}
@@ -60,5 +58,4 @@
- {{/if}} diff --git a/template/components/_head.hbs b/template/components/_head.hbs index bbed9c8..cf19b2e 100644 --- a/template/components/_head.hbs +++ b/template/components/_head.hbs @@ -4,15 +4,12 @@ - + + {{!-- Import maps currently don't support the src="" attribute. Unless we come up with something @@ -41,14 +38,14 @@ clever to do while browser vendors figure that out, we'll just have to do a cach }; diff --git a/template/components/_jar.hbs b/template/components/_jar.hbs deleted file mode 100644 index 48941e1..0000000 --- a/template/components/_jar.hbs +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/template/components/_nav.hbs b/template/components/_nav.hbs new file mode 100644 index 0000000..7ce797d --- /dev/null +++ b/template/components/_nav.hbs @@ -0,0 +1,5 @@ + diff --git a/template/components/_sidebar.hbs b/template/components/_sidebar.hbs deleted file mode 100644 index ae1dff5..0000000 --- a/template/components/_sidebar.hbs +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/template/components/_tree.hbs b/template/components/_tree.hbs index c9368f1..cf3379d 100644 --- a/template/components/_tree.hbs +++ b/template/components/_tree.hbs @@ -1,13 +1,18 @@
-
- {{#if (ne page.tree_path 'index')}} -
-

{{ page.title }}

-
- {{/if}} + {{!-- Append page styles and scripts into the main content, such that they can be inlined + into linked branches when those are loaded in. Putting them in the page's head would make + extracting them way more painful than it needs to be. --}} - {{{~ page.tree }}} -
+ {{#each page.styles}} + + {{/each}} - {{> components/_footer.hbs }} + + + {{{ page.tree }}}
diff --git a/treehouse.toml b/treehouse.toml index 8881c23..34ec8f3 100644 --- a/treehouse.toml +++ b/treehouse.toml @@ -12,7 +12,7 @@ commit_base_url = "https://src.liquidev.net/liquidex/treehouse/src/commit" [user] title = "riki's house" author = "riki" -description = "a pink ragdoll's fluffy house = —w— =" +description = "a fluffy ragdoll's fluffy house = —w— =" canonical_url = "https://riki.house" # URI prefix to use for entry IDs in feeds. @@ -26,7 +26,6 @@ feed_id_prefix = "https://liquidex.house" "social/github" = "https://github.com/liquidev" "social/soundcloud" = "https://soundcloud.com/daknus" "social/listenbrainz" = "https://listenbrainz.org/user/liquidev/" -"social/bandcamp" = "https://bandcamp.com/rikimoe" # treehouse management facilities "treehouse/issues" = "https://src.liquidev.net/riki/treehouse/issues"