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
-
- {{/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"