toolbar and keyboard navigation updates

1. Toolbarbuttons changes:
	1. “New Library” moved from toolbar to File menu
	2. “New note” on click makes a standalone note
	3. “New attachment” on click adds stored copy of a file
	4. Menuitems from old note or attachment menus moved to the File menu
	5. “Advanced search” removed. Sync and Locate buttons moved to the top-right corner (Locate - temporarily)
	6. Added “Opened tabs” toolbarbutton
	7. Updated tooltips
2. Added a toolbarbutton that reveals collection search field on click. Search field is hidden on blur if empty. Keyboard accessibility via tab/shift-tab.
3. Tab based navigation restructuring. For each component, create a mapping of target ids with desired focus destination for each relevant keyboard event and use that to determine where to move focus next. Fixed bugs with focus wraparound not working when a note or attachment is opened in contextMenu.
4. Make quick-search dropmarker a sibling of the textfield. QuickSearchTextbox does not extend search-textfield anymore. It serves as a container to house dropmarker and textfield as siblings. This is needed to remove focus ring from quick search textbook when focus moves onto the dropmarker.
5. Separate toolbars for item and collection trees. Removed unused code manually setting the width of toolbar section
6. Focusable tabs + keyboard navigation:
	1. Shift-tab from opened tabs menu moves focus to the currently opened tab
	2. When a tab has focus, use left/right arrows to select tabs, or CMD/Ctrl + arrows to move focus between tabs
	3. Enter or Space on a focused tab will select it if needed and move focus to the contentPane of the reader of this tab
	4. Shift-tab from tabs wraps focus around to itemTree or itemPane
7. “New collection” creates collection contextually within currently selected library or collection.
8. Items pane minimum width increase to avoid quicksearch from being squashed
9. Do not move focus to title field of itemBox after itemTypeMenu is closed with ESC.
10. Display all itemTypes without "Show more" submenu in "New Item" meunu. Removed "Store Copy of File" and "Link to File" from "New Item" menu.
This commit is contained in:
abaevbog 2023-10-31 04:04:13 -04:00 committed by Dan Stillman
parent 3f91729141
commit 200e1d7564
20 changed files with 834 additions and 533 deletions

View file

@ -7,7 +7,7 @@
-moz-appearance: none; -moz-appearance: none;
} }
#zotero-pane #zotero-toolbar { #zotero-pane .zotero-toolbar {
-moz-appearance: none !important; -moz-appearance: none !important;
margin-top: -3px; margin-top: -3px;
border-bottom: 1px solid var(--theme-border-color); border-bottom: 1px solid var(--theme-border-color);
@ -43,7 +43,7 @@
padding-right: .7em; padding-right: .7em;
} }
#zotero-toolbar { .zotero-toolbar {
-moz-appearance: toolbox !important; -moz-appearance: toolbox !important;
padding-left: 2px; padding-left: 2px;
} }

View file

