diff --git a/chrome/content/zotero/bindings/tagselector.xml b/chrome/content/zotero/bindings/tagselector.xml index f10463302..3290318d5 100644 --- a/chrome/content/zotero/bindings/tagselector.xml +++ b/chrome/content/zotero/bindings/tagselector.xml @@ -833,6 +833,59 @@ + + + + + + + { + Zotero.updateZoteroPaneProgressMeter( + Math.round(progress / progressMax * 100) + ); + } + ); + } + finally { + Zotero.hideZoteroPaneOverlays(); + } + } + }.bind(this))(); + ]]> + + + @@ -1113,6 +1166,7 @@ + + diff --git a/chrome/content/zotero/xpcom/data/tags.js b/chrome/content/zotero/xpcom/data/tags.js index 599e70eec..a3ae45476 100644 --- a/chrome/content/zotero/xpcom/data/tags.js +++ b/chrome/content/zotero/xpcom/data/tags.js @@ -304,64 +304,93 @@ Zotero.Tags = new function() { /** * @return {Promise} */ - this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs) { + this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs, onProgress) { + var d = new Date(); + tagIDs = Zotero.flattenArguments(tagIDs); var deletedNames = []; - var oldItemIDs = []; + var done = 0; - yield Zotero.DB.executeTransaction(function* () { - var notifierPairs = []; - var notifierData = {}; - for (let i=0; i a - b); + + for (let tagID of chunk) { + let name = this.getName(tagID); + if (name === false) { + continue; + } + deletedNames.push(name); + + // Since we're performing the DELETE query directly, + // get the list of items that will need their tags reloaded, + // and generate data for item-tag notifications + let itemIDs = [] + while (i < chunkTagItems.length && chunkTagItems[i].tagID == tagID) { + itemIDs.push(chunkTagItems[i].itemID); + i++; + } + for (let itemID of itemIDs) { + let pair = itemID + "-" + tagID; + notifierPairs.push(pair); + notifierData[pair] = { + libraryID: libraryID, + tag: name + }; + } + oldItemIDs = oldItemIDs.concat(itemIDs); + } + if (oldItemIDs.length) { + Zotero.Notifier.queue('remove', 'item-tag', notifierPairs, notifierData); + } + + var sql = "DELETE FROM itemTags WHERE tagID IN (" + + Array(chunk.length).fill('?').join(', ') + ") AND itemID IN " + + "(SELECT itemID FROM items WHERE libraryID=?)"; + yield Zotero.DB.queryAsync(sql, chunk.concat([libraryID])); + + yield this.purge(chunk); + + // Update internal timestamps on all items that had these tags + yield Zotero.Utilities.Internal.forEachChunkAsync( + Zotero.Utilities.arrayUnique(oldItemIDs), + Zotero.DB.MAX_BOUND_PARAMETERS - 1, + Zotero.Promise.coroutine(function* (chunk2) { + var placeholders = Array(chunk2.length).fill('?').join(','); + + sql = 'UPDATE items SET synced=0, clientDateModified=? ' + + 'WHERE itemID IN (' + placeholders + ')' + yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk2)); + + yield Zotero.Items.reload(oldItemIDs, ['primaryData', 'tags'], true); + }) + ); + + done += chunk.length; + }.bind(this)); - // Since we're performing the DELETE query directly, - // get the list of items that will need their tags reloaded, - // and generate data for item-tag notifications - let tagItems = yield this.getTagItems(libraryID, tagID); - for (let j = 0; j < tagItems.length; j++) { - let itemID = tagItems[i]; - let pair = itemID + "-" + tagID; - notifierPairs.push(pair); - notifierData[pair] = { - libraryID: libraryID, - tag: name - }; + if (onProgress) { + onProgress(done, tagIDs.length); } - oldItemIDs = oldItemIDs.concat(tagItems); - } - if (oldItemIDs.length) { - Zotero.Notifier.queue('remove', 'item-tag', notifierPairs, notifierData); - } - - var sql = "DELETE FROM itemTags WHERE tagID IN (" - + tagIDs.map(x => '?').join(', ') + ") AND itemID IN " - + "(SELECT itemID FROM items WHERE libraryID=?)"; - yield Zotero.DB.queryAsync(sql, tagIDs.concat([libraryID])); - - yield this.purge(tagIDs); - - // Update internal timestamps on all items that had these tags - yield Zotero.Utilities.Internal.forEachChunkAsync( - Zotero.Utilities.arrayUnique(oldItemIDs), - Zotero.DB.MAX_BOUND_PARAMETERS - 1, - Zotero.Promise.coroutine(function* (chunk) { - let placeholders = chunk.map(() => '?').join(','); - - sql = 'UPDATE items SET synced=0, clientDateModified=? ' - + 'WHERE itemID IN (' + placeholders + ')' - yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk)); - - yield Zotero.Items.reload(oldItemIDs, ['primaryData', 'tags'], true); - }) - ); - }.bind(this)); + }.bind(this) + ); // Also delete tag color setting // @@ -371,9 +400,35 @@ Zotero.Tags = new function() { for (let i=0; i + diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index a2a31cb4f..e0707ef0e 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -234,6 +234,8 @@ pane.tagSelector.rename.title = Rename Tag pane.tagSelector.rename.message = Please enter a new name for this tag.\n\nThe tag will be changed in all associated items. pane.tagSelector.delete.title = Delete Tag pane.tagSelector.delete.message = Are you sure you want to delete this tag?\n\nThe tag will be removed from all items. +pane.tagSelector.deleteAutomatic.title = Delete Automatic Tags +pane.tagSelector.deleteAutomatic.message = Are you sure you want to delete %1$S automatic tag in this library?;Are you sure you want to delete %1$S automatic tags in this library? pane.tagSelector.numSelected.none = 0 tags selected pane.tagSelector.numSelected.singular = %S tag selected pane.tagSelector.numSelected.plural = %S tags selected diff --git a/test/tests/tagsTest.js b/test/tests/tagsTest.js index 1bffdf670..606346985 100644 --- a/test/tests/tagsTest.js +++ b/test/tests/tagsTest.js @@ -39,6 +39,36 @@ describe("Zotero.Tags", function () { }); describe("#removeFromLibrary()", function () { + it("should remove tags in given library", function* () { + var libraryID = Zotero.Libraries.userLibraryID; + var groupLibraryID = (yield getGroup()).libraryID; + + var tags = []; + var items = []; + yield Zotero.DB.executeTransaction(function* () { + for (let i = 0; i < 10; i++) { + let tagName = Zotero.Utilities.randomString(); + tags.push(tagName); + let item = createUnsavedDataObject('item'); + item.addTag(tagName); + yield item.save(); + items.push(item); + } + }); + + var groupTagName = Zotero.Utilities.randomString(); + var groupItem = createUnsavedDataObject('item', { libraryID: groupLibraryID }); + groupItem.addTag(groupTagName); + yield groupItem.saveTx(); + + var tagIDs = tags.map(tag => Zotero.Tags.getID(tag)); + yield Zotero.Tags.removeFromLibrary(libraryID, tagIDs); + items.forEach(item => assert.lengthOf(item.getTags(), 0)); + + // Group item should still have the tag + assert.lengthOf(groupItem.getTags(), 1); + }) + it("should reload tags of associated items", function* () { var libraryID = Zotero.Libraries.userLibraryID;