diff --git a/chrome/content/zotero/bindings/noteeditor.xml b/chrome/content/zotero/bindings/noteeditor.xml index ffd21a99a1..8dc4d400cc 100644 --- a/chrome/content/zotero/bindings/noteeditor.xml +++ b/chrome/content/zotero/bindings/noteeditor.xml @@ -349,6 +349,25 @@ + + + { + let n = 0; + while (!this._editorInstance && n++ < 100) { + await Zotero.Promise.delay(10); + } + await this._editorInstance._initPromise; + this._iframe.focus(); + try { + this._editorInstance._iframeWindow.document.querySelector('.toolbar-button-return').focus(); + } catch(e) { + } + })(); + ]]> + + + diff --git a/chrome/content/zotero/components/itemPane/notesList.jsx b/chrome/content/zotero/components/itemPane/notesList.jsx index 25fe9d4cb1..98c9ce0007 100644 --- a/chrome/content/zotero/components/itemPane/notesList.jsx +++ b/chrome/content/zotero/components/itemPane/notesList.jsx @@ -28,9 +28,15 @@ import cx from 'classnames'; const MAX_UNEXPANDED_ALL_NOTES = 7; -const NoteRow = memo(({ id, title, body, date, onClick, onContextMenu, parentItemType, parentTitle }) => { +const NoteRow = memo(({ id, title, body, date, onClick, onKeyDown, onContextMenu, parentItemType, parentTitle }) => { return ( -
onClick(id)} onContextMenu={(event) => onContextMenu(id, event)}> +
onClick(id)} + onContextMenu={(event) => onContextMenu(id, event)} + onKeyDown={onKeyDown} + >
{ parentItemType ?
@@ -76,6 +82,54 @@ const NotesList = forwardRef(({ onClick, onContextMenu, onAddChildButtonDown, on function handleClickMore() { _setExpanded(true); } + + function handleButtonKeydown(event) { + if (event.key === 'Tab' && !event.shiftKey) { + let node = event.target.parentElement.parentElement.querySelector('[tabindex="-1"]'); + if (node) { + node.focus(); + event.preventDefault(); + } + } + else if (event.key === 'Tab' && event.shiftKey) { + let prevSection = event.target.parentElement.parentElement.previousElementSibling; + if (prevSection) { + let node = prevSection.querySelector('[tabindex="-1"]:last-child'); + if (node) { + node.focus(); + event.preventDefault(); + } + } + } + } + + function handleRowKeyDown(event) { + if (['Enter', 'Space'].includes(event.key)) { + // Focus the previous row, because "more-row" will disappear + if (event.target.classList.contains('more-row')) { + let node = event.target.previousElementSibling; + if (node) { + node.focus(); + event.preventDefault(); + } + } + event.target.click(); + } + else if (event.key === 'ArrowUp') { + let node = event.target.previousElementSibling; + if (node) { + node.focus(); + event.preventDefault(); + } + } + else if (event.key === 'ArrowDown') { + let node = event.target.nextElementSibling; + if (node) { + node.focus(); + event.preventDefault(); + } + } + } let childNotes = notes.filter(x => x.isCurrentChild); let allNotes = notes.filter(x => !x.isCurrentChild); @@ -85,22 +139,22 @@ const NotesList = forwardRef(({ onClick, onContextMenu, onAddChildButtonDown, on {hasParent &&

{Zotero.getString('pane.context.itemNotes')}

- +
{!childNotes.length &&
{Zotero.getString('pane.context.noNotes')}
} {childNotes.map(note => )} + onClick={onClick} onKeyDown={handleRowKeyDown} onContextMenu={onContextMenu}/>)}
}

{Zotero.getString('pane.context.allNotes')}

- +
{!allNotes.length &&
{Zotero.getString('pane.context.noNotes')}
} {visibleNotes.map(note => )} + onClick={onClick} onKeyDown={handleRowKeyDown} onContextMenu={onContextMenu}/>)} {allNotes.length > visibleNotes.length - &&
{ + &&
{ Zotero.getString('general.numMore', Zotero.Utilities.numberFormat( [allNotes.length - visibleNotes.length], 0)) }
diff --git a/chrome/content/zotero/contextPane.js b/chrome/content/zotero/contextPane.js index df1663ad48..c3ec88c34d 100644 --- a/chrome/content/zotero/contextPane.js +++ b/chrome/content/zotero/contextPane.js @@ -57,7 +57,8 @@ var ZoteroContextPane = new function () { this.update = _update; this.getActiveEditor = _getActiveEditor; - + this.focus = _focus; + this.init = function () { if (!Zotero) { return; @@ -247,6 +248,36 @@ var ZoteroContextPane = new function () { } } + function _focus() { + var splitter; + if (Zotero.Prefs.get('layout') == 'stacked') { + splitter = _contextPaneSplitterStacked; + } + else { + splitter = _contextPaneSplitter; + } + + if (splitter.getAttribute('state') != 'collapsed') { + if (_panesDeck.selectedIndex == 0) { + var node = _itemPaneDeck.selectedPanel; + node.querySelector('tab[selected]').focus(); + return true; + } + else { + var node = _notesPaneDeck.selectedPanel; + if (node.selectedIndex == 0) { + node.querySelector('textbox').focus(); + return true; + } + else { + node.querySelector('zoteronoteeditor').focusFirst(); + return true; + } + } + } + return false; + } + function _updateAddToNote() { var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); if (reader) { @@ -358,6 +389,10 @@ var ZoteroContextPane = new function () { splitter.setAttribute('state', hide ? 'collapsed' : 'open'); _update(); + + if (!hide) { + ZoteroContextPane.focus(); + } } function _getCurrentAttachment() { @@ -455,6 +490,8 @@ var ZoteroContextPane = new function () { listBox.setAttribute('flex', '1'); var listInner = document.createElementNS(HTML_NS, 'div'); listInner.className = 'notes-list-container'; + // Otherwise it can be focused with tab + listInner.tabIndex = -1; listBox.append(listInner); list.append(head, listBox); diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 6904345e00..aa68996d49 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -158,6 +158,22 @@ class ReaderInstance { setSidebarOpen(open) { this._postMessage({ action: 'setSidebarOpen', open }); } + + focusLastToolbarButton() { + this._iframeWindow.focus(); + this._postMessage({ action: 'focusLastToolbarButton' }); + } + + tabToolbar(reverse) { + this._postMessage({ action: 'tabToolbar', reverse }); + // Avoid toolbar find button being focused for a short moment + setTimeout(() => this._iframeWindow.focus()); + } + + focusFirst() { + this._postMessage({ action: 'focusFirst' }); + setTimeout(() => this._iframeWindow.focus()); + } async setBottomPlaceholderHeight(height) { await this._initPromise; @@ -738,6 +754,22 @@ class ReaderInstance { } return; } + case 'focusSplitButton': { + let win = Zotero.getMainWindow(); + if (win) { + win.document.getElementById('zotero-tb-toggle-item-pane').focus(); + } + return; + } + case 'focusContextPane': { + let win = Zotero.getMainWindow(); + if (win) { + if (!this._window.ZoteroContextPane.focus()) { + this.focusFirst(); + } + } + return; + } } } catch (e) { @@ -822,6 +854,7 @@ class ReaderTab extends ReaderInstance { this._tabContainer = container; this._iframe = this._window.document.createElement('browser'); + this._iframe.setAttribute('class', 'reader'); this._iframe.setAttribute('flex', '1'); this._iframe.setAttribute('type', 'content'); this._iframe.setAttribute('src', 'resource://zotero/pdf-reader/viewer.html'); diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 5a667bc648..3e4b8d5e78 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -515,6 +515,79 @@ var ZoteroPane = new function() * Trigger actions based on keyboard shortcuts */ function handleKeyDown(event, from) { + if (Zotero_Tabs.selectedIndex > 0) { + let itemPaneToggle = document.getElementById('zotero-tb-toggle-item-pane'); + let notesPaneToggle = document.getElementById('zotero-tb-toggle-notes-pane'); + // Using ArrowDown and ArrowUp to be consistent with pdf-reader + if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { + if (event.target === itemPaneToggle) { + notesPaneToggle.focus(); + } + } + else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { + if (event.target === notesPaneToggle) { + itemPaneToggle.focus(); + } + else if (event.target === itemPaneToggle) { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + if (reader) { + reader.focusLastToolbarButton(); + } + } + } + else if (event.key === 'Tab' + && [itemPaneToggle, notesPaneToggle].includes(event.target)) { + if (event.shiftKey) { + ZoteroContextPane.focus(); + } + else { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + if (reader) { + reader.tabToolbar(); + } + } + event.preventDefault(); + event.stopPropagation(); + } + else if (event.key === 'Escape') { + if (!document.activeElement.classList.contains('reader')) { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + if (reader) { + reader.focus(); + event.preventDefault(); + event.stopPropagation(); + } + } + } + else if (event.key === 'Tab' && event.shiftKey) { + let node = document.activeElement; + if (node && node.nodeType === Node.ELEMENT_NODE && ( + node.parentNode.classList.contains('zotero-editpane-tabs') + || node.getAttribute('type') === 'search' + || node.getAttribute('anonid') === 'editor-view' + && node.contentWindow.document.activeElement.classList.contains('toolbar-button-return'))) { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + if (reader) { + reader.focus(); + } + event.preventDefault(); + event.stopPropagation(); + } + } + else if (event.key === 'Tab') { + if (!document.activeElement.classList.contains('reader')) { + setTimeout(() => { + if (document.activeElement.classList.contains('reader')) { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + if (reader) { + reader.focusFirst(); + } + } + }); + } + } + } + const cmdOrCtrlOnly = Zotero.isMac ? (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey) : (event.ctrlKey && !event.shiftKey && !event.altKey); diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul index 2a1e2ca556..f548ea976f 100644 --- a/chrome/content/zotero/zoteroPane.xul +++ b/chrome/content/zotero/zoteroPane.xul @@ -84,8 +84,8 @@ diff --git a/pdf-reader b/pdf-reader index b97d62e02b..cc517ae53b 160000 --- a/pdf-reader +++ b/pdf-reader @@ -1 +1 @@ -Subproject commit b97d62e02bdba96ef95a74dcefeab23ce2520eae +Subproject commit cc517ae53bd849e7dc3a958b156552117d209e53 diff --git a/scss/abstracts/_split-button.scss b/scss/abstracts/_split-button.scss index 12097d7fac..25784c286d 100644 --- a/scss/abstracts/_split-button.scss +++ b/scss/abstracts/_split-button.scss @@ -189,6 +189,11 @@ $toolbar-btn-icon-active-offset: 0; } } + // Remove strange inner dotted outline that appears on focus + &::-moz-focus-inner { + border: 0; + } + &:hover { background: $toolbar-btn-hover-bg; border-color: $toolbar-btn-border-hover-color; diff --git a/scss/components/_notesList.scss b/scss/components/_notesList.scss index 33f58bcf46..1269ed78bc 100644 --- a/scss/components/_notesList.scss +++ b/scss/components/_notesList.scss @@ -136,3 +136,16 @@ padding-top: 6px !important; } } + +.note-row, .more-row { + outline: 0; + + &:-moz-focusring { + box-shadow: $toolbar-btn-focus-box-shadow; + z-index: 1; + + @include retina { + box-shadow: $toolbar-btn-focus-box-shadow-2x; + } + } +} \ No newline at end of file