Table of contents: Replace and Remove take the heading paragraph above the field's paragraph with the table (#4039)

This commit is contained in:
aiongg
2026-10-24 21:05:19 +00:00
parent 17804c706f
commit 34ec59fee1
2 changed files with 18 additions and 4 deletions
+14 -4
View File
@@ -338,21 +338,31 @@ fn toc_range(s: &Session) -> Option<(usize, usize)> {
}
/// Removes the TOC blocks `start..=end` and returns where the TOC was. A field paragraph that ends
/// a section stays, emptied, so its section break isn't lost (#3889).
/// a section stays, emptied, so its section break isn't lost (#3889). A heading paragraph of its
/// own just above the field's paragraph (Word's tables start the field after their heading) goes
/// with the table.
fn remove_toc(s: &mut Session, start: usize, end: usize) -> Result<usize, CmdError> {
for i in (start + 1..=end).rev() {
s.doc.remove_block(StoryRef::Body, &Path::top(i))?;
}
match s.doc.para_mut(StoryRef::Body, &Path::top(start)) {
let at = match s.doc.para_mut(StoryRef::Body, &Path::top(start)) {
Ok(p) if p.section.is_some() => {
*p = Paragraph { section: p.section.take(), ..Paragraph::new() };
Ok(start + 1)
start + 1
}
_ => {
s.doc.remove_block(StoryRef::Body, &Path::top(start))?;
Ok(start)
start
}
};
let titled = start.checked_sub(1).and_then(|i| s.doc.body.get(i)).and_then(|b| b.as_para()).is_some_and(|p| {
p.props.style.as_deref() == Some("TOCHeading") && p.section.is_none() && toc_field(p).is_none()
});
if titled {
s.doc.remove_block(StoryRef::Body, &Path::top(start - 1))?;
return Ok(at - 1);
}
Ok(at)
}
/// The caret is in the table of contents (its heading or an entry), where Word's context menu
+4
View File
@@ -3421,10 +3421,14 @@ fn toc_in_columns_with_localized_styles_keeps_its_layout_through_update_table()
// The field's paragraph ends the one-column section: replacing or removing the TOC keeps it.
let columns = |s: &Session| s.doc.sections().iter().map(|(_, x)| x.columns.count).collect::<Vec<_>>();
assert_eq!(columns(&s), [1, 2, 1]);
// The heading goes with the table: one "Contents" after Replace, none after Remove.
let titles = |s: &Session| s.doc.body.iter().filter_map(|b| b.as_para()).filter(|p| p.plain_text() == "Contents").count();
run(&mut s, "references.toc", json!({"levels": 2}));
assert_eq!(columns(&s), [1, 2, 1], "{}", text(&s));
assert_eq!(titles(&s), 1, "{}", text(&s));
run(&mut s, "references.removeToc", json!({}));
assert_eq!(columns(&s), [1, 2, 1], "{}", text(&s));
assert_eq!(titles(&s), 0, "{}", text(&s));
}
/// #2182: with two TOCs only the one Update Table would refresh is offered Update Field; the other