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.