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;