mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added global search input debounce and loading indicator
This commit is contained in:
parent
2c1f20969a
commit
c617190905
@ -1,8 +1,9 @@
|
|||||||
|
import {htmlToDom} from "../services/dom";
|
||||||
|
import {debounce} from "../services/util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends {Component}
|
* @extends {Component}
|
||||||
*/
|
*/
|
||||||
import {htmlToDom} from "../services/dom";
|
|
||||||
|
|
||||||
class GlobalSearch {
|
class GlobalSearch {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@ -10,23 +11,43 @@ class GlobalSearch {
|
|||||||
this.input = this.$refs.input;
|
this.input = this.$refs.input;
|
||||||
this.suggestions = this.$refs.suggestions;
|
this.suggestions = this.$refs.suggestions;
|
||||||
this.suggestionResultsWrap = this.$refs.suggestionResults;
|
this.suggestionResultsWrap = this.$refs.suggestionResults;
|
||||||
|
this.loadingWrap = this.$refs.loading;
|
||||||
|
|
||||||
this.setupListeners();
|
this.setupListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupListeners() {
|
setupListeners() {
|
||||||
|
const updateSuggestionsDebounced = debounce(this.updateSuggestions.bind(this), 200, false);
|
||||||
|
|
||||||
|
// Handle search input changes
|
||||||
this.input.addEventListener('input', () => {
|
this.input.addEventListener('input', () => {
|
||||||
const value = this.input.value;
|
const value = this.input.value;
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
this.updateSuggestions(value);
|
this.loadingWrap.style.display = 'block';
|
||||||
|
this.suggestionResultsWrap.style.opacity = '0.5';
|
||||||
|
updateSuggestionsDebounced(value);
|
||||||
} else {
|
} else {
|
||||||
this.hideSuggestions();
|
this.hideSuggestions();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Allow double click to show auto-click suggestions
|
||||||
|
this.input.addEventListener('dblclick', () => {
|
||||||
|
this.input.setAttribute('autocomplete', 'on');
|
||||||
|
this.input.blur();
|
||||||
|
this.input.focus();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} search
|
||||||
|
*/
|
||||||
async updateSuggestions(search) {
|
async updateSuggestions(search) {
|
||||||
const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5});
|
const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5});
|
||||||
|
if (!this.input.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const resultDom = htmlToDom(results);
|
const resultDom = htmlToDom(results);
|
||||||
|
|
||||||
const childrenToTrim = Array.from(resultDom.children).slice(9);
|
const childrenToTrim = Array.from(resultDom.children).slice(9);
|
||||||
@ -35,6 +56,8 @@ class GlobalSearch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.suggestionResultsWrap.innerHTML = '';
|
this.suggestionResultsWrap.innerHTML = '';
|
||||||
|
this.suggestionResultsWrap.style.opacity = '1';
|
||||||
|
this.loadingWrap.style.display = 'none';
|
||||||
this.suggestionResultsWrap.append(resultDom);
|
this.suggestionResultsWrap.append(resultDom);
|
||||||
if (!this.container.classList.contains('search-active')) {
|
if (!this.container.classList.contains('search-active')) {
|
||||||
this.showSuggestions();
|
this.showSuggestions();
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
* N milliseconds. If `immediate` is passed, trigger the function on the
|
* N milliseconds. If `immediate` is passed, trigger the function on the
|
||||||
* leading edge, instead of the trailing.
|
* leading edge, instead of the trailing.
|
||||||
* @attribution https://davidwalsh.name/javascript-debounce-function
|
* @attribution https://davidwalsh.name/javascript-debounce-function
|
||||||
* @param func
|
* @param {Function} func
|
||||||
* @param wait
|
* @param {Number} wait
|
||||||
* @param immediate
|
* @param {Boolean} immediate
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function debounce(func, wait, immediate) {
|
export function debounce(func, wait, immediate) {
|
||||||
|
@ -131,7 +131,7 @@ header .search-box {
|
|||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
margin-block-end: 0;
|
margin-inline-end: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,17 +163,23 @@ header .search-box {
|
|||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.global-search-loading {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.search-active:focus-within .global-search-suggestions {
|
header .search-box.search-active:focus-within {
|
||||||
display: block;
|
.global-search-suggestions {
|
||||||
}
|
display: block;
|
||||||
header .search-box.search-active input {
|
}
|
||||||
background-color: #EEE;
|
input {
|
||||||
color: #444;
|
background-color: #EEE;
|
||||||
border-color: #DDD;
|
color: #444;
|
||||||
}
|
border-color: #DDD;
|
||||||
header .search-box.search-active #header-search-box-button {
|
}
|
||||||
color: #444;
|
#header-search-box-button {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
|
aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
|
||||||
value="{{ $searchTerm ?? '' }}">
|
value="{{ $searchTerm ?? '' }}">
|
||||||
<div refs="global-search@suggestions" class="global-search-suggestions card">
|
<div refs="global-search@suggestions" class="global-search-suggestions card">
|
||||||
|
<div refs="global-search@loading" class="text-center px-m global-search-loading">@include('common.loading-icon')</div>
|
||||||
<div refs="global-search@suggestion-results" class="px-m"></div>
|
<div refs="global-search@suggestion-results" class="px-m"></div>
|
||||||
<button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button>
|
<button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user