From 6bc97a6af62616369e1aaf5665f79179fe025e1e Mon Sep 17 00:00:00 2001 From: Abe Jellinek Date: Thu, 26 Oct 2023 16:21:22 -0400 Subject: [PATCH] Extract field transform menu and use for title header And extract edit context menu building code into a separate function in `editMenuOverlay.js` so we can build menus on top of it. --- chrome/content/zotero/editMenuOverlay.js | 48 +++++++------ chrome/content/zotero/elements/itemBox.js | 74 +++++--------------- chrome/content/zotero/elements/paneHeader.js | 26 +++++++ chrome/content/zotero/itemPane.js | 33 +++++++++ 4 files changed, 102 insertions(+), 79 deletions(-) diff --git a/chrome/content/zotero/editMenuOverlay.js b/chrome/content/zotero/editMenuOverlay.js index 84cbba0dfc..5a7f1bba17 100644 --- a/chrome/content/zotero/editMenuOverlay.js +++ b/chrome/content/zotero/editMenuOverlay.js @@ -92,6 +92,31 @@ window.addEventListener( { once: true } ); +function goBuildEditContextMenu() { + let popup = document.getElementById("textbox-contextmenu"); + if (!popup) { + MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl"); + document.documentElement.appendChild( + MozXULElement.parseXULToFragment(` + + + + + + + + + + + + + `) + ); + popup = document.documentElement.lastElementChild.firstElementChild; + } + return popup; +} + // Support context menus on html textareas in the parent process: window.addEventListener("contextmenu", e => { const HTML_NS = "http://www.w3.org/1999/xhtml"; @@ -107,28 +132,7 @@ window.addEventListener("contextmenu", e => { return; } - let popup = document.getElementById("textbox-contextmenu"); - if (!popup) { - MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl"); - document.documentElement.appendChild( - MozXULElement.parseXULToFragment(` - - - - - - - - - - - - - `) - ); - popup = document.documentElement.lastElementChild.firstElementChild; - } - + let popup = goBuildEditContextMenu(); goUpdateGlobalEditMenuItems(true); popup.openPopupAtScreen(e.screenX, e.screenY, true, e); // Don't show any other context menu at the same time. There can be a diff --git a/chrome/content/zotero/elements/itemBox.js b/chrome/content/zotero/elements/itemBox.js index 36ad39b886..3a9af16304 100644 --- a/chrome/content/zotero/elements/itemBox.js +++ b/chrome/content/zotero/elements/itemBox.js @@ -60,12 +60,6 @@
- - - - @@ -174,16 +168,6 @@ } }); - this._id('zotero-field-transform-menu').addEventListener('popupshowing', () => { - this._id('creator-transform-title-case').disabled = !this.canTextTransformField(document.popupNode, 'title'); - this._id('creator-transform-sentence-case').disabled = !this.canTextTransformField(document.popupNode, 'sentence'); - }); - - this._id('creator-transform-title-case').addEventListener('command', - () => this.textTransformField(document.popupNode, 'title')); - this._id('creator-transform-sentence-case').addEventListener('command', - () => this.textTransformField(document.popupNode, 'sentence')); - this._id('zotero-creator-transform-menu').addEventListener('popupshowing', (event) => { var row = document.popupNode.closest('tr'); var typeBox = row.querySelector('.creator-type-label'); @@ -1446,14 +1430,17 @@ if (this.editable && (fieldName == 'seriesTitle' || fieldName == 'shortTitle' || Zotero.ItemFields.isFieldOfBase(fieldID, 'title') || Zotero.ItemFields.isFieldOfBase(fieldID, 'publicationTitle'))) { - valueElement.setAttribute('context', 'zotero-field-transform-menu'); valueElement.oncontextmenu = (event) => { - document.popupNode = valueElement; - this._id('zotero-field-transform-menu').openPopupAtScreen( - event.screenX + 1, - event.screenY + 1, - true - ); + let oldValue = this._getFieldValue(valueElement); + let menupopup = ZoteroItemPane.buildFieldTransformMenu({ + value: oldValue, + onTransform: (newValue) => { + this._setFieldTransformedValue(valueElement, newValue); + } + }); + this.querySelector('popupset').append(menupopup); + menupopup.addEventListener('popuphidden', () => menupopup.remove()); + menupopup.openPopupAtScreen(event.screenX + 1, event.screenY + 1, true); }; } } @@ -2132,39 +2119,10 @@ } } - textTransformString(val, mode) { - switch (mode) { - case 'title': - return Zotero.Utilities.capitalizeTitle(val.toLowerCase(), true); - case 'sentence': - return Zotero.Utilities.sentenceCase(val); - default: - throw new Error("Invalid transform mode '" + mode + "' in ItemBox.textTransformString()"); - } - } - - canTextTransformField(label, mode) { - let val = this._getFieldValue(label); - return this.textTransformString(val, mode) != val; - } - - /** - * TODO: work with textboxes too - */ - async textTransformField(label, mode) { - var val = this._getFieldValue(label); - var newVal = this.textTransformString(val, mode); - this._setFieldValue(label, newVal); + async _setFieldTransformedValue(label, newValue) { + this._setFieldValue(label, newValue); var fieldName = label.getAttribute('fieldname'); - this._modifyField(fieldName, newVal); - - // If this is a title field, convert the Short Title too - var isTitle = Zotero.ItemFields.getBaseIDFromTypeAndField( - this.item.itemTypeID, fieldName) == Zotero.ItemFields.getID('title'); - var shortTitleVal = this.item.getField('shortTitle'); - if (isTitle && newVal.toLowerCase().startsWith(shortTitleVal.toLowerCase())) { - this._modifyField('shortTitle', newVal.substr(0, shortTitleVal.length)); - } + this._modifyField(fieldName, newValue); if (this.saveOnEdit) { // If a field is open, blur it, which will trigger a save and cause @@ -2232,7 +2190,8 @@ fields.firstName = lastName; this.modifyCreator(creatorIndex, fields); if (this.saveOnEdit) { - // See note in transformText() + // If a field is open, blur it, which will trigger a save and cause + // the saveTx() to be a no-op await this.blurOpenField(); await this.item.saveTx(); } @@ -2256,7 +2215,8 @@ fields.lastName = fields.lastName && Zotero.Utilities.capitalizeName(fields.lastName); this.modifyCreator(creatorIndex, fields); if (this.saveOnEdit) { - // See note in transformText() + // If a field is open, blur it, which will trigger a save and cause + // the saveTx() to be a no-op await this.blurOpenField(); await this.item.saveTx(); } diff --git a/chrome/content/zotero/elements/paneHeader.js b/chrome/content/zotero/elements/paneHeader.js index 5f37361ae3..838d8d2ff6 100644 --- a/chrome/content/zotero/elements/paneHeader.js +++ b/chrome/content/zotero/elements/paneHeader.js @@ -79,6 +79,22 @@ this.titleField.addEventListener('change', () => this.save()); this.titleField.ariaLabel = Zotero.getString('itemFields.title'); + this.titleField.addEventListener('contextmenu', (event) => { + if (!this._item) return; + + event.preventDefault(); + let oldValue = this.titleField.value; + let menupopup = ZoteroItemPane.buildFieldTransformMenu({ + value: oldValue, + onTransform: (newValue) => { + this._setTransformedValue(oldValue, newValue); + }, + includeEditMenuOptions: true + }); + this.ownerDocument.querySelector('popupset').append(menupopup); + menupopup.addEventListener('popuphidden', () => menupopup.remove()); + menupopup.openPopupAtScreen(event.screenX + 1, event.screenY + 1, true); + }); this.render(); } @@ -93,6 +109,16 @@ } } + async _setTransformedValue(oldValue, newValue) { + await this.blurOpenField(); + this._item.setField(this._titleFieldID, newValue); + let shortTitleVal = this._item.getField('shortTitle'); + if (newValue.toLowerCase().startsWith(shortTitleVal.toLowerCase())) { + this._item.setField('shortTitle', newValue.substr(0, shortTitleVal.length)); + } + await this._item.saveTx(); + } + async save() { if (this.item) { this.item.setField(this._titleFieldID, this.titleField.value); diff --git a/chrome/content/zotero/itemPane.js b/chrome/content/zotero/itemPane.js index 0e244dbef0..1e4b684288 100644 --- a/chrome/content/zotero/itemPane.js +++ b/chrome/content/zotero/itemPane.js @@ -250,6 +250,39 @@ var ZoteroItemPane = new function() { this.getSidenavSelectedPane = function () { return _sidenav.selectedPane; }; + + + this.buildFieldTransformMenu = function ({ value, onTransform, includeEditMenuOptions = false }) { + let valueTitleCased = Zotero.Utilities.capitalizeTitle(value.toLowerCase(), true); + let valueSentenceCased = Zotero.Utilities.sentenceCase(value); + + let menupopup = document.createXULElement('menupopup'); + + if (includeEditMenuOptions) { + goBuildEditContextMenu(); + let menuitems = [...document.getElementById('textbox-contextmenu').children]; + menupopup.append(...menuitems.map(menuitem => menuitem.cloneNode(true))); + menupopup.append(document.createXULElement('menuseparator')); + } + + let titleCase = document.createXULElement('menuitem'); + titleCase.setAttribute('label', Zotero.getString('zotero.item.textTransform.titlecase')); + titleCase.addEventListener('command', () => { + onTransform(valueTitleCased); + }); + titleCase.disabled = valueTitleCased == value; + menupopup.append(titleCase); + + let sentenceCase = document.createXULElement('menuitem'); + sentenceCase.setAttribute('label', Zotero.getString('zotero.item.textTransform.sentencecase')); + sentenceCase.addEventListener('command', () => { + onTransform(valueSentenceCased); + }); + sentenceCase.disabled = valueSentenceCased == value; + menupopup.append(sentenceCase); + + return menupopup; + }; }; addEventListener("load", function(e) { ZoteroItemPane.onLoad(e); }, false);