Roll a standalone attachment's annotations up to the attachment itself

_rollUpAnyToLevel() only followed an annotation's parent when the
attachment itself had a parent, so in a search for top-level items, a
tag on a standalone attachment's annotation matched nothing.
This commit is contained in:
Dan Stillman 2026-07-05 16:43:12 -04:00
parent d95d8f294d
commit cc28fad6da
2 changed files with 22 additions and 2 deletions

View file

@ -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"
+ ")";
}
};

View file

@ -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