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">
|
<script setup lang="ts">
|
||||||
const search = ref('');
|
import { useDebounce } from '@vueuse/core';
|
||||||
|
|
||||||
const isSearchBlank = computed(() => {
|
const query = ref('');
|
||||||
return search.value.trim().length === 0;
|
const debouncedQuery = useDebounce(query);
|
||||||
});
|
|
||||||
|
|
||||||
const searchAsyncData = useAsyncData('search', async () => {
|
const searchAsyncData = useAsyncData('search', async () => {
|
||||||
if (isSearchBlank.value) {
|
if (debouncedQuery.value.trim().length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return await $fetch('/api/search', {
|
return await $fetch('/api/search', {
|
||||||
query: {
|
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(() => {
|
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 dialogue = useTemplateRef('dialogue');
|
||||||
|
|
||||||
const open = () => {
|
const open = () => {
|
||||||
search.value = '';
|
query.value = '';
|
||||||
searchAsyncData.clear();
|
searchAsyncData.clear();
|
||||||
dialogue.value?.showModal();
|
dialogue.value?.showModal();
|
||||||
};
|
};
|
||||||
@ -59,11 +62,11 @@ defineExpose({ open, close });
|
|||||||
<dialog ref="dialogue" class="container m-auto h-100 rounded border">
|
<dialog ref="dialogue" class="container m-auto h-100 rounded border">
|
||||||
<div class="input-group mb-4">
|
<div class="input-group mb-4">
|
||||||
<span class="input-group-text">
|
<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" />
|
<Icon v-else v="search" />
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="search"
|
v-model="query"
|
||||||
type="search"
|
type="search"
|
||||||
class="form-control border-primary"
|
class="form-control border-primary"
|
||||||
:placeholder="$t('crud.filterLong')"
|
:placeholder="$t('crud.filterLong')"
|
||||||
@ -72,7 +75,7 @@ defineExpose({ open, close });
|
|||||||
<Icon v="times" />
|
<Icon v="times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isSearchBlank" class="alert alert-info">
|
<div v-if="query.trim().length === 0" class="alert alert-info">
|
||||||
<T>search.noInput</T>
|
<T>search.noInput</T>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
@ -88,7 +91,7 @@ defineExpose({ open, close });
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-else-if="hasNoResults" class="alert alert-info">
|
<div v-else-if="hasNoResults" class="alert alert-info">
|
||||||
<T :params="{ search }">search.noResults</T>
|
<T :params="{ query }">search.noResults</T>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</template>
|
</template>
|
||||||
|
@ -951,7 +951,7 @@ crud:
|
|||||||
search:
|
search:
|
||||||
header: 'Search'
|
header: 'Search'
|
||||||
noInput: 'Start searching by entering a search term'
|
noInput: 'Start searching by entering a search term'
|
||||||
noResults: 'No results for “%search%” found'
|
noResults: 'No results for “%query%” found'
|
||||||
|
|
||||||
footer:
|
footer:
|
||||||
license: >
|
license: >
|
||||||
|
@ -931,7 +931,7 @@ crud:
|
|||||||
search:
|
search:
|
||||||
header: 'Suche'
|
header: 'Suche'
|
||||||
noInput: 'Starte eine Suche durch Eingabe eines Suchbegriffs'
|
noInput: 'Starte eine Suche durch Eingabe eines Suchbegriffs'
|
||||||
noResults: 'Keine Ergebnisse für „%search%“ gefunden'
|
noResults: 'Keine Ergebnisse für „%query%“ gefunden'
|
||||||
|
|
||||||
footer:
|
footer:
|
||||||
license: >
|
license: >
|
||||||
|
@ -1153,7 +1153,7 @@ crud:
|
|||||||
search:
|
search:
|
||||||
header: 'Search'
|
header: 'Search'
|
||||||
noInput: 'Start searching by entering a search term'
|
noInput: 'Start searching by entering a search term'
|
||||||
noResults: 'No results for “%search%” found'
|
noResults: 'No results for “%query%” found'
|
||||||
|
|
||||||
footer:
|
footer:
|
||||||
license: >
|
license: >
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"@sentry/vue": "^7.109.0",
|
"@sentry/vue": "^7.109.0",
|
||||||
"@vite-pwa/nuxt": "^0.10.1",
|
"@vite-pwa/nuxt": "^0.10.1",
|
||||||
"@vuepic/vue-datepicker": "^8.8.1",
|
"@vuepic/vue-datepicker": "^8.8.1",
|
||||||
|
"@vueuse/core": "^12.2.0",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"autoprefixer": "^10.4.5",
|
"autoprefixer": "^10.4.5",
|
||||||
"avris-columnist": "^0.3.4",
|
"avris-columnist": "^0.3.4",
|
||||||
|
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@ -47,6 +47,9 @@ importers:
|
|||||||
'@vuepic/vue-datepicker':
|
'@vuepic/vue-datepicker':
|
||||||
specifier: ^8.8.1
|
specifier: ^8.8.1
|
||||||
version: 8.8.1(vue@3.5.13(typescript@5.6.2))
|
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:
|
abort-controller:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
@ -2892,6 +2895,9 @@ packages:
|
|||||||
'@types/uuid@8.3.2':
|
'@types/uuid@8.3.2':
|
||||||
resolution: {integrity: sha512-u40ViizKDmdl5FhOXn9WQbulpigYCaiD5hD4KqR3xyQww6l3+0ND+A9TeFla8tFpqvR+UAkJdYb/8jdaQG4/nw==}
|
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':
|
'@types/yauzl@2.10.3':
|
||||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
|
|
||||||
@ -3152,6 +3158,15 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: '>=3.2.0'
|
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':
|
'@webassemblyjs/ast@1.12.1':
|
||||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||||
|
|
||||||
@ -11992,6 +12007,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/uuid@8.3.2': {}
|
'@types/uuid@8.3.2': {}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.20': {}
|
||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.16.5
|
'@types/node': 20.16.5
|
||||||
@ -12442,6 +12459,23 @@ snapshots:
|
|||||||
date-fns: 3.6.0
|
date-fns: 3.6.0
|
||||||
vue: 3.5.13(typescript@5.6.2)
|
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':
|
'@webassemblyjs/ast@1.12.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@webassemblyjs/helper-numbers': 1.11.6
|
'@webassemblyjs/helper-numbers': 1.11.6
|
||||||
|
@ -76,10 +76,10 @@ const loadIndices = async (
|
|||||||
return new Map(indices.map((loadedKind) => [loadedKind.kind.kind, loadedKind]));
|
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()]
|
return [...indices.values()]
|
||||||
.flatMap(({ index }) => {
|
.flatMap(({ index }) => {
|
||||||
return index.search(text, { prefix: true, fuzzy: 1 });
|
return index.search(query, { prefix: true, fuzzy: 1 });
|
||||||
})
|
})
|
||||||
.toSorted((resultA, resultB) => {
|
.toSorted((resultA, resultB) => {
|
||||||
return resultB.score - resultA.score;
|
return resultB.score - resultA.score;
|
||||||
@ -442,9 +442,8 @@ const SEARCH_LIMIT = 20;
|
|||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const indices = await loadIndices(kinds, global.config);
|
const indices = await loadIndices(kinds, global.config);
|
||||||
|
|
||||||
const query = getQuery(event);
|
const query = getQuery(event).query as string;
|
||||||
const text = query.text as string;
|
return searchIndices(indices, query)
|
||||||
return searchIndices(indices, text)
|
|
||||||
.slice(0, SEARCH_LIMIT)
|
.slice(0, SEARCH_LIMIT)
|
||||||
.map((result) => transformResult(indices, result));
|
.map((result) => transformResult(indices, result));
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user