diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js index 19c3d7f5e7..b50672272d 100644 --- a/chrome/content/zotero/xpcom/data/search.js +++ b/chrome/content/zotero/xpcom/data/search.js @@ -2391,13 +2391,15 @@ Zotero.Search._rollUpAnyToLevel = function (sql, toLevel) { // annotation of an attachment), matches case 'item': default: + // For an annotation, prefer its attachment's parent, but fall back to the + // attachment itself (annot.parentItemID) when the attachment is standalone return "itemID IN (" - + "SELECT COALESCE(aAtt.parentItemID, att.parentItemID, note.parentItemID, m.itemID) " + + "SELECT COALESCE(aAtt.parentItemID, annot.parentItemID, att.parentItemID, note.parentItemID, m.itemID) " + `FROM (${matches}) m ` + "LEFT JOIN itemAttachments att ON att.itemID = m.itemID AND att.parentItemID IS NOT NULL " + "LEFT JOIN itemNotes note ON note.itemID = m.itemID AND note.parentItemID IS NOT NULL " + "LEFT JOIN itemAnnotations annot ON annot.itemID = m.itemID " - + "LEFT JOIN itemAttachments aAtt ON aAtt.itemID = annot.parentItemID AND aAtt.parentItemID IS NOT NULL" + + "LEFT JOIN itemAttachments aAtt ON aAtt.itemID = annot.parentItemID" + ")"; } }; diff --git a/test/tests/searchTest.js b/test/tests/searchTest.js index 8bd81a2c0b..64f9b90456 100644 --- a/test/tests/searchTest.js +++ b/test/tests/searchTest.js @@ -660,6 +660,24 @@ describe("Zotero.Search", function () { await standalone.eraseTx(); }); + it("should return a standalone attachment whose annotation has a tag, in a search for top-level items", async function () { + // Same as above, but for a level-agnostic condition (tag), which rolls up + // separately from a fixed-level one like annotationComment + var tag = 'zsat' + Zotero.Utilities.randomString(); + var standalone = await importPDFAttachment(); // top-level PDF, no parent + var annotation = await createAnnotation('highlight', standalone); + annotation.addTag(tag); + await annotation.saveTx(); + + var s = new Zotero.Search(); + s.libraryID = userLibraryID; + s.addCondition('resultLevel', 'item'); + s.addCondition('tag', 'is', tag); + assert.sameMembers(await s.search(), [standalone.id]); + + await standalone.eraseTx(); + }); + it("should bind a same-entity group below a non-item result level", async function () { // Result level attachment + a group scoped to annotation: find attachments