diff --git a/chrome/content/zotero/customElements.js b/chrome/content/zotero/customElements.js index 349414c97e..8406f3cb59 100644 --- a/chrome/content/zotero/customElements.js +++ b/chrome/content/zotero/customElements.js @@ -29,6 +29,7 @@ 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/itemTreeMenuBar.js', this); { // https://searchfox.org/mozilla-central/rev/8e885f04a0a4ff6d64ea59741c10d9b8e45d9ff8/toolkit/content/customElements.js#826-832 @@ -70,8 +71,7 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemPaneSe ['note-row', 'chrome://zotero/content/elements/noteRow.js'], ['notes-context', 'chrome://zotero/content/elements/notesContext.js'], ['libraries-collections-box', 'chrome://zotero/content/elements/librariesCollectionsBox.js'], - ['autocomplete-textarea', 'chrome://zotero/content/elements/autocompleteTextArea.js'], - ['item-tree-menu-bar', 'chrome://zotero/content/elements/itemTreeMenuBar.js'], + ['autocomplete-textarea', 'chrome://zotero/content/elements/autocompleteTextArea.js'] ]) { customElements.setElementCreationCallback(tag, () => { Services.scriptloader.loadSubScript(script, window); diff --git a/chrome/content/zotero/elements/itemTreeMenuBar.js b/chrome/content/zotero/elements/itemTreeMenuBar.js index ddd95ccec0..a1ec6a96d8 100644 --- a/chrome/content/zotero/elements/itemTreeMenuBar.js +++ b/chrome/content/zotero/elements/itemTreeMenuBar.js @@ -23,85 +23,128 @@ ***** END LICENSE BLOCK ***** */ -"use strict"; -{ - class ItemTreeMenuBar extends XULElement { - // Menu bar containing View options for manipulating the itemTree table - // (View > Columns, Sort By, Move Column). - // Should be added to non-main windows or dialogs containing an itemTree - // to expose the functionality of table manipulation to keyboard users. - // On Windows or Linux, this menubar appears on Alt keypress. - constructor() { - super(); +class ItemTreeMenuBar extends XULElement { + // Menu bar containing View options for manipulating the itemTree table + // (View > Columns, Sort By, Move Column). + // Added to windows without a menubar by ItemTree.init() + // to expose the functionality of table manipulation to keyboard users. + // On Windows or Linux, this menubar appears on Alt keypress. + constructor() { + super(); + this._inactiveTimeout = null; - this.content = MozXULElement.parseXULToFragment(` - - - - - - - - - - - - - - - - - - - - - - - - - - - - `, ['chrome://zotero/locale/standalone.dtd']); + this.content = MozXULElement.parseXULToFragment(` + + + + + + + + + + + + + + + + + + + + + + + + + `, ['chrome://zotero/locale/standalone.dtd']); + } + + + connectedCallback() { + this.append(document.importNode(this.content, true)); + this.hidden = true; + } + + // Show View > Columns, Sort By menus for windows that have an itemTree + static handleItemTreeMenuShowing(event, menu, itemsView) { + if (event.target !== menu.menupopup) { + return; } - - - connectedCallback() { - this.append(document.importNode(this.content, true)); - this.hidden = true; + menu.menupopup.replaceChildren(); + if (menu.id == "column-picker-submenu") { + // View > Columns + itemsView.buildColumnPickerMenu(menu.menupopup); } - - init(itemTree) { - Zotero.Utilities.Internal.setItemTreeSortKeys(window, itemTree); - for (let menu of [...this.querySelectorAll(".menu-type-library")]) { - menu.addEventListener("popupshowing", (event) => { - Zotero.Utilities.Internal.handleItemTreeMenuShowing(event, menu.id, itemTree); - }); - } - if (!Zotero.isMac) { - // On windows and linux, display and focus menubar on Alt keypress - document.addEventListener("keydown", (event) => { - if (event.key == "Alt") { - this.hidden = !this.hidden; - document.getElementById("main-menubar").focus(); - } - }, true); - // Hide menubar when it is no longer active - document.addEventListener("DOMMenuBarInactive", (_) => { - this.hidden = true; - }); + else if (menu.id == "sort-submenu") { + // View > Sort By + itemsView.buildSortMenu(menu.menupopup); + for (let i = 0; i < 10; i++) { + if (!menu.menupopup.children[i]) { + break; + } + menu.menupopup.children[i].setAttribute('key', 'key_sortCol' + i); } } } - - customElements.define("item-tree-menu-bar", ItemTreeMenuBar); + + // Set the access keys for menuitems to sort the itemTree + static setItemTreeSortKeys(itemsView) { + let sortSubmenuKeys = document.getElementById('sortSubmenuKeys'); + for (let i = 0; i < 10; i++) { + let key = sortSubmenuKeys.children[i]; + key.setAttribute('modifiers', Zotero.isMac ? 'accel alt control' : 'alt'); + key.setAttribute('key', (i + 1) % 10); + key.addEventListener('command', () => { + if (!window.Zotero_Tabs || window.Zotero_Tabs.selectedType == 'library') { + itemsView.toggleSort(i, true); + } + }); + } + } + + init(itemTree) { + this.constructor.setItemTreeSortKeys(itemTree); + for (let menu of [...this.querySelectorAll(".menu-type-library")]) { + menu.addEventListener("popupshowing", (event) => { + this.constructor.handleItemTreeMenuShowing(event, menu, itemTree); + }); + } + if (!Zotero.isMac) { + // On windows and linux, display and focus menubar on Alt keypress + document.addEventListener("keydown", (event) => { + if (event.key == "Alt") { + this.hidden = !this.hidden; + document.getElementById("main-menubar").focus(); + } + }, true); + // Hide menubar on click or tab away. If a selected menu is clicked, it will + // fire DOMMenuBarInactive event first followed by DOMMenuBarActive. + // Listen to both events and hide inactive menu after delay if it is not cancelled. + // https://searchfox.org/mozilla-central/source/browser/base/content/browser-customization.js#165 + document.addEventListener("DOMMenuBarInactive", (_) => { + this._inactiveTimeout = setTimeout(() => { + this._inactiveTimeout = null; + this.hidden = true; + }); + }); + document.addEventListener("DOMMenuBarActive", (_) => { + if (this._inactiveTimeout) { + clearTimeout(this._inactiveTimeout); + this._inactiveTimeout = null; + } + this.hidden = false; + }); + } + } } + +customElements.define("item-tree-menu-bar", ItemTreeMenuBar); diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index bc83b0fc66..b45075ec67 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -54,8 +54,7 @@ var ItemTree = class ItemTree extends LibraryTree { // Add a menubar with View options to manipulate the table (only if a menubar doesn't already exist in .xhtml) if (!document.querySelector("menubar")) { itemTreeMenuBar = document.createXULElement("item-tree-menu-bar"); - let win = document.querySelector("window"); - win.insertBefore(itemTreeMenuBar, win.firstChild); + document.documentElement.prepend(itemTreeMenuBar); } await new Promise((resolve) => { ReactDOM.createRoot(domEl).render( { @@ -2670,6 +2669,43 @@ var ItemTree = class ItemTree extends LibraryTree { menuitem.setAttribute('anonid', prefix + 'restore-order'); menuitem.addEventListener('command', () => this.tree._columns.restoreDefaultOrder()); menupopup.appendChild(menuitem); + + + sep = document.createXULElement('menuseparator'); + menupopup.appendChild(sep); + + // + // Move Column Back + // + let moveColumnMenu = document.createXULElement('menu'); + moveColumnMenu.setAttribute('label', Zotero.getString('zotero.items.moveColumn.label')); + moveColumnMenu.setAttribute('anonid', prefix + 'move-column'); + let moveColumnPopup = document.createXULElement('menupopup'); + moveColumnPopup.setAttribute('anonid', prefix + 'move-column-popup'); + moveColumnMenu.appendChild(moveColumnPopup); + menupopup.appendChild(moveColumnMenu); + + let firstColumn = true; + // Only list visible columns + for (let i = 0; i < columns.length; i++) { + let column = columns[i]; + if (column.hidden) continue; + // Skip first column (since there is nowhere to move it) + if (firstColumn) { + firstColumn = false; + continue; + } + let label = formatColumnName(column); + menuitem = document.createXULElement('menuitem'); + menuitem.setAttribute('label', label); + menuitem.setAttribute('colindex', i); + // Swap the column with its previous visible neighbor + menuitem.addEventListener('command', () => { + let previousIndex = columns.findLastIndex((col, index) => index < i && !col.hidden); + this.tree._columns.setOrder(i, previousIndex); + }); + moveColumnPopup.appendChild(menuitem); + } } buildSortMenu(menupopup) { @@ -2687,25 +2723,6 @@ var ItemTree = class ItemTree extends LibraryTree { }); } - buildColumnMoverMenu(menupopup) { - let columns = this._getColumns(); - let menuitem; - for (let i = 0; i < columns.length; i++) { - let column = columns[i]; - if (column.hidden) continue; - let label = formatColumnName(column); - menuitem = document.createXULElement('menuitem'); - menuitem.setAttribute('label', label); - menuitem.setAttribute('colindex', i); - // swap the column with its next nearest visible column - menuitem.addEventListener('command', () => { - let nextIndex = columns.findIndex((col, index) => index > i && !col.hidden); - this.tree._columns.setOrder(i, nextIndex + 1); - }); - menupopup.appendChild(menuitem); - } - } - toggleSort(sortIndex, countVisible = false) { if (countVisible) { let cols = this._getColumns(); diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 34719b359c..fe79fd4e4f 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -1701,52 +1701,6 @@ Zotero.Utilities.Internal = { return menu; }, - // Show View > Columns, Sort By and Move Column menus for windows that have an itemTree - handleItemTreeMenuShowing: function (event, menuID, itemsView) { - let document = event.target.ownerDocument; - let menuPopup = document.getElementById(menuID).menupopup; - if (event.target !== menuPopup) { - return; - } - menuPopup.replaceChildren(); - if (menuID == "column-picker-submenu") { - // View > Columns - itemsView?.buildColumnPickerMenu(menuPopup); - } - else if (menuID == "sort-submenu") { - // View > Sort By - itemsView?.buildSortMenu(menuPopup); - for (let i = 0; i < 10; i++) { - if (!menuPopup.children[i]) { - break; - } - menuPopup.children[i].setAttribute('key', 'key_sortCol' + i); - } - } - else if (menuID == "column-move-submenu") { - // View > Move Column - itemsView?.buildColumnMoverMenu(menuPopup); - } - }, - - // Set the access keys for menuitems to sort the itemTree - setItemTreeSortKeys(window, itemsView, condition) { - let sortSubmenuKeys = window.document.getElementById('sortSubmenuKeys'); - if (!condition) { - condition = () => true; - } - for (let i = 0; i < 10; i++) { - let key = sortSubmenuKeys.children[i]; - key.setAttribute('modifiers', Zotero.isMac ? 'accel alt control' : 'alt'); - key.setAttribute('key', (i + 1) % 10); - key.addEventListener('command', () => { - if (condition()) { - itemsView.toggleSort(i, true); - } - }); - } - }, - openPreferences: function (paneID, options = {}) { if (typeof options == 'string') { throw new Error("openPreferences() now takes an 'options' object -- update your code"); diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index e445d4a99e..e74b208472 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1655,7 +1655,7 @@ var ZoteroPane = new function() ZoteroPane.itemsView.onRefresh.addListener(() => ZoteroPane.setTagScope()); ZoteroPane.itemsView.waitForLoad().then(() => Zotero.uiIsReady()); - Zotero.Utilities.Internal.setItemTreeSortKeys(window, ZoteroPane.itemsView, () => Zotero_Tabs.selectedType === 'library'); + ItemTreeMenuBar.setItemTreeSortKeys(ZoteroPane.itemsView); } catch (e) { Zotero.logError(e); diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml index dc2781819c..87acfd0696 100644 --- a/chrome/content/zotero/zoteroPane.xhtml +++ b/chrome/content/zotero/zoteroPane.xhtml @@ -645,19 +645,13 @@ + onpopupshowing="ItemTreeMenuBar.handleItemTreeMenuShowing(event, this, ZoteroPane.itemsView)"> - - - + onpopupshowing="ItemTreeMenuBar.handleItemTreeMenuShowing(event, this, ZoteroPane.itemsView)"> diff --git a/chrome/locale/en-US/zotero/standalone.dtd b/chrome/locale/en-US/zotero/standalone.dtd index 6bd058da3b..7569ea8df0 100644 --- a/chrome/locale/en-US/zotero/standalone.dtd +++ b/chrome/locale/en-US/zotero/standalone.dtd @@ -63,7 +63,6 @@ - diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index 7c050cbf0e..a248932bad 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -60,6 +60,7 @@ +