move logic into ItemTreeMenuBar class

- menupopup handling moved from Zotero.Utilities.Internal
into ItemTreeMenuBar as static functions
- menu to move columns renamed for "Move Column Back" and
moved into the submenu of "Columns"
- improved the logic of hiding menubar to not remove it
if it is clicked while acgive
- a few additional minor fixes
This commit is contained in:
Bogdan Abaev 2024-08-08 16:46:59 -07:00
parent 9b6a401afe
commit 0cd2c7a225
8 changed files with 161 additions and 153 deletions

View file

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

View file

@ -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(`
<keyset id="sortSubmenuKeys">
<key id="key_sortCol0"/>
<key id="key_sortCol1"/>
<key id="key_sortCol2"/>
<key id="key_sortCol3"/>
<key id="key_sortCol4"/>
<key id="key_sortCol5"/>
<key id="key_sortCol6"/>
<key id="key_sortCol7"/>
<key id="key_sortCol8"/>
<key id="key_sortCol9"/>
</keyset>
<menubar id="main-menubar">
<menu id="view-menu"
label="&viewMenu.label;"
accesskey="&viewMenu.accesskey;">
<menupopup id="menu_viewPopup">
<menu id="column-picker-submenu"
class="menu-type-library"
label="&columns.label;">
<menupopup/>
</menu>
<menu id="sort-submenu"
class="menu-type-library"
label="&sortBy.label;">
<menupopup/>
</menu>
<menu id="column-move-submenu"
class="menu-type-library"
label="&moveColumn.label;">
<menupopup/>
</menu>
</menupopup>
</menu>
</menubar>
`, ['chrome://zotero/locale/standalone.dtd']);
this.content = MozXULElement.parseXULToFragment(`
<keyset id="sortSubmenuKeys">
<key id="key_sortCol0"/>
<key id="key_sortCol1"/>
<key id="key_sortCol2"/>
<key id="key_sortCol3"/>
<key id="key_sortCol4"/>
<key id="key_sortCol5"/>
<key id="key_sortCol6"/>
<key id="key_sortCol7"/>
<key id="key_sortCol8"/>
<key id="key_sortCol9"/>
</keyset>
<menubar id="main-menubar">
<menu id="view-menu"
label="&viewMenu.label;"
accesskey="&viewMenu.accesskey;">
<menupopup id="menu_viewPopup">
<menu id="column-picker-submenu"
class="menu-type-library"
label="&columns.label;">
<menupopup/>
</menu>
<menu id="sort-submenu"
class="menu-type-library"
label="&sortBy.label;">
<menupopup/>
</menu>
</menupopup>
</menu>
</menubar>
`, ['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);

View file

@ -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(<ItemTree ref={(c) => {
@ -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();

View file

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

View file

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

View file

@ -645,19 +645,13 @@
<menu id="column-picker-submenu"
class="menu-type-library"
label="&columns.label;"
onpopupshowing="Zotero.Utilities.Internal.handleItemTreeMenuShowing(event, 'column-picker-submenu', ZoteroPane.itemsView)">
onpopupshowing="ItemTreeMenuBar.handleItemTreeMenuShowing(event, this, ZoteroPane.itemsView)">
<menupopup/>
</menu>
<menu id="sort-submenu"
class="menu-type-library"
label="&sortBy.label;"
onpopupshowing="Zotero.Utilities.Internal.handleItemTreeMenuShowing(event, 'sort-submenu', ZoteroPane.itemsView)">
<menupopup/>
</menu>
<menu id="column-move-submenu"
class="menu-type-library"
label="&moveColumn.label;"
onpopupshowing="Zotero.Utilities.Internal.handleItemTreeMenuShowing(event, 'column-move-submenu', ZoteroPane.itemsView)">
onpopupshowing="ItemTreeMenuBar.handleItemTreeMenuShowing(event, this, ZoteroPane.itemsView)">
<menupopup/>
</menu>
<menuseparator class="menu-type-library"/>

View file

@ -63,7 +63,6 @@
<!ENTITY noteFontSize.label "Note Font Size">
<!ENTITY columns.label "Columns">
<!ENTITY sortBy.label "Sort By">
<!ENTITY moveColumn.label "Move Column">
<!--TOOLS MENU-->
<!ENTITY toolsMenu.label "Tools">

View file

@ -60,6 +60,7 @@
<!ENTITY zotero.items.dateModified_column "Date Modified">
<!ENTITY zotero.items.moreColumns.label "More Columns">
<!ENTITY zotero.items.restoreColumnOrder.label "Restore Column Order">
<!ENTITY zotero.items.moveColumn.label "Move Column Back">
<!ENTITY zotero.items.menu.showInLibrary "Show in Library">
<!ENTITY zotero.items.menu.attach.note "Add Note">