fix(html): emit heading id anchors so exported #links resolve
Exported HTML anchor links (TOC entries, [[Page#Heading]], [[#Heading]]) went nowhere because heading elements carried no `id`. Worse, the two TOC builders slugified anchors (`#my-heading`) while the link resolver emits the raw heading text (`#My Heading`, matching upstream vimwiki), so even once ids existed the two halves wouldn't have agreed. Fix — adopt vimwiki's scheme (raw heading text as the anchor) everywhere: - render_heading emits `<hN id="<plain heading text>">`. - build_toc_html (HTML export TOC) uses the raw, HTML-escaped title for both the href and the link text (was slugify). - collect_toc_items (:VimwikiTOC buffer output) uses the raw title for the generated `[[#anchor]]` (was slugify). In-editor navigation slugifies both sides, so it still resolves. Consistency: add canonical `nuwiki_core::ast::inline_text()` and route the heading id, the HTML-TOC title, the buffer-TOC title, and `diagnostics::heading_text` through it — three duplicated extractors collapsed into one, so the anchor and its validation can't drift. Regression test: heading_id_matches_anchor_link_href (core) asserts the heading id and a `#anchor` link's href are identical. Updated the heading assertions in the renderer/export tests for the new `id=` attribute. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -384,10 +384,22 @@ fn render_page_html_numbers_headers_when_enabled() {
|
||||
HashMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(html.contains("<h1>1. Intro</h1>"), "got: {html}");
|
||||
assert!(html.contains("<h2>1.1. Background</h2>"), "got: {html}");
|
||||
assert!(html.contains("<h2>1.2. Methods</h2>"), "got: {html}");
|
||||
assert!(html.contains("<h1>2. Results</h1>"), "got: {html}");
|
||||
assert!(
|
||||
html.contains("<h1 id=\"Intro\">1. Intro</h1>"),
|
||||
"got: {html}"
|
||||
);
|
||||
assert!(
|
||||
html.contains("<h2 id=\"Background\">1.1. Background</h2>"),
|
||||
"got: {html}"
|
||||
);
|
||||
assert!(
|
||||
html.contains("<h2 id=\"Methods\">1.2. Methods</h2>"),
|
||||
"got: {html}"
|
||||
);
|
||||
assert!(
|
||||
html.contains("<h1 id=\"Results\">2. Results</h1>"),
|
||||
"got: {html}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -407,11 +419,14 @@ fn render_page_html_numbering_skips_headers_above_start_level() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
html.contains("<h1>Title</h1>"),
|
||||
html.contains("<h1 id=\"Title\">Title</h1>"),
|
||||
"h1 unnumbered, got: {html}"
|
||||
);
|
||||
assert!(html.contains("<h2>1 Section</h2>"), "got: {html}");
|
||||
assert!(html.contains("<h3>1.1 Sub</h3>"), "got: {html}");
|
||||
assert!(
|
||||
html.contains("<h2 id=\"Section\">1 Section</h2>"),
|
||||
"got: {html}"
|
||||
);
|
||||
assert!(html.contains("<h3 id=\"Sub\">1.1 Sub</h3>"), "got: {html}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -428,8 +443,8 @@ fn render_page_html_no_header_numbering_by_default() {
|
||||
HashMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(html.contains("<h1>Intro</h1>"), "got: {html}");
|
||||
assert!(html.contains("<h2>Sub</h2>"), "got: {html}");
|
||||
assert!(html.contains("<h1 id=\"Intro\">Intro</h1>"), "got: {html}");
|
||||
assert!(html.contains("<h2 id=\"Sub\">Sub</h2>"), "got: {html}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user