diff --git a/docs/static/style.css b/docs/static/style.css index 9ad2c87..a1106ec 100644 --- a/docs/static/style.css +++ b/docs/static/style.css @@ -193,6 +193,31 @@ h3 { margin: 1.5rem 0 0.75rem; } +/* Heading anchor links (¶) - hover reveal for deep-linking */ +.heading-anchor { + visibility: hidden; + margin-left: 0.5em; + font-size: 0.75em; + color: var(--fg-muted); + text-decoration: none; + opacity: 0.5; +} + +.heading-anchor:hover { + color: var(--accent); + opacity: 1; +} + +/* Show anchor on heading hover */ +h1:hover .heading-anchor, +h2:hover .heading-anchor, +h3:hover .heading-anchor, +h4:hover .heading-anchor, +h5:hover .heading-anchor, +h6:hover .heading-anchor { + visibility: visible; +} + p { margin-bottom: 1rem; } diff --git a/docs/templates/base.html b/docs/templates/base.html index d6b679e..53886e7 100644 --- a/docs/templates/base.html +++ b/docs/templates/base.html @@ -41,7 +41,7 @@ {% if page_path == item.path and page.toc and anchors %} {% endif %} @@ -53,7 +53,7 @@ {% if page_path == child.path and page.toc and anchors %} {% endif %} diff --git a/src/render.rs b/src/render.rs index 56e8040..f6fdd51 100644 --- a/src/render.rs +++ b/src/render.rs @@ -172,7 +172,11 @@ pub fn markdown_to_html(markdown: &str) -> (String, Vec) { let insert_pos = pos + format!("", id)); } - html_output.push_str(&format!("\n", level_num)); + // Add pilcrow anchor link for deep-linking (hover-reveal via CSS) + html_output.push_str(&format!( + "\n", + id, level_num + )); // Extract anchor for h2-h6 (skip h1) if level_num >= 2 { @@ -339,7 +343,10 @@ mod tests { fn test_basic_markdown() { let md = "# Hello\n\nThis is a *test*."; let (html, _) = markdown_to_html(md); - assert!(html.contains("

Hello

")); + // Heading includes pilcrow anchor for deep-linking + assert!(html.contains( + "

Hello

" + )); assert!(html.contains("test")); } diff --git a/src/template_engine.rs b/src/template_engine.rs index 41b675d..03fd020 100644 --- a/src/template_engine.rs +++ b/src/template_engine.rs @@ -1,9 +1,10 @@ //! Tera-based template engine for runtime HTML generation. +use std::collections::HashMap; use std::path::Path; use serde::Serialize; -use tera::{Context, Tera}; +use tera::{Context, Tera, Value}; use crate::config::SiteConfig; use crate::content::{Content, NavItem}; @@ -19,7 +20,11 @@ impl TemplateEngine { /// Load templates from a directory (glob pattern: `templates/**/*`). pub fn new(template_dir: &Path) -> Result { let pattern = template_dir.join("**/*").display().to_string(); - let tera = Tera::new(&pattern).map_err(Error::TemplateLoad)?; + let mut tera = Tera::new(&pattern).map_err(Error::TemplateLoad)?; + + // Register custom filters + tera.register_filter("strip_parens", strip_parens_filter); + Ok(Self { tera }) } @@ -198,6 +203,23 @@ impl ContentContext { } } +/// Tera filter to strip parenthetical text from strings. +/// E.g., "Content (in Section)" → "Content" +fn strip_parens_filter(value: &Value, _args: &HashMap) -> tera::Result { + match value { + Value::String(s) => { + // Find opening paren and trim everything from there + let result = if let Some(pos) = s.find('(') { + s[..pos].trim().to_string() + } else { + s.clone() + }; + Ok(Value::String(result)) + } + _ => Ok(value.clone()), + } +} + #[cfg(test)] mod tests { use super::*;