diff --git a/crates/nuwiki-core/src/render/html.rs b/crates/nuwiki-core/src/render/html.rs index b8bea65..2e4f935 100644 --- a/crates/nuwiki-core/src/render/html.rs +++ b/crates/nuwiki-core/src/render/html.rs @@ -59,6 +59,10 @@ pub struct HtmlRenderer { /// vimwiki `html_header_numbering_sym`: a symbol appended after the /// section number (e.g. `.` → `1.2. Heading`). Empty by default. header_numbering_sym: String, + /// vimwiki `toc_header`: the heading text that identifies the TOC + /// section. When a heading matches this value (case-insensitive), it + /// gets `class="toc"` in HTML export. + toc_header: String, /// vimwiki `valid_html_tags`: inline HTML tag names passed through to /// export verbatim (instead of escaping `<`/`>`). Empty = escape all. /// `span` is always allowed so colour spans render. @@ -90,6 +94,7 @@ impl HtmlRenderer { color_template: "__CONTENT__".to_string(), header_numbering: 0, header_numbering_sym: String::new(), + toc_header: String::new(), valid_html_tags: Vec::new(), emoji_enable: false, text_ignore_newline: true, @@ -183,6 +188,13 @@ impl HtmlRenderer { self.header_numbering_sym = sym.into(); self } + + /// Set the vimwiki TOC heading text. When a heading matches this value + /// (case-insensitive) it gets `class="toc"` in HTML export. + pub fn with_toc_header(mut self, header: impl Into) -> Self { + self.toc_header = header.into(); + self + } } /// Running state for vimwiki-style HTML section numbering @@ -314,15 +326,27 @@ impl HtmlRenderer { /// pass `""` for no numbering. fn render_heading(&self, n: &HeadingNode, number: &str, w: &mut dyn Write) -> io::Result<()> { let level = n.level.clamp(1, 6); - let class = if n.centered { - " class=\"centered\"" - } else { - "" - }; - // `id` is the heading's plain text (vimwiki's anchor scheme), so - // `[[Page#Heading]]` and TOC `#Heading` links land here. Matches the - // anchor text the LSP validates and the resolver emits. let anchor = crate::ast::inline_text(&n.children); + let is_toc = self + .toc_header + .as_bytes() + .iter() + .zip(anchor.as_bytes()) + .all(|(a, b)| a.eq_ignore_ascii_case(b)) + && !self.toc_header.is_empty() + && self.toc_header.len() == anchor.len(); + let mut classes = Vec::new(); + if is_toc { + classes.push("toc"); + } + if n.centered { + classes.push("centered"); + } + let class = if classes.is_empty() { + String::new() + } else { + format!(" class=\"{}\"", classes.join(" ")) + }; write!(w, "")?; diff --git a/crates/nuwiki-lsp/src/config.rs b/crates/nuwiki-lsp/src/config.rs index 7ad8d78..d4e3025 100644 --- a/crates/nuwiki-lsp/src/config.rs +++ b/crates/nuwiki-lsp/src/config.rs @@ -176,6 +176,10 @@ pub struct HtmlConfig { /// vimwiki `html_header_numbering_sym`: symbol appended after the /// section number (e.g. `.` → `1.2. Heading`). Empty by default. pub html_header_numbering_sym: String, + /// vimwiki `toc_header`: the heading text that identifies the TOC + /// section. When a heading matches this value (case-insensitive), it + /// gets `class="toc"` in HTML export. + pub toc_header: String, /// vimwiki `rss_name`: filename of the generated RSS feed (default /// `rss.xml`), relative to `html_path`. pub rss_name: String, @@ -224,6 +228,7 @@ impl Default for HtmlConfig { base_url: String::new(), html_header_numbering: 0, html_header_numbering_sym: String::new(), + toc_header: default_toc_header(), rss_name: "rss.xml".into(), rss_max_items: 10, valid_html_tags: default_valid_html_tags(), @@ -954,6 +959,9 @@ impl From for WikiConfig { if let Some(s) = r.html_header_numbering_sym { html.html_header_numbering_sym = s; } + if let Some(ref s) = r.toc_header { + html.toc_header = s.clone(); + } if let Some(s) = r.rss_name { html.rss_name = s; } diff --git a/crates/nuwiki-lsp/src/export.rs b/crates/nuwiki-lsp/src/export.rs index 405a009..4d9ba68 100644 --- a/crates/nuwiki-lsp/src/export.rs +++ b/crates/nuwiki-lsp/src/export.rs @@ -259,6 +259,7 @@ pub fn render_page_html( cfg.html_header_numbering_sym.clone(), ); } + r = r.with_toc_header(&cfg.toc_header); if !cfg.valid_html_tags.is_empty() { r = r.with_valid_html_tags(cfg.valid_html_tags.clone()); } diff --git a/lua/nuwiki/config.lua b/lua/nuwiki/config.lua index d175d9a..05936d9 100644 --- a/lua/nuwiki/config.lua +++ b/lua/nuwiki/config.lua @@ -35,10 +35,19 @@ M.defaults = { -- custom_wiki2html, custom_wiki2html_args, base_url, -- toc_header, toc_header_level, links_header, links_header_level, -- tags_header, tags_header_level, + -- html_header_numbering, html_header_numbering_sym, -- listsyms, listsym_rejected, listsyms_propagate, list_margin, -- links_space_char wikis = nil, + -- TOC defaults (per-wiki, also accepted as top-level for single-wiki setups) + toc_header = 'Contents', + toc_header_level = 1, + + -- HTML export defaults (per-wiki, also accepted as top-level for single-wiki setups) + html_header_numbering = 0, + html_header_numbering_sym = '', + -- Per-buffer glue. Each subgroup mirrors vimwiki's -- `g:vimwiki_key_mappings` shape and can be flipped off -- independently to suppress that group of keymaps.