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:
@@ -492,7 +492,9 @@ fn toc_edit_inserts_at_top_when_no_existing_toc() {
|
||||
// Insertion at position 0,0 → zero-width range.
|
||||
assert_eq!(te.range.start, te.range.end);
|
||||
assert!(te.new_text.starts_with("= Contents =\n"));
|
||||
assert!(te.new_text.contains("[[#one|One]]"));
|
||||
// Anchor is the raw heading text (vimwiki scheme), matching the heading id
|
||||
// the HTML renderer emits on export.
|
||||
assert!(te.new_text.contains("[[#One|One]]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -504,7 +506,7 @@ fn toc_edit_replaces_existing_toc() {
|
||||
let changes = edit.changes.expect("changes map");
|
||||
let edits = &changes[&uri];
|
||||
let te = &edits[0];
|
||||
assert!(te.new_text.contains("[[#real|Real]]"));
|
||||
assert!(te.new_text.contains("[[#Real|Real]]"));
|
||||
assert!(!te.new_text.contains("Stale"));
|
||||
// The replacement range must start at line 0.
|
||||
assert_eq!(te.range.start.line, 0);
|
||||
@@ -536,7 +538,7 @@ fn toc_rebuild_edit_only_acts_when_toc_already_present() {
|
||||
let edit =
|
||||
ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1, 0, 0).expect("rebuild edit");
|
||||
let te = &edit.changes.unwrap()[&uri][0];
|
||||
assert!(te.new_text.contains("[[#real|Real]]"));
|
||||
assert!(te.new_text.contains("[[#Real|Real]]"));
|
||||
assert!(!te.new_text.contains("Stale"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user