diff --git a/Cargo.lock b/Cargo.lock
index 5f8e6a8..e89722b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -724,7 +724,7 @@ dependencies = [
"pest_derive",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1409,7 +1409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
dependencies = [
"memchr",
- "thiserror",
+ "thiserror 1.0.69",
"ucd-trie",
]
@@ -1599,7 +1599,7 @@ dependencies = [
"rand_chacha",
"simd_helpers",
"system-deps",
- "thiserror",
+ "thiserror 1.0.69",
"v_frame",
"wasm-bindgen",
]
@@ -1931,7 +1931,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl",
+ "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",
]
[[package]]
@@ -1945,6 +1954,17 @@ 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"
@@ -2176,24 +2196,17 @@ 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 9c407e9..97f0b1b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,37 @@
-[workspace]
-members = ["crates/*"]
-resolver = "2"
+[package]
+name = "treehouse"
+version = "0.1.0"
+edition = "2021"
-[workspace.dependencies]
+[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"] }
tracing = "0.1.40"
-
-treehouse-format = { path = "crates/treehouse-format" }
+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"
[profile.dev]
package.webp.opt-level = 3
diff --git a/Justfile b/Justfile
index 7cc2557..e9915ae 100644
--- a/Justfile
+++ b/Justfile
@@ -1,7 +1,7 @@
port := "8080"
serve:
- cargo watch -- cargo run -- serve --port {{port}}
+ RUST_BACKTRACE=1 cargo watch -- cargo run -- serve --port {{port}}
fix:
cargo run -- fix-all --apply
diff --git a/content/about.tree b/content/about/v2.tree
similarity index 100%
rename from content/about.tree
rename to content/about/v2.tree
diff --git a/content/index.dj b/content/index.dj
new file mode 100644
index 0000000..2fdb873
--- /dev/null
+++ b/content/index.dj
@@ -0,0 +1,37 @@
+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 7f437f2..5e7245f 100644
--- a/content/index.tree
+++ b/content/index.tree
@@ -73,10 +73,6 @@ 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
new file mode 100644
index 0000000..0a32dfc
--- /dev/null
+++ b/content/requiem.dj
@@ -0,0 +1,130 @@
+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 371207f..5232f89 100644
--- a/content/treehouse/new.tree
+++ b/content/treehouse/new.tree
@@ -26,6 +26,13 @@ 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
deleted file mode 100644
index 7db6b26..0000000
--- a/crates/treehouse-format/Cargo.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-[package]
-name = "treehouse-format"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-thiserror = "1.0.47"
diff --git a/crates/treehouse-format/src/lib.rs b/crates/treehouse-format/src/lib.rs
deleted file mode 100644
index 6afdf89..0000000
--- a/crates/treehouse-format/src/lib.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-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/crates/treehouse/Cargo.toml b/crates/treehouse/Cargo.toml
deleted file mode 100644
index 2198fdf..0000000
--- a/crates/treehouse/Cargo.toml
+++ /dev/null
@@ -1,36 +0,0 @@
-[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/crates/treehouse/src/generate.rs b/crates/treehouse/src/generate.rs
deleted file mode 100644
index ec43d98..0000000
--- a/crates/treehouse/src/generate.rs
+++ /dev/null
@@ -1,238 +0,0 @@
-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/crates/treehouse/src/generate/simple_template.rs b/crates/treehouse/src/generate/simple_template.rs
deleted file mode 100644
index 6ed88e6..0000000
--- a/crates/treehouse/src/generate/simple_template.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-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
deleted file mode 100644
index 9fc77aa..0000000
--- a/crates/treehouse/src/generate/tree.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-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/crates/treehouse/src/cli.rs b/src/cli.rs
similarity index 100%
rename from crates/treehouse/src/cli.rs
rename to src/cli.rs
diff --git a/crates/treehouse/src/cli/fix.rs b/src/cli/fix.rs
similarity index 99%
rename from crates/treehouse/src/cli/fix.rs
rename to src/cli/fix.rs
index b5e1342..6e64cc0 100644
--- a/crates/treehouse/src/cli/fix.rs
+++ b/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/crates/treehouse/src/cli/serve.rs b/src/cli/serve.rs
similarity index 100%
rename from crates/treehouse/src/cli/serve.rs
rename to src/cli/serve.rs
diff --git a/crates/treehouse/src/cli/serve/live_reload.rs b/src/cli/serve/live_reload.rs
similarity index 100%
rename from crates/treehouse/src/cli/serve/live_reload.rs
rename to src/cli/serve/live_reload.rs
diff --git a/crates/treehouse/src/cli/serve/picture_upload.rs b/src/cli/serve/picture_upload.rs
similarity index 100%
rename from crates/treehouse/src/cli/serve/picture_upload.rs
rename to src/cli/serve/picture_upload.rs
diff --git a/crates/treehouse/src/cli/wc.rs b/src/cli/wc.rs
similarity index 97%
rename from crates/treehouse/src/cli/wc.rs
rename to src/cli/wc.rs
index b210225..7028dc3 100644
--- a/crates/treehouse/src/cli/wc.rs
+++ b/src/cli/wc.rs
@@ -1,10 +1,9 @@
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/crates/treehouse/src/config.rs b/src/config.rs
similarity index 97%
rename from crates/treehouse/src/config.rs
rename to src/config.rs
index 48fe361..c55cba2 100644
--- a/crates/treehouse/src/config.rs
+++ b/src/config.rs
@@ -152,6 +152,8 @@ 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/crates/treehouse/src/dirs.rs b/src/dirs.rs
similarity index 100%
rename from crates/treehouse/src/dirs.rs
rename to src/dirs.rs
diff --git a/crates/treehouse/src/fun.rs b/src/fun.rs
similarity index 100%
rename from crates/treehouse/src/fun.rs
rename to src/fun.rs
diff --git a/crates/treehouse/src/fun/seasons.rs b/src/fun/seasons.rs
similarity index 100%
rename from crates/treehouse/src/fun/seasons.rs
rename to src/fun/seasons.rs
diff --git a/src/generate.rs b/src/generate.rs
new file mode 100644
index 0000000..3caa73e
--- /dev/null
+++ b/src/generate.rs
@@ -0,0 +1,128 @@
+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/crates/treehouse/src/generate/atom.rs b/src/generate/atom.rs
similarity index 64%
rename from crates/treehouse/src/generate/atom.rs
rename to src/generate/atom.rs
index 9a39f7c..1c042eb 100644
--- a/crates/treehouse/src/generate/atom.rs
+++ b/src/generate/atom.rs
@@ -4,15 +4,14 @@ use anyhow::Context;
use chrono::{DateTime, Utc};
use handlebars::Handlebars;
use serde::Serialize;
-use tracing::{info, info_span, instrument};
-use ulid::Ulid;
+use tracing::{info_span, instrument};
use crate::{
dirs::Dirs,
html::djot::{self, resolve_link},
sources::Sources,
state::FileId,
- tree::SemaBranchId,
+ tree::{feed, SemaBranchId},
vfs::{self, Content, Dir, Entries, VPath, VPathBuf},
};
@@ -156,7 +155,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
+
+ {{/each}}
+
+{{/if}}
+
diff --git a/template/components/_footer.hbs b/template/components/_footer.hbs
index e216374..064b07e 100644
--- a/template/components/_footer.hbs
+++ b/template/components/_footer.hbs
@@ -9,6 +9,8 @@
+ {{!-- For all pages except the one linked from the footer, include the footer icon. --}}
+ {{#if (ne page.tree_path "treehouse")}}
+ {{/if}}
diff --git a/template/components/_head.hbs b/template/components/_head.hbs
index cf19b2e..bbed9c8 100644
--- a/template/components/_head.hbs
+++ b/template/components/_head.hbs
@@ -4,12 +4,15 @@
-
+
-
{{!--
Import maps currently don't support the src="" attribute. Unless we come up with something
@@ -38,14 +41,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
new file mode 100644
index 0000000..48941e1
--- /dev/null
+++ b/template/components/_jar.hbs
@@ -0,0 +1,16 @@
+
+
diff --git a/template/components/_nav.hbs b/template/components/_nav.hbs
deleted file mode 100644
index 7ce797d..0000000
--- a/template/components/_nav.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/template/components/_sidebar.hbs b/template/components/_sidebar.hbs
new file mode 100644
index 0000000..ae1dff5
--- /dev/null
+++ b/template/components/_sidebar.hbs
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/template/components/_tree.hbs b/template/components/_tree.hbs
index cf3379d..c9368f1 100644
--- a/template/components/_tree.hbs
+++ b/template/components/_tree.hbs
@@ -1,18 +1,13 @@
- {{!-- 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. --}}
+
+ {{#if (ne page.tree_path 'index')}}
+
+