@ -272,6 +272,46 @@ const TabBar = forwardRef(function (props, ref) {
event.preventDefault(); event.preventDefault();
} }
function handleKeyDown(event) {
event = event.nativeEvent;
let key = event.key;
if (key == "Tab") {
if (event.shiftKey) {
// On shift-tab, wrap focus back to itemTree/itemPane
props.wrapFocusAround();
}
else {
// On tab go back to opened tabs menu
document.getElementById('zotero-tb-opened-tabs').focus();
}
event.preventDefault();
event.stopPropagation();
}
// Move focus between tabs with arrows
if (["ArrowLeft", "ArrowRight"].includes(key)) {
let direction = key == "ArrowLeft" ? "left" : "right";
const cmdOrCtrlOnly = Zotero.isMac
? (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey)
: (event.ctrlKey && !event.shiftKey && !event.altKey);
// Ctrl/CMD and an arrow shifts focus
if (cmdOrCtrlOnly) {
props.moveFocus(direction);
}
// Just an arrow selects the tab
else if (direction == "left") {
props.selectPrev({ keepTabFocused: true });
}
else {
props.selectNext({ keepTabFocused: true });
};
}
// Select focused tab on space or enter and focus on content pane
if (key == " " || key == "Enter") {
let tabID = event.target.getAttribute('data-id');
props.onTabSelect(tabID, false, { keepTabFocused: false });
}
}
function renderTab({ id, title, selected, iconBackgroundImage }, index) { function renderTab({ id, title, selected, iconBackgroundImage }, index) {
return ( return (
<div <div
@ -285,6 +325,8 @@ const TabBar = forwardRef(function (props, ref) {
onAuxClick={(event) => handleTabClick(event, id)} onAuxClick={(event) => handleTabClick(event, id)}
onDragStart={(event) => handleDragStart(event, id, index)} onDragStart={(event) => handleDragStart(event, id, index)}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onKeyDown={handleKeyDown}
tabIndex="-1"
> >
<div className="tab-name" dir="auto">{iconBackgroundImage && <div className="tab-name" dir="auto">{iconBackgroundImage &&
<span className="icon-bg" style={{ backgroundImage: iconBackgroundImage }}/>}{title}</div> <span className="icon-bg" style={{ backgroundImage: iconBackgroundImage }}/>}{title}</div>

View file

@ -222,7 +222,10 @@ var ZoteroContextPane = new function () {
if (Zotero_Tabs.selectedID == reader.tabID if (Zotero_Tabs.selectedID == reader.tabID
&& (!document.activeElement && (!document.activeElement
|| !document.activeElement.closest('.context-node iframe[id="editor-view"]'))) { || !document.activeElement.closest('.context-node iframe[id="editor-view"]'))) {
reader.focus(); if (!Zotero_Tabs.focusOptions?.keepTabFocused) {
// Do not move focus to the reader during keyboard navigation
reader.focus();
}
} }
var attachment = await Zotero.Items.getAsync(reader.itemID); var attachment = await Zotero.Items.getAsync(reader.itemID);

View file

@ -26,13 +26,17 @@
"use strict"; "use strict";
{ {
// The search-textbox CE is defined lazily. Create one now to get class QuickSearchTextbox extends XULElement {
// search-textbox defined, allowing us to inherit from it. constructor() {
if (!customElements.get("search-textbox")) { super();
delete document.createXULElement("search-textbox");
} this.searchTextbox = null;
this.content = MozXULElement.parseXULToFragment(`
class QuickSearchTextbox extends customElements.get("search-textbox") { <hbox id="search-wrapper" flex="1" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
</hbox>
`, ['chrome://zotero/locale/zotero.dtd']);
}
_searchModes = { _searchModes = {
titleCreatorYear: Zotero.getString('quickSearch.mode.titleCreatorYear'), titleCreatorYear: Zotero.getString('quickSearch.mode.titleCreatorYear'),
fields: Zotero.getString('quickSearch.mode.fieldsAndTags'), fields: Zotero.getString('quickSearch.mode.fieldsAndTags'),
@ -42,16 +46,18 @@
_searchModePopup = null; _searchModePopup = null;
connectedCallback() { connectedCallback() {
super.connectedCallback(); let content = document.importNode(this.content, true);
this.append(content);
if (this.delayConnectedCallback()) { // Top level wrapper that will have dropmarker and search-textbox as children.
return; // That way, we can move focus-ring between these two siblings regardless of
} // their shadom DOMs.
let wrapper = this._id('search-wrapper');
// Need to create an inner shadow DOM so that global.css styles, // Need to create an inner shadow DOM so that global.css styles,
// which we need for the menupopup, don't break the search textbox // which we need for the menupopup, don't break the search textbox
let dropmarkerHost = document.createXULElement('hbox'); let dropmarkerHost = document.createXULElement('hbox');
let dropmarkerShadow = dropmarkerHost.attachShadow({ mode: 'open' }); let dropmarkerShadow = dropmarkerHost.attachShadow({ mode: 'open' });
dropmarkerHost.id = 'zotero-tb-search-dropmarker';
let s1 = document.createElement("link"); let s1 = document.createElement("link");
s1.rel = "stylesheet"; s1.rel = "stylesheet";
@ -69,8 +75,15 @@
dropmarkerShadow.append(s1, s2, dropmarker); dropmarkerShadow.append(s1, s2, dropmarker);
this.inputField.before(dropmarkerHost); let searchBox = document.createXULElement("search-textbox");
searchBox.id = "zotero-tb-search-textbox";
searchBox.inputField.style = "padding-left:10px";
this.searchTextbox = searchBox;
wrapper.appendChild(dropmarkerHost);
wrapper.appendChild(searchBox);
// If Alt-Up/Down, show popup // If Alt-Up/Down, show popup
this.addEventListener('keypress', (event) => { this.addEventListener('keypress', (event) => {
if (event.altKey && (event.keyCode == event.DOM_VK_UP || event.keyCode == event.DOM_VK_DOWN)) { if (event.altKey && (event.keyCode == event.DOM_VK_UP || event.keyCode == event.DOM_VK_DOWN)) {
@ -126,7 +139,11 @@
this.searchModePopup.querySelector(`menuitem[value="${mode}"]`) this.searchModePopup.querySelector(`menuitem[value="${mode}"]`)
.setAttribute('checked', 'true'); .setAttribute('checked', 'true');
this.placeholder = this._searchModes[mode]; this.searchTextbox.placeholder = this._searchModes[mode];
}
_id(id) {
return this.querySelector(`[id=${id}]`);
} }
} }

View file

@ -499,7 +499,7 @@ var ItemTree = class ItemTree extends LibraryTree {
var activeWindow = zp && zp.itemsView == this; var activeWindow = zp && zp.itemsView == this;
var quickSearch = this._ownerDocument.getElementById('zotero-tb-search'); var quickSearch = this._ownerDocument.getElementById('zotero-tb-search');
var hasQuickSearch = quickSearch && quickSearch.value != ''; var hasQuickSearch = quickSearch && quickSearch.searchTextbox.value != '';
// 'collection-item' ids are in the form collectionID-itemID // 'collection-item' ids are in the form collectionID-itemID
if (type == 'collection-item') { if (type == 'collection-item') {
@ -1098,7 +1098,7 @@ var ItemTree = class ItemTree extends LibraryTree {
Zotero.debug("_rowMap not yet set; not selecting items"); Zotero.debug("_rowMap not yet set; not selecting items");
return 0; return 0;
} }
Zotero.debug('Item group not found and no row map in ItemTree.selectItem() -- discarding select', 2); Zotero.debug('Item group not found and no row map in ItemTree.selectItem() -- discarding select', 2);
return 0; return 0;
} }

View file

@ -56,6 +56,10 @@ var Zotero_Tabs = new function () {
get: () => this._tabs.length get: () => this._tabs.length
}); });
Object.defineProperty(this, 'focusOptions', {
get: () => this._focusOptions
});
this._tabBarRef = React.createRef(); this._tabBarRef = React.createRef();
this._tabs = [{ this._tabs = [{
id: 'zotero-pane', id: 'zotero-pane',
@ -65,6 +69,7 @@ var Zotero_Tabs = new function () {
this._selectedID = 'zotero-pane'; this._selectedID = 'zotero-pane';
this._prevSelectedID = null; this._prevSelectedID = null;
this._history = []; this._history = [];
this._focusOptions = {};
this._unloadInterval = setInterval(() => { this._unloadInterval = setInterval(() => {
this.unloadUnusedTabs(); this.unloadUnusedTabs();
@ -115,6 +120,10 @@ var Zotero_Tabs = new function () {
onTabMove={this.move.bind(this)} onTabMove={this.move.bind(this)}
onTabClose={this.close.bind(this)} onTabClose={this.close.bind(this)}
onContextMenu={this._openMenu.bind(this)} onContextMenu={this._openMenu.bind(this)}
moveFocus={this.moveFocus.bind(this)}
wrapFocusAround={this.focusWrapAround.bind(this)}
selectNext={this.selectNext.bind(this)}
selectPrev={this.selectPrev.bind(this)}
/>, />,
document.getElementById('tab-bar-container'), document.getElementById('tab-bar-container'),
() => { () => {
@ -372,14 +381,34 @@ var Zotero_Tabs = new function () {
this.select = function (id, reopening, options) { this.select = function (id, reopening, options) {
var { tab, tabIndex } = this._getTab(id); var { tab, tabIndex } = this._getTab(id);
if (!tab || tab.id === this._selectedID) { if (!tab || tab.id === this._selectedID) {
// Focus on reader when keepTabFocused is explicitly false
// E.g. when a tab is selected via Space or Enter
if (options?.keepTabFocused === false && tab?.id === this._selectedID) {
var reader = Zotero.Reader.getByTabID(this._selectedID);
if (reader) {
reader.focus();
}
}
return; return;
} }
let selectedTab;
if (this._selectedID) { if (this._selectedID) {
let { tab: selectedTab } = this._getTab(this._selectedID); selectedTab = this._getTab(this._selectedID).tab;
if (selectedTab) { if (selectedTab) {
selectedTab.timeUnselected = Zotero.Date.getUnixTimestamp(); selectedTab.timeUnselected = Zotero.Date.getUnixTimestamp();
} }
} }
// If the last focus data was recorded for a different item, discard it
if (this._focusOptions.itemID && this._focusOptions.itemID != tab?.data?.itemID) {
this._focusOptions = {};
}
// Save focus option for this item to tell reader and contextPane how to handle focus
if (options && selectedTab) {
this._focusOptions.keepTabFocused = !!options.keepTabFocused;
this._focusOptions.itemID = tab?.data?.itemID;
}
if (tab.type === 'reader-unloaded') { if (tab.type === 'reader-unloaded') {
this.close(tab.id); this.close(tab.id);
Zotero.Reader.open(tab.data.itemID, options && options.location, { Zotero.Reader.open(tab.data.itemID, options && options.location, {
@ -393,7 +422,6 @@ var Zotero_Tabs = new function () {
return; return;
} }
if (this._selectedID === 'zotero-pane') { if (this._selectedID === 'zotero-pane') {
var { tab: selectedTab } = this._getTab(this._selectedID);
selectedTab.lastFocusedElement = document.activeElement; selectedTab.lastFocusedElement = document.activeElement;
} }
this._prevSelectedID = reopening ? this._selectedID : null; this._prevSelectedID = reopening ? this._selectedID : null;
@ -408,14 +436,18 @@ var Zotero_Tabs = new function () {
} }
tab.lastFocusedElement = null; tab.lastFocusedElement = null;
} }
let tabNode = document.querySelector(`.tab[data-id="${tab.id}"]`);
if (this._focusOptions.keepTabFocused && document.activeElement.getAttribute('data-id') != tabNode.getAttribute('data-id')) {
// Keep focus on the currently selected tab during keyboard navigation
tabNode.focus();
}
// Allow React to create a tab node // Allow React to create a tab node
setTimeout(() => { setTimeout(() => {
document.querySelector(`.tab[data-id="${tab.id}"]`).scrollIntoView({ behavior: 'smooth' }); tabNode.scrollIntoView({ behavior: 'smooth' });
}); });
// Border is not included when scrolling element node into view, therefore we do it manually. // Border is not included when scrolling element node into view, therefore we do it manually.
// TODO: `scroll-padding` since Firefox 68 can probably be used instead // TODO: `scroll-padding` since Firefox 68 can probably be used instead
setTimeout(() => { setTimeout(() => {
let tabNode = document.querySelector(`.tab[data-id="${tab.id}"]`);
if (!tabNode) { if (!tabNode) {
return; return;
} }
@ -466,17 +498,17 @@ var Zotero_Tabs = new function () {
/** /**
* Select the previous tab (closer to the library tab) * Select the previous tab (closer to the library tab)
*/ */
this.selectPrev = function () { this.selectPrev = function (options) {
var { tabIndex } = this._getTab(this._selectedID); var { tabIndex } = this._getTab(this._selectedID);
this.select((this._tabs[tabIndex - 1] || this._tabs[this._tabs.length - 1]).id); this.select((this._tabs[tabIndex - 1] || this._tabs[this._tabs.length - 1]).id, false, options || {});
}; };
/** /**
* Select the next tab (farther to the library tab) * Select the next tab (farther to the library tab)
*/ */
this.selectNext = function () { this.selectNext = function (options) {
var { tabIndex } = this._getTab(this._selectedID); var { tabIndex } = this._getTab(this._selectedID);
this.select((this._tabs[tabIndex + 1] || this._tabs[0]).id); this.select((this._tabs[tabIndex + 1] || this._tabs[0]).id, false, options || {});
}; };
/** /**
@ -485,6 +517,62 @@ var Zotero_Tabs = new function () {
this.selectLast = function () { this.selectLast = function () {
this.select(this._tabs[this._tabs.length - 1].id); this.select(this._tabs[this._tabs.length - 1].id);
}; };
/**
* Moves focus to a tab in the specified direction.
* @param {String} direction. "first", "last", "left", "right", or "current"
* If document.activeElement is a tab, "left" or "right" direction moves focus from that tab.
* Otherwise, focus is moved in the given direction from the currently selected tab.
*/
this.moveFocus = function (direction) {
let focusedTabID = document.activeElement.getAttribute('data-id');
var { tabIndex } = this._getTab(this._selectedID);
let tabIndexToFocus = null;
if (direction === "last") {
tabIndexToFocus = this._tabs.length - 1;
}
else if (direction == "first") {
tabIndexToFocus = 0;
}
else if (direction == "current") {
tabIndexToFocus = tabIndex;
}
else {
let focusedTabIndex = this._tabs.findIndex(tab => tab.id === focusedTabID);
// If the currently focused element is not a tab, use tab that is selected
if (focusedTabIndex === -1) {
focusedTabIndex = tabIndex;
}
switch (direction) {
case "left":
tabIndexToFocus = focusedTabIndex > 0 ? focusedTabIndex - 1 : null;
break;
case "right":
tabIndexToFocus = focusedTabIndex < this._tabs.length - 1 ? focusedTabIndex + 1 : null;
break;
default:
throw new Error(`${direction} is an invalid direction.`);
}
}
if (tabIndexToFocus !== null) {
const nextTab = this._tabs[tabIndexToFocus];
// There may be duplicate tabs - in normal tab array and in pinned tabs
// So to get the right one, fetch all tabs with a given id and filter out one
// that's visible
let candidates = document.querySelectorAll(`[data-id="${nextTab.id}"]`);
for (let node of candidates) {
if (node.offsetParent) {
node.focus();
return;
}
}
}
};
/** /**
* Jump to the tab at a particular index. If the index points beyond the array, jump to the last * Jump to the tab at a particular index. If the index points beyond the array, jump to the last
@ -651,4 +739,66 @@ var Zotero_Tabs = new function () {
document.getElementById('tab-bar-container').hidden = true; document.getElementById('tab-bar-container').hidden = true;
document.getElementById('main-window').setAttribute('legacytoolbar', 'true'); document.getElementById('main-window').setAttribute('legacytoolbar', 'true');
}; };
// Used to move focus back to itemTree or contextPane from the tabs.
this.focusWrapAround = function () {
// If no item is selected, focus items list.
const pane = document.getElementById("zotero-item-pane-content");
if (pane.selectedIndex === "0") {
document.getElementById("item-tree-main-default").focus();
}
else {
let selected = ZoteroPane.getSelectedItems();
// If the selected collection row is duplicates, just focus on the
// itemTree until the merge pane is keyboard accessible
// If multiple items selected, focus on itemTree as well.
let collectionRow = ZoteroPane.collectionsView.selectedTreeRow;
if (collectionRow.isDuplicates() || selected.length !== 1) {
document.getElementById("item-tree-main-default").focus();
return;
}
// Special treatment for notes and attachments in itemPane
selected = selected[0];
if (selected.isNote()) {
document.getElementById("zotero-note-editor").focus();
return;
}
if (selected.isAttachment()) {
document.getElementById("attachment-note-editor").focus();
return;
}
// Focusing on the last field in whichever tab is opened for
// regular items
const tabBox = document.getElementById("zotero-view-tabbox");
if (tabBox.selectedIndex === 0) {
const itembox = document.getElementById("zotero-editpane-item-box");
itembox.focusLastField();
}
else if (tabBox.selectedIndex === 1) {
const notes = document.getElementById("zotero-editpane-notes");
const nodes = notes.querySelectorAll("button");
const node = nodes[nodes.length - 1];
node.focus();
// TODO: the notes are currently inaccessible to the keyboard
}
else if (tabBox.selectedIndex === 2) {
const tagContainer = document.getElementById("tags-box-container");
const tags = tagContainer.querySelectorAll("#tags-box-add-button,.zotero-clicky");
const last = tags[tags.length - 1];
if (last.id === "tags-box-add-button") {
last.focus();
}
else {
last.click();
}
}
else if (tabBox.selectedIndex === 3) {
const related = tabBox.querySelector("relatedbox");
related.receiveKeyboardFocus("end");
}
else {
throw new Error("The selectedIndex should always be between 1 and 4");
}
}
};
}; };

View file

@ -1455,11 +1455,10 @@ class Reader {
this._loadSidebarState(); this._loadSidebarState();
this.triggerAnnotationsImportCheck(itemID); this.triggerAnnotationsImportCheck(itemID);
let reader; let reader;
let win = Zotero.getMainWindow();
// If duplicating is not allowed, and no reader instance is loaded for itemID, // If duplicating is not allowed, and no reader instance is loaded for itemID,
// try to find an unloaded tab and select it. Zotero.Reader.open will then be called again // try to find an unloaded tab and select it. Zotero.Reader.open will then be called again
if (!allowDuplicate && !this._readers.find(r => r.itemID === itemID)) { if (!allowDuplicate && !this._readers.find(r => r.itemID === itemID)) {
let win = Zotero.getMainWindow();
if (win) { if (win) {
let existingTabID = win.Zotero_Tabs.getTabIDByItemID(itemID); let existingTabID = win.Zotero_Tabs.getTabIDByItemID(itemID);
if (existingTabID) { if (existingTabID) {
@ -1532,7 +1531,9 @@ class Reader {
this._readers.push(reader); this._readers.push(reader);
} }
if (!openInBackground) { if (!openInBackground
&& !win.Zotero_Tabs.focusOptions.keepTabFocused) {
// Do not change focus when tabs are traversed/selected using a keyboard
reader.focus(); reader.focus();
} }
return reader; return reader;

View file

@ -144,265 +144,240 @@ var ZoteroPane = new function()
// continue loading pane // continue loading pane
_loadPane(); _loadPane();
setUpToolbar(); setUpKeyboardNavigation();
}; };
function setUpToolbar() { function setUpKeyboardNavigation() {
// if the hidden property is ever set on a grandparent or more distant let collectionTreeToolbar = this.document.getElementById("zotero-toolbar-collection-tree");
// ancestor this will need to be updated let itemTreeToolbar = this.document.getElementById("zotero-toolbar-item-tree");
const isVisible = b => !b.hidden && !b.parentElement.hidden; let tabsToolbar = this.document.getElementById("zotero-tabs-toolbar");
const isTbButton = node => node && node.tagName === "toolbarbutton"; let itemTree = this.document.getElementById("zotero-items-tree");
let collectionsTree = this.document.getElementById("zotero-collections-tree");
let tagSelector = this.document.getElementById("zotero-tag-selector");
let tagContainer = this.document.getElementById('zotero-tag-selector-container');
let collectionsPane = this.document.getElementById("zotero-collections-pane");
function nextVisible(id, field = "after") { // function to handle actual focusing based on a given event
let b = document.getElementById(id); // and a mapping of event targets + keys to the focus destinations
while (!isVisible(b)) { let moveFocus = function (actionsMap, event, verticalArrowIsTab = false) {
const mapData = focusMap.get(b.id); if (key === 'Tab' && modifierIsNotShift(event)) return;
b = document.getElementById(mapData[field]);
var key = event.key;
if (event.shiftKey) {
key = 'Shift' + key;
} }
return b; // ArrowUp or ArrowDown act the same way as as
} // tab/shift-tab unles it is on a menu, in which case
// it'll open the menu popup
/* constants */ let isMenu = event.target.getAttribute('type') === 'menu'
const toolbar = this.document.getElementById("zotero-toolbar"); || event.originalTarget?.getAttribute('type') === 'menu';
if (isMenu && ['ArrowUp', 'ArrowDown'].includes(key)) {
// assumes no toolbarbuttons are dynamically added, just hidden return;
// or revealed. If this changes, the observer will have to monitor
// changes to the childList for each hbox in the toolbar which might
// have dynamic children
const buttons = toolbar.getElementsByTagName("toolbarbutton");
const focusMap = new Map();
const zones = [
{
get start() {
return document.getElementById("zotero-tb-collection-add");
},
focusBefore() {
// If no item is selected, focus items list.
const pane = document.getElementById("zotero-item-pane-content");
if (pane.selectedIndex === "0") {
document.getElementById("item-tree-main-default").focus();
}
else {
const tabBox = document.getElementById("zotero-view-tabbox");
if (tabBox.selectedIndex === 0) {
const itembox = document.getElementById("zotero-editpane-item-box");
itembox.focusLastField();
}
else if (tabBox.selectedIndex === 1) {
const notes = document.getElementById("zotero-editpane-notes");
const nodes = notes.querySelectorAll("button");
const node = nodes[nodes.length - 1];
node.focus();
// TODO: the notes are currently inaccessible to the keyboard
}
else if (tabBox.selectedIndex === 2) {
const tagContainer = document.getElementById("tags-box-container");
const tags = tagContainer.querySelectorAll("#tags-box-add-button,.zotero-clicky");
const last = tags[tags.length - 1];
if (last.id === "tags-box-add-button") {
last.focus();
}
else {
last.click();
}
}
else if (tabBox.selectedIndex === 3) {
const related = tabBox.querySelector("relatedbox");
related.receiveKeyboardFocus("end");
}
else {
throw new Error("The selectedIndex should always be between 1 and 4");
}
}
},
focusAfter() {
document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus();
}
},
{
get start() {
return document.getElementById("zotero-tb-locate");
},
focusBefore() {
document.getElementById("zotero-tb-search").focus();
},
focusAfter() {
document.getElementById("collection-tree").focus();
}
} }
]; if (verticalArrowIsTab && key == 'ArrowUp') {
key = 'Tab';
/*
observe buttons and containers for changes in the "hidden"
attribute
*/
const observer = new MutationObserver((mutations, _) => {
for (const mutation of mutations) {
if (mutation.target.hidden
&& (document.activeElement === mutation.target
|| mutation.target.contains(document.activeElement))
) {
const next = nextVisible(document.activeElement.id, "before");
next.focus();
}
} }
else if (verticalArrowIsTab && key == 'ArrowDown') {
key = 'ShiftTab';
}
let focusFunction = actionsMap[event.target.id]?.[key];
// If the focusFunction is undefined, nothing was found
// for this combination of keys, so do nothing
if (focusFunction === undefined) {
return;
}
// Otherwise, fetch the target to focus on
let target = focusFunction();
// If returned target is false, focusing was not handled,
// so fallback to default focus target
if (target === false) {
return;
}
// If target is undefined, the actionsMap's function
// handled focus by itself (e.g. by calling .click)
if (target) {
// If desired target is hidden/disabled, create a fake event
// and dispatch it on the hidden target to rerun moveFocus
// and place focus on the next non-hidden node
if (target.disabled || target.hidden || target.parentNode.hidden) {
event.target = target;
let fakeEventCopy = new KeyboardEvent('keydown', {
key: event.key,
shiftKey: event.shiftKey,
bubbles: true
});
target.dispatchEvent(fakeEventCopy);
event.preventDefault();
event.stopPropagation();
return;
}
target.focus();
}
event.preventDefault();
event.stopPropagation();
};
tabsToolbar.addEventListener("keydown", (event) => {
// Mapping of target ids and possible key presses to desired focus outcomes
let actionsMap = {
'zotero-tb-opened-tabs': {
ArrowRight: () => null,
ArrowLeft: () => null,
Tab: () => document.getElementById('zotero-tb-sync-error'),
ShiftTab: () => {
Zotero_Tabs.moveFocus("current");
},
},
'zotero-tb-sync': {
ArrowRight: () => null,
ArrowLeft: () => null,
Tab: () => {
if (collectionsPane.getAttribute("collapsed")) {
return document.getElementById('zotero-tb-add');
}
return document.getElementById('zotero-tb-collection-add');
},
ShiftTab: () => document.getElementById('zotero-tb-sync-stop')
},
'zotero-tb-sync-stop': {
ArrowRight: () => null,
ArrowLeft: () => null,
Tab: () => document.getElementById('zotero-tb-sync'),
ShiftTab: () => document.getElementById('zotero-tb-sync-error')
},
'zotero-tb-sync-error': {
ArrowRight: () => null,
ArrowLeft: () => null,
Tab: () => document.getElementById('zotero-tb-sync-stop'),
ShiftTab: () => document.getElementById('zotero-tb-opened-tabs'),
Enter: () => document.getElementById("zotero-tb-sync-error")
.dispatchEvent(new MouseEvent("click", { target: event.target })),
' ': () => document.getElementById("zotero-tb-sync-error")
.dispatchEvent(new MouseEvent("click", { target: event.target }))
}
};
moveFocus(actionsMap, event);
}); });
/* collectionTreeToolbar.addEventListener("keydown", (event) => {
build a chain which connects all the <toolbarbutton>s, let actionsMap = {
except for zotero-tb-locate and zotero-tb-advanced-search 'zotero-tb-collection-add': {
which is where the chain breaks ArrowRight: () => null,
*/ ArrowLeft: () => null,
let prev = null; Tab: () => document.getElementById('zotero-tb-collection-search').click(),
let _zone = zones[0]; ShiftTab: () => document.getElementById('zotero-tb-sync')
for (const button of buttons) { },
focusMap.set(button.id, { 'zotero-collections-search': {
before: prev, ArrowRight: () => null,
after: null, ArrowLeft: () => null,
zone: _zone Tab: () => document.getElementById('zotero-tb-add'),
}); ShiftTab: () => document.getElementById('zotero-tb-collection-add')
},
/* observe each button for changes to "hidden" */ };
observer.observe(button, { moveFocus(actionsMap, event, true);
attributes: true,
attributeFilter: ["hidden"]
});
if (focusMap.has(prev)) {
focusMap.get(prev).after = button.id;
}
prev = button.id;
// break the chain at zotero-tb-advanced-search
if (button.id === "zotero-tb-advanced-search") {
_zone = zones[1];
prev = null;
}
}
observer.observe(document.getElementById("zotero-tb-sync-stop"), {
attributes: true,
attributeFilter: ["hidden"]
}); });
// lookupButton and syncErrorButton show popup panels, and so need special treatment itemTreeToolbar.addEventListener("keydown", (event) => {
const lookupButton = document.getElementById("zotero-tb-lookup"); let openMenu = (node) => {
const syncErrorButton = document.getElementById("zotero-tb-sync-error"); const popup = node.querySelector("menupopup");
if (popup !== null && !node.disabled) {
/* buttons at the start of zones need tabindex=0 */ popup.openPopup();
for (const zone of zones) {
zone.start.setAttribute("tabindex", "0");
}
toolbar.addEventListener("keydown", (event) => {
// manually move focus when Shift+Tabbing from the search-menu-button
if (event.key === 'Tab' && event.shiftKey
&& !modifierIsNotShift(event)
&& event.originalTarget
&& event.originalTarget.id == "zotero-tb-search-menu-button") {
event.preventDefault();
event.stopPropagation();
zones[0].start.focus();
return;
}
// manually move focus to search menu when Shift+Tabbing from the search-menu
if (event.key === 'Tab' && event.shiftKey
&& !modifierIsNotShift(event)
&& event.originalTarget?.tagName == "input") {
event.preventDefault();
event.stopPropagation();
document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus();
return;
}
// only handle events on a <toolbarbutton>
if (!isTbButton(event.target)) return;
const mapData = focusMap.get(event.target.id);
if (!Zotero.rtl && event.key === 'ArrowRight'
|| Zotero.rtl && event.key === 'ArrowLeft') {
event.preventDefault();
event.stopPropagation();
if (mapData.after) {
nextVisible(mapData.after, "after").focus();
} }
return; };
} let actionsMap = {
if (!Zotero.rtl && event.key === 'ArrowLeft' 'zotero-tb-add': {
|| Zotero.rtl && event.key === 'ArrowRight') { ArrowRight: () => document.getElementById("zotero-tb-lookup"),
event.preventDefault(); ArrowLeft: () => null,
event.stopPropagation(); Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
if (mapData.before) { ShiftTab: () => {
nextVisible(mapData.before, "before").focus(); if (collectionsPane.getAttribute("collapsed")) {
return document.getElementById('zotero-tb-sync');
}
document.getElementById('zotero-tb-collection-search').click();
return null;
},
' ': () => {
openMenu(document.getElementById('zotero-tb-add'));
},
Enter: () => {
openMenu(document.getElementById('zotero-tb-add'));
}
},
'zotero-tb-lookup': {
ArrowRight: () => document.getElementById("zotero-tb-attachment-add"),
ArrowLeft: () => document.getElementById("zotero-tb-add"),
Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
ShiftTab: () => document.getElementById('zotero-tb-collection-search').click(),
Enter: () => Zotero_Lookup.showPanel(event.target),
' ': () => Zotero_Lookup.showPanel(event.target)
},
'zotero-tb-attachment-add': {
ArrowRight: () => document.getElementById("zotero-tb-note-add"),
ArrowLeft: () => document.getElementById("zotero-tb-lookup"),
Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
ShiftTab: () => document.getElementById('zotero-tb-collection-search').click()
},
'zotero-tb-note-add': {
ArrowRight: () => null,
ArrowLeft: () => document.getElementById("zotero-tb-attachment-add"),
Tab: () => document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus(),
ShiftTab: () => document.getElementById('zotero-tb-collection-search').click()
},
'zotero-tb-search-textbox': {
ArrowRight: () => null,
ArrowLeft: () => null,
ShiftTab: () => {
document.getElementById("zotero-tb-search")._searchModePopup.flattenedTreeParentNode.focus();
},
Tab: () => document.getElementById("collection-tree")
},
'zotero-tb-search-dropmarker': {
ArrowRight: () => null,
ArrowLeft: () => null,
Tab: () => document.getElementById("zotero-tb-search-textbox"),
ShiftTab: () => document.getElementById('zotero-tb-add')
} }
return; };
} moveFocus(actionsMap, event, true);
});
/* manually trigger on space and enter */ collectionsTree.addEventListener("keydown", (event) => {
if (event.key === ' ' || event.key === 'Enter') { let actionsMap = {
if (event.target.disabled) return; 'collection-tree': {
ShiftTab: () => document.getElementById("zotero-tb-search-textbox"),
if (event.target === lookupButton) { Tab: () => {
event.preventDefault(); if (tagContainer.getAttribute('collapsed') == "true") {
event.stopPropagation(); return document.getElementById('item-tree-main-default');
Zotero_Lookup.showPanel(event.target); }
} // If tag selector is collapsed, go to itemTree, otherwise
else if (event.target === syncErrorButton) { // default to focusing on tag selector
event.preventDefault(); return false;
event.stopPropagation();
syncErrorButton.dispatchEvent(new MouseEvent("click", {
target: event.target
}));
}
else if (event.target.getAttribute('type') === 'menu') {
event.preventDefault();
event.stopPropagation();
const popup = event.target.querySelector("menupopup");
if (popup !== null && !event.target.disabled) {
popup.openPopup();
} }
} }
} };
moveFocus(actionsMap, event);
});
/* activate menus and popups on ArrowDown and ArrowUp, otherwise prepare for a focus change */ itemTree.addEventListener("keydown", (event) => {
else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { let actionsMap = {
if (event.target === lookupButton && !event.target.disabled) { 'item-tree-main-default': {
event.preventDefault(); ShiftTab: () => {
event.stopPropagation(); if (tagContainer.getAttribute('collapsed') == "true") {
Zotero_Lookup.showPanel(event.target); return document.getElementById('collection-tree');
} }
else if (event.target.getAttribute('type') === 'menu' && !event.target.disabled) { return document.getElementById('zotero-tag-selector').querySelector('button');
event.preventDefault();
event.stopPropagation();
const popup = event.target.querySelector("menupopup");
if (popup !== null && !event.target.disabled) {
popup.openPopup();
} }
} }
};
moveFocus(actionsMap, event);
});
/* prepare for a focus change */ tagSelector.addEventListener("keydown", (event) => {
else if (event.key === 'ArrowDown') { // Special treatment for tag selector button because it has no id
event.preventDefault(); if (event.target.tagName == "button" && event.key == "Tab" && !event.shiftKey) {
event.stopPropagation(); document.getElementById('item-tree-main-default').focus();
mapData.zone.focusBefore();
}
else if (event.key === 'ArrowUp') {
event.preventDefault();
event.stopPropagation();
mapData.zone.focusAfter();
}
}
else if (event.key === 'Tab' && !modifierIsNotShift(event)) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (event.shiftKey) {
mapData.zone.focusBefore();
}
else {
mapData.zone.focusAfter();
}
} }
}); });
} }
@ -582,31 +557,33 @@ var ZoteroPane = new function()
var addMenu = document.getElementById('zotero-tb-add').firstElementChild; var addMenu = document.getElementById('zotero-tb-add').firstElementChild;
// Remove all nodes so we can regenerate // Remove all nodes so we can regenerate
var options = [...addMenu.querySelectorAll('.zotero-tb-add')]; addMenu.replaceChildren();
while (options.length) {
options.shift().remove();
}
var moreMenu = document.getElementById('zotero-tb-add-more');
while (moreMenu.hasChildNodes()) {
moreMenu.removeChild(moreMenu.firstChild);
}
var separator = addMenu.firstChild; // Primary types from MRU
let primaryItemTypes = primaryTypes.map((type) => {
// Populate primary types from MRU return {
var itemTypes = [];
for (let type of primaryTypes) {
itemTypes.push({
id: type.id, id: type.id,
name: type.name, name: type.name,
localized: Zotero.ItemTypes.getLocalizedString(type.id) localized: Zotero.ItemTypes.getLocalizedString(type.id)
}); };
} });
// Item types not in the MRU list
let secondaryItemTypes = Zotero.ItemTypes.getSecondaryTypes().map((type) => {
return {
id: type.id,
name: type.name,
localized: Zotero.ItemTypes.getLocalizedString(type.id)
};
});
var collation = Zotero.getLocaleCollation(); var collation = Zotero.getLocaleCollation();
itemTypes.sort(function(a, b) { primaryItemTypes.sort(function (a, b) {
return collation.compareString(1, a.localized, b.localized); return collation.compareString(1, a.localized, b.localized);
}); });
secondaryItemTypes.sort(function (a, b) {
return collation.compareString(1, a.localized, b.localized);
});
let lastPrimaryType = primaryItemTypes[primaryItemTypes.length - 1];
let itemTypes = primaryItemTypes.concat(secondaryItemTypes);
for (let itemType of itemTypes) { for (let itemType of itemTypes) {
let menuitem = document.createXULElement("menuitem"); let menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("label", itemType.localized); menuitem.setAttribute("label", itemType.localized);
@ -615,32 +592,12 @@ var ZoteroPane = new function()
menuitem.addEventListener("command", function () { menuitem.addEventListener("command", function () {
ZoteroPane.newItem(type, {}, null, true); ZoteroPane.newItem(type, {}, null, true);
}); });
menuitem.className = "zotero-tb-add"; addMenu.appendChild(menuitem);
addMenu.insertBefore(menuitem, separator); // Add a separator between primary and secondary types
} if (lastPrimaryType.id == type) {
let separator = document.createXULElement("menuseparator");
// Populate submenu with each item type not in the MRU list addMenu.appendChild(separator);
itemTypes = []; }
for (let type of Zotero.ItemTypes.getSecondaryTypes()) {
itemTypes.push({
id: type.id,
name: type.name,
localized: Zotero.ItemTypes.getLocalizedString(type.id)
});
}
var collation = Zotero.getLocaleCollation();
itemTypes.sort(function(a, b) {
return collation.compareString(1, a.localized, b.localized);
});
for (var i = 0; i<itemTypes.length; i++) {
var menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("label", itemTypes[i].localized);
menuitem.setAttribute("tooltiptext", "");
let type = itemTypes[i].id;
menuitem.addEventListener("command", function () {
ZoteroPane.newItem(type, {}, null, true);
});
moreMenu.appendChild(menuitem);
} }
} }
@ -711,7 +668,7 @@ var ZoteroPane = new function()
// Focus the quicksearch on pane open // Focus the quicksearch on pane open
var searchBar = document.getElementById('zotero-tb-search'); var searchBar = document.getElementById('zotero-tb-search');
setTimeout(function () { setTimeout(function () {
searchBar.inputField.select(); searchBar.searchTextbox.select();
}, 1); }, 1);
// //
@ -1072,6 +1029,33 @@ var ZoteroPane = new function()
} }
ZoteroPane_Local.collectionsView.setHighlightedRows(); ZoteroPane_Local.collectionsView.setHighlightedRows();
} }
this.revealCollectionSearch = function () {
let collectionSearchField = document.getElementById("zotero-collections-search");
let collectionSearchButton = document.getElementById("zotero-tb-collection-search");
var hideIfEmpty = function () {
if (!collectionSearchField.value.length) {
collectionSearchField.classList.remove("visible");
collectionSearchButton.style.display = '';
}
};
if (!collectionSearchField.classList.contains("visible")) {
collectionSearchButton.style.display = 'none';
collectionSearchField.classList.add("visible");
collectionSearchField.addEventListener('blur', hideIfEmpty);
}
collectionSearchField.focus();
};
this.handleCollectionSearchKeypress = function (textbox, event) {
if (event.keyCode == event.DOM_VK_ESCAPE) {
textbox.value = '';
textbox.blur();
}
};
this.handleCollectionSearchInput = function (textbox, _) { };
function handleKeyUp(event) { function handleKeyUp(event) {
if ((Zotero.isWin && event.keyCode == 17) || if ((Zotero.isWin && event.keyCode == 17) ||
@ -1169,7 +1153,7 @@ var ZoteroPane = new function()
document.getElementById(ZoteroPane.collectionsView.id).focus(); document.getElementById(ZoteroPane.collectionsView.id).focus();
break; break;
case 'quicksearch': case 'quicksearch':
document.getElementById('zotero-tb-search').select(); document.getElementById('zotero-tb-search-textbox').select();
break; break;
case 'newItem': case 'newItem':
(async function () { (async function () {
@ -1182,7 +1166,7 @@ var ZoteroPane = new function()
var menu = itemBox.itemTypeMenu; var menu = itemBox.itemTypeMenu;
// If the new item's type is changed immediately, update the MRU // If the new item's type is changed immediately, update the MRU
var handleTypeChange = function () { var handleTypeChange = function () {
this.addItemTypeToNewItemTypeMRU(Zotero.ItemTypes.getName(menu.value)); this.addItemTypeToNewItemTypeMRU(Zotero.ItemTypes.getName(menu.getAttribute('value')));
itemBox.removeHandler('itemtypechange', handleTypeChange); itemBox.removeHandler('itemtypechange', handleTypeChange);
}.bind(this); }.bind(this);
// Don't update the MRU on subsequent opens of the item type menu // Don't update the MRU on subsequent opens of the item type menu
@ -1325,6 +1309,9 @@ var ZoteroPane = new function()
this.addItemTypeToNewItemTypeMRU = function (itemType) { this.addItemTypeToNewItemTypeMRU = function (itemType) {
if (!itemType) {
throw new Error(`Item type not provided`);
}
var mru = Zotero.Prefs.get('newItemTypeMRU'); var mru = Zotero.Prefs.get('newItemTypeMRU');
if (mru) { if (mru) {
var mru = mru.split(','); var mru = mru.split(',');
@ -1643,7 +1630,7 @@ var ZoteroPane = new function()
let type = Zotero.Libraries.get(collectionTreeRow.ref.libraryID).libraryType; let type = Zotero.Libraries.get(collectionTreeRow.ref.libraryID).libraryType;
// Clear quick search and tag selector when switching views // Clear quick search and tag selector when switching views
document.getElementById('zotero-tb-search').value = ""; document.getElementById('zotero-tb-search-textbox').value = "";
if (ZoteroPane.tagSelector) { if (ZoteroPane.tagSelector) {
ZoteroPane.tagSelector.clearTagSelection(); ZoteroPane.tagSelector.clearTagSelection();
} }
@ -2722,8 +2709,8 @@ var ZoteroPane = new function()
this.clearQuicksearch = Zotero.Promise.coroutine(function* () { this.clearQuicksearch = Zotero.Promise.coroutine(function* () {
var search = document.getElementById('zotero-tb-search'); var search = document.getElementById('zotero-tb-search');
if (search.value !== '') { if (search.searchTextbox.value !== '') {
search.value = ''; search.searchTextbox.value = '';
yield this.search(); yield this.search();
return true; return true;
} }
@ -2736,7 +2723,7 @@ var ZoteroPane = new function()
*/ */
this.handleSearchKeypress = function (textbox, event) { this.handleSearchKeypress = function (textbox, event) {
if (event.keyCode == event.DOM_VK_ESCAPE) { if (event.keyCode == event.DOM_VK_ESCAPE) {
textbox.value = ''; textbox.searchTextbox.value = '';
this.search(); this.search();
} }
else if (event.keyCode == event.DOM_VK_RETURN) { else if (event.keyCode == event.DOM_VK_RETURN) {
@ -2760,12 +2747,12 @@ var ZoteroPane = new function()
return; return;
} }
var search = document.getElementById('zotero-tb-search'); var search = document.getElementById('zotero-tb-search');
if (!runAdvanced && search.value.indexOf('"') != -1) { var searchVal = search.searchTextbox.value;
if (!runAdvanced && searchVal.indexOf('"') != -1) {
return; return;
} }
var spinner = document.getElementById('zotero-tb-search-spinner'); var spinner = document.getElementById('zotero-tb-search-spinner');
spinner.style.display = 'inline'; spinner.style.display = 'inline';
var searchVal = search.value;
yield this.itemsView.setFilter('search', searchVal); yield this.itemsView.setFilter('search', searchVal);
spinner.style.display = 'none'; spinner.style.display = 'none';
if (runAdvanced) { if (runAdvanced) {
@ -6290,49 +6277,15 @@ var ZoteroPane = new function()
var paneStack = document.getElementById("zotero-pane-stack"); var paneStack = document.getElementById("zotero-pane-stack");
if(paneStack.hidden) return; if(paneStack.hidden) return;
var stackedLayout = Zotero.Prefs.get("layout") === "stacked";
var collectionsPane = document.getElementById("zotero-collections-pane"); var collectionsPane = document.getElementById("zotero-collections-pane");
var collectionsToolbar = document.getElementById("zotero-collections-toolbar");
var collectionsTree = document.querySelector('#zotero-collections-tree .tree');
var itemsPane = document.getElementById("zotero-items-pane");
var itemsToolbar = document.getElementById("zotero-items-toolbar");
var itemPane = document.getElementById("zotero-item-pane");
var itemToolbar = document.getElementById("zotero-item-toolbar");
var tagSelector = document.getElementById("zotero-tag-selector"); var tagSelector = document.getElementById("zotero-tag-selector");
var collectionsPaneWidth = collectionsPane.getBoundingClientRect().width; var collectionsPaneWidth = collectionsPane.getBoundingClientRect().width;
collectionsToolbar.style.width = collectionsPaneWidth + 'px';
tagSelector.style.maxWidth = collectionsPaneWidth + 'px'; tagSelector.style.maxWidth = collectionsPaneWidth + 'px';
if (collectionsTree) {
let borderSize = Zotero.isMac ? 0 : 2;
collectionsTree.style.maxWidth = (collectionsPaneWidth - borderSize) + 'px';
}
if (ZoteroPane.itemsView) { if (ZoteroPane.itemsView) {
ZoteroPane.itemsView.updateHeight(); ZoteroPane.itemsView.updateHeight();
} }
if (stackedLayout || itemPane.collapsed) {
// The itemsToolbar and itemToolbar share the same space, and it seems best to use some flex attribute from right (because there might be other icons appearing or vanishing).
itemsToolbar.setAttribute("flex", "1");
itemToolbar.setAttribute("flex", "0");
} else {
var itemsToolbarWidth = itemsPane.getBoundingClientRect().width;
if (collectionsPane.collapsed) {
itemsToolbarWidth -= collectionsToolbar.getBoundingClientRect().width;
}
// Not sure why this is necessary, but it keeps the search bar from overflowing into the
// right-hand pane
else {
itemsToolbarWidth -= 8;
}
itemsToolbar.style.width = itemsToolbarWidth + "px";
itemsToolbar.setAttribute("flex", "0");
itemToolbar.setAttribute("flex", "1");
}
this.handleTagSelectorResize(); this.handleTagSelectorResize();
} }

View file

@ -100,7 +100,7 @@
<!--EDIT--> <!--EDIT-->
<command id="cmd_find" <command id="cmd_find"
oncommand="document.getElementById('zotero-tb-search').select()"/> oncommand="document.getElementById('zotero-tb-search-textbox').select()"/>
</commandset> </commandset>
<keyset id="mainKeyset"> <keyset id="mainKeyset">
@ -181,10 +181,35 @@
<menupopup id="menu_NewItemPopup" <menupopup id="menu_NewItemPopup"
onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/> onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/>
</menu> </menu>
<menuitem id="menu_newNote" class="menu-type-library" label="&zotero.toolbar.newNote;"
command="cmd_zotero_newStandaloneNote"/>
<menuitem id="menu_newCollection" class="menu-type-library" label="&zotero.toolbar.newCollection.label;" <menuitem id="menu_newCollection" class="menu-type-library" label="&zotero.toolbar.newCollection.label;"
command="cmd_zotero_newCollection"/> command="cmd_zotero_newCollection"/>
<menu id="menu_libraryAdd"
label="&zotero.toolbar.newLibrary.label;">
<menupopup id="menu_libraryAddPopup">
<menuitem id="menu_groupAdd" label="&zotero.toolbar.newGroup;" oncommand="ZoteroPane_Local.newGroup()"/>
<menu id="menu_feedAddMenu" label="&zotero.toolbar.feeds.new;">
<menupopup>
<menuitem id="menu_feedAddFromURL" label="&zotero.toolbar.feeds.new.fromURL;"
command="cmd_zotero_newFeed_fromURL"/>
<menuitem id="menu_feedAddFromOPML" label="&zotero.toolbar.feeds.new.fromOPML;"
oncommand="ZoteroPane_Local.importFeedsFromOPML()"/>
</menupopup>
</menu>
</menupopup>
</menu>
<menu id="menu_attachmentAdd" label="&zotero.items.menu.attach;">
<menupopup onpopupshowing="ZoteroPane_Local.updateAttachmentButtonMenu(this)">
<menuitem class="menuitem-iconic zotero-menuitem-attachments-web-link" label="&zotero.items.menu.attach.link.uri;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromURI(true, itemID);"/>
<menuitem class="menuitem-iconic zotero-menuitem-attachments-file" label="&zotero.items.menu.attach.file;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(false, itemID);"/>
<menuitem class="menuitem-iconic zotero-menuitem-attachments-link" label="&zotero.items.menu.attach.fileLink;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(true, itemID);" id="zotero-tb-attachment-add-file-link"/>
</menupopup>
</menu>
<menu id="menu_noteAdd" label="&zotero.toolbar.newNote;">
<menupopup onpopupshowing="ZoteroPane_Local.updateNoteButtonMenu()">
<menuitem label="&zotero.toolbar.note.standalone;" command="cmd_zotero_newStandaloneNote"/>
<menuitem label="&zotero.toolbar.note.child;" command="cmd_zotero_newChildNote"/>
</menupopup>
</menu>
<menuitem <menuitem
id="menu_transferFromPDF" id="menu_transferFromPDF"
class="menu-type-reader pdf" class="menu-type-reader pdf"
@ -706,7 +731,61 @@
</toolbar> </toolbar>
</toolbox> </toolbox>
<!-- Keep in sync with Zotero.test conditional block in overlay.js --> <!-- Keep in sync with Zotero.test conditional block in overlay.js -->
<div xmlns="http://www.w3.org/1999/xhtml" id="tab-bar-container"/> <hbox>
<div xmlns="http://www.w3.org/1999/xhtml" id="tab-bar-container" style="-moz-box-flex: 1;"/>
<hbox id="zotero-tabs-toolbar" align="center">
<hbox align="center" pack="start">
<toolbarbutton
id="zotero-tb-locate"
class="zotero-tb-button"
tooltiptext="&zotero.toolbar.openURL.label;"
type="menu"
tabindex="-1"
wantdropmarker="true">
<menupopup id="zotero-tb-locate-menu" onpopupshowing="Zotero_LocateMenu.buildLocateMenu()"/>
</toolbarbutton>
</hbox>
<toolbarbutton id="zotero-tb-opened-tabs" class="zotero-tb-button" tabindex="-1" data-l10n-id="zotero-toolbar-opened-tabs-menu" type="panel" />
<hbox id="zotero-tb-sync-progress-box" align="center" pack="end">
<toolbarbutton id="zotero-tb-sync-stop"
tooltiptext="&zotero.sync.stop;"
tabindex="-1"
style="-moz-user-focus: normal;"
oncommand="this.hidden = true; Zotero.Sync.Runner.stop()"
hidden="true"/>
</hbox>
<hbox id="zotero-pq-buttons">
</hbox>
<toolbarbutton tabindex="-1" id="zotero-tb-sync-error" hidden="true" style="-moz-user-focus: normal;" />
<!--
We put this here, but it can be used wherever
Zotero.Sync.Runner.updateErrorPanel() puts it
-->
<panel id="zotero-sync-error-panel" type="arrow" onpopupshown="
const buttons = this.getElementsByTagName('button');
if (buttons[0]) {
buttons[0].focus();
}" />
<toolbarbutton id="zotero-tb-sync" tabindex="-1" class="zotero-tb-button" tooltip="_child"
oncommand="ZoteroPane.sync()">
<tooltip
id="zotero-tb-sync-tooltip"
onpopupshowing="Zotero.Sync.Runner.registerSyncStatus(this)"
onpopuphiding="Zotero.Sync.Runner.registerSyncStatus()"
noautohide="true">
<label id="zotero-tb-sync-label"/>
<label id="zotero-tb-sync-status" class="sync-button-tooltip-status" hidden="true"/>
<label id="zotero-tb-sync-last-sync" class="sync-button-tooltip-last-sync"/>
<div xmlns="http://www.w3.org/1999/xhtml" class="sync-button-tooltip-messages"/>
</tooltip>
</toolbarbutton>
</hbox>
</hbox>
<commandset id="mainCommandSet"> <commandset id="mainCommandSet">
<command id="cmd_zotero_reportErrors" oncommand="ZoteroPane_Local.reportErrors();"/> <command id="cmd_zotero_reportErrors" oncommand="ZoteroPane_Local.reportErrors();"/>
@ -722,7 +801,7 @@
disabled="true"/> disabled="true"/>
<command id="cmd_zotero_createTimeline" oncommand="Zotero_Timeline_Interface.loadTimeline();"/> <command id="cmd_zotero_createTimeline" oncommand="Zotero_Timeline_Interface.loadTimeline();"/>
<command id="cmd_zotero_rtfScan" oncommand="window.openDialog('chrome://zotero/content/rtfScan.xhtml', 'rtfScan', 'chrome,centerscreen')"/> <command id="cmd_zotero_rtfScan" oncommand="window.openDialog('chrome://zotero/content/rtfScan.xhtml', 'rtfScan', 'chrome,centerscreen')"/>
<command id="cmd_zotero_newCollection" oncommand="ZoteroPane_Local.newCollection()"/> <command id="cmd_zotero_newCollection" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection()?.key)"/>
<command id="cmd_zotero_newFeed_fromURL" oncommand="ZoteroPane_Local.newFeedFromURL()"/> <command id="cmd_zotero_newFeed_fromURL" oncommand="ZoteroPane_Local.newFeedFromURL()"/>
<command id="cmd_zotero_newSavedSearch" oncommand="ZoteroPane_Local.newSearch()"/> <command id="cmd_zotero_newSavedSearch" oncommand="ZoteroPane_Local.newSearch()"/>
<command id="cmd_zotero_newStandaloneNote" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/> <command id="cmd_zotero_newStandaloneNote" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/>
@ -751,166 +830,6 @@
onkeypress="ZoteroPane_Local.handleKeyPress(event)" onkeypress="ZoteroPane_Local.handleKeyPress(event)"
chromedir="&locale.dir;"> chromedir="&locale.dir;">
<toolbar id="zotero-toolbar" tabindex="-1" class="toolbar toolbar-primary">
<hbox id="zotero-collections-toolbar" align="center">
<toolbarbutton id="zotero-tb-collection-add" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newCollection.label;" command="cmd_zotero_newCollection"/>
<toolbarbutton
id="zotero-tb-library-add-menu"
class="zotero-tb-button"
tabindex="-1"
tooltiptext="&zotero.toolbar.newLibrary.label;"
type="menu"
wantdropmarker="true">
<menupopup id="zotero-tb-library-add-popup">
<menuitem id="zotero-tb-group-add" label="&zotero.toolbar.newGroup;" oncommand="ZoteroPane_Local.newGroup()"/>
<menu id="zotero-tb-feed-add-menu" label="&zotero.toolbar.feeds.new;">
<menupopup>
<menuitem id="zotero-tb-feed-add-fromURL" label="&zotero.toolbar.feeds.new.fromURL;"
command="cmd_zotero_newFeed_fromURL"/>
<menuitem id="zotero-tb-feed-add-fromOPML" label="&zotero.toolbar.feeds.new.fromOPML;"
oncommand="ZoteroPane_Local.importFeedsFromOPML()"/>
</menupopup>
</menu>
</menupopup>
</toolbarbutton>
</hbox>
<hbox id="zotero-items-toolbar" align="center">
<toolbarbutton
id="zotero-tb-add"
class="zotero-tb-button"
tabindex="-1"
tooltiptext="&zotero.toolbar.newItem.label;"
type="menu"
wantdropmarker="true"
onmousedown="if (this.disabled) { event.preventDefault(); return; }">
<menupopup onpopupshowing="ZoteroPane_Local.updateNewItemTypes()">
<menuseparator/>
<menuitem label="&zotero.toolbar.attachment.linked;" oncommand="ZoteroPane_Local.addAttachmentFromDialog(true);" tooltiptext=""/>
<menuitem label="&zotero.toolbar.attachment.add;" oncommand="ZoteroPane_Local.addAttachmentFromDialog();" tooltiptext=""/>
<menuseparator/>
<menu label="&zotero.toolbar.moreItemTypes.label;" tooltiptext="">
<menupopup id="zotero-tb-add-more" onpopupshowing="event.stopPropagation()"/>
</menu>
</menupopup>
</toolbarbutton>
<toolbarbutton id="zotero-tb-lookup" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.lookup.label;" type="panel"
onmousedown="if (this.disabled) { event.preventDefault(); return; } Zotero_Lookup.showPanel(this)"/>
<panel id="zotero-lookup-panel" type="arrow"
onpopupshowing="Zotero_Lookup.onShowing(event)"
onpopupshown="Zotero_Lookup.onShown(event)"
onpopuphidden="Zotero_Lookup.onHidden(event)"
onfocusout="Zotero_Lookup.onFocusOut(event)"
>
<vbox>
<label control="zotero-lookup-textbox">&zotero.lookup.description;</label>
<vbox id="zotero-lookup-multiline">
<html:textarea
id="zotero-lookup-textbox"
onkeypress="Zotero_Lookup.onKeyPress(event, this)"
oninput="Zotero_Lookup.onInput(event, this)"
rows="1"
wrap="off"/>
<hbox id="zotero-lookup-buttons" align="start" hidden="true">
<button
align="start"
oncommand="Zotero_Lookup.accept(document.getElementById('zotero-lookup-textbox'))"
label="&zotero.lookup.button.search;"/>
<html:progress id="zotero-lookup-multiline-progress" value="0" hidden="true" style="-moz-box-flex: 1"/>
</hbox>
</vbox>
</vbox>
</panel>
<!--<toolbarbutton id="zotero-tb-note-add" class="zotero-tb-button" tooltiptext="&zotero.toolbar.note.standalone;" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/>-->
<toolbarbutton
id="zotero-tb-note-add" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newNote;"
type="menu"
tabindex="-1"
wantdropmarker="true"
onmousedown="if (this.disabled) { event.preventDefault(); return; }">
<menupopup onpopupshowing="ZoteroPane_Local.updateNoteButtonMenu()">
<menuitem label="&zotero.toolbar.note.standalone;" command="cmd_zotero_newStandaloneNote"/>
<menuitem label="&zotero.toolbar.note.child;" command="cmd_zotero_newChildNote"/>
</menupopup>
</toolbarbutton>
<toolbarbutton
id="zotero-tb-attachment-add"
class="zotero-tb-button"
tabindex="-1"
tooltiptext="&zotero.items.menu.attach;"
type="menu"
wantdropmarker="true"
onmousedown="if (this.disabled) { event.preventDefault(); return; }">
<menupopup onpopupshowing="ZoteroPane_Local.updateAttachmentButtonMenu(this)">
<menuitem class="menuitem-iconic zotero-menuitem-attachments-web-link" label="&zotero.items.menu.attach.link.uri;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromURI(true, itemID);"/>
<menuitem class="menuitem-iconic zotero-menuitem-attachments-file" label="&zotero.items.menu.attach.file;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(false, itemID);"/>
<menuitem class="menuitem-iconic zotero-menuitem-attachments-link" label="&zotero.items.menu.attach.fileLink;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(true, itemID);" id="zotero-tb-attachment-add-file-link"/>
</menupopup>
</toolbarbutton>
<toolbarseparator/>
<toolbarbutton id="zotero-tb-advanced-search" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.advancedSearch;" command="cmd_zotero_advancedSearch"/>
<spacer flex="1"/>
<image id="zotero-tb-search-spinner" class="zotero-spinner-14" style="display: none"/>
<quick-search-textbox id="zotero-tb-search" timeout="250"
onkeypress="ZoteroPane_Local.handleSearchKeypress(this, event)"
oninput="ZoteroPane_Local.handleSearchInput(this, event)"
oncommand="ZoteroPane_Local.search()"/>
</hbox>
<hbox id="zotero-item-toolbar" flex="1" align="center">
<hbox align="center" pack="start" flex="1">
<toolbarbutton
id="zotero-tb-locate"
class="zotero-tb-button"
tooltiptext="&zotero.toolbar.openURL.label;"
type="menu"
wantdropmarker="true">
<menupopup id="zotero-tb-locate-menu" onpopupshowing="Zotero_LocateMenu.buildLocateMenu()"/>
</toolbarbutton>
</hbox>
<hbox id="zotero-tb-sync-progress-box" align="center" pack="end">
<toolbarbutton id="zotero-tb-sync-stop"
tooltiptext="&zotero.sync.stop;"
tabindex="-1"
style="-moz-user-focus: normal;"
oncommand="this.hidden = true; Zotero.Sync.Runner.stop()"
hidden="true"/>
</hbox>
<hbox id="zotero-pq-buttons">
</hbox>
<toolbarbutton tabindex="-1" id="zotero-tb-sync-error" hidden="true" style="-moz-user-focus: normal;" />
<!--
We put this here, but it can be used wherever
Zotero.Sync.Runner.updateErrorPanel() puts it
-->
<panel id="zotero-sync-error-panel" type="arrow" onpopupshown="
const buttons = this.getElementsByTagName('button');
if (buttons[0]) {
buttons[0].focus();
}" />
<toolbarbutton id="zotero-tb-sync" tabindex="-1" class="zotero-tb-button" tooltip="_child"
oncommand="ZoteroPane.sync()">
<tooltip
id="zotero-tb-sync-tooltip"
onpopupshowing="Zotero.Sync.Runner.registerSyncStatus(this)"
onpopuphiding="Zotero.Sync.Runner.registerSyncStatus()"
noautohide="true">
<label id="zotero-tb-sync-label"/>
<label id="zotero-tb-sync-status" class="sync-button-tooltip-status" hidden="true"/>
<label id="zotero-tb-sync-last-sync" class="sync-button-tooltip-last-sync"/>
<div xmlns="http://www.w3.org/1999/xhtml" class="sync-button-tooltip-messages"/>
</tooltip>
</toolbarbutton>
</hbox>
</toolbar>
<vbox id="sync-reminder-container" collapsed="true" role="status"> <vbox id="sync-reminder-container" collapsed="true" role="status">
<html:div id="sync-reminder-banner"> <html:div id="sync-reminder-banner">
<html:div id="sync-reminder-message"/> <html:div id="sync-reminder-message"/>
@ -1018,6 +937,19 @@
<hbox id="zotero-trees" flex="1"> <hbox id="zotero-trees" flex="1">
<vbox id="zotero-collections-pane" zotero-persist="width"> <vbox id="zotero-collections-pane" zotero-persist="width">
<toolbar id="zotero-toolbar-collection-tree" tabindex="-1" class="zotero-toolbar toolbar toolbar-primary">
<hbox id="zotero-collections-toolbar" align="center" flex="1">
<toolbarbutton id="zotero-tb-collection-add" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newCollection.label;" command="cmd_zotero_newCollection"/>
<spacer flex="1"></spacer>
<html:div style="display: flex;flex-direction: row;">
<toolbarbutton id="zotero-tb-collection-search" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newCollection.label;" oncommand="ZoteroPane.revealCollectionSearch()"/>
<search-textbox
id="zotero-collections-search" class="hidden"
onkeydown="ZoteroPane.handleCollectionSearchKeypress(this, event)"
oncommand="ZoteroPane.handleCollectionSearchInput(this, event)"/>
</html:div>
</hbox>
</toolbar>
<!-- This is used for positioning the sync error icon panel <!-- This is used for positioning the sync error icon panel
under specific tree cells, which don't exist as under specific tree cells, which don't exist as
elements on their own --> elements on their own -->
@ -1055,9 +987,72 @@
</splitter> </splitter>
<box id="zotero-layout-switcher" orient="horizontal" zotero-persist="orient" flex="1"> <box id="zotero-layout-switcher" orient="horizontal" zotero-persist="orient" flex="1">
<hbox id="zotero-items-pane" class="virtualized-table-container" zotero-persist="width height" flex="1" clickthrough="never"> <vbox flex="1">
<html:div id="zotero-items-tree"></html:div> <toolbar id="zotero-toolbar-item-tree" tabindex="-1" class="zotero-toolbar toolbar toolbar-primary">
</hbox> <hbox id="zotero-items-toolbar" align="center" flex="1">
<toolbarbutton
id="zotero-tb-add"
class="zotero-tb-button"
tabindex="-1"
tooltiptext="&zotero.toolbar.newItem.label;"
type="menu"
wantdropmarker="true"
onmousedown="if (this.disabled) { event.preventDefault(); return; }">
<menupopup onpopupshowing="ZoteroPane_Local.updateNewItemTypes()"></menupopup>
</toolbarbutton>
<toolbarbutton id="zotero-tb-lookup" tabindex="-1" class="zotero-tb-button" tooltiptext="&zotero.toolbar.lookup.label;" type="panel"
onmousedown="if (this.disabled) { event.preventDefault(); return; } Zotero_Lookup.showPanel(this)"/>
<panel id="zotero-lookup-panel" type="arrow"
onpopupshowing="Zotero_Lookup.onShowing(event)"
onpopupshown="Zotero_Lookup.onShown(event)"
onpopuphidden="Zotero_Lookup.onHidden(event)"
onfocusout="Zotero_Lookup.onFocusOut(event)"
>
<vbox>
<label control="zotero-lookup-textbox">&zotero.lookup.description;</label>
<vbox id="zotero-lookup-multiline">
<html:textarea
id="zotero-lookup-textbox"
onkeypress="Zotero_Lookup.onKeyPress(event, this)"
oninput="Zotero_Lookup.onInput(event, this)"
rows="1"
wrap="off"/>
<hbox id="zotero-lookup-buttons" align="start" hidden="true">
<button
align="start"
oncommand="Zotero_Lookup.accept(document.getElementById('zotero-lookup-textbox'))"
label="&zotero.lookup.button.search;"/>
<html:progress id="zotero-lookup-multiline-progress" value="0" hidden="true" style="-moz-box-flex: 1"/>
</hbox>
</vbox>
</vbox>
</panel>
<toolbarbutton
id="zotero-tb-attachment-add"
class="zotero-tb-button"
tabindex="-1"
data-l10n-id="zotero-toolbar-new-attachment"
oncommand="var selected = ZoteroPane.getSelectedItems();if (this.disabled || !selected.length) { event.preventDefault(); return; }; ZoteroPane_Local.addAttachmentFromDialog(false, selected[0].id);">
</toolbarbutton>
<toolbarbutton id="zotero-tb-note-add" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newNote;" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/>
<spacer flex="1"/>
<image id="zotero-tb-search-spinner" class="zotero-spinner-14" style="display: none"/>
<quick-search-textbox id="zotero-tb-search" timeout="250"
onkeypress="ZoteroPane_Local.handleSearchKeypress(this, event)"
oninput="ZoteroPane_Local.handleSearchInput(this, event)"
oncommand="ZoteroPane_Local.search()"/>
</hbox>
</toolbar>
<hbox id="zotero-items-pane" class="virtualized-table-container" zotero-persist="width height" flex="1" clickthrough="never">
<html:div id="zotero-items-tree"></html:div>
</hbox>
</vbox>
<splitter id="zotero-items-splitter" resizebefore="closest" resizeafter="closest" collapse="after" orient="horizontal" zotero-persist="state orient" <splitter id="zotero-items-splitter" resizebefore="closest" resizeafter="closest" collapse="after" orient="horizontal" zotero-persist="state orient"
onmousemove="ZoteroPane.updateToolbarPosition()" onmousemove="ZoteroPane.updateToolbarPosition()"

View file

@ -1,3 +1,8 @@
zotero-toolbar-new-attachment =
.tooltiptext = Add File
zotero-toolbar-opened-tabs-menu =
.tooltiptext = List all tabs
import-window = import-window =
.title = Import .title = Import

View file

@ -6,7 +6,7 @@
#zotero-collections-pane #zotero-collections-pane
{ {
min-width: 150px; min-width: 250px;
width: 250px; width: 250px;
} }
@ -196,6 +196,7 @@
.toolbar { .toolbar {
height: 32px !important; /* Hard-code this to fix toolbar icon compression on Linux */ height: 32px !important; /* Hard-code this to fix toolbar icon compression on Linux */
min-height: 32px; /* Needed to prevent squashing by stretched tag selector */
margin: 0; margin: 0;
padding: 0; padding: 0;
min-width: 1px; min-width: 1px;
@ -280,6 +281,34 @@
padding: 0; padding: 0;
} }
#zotero-collections-search {
height: 25px;
max-width: 0;
display: inline-block;
overflow: hidden;
transition: max-width 0.7s ease;
padding: 0;
margin: 0;
appearance: none;
visibility: hidden;
}
#zotero-collections-search.visible {
max-width: 200px;
appearance: auto;
visibility: visible;
}
/* Hide collection search on pane collapse, otherwise it still shows when focused */
#zotero-collections-pane[collapsed=true] #zotero-collections-search.visible {
visibility: hidden;
}
/* Hide the add button on pane collapse. Otherwise, it may show on top of 'New Item' */
#zotero-collections-pane[collapsed=true] #zotero-tb-collection-add {
visibility: hidden;
}
#zotero-tb-add #zotero-tb-add
{ {
@ -592,7 +621,7 @@
color: gray; color: gray;
} }
#zotero-toolbar .zotero-toolbar
{ {
visibility: visible !important; visibility: visible !important;
display: inherit !important; display: inherit !important;

View file

@ -13,7 +13,7 @@
} }
#zotero-items-pane { #zotero-items-pane {
min-width: 290px; min-width: 370px;
min-height: 150px; min-height: 150px;
height: 150px; height: 150px;
width: 290px; width: 290px;

View file

@ -134,7 +134,7 @@
background: var(--material-tabbar) background: var(--material-tabbar)
} }
#zotero-toolbar { .zotero-toolbar {
-moz-appearance: none; -moz-appearance: none;
background: var(--material-tabbar); background: var(--material-tabbar);
border-bottom: var(--material-panedivider); border-bottom: var(--material-panedivider);

View file

@ -26,6 +26,11 @@ quick-search-textbox {
display: none; /* Hide automatic dropmarker */ display: none; /* Hide automatic dropmarker */
} }
#zotero-tb-search-dropmarker {
position: relative;
left: 38px;
}
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */ /* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
@media (min-resolution: 1.25dppx) { @media (min-resolution: 1.25dppx) {
#zotero-tb-search-menu-button { list-style-image: url("chrome://zotero/skin/searchbar-dropmarker@2x.png"); } #zotero-tb-search-menu-button { list-style-image: url("chrome://zotero/skin/searchbar-dropmarker@2x.png"); }

View file

@ -5,6 +5,10 @@
height: 12px; height: 12px;
} }
#zotero-tb-search-dropmarker {
left: 44px;
}
#zotero-tb-search .textbox-search-icon { #zotero-tb-search .textbox-search-icon {
visibility: hidden; visibility: hidden;
} }

View file

@ -1,4 +1,4 @@
#zotero-tb-search-menu-button { #zotero-tb-search-menu-button {
margin: -6px 0 -6px -18px; margin: -6px 0 0 -18px;
padding: 6px 0 6px 15px; padding: 6px 0 0 15px;
} }

View file

@ -5,6 +5,10 @@
background: transparent; background: transparent;
} }
#zotero-tb-search-dropmarker {
left: 27px;
}
#zotero-tb-search .textbox-search-icon { #zotero-tb-search .textbox-search-icon {
visibility: hidden; visibility: hidden;
} }

View file

@ -1348,7 +1348,7 @@ describe("Zotero.CollectionTree", function() {
await cv.selectFeeds(); await cv.selectFeeds();
await waitForItemsLoad(win); await waitForItemsLoad(win);
var quickSearch = win.document.getElementById('zotero-tb-search'); var quickSearch = win.document.getElementById('zotero-tb-search-textbox');
quickSearch.value = feedItem1.getField('title'); quickSearch.value = feedItem1.getField('title');
quickSearch.doCommand(); quickSearch.doCommand();

View file

@ -344,7 +344,7 @@ describe("Zotero.ItemTree", function() {
await createDataObject('item'); await createDataObject('item');
var quicksearch = win.document.getElementById('zotero-tb-search'); var quicksearch = win.document.getElementById('zotero-tb-search');
quicksearch.value = Zotero.randomString(); quicksearch.searchTextbox.value = Zotero.randomString();
quicksearch.doCommand(); quicksearch.doCommand();
await itemsView._refreshPromise; await itemsView._refreshPromise;
@ -367,7 +367,7 @@ describe("Zotero.ItemTree", function() {
yield createDataObject('item'); yield createDataObject('item');
var quicksearch = win.document.getElementById('zotero-tb-search'); var quicksearch = win.document.getElementById('zotero-tb-search-textbox');
quicksearch.value = searchString; quicksearch.value = searchString;
quicksearch.doCommand(); quicksearch.doCommand();
yield itemsView._refreshPromise; yield itemsView._refreshPromise;

View file

@ -1485,4 +1485,97 @@ describe("ZoteroPane", function() {
stub.restore(); stub.restore();
}); });
}); });
describe("#focus()", function () {
before(async function () {
var collection = new Zotero.Collection;
collection.name = "Focus Test";
await collection.saveTx();
await waitForItemsLoad(win);
});
var tab = new KeyboardEvent('keydown', {
key: 'Tab',
shiftKey: false,
bubbles: true
});
var shiftTab = new KeyboardEvent('keydown', {
key: 'Tab',
shiftKey: true,
bubbles: true
});
var rightArrow = new KeyboardEvent('keydown', {
key: 'ArrowRight',
bubbles: true
});
var leftArrow = new KeyboardEvent('keydown', {
key: 'ArrowLeft',
bubbles: true
});
it("should shift-tab through the toolbar to item-tree", async function () {
let searchBox = doc.getElementById('zotero-tb-search-textbox');
searchBox.focus();
let sequence = [
"zotero-tb-search-dropmarker",
"zotero-tb-add",
"zotero-collections-search",
"zotero-tb-collection-add",
"zotero-tb-sync",
"zotero-tb-opened-tabs"
];
for (let id of sequence) {
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement.id, id);
}
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement.className, "tab selected");
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement.id, "item-tree-main-default");
});
it("should tab through the toolbar to collection-tree", async function () {
win.Zotero_Tabs.moveFocus("current");
let sequence = [
"zotero-tb-opened-tabs",
"zotero-tb-sync",
"zotero-tb-collection-add",
"zotero-collections-search",
"zotero-tb-add",
"zotero-tb-search-dropmarker",
'zotero-tb-search-textbox',
'collection-tree',
];
for (let id of sequence) {
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement.id, id);
}
});
it("should navigate toolbarbuttons with arrows", async function () {
let addItem = doc.getElementById('zotero-tb-add');
addItem.focus();
doc.activeElement.dispatchEvent(rightArrow);
assert.equal(doc.activeElement.id, "zotero-tb-lookup");
doc.activeElement.dispatchEvent(rightArrow);
assert.equal(doc.activeElement.id, "zotero-tb-attachment-add");
doc.activeElement.dispatchEvent(rightArrow);
assert.equal(doc.activeElement.id, "zotero-tb-note-add");
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement.id, "zotero-tb-attachment-add");
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement.id, "zotero-tb-lookup");
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement.id, "zotero-tb-add");
});
});
}) })