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.
This commit is contained in:
Abe Jellinek 2023-10-26 16:21:22 -04:00 committed by Dan Stillman
parent 17f758d0cf
commit 6bc97a6af6
4 changed files with 102 additions and 79 deletions

View file

@ -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(`
<popupset>
<menupopup id="textbox-contextmenu" class="textbox-contextmenu">
<menuitem data-l10n-id="text-action-undo" command="cmd_undo"></menuitem>
<menuitem data-l10n-id="text-action-redo" command="cmd_redo"></menuitem>
<menuseparator></menuseparator>
<menuitem data-l10n-id="text-action-cut" command="cmd_cut"></menuitem>
<menuitem data-l10n-id="text-action-copy" command="cmd_copy"></menuitem>
<menuitem data-l10n-id="text-action-paste" command="cmd_paste"></menuitem>
<menuitem data-l10n-id="text-action-delete" command="cmd_delete"></menuitem>
<menuitem data-l10n-id="text-action-select-all" command="cmd_selectAll"></menuitem>
</menupopup>
</popupset>
`)
);
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(`
<popupset>
<menupopup id="textbox-contextmenu" class="textbox-contextmenu">
<menuitem data-l10n-id="text-action-undo" command="cmd_undo"></menuitem>
<menuitem data-l10n-id="text-action-redo" command="cmd_redo"></menuitem>
<menuseparator></menuseparator>
<menuitem data-l10n-id="text-action-cut" command="cmd_cut"></menuitem>
<menuitem data-l10n-id="text-action-copy" command="cmd_copy"></menuitem>
<menuitem data-l10n-id="text-action-paste" command="cmd_paste"></menuitem>
<menuitem data-l10n-id="text-action-delete" command="cmd_delete"></menuitem>
<menuitem data-l10n-id="text-action-select-all" command="cmd_selectAll"></menuitem>
</menupopup>
</popupset>
`)
);
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

View file

@ -60,12 +60,6 @@
<div id="item-box" xmlns="http://www.w3.org/1999/xhtml">
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<menupopup id="creator-type-menu" position="after_start"/>
<menupopup id="zotero-field-transform-menu">
<menuitem id="creator-transform-title-case" label="&zotero.item.textTransform.titlecase;"
class="menuitem-non-iconic"/>
<menuitem id="creator-transform-sentence-case" label="&zotero.item.textTransform.sentencecase;"
class="menuitem-non-iconic"/>
</menupopup>
<menupopup id="zotero-creator-transform-menu">
<menuitem id="creator-transform-swap-names" label="&zotero.item.creatorTransform.nameSwap;"/>
<menuitem id="creator-transform-capitalize" label="&zotero.item.creatorTransform.fixCase;"/>
@ -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();
}

View file

@ -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);

View file

@ -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);