From 3f282fc25f687a70b50133ac43a903fb1bfcba7e Mon Sep 17 00:00:00 2001 From: Abe Jellinek Date: Sat, 9 Jul 2022 01:37:14 -0400 Subject: [PATCH] `recursiveCollections`: Remove from all subcollections on delete (#2689) --- chrome/content/zotero/itemTree.jsx | 26 +++++++++++--- chrome/content/zotero/zoteroPane.js | 38 ++++++++++++++++---- chrome/locale/en-US/zotero/zotero.properties | 3 ++ test/tests/zoteroPaneTest.js | 29 +++++++++++++++ 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index f9f114712b..cf5f806ebd 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -474,14 +474,17 @@ var ItemTree = class ItemTree extends LibraryTree { return; } + var visibleSubcollections = Zotero.Prefs.get('recursiveCollections') + ? collectionTreeRow.ref.getDescendents(false, 'collection') + : []; var splitIDs = []; for (let id of ids) { var split = id.split('-'); - // Skip if not an item in this collection - if (split[0] != collectionTreeRow.ref.id) { - continue; + // Include if an item in this collection or a visible subcollection + if (split[0] == collectionTreeRow.ref.id + || visibleSubcollections.some(c => split[0] == c.id)) { + splitIDs.push(split[1]); } - splitIDs.push(split[1]); } ids = splitIDs; } @@ -1740,8 +1743,21 @@ var ItemTree = class ItemTree extends LibraryTree { await Zotero.Items.trashTx(ids); } else if (collectionTreeRow.isCollection()) { + let collectionIDs = [collectionTreeRow.ref.id]; + if (Zotero.Prefs.get('recursiveCollections')) { + collectionIDs.push(...collectionTreeRow.ref.getDescendents(false, 'collection').map(c => c.id)); + } + await Zotero.DB.executeTransaction(async () => { - await collectionTreeRow.ref.removeItems(ids); + for (let itemID of ids) { + let item = Zotero.Items.get(itemID); + for (let collectionID of collectionIDs) { + item.removeFromCollection(collectionID); + } + await item.save({ + skipDateModifiedUpdate: true + }); + } }); } else if (collectionTreeRow.isPublications()) { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index a4e1c56af5..af6dd1d727 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1877,13 +1877,39 @@ var ZoteroPane = new function() var prompt = (force && !fromMenu) ? false : toTrash; } else if (collectionTreeRow.isCollection()) { - - // Ignore unmodified action if only child items are selected - if (!force && this.itemsView.getSelectedItems().every(item => !item.isTopLevelItem())) { - return; + if (force) { + var prompt = toTrash; + } + else { + // Ignore unmodified action if only child items are selected + if (this.itemsView.getSelectedItems().every(item => !item.isTopLevelItem())) { + return; + } + + // If unmodified, recursiveCollections is true, and items are in + // descendant collections (even if also in the selected collection), + // prompt to remove from all + if (Zotero.Prefs.get('recursiveCollections')) { + let descendants = collectionTreeRow.ref.getDescendents(false, 'collection'); + let inSubcollection = descendants + .some(({ id }) => this.itemsView.getSelectedItems() + .some(item => item.inCollection(id))); + if (inSubcollection) { + var prompt = { + title: Zotero.getString('pane.items.removeRecursive.title'), + text: Zotero.getString( + 'pane.items.removeRecursive' + (this.itemsView.selection.count > 1 ? '.multiple' : '') + ) + }; + } + else { + var prompt = toRemove; + } + } + else { + var prompt = toRemove; + } } - - var prompt = force ? toTrash : toRemove; } else if (collectionTreeRow.isTrash() || collectionTreeRow.isBucket()) { var prompt = toDelete; diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 966ecd5029..0dc5631098 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -328,6 +328,9 @@ pane.items.remove.multiple = Are you sure you want to remove the selected items pane.items.removeFromPublications.title = Remove from My Publications pane.items.removeFromPublications = Are you sure you want to remove the selected item from My Publications? pane.items.removeFromPublications.multiple = Are you sure you want to remove the selected items from My Publications? +pane.items.removeRecursive.title = Remove from Collection and Subcollections +pane.items.removeRecursive = Are you sure you want to remove the selected item from this collection and all its subcollections? +pane.items.removeRecursive.multiple = Are you sure you want to remove the selected items from this collection and all its subcollections? pane.items.menu.addNoteFromAnnotations = Add Note from Annotations pane.items.menu.addNoteFromAnnotations.multiple = Add Notes from Annotations pane.items.menu.findAvailablePDF = Find Available PDF diff --git a/test/tests/zoteroPaneTest.js b/test/tests/zoteroPaneTest.js index 6f552027a0..65d401f3f2 100644 --- a/test/tests/zoteroPaneTest.js +++ b/test/tests/zoteroPaneTest.js @@ -600,6 +600,35 @@ describe("ZoteroPane", function() { assert.isTrue(item.deleted); }); + + it("should prompt to remove an item from subcollections when recursiveCollections enabled", async function () { + Zotero.Prefs.set('recursiveCollections', true); + + let collection1 = await createDataObject('collection'); + let collection2 = await createDataObject('collection', { parentID: collection1.id }); + let item = await createDataObject('item', { collections: [collection2.id] }); + assert.ok(await zp.collectionsView.selectCollection(collection1.id)); + + await waitForItemsLoad(win); + + let iv = zp.itemsView; + assert.ok(await iv.selectItem(item.id)); + + await Zotero.Promise.delay(1); + + let promise = waitForDialog(); + let modifyPromise = waitForItemEvent('modify'); + + await zp.deleteSelectedItems(false); + + let dialog = await promise; + await modifyPromise; + + assert.include(dialog.document.documentElement.textContent, Zotero.getString('pane.items.removeRecursive')); + assert.isFalse(item.inCollection(collection2.id)); + + Zotero.Prefs.clear('recursiveCollections'); + }); }); describe("#deleteSelectedCollection()", function () {