mirror of
https://github.com/zotero/zotero.git
synced 2026-10-04 02:31:47 +00:00
Advanced search: Use the shared find-as-you-type for the conditions menu
The conditions menu had its own find-as-you-type, which Utilities.Internal.addMenuFindAsYouType() now provides for any menu. Matching walks the menu rather than a cached list of every condition, so typing can no longer select a condition the menu doesn't offer -- Collection and Saved Search are removed when the search spans multiple libraries. An attachment or annotation condition is shown with a short label inside its submenu, so pass the full name to match on, which is what the menulist shows once the condition is selected.
This commit is contained in:
parent
c8b841df8a
commit
fa94212496
2 changed files with 29 additions and 70 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue