diff --git a/crates/treehouse/src/cli/fix.rs b/crates/treehouse/src/cli/fix.rs index 7a79941..07b4bea 100644 --- a/crates/treehouse/src/cli/fix.rs +++ b/crates/treehouse/src/cli/fix.rs @@ -105,7 +105,7 @@ pub fn fix_file( file_id: FileId, ) -> Result { parse_tree_with_diagnostics(treehouse, file_id).map(|roots| { - let mut source = treehouse.get_source(file_id).to_owned(); + let mut source = treehouse.source(file_id).to_owned(); let mut state = State::default(); for branch in &roots.branches { @@ -129,14 +129,14 @@ pub fn fix_file_cli(fix_args: FixArgs) -> anyhow::Result<()> { let file = std::fs::read_to_string(&fix_args.file).context("cannot read file to fix")?; let mut treehouse = Treehouse::new(); - let file_id = treehouse.files.add(utf8_filename, file); + let file_id = treehouse.add_file(utf8_filename, None, file); if let Ok(fixed) = fix_file(&mut treehouse, file_id) { if fix_args.apply { // Try to write the backup first. If writing that fails, bail out without overwriting // the source file. if let Some(backup_path) = fix_args.backup { - std::fs::write(backup_path, treehouse.get_source(file_id)) + std::fs::write(backup_path, treehouse.source(file_id)) .context("cannot write backup; original file will not be overwritten")?; } std::fs::write(&fix_args.file, fixed).context("cannot overwrite original file")?; diff --git a/crates/treehouse/src/cli/parse.rs b/crates/treehouse/src/cli/parse.rs index 2b728f0..1f789cf 100644 --- a/crates/treehouse/src/cli/parse.rs +++ b/crates/treehouse/src/cli/parse.rs @@ -11,7 +11,7 @@ pub fn parse_tree_with_diagnostics( treehouse: &mut Treehouse, file_id: FileId, ) -> Result { - let input = treehouse.get_source(file_id); + let input = treehouse.source(file_id); Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| { treehouse.diagnostics.push(Diagnostic { severity: Severity::Error, @@ -34,7 +34,7 @@ pub fn parse_toml_with_diagnostics( file_id: FileId, range: Range, ) -> Result { - let input = &treehouse.get_source(file_id)[range.clone()]; + let input = &treehouse.source(file_id)[range.clone()]; toml_edit::Document::from_str(input).map_err(|error| { treehouse .diagnostics diff --git a/crates/treehouse/src/cli/regenerate.rs b/crates/treehouse/src/cli/regenerate.rs index 7eb1b7d..5711d59 100644 --- a/crates/treehouse/src/cli/regenerate.rs +++ b/crates/treehouse/src/cli/regenerate.rs @@ -45,14 +45,8 @@ impl Generator { ) -> anyhow::Result { let source = std::fs::read_to_string(path) .with_context(|| format!("cannot read template file {path:?}"))?; - let file_id = treehouse - .files - .add(path.to_string_lossy().into_owned(), source); - let file = treehouse - .files - .get(file_id) - .expect("file was just added to the list"); - let source = file.source(); + let file_id = treehouse.add_file(path.to_string_lossy().into_owned(), None, source); + let source = treehouse.source(file_id); if let Err(error) = handlebars.register_template_string(name, source) { Self::wrangle_handlebars_error_into_diagnostic( treehouse, @@ -90,11 +84,8 @@ impl Generator { notes: vec![], }) } else { - let file = treehouse - .files - .get(file_id) - .expect("file should already be in the list"); - bail!("template error in {}: {message}", file.name()); + let file = treehouse.filename(file_id); + bail!("template error in {file}: {message}"); } Ok(()) } @@ -112,11 +103,12 @@ impl Generator { for path in &self.tree_files { let utf8_filename = path.to_string_lossy(); - let target_file = path.strip_prefix(dirs.content_dir).unwrap_or(path); - let target_path = if target_file == OsStr::new("index.tree") { + + let tree_path = path.strip_prefix(dirs.content_dir).unwrap_or(path); + let target_path = if tree_path == OsStr::new("index.tree") { dirs.target_dir.join("index.html") } else { - dirs.target_dir.join(target_file).with_extension("html") + dirs.target_dir.join(tree_path).with_extension("html") }; debug!("generating: {path:?} -> {target_path:?}"); @@ -133,7 +125,11 @@ impl Generator { continue; } }; - let file_id = treehouse.files.add(utf8_filename.into_owned(), source); + let file_id = treehouse.add_file( + utf8_filename.into_owned(), + Some(tree_path.with_extension("").to_string_lossy().into_owned()), + source, + ); if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) { let mut tree = String::new(); diff --git a/crates/treehouse/src/html/tree.rs b/crates/treehouse/src/html/tree.rs index 5be2cf0..c09f9d9 100644 --- a/crates/treehouse/src/html/tree.rs +++ b/crates/treehouse/src/html/tree.rs @@ -17,11 +17,19 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId let attributes = parse_attributes(treehouse, file_id, branch); // Reborrow because the closure requires unique access (it adds a new diagnostic.) - let source = treehouse.get_source(file_id); + let source = treehouse.source(file_id); let has_children = !branch.children.is_empty() || matches!(attributes.content, Content::Link(_)); + let id = format!( + "{}:{}", + treehouse + .tree_path(file_id) + .expect("file should have a tree path"), + attributes.id + ); + let class = if has_children { "branch" } else { "leaf" }; let linked_branch = if let Content::Link(link) = &attributes.content { format!( @@ -34,7 +42,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId write!( s, "
  • ", - EscapeAttribute(&attributes.id) + EscapeAttribute(&id) ) .unwrap(); { @@ -95,7 +103,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId write!( s, "", - EscapeAttribute(&attributes.id) + EscapeAttribute(&id) ) .unwrap(); } @@ -113,7 +121,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId } fn parse_attributes(treehouse: &mut Treehouse, file_id: usize, branch: &Branch) -> Attributes { - let source = treehouse.get_source(file_id); + let source = treehouse.source(file_id); let mut successfully_parsed = true; let mut attributes = if let Some(attributes) = &branch.attributes { @@ -160,7 +168,7 @@ fn parse_attributes(treehouse: &mut Treehouse, file_id: usize, branch: &Branch) "note: a generated id `{}` will be used, but this id is unstable and will not persist across generations", attributes.id ), - format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.get_filename(file_id)), + format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.filename(file_id)), ], }); } diff --git a/crates/treehouse/src/main.rs b/crates/treehouse/src/main.rs index 14ceba9..324758a 100644 --- a/crates/treehouse/src/main.rs +++ b/crates/treehouse/src/main.rs @@ -10,6 +10,7 @@ use log::{error, info}; mod cli; mod html; +mod paths; mod state; async fn fallible_main() -> anyhow::Result<()> { diff --git a/crates/treehouse/src/paths.rs b/crates/treehouse/src/paths.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/treehouse/src/state.rs b/crates/treehouse/src/state.rs index ddec40a..8defbea 100644 --- a/crates/treehouse/src/state.rs +++ b/crates/treehouse/src/state.rs @@ -6,6 +6,7 @@ use codespan_reporting::{ files::SimpleFiles, term::termcolor::{ColorChoice, StandardStream}, }; +use log::debug; use ulid::Ulid; pub type Files = SimpleFiles; @@ -16,6 +17,9 @@ pub struct Treehouse { pub files: Files, pub diagnostics: Vec>, + // Bit of a hack because I don't wanna write my own `Files`. + tree_paths: Vec>, + missingno_generator: ulid::Generator, } @@ -25,12 +29,25 @@ impl Treehouse { files: Files::new(), diagnostics: vec![], + tree_paths: vec![], + missingno_generator: ulid::Generator::new(), } } + pub fn add_file( + &mut self, + filename: String, + tree_path: Option, + source: String, + ) -> FileId { + let id = self.files.add(filename, source); + self.tree_paths.push(tree_path); + id + } + /// Get the source code of a file, assuming it was previously registered. - pub fn get_source(&self, file_id: FileId) -> &str { + pub fn source(&self, file_id: FileId) -> &str { self.files .get(file_id) .expect("file should have been registered previously") @@ -38,13 +55,17 @@ impl Treehouse { } /// Get the name of a file, assuming it was previously registered. - pub fn get_filename(&self, file_id: FileId) -> &str { + pub fn filename(&self, file_id: FileId) -> &str { self.files .get(file_id) .expect("file should have been registered previously") .name() } + pub fn tree_path(&self, file_id: FileId) -> Option<&str> { + self.tree_paths[file_id].as_deref() + } + pub fn report_diagnostics(&self) -> anyhow::Result<()> { let writer = StandardStream::stderr(ColorChoice::Auto); let config = codespan_reporting::term::Config::default(); diff --git a/static/css/tree.css b/static/css/tree.css index 751b9d6..12cbe83 100644 --- a/static/css/tree.css +++ b/static/css/tree.css @@ -1,17 +1,11 @@ -/* The tree indents shouldn't be too spaced out */ - .tree ul { padding-left: clamp(12px, 2vw, 24px); } -/* But the first block should not be indented. */ - .tree>ul { padding-left: 0; } -/* Make the tree have + and - instead of the default details/summary arrow */ - .tree { --tree-icon-position: 8px 50%; --tree-icon-space: 28px; @@ -76,7 +70,7 @@ background-color: rgba(0, 0, 0, 5%); } -.tree li.leaf { +.tree li>div { background-image: url('../svg/leaf.svg'); background-repeat: no-repeat; background-position: var(--tree-icon-position); @@ -157,3 +151,11 @@ padding-left: 24px; opacity: 50%; } + +.tree :target>details>summary, +.tree :target>div { + border-bottom: 1px dashed rgba(0, 0, 0, 30%); + margin-bottom: -1px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} diff --git a/static/js/tree.js b/static/js/tree.js index 0a00e06..94f218f 100644 --- a/static/js/tree.js +++ b/static/js/tree.js @@ -66,3 +66,23 @@ class LinkedBranch extends HTMLLIElement { } customElements.define("th-linked-branch", LinkedBranch, { extends: "li" }); + +function expandDetailsRecursively(element) { + while (element && element.tagName != "MAIN") { + if (element.tagName == "DETAILS") { + element.open = true; + } + element = element.parentElement; + } +} + +// When you click on a link, and the destination is within a
    that is not expanded, +// expand the
    recursively. +window.addEventListener("popstate", _ => { + let element = document.getElementById(window.location.hash.substring(1)); + if (element !== undefined) { + // If the element is already loaded on the page, we're good. + expandDetailsRecursively(element); + window.location.hash = window.location.hash; + } +})