diff --git a/chrome/content/zotero/elements/zoteroSearch.js b/chrome/content/zotero/elements/zoteroSearch.js index 45740f1921..018525878d 100644 --- a/chrome/content/zotero/elements/zoteroSearch.js +++ b/chrome/content/zotero/elements/zoteroSearch.js @@ -958,12 +958,14 @@ var annotationConditionsMenu = this.querySelector('#annotation-conditions-menu'); var conditions = Zotero.SearchConditions.getStandardConditions(); - // Cache the (alphabetically sorted) condition list and set up - // find-as-you-type on the closed menu - this._conditions = conditions; - this._typeAheadBuffer = ''; - this._typeAheadTime = 0; - conditionsMenu.addEventListener('keydown', event => this.handleConditionKeyDown(event), true); + // Most conditions are in submenus, so the menulist's own find-as-you-type can't + // reach them. Match on the full name rather than the short label the attachment + // and annotation submenus show, since the full name is what the menulist shows + // once the condition is selected. + Zotero.Utilities.Internal.addMenuFindAsYouType( + conditionsMenu.menupopup, + item => Zotero.SearchConditions.getLocalizedName(item.value) + ); // Label the submenus and seed the top-level entries with them, so the // headings sort alphabetically alongside the primary conditions @@ -1681,70 +1683,6 @@ return false; } - // Find-as-you-type on the closed conditions menu. The native incremental - // find within the open popup only matches the visible top-level items, so - // this handles typing while the menu is closed to reach any condition, - // including the ~67 in the "More" submenu. - handleConditionKeyDown(event) { - var menu = this.querySelector('#conditionsmenu'); - // Let the native incremental find handle typing while the popup is open, - // and ignore in-progress IME composition (event.key is "Process") - if (menu.open || event.isComposing || event.ctrlKey || event.metaKey || event.altKey) { - return; - } - // Only act on a single printable character. Count code points rather - // than UTF-16 units so supplementary-plane characters (e.g. some CJK - // extension blocks) aren't treated as multi-key sequences. - if (Array.from(event.key).length != 1) { - return; - } - - var now = Date.now(); - // Start a new search if enough time has passed since the last keystroke - if (now - this._typeAheadTime > 1000) { - this._typeAheadBuffer = ''; - } - this._typeAheadTime = now; - - // With no search in progress, a space opens the menu instead of starting - // a search, matching the native menulist behavior - if (event.key == ' ' && !this._typeAheadBuffer) { - return; - } - - this._typeAheadBuffer += event.key.toLowerCase(); - - var conditionName = this.findConditionByPrefix(); - if (conditionName) { - event.preventDefault(); - event.stopPropagation(); - this.onConditionSelected(conditionName); - } - } - - findConditionByPrefix() { - var buffer = this._typeAheadBuffer; - var conditions = this._conditions; - - // When the same character is typed repeatedly, cycle through the - // matching conditions, starting after the currently selected one - var cycling = buffer.length > 1 && [...buffer].every(c => c == buffer[0]); - var prefix = cycling ? buffer[0] : buffer; - - var startIndex = 0; - if (cycling) { - startIndex = conditions.findIndex(c => c.name == this.selectedCondition) + 1; - } - - for (let i = 0; i < conditions.length; i++) { - let condition = conditions[(startIndex + i) % conditions.length]; - if (condition.localized.toLowerCase().startsWith(prefix)) { - return condition.name; - } - } - return null; - } - onRemoveClicked(event) { if (this.parent) { // A keyboard-synthesized click has detail 0, unlike a mouse click diff --git a/test/tests/advancedSearchTest.js b/test/tests/advancedSearchTest.js index 279a278c5f..1b0989a83b 100644 --- a/test/tests/advancedSearchTest.js +++ b/test/tests/advancedSearchTest.js @@ -1110,6 +1110,27 @@ describe("Advanced Search", function () { assert.equal(searchCondition.selectedCondition, 'language'); }); + it("should select an attachment condition by its full name", function () { + var s = new Zotero.Search(); + s.libraryID = Zotero.Libraries.userLibraryID; + s.addCondition('title', 'is', ''); + pane.search = s; + + var searchCondition = conditions.firstChild; + var conditionsMenu = searchCondition.querySelector('#conditionsmenu'); + + // The Attachment submenu shows a short label, but the menulist shows the + // full name, which is what gets typed + let menuitem = conditionsMenu.querySelector('menuitem[value="fileTypeID"]'); + assert.notEqual( + menuitem.label, + Zotero.SearchConditions.getLocalizedName('fileTypeID') + ); + // The spaces in the name would otherwise open the menu (see customElements.js) + typeInMenu(conditionsMenu, 'attachment file t'); + assert.equal(searchCondition.selectedCondition, 'fileTypeID'); + }); + it("should cycle through matches when the same letter is typed repeatedly", function () { var s = new Zotero.Search(); s.libraryID = Zotero.Libraries.userLibraryID;