diff --git a/chrome/content/zotero/containers/tagSelectorContainer.jsx b/chrome/content/zotero/containers/tagSelectorContainer.jsx index 467ca59263..a7e529ac5c 100644 --- a/chrome/content/zotero/containers/tagSelectorContainer.jsx +++ b/chrome/content/zotero/containers/tagSelectorContainer.jsx @@ -642,6 +642,46 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent { await Zotero.Tags.setColor(this.libraryID, io.name, io.color, io.position); } + async openTagSplitterWindow() { + const oldTagName = this.contextTag.name; // contextTag contains { name, width, color } + const dataIn = { + oldTag: this.contextTag.name, + isLongTag: false + }; + const dataOut = { result: null }; + + window.openDialog( + 'chrome://zotero/content/longTagFixer.xhtml', + '', + 'chrome,modal,centerscreen', + dataIn, dataOut + ); + + if (!dataOut.result) { + return; + } + + const oldTagID = Zotero.Tags.getID(oldTagName); + + if (dataOut.result.op === 'split') { + const itemIDs = await Zotero.Tags.getTagItems(this.libraryID, oldTagID); + await Zotero.DB.executeTransaction(async () => { + for (const itemID of itemIDs) { + const item = await Zotero.Items.getAsync(itemID); + const tagType = item.getTagType(oldTagName); + for (const newTagName of dataOut.result.tags) { + item.addTag(newTagName, tagType); + } + item.removeTag(oldTagName); + await item.save(); + } + await Zotero.Tags.purge(oldTagID); + }); + } else { + throw new Error('Unsupported op: ' + dataOut.result.op); + } + } + async openRenamePrompt() { var promptService = Cc['@mozilla.org/embedcomp/prompt-service;1'] .getService(Ci.nsIPromptService); diff --git a/chrome/content/zotero/longTagFixer.js b/chrome/content/zotero/longTagFixer.js index 9acb1fd9b3..d2dd331c83 100644 --- a/chrome/content/zotero/longTagFixer.js +++ b/chrome/content/zotero/longTagFixer.js @@ -1,191 +1,212 @@ /* ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - + + Copyright © 2022 Corporation for Digital Scholarship + Vienna, Virginia, USA + https://www.zotero.org + This file is part of Zotero. - + Zotero is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + Zotero is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with Zotero. If not, see . - + ***** END LICENSE BLOCK ***** */ +const HTML_NS = 'http://www.w3.org/1999/xhtml'; -var Zotero_Long_Tag_Fixer = new function () { - var _oldTag = window.arguments[0]; - var _dataOut = window.arguments[1]; +var Zotero_Long_Tag_Fixer = new function () { // eslint-disable-line camelcase, no-unused-vars + const { oldTag, isLongTag } = window.arguments?.[0] ?? { isLongTag: true, oldTag: '' }; + const dataOut = window.arguments?.[1] || {}; this.init = function () { - document.getElementById('zotero-old-tag').value = _oldTag; - document.getElementById('zotero-old-tag-delimiter').nextSibling.value = Zotero.getString('general.character.singular'); - - var delimiter = Zotero.Prefs.get('lastLongTagDelimiter'); - document.getElementById('zotero-old-tag-delimiter').value = delimiter; - - var lastMode = Zotero.Prefs.get('lastLongTagMode'); - if (!lastMode) { - lastMode = 0; - } - this.switchMode(lastMode); - } + const lastMode = Zotero.Prefs.get('lastLongTagMode') || 0; + const delimiter = Zotero.Prefs.get('lastLongTagDelimiter'); + + this.dialog = document.getElementById('zotero-long-tag-fixer'); + this.intro = document.getElementById('intro'); + this.tabs = document.getElementById('zotero-new-tag-actions'); + this.oldTagInput = document.getElementById('zotero-old-tag'); + this.oldTag = document.getElementById('zotero-old-tag'); + this.delimiterLabel = document.getElementById('delimiter-label'); + this.oldTagDelimiter = document.getElementById('zotero-old-tag-delimiter'); + this.listbox = document.getElementById('zotero-new-tag-list'); + this.newTagInput = document.getElementById('zotero-new-tag-editor'); + this.newTagCharacterCount = document.getElementById('zotero-new-tag-character-count'); + this.zoteroNewTagInfo = document.getElementById('zotero-new-tag-characters'); + + document.addEventListener('dialogaccept', () => this.accept()); + document.addEventListener('dialogcancel', () => this.cancel()); + this.tabs.addEventListener('select', (ev) => { + if (ev.target === this.tabs.querySelector('tabpanels')) { + this.switchMode(ev.currentTarget.selectedIndex); + } + }); + + this.dialog.classList.toggle('is-long-tag', isLongTag); + + this.oldTagDelimiter.addEventListener('input', () => this.onUpdateDelimiter()); + this.newTagInput.addEventListener('input', ev => this.updateEditLength(ev.currentTarget.value.length)); + + this.oldTagInput.value = oldTag; + this.oldTagDelimiter.value = delimiter; + + this.updateLabel(); + this.switchMode(isLongTag ? lastMode : 0); + }; this.switchMode = function (index) { - var dialog = document.getElementById('zotero-long-tag-fixer'); - - document.getElementById('zotero-new-tag-actions').selectedIndex = index; + this.tabs.selectedIndex = index; + let buttonLabel = ""; switch (index) { + default: case 0: - var buttonLabel = "saveTags"; + buttonLabel = 'saveTags'; this.updateTagList(); - document.getElementById('zotero-old-tag-delimiter').select(); + this.oldTagDelimiter.select(); break; case 1: - var buttonLabel = "saveTag"; - document.getElementById('zotero-new-tag-editor').value = _oldTag; - this.updateEditLength(_oldTag.length) + buttonLabel = 'saveTag'; + this.newTagInput.value = oldTag; + this.updateEditLength(oldTag.length); break; case 2: - var buttonLabel = "deleteTag"; - dialog.getButton('accept').disabled = false; + buttonLabel = 'deleteTag'; + this.dialog.getButton('accept').disabled = false; break; } - document.getElementById('zotero-long-tag-fixer').getButton('accept').label = Zotero.getString('sync.longTagFixer.' + buttonLabel); + this.dialog.getButton('accept').label = Zotero.getString('sync.longTagFixer.' + buttonLabel); window.sizeToContent(); - Zotero.Prefs.set('lastLongTagMode', index); - } - + if (isLongTag) { + Zotero.Prefs.set('lastLongTagMode', index); + } + }; /** * Split tags and populate list */ this.updateTagList = function () { - var listbox = document.getElementById('zotero-new-tag-list'); - while (listbox.childNodes.length) { - listbox.removeChild(listbox.lastChild); - } + let tags = []; - var delimiter = document.getElementById('zotero-old-tag-delimiter').value; + const delimiter = document.getElementById('zotero-old-tag-delimiter').value; if (delimiter) { Zotero.Prefs.set('lastLongTagDelimiter', delimiter); - var re = new RegExp("\\s*" + delimiter.replace(/([\.\-\[\]\(\)\?\*\+])/g, "\\$1") + "\\s*"); - var tags = _oldTag.split(re); + const re = new RegExp("\\s*" + delimiter.replace(/([\.\-\[\]\(\)\?\*\+])/g, "\\$1") + "\\s*"); + tags = [...new Set(oldTag.split(re).filter(t => t.length > 0))]; } - var acceptButton = document.getElementById('zotero-long-tag-fixer').getButton('accept'); + const acceptButton = document.getElementById('zotero-long-tag-fixer').getButton('accept'); if (!delimiter || tags.length < 2) { acceptButton.disabled = true; - return; + // return; } else { acceptButton.disabled = false; } tags.sort(); - for (var i=0; i { + const li = document.createElement('richlistitem'); + const div = document.createElement('div'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.checked = true; + checkbox.id = 'tag-' + tag; + const label = document.createElement('label'); + label.setAttribute('for', 'tag-' + tag); + label.textContent = tag; + // Don't toggle checkbox for single-click on label + + div.appendChild(checkbox); + div.appendChild(label); + li.appendChild(div); + this.listbox.append(li); + }); window.sizeToContent(); - } + }; + + this.updateLabel = function () { + this.delimiterLabel.innerHTML = this.oldTagDelimiter.value.length > 1 + ? Zotero.getString('general.character.plural') + : Zotero.getString('general.character.singular'); + }; + + this.onUpdateDelimiter = function () { + this.updateLabel(); + this.updateTagList(); + }; this.deselectAll = function () { - var lis = document.getElementById('zotero-new-tag-list').getElementsByTagName('listitem'); - for (var i=0; i checkbox.checked = false); + }; this.selectAll = function () { - var lis = document.getElementById('zotero-new-tag-list').getElementsByTagName('listitem'); - for (var i=0; i checkbox.checked = true); + }; this.updateEditLength = function (len) { - document.getElementById('zotero-new-tag-character-count').value = len; - var invalid = len == 0 || len > Zotero.Tags.MAX_SYNC_LENGTH; - document.getElementById('zotero-new-tag-characters').setAttribute('invalid', invalid); - document.getElementById('zotero-long-tag-fixer').getButton('accept').disabled = invalid; - } + this.newTagCharacterCount.innerText = len; + const invalid = len == 0 || len > Zotero.Tags.MAX_SYNC_LENGTH; + this.zoteroNewTagInfo.classList.toggle('invalid', invalid); + this.dialog.getButton('accept').disabled = invalid; + }; this.cancel = function () { - _dataOut.result = false; - } + dataOut.result = false; + }; - this.save = function () { + this.accept = function () { try { - - var result = {}; - - var index = document.getElementById('zotero-new-tag-actions').selectedIndex; - - switch (index) { - // Split - case 0: - // Get checked tags - var listbox = document.getElementById('zotero-new-tag-list'); - var len = listbox.childElementCount; - var newTags = []; - for (var i=0; i c.checked) + .map(n => n.nextSibling.textContent); + break; + // Edit + case 1: + result.op = 'edit'; + result.tag = this.newTagInput.value; + break; - result.op = 'split'; - result.tags = newTags; - break; - - // Edit - case 1: - result.op = 'edit'; - result.tag = document.getElementById('zotero-new-tag-editor').value; - break; - - // Delete - case 2: - result.op = 'delete'; - break; - } - - _dataOut.result = result; - + // Delete + case 2: + result.op = 'delete'; + break; + } + dataOut.result = result; } catch (e) { Zotero.debug(e); throw (e); } - } -} + }; +}; diff --git a/chrome/content/zotero/longTagFixer.xhtml b/chrome/content/zotero/longTagFixer.xhtml new file mode 100644 index 0000000000..ca40793638 --- /dev/null +++ b/chrome/content/zotero/longTagFixer.xhtml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +