tree update!
This commit is contained in:
parent
26b6056dbc
commit
5f86f4cee7
10
content/_treehouse/404.tree
Normal file
10
content/_treehouse/404.tree
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
%% title = "404"
|
||||||
|
|
||||||
|
% id = "404"
|
||||||
|
- # 404
|
||||||
|
|
||||||
|
% id = "01HMF8KQ997F1ZTEGDNAE2S6F1"
|
||||||
|
- seems like the page you're looking for isn't here.
|
||||||
|
|
||||||
|
% id = "01HMF8KQ99XNMEP67NE3QH5698"
|
||||||
|
- care to go [back to the index][branch:treehouse]?
|
21
content/_treehouse/b.tree
Normal file
21
content/_treehouse/b.tree
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
%% title = "GET /b"
|
||||||
|
|
||||||
|
% id = "b"
|
||||||
|
- # GET /b?<span class="http-request-parameter">branch</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.http-request-parameter { opacity: 80%; --recursive-wght: 700; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
% id = "01HMF8KQ990KC8Q08XYSKTV4TQ"
|
||||||
|
- this endpoint takes you to the <span class="http-request-parameter">branch</span> with the given ID
|
||||||
|
|
||||||
|
% id = "01HMF8KQ99VBWQSG1Y8NDTM8QA"
|
||||||
|
- it also includes proper OpenGraph metadata for the page, unlike the raw .html files.
|
||||||
|
therefore it's used for permalinks (those on the far right side of the branch →)
|
||||||
|
|
||||||
|
% id = "01HMF8KQ99KWR1K9QHKPYY2K15"
|
||||||
|
+ c'mon, [give it a whirl](/b?the-end-is-never)
|
||||||
|
|
||||||
|
% id = "01HMF8KQ99WX9P6D05T5VYBSKK"
|
||||||
|
- <https://www.youtube.com/watch?v=8Kban1IOQ4M>
|
|
@ -1,3 +1,5 @@
|
||||||
|
%% title = "liquidex's treehouse"
|
||||||
|
|
||||||
% id = "treehouse"
|
% id = "treehouse"
|
||||||
- # liquidex's treehouse
|
- # liquidex's treehouse
|
||||||
<span class="oops-you-seem-to-have-gotten-stuck">
|
<span class="oops-you-seem-to-have-gotten-stuck">
|
||||||
|
|
|
@ -7,11 +7,14 @@ use crate::{
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Roots {
|
pub struct Roots {
|
||||||
|
pub attributes: Option<Attributes>,
|
||||||
pub branches: Vec<Branch>,
|
pub branches: Vec<Branch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Roots {
|
impl Roots {
|
||||||
pub fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
|
pub fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
|
||||||
|
let attributes = parser.top_level_attributes()?;
|
||||||
|
|
||||||
let mut branches = vec![];
|
let mut branches = vec![];
|
||||||
while let Some((branch, indent_level)) = Branch::parse_with_indent_level(parser)? {
|
while let Some((branch, indent_level)) = Branch::parse_with_indent_level(parser)? {
|
||||||
if indent_level != 0 {
|
if indent_level != 0 {
|
||||||
|
@ -19,7 +22,10 @@ impl Roots {
|
||||||
}
|
}
|
||||||
branches.push(branch);
|
branches.push(branch);
|
||||||
}
|
}
|
||||||
Ok(Self { branches })
|
Ok(Self {
|
||||||
|
attributes,
|
||||||
|
branches,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,34 @@ impl<'a> Parser<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn top_level_attributes(&mut self) -> Result<Option<Attributes>, ParseError> {
|
||||||
|
let start = self.position;
|
||||||
|
match self.current() {
|
||||||
|
Some('%') => {
|
||||||
|
let after_one_percent = self.position;
|
||||||
|
self.advance();
|
||||||
|
if self.current() == Some('%') {
|
||||||
|
self.advance();
|
||||||
|
let after_two_percent = self.position;
|
||||||
|
self.eat_indented_lines_until(
|
||||||
|
0,
|
||||||
|
|c| c == '-' || c == '+' || c == '%',
|
||||||
|
AllowCodeBlocks::No,
|
||||||
|
)?;
|
||||||
|
let end = self.position;
|
||||||
|
Ok(Some(Attributes {
|
||||||
|
percent: start..after_two_percent,
|
||||||
|
data: after_two_percent..end,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
self.position = after_one_percent;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_branch(&mut self) -> Result<Option<BranchEvent>, ParseError> {
|
pub fn next_branch(&mut self) -> Result<Option<BranchEvent>, ParseError> {
|
||||||
if self.current().is_none() {
|
if self.current().is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::Context;
|
||||||
use treehouse_format::ast::Branch;
|
use treehouse_format::ast::Branch;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::state::{FileId, Treehouse};
|
use crate::state::{FileId, Source, Treehouse};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics},
|
parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics},
|
||||||
|
@ -106,7 +106,7 @@ pub fn fix_file(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Result<String, parse::ErrorsEmitted> {
|
) -> Result<String, parse::ErrorsEmitted> {
|
||||||
parse_tree_with_diagnostics(treehouse, file_id).map(|roots| {
|
parse_tree_with_diagnostics(treehouse, file_id).map(|roots| {
|
||||||
let mut source = treehouse.source(file_id).to_owned();
|
let mut source = treehouse.source(file_id).input().to_owned();
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
|
|
||||||
for branch in &roots.branches {
|
for branch in &roots.branches {
|
||||||
|
@ -130,14 +130,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 file = std::fs::read_to_string(&fix_args.file).context("cannot read file to fix")?;
|
||||||
|
|
||||||
let mut treehouse = Treehouse::new();
|
let mut treehouse = Treehouse::new();
|
||||||
let file_id = treehouse.add_file(utf8_filename, None, file);
|
let file_id = treehouse.add_file(utf8_filename, Source::Other(file));
|
||||||
|
|
||||||
if let Ok(fixed) = fix_file(&mut treehouse, file_id) {
|
if let Ok(fixed) = fix_file(&mut treehouse, file_id) {
|
||||||
if fix_args.apply {
|
if fix_args.apply {
|
||||||
// Try to write the backup first. If writing that fails, bail out without overwriting
|
// Try to write the backup first. If writing that fails, bail out without overwriting
|
||||||
// the source file.
|
// the source file.
|
||||||
if let Some(backup_path) = fix_args.backup {
|
if let Some(backup_path) = fix_args.backup {
|
||||||
std::fs::write(backup_path, treehouse.source(file_id))
|
std::fs::write(backup_path, treehouse.source(file_id).input())
|
||||||
.context("cannot write backup; original file will not be overwritten")?;
|
.context("cannot write backup; original file will not be overwritten")?;
|
||||||
}
|
}
|
||||||
std::fs::write(&fix_args.file, fixed).context("cannot overwrite original file")?;
|
std::fs::write(&fix_args.file, fixed).context("cannot overwrite original file")?;
|
||||||
|
@ -160,10 +160,10 @@ pub fn fix_all_cli(fix_all_args: FixAllArgs, paths: &Paths<'_>) -> anyhow::Resul
|
||||||
let utf8_filename = entry.path().to_string_lossy();
|
let utf8_filename = entry.path().to_string_lossy();
|
||||||
|
|
||||||
let mut treehouse = Treehouse::new();
|
let mut treehouse = Treehouse::new();
|
||||||
let file_id = treehouse.add_file(utf8_filename.into_owned(), None, file);
|
let file_id = treehouse.add_file(utf8_filename.into_owned(), Source::Other(file));
|
||||||
|
|
||||||
if let Ok(fixed) = fix_file(&mut treehouse, file_id) {
|
if let Ok(fixed) = fix_file(&mut treehouse, file_id) {
|
||||||
if fixed != treehouse.source(file_id) {
|
if fixed != treehouse.source(file_id).input() {
|
||||||
if fix_all_args.apply {
|
if fix_all_args.apply {
|
||||||
println!("fixing: {:?}", entry.path());
|
println!("fixing: {:?}", entry.path());
|
||||||
std::fs::write(entry.path(), fixed).with_context(|| {
|
std::fs::write(entry.path(), fixed).with_context(|| {
|
||||||
|
|
|
@ -5,17 +5,14 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use axum::Router;
|
|
||||||
use codespan_reporting::{
|
use codespan_reporting::{
|
||||||
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
||||||
files::Files as _,
|
files::Files as _,
|
||||||
};
|
};
|
||||||
use copy_dir::copy_dir;
|
use copy_dir::copy_dir;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use log::{debug, info};
|
use log::{debug, error, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
use tower_livereload::LiveReloadLayer;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -26,6 +23,7 @@ use crate::{
|
||||||
navmap::{build_navigation_map, NavigationMap},
|
navmap::{build_navigation_map, NavigationMap},
|
||||||
tree::branches_to_html,
|
tree::branches_to_html,
|
||||||
},
|
},
|
||||||
|
state::Source,
|
||||||
tree::SemaRoots,
|
tree::SemaRoots,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,7 +61,8 @@ impl Generator {
|
||||||
) -> anyhow::Result<FileId> {
|
) -> anyhow::Result<FileId> {
|
||||||
let source = std::fs::read_to_string(path)
|
let source = std::fs::read_to_string(path)
|
||||||
.with_context(|| format!("cannot read template file {path:?}"))?;
|
.with_context(|| format!("cannot read template file {path:?}"))?;
|
||||||
let file_id = treehouse.add_file(path.to_string_lossy().into_owned(), None, source);
|
let file_id =
|
||||||
|
treehouse.add_file(path.to_string_lossy().into_owned(), Source::Other(source));
|
||||||
let source = treehouse.source(file_id);
|
let source = treehouse.source(file_id);
|
||||||
if let Err(error) = handlebars.register_template_string(name, source) {
|
if let Err(error) = handlebars.register_template_string(name, source) {
|
||||||
Self::wrangle_handlebars_error_into_diagnostic(
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
|
@ -136,9 +135,17 @@ impl Generator {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let tree_path = tree_path.with_extension("").to_string_lossy().replace('\\', "/");
|
let tree_path = tree_path
|
||||||
let file_id =
|
.with_extension("")
|
||||||
treehouse.add_file(utf8_filename.into_owned(), Some(tree_path.clone()), source);
|
.to_string_lossy()
|
||||||
|
.replace('\\', "/");
|
||||||
|
let file_id = treehouse.add_file(
|
||||||
|
utf8_filename.into_owned(),
|
||||||
|
Source::Tree {
|
||||||
|
input: source,
|
||||||
|
tree_path: tree_path.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) {
|
if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) {
|
||||||
let roots = SemaRoots::from_roots(&mut treehouse, file_id, roots);
|
let roots = SemaRoots::from_roots(&mut treehouse, file_id, roots);
|
||||||
|
@ -186,20 +193,30 @@ impl Generator {
|
||||||
parsed_tree.file_id,
|
parsed_tree.file_id,
|
||||||
&roots.branches,
|
&roots.branches,
|
||||||
);
|
);
|
||||||
treehouse.roots.insert(parsed_tree.tree_path, roots);
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Page {
|
||||||
|
pub title: String,
|
||||||
|
pub breadcrumbs: String,
|
||||||
|
pub tree: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TemplateData<'a> {
|
pub struct TemplateData<'a> {
|
||||||
pub config: &'a Config,
|
pub config: &'a Config,
|
||||||
pub breadcrumbs: String,
|
pub page: Page,
|
||||||
pub tree: String,
|
|
||||||
}
|
}
|
||||||
let template_data = TemplateData {
|
let template_data = TemplateData {
|
||||||
config,
|
config,
|
||||||
breadcrumbs,
|
page: Page {
|
||||||
tree,
|
title: roots.attributes.title.clone(),
|
||||||
|
breadcrumbs,
|
||||||
|
tree,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
treehouse.roots.insert(parsed_tree.tree_path, roots);
|
||||||
|
|
||||||
let templated_html = match handlebars.render("tree", &template_data) {
|
let templated_html = match handlebars.render("tree", &template_data) {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -227,7 +244,7 @@ impl Generator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> {
|
pub fn generate(paths: &Paths<'_>) -> anyhow::Result<Treehouse> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
info!("loading config");
|
info!("loading config");
|
||||||
|
@ -268,26 +285,19 @@ pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> {
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
info!("generation done in {duration:?}");
|
info!("generation done in {duration:?}");
|
||||||
|
|
||||||
Ok(())
|
if !treehouse.has_errors() {
|
||||||
}
|
Ok(treehouse)
|
||||||
|
} else {
|
||||||
pub fn regenerate_or_report_error(paths: &Paths<'_>) {
|
bail!("generation errors occurred; diagnostics were emitted with detailed descriptions");
|
||||||
info!("regenerating site content");
|
|
||||||
|
|
||||||
match regenerate(paths) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(error) => eprintln!("error: {error:?}"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn web_server(port: u16) -> anyhow::Result<()> {
|
pub fn regenerate_or_report_error(paths: &Paths<'_>) -> anyhow::Result<Treehouse> {
|
||||||
let app = Router::new().nest_service("/", ServeDir::new("target/site"));
|
info!("regenerating site content");
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
let result = generate(paths);
|
||||||
let app = app.layer(LiveReloadLayer::new());
|
if let Err(e) = &result {
|
||||||
|
error!("{e:?}");
|
||||||
info!("serving on port {port}");
|
}
|
||||||
Ok(axum::Server::bind(&([0, 0, 0, 0], port).into())
|
result
|
||||||
.serve(app.into_make_service())
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod fix;
|
pub mod fix;
|
||||||
pub mod generate;
|
pub mod generate;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
pub mod serve;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -24,14 +25,21 @@ pub enum Command {
|
||||||
///
|
///
|
||||||
/// By default only prints which files would be changed. To apply the changes, use `--apply`.
|
/// By default only prints which files would be changed. To apply the changes, use `--apply`.
|
||||||
FixAll(#[clap(flatten)] FixAllArgs),
|
FixAll(#[clap(flatten)] FixAllArgs),
|
||||||
|
|
||||||
|
/// `generate` and start a treehouse server.
|
||||||
|
///
|
||||||
|
/// The server uses the generated files and provides extra functionality on top, handling
|
||||||
|
Serve {
|
||||||
|
#[clap(flatten)]
|
||||||
|
generate: GenerateArgs,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
serve: ServeArgs,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct GenerateArgs {
|
pub struct GenerateArgs {}
|
||||||
/// Start a web server serving the static files on the given port. Useful with `cargo watch`.
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub serve: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct FixArgs {
|
pub struct FixArgs {
|
||||||
|
@ -57,6 +65,13 @@ pub struct FixAllArgs {
|
||||||
pub apply: bool,
|
pub apply: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct ServeArgs {
|
||||||
|
/// The port under which to serve the treehouse.
|
||||||
|
#[clap(short, long, default_value_t = 8080)]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Paths<'a> {
|
pub struct Paths<'a> {
|
||||||
pub target_dir: &'a Path,
|
pub target_dir: &'a Path,
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn parse_tree_with_diagnostics(
|
||||||
treehouse: &mut Treehouse,
|
treehouse: &mut Treehouse,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Result<Roots, ErrorsEmitted> {
|
) -> Result<Roots, ErrorsEmitted> {
|
||||||
let input = treehouse.source(file_id);
|
let input = &treehouse.source(file_id).input();
|
||||||
Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
|
Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
|
||||||
treehouse.diagnostics.push(Diagnostic {
|
treehouse.diagnostics.push(Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
@ -34,7 +34,7 @@ pub fn parse_toml_with_diagnostics(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
) -> Result<toml_edit::Document, ErrorsEmitted> {
|
) -> Result<toml_edit::Document, ErrorsEmitted> {
|
||||||
let input = &treehouse.source(file_id)[range.clone()];
|
let input = &treehouse.source(file_id).input()[range.clone()];
|
||||||
toml_edit::Document::from_str(input).map_err(|error| {
|
toml_edit::Document::from_str(input).map_err(|error| {
|
||||||
treehouse
|
treehouse
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
86
crates/treehouse/src/cli/serve.rs
Normal file
86
crates/treehouse/src/cli/serve.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use axum::{
|
||||||
|
extract::{RawQuery, State},
|
||||||
|
response::Html,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use log::{error, info};
|
||||||
|
use pulldown_cmark::escape::escape_html;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
use crate::state::{Source, Treehouse};
|
||||||
|
|
||||||
|
use super::Paths;
|
||||||
|
|
||||||
|
struct SystemPages {
|
||||||
|
four_oh_four: String,
|
||||||
|
b_docs: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Server {
|
||||||
|
treehouse: Treehouse,
|
||||||
|
target_dir: PathBuf,
|
||||||
|
system_pages: SystemPages,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(treehouse: Treehouse, paths: &Paths<'_>, port: u16) -> anyhow::Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.nest_service("/", ServeDir::new(paths.target_dir))
|
||||||
|
.route("/b", get(branch))
|
||||||
|
.with_state(Arc::new(Server {
|
||||||
|
treehouse,
|
||||||
|
target_dir: paths.target_dir.to_owned(),
|
||||||
|
system_pages: SystemPages {
|
||||||
|
four_oh_four: std::fs::read_to_string(paths.target_dir.join("_treehouse/404.html"))
|
||||||
|
.context("cannot read 404 page")?,
|
||||||
|
b_docs: std::fs::read_to_string(paths.target_dir.join("_treehouse/b.html"))
|
||||||
|
.context("cannot read /b documentation page")?,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let app = app.layer(tower_livereload::LiveReloadLayer::new());
|
||||||
|
|
||||||
|
info!("serving on port {port}");
|
||||||
|
Ok(axum::Server::bind(&([0, 0, 0, 0], port).into())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branch(RawQuery(named_id): RawQuery, State(state): State<Arc<Server>>) -> Html<String> {
|
||||||
|
if let Some(named_id) = named_id {
|
||||||
|
if let Some(&branch_id) = state.treehouse.branches_by_named_id.get(&named_id) {
|
||||||
|
let branch = state.treehouse.tree.branch(branch_id);
|
||||||
|
if let Source::Tree { input, tree_path } = state.treehouse.source(branch.file_id) {
|
||||||
|
let file_path = state.target_dir.join(format!("{tree_path}.html"));
|
||||||
|
match std::fs::read_to_string(&file_path) {
|
||||||
|
Ok(content) => {
|
||||||
|
let branch_markdown_content = input[branch.content.clone()].trim();
|
||||||
|
let mut per_page_metadata =
|
||||||
|
String::from("<meta property=\"og:description\" content=\"");
|
||||||
|
escape_html(&mut per_page_metadata, branch_markdown_content).unwrap();
|
||||||
|
per_page_metadata.push_str("\">");
|
||||||
|
|
||||||
|
const PER_PAGE_METADATA_REPLACEMENT_STRING: &str = "<!-- treehouse-ca37057a-cff5-45b3-8415-3b02dbf6c799-per-branch-metadata -->";
|
||||||
|
return Html(content.replacen(
|
||||||
|
PER_PAGE_METADATA_REPLACEMENT_STRING,
|
||||||
|
&per_page_metadata,
|
||||||
|
// Replace one under the assumption that it appears in all pages.
|
||||||
|
1,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error while reading file {file_path:?}: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Html(state.system_pages.four_oh_four.clone())
|
||||||
|
} else {
|
||||||
|
Html(state.system_pages.b_docs.clone())
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ pub fn branch_to_html(
|
||||||
s.push_str("<div>");
|
s.push_str("<div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_block_content = &source[branch.content.clone()];
|
let raw_block_content = &source.input()[branch.content.clone()];
|
||||||
let mut unindented_block_content = String::with_capacity(raw_block_content.len());
|
let mut unindented_block_content = String::with_capacity(raw_block_content.len());
|
||||||
for line in raw_block_content.lines() {
|
for line in raw_block_content.lines() {
|
||||||
// Bit of a jank way to remove at most branch.indent_level spaces from the front.
|
// Bit of a jank way to remove at most branch.indent_level spaces from the front.
|
||||||
|
@ -93,7 +93,11 @@ pub fn branch_to_html(
|
||||||
.get(linked)
|
.get(linked)
|
||||||
.map(|&branch_id| {
|
.map(|&branch_id| {
|
||||||
(
|
(
|
||||||
format!("#{}", treehouse.tree.branch(branch_id).html_id).into(),
|
format!(
|
||||||
|
"/b?{}",
|
||||||
|
treehouse.tree.branch(branch_id).attributes.id
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
"".into(),
|
"".into(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
@ -144,8 +148,8 @@ pub fn branch_to_html(
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
s,
|
s,
|
||||||
"<a class=\"icon icon-permalink\" href=\"#{}\" title=\"permalink\"></a>",
|
"<a class=\"icon icon-permalink\" href=\"/b?{}\" title=\"permalink\"></a>",
|
||||||
EscapeAttribute(&branch.html_id)
|
EscapeAttribute(&branch.attributes.id)
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ use std::path::Path;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{
|
use cli::{
|
||||||
fix::{fix_all_cli, fix_file_cli},
|
fix::{fix_all_cli, fix_file_cli},
|
||||||
generate::{self, regenerate_or_report_error},
|
generate::regenerate_or_report_error,
|
||||||
|
serve::serve,
|
||||||
Command, Paths, ProgramArgs,
|
Command, Paths, ProgramArgs,
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info, warn};
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
|
@ -30,14 +31,17 @@ async fn fallible_main() -> anyhow::Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Generate(regenerate_args) => {
|
Command::Generate(_generate_args) => {
|
||||||
info!("regenerating using directories: {paths:#?}");
|
info!("regenerating using directories: {paths:#?}");
|
||||||
|
regenerate_or_report_error(&paths)?;
|
||||||
regenerate_or_report_error(&paths);
|
warn!("`generate` is for debugging only and the files cannot be fully served using a static file server; use `treehouse serve` if you wish to start a treehouse server");
|
||||||
|
}
|
||||||
if let Some(port) = regenerate_args.serve {
|
Command::Serve {
|
||||||
generate::web_server(port).await?;
|
generate: _,
|
||||||
}
|
serve: serve_args,
|
||||||
|
} => {
|
||||||
|
let treehouse = regenerate_or_report_error(&paths)?;
|
||||||
|
serve(treehouse, &paths, serve_args.port).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Fix(fix_args) => fix_file_cli(fix_args)?,
|
Command::Fix(fix_args) => fix_file_cli(fix_args)?,
|
||||||
|
|
|
@ -10,7 +10,28 @@ use ulid::Ulid;
|
||||||
|
|
||||||
use crate::tree::{SemaBranchId, SemaRoots, SemaTree};
|
use crate::tree::{SemaBranchId, SemaRoots, SemaTree};
|
||||||
|
|
||||||
pub type Files = SimpleFiles<String, String>;
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Source {
|
||||||
|
Tree { input: String, tree_path: String },
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
pub fn input(&self) -> &str {
|
||||||
|
match &self {
|
||||||
|
Source::Tree { input, .. } => input,
|
||||||
|
Source::Other(source) => source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Source {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.input()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Files = SimpleFiles<String, Source>;
|
||||||
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
||||||
|
|
||||||
/// Treehouse compilation context.
|
/// Treehouse compilation context.
|
||||||
|
@ -22,19 +43,9 @@ pub struct Treehouse {
|
||||||
pub branches_by_named_id: HashMap<String, SemaBranchId>,
|
pub branches_by_named_id: HashMap<String, SemaBranchId>,
|
||||||
pub roots: HashMap<String, SemaRoots>,
|
pub roots: HashMap<String, SemaRoots>,
|
||||||
|
|
||||||
// Bit of a hack because I don't wanna write my own `Files`.
|
|
||||||
tree_paths: Vec<Option<String>>,
|
|
||||||
|
|
||||||
missingno_generator: ulid::Generator,
|
missingno_generator: ulid::Generator,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BranchRef {
|
|
||||||
pub html_id: String,
|
|
||||||
pub file_id: FileId,
|
|
||||||
pub kind_span: Range<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Treehouse {
|
impl Treehouse {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -45,25 +56,16 @@ impl Treehouse {
|
||||||
branches_by_named_id: HashMap::new(),
|
branches_by_named_id: HashMap::new(),
|
||||||
roots: HashMap::new(),
|
roots: HashMap::new(),
|
||||||
|
|
||||||
tree_paths: vec![],
|
|
||||||
|
|
||||||
missingno_generator: ulid::Generator::new(),
|
missingno_generator: ulid::Generator::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_file(
|
pub fn add_file(&mut self, filename: String, source: Source) -> FileId {
|
||||||
&mut self,
|
self.files.add(filename, source)
|
||||||
filename: String,
|
|
||||||
tree_path: Option<String>,
|
|
||||||
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.
|
/// Get the source code of a file, assuming it was previously registered.
|
||||||
pub fn source(&self, file_id: FileId) -> &str {
|
pub fn source(&self, file_id: FileId) -> &Source {
|
||||||
self.files
|
self.files
|
||||||
.get(file_id)
|
.get(file_id)
|
||||||
.expect("file should have been registered previously")
|
.expect("file should have been registered previously")
|
||||||
|
@ -79,7 +81,10 @@ impl Treehouse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tree_path(&self, file_id: FileId) -> Option<&str> {
|
pub fn tree_path(&self, file_id: FileId) -> Option<&str> {
|
||||||
self.tree_paths[file_id].as_deref()
|
match self.source(file_id) {
|
||||||
|
Source::Tree { tree_path, .. } => Some(tree_path),
|
||||||
|
Source::Other(_) => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_diagnostics(&self) -> anyhow::Result<()> {
|
pub fn report_diagnostics(&self) -> anyhow::Result<()> {
|
||||||
|
@ -98,6 +103,12 @@ impl Treehouse {
|
||||||
.generate()
|
.generate()
|
||||||
.expect("just how much disk space do you have?")
|
.expect("just how much disk space do you have?")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_errors(&self) -> bool {
|
||||||
|
self.diagnostics
|
||||||
|
.iter()
|
||||||
|
.any(|diagnostic| diagnostic.severity == Severity::Error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TomlError {
|
pub struct TomlError {
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Top-level `%%` root attributes.
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct RootAttributes {
|
||||||
|
/// Title of the generated .html page.
|
||||||
|
///
|
||||||
|
/// The page's tree path is used if empty.
|
||||||
|
#[serde(default)]
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Branch attributes.
|
/// Branch attributes.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
|
|
|
@ -9,10 +9,12 @@ use treehouse_format::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse},
|
state::{toml_error_to_diagnostic, FileId, Source, TomlError, Treehouse},
|
||||||
tree::attributes::{Attributes, Content},
|
tree::attributes::{Attributes, Content},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::attributes::RootAttributes;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct SemaTree {
|
pub struct SemaTree {
|
||||||
branches: Vec<SemaBranch>,
|
branches: Vec<SemaBranch>,
|
||||||
|
@ -35,12 +37,14 @@ impl SemaTree {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SemaRoots {
|
pub struct SemaRoots {
|
||||||
|
pub attributes: RootAttributes,
|
||||||
pub branches: Vec<SemaBranchId>,
|
pub branches: Vec<SemaBranchId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SemaRoots {
|
impl SemaRoots {
|
||||||
pub fn from_roots(treehouse: &mut Treehouse, file_id: FileId, roots: Roots) -> Self {
|
pub fn from_roots(treehouse: &mut Treehouse, file_id: FileId, roots: Roots) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
attributes: Self::parse_attributes(treehouse, file_id, &roots),
|
||||||
branches: roots
|
branches: roots
|
||||||
.branches
|
.branches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -48,6 +52,44 @@ impl SemaRoots {
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_attributes(
|
||||||
|
treehouse: &mut Treehouse,
|
||||||
|
file_id: FileId,
|
||||||
|
roots: &Roots,
|
||||||
|
) -> RootAttributes {
|
||||||
|
let source = treehouse.source(file_id);
|
||||||
|
|
||||||
|
let mut successfully_parsed = true;
|
||||||
|
let mut attributes = if let Some(attributes) = &roots.attributes {
|
||||||
|
toml_edit::de::from_str(&source.input()[attributes.data.clone()]).unwrap_or_else(
|
||||||
|
|error| {
|
||||||
|
treehouse
|
||||||
|
.diagnostics
|
||||||
|
.push(toml_error_to_diagnostic(TomlError {
|
||||||
|
message: error.message().to_owned(),
|
||||||
|
span: error.span(),
|
||||||
|
file_id,
|
||||||
|
input_range: attributes.data.clone(),
|
||||||
|
}));
|
||||||
|
successfully_parsed = false;
|
||||||
|
RootAttributes::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RootAttributes::default()
|
||||||
|
};
|
||||||
|
let successfully_parsed = successfully_parsed;
|
||||||
|
|
||||||
|
if successfully_parsed && attributes.title.is_empty() {
|
||||||
|
attributes.title = match treehouse.source(file_id) {
|
||||||
|
Source::Tree { tree_path, .. } => tree_path.clone(),
|
||||||
|
_ => panic!("parse_attributes called for a non-.tree file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyzed branch.
|
/// Analyzed branch.
|
||||||
|
@ -132,18 +174,20 @@ impl SemaBranch {
|
||||||
|
|
||||||
let mut successfully_parsed = true;
|
let mut successfully_parsed = true;
|
||||||
let mut attributes = if let Some(attributes) = &branch.attributes {
|
let mut attributes = if let Some(attributes) = &branch.attributes {
|
||||||
toml_edit::de::from_str(&source[attributes.data.clone()]).unwrap_or_else(|error| {
|
toml_edit::de::from_str(&source.input()[attributes.data.clone()]).unwrap_or_else(
|
||||||
treehouse
|
|error| {
|
||||||
.diagnostics
|
treehouse
|
||||||
.push(toml_error_to_diagnostic(TomlError {
|
.diagnostics
|
||||||
message: error.message().to_owned(),
|
.push(toml_error_to_diagnostic(TomlError {
|
||||||
span: error.span(),
|
message: error.message().to_owned(),
|
||||||
file_id,
|
span: error.span(),
|
||||||
input_range: attributes.data.clone(),
|
file_id,
|
||||||
}));
|
input_range: attributes.data.clone(),
|
||||||
successfully_parsed = false;
|
}));
|
||||||
Attributes::default()
|
successfully_parsed = false;
|
||||||
})
|
Attributes::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Attributes::default()
|
Attributes::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
/*** Icons ***/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--icon-breadcrumb: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNTU0MjNlIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=');
|
||||||
|
--icon-expand: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNyA1di0zaC0ydjNoLTN2MmgzdjNoMnYtM2gzdi0yeiIgZmlsbD0iIzU1NDIzZSIgZmlsbC1vcGFjaXR5PSIuNSIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+');
|
||||||
|
--icon-leaf: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iNiIgY3k9IjYiIGZpbGw9IiM1NTQyM2UiIGZpbGwtb3BhY2l0eT0iLjUiIHI9IjIiLz48L3N2Zz4=');
|
||||||
|
--icon-collapse: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTMgNmg2IiBzdHJva2U9IiM1NTQyM2UiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
||||||
|
--icon-more: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQgNiA0IDQgNC00IiBmaWxsPSJub25lIiBzdHJva2U9IiM1NTQyM2UiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
||||||
|
--icon-permalink: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTcuNjU2ODYgMiAxLjQxNDIxIDEuNDE0MjJjLjY4MDUxLjY4MDUxIDEuMDY0NTMgMS41NDUyMSAxLjE1MjEzIDIuNDMzNjIuODg4NC4wODc2IDEuNzUzMS40NzE2NSAyLjQzMzcgMS4xNTIxNmwxLjQxNDIgMS40MTQyMmMxLjU2MjEgMS41NjIwOSAxLjU2MjEgNC4wOTQ3OCAwIDUuNjU2ODhzLTQuMDk0NzkgMS41NjIxLTUuNjU2ODggMGwtMS40MTQyMi0xLjQxNDJjLS42ODA1MS0uNjgwNi0xLjA2NDU2LTEuNTQ1My0xLjE1MjE2LTIuNDMzNy0uODg4NDEtLjA4NzYtMS43NTMxMS0uNDcxNjItMi40MzM2Mi0xLjE1MjEzbC0xLjQxNDIyLTEuNDE0MjFjLTEuNTYyMDk0LTEuNTYyMS0xLjU2MjA5NC00LjA5NDc2IDAtNS42NTY4NiAxLjU2MjEtMS41NjIwOTQgNC4wOTQ3Ni0xLjU2MjA5NCA1LjY1Njg2IDB6bS42MTggNy42ODkwN2MtLjE0NDMuMDg1MjItLjI5MjgxLjE2MDYxLS40NDQ1NS4yMjYxNi4wMjA4My40ODI1Ny4yMTU0Ni45NTg5Ny41ODM5MSAxLjMyNzM3bDEuNDE0MjEgMS40MTQzYy43ODEwNy43ODEgMi4wNDczNy43ODEgMi44Mjg0NyAwIC43ODEtLjc4MTEuNzgxLTIuMDQ3NCAwLTIuODI4NDdsLTEuNDE0My0xLjQxNDIxYy0uMzY4NC0uMzY4NDUtLjg0NDgtLjU2MzA4LTEuMzI3MzctLjU4MzkxLS4wNjU1NS4xNTE3My0uMTQwOTMuMzAwMjQtLjIyNjE2LjQ0NDU0bDEuODQ2NDMgMS44NDY0NS0xLjQxNDIgMS40MTQyem0tLjYxOC00Ljg2MDY0Yy4zNjg0NC4zNjg0NS41NjMwOC44NDQ4OC41ODM5MSAxLjMyNzQyLS4xNTE3NC4wNjU1NC0uMzAwMjQuMTQwOTMtLjQ0NDU0LjIyNjE1bC0xLjkxNzU0LTEuOTE3NTMtMS40MTQyMSAxLjQxNDIxIDEuOTE3NTMgMS45MTc1M2MtLjA4NTIzLjE0NDMxLS4xNjA2MS4yOTI4Mi0uMjI2MTYuNDQ0NTYtLjQ4MjU0LS4wMjA4My0uOTU4OTctLjIxNTQ3LTEuMzI3NDItLjU4MzkxbC0xLjQxNDIxLTEuNDE0MjJjLS43ODEwNS0uNzgxMDUtLjc4MTA1LTIuMDQ3MzcgMC0yLjgyODQyczIuMDQ3MzctLjc4MTA1IDIuODI4NDIgMHoiIGZpbGw9IiM1NTQyM2UiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==");
|
||||||
|
--icon-go: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEwLjU4NTggNy0yLjI5Mjg5LTIuMjkyODkgMS40MTQyMS0xLjQxNDIyIDQuNzA3MDggNC43MDcxMS00LjcwNzA4IDQuNzA3MS0xLjQxNDIxLTEuNDE0MiAyLjI5Mjg5LTIuMjkyOWgtNy41ODU4di0yeiIgZmlsbD0iIzU1NDIzZSIvPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--icon-breadcrumb: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDdjZGJmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=');
|
||||||
|
--icon-expand: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNyA1di0zaC0ydjNoLTN2MmgzdjNoMnYtM2gzdi0yeiIgZmlsbD0iI2Q3Y2RiZiIgZmlsbC1vcGFjaXR5PSIuNSIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+');
|
||||||
|
--icon-leaf: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iNiIgY3k9IjYiIGZpbGw9IiNkN2NkYmYiIGZpbGwtb3BhY2l0eT0iLjUiIHI9IjIiLz48L3N2Zz4=');
|
||||||
|
--icon-collapse: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTMgNmg2IiBzdHJva2U9IiNkN2NkYmYiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
||||||
|
--icon-permalink: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTcuNjU2ODYgMiAxLjQxNDIxIDEuNDE0MjJjLjY4MDUxLjY4MDUxIDEuMDY0NTMgMS41NDUyMSAxLjE1MjEzIDIuNDMzNjIuODg4NC4wODc2IDEuNzUzMS40NzE2NSAyLjQzMzcgMS4xNTIxNmwxLjQxNDIgMS40MTQyMmMxLjU2MjEgMS41NjIwOSAxLjU2MjEgNC4wOTQ3OCAwIDUuNjU2ODhzLTQuMDk0NzkgMS41NjIxLTUuNjU2ODggMGwtMS40MTQyMi0xLjQxNDJjLS42ODA1MS0uNjgwNi0xLjA2NDU2LTEuNTQ1My0xLjE1MjE2LTIuNDMzNy0uODg4NDEtLjA4NzYtMS43NTMxMS0uNDcxNjItMi40MzM2Mi0xLjE1MjEzbC0xLjQxNDIyLTEuNDE0MjFjLTEuNTYyMDk0LTEuNTYyMS0xLjU2MjA5NC00LjA5NDc2IDAtNS42NTY4NiAxLjU2MjEtMS41NjIwOTQgNC4wOTQ3Ni0xLjU2MjA5NCA1LjY1Njg2IDB6bS42MTggNy42ODkwN2MtLjE0NDMuMDg1MjItLjI5MjgxLjE2MDYxLS40NDQ1NS4yMjYxNi4wMjA4My40ODI1Ny4yMTU0Ni45NTg5Ny41ODM5MSAxLjMyNzM3bDEuNDE0MjEgMS40MTQzYy43ODEwNy43ODEgMi4wNDczNy43ODEgMi44Mjg0NyAwIC43ODEtLjc4MTEuNzgxLTIuMDQ3NCAwLTIuODI4NDdsLTEuNDE0My0xLjQxNDIxYy0uMzY4NC0uMzY4NDUtLjg0NDgtLjU2MzA4LTEuMzI3MzctLjU4MzkxLS4wNjU1NS4xNTE3My0uMTQwOTMuMzAwMjQtLjIyNjE2LjQ0NDU0bDEuODQ2NDMgMS44NDY0NS0xLjQxNDIgMS40MTQyem0tLjYxOC00Ljg2MDY0Yy4zNjg0NC4zNjg0NS41NjMwOC44NDQ4OC41ODM5MSAxLjMyNzQyLS4xNTE3NC4wNjU1NC0uMzAwMjQuMTQwOTMtLjQ0NDU0LjIyNjE1bC0xLjkxNzU0LTEuOTE3NTMtMS40MTQyMSAxLjQxNDIxIDEuOTE3NTMgMS45MTc1M2MtLjA4NTIzLjE0NDMxLS4xNjA2MS4yOTI4Mi0uMjI2MTYuNDQ0NTYtLjQ4MjU0LS4wMjA4My0uOTU4OTctLjIxNTQ3LTEuMzI3NDItLjU4MzkxbC0xLjQxNDIxLTEuNDE0MjJjLS43ODEwNS0uNzgxMDUtLjc4MTA1LTIuMDQ3MzcgMC0yLjgyODQyczIuMDQ3MzctLjc4MTA1IDIuODI4NDIgMHoiIGZpbGw9IiNkN2NkYmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==");
|
||||||
|
--icon-go: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEwLjU4NTggNy0yLjI5Mjg5LTIuMjkyODkgMS40MTQyMS0xLjQxNDIyIDQuNzA3MDggNC43MDcxMS00LjcwNzA4IDQuNzA3MS0xLjQxNDIxLTEuNDE0MiAyLjI5Mjg5LTIuMjkyOWgtNy41ODU4di0yeiIgZmlsbD0iI2Q3Y2RiZiIvPjwvc3ZnPg==");
|
||||||
|
--icon-more: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQgNiA0IDQgNC00IiBmaWxsPSJub25lIiBzdHJva2U9IiNkN2NkYmYiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*** Breadcrumbs ***/
|
/*** Breadcrumbs ***/
|
||||||
|
|
||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
|
@ -22,7 +46,7 @@
|
||||||
|
|
||||||
background-image:
|
background-image:
|
||||||
/* breadcrumb */
|
/* breadcrumb */
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNTU0MjNlIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=');
|
var(--icon-breadcrumb);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
opacity: 70%;
|
opacity: 70%;
|
||||||
|
@ -107,9 +131,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree details>summary {
|
.tree details>summary {
|
||||||
background-image:
|
background-image: var(--icon-expand);
|
||||||
/* expand */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNyA1di0zaC0ydjNoLTN2MmgzdjNoMnYtM2gzdi0yeiIgZmlsbD0iIzU1NDIzZSIgZmlsbC1vcGFjaXR5PSIuNSIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+');
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: var(--tree-icon-position);
|
background-position: var(--tree-icon-position);
|
||||||
padding-left: var(--tree-icon-space);
|
padding-left: var(--tree-icon-space);
|
||||||
|
@ -127,9 +149,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree li>div {
|
.tree li>div {
|
||||||
background-image:
|
background-image: var(--icon-leaf);
|
||||||
/* leaf */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iNiIgY3k9IjYiIGZpbGw9IiM1NTQyM2UiIGZpbGwtb3BhY2l0eT0iLjUiIHI9IjIiLz48L3N2Zz4=');
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: var(--tree-icon-position);
|
background-position: var(--tree-icon-position);
|
||||||
padding-left: var(--tree-icon-space);
|
padding-left: var(--tree-icon-space);
|
||||||
|
@ -139,18 +159,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree details[open]>summary {
|
.tree details[open]>summary {
|
||||||
background-image:
|
background-image: var(--icon-collapse);
|
||||||
/* collapse */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTMgNmg2IiBzdHJva2U9IiM1NTQyM2UiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree details:not([open])>summary>.branch-summary>:last-child::after {
|
.tree details:not([open])>summary>.branch-summary>:last-child::after {
|
||||||
content: '\00A0';
|
content: '\00A0';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
background-image:
|
background-image: var(--icon-more);
|
||||||
/* more */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQgNiA0IDQgNC00IiBmaWxsPSJub25lIiBzdHJva2U9IiM1NTQyM2UiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
|
|
||||||
|
@ -217,15 +233,11 @@
|
||||||
|
|
||||||
|
|
||||||
.tree .icon-permalink {
|
.tree .icon-permalink {
|
||||||
background-image:
|
background-image: var(--icon-permalink);
|
||||||
/* permalink */
|
|
||||||
url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTcuNjU2ODYgMiAxLjQxNDIxIDEuNDE0MjJjLjY4MDUxLjY4MDUxIDEuMDY0NTMgMS41NDUyMSAxLjE1MjEzIDIuNDMzNjIuODg4NC4wODc2IDEuNzUzMS40NzE2NSAyLjQzMzcgMS4xNTIxNmwxLjQxNDIgMS40MTQyMmMxLjU2MjEgMS41NjIwOSAxLjU2MjEgNC4wOTQ3OCAwIDUuNjU2ODhzLTQuMDk0NzkgMS41NjIxLTUuNjU2ODggMGwtMS40MTQyMi0xLjQxNDJjLS42ODA1MS0uNjgwNi0xLjA2NDU2LTEuNTQ1My0xLjE1MjE2LTIuNDMzNy0uODg4NDEtLjA4NzYtMS43NTMxMS0uNDcxNjItMi40MzM2Mi0xLjE1MjEzbC0xLjQxNDIyLTEuNDE0MjFjLTEuNTYyMDk0LTEuNTYyMS0xLjU2MjA5NC00LjA5NDc2IDAtNS42NTY4NiAxLjU2MjEtMS41NjIwOTQgNC4wOTQ3Ni0xLjU2MjA5NCA1LjY1Njg2IDB6bS42MTggNy42ODkwN2MtLjE0NDMuMDg1MjItLjI5MjgxLjE2MDYxLS40NDQ1NS4yMjYxNi4wMjA4My40ODI1Ny4yMTU0Ni45NTg5Ny41ODM5MSAxLjMyNzM3bDEuNDE0MjEgMS40MTQzYy43ODEwNy43ODEgMi4wNDczNy43ODEgMi44Mjg0NyAwIC43ODEtLjc4MTEuNzgxLTIuMDQ3NCAwLTIuODI4NDdsLTEuNDE0My0xLjQxNDIxYy0uMzY4NC0uMzY4NDUtLjg0NDgtLjU2MzA4LTEuMzI3MzctLjU4MzkxLS4wNjU1NS4xNTE3My0uMTQwOTMuMzAwMjQtLjIyNjE2LjQ0NDU0bDEuODQ2NDMgMS44NDY0NS0xLjQxNDIgMS40MTQyem0tLjYxOC00Ljg2MDY0Yy4zNjg0NC4zNjg0NS41NjMwOC44NDQ4OC41ODM5MSAxLjMyNzQyLS4xNTE3NC4wNjU1NC0uMzAwMjQuMTQwOTMtLjQ0NDU0LjIyNjE1bC0xLjkxNzU0LTEuOTE3NTMtMS40MTQyMSAxLjQxNDIxIDEuOTE3NTMgMS45MTc1M2MtLjA4NTIzLjE0NDMxLS4xNjA2MS4yOTI4Mi0uMjI2MTYuNDQ0NTYtLjQ4MjU0LS4wMjA4My0uOTU4OTctLjIxNTQ3LTEuMzI3NDItLjU4MzkxbC0xLjQxNDIxLTEuNDE0MjJjLS43ODEwNS0uNzgxMDUtLjc4MTA1LTIuMDQ3MzcgMC0yLjgyODQyczIuMDQ3MzctLjc4MTA1IDIuODI4NDIgMHoiIGZpbGw9IiM1NTQyM2UiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree .icon-go {
|
.tree .icon-go {
|
||||||
background-image:
|
background-image: var(--icon-go);
|
||||||
/* go */
|
|
||||||
url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEwLjU4NTggNy0yLjI5Mjg5LTIuMjkyODkgMS40MTQyMS0xLjQxNDIyIDQuNzA3MDggNC43MDcxMS00LjcwNzA4IDQuNzA3MS0xLjQxNDIxLTEuNDE0MiAyLjI5Mjg5LTIuMjkyOWgtNy41ODU4di0yeiIgZmlsbD0iIzU1NDIzZSIvPjwvc3ZnPg==");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree a.navigate {
|
.tree a.navigate {
|
||||||
|
@ -241,54 +253,14 @@
|
||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree :target>details>summary,
|
.tree :target,
|
||||||
.tree :target>div {
|
.tree .target {
|
||||||
border-bottom: 1px dashed var(--border-2);
|
|
||||||
margin-bottom: -1px;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
&>details>summary,
|
||||||
.breadcrumb::before {
|
&>div {
|
||||||
background-image:
|
border-bottom: 1px dashed var(--border-2);
|
||||||
/* breadcrumb */
|
margin-bottom: -1px;
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDdjZGJmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=')
|
border-bottom-left-radius: 0;
|
||||||
}
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
.tree details>summary {
|
|
||||||
background-image:
|
|
||||||
/* expand */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNyA1di0zaC0ydjNoLTN2MmgzdjNoMnYtM2gzdi0yeiIgZmlsbD0iI2Q3Y2RiZiIgZmlsbC1vcGFjaXR5PSIuNSIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+');
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree li>div {
|
|
||||||
background-image:
|
|
||||||
/* leaf */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iNiIgY3k9IjYiIGZpbGw9IiNkN2NkYmYiIGZpbGwtb3BhY2l0eT0iLjUiIHI9IjIiLz48L3N2Zz4=');
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree details[open]>summary {
|
|
||||||
background-image:
|
|
||||||
/* collapse */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTMgNmg2IiBzdHJva2U9IiNkN2NkYmYiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree .icon-permalink {
|
|
||||||
background-image:
|
|
||||||
/* permalink */
|
|
||||||
url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTcuNjU2ODYgMiAxLjQxNDIxIDEuNDE0MjJjLjY4MDUxLjY4MDUxIDEuMDY0NTMgMS41NDUyMSAxLjE1MjEzIDIuNDMzNjIuODg4NC4wODc2IDEuNzUzMS40NzE2NSAyLjQzMzcgMS4xNTIxNmwxLjQxNDIgMS40MTQyMmMxLjU2MjEgMS41NjIwOSAxLjU2MjEgNC4wOTQ3OCAwIDUuNjU2ODhzLTQuMDk0NzkgMS41NjIxLTUuNjU2ODggMGwtMS40MTQyMi0xLjQxNDJjLS42ODA1MS0uNjgwNi0xLjA2NDU2LTEuNTQ1My0xLjE1MjE2LTIuNDMzNy0uODg4NDEtLjA4NzYtMS43NTMxMS0uNDcxNjItMi40MzM2Mi0xLjE1MjEzbC0xLjQxNDIyLTEuNDE0MjFjLTEuNTYyMDk0LTEuNTYyMS0xLjU2MjA5NC00LjA5NDc2IDAtNS42NTY4NiAxLjU2MjEtMS41NjIwOTQgNC4wOTQ3Ni0xLjU2MjA5NCA1LjY1Njg2IDB6bS42MTggNy42ODkwN2MtLjE0NDMuMDg1MjItLjI5MjgxLjE2MDYxLS40NDQ1NS4yMjYxNi4wMjA4My40ODI1Ny4yMTU0Ni45NTg5Ny41ODM5MSAxLjMyNzM3bDEuNDE0MjEgMS40MTQzYy43ODEwNy43ODEgMi4wNDczNy43ODEgMi44Mjg0NyAwIC43ODEtLjc4MTEuNzgxLTIuMDQ3NCAwLTIuODI4NDdsLTEuNDE0My0xLjQxNDIxYy0uMzY4NC0uMzY4NDUtLjg0NDgtLjU2MzA4LTEuMzI3MzctLjU4MzkxLS4wNjU1NS4xNTE3My0uMTQwOTMuMzAwMjQtLjIyNjE2LjQ0NDU0bDEuODQ2NDMgMS44NDY0NS0xLjQxNDIgMS40MTQyem0tLjYxOC00Ljg2MDY0Yy4zNjg0NC4zNjg0NS41NjMwOC44NDQ4OC41ODM5MSAxLjMyNzQyLS4xNTE3NC4wNjU1NC0uMzAwMjQuMTQwOTMtLjQ0NDU0LjIyNjE1bC0xLjkxNzU0LTEuOTE3NTMtMS40MTQyMSAxLjQxNDIxIDEuOTE3NTMgMS45MTc1M2MtLjA4NTIzLjE0NDMxLS4xNjA2MS4yOTI4Mi0uMjI2MTYuNDQ0NTYtLjQ4MjU0LS4wMjA4My0uOTU4OTctLjIxNTQ3LTEuMzI3NDItLjU4MzkxbC0xLjQxNDIxLTEuNDE0MjJjLS43ODEwNS0uNzgxMDUtLjc4MTA1LTIuMDQ3MzcgMC0yLjgyODQyczIuMDQ3MzctLjc4MTA1IDIuODI4NDIgMHoiIGZpbGw9IiNkN2NkYmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==");
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree .icon-go {
|
|
||||||
background-image:
|
|
||||||
/* go */
|
|
||||||
url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEwLjU4NTggNy0yLjI5Mjg5LTIuMjkyODkgMS40MTQyMS0xLjQxNDIyIDQuNzA3MDggNC43MDcxMS00LjcwNzA4IDQuNzA3MS0xLjQxNDIxLTEuNDE0MiAyLjI5Mjg5LTIuMjkyOWgtNy41ODU4di0yeiIgZmlsbD0iI2Q3Y2RiZiIvPjwvc3ZnPg==");
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree details:not([open])>summary>.branch-summary>:last-child::after {
|
|
||||||
background-image:
|
|
||||||
/* more */
|
|
||||||
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQgNiA0IDQgNC00IiBmaWxsPSJub25lIiBzdHJva2U9IiNkN2NkYmYiIHN0cm9rZS1vcGFjaXR5PSIuNSIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ function branchIsOpen(branchID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Branch extends HTMLLIElement {
|
class Branch extends HTMLLIElement {
|
||||||
|
static branchesByNamedID = new Map();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -31,6 +33,9 @@ class Branch extends HTMLLIElement {
|
||||||
this.details.addEventListener("toggle", _ => {
|
this.details.addEventListener("toggle", _ => {
|
||||||
saveBranchIsOpen(this.id, this.details.open);
|
saveBranchIsOpen(this.id, this.details.open);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Branch.branchesByNamedID.set(this.id.split(':')[1], this);
|
||||||
|
console.log(Branch.branchesByNamedID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +52,6 @@ class LinkedBranch extends Branch {
|
||||||
this.linkedTree = this.getAttribute("data-th-link");
|
this.linkedTree = this.getAttribute("data-th-link");
|
||||||
LinkedBranch.byLink.set(this.linkedTree, this);
|
LinkedBranch.byLink.set(this.linkedTree, this);
|
||||||
|
|
||||||
this.loadingState = "notloaded";
|
|
||||||
|
|
||||||
this.loadingText = document.createElement("p");
|
this.loadingText = document.createElement("p");
|
||||||
{
|
{
|
||||||
this.loadingText.className = "link-loading";
|
this.loadingText.className = "link-loading";
|
||||||
|
@ -109,8 +112,10 @@ function rehash() { // https://www.youtube.com/watch?v=Tv1SYqLllKI
|
||||||
if (!rehashing) {
|
if (!rehashing) {
|
||||||
rehashing = true;
|
rehashing = true;
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
window.location.hash = "";
|
if (hash.length > 0) {
|
||||||
window.location.hash = hash;
|
window.location.hash = "";
|
||||||
|
window.location.hash = hash;
|
||||||
|
}
|
||||||
rehashing = false;
|
rehashing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,9 +188,17 @@ async function navigateToBranch(fragment) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentlyHighlightedBranch() {
|
||||||
|
if (window.location.pathname == "/b" && window.location.search.length > 0) {
|
||||||
|
let shortID = window.location.search.substring(1);
|
||||||
|
return Branch.branchesByNamedID.get(shortID).id;
|
||||||
|
} else {
|
||||||
|
return window.location.hash.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function navigateToCurrentBranch() {
|
async function navigateToCurrentBranch() {
|
||||||
let location = window.location.hash.substring(1);
|
await navigateToBranch(getCurrentlyHighlightedBranch());
|
||||||
await navigateToBranch(location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When you click on a link, and the destination is within a <details> that is not expanded,
|
// When you click on a link, and the destination is within a <details> that is not expanded,
|
||||||
|
@ -196,9 +209,9 @@ addEventListener("DOMContentLoaded", navigateToCurrentBranch);
|
||||||
// When you enter the website through a link someone sent you, it would be nice if the linked branch
|
// When you enter the website through a link someone sent you, it would be nice if the linked branch
|
||||||
// got expanded by default.
|
// got expanded by default.
|
||||||
async function expandLinkedBranch() {
|
async function expandLinkedBranch() {
|
||||||
let hash = window.location.hash;
|
let currentlyHighlightedBranch = getCurrentlyHighlightedBranch();
|
||||||
if (hash.length > 0) {
|
if (currentlyHighlightedBranch.length > 0) {
|
||||||
let linkedBranch = document.getElementById(hash.substring(1));
|
let linkedBranch = document.getElementById(currentlyHighlightedBranch);
|
||||||
if (linkedBranch.children.length > 0 && linkedBranch.children[0].tagName == "DETAILS") {
|
if (linkedBranch.children.length > 0 && linkedBranch.children[0].tagName == "DETAILS") {
|
||||||
expandDetailsRecursively(linkedBranch.children[0]);
|
expandDetailsRecursively(linkedBranch.children[0]);
|
||||||
}
|
}
|
||||||
|
@ -206,3 +219,12 @@ async function expandLinkedBranch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener("DOMContentLoaded", expandLinkedBranch);
|
addEventListener("DOMContentLoaded", expandLinkedBranch);
|
||||||
|
|
||||||
|
async function highlightCurrentBranch() {
|
||||||
|
let branch = document.getElementById(getCurrentlyHighlightedBranch());
|
||||||
|
if (branch != null) {
|
||||||
|
branch.classList.add("target");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener("DOMContentLoaded", highlightCurrentBranch);
|
||||||
|
|
|
@ -5,13 +5,19 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
<title>{{ config.user.title }}</title>
|
<title>{{#if (ne page.title config.user.title)}}{{ page.title }} · {{/if}}{{ config.user.title }}</title>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<meta property="og:title" content="{{ config.user.title }}">
|
|
||||||
<meta property="og:site_name" content="{{ config.user.title }}">
|
<meta property="og:site_name" content="{{ config.user.title }}">
|
||||||
<meta property="og:description" content="{{ config.user.description }}">
|
<meta property="og:title" content="{{ page.title }}">
|
||||||
|
{{!--
|
||||||
|
This is a bit of a hack to quickly insert metadata into generated pages without going through Handlebars, which
|
||||||
|
would involve registering, parsing, and generating a page from a template.
|
||||||
|
Yes it would be more flexible that way, but it doesn't need to be.
|
||||||
|
It just needs to be a string replacement.
|
||||||
|
--}}
|
||||||
|
<!-- treehouse-ca37057a-cff5-45b3-8415-3b02dbf6c799-per-branch-metadata -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ config.site }}/static/css/main.css">
|
<link rel="stylesheet" href="{{ config.site }}/static/css/main.css">
|
||||||
<link rel="stylesheet" href="{{ config.site }}/static/css/tree.css">
|
<link rel="stylesheet" href="{{ config.site }}/static/css/tree.css">
|
||||||
|
@ -34,9 +40,9 @@
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{{#if breadcrumbs}}
|
{{#if page.breadcrumbs}}
|
||||||
<ol class="breadcrumbs">
|
<ol class="breadcrumbs">
|
||||||
{{{ breadcrumbs }}}
|
{{{ page.breadcrumbs }}}
|
||||||
</ol>
|
</ol>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -57,7 +63,8 @@
|
||||||
if you don't believe me, you're free to inspect the source yourself! all the scripts are written
|
if you don't believe me, you're free to inspect the source yourself! all the scripts are written
|
||||||
lovingly in vanilla JS (not minified!) by yours truly ❤️</p>
|
lovingly in vanilla JS (not minified!) by yours truly ❤️</p>
|
||||||
<small>and if this box is annoying, feel free to block it with uBlock Origin or something. I have no
|
<small>and if this box is annoying, feel free to block it with uBlock Origin or something. I have no
|
||||||
way of remembering you closed it, and don't wanna host this site on a dynamic server.</small>
|
way of remembering you closed it, and don't wanna add a database to this website. simplicity
|
||||||
|
rules!</small>
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
|
@ -70,7 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="tree">
|
<main class="tree">
|
||||||
{{{ tree }}}
|
{{{ page.tree }}}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue