fix: TOC anchor navigation - remove anchor text from parsed content

- Fix parse_blocks: after extracting anchor from \\x03..\\x04 markers,
  remove the anchor text too (was contaminating display text)
- When TOC jump occurs, clear pending_anchor to prevent saved position
  from overriding the TOC navigation on first frame
- Add tests for heading with/without anchor parsing
This commit is contained in:
Developer
2026-05-22 21:03:56 +08:00
parent 1d2407098c
commit 528d70fc33
2 changed files with 27 additions and 1 deletions

View File

@@ -382,6 +382,7 @@ BgType::Custom(ref path) if !path.is_empty() => {
if let Some(section) = action.jump_to_section { if let Some(section) = action.jump_to_section {
self.state.current_section = section; self.state.current_section = section;
self.state.current_page = 0; self.state.current_page = 0;
self.state.pending_anchor = None;
if let Some(ref anchor) = action.jump_to_anchor { if let Some(ref anchor) = action.jump_to_anchor {
if let Some(ref book) = self.state.book { if let Some(ref book) = self.state.book {
if section < book.sections.len() { if section < book.sections.len() {

View File

@@ -26,11 +26,14 @@ fn parse_blocks(raw_text: &str) -> Vec<ContentBlock> {
if let Some(anchor_end) = after_level.find('\x04') { if let Some(anchor_end) = after_level.find('\x04') {
let anchor_str = &after_level[anchor_start + 1..anchor_end]; let anchor_str = &after_level[anchor_start + 1..anchor_end];
anchor = Some(anchor_str.to_string()); anchor = Some(anchor_str.to_string());
// Remove \x03<anchor>\x04 from text to prevent anchor ID contamination
let rm_start = pos + 2 + anchor_start;
let rm_end = pos + 2 + anchor_end + 1;
text.replace_range(rm_start..rm_end, "");
} }
} }
text.drain(pos..pos + 2); text.drain(pos..pos + 2);
} }
text = text.replace(&['\x03', '\x04'][..], "");
text = text.replace('\x02', ""); text = text.replace('\x02', "");
} }
let text = text.trim().to_string(); let text = text.trim().to_string();
@@ -682,4 +685,26 @@ mod tests {
assert_eq!(blocks[0].text, "段一"); assert_eq!(blocks[0].text, "段一");
assert_eq!(blocks[1].text, "段二"); assert_eq!(blocks[1].text, "段二");
} }
#[test]
fn test_parse_blocks_heading_with_anchor() {
// \x011\x03toc1\x04Chapter 1\x02 → heading_level=1, anchor="toc1", text="Chapter 1"
let blocks = parse_blocks("\x011\x03toc1\x04Chapter 1\x02\n\nbody text");
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].heading_level, 1);
assert_eq!(blocks[0].anchor.as_deref(), Some("toc1"));
assert_eq!(blocks[0].text, "Chapter 1");
assert_eq!(blocks[1].heading_level, 0);
assert_eq!(blocks[1].text, "body text");
}
#[test]
fn test_parse_blocks_heading_without_anchor() {
// \x011Title\x02 → heading_level=1, anchor=None, text="Title"
let blocks = parse_blocks("\x011Title\x02");
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0].heading_level, 1);
assert_eq!(blocks[0].anchor, None);
assert_eq!(blocks[0].text, "Title");
}
} }