diff --git a/crates/doc/src/notes.rs b/crates/doc/src/notes.rs index b681542c..694e9108 100644 --- a/crates/doc/src/notes.rs +++ b/crates/doc/src/notes.rs @@ -166,15 +166,18 @@ impl Story { self.text.match_indices(FOOTNOTE_REF).nth(k).map(|(i, _)| i) } - /// Insert a footnote reference at `pos` with the given footnote text. Returns the footnote id. - pub fn insert_note(&mut self, pos: usize, text: &str, para: ParaFormat) -> u64 { + /// Insert a footnote reference at `pos` with the given footnote text. Returns the footnote + /// id, or `None` if the story has no footnote for the new reference. + pub fn insert_note(&mut self, pos: usize, text: &str, para: ParaFormat) -> Option { let pos = crate::story::floor_char_boundary(&self.text, pos.min(self.len())); let fmt = self.char_format_at(pos).clone(); self.insert_with(pos, &FOOTNOTE_REF.to_string(), fmt); + // A file may hold fewer footnotes than references; give every reference one. + self.fix_notes(); let k = self.notes_before(pos); - let note = Arc::make_mut(&mut self.notes[k]); + let note = Arc::make_mut(self.notes.get_mut(k)?); note.text = Story::with_text(StoryId(0), text, para); - note.id + Some(note.id) } pub(crate) fn next_note_id(&self) -> u64 { @@ -283,8 +286,8 @@ mod tests { #[test] fn references_own_notes_in_order() { let mut s = st("alpha beta gamma"); - let b = s.insert_note(10, "second", ParaFormat::default()); - let a = s.insert_note(5, "first", ParaFormat::default()); + let b = s.insert_note(10, "second", ParaFormat::default()).unwrap(); + let a = s.insert_note(5, "first", ParaFormat::default()).unwrap(); s.check().unwrap(); assert_eq!(s.notes.len(), 2); assert_eq!(s.notes[0].id, a); diff --git a/crates/engine/src/cmd/editorial.rs b/crates/engine/src/cmd/editorial.rs index 3ebce6b0..058068bb 100644 --- a/crates/engine/src/cmd/editorial.rs +++ b/crates/engine/src/cmd/editorial.rs @@ -142,18 +142,21 @@ mod tests { s.execute("file.new", &json!({})).unwrap(); let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "Draft copy."})).unwrap(); let sid = designcraft_doc::StoryId(r["story"].as_u64().unwrap()); - // A damaged file: a note anchor and an endnote reference without payloads. + // A damaged file: a note anchor, an endnote and a footnote reference without payloads. let st = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).story_mut(sid).unwrap(); - st.insert(0, &format!("{}{}", designcraft_doc::NOTE_MARK, designcraft_doc::ENDNOTE_REF)); + st.insert(0, &format!("{}{}{}", designcraft_doc::NOTE_MARK, designcraft_doc::ENDNOTE_REF, designcraft_doc::FOOTNOTE_REF)); st.editorial.clear(); st.endnotes.clear(); + st.notes.clear(); let end = st.len(); s.execute("text.select", &json!({"story": r["story"], "anchor": end, "focus": end})).unwrap(); s.execute("note.new", &json!({"text": "check this"})).unwrap(); s.execute("endnote.insert", &json!({"text": "An endnote."})).unwrap(); + s.execute("footnote.insert", &json!({"text": "A footnote."})).unwrap(); let st = &s.doc().unwrap().doc.stories[&sid]; st.check().unwrap(); assert_eq!(st.editorial[1].text, "check this"); assert_eq!(st.endnotes[1].text.text, "An endnote."); + assert_eq!(st.notes[1].text.text, "A footnote."); } } diff --git a/crates/engine/src/cmd/notes.rs b/crates/engine/src/cmd/notes.rs index 4d7b0f13..8f67d159 100644 --- a/crates/engine/src/cmd/notes.rs +++ b/crates/engine/src/cmd/notes.rs @@ -66,7 +66,7 @@ fn insert(s: &mut Session, p: &Value) -> Result { let r = t.range(); let r = r.start.min(st.len())..r.end.min(st.len()); st.delete(r.clone()); - let id = st.insert_note(r.start, &text, para); + let id = st.insert_note(r.start, &text, para).ok_or_else(|| bad("footnote.insert", "the story's footnotes don't match their references"))?; sel.text = Some(TextSel { story: t.story, anchor: text.len(), focus: text.len(), frame: t.frame, cell: Some(CellAddr::footnote(id)) }); Ok(json!({"story": t.story.0, "id": id})) }) diff --git a/crates/textimport/src/docx.rs b/crates/textimport/src/docx.rs index 67a2b71a..9db74ecf 100644 --- a/crates/textimport/src/docx.rs +++ b/crates/textimport/src/docx.rs @@ -305,9 +305,13 @@ impl Ctx { self.blocks(¬e, &mut ns, false); // Word puts the reference mark in the note text: we generate the number. let len = st.len(); - let id = st.insert_note(len, "", ParaFormat::default()); - if let Some(n) = st.note_mut(id) { - n.text = ns; + match st.insert_note(len, "", ParaFormat::default()) { + Some(id) => { + if let Some(n) = st.note_mut(id) { + n.text = ns; + } + } + None => self.warnings.push("A footnote in the Word file could not be imported.".into()), } } continue;