mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
(search) use debounce to prevent multiple requests while typing
This commit is contained in:
parent
6a576b6c7a
commit
9318d64e44
@ -1,31 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
const search = ref('');
|
||||
import { useDebounce } from '@vueuse/core';
|
||||
|
||||
const isSearchBlank = computed(() => {
|
||||
return search.value.trim().length === 0;
|
||||
});
|
||||
const query = ref('');
|
||||
const debouncedQuery = useDebounce(query);
|
||||
|
||||
const searchAsyncData = useAsyncData('search', async () => {
|
||||
if (isSearchBlank.value) {
|
||||
if (debouncedQuery.value.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return await $fetch('/api/search', {
|
||||
query: {
|
||||
text: search.value,
|
||||
query: debouncedQuery.value,
|
||||
},
|
||||
});
|
||||
}, {
|
||||
watch: [search],
|
||||
watch: [debouncedQuery],
|
||||
});
|
||||
|
||||
const isLoading = computed(() => {
|
||||
return query.value !== debouncedQuery.value || searchAsyncData.status.value === 'pending';
|
||||
});
|
||||
|
||||
const hasNoResults = computed(() => {
|
||||
return searchAsyncData.status.value !== 'pending' && searchAsyncData.data.value?.length === 0;
|
||||
return !isLoading.value && searchAsyncData.data.value?.length === 0;
|
||||
});
|
||||
|
||||
const dialogue = useTemplateRef('dialogue');
|
||||
|
||||
const open = () => {
|
||||
search.value = '';
|
||||
query.value = '';
|
||||
searchAsyncData.clear();
|
||||
dialogue.value?.showModal();
|
||||
};
|
||||
@ -59,11 +62,11 @@ defineExpose({ open, close });
|
||||
<dialog ref="dialogue" class="container m-auto h-100 rounded border">
|
||||
<div class="input-group mb-4">
|
||||
<span class="input-group-text">
|
||||
<Spinner v-if="searchAsyncData.status.value === 'pending'" size="1.25em" />
|
||||
<Spinner v-if="isLoading" size="1.25em" />
|
||||
<Icon v-else v="search" />
|
||||
</span>
|
||||
<input
|
||||
v-model="search"
|
||||
v-model="query"
|
||||
type="search"
|
||||
class="form-control border-primary"
|
||||
:placeholder="$t('crud.filterLong')"
|
||||
@ -72,7 +75,7 @@ defineExpose({ open, close });
|
||||
<Icon v="times" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isSearchBlank" class="alert alert-info">
|
||||
<div v-if="query.trim().length === 0" class="alert alert-info">
|
||||
<T>search.noInput</T>
|
||||
</div>
|
||||
<ul
|
||||
@ -88,7 +91,7 @@ defineExpose({ open, close });
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else-if="hasNoResults" class="alert alert-info">
|
||||
<T :params="{ search }">search.noResults</T>
|
||||
<T :params="{ query }">search.noResults</T>
|
||||
</div>
|
||||
</dialog>
|
||||
</template>
|
||||
|
@ -951,7 +951,7 @@ crud:
|
||||
search:
|
||||
header: 'Search'
|
||||
noInput: 'Start searching by entering a search term'
|
||||
noResults: 'No results for “%search%” found'
|
||||
noResults: 'No results for “%query%” found'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
|
@ -931,7 +931,7 @@ crud:
|
||||
search:
|
||||
header: 'Suche'
|
||||
noInput: 'Starte eine Suche durch Eingabe eines Suchbegriffs'
|
||||
noResults: 'Keine Ergebnisse für „%search%“ gefunden'
|
||||
noResults: 'Keine Ergebnisse für „%query%“ gefunden'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
|
@ -1153,7 +1153,7 @@ crud:
|
||||
search:
|
||||
header: 'Search'
|
||||
noInput: 'Start searching by entering a search term'
|
||||
noResults: 'No results for “%search%” found'
|
||||
noResults: 'No results for “%query%” found'
|
||||
|
||||
footer:
|
||||
license: >
|
||||
|
@ -28,6 +28,7 @@
|
||||
"@sentry/vue": "^7.109.0",
|
||||
"@vite-pwa/nuxt": "^0.10.1",
|
||||
"@vuepic/vue-datepicker": "^8.8.1",
|
||||
"@vueuse/core": "^12.2.0",
|
||||
"abort-controller": "^3.0.0",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"avris-columnist": "^0.3.4",
|
||||
|
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@ -47,6 +47,9 @@ importers:
|
||||
'@vuepic/vue-datepicker':
|
||||
specifier: ^8.8.1
|
||||
version: 8.8.1(vue@3.5.13(typescript@5.6.2))
|
||||
'@vueuse/core':
|
||||
specifier: ^12.2.0
|
||||
version: 12.2.0(typescript@5.6.2)
|
||||
abort-controller:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@ -2892,6 +2895,9 @@ packages:
|
||||
'@types/uuid@8.3.2':
|
||||
resolution: {integrity: sha512-u40ViizKDmdl5FhOXn9WQbulpigYCaiD5hD4KqR3xyQww6l3+0ND+A9TeFla8tFpqvR+UAkJdYb/8jdaQG4/nw==}
|
||||
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
@ -3152,6 +3158,15 @@ packages:
|
||||
peerDependencies:
|
||||
vue: '>=3.2.0'
|
||||
|
||||
'@vueuse/core@12.2.0':
|
||||
resolution: {integrity: sha512-jksyNu+5EGwggNkRWd6xX+8qBkYbmrwdFQMgCABsz+wq8bKF6w3soPFLB8vocFp3wFIzn0OYkSPM9JP+AFKwsg==}
|
||||
|
||||
'@vueuse/metadata@12.2.0':
|
||||
resolution: {integrity: sha512-x6zynZtTh1l52m0y8d/EgzpshnMjg8cNZ2KWoncJ62Z5qPSGoc4FUunmMVrrRM/I/5542rTEY89CGftngZvrkQ==}
|
||||
|
||||
'@vueuse/shared@12.2.0':
|
||||
resolution: {integrity: sha512-SRr4AZwv/giS+EmyA1ZIzn3/iALjjnWAGaBNmoDTMEob9JwQaevAocuaMDnPAvU7Z35Y5g3CFRusCWgp1gVJ3Q==}
|
||||
|
||||
'@webassemblyjs/ast@1.12.1':
|
||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||
|
||||
@ -11992,6 +12007,8 @@ snapshots:
|
||||
|
||||
'@types/uuid@8.3.2': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 20.16.5
|
||||
@ -12442,6 +12459,23 @@ snapshots:
|
||||
date-fns: 3.6.0
|
||||
vue: 3.5.13(typescript@5.6.2)
|
||||
|
||||
'@vueuse/core@12.2.0(typescript@5.6.2)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.20
|
||||
'@vueuse/metadata': 12.2.0
|
||||
'@vueuse/shared': 12.2.0(typescript@5.6.2)
|
||||
vue: 3.5.13(typescript@5.6.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/metadata@12.2.0': {}
|
||||
|
||||
'@vueuse/shared@12.2.0(typescript@5.6.2)':
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.6.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@webassemblyjs/ast@1.12.1':
|
||||
dependencies:
|
||||
'@webassemblyjs/helper-numbers': 1.11.6
|
||||
|
@ -76,10 +76,10 @@ const loadIndices = async (
|
||||
return new Map(indices.map((loadedKind) => [loadedKind.kind.kind, loadedKind]));
|
||||
};
|
||||
|
||||
const searchIndices = (indices: Map<SearchDocument['kind'], LoadedSearchKind>, text: string): SearchResult[] => {
|
||||
const searchIndices = (indices: Map<SearchDocument['kind'], LoadedSearchKind>, query: string): SearchResult[] => {
|
||||
return [...indices.values()]
|
||||
.flatMap(({ index }) => {
|
||||
return index.search(text, { prefix: true, fuzzy: 1 });
|
||||
return index.search(query, { prefix: true, fuzzy: 1 });
|
||||
})
|
||||
.toSorted((resultA, resultB) => {
|
||||
return resultB.score - resultA.score;
|
||||
@ -442,9 +442,8 @@ const SEARCH_LIMIT = 20;
|
||||
export default defineEventHandler(async (event) => {
|
||||
const indices = await loadIndices(kinds, global.config);
|
||||
|
||||
const query = getQuery(event);
|
||||
const text = query.text as string;
|
||||
return searchIndices(indices, text)
|
||||
const query = getQuery(event).query as string;
|
||||
return searchIndices(indices, query)
|
||||
.slice(0, SEARCH_LIMIT)
|
||||
.map((result) => transformResult(indices, result));
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user