From a127a0407d321e34239c1c732ef86974645deee8 Mon Sep 17 00:00:00 2001 From: trisua Date: Thu, 14 Aug 2025 03:11:46 -0400 Subject: [PATCH] add: multiple pages in one entry, details syntax --- app/public/app.js | 17 ++++ app/public/style.css | 17 ++++ src/markdown.rs | 187 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 217 insertions(+), 4 deletions(-) diff --git a/app/public/app.js b/app/public/app.js index ebe8480..1689f5d 100644 --- a/app/public/app.js +++ b/app/public/app.js @@ -349,3 +349,20 @@ globalThis.toggle_metadata_css = (e) => { window.location.reload(); } }; + +globalThis.hash_check = (hash) => { + if (hash.startsWith("#/")) { + for (const x of Array.from(document.querySelectorAll(".subpage"))) { + x.classList.add("hidden"); + } + + document.getElementById(hash).classList.remove("hidden"); + } +}; + +window.addEventListener("hashchange", (_) => hash_check(window.location.hash)); + +setTimeout(() => { + // run initial hash check + hash_check(window.location.hash); +}, 150); diff --git a/app/public/style.css b/app/public/style.css index 1ff752a..bc1829a 100644 --- a/app/public/style.css +++ b/app/public/style.css @@ -709,3 +709,20 @@ table tr:not(thead *):nth-child(odd) { table thead th { text-align: left; } + +/* details */ +details { + width: 100%; + margin: var(--pad-4) 0; +} + +details summary { + background: var(--color-super-raised); + padding: var(--pad-2) var(--pad-4); + cursor: pointer; +} + +details .content { + padding: var(--pad-4); + background: var(--color-surface); +} diff --git a/src/markdown.rs b/src/markdown.rs index a5ac837..aba7642 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -1,11 +1,13 @@ use std::collections::HashSet; pub fn render_markdown(input: &str) -> String { - let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color( - &parse_highlight(&parse_link(&parse_image(&parse_image_size( - &parse_underline(&parse_comment(&input.replace("[/]", "
"))), + let html = tetratto_shared::markdown::render_markdown_dirty(&parse_page(&parse_details( + &parse_text_color(&parse_highlight(&parse_link(&parse_image( + &parse_image_size(&parse_underline(&parse_comment( + &input.replace("[/]", "
"), + ))), )))), - )) + ))) .replace("$per", "%"); let mut allowed_attributes = HashSet::new(); @@ -704,3 +706,180 @@ pub fn parse_image(input: &str) -> String { pub fn parse_link(input: &str) -> String { parser_ignores_pre!(parse_link_line, input) } + +/// Match page definitions. +/// +/// Each page is denoted with two at symbols, followed by the name of the page. +/// The page can also have an optional second argument (separated by a semicolon) +/// which accepts the "visible" value; marking the page as visible by default. +/// +/// To close a page (after you're done with the page's content), just put two +/// at symbols with nothing else on the line. +/// +/// You're able to put content AFTER the page closing line. This allows you to have +/// persistant content which is shared between every page. Only content within pages +/// is hidden when navigating to another page. This means everything in the entry +/// that isn't part of a page will remian throughout navigations. +/// +/// # Example +/// ```md +/// @@ home; visible +/// this is the homepage which is shown by default! +/// @@ +/// +/// @@ about +/// this is the about page which is NOT shown by default! a link with an href of "#/about" will open this page +/// @@ +/// ``` +pub fn parse_page(input: &str) -> String { + let mut output = String::new(); + let mut buffer = String::new(); + let mut page_id = String::new(); + + let mut start_shown = false; + let mut in_page = false; + let mut in_pre = false; + + for line in input.split("\n") { + if line.starts_with("```") { + in_pre = !in_pre; + + if in_page { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + + continue; + } + + if in_pre { + if in_page { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + + continue; + } + + // not in pre + if line == "@@" { + // ending block + if in_page { + output.push_str(&format!( + "
\n{}\n
", + if !start_shown { "hidden " } else { "" }, + render_markdown(&buffer) // recurse to render markdown since the renderer is ignoring the div content :/ + )); + + start_shown = false; + in_page = false; + + buffer.clear(); + continue; + } + } else if line.starts_with("@@") { + if !in_page { + in_page = true; + + let x = line.replace("@@", "").trim().to_string(); + let id_parts: Vec<&str> = x.split(";").map(|x| x.trim()).collect(); + page_id = id_parts[0].to_string(); + + if let Some(x) = id_parts.get(1) { + if *x == "visible" { + start_shown = true; + } + } + + continue; + } + } + + // otherwise + if in_page { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + } + + output +} + +/// Parse the markdown syntax for the expandable `
` element. +/// +/// Similar to the [`parse_page`] page definitions, details elements are denoted +/// with two ampersand symbols. The opening line should look like `&& [summary]; [open?]`. +/// +/// The block is closed with a line of exactly two ampersand symbols. +/// +/// # Example +/// ```md +/// && other summary +/// this element starts closed, but can be expanded +/// && +/// ``` +pub fn parse_details(input: &str) -> String { + let mut output = String::new(); + let mut buffer = String::new(); + let mut summary = String::new(); + + let mut in_details = false; + let mut in_pre = false; + + for line in input.split("\n") { + if line.starts_with("```") { + in_pre = !in_pre; + + if in_details { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + + continue; + } + + if in_pre { + if in_details { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + + continue; + } + + // not in pre + if line == "&&" { + // ending block + if in_details { + output.push_str(&format!( + "
{summary}
{}
", + render_markdown(&buffer), + )); + + in_details = false; + buffer.clear(); + continue; + } + } else if line.starts_with("&&") { + if !in_details { + in_details = true; + summary = line.replace("&&", "").trim().to_string(); + continue; + } + } + + // otherwise + if in_details { + buffer.push_str(&format!("{line}\n")); + } else { + output.push_str(&format!("{line}\n")); + } + } + + output +}