X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/851ab47f8adb2d04555c28fceae371e038d90a40..refs/pull/3878/head:/resources/js/components/global-search.js diff --git a/resources/js/components/global-search.js b/resources/js/components/global-search.js index 0af3ed375..7bc8a1d45 100644 --- a/resources/js/components/global-search.js +++ b/resources/js/components/global-search.js @@ -1,42 +1,66 @@ -/** - * @extends {Component} - */ import {htmlToDom} from "../services/dom"; +import {debounce} from "../services/util"; +import {KeyboardNavigationHandler} from "../services/keyboard-navigation"; +import {Component} from "./component"; -class GlobalSearch { +/** + * Global (header) search box handling. + * Mainly to show live results preview. + */ +export class GlobalSearch extends Component { setup() { this.container = this.$el; this.input = this.$refs.input; this.suggestions = this.$refs.suggestions; this.suggestionResultsWrap = this.$refs.suggestionResults; + this.loadingWrap = this.$refs.loading; + this.button = this.$refs.button; this.setupListeners(); } setupListeners() { - this.hideOnOuterEventListener = this.hideOnOuterEventListener.bind(this); + const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false); + // Handle search input changes this.input.addEventListener('input', () => { const value = this.input.value; if (value.length > 0) { - this.updateSuggestions(value); + this.loadingWrap.style.display = 'block'; + this.suggestionResultsWrap.style.opacity = '0.5'; + updateSuggestionsDebounced(value); } else { this.hideSuggestions(); } }); + + // Allow double click to show auto-click suggestions + this.input.addEventListener('dblclick', () => { + this.input.setAttribute('autocomplete', 'on'); + this.button.focus(); + this.input.focus(); + }); + + new KeyboardNavigationHandler(this.container, () => { + this.hideSuggestions(); + }); } + /** + * @param {String} search + */ async updateSuggestions(search) { - const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5}); - const resultDom = htmlToDom(results); - - const childrenToTrim = Array.from(resultDom.children).slice(9); - for (const child of childrenToTrim) { - child.remove(); + const {data: results} = await window.$http.get('/search/suggest', {term: search}); + if (!this.input.value) { + return; } + + const resultDom = htmlToDom(results); this.suggestionResultsWrap.innerHTML = ''; + this.suggestionResultsWrap.style.opacity = '1'; + this.loadingWrap.style.display = 'none'; this.suggestionResultsWrap.append(resultDom); if (!this.container.classList.contains('search-active')) { this.showSuggestions(); @@ -45,8 +69,6 @@ class GlobalSearch { showSuggestions() { this.container.classList.add('search-active'); - document.addEventListener('click', this.hideOnOuterEventListener); - document.addEventListener('focusin', this.hideOnOuterEventListener); window.requestAnimationFrame(() => { this.suggestions.classList.add('search-suggestions-animation'); }) @@ -56,15 +78,5 @@ class GlobalSearch { this.container.classList.remove('search-active'); this.suggestions.classList.remove('search-suggestions-animation'); this.suggestionResultsWrap.innerHTML = ''; - document.removeEventListener('click', this.hideOnOuterEventListener); - document.removeEventListener('focusin', this.hideOnOuterEventListener); } - - hideOnOuterEventListener(event) { - if (!this.container.contains(event.target)) { - this.hideSuggestions(); - } - }; -} - -export default GlobalSearch; \ No newline at end of file +} \ No newline at end of file