add: multiple pages in one entry, details syntax

This commit is contained in:
trisua 2025-08-14 03:11:46 -04:00
parent db63427795
commit a127a0407d
3 changed files with 217 additions and 4 deletions

View file

@ -349,3 +349,20 @@ globalThis.toggle_metadata_css = (e) => {
window.location.reload(); 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);

View file

@ -709,3 +709,20 @@ table tr:not(thead *):nth-child(odd) {
table thead th { table thead th {
text-align: left; 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);
}

View file

@ -1,11 +1,13 @@
use std::collections::HashSet; use std::collections::HashSet;
pub fn render_markdown(input: &str) -> String { pub fn render_markdown(input: &str) -> String {
let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color( let html = tetratto_shared::markdown::render_markdown_dirty(&parse_page(&parse_details(
&parse_highlight(&parse_link(&parse_image(&parse_image_size( &parse_text_color(&parse_highlight(&parse_link(&parse_image(
&parse_underline(&parse_comment(&input.replace("[/]", "<br />"))), &parse_image_size(&parse_underline(&parse_comment(
&input.replace("[/]", "<br />"),
))),
)))), )))),
)) )))
.replace("$per", "%"); .replace("$per", "%");
let mut allowed_attributes = HashSet::new(); let mut allowed_attributes = HashSet::new();
@ -704,3 +706,180 @@ pub fn parse_image(input: &str) -> String {
pub fn parse_link(input: &str) -> String { pub fn parse_link(input: &str) -> String {
parser_ignores_pre!(parse_link_line, input) 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!(
"<div id=\"#/{page_id}\" class=\"{}subpage no_p_margin fadein\">\n{}\n</div>",
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 `<details>` 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!(
"<details><summary>{summary}</summary><div class=\"content\">{}</div></details>",
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
}