import BasicWidget from "./basic_widget.js"; import server from "../services/server.js"; import linkService from "../services/link.js"; import froca from "../services/froca.js"; import utils from "../services/utils.js"; import appContext from "../components/app_context.js"; import shortcutService from "../services/shortcuts.js"; const TPL = ` `; const MAX_DISPLAYED_NOTES = 15; export default class QuickSearchWidget extends BasicWidget { doRender() { this.$widget = $(TPL); this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); this.$searchString = this.$widget.find('.search-string'); this.$dropdownMenu = this.$widget.find('.dropdown-menu'); this.$widget.find('.input-group-prepend').on('shown.bs.dropdown', () => this.search()); if (utils.isMobile()) { this.$searchString.keydown(e => { if (e.which === 13) { if (this.$dropdownMenu.is(":visible")) { this.search(); // just update already visible dropdown } else { this.dropdown.show(); } e.preventDefault(); e.stopPropagation(); } }) } shortcutService.bindElShortcut(this.$searchString, 'return', () => { if (this.$dropdownMenu.is(":visible")) { this.search(); // just update already visible dropdown } else { this.dropdown.show(); } this.$searchString.focus(); }); shortcutService.bindElShortcut(this.$searchString, 'down', () => { this.$dropdownMenu.find('.dropdown-item:first').focus(); }); shortcutService.bindElShortcut(this.$searchString, 'esc', () => { this.dropdown.hide(); }); return this.$widget; } async search() { const searchString = this.$searchString.val().trim(); if (!searchString) { this.dropdown.hide(); return; } this.$dropdownMenu.empty(); this.$dropdownMenu.append(' Searching ...'); const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`); if (error) { let tooltip = new bootstrap.Tooltip(this.$searchString, { trigger: 'manual', title: `Search error: ${error}`, placement: 'right' }); tooltip.show(); setTimeout(() => tooltip.dispose(), 4000); } const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length)); this.$dropdownMenu.empty(); if (displayedNoteIds.length === 0) { this.$dropdownMenu.append('No results found'); } for (const note of await froca.getNotes(displayedNoteIds)) { const $link = await linkService.createLink(note.noteId, { showNotePath: true }); $link.addClass('dropdown-item'); $link.attr("tabIndex", "0"); $link.on('click', e => { this.dropdown.hide(); if (!e.target || e.target.nodeName !== 'A') { // click on the link is handled by link handling, but we want the whole item clickable appContext.tabManager.getActiveContext().setNote(note.noteId); } }); shortcutService.bindElShortcut($link, 'return', () => { this.dropdown.hide(); appContext.tabManager.getActiveContext().setNote(note.noteId); }); this.$dropdownMenu.append($link); } if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) { this.$dropdownMenu.append(`... and ${searchResultNoteIds.length - MAX_DISPLAYED_NOTES} more results.`); } const $showInFullButton = $('') .append($('')); this.$dropdownMenu.append($showInFullButton); $showInFullButton.on('click', () => this.showInFullSearch()); shortcutService.bindElShortcut($showInFullButton, 'return', () => this.showInFullSearch()); shortcutService.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus()); this.dropdown.update(); } async showInFullSearch() { this.dropdown.hide(); await appContext.triggerCommand('searchNotes', { searchString: this.$searchString.val() }); } quickSearchEvent() { this.$searchString.focus(); } }