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 @@
-
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 @@
+