From e44dc23f55de4f94135dea200904c7668b71bbe5 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 01:14:36 -0500 Subject: [PATCH 01/17] Convert spaces to tabs in .eslintrc --- .eslintrc | 742 +++++++++++++++++++++++++++--------------------------- 1 file changed, 371 insertions(+), 371 deletions(-) diff --git a/.eslintrc b/.eslintrc index c884555ba44..e2919b86155 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,373 +1,373 @@ { - "env": { - "browser": true, - "es6": true, - "node": true - }, - "globals": { - "Zotero": false, - "ZOTERO_CONFIG": false, - "AddonManager": false, - "assert": false, - "Cc": false, - "Ci": false, - "Components": false, - "ConcurrentCaller": false, - "ctypes": false, - "OS": false, - "PluralForm": false, - "Services": false, - "XPCOMUtils": false, - "XRegExp": false - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended" - ], - "parserOptions": { - "ecmaVersion": 2018, - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module" - }, - "plugins": [ - "react" - ], - "settings": { - "react": { - "version": "16.3" - } - }, - "rules": { - "accessor-pairs": "error", - "array-bracket-newline": [ - "error", - "consistent" - ], - "array-bracket-spacing": "error", - "array-callback-return": "error", - "array-element-newline": [ - "error", - "consistent" - ], - "arrow-body-style": "off", - "arrow-parens": [ - "warn", - "as-needed", - { - "requireForBlockBody": true - } - ], - "arrow-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - "block-scoped-var": "off", - "block-spacing": [ - "error", - "always" - ], - "brace-style": [ - "warn", - "stroustrup" - ], - "callback-return": "off", - "camelcase": "error", - "capitalized-comments": "off", - "class-methods-use-this": "error", - "comma-dangle": "off", - "comma-spacing": [ - "warn", - { - "after": true, - "before": false - } - ], - "comma-style": [ - "error", - "last" - ], - "complexity": "off", - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": "error", - "consistent-this": "error", - "curly": "off", - "default-case": "off", - "dot-location": [ - "error", - "property" - ], - "dot-notation": "error", - "eol-last": "error", - "eqeqeq": "off", - "func-call-spacing": "error", - "func-name-matching": "error", - "func-names": [ - "error", - "never" - ], - "func-style": "off", - "function-paren-newline": "off", - "generator-star-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "global-require": "off", - "guard-for-in": "off", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-length": "off", - "id-match": "error", - "implicit-arrow-linebreak": [ - "error", - "beside" - ], - "indent": [ - "error", - "tab" - ], - "init-declarations": "off", - "jsx-quotes": "error", - "key-spacing": "warn", - "keyword-spacing": "warn", - "line-comment-position": "off", - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "error", - "lines-around-directive": "error", - "lines-between-class-members": "error", - "max-depth": "off", - "max-len": "off", - "max-lines": "off", - "max-nested-callbacks": "error", - "max-params": "off", - "max-statements": "off", - "max-statements-per-line": "off", - "multiline-comment-style": "off", - "multiline-ternary": [ - "error", - "always-multiline" - ], - "new-parens": "off", - "newline-per-chained-call": "error", - "no-alert": "off", - "no-array-constructor": "error", - "no-await-in-loop": "warn", - "no-bitwise": "off", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-confusing-arrow": "error", - "no-constant-condition": [ - "error", - { - "checkLoops": false - } - ], - "no-control-regex": "off", - "no-continue": "off", - "no-div-regex": "off", - "no-duplicate-imports": "error", - "no-else-return": "off", - "no-empty": "off", - "no-empty-function": "off", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - "no-inline-comments": "off", - "no-invalid-this": "off", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "off", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-mixed-operators": "off", - "no-mixed-requires": "error", - "no-multi-assign": "off", - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": "warn", - "no-native-reassign": "error", - "no-negated-condition": "off", - "no-negated-in-lhs": "error", - "no-nested-ternary": "off", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "off", - "no-path-concat": "error", - "no-plusplus": "off", - "no-process-env": "error", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "off", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": "off", - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "off", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sync": "off", - "no-tabs": "off", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-throw-literal": "error", - "no-trailing-spaces": [ - "error", - { - "skipBlankLines": true - } - ], - "no-undef-init": "error", - "no-undefined": "off", - "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": [ - "error", - { - "defaultAssignment": true - } - ], - "no-unused-expressions": [ - "error", - { - "allowShortCircuit": true - } - ], - "no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_" - } - ], - "no-use-before-define": "off", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "off", - "no-void": "error", - "no-warning-comments": "off", - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": "error", - "object-curly-newline": "error", - "object-curly-spacing": [ - "error", - "always" - ], - "object-shorthand": "off", - "one-var": "off", - "one-var-declaration-per-line": "off", - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": [ - "error", - "before" - ], - "padded-blocks": [ - "error", - "never" - ], - "padding-line-between-statements": "error", - "prefer-arrow-callback": "off", - "prefer-const": "off", - "prefer-destructuring": "off", - "prefer-numeric-literals": "error", - "prefer-promise-reject-errors": "error", - "prefer-rest-params": "off", - "prefer-spread": "error", - "prefer-template": "off", - "quote-props": [ - "error", - "as-needed" - ], - "quotes": "off", - "radix": [ - "error", - "as-needed" - ], - "require-await": "off", - "require-yield": "off", - "rest-spread-spacing": [ - "error", - "never" - ], - "semi": "error", - "semi-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "semi-style": [ - "error", - "last" - ], - "space-before-blocks": "warn", - "space-before-function-paren": [ - "warn", - { - "anonymous": "always", - "asyncArrow": "always", - "named": "never" - } - ], - "space-in-parens": [ - "error", - "never" - ], - "space-infix-ops": "warn", - "space-unary-ops": "error", - "spaced-comment": "warn", - "strict": "error", - "switch-colon-spacing": "error", - "symbol-description": "error", - "template-curly-spacing": [ - "error", - "never" - ], - "template-tag-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "vars-on-top": "off", - "wrap-regex": "off", - "yield-star-spacing": "error" - } + "env": { + "browser": true, + "es6": true, + "node": true + }, + "globals": { + "Zotero": false, + "ZOTERO_CONFIG": false, + "AddonManager": false, + "assert": false, + "Cc": false, + "Ci": false, + "Components": false, + "ConcurrentCaller": false, + "ctypes": false, + "OS": false, + "PluralForm": false, + "Services": false, + "XPCOMUtils": false, + "XRegExp": false + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended" + ], + "parserOptions": { + "ecmaVersion": 2018, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" + }, + "plugins": [ + "react" + ], + "settings": { + "react": { + "version": "16.3" + } + }, + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": [ + "error", + "consistent" + ], + "array-bracket-spacing": "error", + "array-callback-return": "error", + "array-element-newline": [ + "error", + "consistent" + ], + "arrow-body-style": "off", + "arrow-parens": [ + "warn", + "as-needed", + { + "requireForBlockBody": true + } + ], + "arrow-spacing": [ + "error", + { + "after": true, + "before": true + } + ], + "block-scoped-var": "off", + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "warn", + "stroustrup" + ], + "callback-return": "off", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "off", + "comma-spacing": [ + "warn", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "complexity": "off", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "error", + "consistent-this": "error", + "curly": "off", + "default-case": "off", + "dot-location": [ + "error", + "property" + ], + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "off", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": [ + "error", + "never" + ], + "func-style": "off", + "function-paren-newline": "off", + "generator-star-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "global-require": "off", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": [ + "error", + "beside" + ], + "indent": [ + "error", + "tab" + ], + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "warn", + "keyword-spacing": "warn", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "error", + "lines-around-directive": "error", + "lines-between-class-members": "error", + "max-depth": "off", + "max-len": "off", + "max-lines": "off", + "max-nested-callbacks": "error", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "off", + "multiline-comment-style": "off", + "multiline-ternary": [ + "error", + "always-multiline" + ], + "new-parens": "off", + "newline-per-chained-call": "error", + "no-alert": "off", + "no-array-constructor": "error", + "no-await-in-loop": "warn", + "no-bitwise": "off", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-confusing-arrow": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": false + } + ], + "no-control-regex": "off", + "no-continue": "off", + "no-div-regex": "off", + "no-duplicate-imports": "error", + "no-else-return": "off", + "no-empty": "off", + "no-empty-function": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-invalid-this": "off", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "off", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "off", + "no-mixed-requires": "error", + "no-multi-assign": "off", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": "warn", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "off", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "off", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "off", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "off", + "no-tabs": "off", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": [ + "error", + { + "skipBlankLines": true + } + ], + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": true + } + ], + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": true + } + ], + "no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_" + } + ], + "no-use-before-define": "off", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": [ + "error", + "always" + ], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": "off", + "operator-assignment": [ + "error", + "always" + ], + "operator-linebreak": [ + "error", + "before" + ], + "padded-blocks": [ + "error", + "never" + ], + "padding-line-between-statements": "error", + "prefer-arrow-callback": "off", + "prefer-const": "off", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-rest-params": "off", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": [ + "error", + "as-needed" + ], + "quotes": "off", + "radix": [ + "error", + "as-needed" + ], + "require-await": "off", + "require-yield": "off", + "rest-spread-spacing": [ + "error", + "never" + ], + "semi": "error", + "semi-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "semi-style": [ + "error", + "last" + ], + "space-before-blocks": "warn", + "space-before-function-paren": [ + "warn", + { + "anonymous": "always", + "asyncArrow": "always", + "named": "never" + } + ], + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "warn", + "space-unary-ops": "error", + "spaced-comment": "warn", + "strict": "error", + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": [ + "error", + "never" + ], + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "vars-on-top": "off", + "wrap-regex": "off", + "yield-star-spacing": "error" + } } From 3a777dcf407b2438496df0b842db0aa5ebc61c21 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 01:16:13 -0500 Subject: [PATCH 02/17] ESLint: Use ignoreChainWithDepth: 3 for newline-per-chained-call --- .eslintrc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index e2919b86155..06188491260 100644 --- a/.eslintrc +++ b/.eslintrc @@ -161,7 +161,12 @@ "always-multiline" ], "new-parens": "off", - "newline-per-chained-call": "error", + "newline-per-chained-call": [ + "error", + { + "ignoreChainWithDepth": 3 + } + ], "no-alert": "off", "no-array-constructor": "error", "no-await-in-loop": "warn", From fc43514ff0c03694e3b1d4ea9f577550a2d86432 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 07:27:57 -0500 Subject: [PATCH 03/17] Deselect tags when deleted (regression from Reactification) --- .../content/zotero/containers/tagSelector.jsx | 20 ++++--- test/tests/tagSelectorTest.js | 58 +++++++++++++++---- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/chrome/content/zotero/containers/tagSelector.jsx b/chrome/content/zotero/containers/tagSelector.jsx index e6d4197fd6f..39b709637ca 100644 --- a/chrome/content/zotero/containers/tagSelector.jsx +++ b/chrome/content/zotero/containers/tagSelector.jsx @@ -74,17 +74,21 @@ Zotero.TagSelector = class TagSelectorContainer extends React.Component { if (type == 'item' && (event == 'trash')) { return this.setState({tags: await this.getTags()}); } - + // If a selected tag no longer exists, deselect it - if (type == 'item-tag') { - if (event == 'delete' || event == 'trash' || event == 'modify') { - for (let tag of this.selectedTags) { - if (tag == extraData[ids[0]].old.tag) { - this.selectedTags.delete(tag); - } + if (type == 'tag' && (event == 'modify' || event == 'delete')) { + let changed = false; + for (let id of ids) { + let tag = extraData[id].old.tag; + if (this.selectedTags.has(tag)) { + this.selectedTags.delete(tag); + changed = true; } } - return this.setState({tags: await this.getTags()}); + if (changed && typeof(this.props.onSelection) === 'function') { + this.props.onSelection(this.selectedTags); + } + return; } this.setState({tags: await this.getTags()}); diff --git a/test/tests/tagSelectorTest.js b/test/tests/tagSelectorTest.js index ce56b286949..b75121848d3 100644 --- a/test/tests/tagSelectorTest.js +++ b/test/tests/tagSelectorTest.js @@ -189,32 +189,36 @@ describe("Tag Selector", function () { describe("#notify()", function () { - it("should add a tag when added to an item in the library root", function* () { + it("should add a tag when added to an item in the library root", async function () { var promise; if (collectionsView.selection.currentIndex != 0) { promise = waitForTagSelector(win); - yield collectionsView.selectLibrary(); - yield promise; + await collectionsView.selectLibrary(); + await promise; } // Add item with tag to library root - var item = createUnsavedDataObject('item'); + promise = waitForTagSelector(win); + var item = await createDataObject('item'); + await promise + + var tagA = Zotero.Utilities.randomString(); + var tagB = Zotero.Utilities.randomString(); item.setTags([ { - tag: 'A' + tag: tagA }, { - tag: 'B', + tag: tagB, type: 1 } ]); promise = waitForTagSelector(win); - yield item.saveTx(); - yield promise; + await item.saveTx(); + await promise; - // Tag selector should have at least one tag - assert.isAbove(getRegularTags().length, 1); + assert.includeMembers(getRegularTags(), [tagA, tagB]); }); it("should add a tag when an item is added in a collection", function* () { @@ -394,7 +398,7 @@ describe("Tag Selector", function () { yield item.saveTx(); yield promise; - // Tag selector should show the new item's tag + // Tag selector should show the new tag assert.include(getRegularTags(), "A"); // Remove tag from library @@ -404,8 +408,38 @@ describe("Tag Selector", function () { yield tagSelector.openDeletePrompt(); yield promise; - // Tag selector shouldn't show the deleted item's tag + // Tag selector shouldn't show the deleted tag assert.notInclude(getRegularTags(), "A"); + }); + + it("should deselect a tag when deleted from a library", async function () { + var libraryID = Zotero.Libraries.userLibraryID; + await selectLibrary(win); + + var promise = waitForTagSelector(win); + + var item1 = await createDataObject('item', { tags: [{ tag: 'A' }] }); + var item2 = await createDataObject('item', { tags: [{ tag: 'B' }] }); + await promise; + + tagSelector.handleTagSelected('A'); + await waitForTagSelector(win); + + // Tag selector should show the selected tag + assert.include(getRegularTags(), 'A'); + // And not the unselected one + assert.notInclude(getRegularTags(), 'B'); + + // Remove tag from library + promise = waitForTagSelector(win); + await Zotero.Tags.removeFromLibrary(libraryID, Zotero.Tags.getID('A')); + await promise; + + // Deleted tag should no longer be shown or selected + assert.notInclude(getRegularTags(), 'A'); + assert.notInclude(Array.from(tagSelector.getTagSelection()), 'A'); + // Other tags should be shown again + assert.include(getRegularTags(), 'B'); }) }) From d7dc5670d53b4e058cd278dc64021d34a7ae852c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 07:35:07 -0500 Subject: [PATCH 04/17] When clearing automatic tags, don't delete manual tags with same name --- chrome/content/zotero/xpcom/data/tags.js | 122 +++++++++++------------ test/tests/tagsTest.js | 101 ++++++++++++------- 2 files changed, 127 insertions(+), 96 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/tags.js b/chrome/content/zotero/xpcom/data/tags.js index 1e06cf68c9a..0c6c2d00131 100644 --- a/chrome/content/zotero/xpcom/data/tags.js +++ b/chrome/content/zotero/xpcom/data/tags.js @@ -281,14 +281,23 @@ Zotero.Tags = new function() { /** + * @param {Integer} libraryID + * @param {Integer[]} tagIDs + * @param {Function} [onProgress] + * @param {Integer[]} [types] * @return {Promise} */ - this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs, onProgress) { + this.removeFromLibrary = Zotero.Promise.coroutine(function* (libraryID, tagIDs, onProgress, types) { var d = new Date(); - tagIDs = Zotero.flattenArguments(tagIDs); + if (!Array.isArray(tagIDs)) { + tagIDs = [tagIDs]; + } + if (types && !Array.isArray(types)) { + types = [types]; + } - var deletedNames = []; + var colors = this.getColors(libraryID); var done = 0; yield Zotero.Utilities.Internal.forEachChunkAsync( @@ -296,90 +305,77 @@ Zotero.Tags = new function() { 100, async function (chunk) { await Zotero.DB.executeTransaction(function* () { - var oldItemIDs = []; - - var notifierPairs = []; + var rowIDs = []; + var itemIDs = []; + var uniqueTags = new Set(); + var notifierIDs = []; var notifierData = {}; - var a = new Date(); - var sql = 'SELECT tagID, itemID FROM itemTags JOIN items USING (itemID) ' + var sql = 'SELECT IT.ROWID AS rowID, tagID, itemID, type FROM itemTags IT ' + + 'JOIN items USING (itemID) ' + 'WHERE libraryID=? AND tagID IN (' + Array(chunk.length).fill('?').join(', ') - + ') ORDER BY tagID'; - var chunkTagItems = yield Zotero.DB.queryAsync(sql, [libraryID, ...chunk]); - var i = 0; - - chunk.sort((a, b) => a - b); - - for (let tagID of chunk) { + + ') '; + if (types) { + sql += 'AND type IN (' + types.join(', ') + ') '; + } + sql += 'ORDER BY tagID, type'; + var rows = yield Zotero.DB.queryAsync(sql, [libraryID, ...chunk]); + for (let { rowID, tagID, itemID, type } of rows) { + uniqueTags.add(tagID); + 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 - }; + rowIDs.push(rowID); + itemIDs.push(itemID); + + let ids = itemID + '-' + tagID; + notifierIDs.push(ids); + notifierData[ids] = { + libraryID: libraryID, + tag: name, + type + }; + + // If we're deleting the tag and not just a specific type, also clear any + // tag color + if (colors.has(name) && !types) { + yield this.setColor(libraryID, name, false); } - oldItemIDs = oldItemIDs.concat(itemIDs); } - if (oldItemIDs.length) { - Zotero.Notifier.queue('remove', 'item-tag', notifierPairs, notifierData); + if (itemIDs.length) { + Zotero.Notifier.queue('remove', 'item-tag', notifierIDs, 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])); + sql = "DELETE FROM itemTags WHERE ROWID IN (" + rowIDs.join(", ") + ")"; + yield Zotero.DB.queryAsync(sql); yield this.purge(chunk); // Update internal timestamps on all items that had these tags yield Zotero.Utilities.Internal.forEachChunkAsync( - Zotero.Utilities.arrayUnique(oldItemIDs), + Zotero.Utilities.arrayUnique(itemIDs), Zotero.DB.MAX_BOUND_PARAMETERS - 1, - Zotero.Promise.coroutine(function* (chunk2) { - var placeholders = Array(chunk2.length).fill('?').join(','); + async function (chunk) { + var sql = 'UPDATE items SET synced=0, clientDateModified=? ' + + 'WHERE itemID IN (' + Array(chunk.length).fill('?').join(',') + ')'; + await Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk)); - 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); - }) + await Zotero.Items.reload(itemIDs, ['primaryData', 'tags'], true); + } ); - done += chunk.length; + if (onProgress) { + done += uniqueTags.size; + onProgress(done, tagIDs.length); + } }.bind(this)); - - if (onProgress) { - onProgress(done, tagIDs.length); - } }.bind(this) ); - // Also delete tag color setting - // - // Note that this isn't done in purge(), so the setting will not - // be removed if the tag is just removed from all items without - // being explicitly deleted. - for (let i=0; i Zotero.Tags.getID(x)); + await Zotero.Tags.removeFromLibrary(libraryID, tagIDs); + + assert.sameDeepMembers(item1.getTags(), [{ tag: 'b', type: 1 }]); + assert.sameDeepMembers(item2.getTags(), [{ tag: 'b' }, { tag: 'c', type: 1 }]); + assert.lengthOf(item3.getTags(), 0); + assert.equal(Zotero.Tags.getID('a'), tagIDs[0]); + assert.isFalse(Zotero.Tags.getID('d')); - var groupTagName = tags[0]; - var groupItem = await createDataObject( - 'item', - { - libraryID: groupLibraryID, - tags: [groupTagName] - } + // Group item should still have all tags + assert.sameDeepMembers(item4.getTags(), [{ tag: 'a' }, { tag: 'b', type: 1 }]); + assert.equal( + await Zotero.DB.valueQueryAsync( + "SELECT COUNT(*) FROM itemTags WHERE itemID=?", + item4.id + ), + 2 ); + }); + + + it("should remove tags of a given type", async function () { + var libraryID = Zotero.Libraries.userLibraryID; + var groupLibraryID = (await getGroup()).libraryID; - var tagIDs = tags.map(tag => Zotero.Tags.getID(tag)); - await Zotero.Tags.removeFromLibrary(libraryID, tagIDs); - items.forEach(item => assert.lengthOf(item.getTags(), 0)); + var item1 = await createDataObject('item', { tags: [{ tag: 'a' }, { tag: 'b', type: 1 }] }); + var item2 = await createDataObject('item', { tags: [{ tag: 'b' }, { tag: 'c', type: 1 }] }); + var item3 = await createDataObject('item', { tags: [{ tag: 'd', type: 1 }] }); + var item4 = await createDataObject('item', { libraryID: groupLibraryID, tags: [{ tag: 'a' }, { tag: 'b', type: 1 }] }); + + var tagIDs = ['a', 'b', 'c', 'd'].map(x => Zotero.Tags.getID(x)); + var tagType = 1; + await Zotero.Tags.removeFromLibrary(libraryID, tagIDs, null, tagType); + + assert.sameDeepMembers(item1.getTags(), [{ tag: 'a' }]); + assert.sameDeepMembers(item2.getTags(), [{ tag: 'b' }]); + assert.lengthOf(item3.getTags(), 0); + assert.isFalse(Zotero.Tags.getID('d')); - // Group item should still have the tag - assert.sameDeepMembers(groupItem.getTags(), [{ tag: groupTagName }]); + // Group item should still have all tags + assert.sameDeepMembers(item4.getTags(), [{ tag: 'a' }, { tag: 'b', type: 1 }]); assert.equal( await Zotero.DB.valueQueryAsync( "SELECT COUNT(*) FROM itemTags WHERE itemID=?", - groupItem.id + item4.id ), - 1 + 2 ); }); - it("should reload tags of associated items", function* () { + it("should delete colored tag when removing tag", async function () { var libraryID = Zotero.Libraries.userLibraryID; - var tagName = Zotero.Utilities.randomString(); - var item = createUnsavedDataObject('item'); - item.addTag(tagName); - yield item.saveTx(); - assert.lengthOf(item.getTags(), 1); + var tag = Zotero.Utilities.randomString(); + var item = await createDataObject('item', { tags: [{ tag: tag, type: 1 }] }); + await Zotero.Tags.setColor(libraryID, tag, '#ABCDEF', 0); + + await Zotero.Tags.removeFromLibrary(libraryID, [Zotero.Tags.getID(tag)]); - var tagID = Zotero.Tags.getID(tagName); - yield Zotero.Tags.removeFromLibrary(libraryID, tagID); assert.lengthOf(item.getTags(), 0); - }) + assert.isFalse(Zotero.Tags.getColor(libraryID, tag)); + }); + + it("shouldn't delete colored tag when removing tag of a given type", async function () { + var libraryID = Zotero.Libraries.userLibraryID; + + var tag = Zotero.Utilities.randomString(); + var item = await createDataObject('item', { tags: [{ tag: tag, type: 1 }] }); + await Zotero.Tags.setColor(libraryID, tag, '#ABCDEF', 0); + + await Zotero.Tags.removeFromLibrary(libraryID, [Zotero.Tags.getID(tag)], null, 1); + + assert.lengthOf(item.getTags(), 0); + assert.ok(Zotero.Tags.getColor(libraryID, tag)); + }); }) describe("#purge()", function () { From ae9c54b76d4eb033f785f1fde9f2a5691ef230ad Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 19:15:50 -0500 Subject: [PATCH 05/17] Fix "Move to Top" logic for creators --- chrome/content/zotero/bindings/itembox.xml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/chrome/content/zotero/bindings/itembox.xml b/chrome/content/zotero/bindings/itembox.xml index 769d047a4e8..4dbc9330b31 100644 --- a/chrome/content/zotero/bindings/itembox.xml +++ b/chrome/content/zotero/bindings/itembox.xml @@ -2196,10 +2196,21 @@ newIndex = index + 1; break; } - var a = this.item.getCreator(index); - var b = this.item.getCreator(newIndex); - this.item.setCreator(newIndex, a); - this.item.setCreator(index, b); + let creator = this.item.getCreator(index); + // When moving to top, increment index of all other creators + if (dir == 'top') { + let otherCreators = this.item.getCreators(); + this.item.setCreator(newIndex, creator); + for (let i = 0; i < index; i++) { + this.item.setCreator(i + 1, otherCreators[i]); + } + } + // When moving up or down, swap places with next creator + else { + let otherCreator = this.item.getCreator(newIndex); + this.item.setCreator(newIndex, creator); + this.item.setCreator(index, otherCreator); + } if (this.saveOnEdit) { // See note in transformText() yield this.blurOpenField(); From 123f82571ee22aa8000d5e17ca4c986d23170e29 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 21:15:27 -0500 Subject: [PATCH 06/17] Update locales from Transifex --- chrome/locale/cs-CZ/zotero/zotero.properties | 2 +- chrome/locale/tr-TR/zotero/preferences.dtd | 2 +- chrome/locale/tr-TR/zotero/zotero.dtd | 2 +- chrome/locale/tr-TR/zotero/zotero.properties | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chrome/locale/cs-CZ/zotero/zotero.properties b/chrome/locale/cs-CZ/zotero/zotero.properties index 87db3d536ab..678b3cc1f63 100644 --- a/chrome/locale/cs-CZ/zotero/zotero.properties +++ b/chrome/locale/cs-CZ/zotero/zotero.properties @@ -190,7 +190,7 @@ startupError.incompatibleDBVersion=Tato %1$S databáze vyžaduje %1$S %2$S nebo startupError.zoteroVersionIsOlder.current=Aktuální verze je: %S startupError.zoteroVersionIsOlder.upgrade=Proveďte, prosím, upgrade na poslední verzi z %S. startupError.databaseUpgradeError=Chyba při aktualizaci databáze -startupError.internetFunctionalityMayNotWork=Functionality that depends on an internet connection may not work. +startupError.internetFunctionalityMayNotWork=Funkcionalita závislá na internetovém připojení nemusí fungovat. startupError.bundledFileUpdateError=%S was unable to load translators and styles. startupError.startedFromDiskImage1=%S was started from a disk image, which can break some functionality. startupError.startedFromDiskImage2=To install %1$S properly, quit the program, open the disk image you downloaded, and drag “%1$S” to the alias of the Applications folder shown in the window. Then eject the disk image and launch Zotero by opening it from your Applications folder. diff --git a/chrome/locale/tr-TR/zotero/preferences.dtd b/chrome/locale/tr-TR/zotero/preferences.dtd index 0e6bcf2547c..291f1c3e058 100644 --- a/chrome/locale/tr-TR/zotero/preferences.dtd +++ b/chrome/locale/tr-TR/zotero/preferences.dtd @@ -24,7 +24,7 @@ - + diff --git a/chrome/locale/tr-TR/zotero/zotero.dtd b/chrome/locale/tr-TR/zotero/zotero.dtd index 4eb6f3fb42c..998273f2e06 100644 --- a/chrome/locale/tr-TR/zotero/zotero.dtd +++ b/chrome/locale/tr-TR/zotero/zotero.dtd @@ -203,7 +203,7 @@ - + diff --git a/chrome/locale/tr-TR/zotero/zotero.properties b/chrome/locale/tr-TR/zotero/zotero.properties index 8d4e2edca01..5606d400aa1 100644 --- a/chrome/locale/tr-TR/zotero/zotero.properties +++ b/chrome/locale/tr-TR/zotero/zotero.properties @@ -192,7 +192,7 @@ startupError.zoteroVersionIsOlder.upgrade=Lütfen %S adresinden indirerek, sür startupError.databaseUpgradeError=Veritabanı yükseltme hatası startupError.internetFunctionalityMayNotWork=İnternet bağlantısı gerektiren işlevler çalışmayabilir. startupError.bundledFileUpdateError=%S çevirmenleri ve stilleri yükleyemedi. -startupError.startedFromDiskImage1=%S was started from a disk image, which can break some functionality. +startupError.startedFromDiskImage1=%S bir disk görüntüsünden başlatılmıştır. Bu bazı işlevsellikleri bozabilir. startupError.startedFromDiskImage2=%1$S programını doğru şekilde kurmak için, programdan ayrılınız ve indirdiğiniz disk görüntüsünü açıp, “%1$S” programını pencerede gördüğünüz Uygulamalar klasörünün armasına sürükleyiniz. Ardından disk görüntüsünü çıkarınız ve Zotero'yu Uygulamalar klasöründen başlatınız. date.relative.secondsAgo.one=1 saniye önce @@ -352,7 +352,7 @@ pane.item.defaultLastName=son pane.item.defaultFullName=tam isim pane.item.switchFieldMode.one=Tek alana geç pane.item.switchFieldMode.two=İki alana geç -pane.item.creator.moveToTop=Move to Top +pane.item.creator.moveToTop=En üste taşı pane.item.creator.moveUp=Bir Yukarı Çık pane.item.creator.moveDown=Bir Aşağı İn pane.item.notes.untitled=Başlıksız Not @@ -721,9 +721,9 @@ fileInterface.exportError=Seçili dosyayı dışarı aktarırken bir hata oluşt fileInterface.importOPML=Beslemeleri OPML'den İçeri Aktarın fileInterface.OPMLFeedFilter=OPML Besleme Listesi -import.fileHandling.store=Copy files to the %S storage folder -import.fileHandling.link=Link to files in original location -import.fileHandling.description=Linked files cannot be synced by %S. +import.fileHandling.store=Dosyaları %S depo klasörüne kopyala +import.fileHandling.link=Dosyalara orijinal yerlerinde bağlantı kur +import.fileHandling.description=Bağlantılanmış dosyalar %S tarafından eşitlenemiyor. quickCopy.copyAs=%S olarak kopyala @@ -871,7 +871,7 @@ integration.error.styleMissing=Bu belgede kullanılan gönderme stili kayıptır integration.error.styleNotFound=%S gönderme stili bulunamadı. integration.error.macWordSBPermissionsMissing.title=Eksik İzin integration.error.macWordSBPermissionsMissing=Zotero, Word programını kullanabilme iznine sahip değildir. Zotero'ya bu izni verebilmek için:\n\n1) Sistem Tercihleri'ni açınız\n2) Güvenlik ve Gizlilik'i tıklayınız\n3) Gizlilik sekmesini seçiniz\n4) Sol tarafta Otomasyon'u bulup seçiniz\n5) Zotero altındaki “Microsoft Word” onay kutusunu seçiniz\n6) Word programını yeniden başlatınız -integration.error.macWordSBPermissionsMissing.pre2016=Eğer “Otomasyon” altında “Mıcrosoft Word” seçeneği gözükmüyorsa, Word 2011 sürüm 14.7.7 veya üstünü kullandığınızdan emin olunuz. +integration.error.macWordSBPermissionsMissing.pre2016=Eğer “Otomasyon” altında “Microsoft Word” seçeneği gözükmüyorsa, Word 2011 sürüm 14.7.7 veya üstünü kullandığınızdan emin olunuz. integration.replace=Bu Zotero alanını değiştir? integration.missingItem.single=Bu eser Zotero veritabanınızda artık mevcut değildir. Benzer bir eser seçmek istermisiniz? @@ -1024,7 +1024,7 @@ sync.storage.error.serverCouldNotBeReached=Bu sunucuya ulaşılamıyor: %S sync.storage.error.permissionDeniedAtAddress=Belirtilen adreste Zotero dizini yaratmak için izne sahip değilsiniz: sync.storage.error.checkFileSyncSettings=Lütfen, dosya eşitleme ayarlarınızı kontrol ediniz veya WebDAV sunucusu yöneticinizle temas kurunuz. sync.storage.error.verificationFailed=%S onaylaması başarısız. Zotero tercihlerinde, Eşitleme bölmesindeki dosya eşitleme ayarlarınızı doğrulayınız. -sync.storage.error.fileNotCreated='%S' dosyası Zotero 'depo' dizininde yaratılamadı. +sync.storage.error.fileNotCreated='%S' dosyası Zotero 'storage' (depo) dizininde yaratılamadı. sync.storage.error.encryptedFilenames='%S' dosyasını oluştururken bir hata oldu.\n\nDaha çok bilgi edinebilmek için http://www.zotero.org/support/kb/encrypted_filenames adresine bakınız. sync.storage.error.fileEditingAccessLost=Zotero grubu '%S''yi düzenleme hakkına sahip değilsiniz ve yaptığınız ya da düzelttiğiniz dosya eklentileri sunucuyla eşitlenmeyecek. sync.storage.error.copyChangedItems=Eğer değiştirilmiş dosya ve eserleri başka bir yere kopyalamak istiyorsanız, şimdi eşitlemeyi iptal edin. From 9cdfe8e01c1679608c1f739f9db927f35f42e709 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 21:17:14 -0500 Subject: [PATCH 07/17] Update submodules --- chrome/content/zotero/locale/csl | 2 +- resource/schema/repotime.txt | 2 +- translators | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chrome/content/zotero/locale/csl b/chrome/content/zotero/locale/csl index 7a5a83e8526..6b0cb468912 160000 --- a/chrome/content/zotero/locale/csl +++ b/chrome/content/zotero/locale/csl @@ -1 +1 @@ -Subproject commit 7a5a83e852650b7f2cda77f40f919e8924d32e86 +Subproject commit 6b0cb4689127a69852f48608b6d1a879900f418b diff --git a/resource/schema/repotime.txt b/resource/schema/repotime.txt index 27af1ddee27..6eab4a03a89 100644 --- a/resource/schema/repotime.txt +++ b/resource/schema/repotime.txt @@ -1 +1 @@ -2019-01-07 08:25:00 +2019-03-06 00:00:00 diff --git a/translators b/translators index a8230bda8b8..03bf1dc4e06 160000 --- a/translators +++ b/translators @@ -1 +1 @@ -Subproject commit a8230bda8b8927145e80ccf51a23f86247486ca4 +Subproject commit 03bf1dc4e060f1005e5f01e1a575c552ea780c75 From ef4866f97b1377d453e85a6c1c79719aaaecce1c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Mar 2019 21:17:45 -0500 Subject: [PATCH 08/17] Update renamed-styles.json --- resource/schema/renamed-styles.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resource/schema/renamed-styles.json b/resource/schema/renamed-styles.json index 6d57ffed3b9..d71b178c1d0 100644 --- a/resource/schema/renamed-styles.json +++ b/resource/schema/renamed-styles.json @@ -279,7 +279,8 @@ "frontiers-in-vascular-physiology": "frontiers", "frontiers-in-virology": "frontiers", "frontiers-in-womens-cancer": "frontiers", - "future-science": "future-science-journals", + "future-science": "future-science-group", + "future-science-journals": "future-science-group", "geistes-und-kulturwissenschaften-teilmann": "geistes-und-kulturwissenschaften-heilmann", "genetic-vaccines-and-therapy": "biomed-central", "geological-society-america-bulletin": "geological-society-of-america-bulletin", From ce1fc663513b2ef9f870867d3ce31242644cfe37 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 6 Mar 2019 00:30:23 -0500 Subject: [PATCH 09/17] Update submodules --- translators | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translators b/translators index 03bf1dc4e06..53102fac9bf 160000 --- a/translators +++ b/translators @@ -1 +1 @@ -Subproject commit 03bf1dc4e060f1005e5f01e1a575c552ea780c75 +Subproject commit 53102fac9bf8acdefafdff4e1e8d3f8796312880 From e8f186479f083696c8ada55a485c70c0b9bf0b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Wed, 6 Mar 2019 13:13:40 +0200 Subject: [PATCH 10/17] Address tag selector tests race conditions. Addresses #1659 --- chrome/content/zotero/containers/tagSelector.jsx | 6 ++---- test/tests/tagSelectorTest.js | 11 +++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/chrome/content/zotero/containers/tagSelector.jsx b/chrome/content/zotero/containers/tagSelector.jsx index 39b709637ca..118c67fcf29 100644 --- a/chrome/content/zotero/containers/tagSelector.jsx +++ b/chrome/content/zotero/containers/tagSelector.jsx @@ -68,10 +68,10 @@ Zotero.TagSelector = class TagSelectorContainer extends React.Component { default: return; } + return this.setState({tags: await this.getTags()}); } - // Ignore item events other than 'trash' - if (type == 'item' && (event == 'trash')) { + if (type == 'item' || type == 'item-tag') { return this.setState({tags: await this.getTags()}); } @@ -90,8 +90,6 @@ Zotero.TagSelector = class TagSelectorContainer extends React.Component { } return; } - - this.setState({tags: await this.getTags()}); } async getTags(tagsInScope, tagColors) { diff --git a/test/tests/tagSelectorTest.js b/test/tests/tagSelectorTest.js index b75121848d3..11acaa575fe 100644 --- a/test/tests/tagSelectorTest.js +++ b/test/tests/tagSelectorTest.js @@ -418,8 +418,8 @@ describe("Tag Selector", function () { var promise = waitForTagSelector(win); - var item1 = await createDataObject('item', { tags: [{ tag: 'A' }] }); - var item2 = await createDataObject('item', { tags: [{ tag: 'B' }] }); + await createDataObject('item', { tags: [{ tag: 'A' }] }); + await createDataObject('item', { tags: [{ tag: 'B' }] }); await promise; tagSelector.handleTagSelected('A'); @@ -433,15 +433,18 @@ describe("Tag Selector", function () { // Remove tag from library promise = waitForTagSelector(win); await Zotero.Tags.removeFromLibrary(libraryID, Zotero.Tags.getID('A')); + // notify item-tag remove await promise; + // notify tag delete which triggers #onSelected, which eventually triggers #onItemViewChanged + await waitForTagSelector(win); // Deleted tag should no longer be shown or selected assert.notInclude(getRegularTags(), 'A'); assert.notInclude(Array.from(tagSelector.getTagSelection()), 'A'); // Other tags should be shown again assert.include(getRegularTags(), 'B'); - }) - }) + }); + }); describe("#openRenamePrompt", function () { it("should rename a tag and update the tag selector", function* () { From b20bf345e6e036d1f1e8f987733559f171314c04 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 6 Mar 2019 07:26:11 -0500 Subject: [PATCH 11/17] Address race condition in tag selector tests (#1659) --- test/tests/tagSelectorTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tests/tagSelectorTest.js b/test/tests/tagSelectorTest.js index 11acaa575fe..989985a2a6e 100644 --- a/test/tests/tagSelectorTest.js +++ b/test/tests/tagSelectorTest.js @@ -40,6 +40,7 @@ describe("Tag Selector", function () { tagSelector.selectedTags = new Set(); tagSelector.handleSearch(''); tagSelector.onItemViewChanged({libraryID}); + yield waitForTagSelector(win); }); after(function () { From d01038b13bc1f63c733ca4d84e59ccb40ea90312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Wed, 6 Mar 2019 14:35:28 +0200 Subject: [PATCH 12/17] Fix missing react localization in tests. Closes #1661 --- chrome/content/zotero/overlay.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js index f5b884eee8f..27c7bf56283 100644 --- a/chrome/content/zotero/overlay.js +++ b/chrome/content/zotero/overlay.js @@ -83,7 +83,8 @@ var ZoteroOverlay = new function() if (Zotero.skipLoading) { throw new Error("Skipping loading"); } - + + ZoteroPane.Containers.init(); ZoteroPane.init(); // Clear old Zotero icon pref From 72fb67d15b4baa7637b9678a72340f1570b36eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Wed, 6 Mar 2019 16:22:01 +0200 Subject: [PATCH 13/17] Restore "Delete All Automatic Tags" menu option for tag selector. Closes #1660 --- .../content/zotero/containers/tagSelector.jsx | 37 +++++++++++++++++++ .../content/zotero/containers/tagSelector.xul | 4 ++ test/tests/tagSelectorTest.js | 31 ++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/chrome/content/zotero/containers/tagSelector.jsx b/chrome/content/zotero/containers/tagSelector.jsx index 118c67fcf29..3548fa3a219 100644 --- a/chrome/content/zotero/containers/tagSelector.jsx +++ b/chrome/content/zotero/containers/tagSelector.jsx @@ -389,6 +389,43 @@ Zotero.TagSelector = class TagSelectorContainer extends React.Component { this.props.onSelection(this.selectedTags); } } + + async deleteAutomatic() { + var num = (await Zotero.Tags.getAutomaticInLibrary(this.libraryID)).length; + if (!num) { + return; + } + + var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Components.interfaces.nsIPromptService); + var confirmed = ps.confirm( + window, + Zotero.getString('pane.tagSelector.deleteAutomatic.title'), + Zotero.getString( + 'pane.tagSelector.deleteAutomatic.message', + new Intl.NumberFormat().format(num), + num + ) + + "\n\n" + + Zotero.getString('general.actionCannotBeUndone') + ); + if (confirmed) { + Zotero.showZoteroPaneProgressMeter(null, true); + try { + await Zotero.Tags.removeAutomaticFromLibrary( + this.libraryID, + (progress, progressMax) => { + Zotero.updateZoteroPaneProgressMeter( + Math.round(progress / progressMax * 100) + ); + } + ); + } + finally { + Zotero.hideZoteroPaneOverlays(); + } + } + } get label() { let count = this.selectedTags.size; diff --git a/chrome/content/zotero/containers/tagSelector.xul b/chrome/content/zotero/containers/tagSelector.xul index 50222810548..d84e3840801 100644 --- a/chrome/content/zotero/containers/tagSelector.xul +++ b/chrome/content/zotero/containers/tagSelector.xul @@ -54,5 +54,9 @@ type="checkbox" oncommand="ZoteroPane.tagSelector.toggleDisplayAllTags(); event.stopPropagation();" /> + \ No newline at end of file diff --git a/test/tests/tagSelectorTest.js b/test/tests/tagSelectorTest.js index 989985a2a6e..870ef29a2d5 100644 --- a/test/tests/tagSelectorTest.js +++ b/test/tests/tagSelectorTest.js @@ -533,4 +533,35 @@ describe("Tag Selector", function () { assert.notInclude(getRegularTags(), tag); }) }); + + describe("#deleteAutomatic()", function() { + it('should delete automatic tags', async function() { + await selectLibrary(win); + var item = createUnsavedDataObject('item'); + item.setTags([ + { + tag: "automatic", + type: 1 + }, + { + tag: 'manual' + } + ]); + var promise = waitForTagSelector(win); + await item.saveTx(); + await promise; + + assert.include(getRegularTags(), "automatic"); + assert.include(getRegularTags(), "manual"); + + var dialogPromise = waitForDialog(); + var tagSelectorPromise = waitForTagSelector(win); + tagSelector.deleteAutomatic(); + await dialogPromise; + await tagSelectorPromise; + + assert.include(getRegularTags(), 'manual'); + assert.notInclude(getRegularTags(), 'automatic'); + }); + }); }) From a4613e6d12ecd73983308e781bc1d42d14a6bba2 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 6 Mar 2019 03:42:30 -0500 Subject: [PATCH 14/17] Fix .eslintrc whitespace, but properly this time --- .eslintrc | 724 +++++++++++++++++++++++++++--------------------------- 1 file changed, 362 insertions(+), 362 deletions(-) diff --git a/.eslintrc b/.eslintrc index 06188491260..dc006cb829d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,378 +1,378 @@ { "env": { - "browser": true, - "es6": true, - "node": true + "browser": true, + "es6": true, + "node": true }, "globals": { - "Zotero": false, - "ZOTERO_CONFIG": false, - "AddonManager": false, - "assert": false, - "Cc": false, - "Ci": false, - "Components": false, - "ConcurrentCaller": false, - "ctypes": false, - "OS": false, - "PluralForm": false, - "Services": false, - "XPCOMUtils": false, - "XRegExp": false + "Zotero": false, + "ZOTERO_CONFIG": false, + "AddonManager": false, + "assert": false, + "Cc": false, + "Ci": false, + "Components": false, + "ConcurrentCaller": false, + "ctypes": false, + "OS": false, + "PluralForm": false, + "Services": false, + "XPCOMUtils": false, + "XRegExp": false }, "extends": [ - "eslint:recommended", - "plugin:react/recommended" + "eslint:recommended", + "plugin:react/recommended" ], "parserOptions": { - "ecmaVersion": 2018, - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module" + "ecmaVersion": 2018, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" }, "plugins": [ - "react" + "react" ], "settings": { - "react": { - "version": "16.3" - } + "react": { + "version": "16.3" + } }, "rules": { - "accessor-pairs": "error", - "array-bracket-newline": [ - "error", - "consistent" - ], - "array-bracket-spacing": "error", - "array-callback-return": "error", - "array-element-newline": [ - "error", - "consistent" - ], - "arrow-body-style": "off", - "arrow-parens": [ - "warn", - "as-needed", - { - "requireForBlockBody": true - } - ], - "arrow-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - "block-scoped-var": "off", - "block-spacing": [ - "error", - "always" - ], - "brace-style": [ - "warn", - "stroustrup" - ], - "callback-return": "off", - "camelcase": "error", - "capitalized-comments": "off", - "class-methods-use-this": "error", - "comma-dangle": "off", - "comma-spacing": [ - "warn", - { - "after": true, - "before": false - } - ], - "comma-style": [ - "error", - "last" - ], - "complexity": "off", - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": "error", - "consistent-this": "error", - "curly": "off", - "default-case": "off", - "dot-location": [ - "error", - "property" - ], - "dot-notation": "error", - "eol-last": "error", - "eqeqeq": "off", - "func-call-spacing": "error", - "func-name-matching": "error", - "func-names": [ - "error", - "never" - ], - "func-style": "off", - "function-paren-newline": "off", - "generator-star-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "global-require": "off", - "guard-for-in": "off", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-length": "off", - "id-match": "error", - "implicit-arrow-linebreak": [ - "error", - "beside" - ], - "indent": [ - "error", - "tab" - ], - "init-declarations": "off", - "jsx-quotes": "error", - "key-spacing": "warn", - "keyword-spacing": "warn", - "line-comment-position": "off", - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "error", - "lines-around-directive": "error", - "lines-between-class-members": "error", - "max-depth": "off", - "max-len": "off", - "max-lines": "off", - "max-nested-callbacks": "error", - "max-params": "off", - "max-statements": "off", - "max-statements-per-line": "off", - "multiline-comment-style": "off", - "multiline-ternary": [ - "error", - "always-multiline" - ], - "new-parens": "off", - "newline-per-chained-call": [ - "error", - { - "ignoreChainWithDepth": 3 - } - ], - "no-alert": "off", - "no-array-constructor": "error", - "no-await-in-loop": "warn", - "no-bitwise": "off", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-confusing-arrow": "error", - "no-constant-condition": [ - "error", - { - "checkLoops": false - } - ], - "no-control-regex": "off", - "no-continue": "off", - "no-div-regex": "off", - "no-duplicate-imports": "error", - "no-else-return": "off", - "no-empty": "off", - "no-empty-function": "off", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - "no-inline-comments": "off", - "no-invalid-this": "off", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "off", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-mixed-operators": "off", - "no-mixed-requires": "error", - "no-multi-assign": "off", - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": "warn", - "no-native-reassign": "error", - "no-negated-condition": "off", - "no-negated-in-lhs": "error", - "no-nested-ternary": "off", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "off", - "no-path-concat": "error", - "no-plusplus": "off", - "no-process-env": "error", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "off", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": "off", - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "off", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sync": "off", - "no-tabs": "off", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-throw-literal": "error", - "no-trailing-spaces": [ - "error", - { - "skipBlankLines": true - } - ], - "no-undef-init": "error", - "no-undefined": "off", - "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": [ - "error", - { - "defaultAssignment": true - } - ], - "no-unused-expressions": [ - "error", - { - "allowShortCircuit": true - } - ], - "no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_" - } - ], - "no-use-before-define": "off", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "off", - "no-void": "error", - "no-warning-comments": "off", - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": "error", - "object-curly-newline": "error", - "object-curly-spacing": [ - "error", - "always" - ], - "object-shorthand": "off", - "one-var": "off", - "one-var-declaration-per-line": "off", - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": [ - "error", - "before" - ], - "padded-blocks": [ - "error", - "never" - ], - "padding-line-between-statements": "error", - "prefer-arrow-callback": "off", - "prefer-const": "off", - "prefer-destructuring": "off", - "prefer-numeric-literals": "error", - "prefer-promise-reject-errors": "error", - "prefer-rest-params": "off", - "prefer-spread": "error", - "prefer-template": "off", - "quote-props": [ - "error", - "as-needed" - ], - "quotes": "off", - "radix": [ - "error", - "as-needed" - ], - "require-await": "off", - "require-yield": "off", - "rest-spread-spacing": [ - "error", - "never" - ], - "semi": "error", - "semi-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "semi-style": [ - "error", - "last" - ], - "space-before-blocks": "warn", - "space-before-function-paren": [ - "warn", - { - "anonymous": "always", - "asyncArrow": "always", - "named": "never" - } - ], - "space-in-parens": [ - "error", - "never" - ], - "space-infix-ops": "warn", - "space-unary-ops": "error", - "spaced-comment": "warn", - "strict": "error", - "switch-colon-spacing": "error", - "symbol-description": "error", - "template-curly-spacing": [ - "error", - "never" - ], - "template-tag-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "vars-on-top": "off", - "wrap-regex": "off", - "yield-star-spacing": "error" + "accessor-pairs": "error", + "array-bracket-newline": [ + "error", + "consistent" + ], + "array-bracket-spacing": "error", + "array-callback-return": "error", + "array-element-newline": [ + "error", + "consistent" + ], + "arrow-body-style": "off", + "arrow-parens": [ + "warn", + "as-needed", + { + "requireForBlockBody": true + } + ], + "arrow-spacing": [ + "error", + { + "after": true, + "before": true + } + ], + "block-scoped-var": "off", + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "warn", + "stroustrup" + ], + "callback-return": "off", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "off", + "comma-spacing": [ + "warn", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "complexity": "off", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "error", + "consistent-this": "error", + "curly": "off", + "default-case": "off", + "dot-location": [ + "error", + "property" + ], + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "off", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": [ + "error", + "never" + ], + "func-style": "off", + "function-paren-newline": "off", + "generator-star-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "global-require": "off", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": [ + "error", + "beside" + ], + "indent": [ + "error", + "tab" + ], + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "warn", + "keyword-spacing": "warn", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "error", + "lines-around-directive": "error", + "lines-between-class-members": "error", + "max-depth": "off", + "max-len": "off", + "max-lines": "off", + "max-nested-callbacks": "error", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "off", + "multiline-comment-style": "off", + "multiline-ternary": [ + "error", + "always-multiline" + ], + "new-parens": "off", + "newline-per-chained-call": [ + "error", + { + "ignoreChainWithDepth": 3 + } + ], + "no-alert": "off", + "no-array-constructor": "error", + "no-await-in-loop": "warn", + "no-bitwise": "off", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-confusing-arrow": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": false + } + ], + "no-control-regex": "off", + "no-continue": "off", + "no-div-regex": "off", + "no-duplicate-imports": "error", + "no-else-return": "off", + "no-empty": "off", + "no-empty-function": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-invalid-this": "off", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "off", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "off", + "no-mixed-requires": "error", + "no-multi-assign": "off", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": "warn", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "off", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "off", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "off", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "off", + "no-tabs": "off", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": [ + "error", + { + "skipBlankLines": true + } + ], + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": true + } + ], + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": true + } + ], + "no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_" + } + ], + "no-use-before-define": "off", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": [ + "error", + "always" + ], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": "off", + "operator-assignment": [ + "error", + "always" + ], + "operator-linebreak": [ + "error", + "before" + ], + "padded-blocks": [ + "error", + "never" + ], + "padding-line-between-statements": "error", + "prefer-arrow-callback": "off", + "prefer-const": "off", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-rest-params": "off", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": [ + "error", + "as-needed" + ], + "quotes": "off", + "radix": [ + "error", + "as-needed" + ], + "require-await": "off", + "require-yield": "off", + "rest-spread-spacing": [ + "error", + "never" + ], + "semi": "error", + "semi-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "semi-style": [ + "error", + "last" + ], + "space-before-blocks": "warn", + "space-before-function-paren": [ + "warn", + { + "anonymous": "always", + "asyncArrow": "always", + "named": "never" + } + ], + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "warn", + "space-unary-ops": "error", + "spaced-comment": "warn", + "strict": "error", + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": [ + "error", + "never" + ], + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "vars-on-top": "off", + "wrap-regex": "off", + "yield-star-spacing": "error" } } From 85c6ec0ca4ac70771064b77fd9aff9907ec5531f Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 7 Mar 2019 03:35:37 -0500 Subject: [PATCH 15/17] Actually change code that deletes automatic tags Follow-up to d7dc5670d53b --- chrome/content/zotero/xpcom/data/tags.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/tags.js b/chrome/content/zotero/xpcom/data/tags.js index 0c6c2d00131..974faa4a399 100644 --- a/chrome/content/zotero/xpcom/data/tags.js +++ b/chrome/content/zotero/xpcom/data/tags.js @@ -396,11 +396,12 @@ Zotero.Tags = new function() { * Remove all automatic tags in the given library */ this.removeAutomaticFromLibrary = async function (libraryID, onProgress) { + var tagType = 1; var tagIDs = await this.getAutomaticInLibrary(libraryID); if (onProgress) { onProgress(0, tagIDs.length); } - return this.removeFromLibrary(libraryID, tagIDs, onProgress); + return this.removeFromLibrary(libraryID, tagIDs, onProgress, tagType); }; From c7698a2324b67ade6caaaac9e08d06868c247c36 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 7 Mar 2019 04:08:45 -0500 Subject: [PATCH 16/17] Restore disabling of automatic tag deletion option when no such tags --- chrome/content/zotero/containers/tagSelector.jsx | 2 +- chrome/content/zotero/containers/tagSelector.xul | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/chrome/content/zotero/containers/tagSelector.jsx b/chrome/content/zotero/containers/tagSelector.jsx index 3548fa3a219..efb0a2dd6b0 100644 --- a/chrome/content/zotero/containers/tagSelector.jsx +++ b/chrome/content/zotero/containers/tagSelector.jsx @@ -159,7 +159,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.Component { onSelect={this.state.viewOnly ? () => {} : this.handleTagSelected} onTagContext={this.handleTagContext} onSearch={this.handleSearch} - onSettings={this.handleSettings} + onSettings={this.handleSettings.bind(this)} loaded={this.state.loaded} />; } diff --git a/chrome/content/zotero/containers/tagSelector.xul b/chrome/content/zotero/containers/tagSelector.xul index d84e3840801..f056b539fab 100644 --- a/chrome/content/zotero/containers/tagSelector.xul +++ b/chrome/content/zotero/containers/tagSelector.xul @@ -41,7 +41,18 @@ onpopupshowing=" document.getElementById('show-automatic').setAttribute('checked', ZoteroPane.tagSelector.showAutomatic); document.getElementById('display-all-tags').setAttribute('checked', ZoteroPane.tagSelector.displayAllTags); - document.getElementById('num-selected').label = ZoteroPane.tagSelector.label"> + document.getElementById('num-selected').label = ZoteroPane.tagSelector.label; + (async function () { + var libraryID = ZoteroPane.tagSelector.libraryID; + var library = Zotero.Libraries.get(libraryID); + var enabled = false; + if (library.editable) { + if ((await Zotero.Tags.getAutomaticInLibrary(libraryID)).length) { + enabled = true; + } + } + document.getElementById('delete-automatic-tags').disabled = !enabled; + })();"> From c735423996bdbba202b362233c8b6c22e3ca31fd Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 8 Mar 2019 20:23:00 -0500 Subject: [PATCH 17/17] Don't show PDF icon for linked-URL PDFs (regression from 8cd5b09053b) --- chrome/content/zotero/xpcom/data/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 83f47f84307..6880bf59978 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -3686,7 +3686,7 @@ Zotero.Item.prototype.getImageSrc = function() { if (itemType == 'attachment') { var linkMode = this.attachmentLinkMode; - if (this.attachmentContentType == 'application/pdf') { + if (this.attachmentContentType == 'application/pdf' && this.isFileAttachment()) { if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) { itemType += '-pdf-link'; }