diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index 8e0c3bc8d2..8b79889644 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -35,7 +35,6 @@ const { getDragTargetOrient } = require('components/utils'); const { Cc, Ci, Cu } = require('chrome'); const CHILD_INDENT = 15; -const TYPING_TIMEOUT = 1000; var CollectionTree = class CollectionTree extends LibraryTree { static async init(domEl, opts) { @@ -89,7 +88,6 @@ var CollectionTree = class CollectionTree extends LibraryTree { this._editing = null; this._editingInput = null; this._dropRow = null; - this._typingString = ""; this._typingTimeout = null; this.onLoad = this.createEventBinding('load', true, true); @@ -153,9 +151,6 @@ var CollectionTree = class CollectionTree extends LibraryTree { else if (event.key == "F2" && !Zotero.isMac && treeRow.isCollection()) { this.handleActivate(event, [this.selection.focused]); } - else if (event.key.length == 1 && !(event.ctrlKey || event.metaKey || event.altKey)) { - this.handleTyping(event.key); - } return true; } @@ -205,48 +200,7 @@ var CollectionTree = class CollectionTree extends LibraryTree { handleEditingChange = (event, index) => { this.getRow(index).editingName = event.target.value; } - - async handleTyping(char) { - char = char.toLowerCase(); - this._typingString += char; - let allSameChar = true; - for (let i = this._typingString.length - 1; i >= 0; i--) { - if (char != this._typingString[i]) { - allSameChar = false; - break; - } - } - if (allSameChar) { - for (let i = this.selection.focused + 1, checked = 0; checked < this._rows.length; i++, checked++) { - i %= this._rows.length; - let row = this.getRow(i); - if (this.isSelectable(i) && row.getName().toLowerCase().indexOf(char) == 0) { - if (i != this.selection.focused) { - this.ensureRowIsVisible(i); - await this.selectWait(i); - } - break; - } - } - } - else { - for (let i = 0; i < this._rows.length; i++) { - let row = this.getRow(i); - if (this.isSelectable(i) && row.getName().toLowerCase().indexOf(this._typingString) == 0) { - if (i != this.selection.focused) { - this.ensureRowIsVisible(i); - await this.selectWait(i); - } - break; - } - } - } - clearTimeout(this._typingTimeout); - this._typingTimeout = setTimeout(() => { - this._typingString = ""; - }, TYPING_TIMEOUT); - } - + async commitEditingName() { let treeRow = this._editing; if (!treeRow.editingName) return; @@ -385,6 +339,8 @@ var CollectionTree = class CollectionTree extends LibraryTree { isContainerEmpty: this.isContainerEmpty, isContainerOpen: this.isContainerOpen, toggleOpenState: this.toggleOpenState, + getRowString: this.getRowString.bind(this), + onItemContextMenu: (e) => this.props.onContextMenu && this.props.onContextMenu(e), onKeyDown: this.handleKeyDown, @@ -1112,6 +1068,10 @@ var CollectionTree = class CollectionTree extends LibraryTree { return this.getRow(this.selection.focused).editable; } + getRowString(index) { + return this.getRow(index).getName(); + } + /** * Return libraryID of selected row (which could be a collection, etc.) */ diff --git a/chrome/content/zotero/components/virtualized-table.jsx b/chrome/content/zotero/components/virtualized-table.jsx index 800ef59cf4..1193e8f5e2 100644 --- a/chrome/content/zotero/components/virtualized-table.jsx +++ b/chrome/content/zotero/components/virtualized-table.jsx @@ -33,6 +33,7 @@ const Draggable = require('./draggable'); const { injectIntl } = require('react-intl'); const { IconDownChevron, getDOMElement } = require('components/icons'); +const TYPING_TIMEOUT = 1000; const DEFAULT_ROW_HEIGHT = 20; // px const RESIZER_WIDTH = 5; // px @@ -286,6 +287,7 @@ class VirtualizedTable extends React.Component { this.state = { resizing: null }; + this._typingString = ""; this._jsWindowID = `virtualized-table-list-${Zotero.Utilities.randomString(5)}`; this._containerWidth = props.containerWidth || window.innerWidth; @@ -405,6 +407,10 @@ class VirtualizedTable extends React.Component { isContainerEmpty: PropTypes.func, isContainerOpen: PropTypes.func, toggleOpenState: PropTypes.func, + + // A function with signature (index:Number) => result:String which will be used + // for find-as-you-type navigation. Find-as-you-type is disabled if prop is undefined. + getRowString: PropTypes.func, // If you want to perform custom key handling it should be in this function // if it returns false then virtualized-table's own key handler won't run @@ -559,10 +565,16 @@ class VirtualizedTable extends React.Component { break; case " ": - this.onSelection(this.selection.focused, false, true); + if (this._typingString.length <= 0) { + this.onSelection(this.selection.focused, false, true); + return; + } break; } - + + if (this.props.getRowString && !(e.ctrlKey || e.metaKey) && e.key.length == 1) { + this._handleTyping(e.key); + } if (shiftSelect || movePivot) return; @@ -597,6 +609,48 @@ class VirtualizedTable extends React.Component { } } + _handleTyping = (char) => { + char = char.toLowerCase(); + this._typingString += char; + let allSameChar = true; + for (let i = this._typingString.length - 1; i >= 0; i--) { + if (char != this._typingString[i]) { + allSameChar = false; + break; + } + } + const rowCount = this.props.getRowCount(); + if (allSameChar) { + for (let i = this.selection.pivot + 1, checked = 0; checked < rowCount; i++, checked++) { + i %= rowCount; + let rowString = this.props.getRowString(i); + if (rowString.toLowerCase().indexOf(char) == 0) { + if (i != this.selection.pivot) { + this.scrollToRow(i); + this.onSelection(i); + } + break; + } + } + } + else { + for (let i = 0; i < rowCount; i++) { + let rowString = this.props.getRowString(i); + if (rowString.toLowerCase().indexOf(this._typingString) == 0) { + if (i != this.selection.pivot) { + this.scrollToRow(i); + this.onSelection(i); + } + break; + } + } + } + clearTimeout(this._typingTimeout); + this._typingTimeout = setTimeout(() => { + this._typingString = ""; + }, TYPING_TIMEOUT); + } + _onDragStart = () => { this._isMouseDrag = true; } diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index 724cfb8f63..5b61aa80cc 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -37,7 +37,6 @@ const { COLUMNS } = require('./itemTreeColumns'); const { Cc, Ci, Cu } = require('chrome'); Cu.import("resource://gre/modules/osfile.jsm"); -const TYPING_TIMEOUT = 1000; const CHILD_INDENT = 12; const COLORED_TAGS_RE = new RegExp("^[0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1}$"); const COLUMN_PREFS_FILEPATH = OS.Path.join(Zotero.Profile.dir, "treePrefs.json"); @@ -88,7 +87,6 @@ var ItemTree = class ItemTree extends LibraryTree { this.type = 'item'; this.name = 'ItemTree'; - this._typingString = ""; this._skipKeypress = false; this._initialized = false; @@ -824,47 +822,6 @@ var ItemTree = class ItemTree extends LibraryTree { this.selection.selectEventsSuppressed = false; } } - - handleTyping(char) { - char = char.toLowerCase(); - this._typingString += char; - let allSameChar = true; - for (let i = this._typingString.length - 1; i >= 0; i--) { - if (char != this._typingString[i]) { - allSameChar = false; - break; - } - } - if (allSameChar) { - for (let i = this.selection.pivot + 1, checked = 0; checked < this._rows.length; i++, checked++) { - i %= this._rows.length; - let row = this.getRow(i); - if (row.getField('title').toLowerCase().indexOf(char) == 0) { - if (i != this.selection.pivot) { - this.ensureRowIsVisible(i); - this.selectItem([row.ref.id]); - } - break; - } - } - } - else { - for (let i = 0; i < this._rows.length; i++) { - let row = this.getRow(i); - if (row.getField('title').toLowerCase().indexOf(this._typingString) == 0) { - if (i != this.selection.pivot) { - this.ensureRowIsVisible(i); - this.selectItem([row.ref.id]); - } - break; - } - } - } - clearTimeout(this._typingTimeout); - this._typingTimeout = setTimeout(() => { - this._typingString = ""; - }, TYPING_TIMEOUT); - } handleActivate = (event, indices) => { // Ignore double-clicks in duplicates view on everything except attachments @@ -932,10 +889,6 @@ var ItemTree = class ItemTree extends LibraryTree { this.collapseAllRows(); return false; } - else if (!(event.ctrlKey || event.metaKey || event.altKey) && event.key.length == 1 && (event.key != " " || this._typingString.length > 1)) { - this.handleTyping(event.key); - return false; - } return true; } @@ -998,6 +951,8 @@ var ItemTree = class ItemTree extends LibraryTree { isContainerOpen: this.isContainerOpen, toggleOpenState: this.toggleOpenState, + getRowString: this.getRowString.bind(this), + onDragOver: e => this.props.dragAndDrop && this.onDragOver(e, -1), onDrop: e => this.props.dragAndDrop && this.onDrop(e, -1), onKeyDown: this.handleKeyDown, @@ -1718,6 +1673,10 @@ var ItemTree = class ItemTree extends LibraryTree { return this._getRowData(index)[column]; } + getRowString(index) { + return this.getCellText(index, this.getSortField()) + } + async deleteSelection(force) { if (arguments.length > 1) { throw new Error("ItemTree.deleteSelection() no longer takes two parameters"); diff --git a/chrome/content/zotero/locateManager.jsx b/chrome/content/zotero/locateManager.jsx index 74bfc6dfcf..0297e6eb08 100644 --- a/chrome/content/zotero/locateManager.jsx +++ b/chrome/content/zotero/locateManager.jsx @@ -51,6 +51,7 @@ function init() { multiSelect={true} columns={columns} disableFontSizeScaling={true} + getRowString={index => getRowData(index).name} onActivate={handleActivate} /> diff --git a/chrome/content/zotero/preferences/preferences_cite.jsx b/chrome/content/zotero/preferences/preferences_cite.jsx index f492a6df7f..bf07acbf89 100644 --- a/chrome/content/zotero/preferences/preferences_cite.jsx +++ b/chrome/content/zotero/preferences/preferences_cite.jsx @@ -129,6 +129,7 @@ Zotero_Preferences.Cite = { disableFontSizeScaling={true} onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined} onKeyDown={handleKeyDown} + getRowString={index => styles[index].title} /> ); diff --git a/chrome/content/zotero/preferences/preferences_export.jsx b/chrome/content/zotero/preferences/preferences_export.jsx index 4d59c33d8a..6176f2f951 100644 --- a/chrome/content/zotero/preferences/preferences_export.jsx +++ b/chrome/content/zotero/preferences/preferences_export.jsx @@ -316,6 +316,7 @@ Zotero_Preferences.Export = { disableFontSizeScaling={true} onSelectionChange={handleSelectionChange} onKeyDown={handleKeyDown} + getRowString={index => this._rows[index].domain} onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()} /> diff --git a/chrome/content/zotero/preferences/preferences_sync.jsx b/chrome/content/zotero/preferences/preferences_sync.jsx index 456ac5d2c7..af8a17ff99 100644 --- a/chrome/content/zotero/preferences/preferences_sync.jsx +++ b/chrome/content/zotero/preferences/preferences_sync.jsx @@ -322,6 +322,7 @@ Zotero_Preferences.Sync = { showHeader={true} columns={columns} staticColumns={true} + getRowString={index => this._rows[index].name} disableFontSizeScaling={true} onKeyDown={handleKeyDown} />