diff --git a/chrome/content/zotero/xpcom/editorInstance.js b/chrome/content/zotero/xpcom/editorInstance.js
index fe8da5c6c2..a396c4c644 100644
--- a/chrome/content/zotero/xpcom/editorInstance.js
+++ b/chrome/content/zotero/xpcom/editorInstance.js
@@ -1307,8 +1307,57 @@ class EditorInstance {
html += '\n';
await editorInstance.importImages(jsonAnnotations);
- let { html: serializedHTML, citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(jsonAnnotations, true);
- html += serializedHTML;
+
+ let multipleParentParent = false;
+ let lastParentParentID;
+ let lastParentID;
+ // Group annotations per attachment
+ let groups = [];
+ for (let i = 0; i < annotations.length; i++) {
+ let annotation = annotations[i];
+ let jsonAnnotation = jsonAnnotations[i];
+ let parentParentID = annotation.parentItem.parentID;
+ let parentID = annotation.parentID;
+ if (groups.length) {
+ if (parentParentID !== lastParentParentID) {
+ // Multiple top level regular items detected, allow including their titles
+ multipleParentParent = true;
+ }
+ }
+ if (!groups.length || parentID !== lastParentID) {
+ groups.push({
+ parentTitle: annotation.parentItem.getDisplayTitle(),
+ parentParentID,
+ parentParentTitle: annotation.parentItem.parentItem && annotation.parentItem.parentItem.getDisplayTitle(),
+ jsonAnnotations: [jsonAnnotation]
+ });
+ }
+ else {
+ let group = groups[groups.length - 1];
+ group.jsonAnnotations.push(jsonAnnotation);
+ }
+ lastParentParentID = parentParentID;
+ lastParentID = parentID;
+ }
+ let citationItems = [];
+ lastParentParentID = null;
+ for (let group of groups) {
+ if (multipleParentParent && group.parentParentTitle && lastParentParentID !== group.parentParentID) {
+ html += `
${group.parentParentTitle}
\n`;
+ }
+ lastParentParentID = group.parentParentID;
+ // If attachment doesn't have a parent or there are more attachments with the same parent, show attachment title
+ if (!group.parentParentID || groups.filter(x => x.parentParentID === group.parentParentID).length > 1) {
+ html += `${group.parentTitle}
\n`;
+ }
+ let { html: _html, citationItems: _citationItems } = Zotero.EditorInstanceUtilities.serializeAnnotations(group.jsonAnnotations, true);
+ html += _html + '\n';
+ for (let _citationItem of _citationItems) {
+ if (!citationItems.find(item => item.uris.some(uri => _citationItem.uris.includes(uri)))) {
+ citationItems.push(_citationItem);
+ }
+ }
+ }
citationItems = encodeURIComponent(JSON.stringify(citationItems));
// Note: Update schema version only if using new features
let schemaVersion = 8;
diff --git a/test/tests/annotationsTest.js b/test/tests/annotationsTest.js
index fa97d7f8ba..6070240878 100644
--- a/test/tests/annotationsTest.js
+++ b/test/tests/annotationsTest.js
@@ -403,4 +403,79 @@ describe("Zotero.Annotations", function() {
await Zotero.Items.erase(splitAnnotations.map(x => x.id));
});
});
-})
\ No newline at end of file
+});
+
+describe("Create a note from annotations from multiple items and attachments", function () {
+ it("should create a note from single PDF file containing multiple annotations", async function () {
+ let annotations = [];
+ let attachment = await importPDFAttachment();
+ let annotation1 = await createAnnotation('highlight', attachment);
+ annotations.push(annotation1);
+ let annotation2 = await createAnnotation('highlight', attachment);
+ annotations.push(annotation2);
+ let note = await Zotero.EditorInstance.createNoteFromAnnotations(annotations, null);
+ assert.equal(note.note.split('test.pdf').length - 1, 1);
+ assert.equal(note.note.split(annotation1.annotationText).length - 1, 1);
+ assert.equal(note.note.split(annotation2.annotationText).length - 1, 1);
+ });
+
+ it("should create a note from multiple PDF files containing single annotation", async function () {
+ let annotations = [];
+ let item = await createDataObject('item', { setTitle: true });
+ let attachment1 = await importPDFAttachment(item);
+ let attachment2 = await importPDFAttachment(item);
+ let annotation1 = await createAnnotation('highlight', attachment1);
+ annotations.push(annotation1);
+ let annotation2 = await createAnnotation('highlight', attachment2);
+ annotations.push(annotation2);
+ let note = await Zotero.EditorInstance.createNoteFromAnnotations(annotations, null);
+ assert.equal(note.note.split('test.pdf').length - 1, 2);
+ assert.equal(note.note.split('>' + item.getField('title') + '<').length - 1, 0);
+ assert.equal(note.note.split(annotation1.annotationText).length - 1, 1);
+ assert.equal(note.note.split(annotation2.annotationText).length - 1, 1);
+ });
+
+ it("should create a note from multiple parent items containing single PDF file with single annotation", async function () {
+ let annotations = [];
+ let item1 = await createDataObject('item', { setTitle: true });
+ let item2 = await createDataObject('item', { setTitle: true });
+ let attachment1 = await importPDFAttachment(item1);
+ let attachment2 = await importPDFAttachment(item2);
+ let annotation1 = await createAnnotation('highlight', attachment1);
+ annotations.push(annotation1);
+ let annotation2 = await createAnnotation('highlight', attachment2);
+ annotations.push(annotation2);
+ let note = await Zotero.EditorInstance.createNoteFromAnnotations(annotations, null);
+ assert.equal(note.note.split('test.pdf').length - 1, 0);
+ assert.equal(note.note.split('>' + item1.getField('title') + '<').length - 1, 1);
+ assert.equal(note.note.split('>' + item2.getField('title') + '<').length - 1, 1);
+ assert.equal(note.note.split(annotation1.annotationText).length - 1, 1);
+ assert.equal(note.note.split(annotation2.annotationText).length - 1, 1);
+ });
+
+ it("should create a note from multiple parent items containing multiple PDF files with multiple annotations", async function () {
+ let annotations = [];
+ let item1 = await createDataObject('item', { setTitle: true });
+ let item2 = await createDataObject('item', { setTitle: true });
+ let attachment1 = await importPDFAttachment(item1);
+ let attachment2 = await importPDFAttachment(item2);
+ let attachment3 = await importPDFAttachment(item2);
+ let annotation1 = await createAnnotation('highlight', attachment1);
+ annotations.push(annotation1);
+ let annotation2 = await createAnnotation('highlight', attachment2);
+ annotations.push(annotation2);
+ let annotation3 = await createAnnotation('highlight', attachment3);
+ annotations.push(annotation3);
+ let annotation4 = await createAnnotation('highlight', attachment3);
+ annotations.push(annotation4);
+ let note = await Zotero.EditorInstance.createNoteFromAnnotations(annotations, null);
+ Zotero.debug(note.note);
+ assert.equal(note.note.split('test.pdf').length - 1, 2);
+ assert.equal(note.note.split('>' + item1.getField('title') + '<').length - 1, 1);
+ assert.equal(note.note.split('>' + item2.getField('title') + '<').length - 1, 1);
+ assert.equal(note.note.split(annotation1.annotationText).length - 1, 1);
+ assert.equal(note.note.split(annotation2.annotationText).length - 1, 1);
+ // Check item URIs count
+ assert.equal(note.note.split('zotero.org').length - 1, 16);
+ });
+});