diff --git a/chrome/content/zotero/customElements.js b/chrome/content/zotero/customElements.js index c027e611cd..9e9f0e0b64 100644 --- a/chrome/content/zotero/customElements.js +++ b/chrome/content/zotero/customElements.js @@ -28,8 +28,6 @@ Services.scriptloader.loadSubScript("chrome://zotero/content/include.js", this); Services.scriptloader.loadSubScript("chrome://global/content/customElements.js", this); Services.scriptloader.loadSubScript("chrome://zotero/content/elements/base.js", this); -Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemPaneSection.js', this); -Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemPaneContainer.js', this); Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemTreeMenuBar.js', this); { @@ -63,6 +61,7 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemTreeMe ['zoterosearchcondition', 'chrome://zotero/content/elements/zoteroSearch.js'], ['zoterosearchtextbox', 'chrome://zotero/content/elements/zoteroSearch.js'], ['zoterosearchagefield', 'chrome://zotero/content/elements/zoteroSearch.js'], + ['item-pane-custom-section', 'chrome://zotero/content/elements/itemPaneCustomSection.js'], ['item-pane-header', 'chrome://zotero/content/elements/itemPaneHeader.js'], ['editable-text', 'chrome://zotero/content/elements/editableText.js'], ['item-pane-sidenav', 'chrome://zotero/content/elements/itemPaneSidenav.js'], diff --git a/chrome/content/zotero/elements/abstractBox.js b/chrome/content/zotero/elements/abstractBox.js index b3e4b34dba..ca8632dd3b 100644 --- a/chrome/content/zotero/elements/abstractBox.js +++ b/chrome/content/zotero/elements/abstractBox.js @@ -26,6 +26,10 @@ "use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); ChromeUtils.importESModule("chrome://zotero/content/actors/ActorManager.mjs"); const SANDBOX_ALL_FLAGS = 0xFFFFF; diff --git a/chrome/content/zotero/elements/attachmentAnnotationsBox.js b/chrome/content/zotero/elements/attachmentAnnotationsBox.js index e4e0498531..a0d5bca8fa 100644 --- a/chrome/content/zotero/elements/attachmentAnnotationsBox.js +++ b/chrome/content/zotero/elements/attachmentAnnotationsBox.js @@ -24,6 +24,11 @@ */ { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class AttachmentAnnotationsBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/attachmentBox.js b/chrome/content/zotero/elements/attachmentBox.js index b9d25371b2..160b90328e 100644 --- a/chrome/content/zotero/elements/attachmentBox.js +++ b/chrome/content/zotero/elements/attachmentBox.js @@ -28,6 +28,10 @@ { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); let { canRenameFileFromParent, renameFileFromParent } = ChromeUtils.importESModule("chrome://zotero/content/renameFiles.mjs"); class AttachmentBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/attachmentPreview.js b/chrome/content/zotero/elements/attachmentPreview.js index 7142a51498..08a9a019b1 100644 --- a/chrome/content/zotero/elements/attachmentPreview.js +++ b/chrome/content/zotero/elements/attachmentPreview.js @@ -24,6 +24,11 @@ */ { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class PreviewRenderAbortError extends Error { constructor() { super("AttachmentPreview render aborted"); diff --git a/chrome/content/zotero/elements/attachmentPreviewBox.js b/chrome/content/zotero/elements/attachmentPreviewBox.js index 1606f3fa6e..5f4426fbab 100644 --- a/chrome/content/zotero/elements/attachmentPreviewBox.js +++ b/chrome/content/zotero/elements/attachmentPreviewBox.js @@ -25,6 +25,11 @@ { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class AttachmentPreviewBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/attachmentsBox.js b/chrome/content/zotero/elements/attachmentsBox.js index 817e02e052..b1882df299 100644 --- a/chrome/content/zotero/elements/attachmentsBox.js +++ b/chrome/content/zotero/elements/attachmentsBox.js @@ -26,6 +26,11 @@ "use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class AttachmentsBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/duplicatesMergePane.js b/chrome/content/zotero/elements/duplicatesMergePane.js index e3e850e051..cd7154c40d 100644 --- a/chrome/content/zotero/elements/duplicatesMergePane.js +++ b/chrome/content/zotero/elements/duplicatesMergePane.js @@ -24,6 +24,11 @@ */ { + const { ItemPaneContainerBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneContainerBase.mjs", + { global: "current" } + ); + let lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { mergeItems: "chrome://zotero/content/mergeItems.mjs", diff --git a/chrome/content/zotero/elements/itemBox.js b/chrome/content/zotero/elements/itemBox.js index 7016085b19..e87a96916e 100644 --- a/chrome/content/zotero/elements/itemBox.js +++ b/chrome/content/zotero/elements/itemBox.js @@ -26,6 +26,10 @@ "use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); const lazy = {}; diff --git a/chrome/content/zotero/elements/itemDetails.js b/chrome/content/zotero/elements/itemDetails.js index e2f7abb846..c441d403da 100644 --- a/chrome/content/zotero/elements/itemDetails.js +++ b/chrome/content/zotero/elements/itemDetails.js @@ -24,6 +24,11 @@ */ { + const { ItemPaneContainerBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneContainerBase.mjs", + { global: "current" } + ); + class ItemDetails extends ItemPaneContainerBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/itemPaneContainer.js b/chrome/content/zotero/elements/itemPaneContainerBase.mjs similarity index 98% rename from chrome/content/zotero/elements/itemPaneContainer.js rename to chrome/content/zotero/elements/itemPaneContainerBase.mjs index ea55068d26..2f760c1246 100644 --- a/chrome/content/zotero/elements/itemPaneContainer.js +++ b/chrome/content/zotero/elements/itemPaneContainerBase.mjs @@ -23,7 +23,6 @@ ***** END LICENSE BLOCK ***** */ - /** * Base class for elements that host a vertical stack of [data-pane] sections * controlled by an . @@ -40,8 +39,7 @@ * render of following panes, etc.) can override the _beforeScrollToPane() and * _afterScrollToPane() hooks. */ -// eslint-disable-next-line no-unused-vars -class ItemPaneContainerBase extends XULElementBase { +export class ItemPaneContainerBase extends XULElementBase { _paneParent = null; connectedCallback() { diff --git a/chrome/content/zotero/elements/itemPaneSection.js b/chrome/content/zotero/elements/itemPaneCustomSection.js similarity index 69% rename from chrome/content/zotero/elements/itemPaneSection.js rename to chrome/content/zotero/elements/itemPaneCustomSection.js index 37d8aef37e..d528942389 100644 --- a/chrome/content/zotero/elements/itemPaneSection.js +++ b/chrome/content/zotero/elements/itemPaneCustomSection.js @@ -23,162 +23,14 @@ ***** END LICENSE BLOCK ***** */ - -class ItemPaneSectionElementBase extends XULElementBase { - get item() { - return this._item; - } - - set item(item) { - this._item = item; - if (this._handleItemChange) this._handleItemChange(); - } - - get editable() { - return this._editable; - } - - set editable(editable) { - this._editable = editable; - this.toggleAttribute('readonly', !editable); - } - - get tabID() { - return this._tabID; - } - - set tabID(tabID) { - this._tabID = tabID; - } - - get tabType() { - return this._tabType; - } - - set tabType(tabType) { - this._tabType = tabType; - this.setAttribute('tabType', tabType); - } - - get collectionTreeRow() { - return this._collectionTreeRow; - } - - set collectionTreeRow(collectionTreeRow) { - this._collectionTreeRow = collectionTreeRow; - } - - _syncRenderPending = false; - - _asyncRenderPending = false; - - /** Controlled by parent element */ - skipRender = false; - - get open() { - return this._section?.open || false; - } - - set open(val) { - if (this._section) { - this._section.open = val; - } - } - - get collapsible() { - return this._section.collapsible; - } - - set collapsible(val) { - this._section.collapsible = !!val; - } - - connectedCallback() { - super.connectedCallback(); - if (!this.render && !this.asyncRender) { - Zotero.warn("Pane section must have method render or asyncRender."); - } - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (this._section) { - this._section.removeEventListener("toggle", this._handleSectionToggle); - this._section = null; - } - this._resetRenderedFlags(); - } - - initCollapsibleSection() { - this._section = this.querySelector('collapsible-section'); - if (this._section) { - this._section.addEventListener("toggle", this._handleSectionToggle); - } - } - - _handleSectionToggle = async (event) => { - if (event.target !== this._section || !this._section.open) { - return; - } - await this._forceRenderAll(); - }; - - get _renderDependencies() { - return [this._tabID, this._item?.id]; - } - - /** - * @param {"sync" | "async"} [type] - * @returns {boolean} - */ - _isAlreadyRendered(type = "sync") { - let key = `_${type}RenderDependencies`; - let pendingKey = `_${type}RenderPending`; - let itemIDKey = `_${type}RenderItemID`; - - let oldDependencies = this[key]; - let newDependencies = this._renderDependencies; - - let isPending = this[pendingKey]; - let isRendered = Zotero.Utilities.arrayEquals(oldDependencies, newDependencies); - if (this.skipRender) { - if (!isRendered) { - this[pendingKey] = true; - } - // Skip render - return true; - } - - if (!isPending && isRendered) { - return true; - } - this[key] = newDependencies; - this[pendingKey] = false; - this[itemIDKey] = this.item?.id; - return false; - } - - _resetRenderedFlags() { - // Clear cached flags to allow re-rendering - delete this._syncRenderDependencies; - delete this._syncRenderItemID; - delete this._asyncRenderDependencies; - delete this._asyncRenderItemID; - } - - async _forceRenderAll() { - this._resetRenderedFlags(); - if (this.hidden || this.skipRender) { - this._syncRenderPending = true; - this._asyncRenderPending = true; - return; - } - if (this.render) this.render(); - if (this.asyncRender) await this.asyncRender(); - } -} +"use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class ItemPaneCustomSection extends ItemPaneSectionElementBase { _hooks = {}; diff --git a/chrome/content/zotero/elements/itemPaneHeader.js b/chrome/content/zotero/elements/itemPaneHeader.js index 41853cf001..3da017133a 100644 --- a/chrome/content/zotero/elements/itemPaneHeader.js +++ b/chrome/content/zotero/elements/itemPaneHeader.js @@ -26,6 +26,11 @@ "use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + const PREF_HEADER_MODE = 'itemPaneHeader'; const PREF_BIB_ENTRY_STYLE = 'itemPaneHeader.bibEntry.style'; const PREF_BIB_ENTRY_LOCALE = 'itemPaneHeader.bibEntry.locale'; diff --git a/chrome/content/zotero/elements/itemPaneSectionElementBase.mjs b/chrome/content/zotero/elements/itemPaneSectionElementBase.mjs new file mode 100644 index 0000000000..661a62c5d7 --- /dev/null +++ b/chrome/content/zotero/elements/itemPaneSectionElementBase.mjs @@ -0,0 +1,178 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2024 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 ***** +*/ + +export class ItemPaneSectionElementBase extends XULElementBase { + get item() { + return this._item; + } + + set item(item) { + this._item = item; + if (this._handleItemChange) this._handleItemChange(); + } + + get editable() { + return this._editable; + } + + set editable(editable) { + this._editable = editable; + this.toggleAttribute('readonly', !editable); + } + + get tabID() { + return this._tabID; + } + + set tabID(tabID) { + this._tabID = tabID; + } + + get tabType() { + return this._tabType; + } + + set tabType(tabType) { + this._tabType = tabType; + this.setAttribute('tabType', tabType); + } + + get collectionTreeRow() { + return this._collectionTreeRow; + } + + set collectionTreeRow(collectionTreeRow) { + this._collectionTreeRow = collectionTreeRow; + } + + _syncRenderPending = false; + + _asyncRenderPending = false; + + /** Controlled by parent element */ + skipRender = false; + + get open() { + return this._section?.open || false; + } + + set open(val) { + if (this._section) { + this._section.open = val; + } + } + + get collapsible() { + return this._section.collapsible; + } + + set collapsible(val) { + this._section.collapsible = !!val; + } + + connectedCallback() { + super.connectedCallback(); + if (!this.render && !this.asyncRender) { + Zotero.warn("Pane section must have method render or asyncRender."); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this._section) { + this._section.removeEventListener("toggle", this._handleSectionToggle); + this._section = null; + } + this._resetRenderedFlags(); + } + + initCollapsibleSection() { + this._section = this.querySelector('collapsible-section'); + if (this._section) { + this._section.addEventListener("toggle", this._handleSectionToggle); + } + } + + _handleSectionToggle = async (event) => { + if (event.target !== this._section || !this._section.open) { + return; + } + await this._forceRenderAll(); + }; + + get _renderDependencies() { + return [this._tabID, this._item?.id]; + } + + /** + * @param {"sync" | "async"} [type] + * @returns {boolean} + */ + _isAlreadyRendered(type = "sync") { + let key = `_${type}RenderDependencies`; + let pendingKey = `_${type}RenderPending`; + let itemIDKey = `_${type}RenderItemID`; + + let oldDependencies = this[key]; + let newDependencies = this._renderDependencies; + + let isPending = this[pendingKey]; + let isRendered = Zotero.Utilities.arrayEquals(oldDependencies, newDependencies); + if (this.skipRender) { + if (!isRendered) { + this[pendingKey] = true; + } + // Skip render + return true; + } + + if (!isPending && isRendered) { + return true; + } + this[key] = newDependencies; + this[pendingKey] = false; + this[itemIDKey] = this.item?.id; + return false; + } + + _resetRenderedFlags() { + // Clear cached flags to allow re-rendering + delete this._syncRenderDependencies; + delete this._syncRenderItemID; + delete this._asyncRenderDependencies; + delete this._asyncRenderItemID; + } + + async _forceRenderAll() { + this._resetRenderedFlags(); + if (this.hidden || this.skipRender) { + this._syncRenderPending = true; + this._asyncRenderPending = true; + return; + } + if (this.render) this.render(); + if (this.asyncRender) await this.asyncRender(); + } +} diff --git a/chrome/content/zotero/elements/librariesCollectionsBox.js b/chrome/content/zotero/elements/librariesCollectionsBox.js index 5e0d3b35d2..5eb173b07e 100644 --- a/chrome/content/zotero/elements/librariesCollectionsBox.js +++ b/chrome/content/zotero/elements/librariesCollectionsBox.js @@ -28,6 +28,11 @@ import { getCSSIcon } from 'components/icons'; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class LibrariesCollectionsBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/noteBox.js b/chrome/content/zotero/elements/noteBox.js index ac0c7ae539..36f2ac9ca3 100644 --- a/chrome/content/zotero/elements/noteBox.js +++ b/chrome/content/zotero/elements/noteBox.js @@ -25,6 +25,10 @@ { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); let { countWords } = ChromeUtils.importESModule("resource://zotero/allfaz.mjs").default; class NoteBox extends ItemPaneSectionElementBase { diff --git a/chrome/content/zotero/elements/notesBox.js b/chrome/content/zotero/elements/notesBox.js index a3ab5bee4f..fbfc52ee6a 100644 --- a/chrome/content/zotero/elements/notesBox.js +++ b/chrome/content/zotero/elements/notesBox.js @@ -28,6 +28,11 @@ import { getCSSItemTypeIcon } from 'components/icons'; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class NotesBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/relatedBox.js b/chrome/content/zotero/elements/relatedBox.js index 499ce58c71..6f92f14a64 100644 --- a/chrome/content/zotero/elements/relatedBox.js +++ b/chrome/content/zotero/elements/relatedBox.js @@ -28,6 +28,11 @@ import { getCSSItemTypeIcon } from 'components/icons'; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class RelatedBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/chrome/content/zotero/elements/tagsBox.js b/chrome/content/zotero/elements/tagsBox.js index 37ad453b51..9e36ee5e02 100644 --- a/chrome/content/zotero/elements/tagsBox.js +++ b/chrome/content/zotero/elements/tagsBox.js @@ -26,6 +26,11 @@ "use strict"; { + const { ItemPaneSectionElementBase } = ChromeUtils.importESModule( + "chrome://zotero/content/elements/itemPaneSectionElementBase.mjs", + { global: "current" } + ); + class TagsBox extends ItemPaneSectionElementBase { content = MozXULElement.parseXULToFragment(` diff --git a/eslint.config.mjs b/eslint.config.mjs index 7a24dbb210..956c4bce34 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,8 +47,6 @@ export default [ XULElementBase: "readonly", XULElementMixin: "readonly", XULTextElement: "readonly", - ItemPaneSectionElementBase: "readonly", - ItemPaneContainerBase: "readonly", Cu: "readonly", ChromeWorker: "readonly", Localization: "readonly",