mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-15 15:02:23 +00:00
Gap 19: ODT export keeps replies to replies
Export wrote only the direct replies of a comment, so a reply to a reply was lost on save. Every reply in a comment's thread is written now, each answering the comment that starts the thread, as LibreOffice's threads are flat. A reply chain that loops (hostile) is written once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBVQLyDnrG8Tn1yDQcUabC
This commit is contained in:
2 files changed
+29
-3
No files matched your search
@@ -139,12 +139,21 @@ fn rich_inlines(doc: &Document, o: &InlineObject, f: &Fmt, opts: FlowOpts) -> Op
|
||||
let endnote = *kind == wordcraft_doc::para::NoteKind::Endnote;
|
||||
vec![Inline::Note { endnote, custom: custom.clone(), blocks }]
|
||||
}
|
||||
// The comment, then its replies, which have no place of their own.
|
||||
// The comment, then the replies in its thread (replies to replies too, each answering the
|
||||
// comment that starts the thread), which have no place of their own.
|
||||
InlineObject::CommentStart { id } if opts.comments => {
|
||||
let Some(c) = doc.comments.get(id) else { return Some(Vec::new()) };
|
||||
let mut out = vec![Inline::Comment { id: *id, comment: Box::new(flow_comment(doc, c, None, opts)) }];
|
||||
for (rid, r) in doc.comments.iter().filter(|(_, r)| r.parent == Some(*id)) {
|
||||
out.push(Inline::Comment { id: *rid, comment: Box::new(flow_comment(doc, r, Some(*id), opts)) });
|
||||
let mut thread = std::collections::BTreeSet::from([*id]);
|
||||
let mut k = 0;
|
||||
while let Some(Inline::Comment { id: at, .. }) = out.get(k) {
|
||||
let at = *at;
|
||||
k += 1;
|
||||
for (rid, r) in doc.comments.iter().filter(|(_, r)| r.parent == Some(at)) {
|
||||
if thread.insert(*rid) {
|
||||
out.push(Inline::Comment { id: *rid, comment: Box::new(flow_comment(doc, r, Some(*id), opts)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -168,3 +168,20 @@ fn unmatched_and_hostile_comments_do_not_panic() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_reply_to_a_reply_is_kept() {
|
||||
let xml = concat!(
|
||||
r#"<text:p><office:annotation office:name="a"><dc:creator>A</dc:creator><text:p>Root</text:p></office:annotation>x<office:annotation-end office:name="a"/>"#,
|
||||
r#"<office:annotation office:name="b" loext:parent-name="a"><dc:creator>B</dc:creator><text:p>Reply</text:p></office:annotation>"#,
|
||||
r#"<office:annotation loext:parent-name="b"><dc:creator>C</dc:creator><text:p>Reply to reply</text:p></office:annotation></text:p>"#,
|
||||
);
|
||||
let d = import(&body(xml)).unwrap();
|
||||
assert_eq!(d.comments.len(), 3);
|
||||
// LibreOffice's threads are flat: every reply answers the comment that starts them.
|
||||
let back = import(&export(&d).unwrap()).unwrap();
|
||||
let got = comments(&back);
|
||||
assert_eq!(got.len(), 3, "{got:?}");
|
||||
assert_eq!(got[2].4.as_deref(), Some("Root"));
|
||||
assert_eq!(got[2].5, "Reply to reply");
|
||||
}
|
||||
Reference in new issue
Block a user