From e4ad1862d103d1162046d8a5aace47306ad14dfe Mon Sep 17 00:00:00 2001 From: windingwind <33902321+windingwind@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:49:52 +0800 Subject: [PATCH] Refactor itemBox.js create element --- chrome/content/zotero/elements/itemBox.js | 342 +++++++++++----------- 1 file changed, 178 insertions(+), 164 deletions(-) diff --git a/chrome/content/zotero/elements/itemBox.js b/chrome/content/zotero/elements/itemBox.js index 62a3873aba..19c89a3151 100644 --- a/chrome/content/zotero/elements/itemBox.js +++ b/chrome/content/zotero/elements/itemBox.js @@ -609,15 +609,15 @@ rowLabel.className = "meta-label"; rowLabel.setAttribute('fieldname', fieldName); - let valueElement = this.createValueElement( + let valueElement = this.createFieldValueElement( val, fieldName ); if (fieldName) { - let label = document.createElement('label'); - label.className = 'key'; - label.textContent = Zotero.ItemFields.getLocalizedString(fieldName); - label.setAttribute("id", `itembox-field-${fieldName}-label`); + let label = this.createLabelElement({ + text: Zotero.ItemFields.getLocalizedString(fieldName), + id: `itembox-field-${fieldName}-label`, + }); rowLabel.appendChild(label); valueElement.setAttribute('aria-labelledby', label.id); } @@ -853,35 +853,6 @@ }); this._showCreatorTypeGuidance = false; } - - // On click of the label, toggle the focus of the value field - for (let label of this.querySelectorAll(".meta-label > label")) { - if (!this.editable) { - break; - } - - label.addEventListener('mousedown', (event) => { - // Prevent default focus/blur behavior - we implement our own below - event.preventDefault(); - }); - - label.addEventListener('click', (event) => { - event.preventDefault(); - - let labelWrapper = label.closest(".meta-label"); - if (labelWrapper.nextSibling.contains(document.activeElement)) { - document.activeElement.blur(); - } - else { - let valueField = labelWrapper.nextSibling.firstChild; - if (valueField.id === "item-type-menu") { - valueField.querySelector("menupopup").openPopup(); - return; - } - labelWrapper.nextSibling.firstChild.focus(); - } - }); - } this._ensureButtonsFocusable(); this._updateCreatorButtonsStatus(); @@ -925,89 +896,30 @@ let labelElem = document.createElement("div"); labelElem.classList.add("meta-label"); - let label = document.createElement("label"); let labelID = `itembox-custom-row-${row.rowID}-label`; - label.id = labelID; - label.classList.add("key"); + let label = this.createLabelElement({ + id: labelID, + text: row.label.text, + }); label.dataset.l10nId = row.label.l10nID; - label.addEventListener('mousedown', (event) => { - // Prevent default focus/blur behavior - we implement our own below - event.preventDefault(); - }); - - label.addEventListener('click', (event) => { - event.preventDefault(); - - let labelWrapper = label.closest(".meta-label"); - if (labelWrapper.nextSibling.contains(document.activeElement)) { - document.activeElement.blur(); - } - else { - labelWrapper.nextSibling.firstChild.focus(); - } - }); - labelElem.appendChild(label); let dataElem = document.createElement("div"); dataElem.classList.add("meta-data"); - let valueElem = document.createXULElement("editable-text"); - valueElem.id = `itembox-custom-row-${row.rowID}-value`; - valueElem.classList.add("value", "custom-row-value"); - - if (row.multiline) { - valueElem.setAttribute('multiline', true); - } - else if (row.nowrap) { - // Usual fields occupy all available space and keep info on one line - valueElem.setAttribute("nowrap", true); - } - - if (typeof row.editable == "boolean") { - let editable = row.editable; - if (!this.editable) editable = false; - valueElem.toggleAttribute("readonly", !editable); - } - - valueElem.addEventListener("focus", () => { - if (row.multiline) { - valueElem.setAttribute("min-lines", 6); + let editable = row.editable ?? true; + if (!this.editable) editable = false; + let valueElem = this.createValueElement({ + id: `itembox-custom-row-${row.rowID}-value`, + classList: ["custom-row-value"], + isMultiline: row.multiline, + isLong: !row.nowrap, + editable, + attributes: { + "aria-labelledby": labelID } }); - valueElem.addEventListener("blur", () => { - if (row.multiline) { - valueElem.setAttribute("min-lines", 1); - } - let onSetData = Zotero.ItemPaneManager.getInfoRowHook(row.rowID, "onSetData"); - if (onSetData) { - try { - onSetData({ - rowID: row.rowID, - item: this.item, - tabType: this.tabType, - editable: this.editable, - value: valueElem.value - }); - } - catch (e) { - Zotero.logError(e); - } - } - }); - - valueElem.setAttribute('tight', true); - - // Regardless, align the text in the label consistently, following the locale's direction - if (Zotero.rtl) { - valueElem.style.textAlign = 'right'; - } - else { - valueElem.style.textAlign = 'left'; - } - - valueElem.setAttribute("aria-labelledby", labelID); dataElem.appendChild(valueElem); @@ -1142,10 +1054,10 @@ var labelWrapper = document.createElement('div'); labelWrapper.className = "meta-label"; labelWrapper.setAttribute("fieldname", "itemType"); - var label = document.createElement("label"); - label.className = "key"; - label.id = "itembox-field-itemType-label"; - label.innerText = Zotero.getString("zotero.items.itemType"); + var label = this.createLabelElement({ + id: "itembox-field-itemType-label", + text: Zotero.getString("zotero.items.itemType") + }); labelWrapper.appendChild(label); var rowData = document.createElement('div'); rowData.className = "meta-data"; @@ -1270,10 +1182,10 @@ } rowLabel.appendChild(labelWrapper); - var label = document.createElement("label"); - label.setAttribute('id', 'creator-type-label-inner'); - label.className = 'key'; - label.textContent = Zotero.getString('creatorTypes.' + Zotero.CreatorTypes.getName(typeID)); + let label = this.createLabelElement({ + id: 'creator-type-label-inner', + text: Zotero.getString('creatorTypes.' + Zotero.CreatorTypes.getName(typeID)) + }); labelWrapper.appendChild(label); var rowData = document.createElement("div"); @@ -1285,7 +1197,7 @@ var fieldName = 'creator-' + rowIndex + '-lastName'; var lastNameElem = firstlast.appendChild( - this.createValueElement( + this.createFieldValueElement( lastName, fieldName, ) @@ -1294,7 +1206,7 @@ lastNameElem.placeholder = this._defaultLastName; fieldName = 'creator-' + rowIndex + '-firstName'; var firstNameElem = firstlast.appendChild( - this.createValueElement( + this.createFieldValueElement( firstName, fieldName, ) @@ -1471,16 +1383,16 @@ var rowLabel = document.createElement("div"); rowLabel.className = "meta-label"; rowLabel.setAttribute("fieldname", field); - var label = document.createElement('label'); - label.className = 'key'; - label.textContent = Zotero.ItemFields.getLocalizedString(field); - label.setAttribute("id", `itembox-field-${field}-label`); + let label = this.createLabelElement({ + text: Zotero.ItemFields.getLocalizedString(field), + id: `itembox-field-${field}-label` + }); rowLabel.appendChild(label); var rowData = document.createElement('div'); rowData.className = "meta-data date-box"; - var elem = this.createValueElement( + var elem = this.createFieldValueElement( Zotero.Date.multipartToStr(value), field ); @@ -1687,18 +1599,46 @@ return openLink; } - createValueElement(valueText, fieldName) { - valueText += ''; - - if (fieldName) { - var fieldID = Zotero.ItemFields.getID(fieldName); + createLabelElement({ text, id, attributes, classList }) { + let label = document.createElement('label'); + label.classList.add('key', ...classList || []); + if (text) label.textContent = text; + if (id) label.id = id; + if (attributes) { + for (let [key, value] of Object.entries(attributes)) { + label.setAttribute(key, value); + } } - - let isMultiline = Zotero.ItemFields.isMultiline(fieldName); - let isLong = Zotero.ItemFields.isLong(fieldName); - - var valueElement = document.createXULElement("editable-text"); - valueElement.className = 'value'; + // On click of the label, toggle the focus of the value field + if (this.editable) { + label.addEventListener('mousedown', (event) => { + // Prevent default focus/blur behavior - we implement our own below + event.preventDefault(); + }); + + label.addEventListener('click', (event) => { + event.preventDefault(); + + let labelWrapper = label.closest(".meta-label"); + if (labelWrapper.nextSibling.contains(document.activeElement)) { + document.activeElement.blur(); + } + else { + let valueField = labelWrapper.nextSibling.firstChild; + if (valueField.id === "item-type-menu") { + valueField.querySelector("menupopup").openPopup(); + return; + } + labelWrapper.nextSibling.firstChild.focus(); + } + }); + } + return label; + } + + createValueElement({ isMultiline, isLong, editable, text, tooltipText, id, attributes, classList } = {}) { + let valueElement = document.createXULElement("editable-text"); + valueElement.classList.add('value', ...classList || []); if (isMultiline) { valueElement.setAttribute('multiline', true); } @@ -1706,9 +1646,7 @@ // Usual fields occupy all available space and keep info on one line valueElement.setAttribute("nowrap", true); } - - - if (this._fieldIsClickable(fieldName)) { + if (editable) { valueElement.addEventListener("focus", e => this.showEditor(e.target)); valueElement.addEventListener("blur", e => this.hideEditor(e.target)); } @@ -1716,13 +1654,51 @@ valueElement.setAttribute('readonly', true); } - valueElement.setAttribute('id', `itembox-field-value-${fieldName}`); - valueElement.setAttribute('fieldname', fieldName); + if (id) valueElement.id = id; + if (tooltipText) valueElement.tooltipText = tooltipText; + if (attributes) { + for (let [key, value] of Object.entries(attributes)) { + valueElement.setAttribute(key, value); + } + } valueElement.setAttribute('tight', true); + valueElement.value = text; + if (text) { + valueElement.dir = 'auto'; + } + // If not, assume it follows the locale's direction + else { + valueElement.dir = Zotero.dir; + } + + // Regardless, align the text in the label consistently, following the locale's direction + if (Zotero.rtl) { + valueElement.style.textAlign = 'right'; + } + else { + valueElement.style.textAlign = 'left'; + } + return valueElement; + } + + createFieldValueElement(valueText, fieldName) { + valueText += ''; + + if (fieldName) { + var fieldID = Zotero.ItemFields.getID(fieldName); + } + + let isMultiline = Zotero.ItemFields.isMultiline(fieldName); + let isLong = Zotero.ItemFields.isLong(fieldName); + + let attributes = { + fieldname: fieldName, + }; + switch (fieldName) { case 'itemType': - valueElement.setAttribute('itemTypeID', valueText); + attributes.itemTypeID = valueText; valueText = Zotero.ItemTypes.getLocalizedString(valueText); break; @@ -1741,33 +1717,25 @@ break; } + let tooltipText; if (fieldID) { // Display the SQL date as a tooltip for date fields // TEMP - filingDate if (Zotero.ItemFields.isFieldOfBase(fieldID, 'date') || fieldName == 'filingDate') { - valueElement.tooltipText = Zotero.Date.multipartToSQL(this.item.getField(fieldName, true)); + tooltipText = Zotero.Date.multipartToSQL(this.item.getField(fieldName, true)); } } - valueElement.value = valueText; + let valueElement = this.createValueElement({ + isMultiline, + isLong, + editable: this._fieldIsClickable(fieldName), + text: valueText, + tooltipText, + id: `itembox-field-value-${fieldName}`, + attributes + }); - // Attempt to make bidi things work automatically: - // If we have text to work off of, let the layout engine try to guess the text direction - if (valueText) { - valueElement.dir = 'auto'; - } - // If not, assume it follows the locale's direction - else { - valueElement.dir = Zotero.dir; - } - - // Regardless, align the text in the label consistently, following the locale's direction - if (Zotero.rtl) { - valueElement.style.textAlign = 'right'; - } - else { - valueElement.style.textAlign = 'left'; - } if (!fieldName.startsWith('creator-')) { // autocomplete for creator names is added in addCreatorRow this.addAutocompleteToElement(valueElement); @@ -1836,13 +1804,25 @@ } async showEditor(elem) { - Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`); + let isCustomRow = elem.classList.contains("custom-row-value"); + var fieldName = elem.getAttribute('fieldname'); + let isMultiline = Zotero.ItemFields.isMultiline(fieldName); + if (isCustomRow) { + isMultiline = elem.hasAttribute("multiline"); + } // Multiline field will be at least 6 lines - if (Zotero.ItemFields.isMultiline(fieldName)) { + if (isMultiline) { elem.setAttribute("min-lines", 6); } + + if (isCustomRow) { + return; + } + + Zotero.debug(`Showing editor for ${fieldName}`); + var [field, creatorIndex, creatorField] = fieldName.split('-'); let value; if (field == 'creator') { @@ -2099,20 +2079,54 @@ if (this.ignoreBlur || !textbox) { return; } + + var fieldName = textbox.getAttribute('fieldname'); + + let isMultiline = Zotero.ItemFields.isMultiline(fieldName); + let isCustomRow = textbox.classList.contains("custom-row-value"); + + if (isCustomRow) { + isMultiline = textbox.hasAttribute("multiline"); + } + + if (isMultiline) { + textbox.setAttribute("min-lines", 1); + } + + if (isCustomRow) { + let rowID = textbox.closest(".meta-row").dataset.customRowId; + let onSetData = Zotero.ItemPaneManager.getInfoRowHook(rowID, "onSetData"); + if (onSetData) { + try { + onSetData({ + rowID, + item: this.item, + tabType: this.tabType, + editable: this.editable, + value: textbox.value + }); + } + catch (e) { + Zotero.logError(e); + } + } + + return; + } + // Handle cases where creator autocomplete doesn't trigger // the textentered and change events handled in showEditor - if (textbox.getAttribute('fieldname').startsWith('creator-')) { + if (fieldName.startsWith('creator-')) { this.handleCreatorAutoCompleteSelect(textbox); } - - Zotero.debug(`Hiding editor for ${textbox.getAttribute('fieldname')}`); + + Zotero.debug(`Hiding editor for ${fieldName}`); // Prevent autocomplete breakage in Firefox 3 if (textbox.mController) { textbox.mController.input = null; } - var fieldName = textbox.getAttribute('fieldname'); // Multiline fields go back to occupying as much space as needed if (Zotero.ItemFields.isMultiline(fieldName)) {