]> BookStack Code Mirror - bookstack/blob - resources/js/components/global-search.js
Extracted keyboard nav. from dropdowns to share w/ search
[bookstack] / resources / js / components / global-search.js
1 import {htmlToDom} from "../services/dom";
2 import {debounce} from "../services/util";
3 import {KeyboardNavigationHandler} from "../services/keyboard-navigation";
4
5 /**
6  * @extends {Component}
7  */
8 class GlobalSearch {
9
10     setup() {
11         this.container = this.$el;
12         this.input = this.$refs.input;
13         this.suggestions = this.$refs.suggestions;
14         this.suggestionResultsWrap = this.$refs.suggestionResults;
15         this.loadingWrap = this.$refs.loading;
16         this.button = this.$refs.button;
17
18         this.setupListeners();
19     }
20
21     setupListeners() {
22         const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false);
23
24         // Handle search input changes
25         this.input.addEventListener('input', () => {
26             const value = this.input.value;
27             if (value.length > 0) {
28                 this.loadingWrap.style.display = 'block';
29                 this.suggestionResultsWrap.style.opacity = '0.5';
30                 updateSuggestionsDebounced(value);
31             }  else {
32                 this.hideSuggestions();
33             }
34         });
35
36         // Allow double click to show auto-click suggestions
37         this.input.addEventListener('dblclick', () => {
38             this.input.setAttribute('autocomplete', 'on');
39             this.button.focus();
40             this.input.focus();
41         });
42
43         new KeyboardNavigationHandler(this.container, () => {
44             this.hideSuggestions();
45         });
46     }
47
48     /**
49      * @param {String} search
50      */
51     async updateSuggestions(search) {
52         const {data: results} = await window.$http.get('/search/suggest', {term: search});
53         if (!this.input.value) {
54             return;
55         }
56         
57         const resultDom = htmlToDom(results);
58
59         this.suggestionResultsWrap.innerHTML = '';
60         this.suggestionResultsWrap.style.opacity = '1';
61         this.loadingWrap.style.display = 'none';
62         this.suggestionResultsWrap.append(resultDom);
63         if (!this.container.classList.contains('search-active')) {
64             this.showSuggestions();
65         }
66     }
67
68     showSuggestions() {
69         this.container.classList.add('search-active');
70         window.requestAnimationFrame(() => {
71             this.suggestions.classList.add('search-suggestions-animation');
72         })
73     }
74
75     hideSuggestions() {
76         this.container.classList.remove('search-active');
77         this.suggestions.classList.remove('search-suggestions-animation');
78         this.suggestionResultsWrap.innerHTML = '';
79     }
80 }
81
82 export default GlobalSearch;