X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/a6633642232efd164d4708967ab59e498fbff896..refs/pull/4525/head:/resources/js/components/auto-suggest.js diff --git a/resources/js/components/auto-suggest.js b/resources/js/components/auto-suggest.js index 68de49b4a..92a6c6af3 100644 --- a/resources/js/components/auto-suggest.js +++ b/resources/js/components/auto-suggest.js @@ -1,13 +1,15 @@ -import {escapeHtml} from "../services/util"; -import {onChildEvent} from "../services/dom"; +import {escapeHtml} from '../services/util'; +import {onChildEvent} from '../services/dom'; +import {Component} from './component'; +import {KeyboardNavigationHandler} from '../services/keyboard-navigation'; const ajaxCache = {}; /** * AutoSuggest - * @extends {Component} */ -class AutoSuggest { +export class AutoSuggest extends Component { + setup() { this.parent = this.$el.parentElement; this.container = this.$el; @@ -21,26 +23,31 @@ class AutoSuggest { } setupListeners() { + const navHandler = new KeyboardNavigationHandler( + this.list, + () => { + this.input.focus(); + setTimeout(() => this.hideSuggestions(), 1); + }, + event => { + event.preventDefault(); + this.selectSuggestion(event.target.textContent); + }, + ); + navHandler.shareHandlingToEl(this.input); + + onChildEvent(this.list, '.text-item', 'click', (event, el) => { + this.selectSuggestion(el.textContent); + }); + this.input.addEventListener('input', this.requestSuggestions.bind(this)); this.input.addEventListener('focus', this.requestSuggestions.bind(this)); + this.input.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this)); this.input.addEventListener('keydown', event => { if (event.key === 'Tab') { this.hideSuggestions(); } }); - - this.input.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this)); - this.container.addEventListener('keydown', this.containerKeyDown.bind(this)); - - onChildEvent(this.list, 'button', 'click', (event, el) => { - this.selectSuggestion(el.textContent); - }); - onChildEvent(this.list, 'button', 'keydown', (event, el) => { - if (event.key === 'Enter') { - this.selectSuggestion(el.textContent); - } - }); - } selectSuggestion(value) { @@ -52,50 +59,16 @@ class AutoSuggest { this.hideSuggestions(); } - containerKeyDown(event) { - if (event.key === 'Enter') event.preventDefault(); - if (this.list.classList.contains('hidden')) return; - - // Down arrow - if (event.key === 'ArrowDown') { - this.moveFocus(true); - event.preventDefault(); - } - // Up Arrow - else if (event.key === 'ArrowUp') { - this.moveFocus(false); - event.preventDefault(); - } - // Escape key - else if (event.key === 'Escape') { - this.hideSuggestions(); - event.preventDefault(); - } - } - - moveFocus(forward = true) { - const focusables = Array.from(this.container.querySelectorAll('input,button')); - const index = focusables.indexOf(document.activeElement); - const newFocus = focusables[index + (forward ? 1 : -1)]; - if (newFocus) { - newFocus.focus() - } - } - async requestSuggestions() { if (Date.now() - this.lastPopulated < 50) { return; } const nameFilter = this.getNameFilterIfNeeded(); - const search = this.input.value.slice(0, 3).toLowerCase(); + const search = this.input.value.toLowerCase(); const suggestions = await this.loadSuggestions(search, nameFilter); - let toShow = suggestions.slice(0, 6); - if (search.length > 0) { - toShow = suggestions.filter(val => { - return val.toLowerCase().includes(search); - }).slice(0, 6); - } + + const toShow = suggestions.filter(val => search === '' || val.toLowerCase().startsWith(search)).slice(0, 10); this.displaySuggestions(toShow); } @@ -111,6 +84,9 @@ class AutoSuggest { * @returns {Promise} */ async loadSuggestions(search, nameFilter = null) { + // Truncate search to prevent over numerous lookups + search = search.slice(0, 4); + const params = {search, name: nameFilter}; const cacheKey = `${this.url}:${JSON.stringify(params)}`; @@ -128,12 +104,15 @@ class AutoSuggest { */ displaySuggestions(suggestions) { if (suggestions.length === 0) { - return this.hideSuggestions(); + this.hideSuggestions(); + return; } - this.list.innerHTML = suggestions.map(value => `
  • `).join(''); + // This used to use