From 688e37bd9d911ed4e19a8b287364f3eae68cee48 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 13:34:09 +0100 Subject: [PATCH 01/20] Add useSearchRequest for request handling --- packages/supersearch/.env.example | 1 + .../src/lib/components/SuperSearch.svelte | 26 ++++++++++- .../supersearch/src/lib/types/superSearch.ts | 1 + .../src/lib/utils/useSearchRequest.svelte.ts | 46 +++++++++++++++++++ packages/supersearch/src/routes/+page.svelte | 25 ++++++++-- 5 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 packages/supersearch/.env.example create mode 100644 packages/supersearch/src/lib/types/superSearch.ts create mode 100644 packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts diff --git a/packages/supersearch/.env.example b/packages/supersearch/.env.example new file mode 100644 index 000000000..94da2bd3b --- /dev/null +++ b/packages/supersearch/.env.example @@ -0,0 +1 @@ +PUBLIC_ENDPOINT_URL= \ No newline at end of file diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 997d30f65..2cecb6b15 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -5,6 +5,8 @@ import { type LanguageSupport } from '@codemirror/language'; import submitFormOnEnterKey from '$lib/extensions/submitFormOnEnterKey.js'; import preventNewLine from '$lib/extensions/preventNewLine.js'; + import useSearchRequest from '$lib/utils/useSearchRequest.svelte.js'; + import type { QueryFunction } from '$lib/types/superSearch.js'; interface Props { name: string; @@ -12,9 +14,19 @@ form?: string; language?: LanguageSupport; placeholder?: string; + endpoint: string; + queryFn?: QueryFunction; } - let { name, value = $bindable(''), form, language, placeholder = '' }: Props = $props(); + let { + name, + value = $bindable(''), + form, + language, + placeholder = '', + endpoint, + queryFn = (value) => new URLSearchParams({ q: value }) + }: Props = $props(); let collapsedEditorView: EditorView | undefined = $state(); let expandedEditorView: EditorView | undefined = $state(); @@ -23,6 +35,17 @@ let placeholderCompartment = new Compartment(); let prevPlaceholder = placeholder; + let search = useSearchRequest({ + endpoint, + queryFn + }); + + $effect(() => { + if (value) { + search.fetchData(value); + } + }); + const extensions = [ submitFormOnEnterKey(form), preventNewLine({ replaceWithSpace: true }), @@ -93,4 +116,5 @@ bind:editorView={expandedEditorView} syncedEditorView={collapsedEditorView} /> +
{JSON.stringify(search.data, null, 2)}
diff --git a/packages/supersearch/src/lib/types/superSearch.ts b/packages/supersearch/src/lib/types/superSearch.ts new file mode 100644 index 000000000..ec1c83df3 --- /dev/null +++ b/packages/supersearch/src/lib/types/superSearch.ts @@ -0,0 +1 @@ +export type QueryFunction = (value: string) => URLSearchParams; diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts new file mode 100644 index 000000000..8463afee7 --- /dev/null +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -0,0 +1,46 @@ +import type { QueryFunction } from '$lib/types/superSearch.js'; + +export function useSearchRequest({ + endpoint, + queryFn +}: { + endpoint: string; + queryFn: QueryFunction; +}) { + let isLoading = $state(false); + let error: string | undefined = $state(); + let data = $state(); + + async function fetchData(query: string) { + try { + isLoading = true; + error = undefined; + + const response = await fetch(`${endpoint}?${queryFn(query).toString()}`); + data = await response.json(); + } catch (err) { + if (err instanceof Error) { + error = 'Failed to fetch data: ' + err.message; + } else { + error = 'Failed to fetch data'; + } + } finally { + isLoading = false; + } + } + + return { + fetchData, + get isLoading() { + return isLoading; + }, + get error() { + return error; + }, + get data() { + return data; + } + }; +} + +export default useSearchRequest; diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 27799f04f..c1fa764d7 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -1,21 +1,38 @@
Supersearch inside <form> element - + + new URLSearchParams({ + _q: query, + _limit: '10' + })} + />
Supersearch using form attribute - +
From 8427d177bc2e16fdc36ff7c0cd6ed86875729fc2 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 13:43:59 +0100 Subject: [PATCH 02/20] Add debouncing --- .../supersearch/src/lib/components/SuperSearch.svelte | 9 ++++++--- packages/supersearch/src/lib/utils/debounce.ts | 10 ++++++++++ .../src/lib/utils/useSearchRequest.svelte.ts | 8 +++++++- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 packages/supersearch/src/lib/utils/debounce.ts diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 2cecb6b15..5221aaa99 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -16,6 +16,7 @@ placeholder?: string; endpoint: string; queryFn?: QueryFunction; + debouncedWait?: number; } let { @@ -25,7 +26,8 @@ language, placeholder = '', endpoint, - queryFn = (value) => new URLSearchParams({ q: value }) + queryFn = (value) => new URLSearchParams({ q: value }), + debouncedWait }: Props = $props(); let collapsedEditorView: EditorView | undefined = $state(); @@ -37,12 +39,13 @@ let search = useSearchRequest({ endpoint, - queryFn + queryFn, + debouncedWait }); $effect(() => { if (value) { - search.fetchData(value); + search.debouncedFetchData(value); } }); diff --git a/packages/supersearch/src/lib/utils/debounce.ts b/packages/supersearch/src/lib/utils/debounce.ts new file mode 100644 index 000000000..87a353a44 --- /dev/null +++ b/packages/supersearch/src/lib/utils/debounce.ts @@ -0,0 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +function debounce(callback: Function, wait = 300) { + let timeout: ReturnType; + return (...args: unknown[]) => { + clearTimeout(timeout); + timeout = setTimeout(() => callback(...args), wait); + }; +} + +export default debounce; diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts index 8463afee7..506e9fb75 100644 --- a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -1,11 +1,14 @@ import type { QueryFunction } from '$lib/types/superSearch.js'; +import debounce from '$lib/utils/debounce.js'; export function useSearchRequest({ endpoint, - queryFn + queryFn, + debouncedWait }: { endpoint: string; queryFn: QueryFunction; + debouncedWait?: number; }) { let isLoading = $state(false); let error: string | undefined = $state(); @@ -29,8 +32,11 @@ export function useSearchRequest({ } } + const debouncedFetchData = debounce((query: string) => fetchData(query), debouncedWait); + return { fetchData, + debouncedFetchData, get isLoading() { return isLoading; }, From 6c8b1bc70ef060229763ad615a1b4c9b3164e48e Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 13:52:49 +0100 Subject: [PATCH 03/20] Add optional transform function prop --- .../src/lib/components/SuperSearch.svelte | 7 +++++-- packages/supersearch/src/lib/types/superSearch.ts | 1 + .../src/lib/utils/useSearchRequest.svelte.ts | 8 ++++++-- packages/supersearch/src/routes/+page.svelte | 13 +++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 5221aaa99..04053402b 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -6,7 +6,7 @@ import submitFormOnEnterKey from '$lib/extensions/submitFormOnEnterKey.js'; import preventNewLine from '$lib/extensions/preventNewLine.js'; import useSearchRequest from '$lib/utils/useSearchRequest.svelte.js'; - import type { QueryFunction } from '$lib/types/superSearch.js'; + import type { QueryFunction, TransformFunction } from '$lib/types/superSearch.js'; interface Props { name: string; @@ -16,6 +16,7 @@ placeholder?: string; endpoint: string; queryFn?: QueryFunction; + transformFn?: TransformFunction; debouncedWait?: number; } @@ -27,7 +28,8 @@ placeholder = '', endpoint, queryFn = (value) => new URLSearchParams({ q: value }), - debouncedWait + debouncedWait, + transformFn }: Props = $props(); let collapsedEditorView: EditorView | undefined = $state(); @@ -40,6 +42,7 @@ let search = useSearchRequest({ endpoint, queryFn, + transformFn, debouncedWait }); diff --git a/packages/supersearch/src/lib/types/superSearch.ts b/packages/supersearch/src/lib/types/superSearch.ts index ec1c83df3..45a83f571 100644 --- a/packages/supersearch/src/lib/types/superSearch.ts +++ b/packages/supersearch/src/lib/types/superSearch.ts @@ -1 +1,2 @@ export type QueryFunction = (value: string) => URLSearchParams; +export type TransformFunction = (data: unknown) => unknown; diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts index 506e9fb75..202c7700b 100644 --- a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -1,13 +1,15 @@ -import type { QueryFunction } from '$lib/types/superSearch.js'; +import type { QueryFunction, TransformFunction } from '$lib/types/superSearch.js'; import debounce from '$lib/utils/debounce.js'; export function useSearchRequest({ endpoint, queryFn, + transformFn, debouncedWait }: { endpoint: string; queryFn: QueryFunction; + transformFn?: TransformFunction; debouncedWait?: number; }) { let isLoading = $state(false); @@ -20,7 +22,9 @@ export function useSearchRequest({ error = undefined; const response = await fetch(`${endpoint}?${queryFn(query).toString()}`); - data = await response.json(); + const jsonResponse = await response.json(); + + data = transformFn?.(jsonResponse) || jsonResponse; } catch (err) { if (err instanceof Error) { error = 'Failed to fetch data: ' + err.message; diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index c1fa764d7..c4ee1080a 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -4,6 +4,18 @@ let value1 = $state(''); let value2 = $state(''); let placeholder = $state('Search'); + + function handleTransform(data: unknown) { + return { + '@id': data['@id'], + totalItems: data.totalItems, + items: data.items.map((item) => ({ + '@id': item?.['@id'], + heading: `${item?.hasTitle?.[0]?.mainTitle}` + })), + '@context': data['@context'] + }; + }
@@ -19,6 +31,7 @@ _q: query, _limit: '10' })} + transformFn={handleTransform} />
From da33b06cd914e910d1d0a850b0e12eb768aa96d2 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 14:06:31 +0100 Subject: [PATCH 04/20] Add support for paginated data --- .../src/lib/components/SuperSearch.svelte | 19 ++++++--- .../supersearch/src/lib/types/superSearch.ts | 4 ++ .../src/lib/utils/useSearchRequest.svelte.ts | 41 +++++++++++++++++-- packages/supersearch/src/routes/+page.svelte | 12 ++++++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 04053402b..2a3f58bc1 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -6,7 +6,11 @@ import submitFormOnEnterKey from '$lib/extensions/submitFormOnEnterKey.js'; import preventNewLine from '$lib/extensions/preventNewLine.js'; import useSearchRequest from '$lib/utils/useSearchRequest.svelte.js'; - import type { QueryFunction, TransformFunction } from '$lib/types/superSearch.js'; + import type { + QueryFunction, + PaginationQueryFunction, + TransformFunction + } from '$lib/types/superSearch.js'; interface Props { name: string; @@ -16,8 +20,8 @@ placeholder?: string; endpoint: string; queryFn?: QueryFunction; + paginationQueryFn?: PaginationQueryFunction; transformFn?: TransformFunction; - debouncedWait?: number; } let { @@ -28,7 +32,7 @@ placeholder = '', endpoint, queryFn = (value) => new URLSearchParams({ q: value }), - debouncedWait, + paginationQueryFn, transformFn }: Props = $props(); @@ -42,8 +46,8 @@ let search = useSearchRequest({ endpoint, queryFn, - transformFn, - debouncedWait + paginationQueryFn, + transformFn }); $effect(() => { @@ -122,5 +126,8 @@ bind:editorView={expandedEditorView} syncedEditorView={collapsedEditorView} /> -
{JSON.stringify(search.data, null, 2)}
+
{JSON.stringify(search.paginatedData || search.data, null, 2)}
+ {#if search.hasMorePaginatedData} + + {/if} diff --git a/packages/supersearch/src/lib/types/superSearch.ts b/packages/supersearch/src/lib/types/superSearch.ts index 45a83f571..3650a83b1 100644 --- a/packages/supersearch/src/lib/types/superSearch.ts +++ b/packages/supersearch/src/lib/types/superSearch.ts @@ -1,2 +1,6 @@ export type QueryFunction = (value: string) => URLSearchParams; +export type PaginationQueryFunction = ( + searchParams: URLSearchParams, + data?: unknown +) => URLSearchParams | undefined; export type TransformFunction = (data: unknown) => unknown; diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts index 202c7700b..92dfb1be5 100644 --- a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -1,30 +1,42 @@ -import type { QueryFunction, TransformFunction } from '$lib/types/superSearch.js'; +import type { + QueryFunction, + PaginationQueryFunction, + TransformFunction +} from '$lib/types/superSearch.js'; import debounce from '$lib/utils/debounce.js'; export function useSearchRequest({ endpoint, queryFn, + paginationQueryFn, transformFn, debouncedWait }: { endpoint: string; queryFn: QueryFunction; + paginationQueryFn?: PaginationQueryFunction; transformFn?: TransformFunction; debouncedWait?: number; }) { let isLoading = $state(false); let error: string | undefined = $state(); let data = $state(); + let paginatedData = $state(); + let moreSearchParams: URLSearchParams | undefined = $state(); + const hasMorePaginatedData = $derived(!!moreSearchParams); - async function fetchData(query: string) { + async function _fetchData(searchParams: URLSearchParams) { try { isLoading = true; error = undefined; - const response = await fetch(`${endpoint}?${queryFn(query).toString()}`); + const response = await fetch(`${endpoint}?${searchParams.toString()}`); const jsonResponse = await response.json(); - data = transformFn?.(jsonResponse) || jsonResponse; + const _data = transformFn?.(jsonResponse) || jsonResponse; + moreSearchParams = paginationQueryFn?.(searchParams, _data); + + return _data; } catch (err) { if (err instanceof Error) { error = 'Failed to fetch data: ' + err.message; @@ -36,11 +48,26 @@ export function useSearchRequest({ } } + async function fetchData(query: string) { + data = await _fetchData(queryFn(query)); + if (paginationQueryFn) { + paginatedData = [data]; + } + } + const debouncedFetchData = debounce((query: string) => fetchData(query), debouncedWait); + async function fetchMoreData() { + if (moreSearchParams) { + const moreData = await _fetchData(moreSearchParams); + paginatedData = [...((Array.isArray(paginatedData) && paginatedData) || []), moreData]; + } + } + return { fetchData, debouncedFetchData, + fetchMoreData, get isLoading() { return isLoading; }, @@ -49,6 +76,12 @@ export function useSearchRequest({ }, get data() { return data; + }, + get paginatedData() { + return paginatedData; + }, + get hasMorePaginatedData() { + return hasMorePaginatedData; } }; } diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index c4ee1080a..1eeccfec3 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -5,6 +5,17 @@ let value2 = $state(''); let placeholder = $state('Search'); + function handlePaginationQuery(searchParams: URLSearchParams, prevData: unknown) { + const paginatedSearchParams = new URLSearchParams(Array.from(searchParams.entries())); + const limit = parseInt(searchParams.get('_limit')!, 10); + const offset = limit + parseInt(searchParams.get('_offset') || '0', 10); + if (offset + limit < prevData?.totalItems) { + paginatedSearchParams.set('_offset', offset.toString()); + return paginatedSearchParams; + } + return undefined; + } + function handleTransform(data: unknown) { return { '@id': data['@id'], @@ -31,6 +42,7 @@ _q: query, _limit: '10' })} + paginationQueryFn={handlePaginationQuery} transformFn={handleTransform} /> From dea26ff2f6c00227f8ab4294835e08842ac26eb2 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 14:24:45 +0100 Subject: [PATCH 05/20] Cancel pending fetches when fetching new data --- .../supersearch/src/lib/utils/useSearchRequest.svelte.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts index 92dfb1be5..edce9e9ce 100644 --- a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -25,12 +25,19 @@ export function useSearchRequest({ let moreSearchParams: URLSearchParams | undefined = $state(); const hasMorePaginatedData = $derived(!!moreSearchParams); + let controller: AbortController; + async function _fetchData(searchParams: URLSearchParams) { try { isLoading = true; error = undefined; - const response = await fetch(`${endpoint}?${searchParams.toString()}`); + controller?.abort(); + controller = new AbortController(); + + const response = await fetch(`${endpoint}?${searchParams.toString()}`, { + signal: controller.signal + }); const jsonResponse = await response.json(); const _data = transformFn?.(jsonResponse) || jsonResponse; From 9c3eb1237154ea529f0c9317e14353a0cac60712 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 15:00:10 +0100 Subject: [PATCH 06/20] Add snippet prop for result items --- .../src/lib/components/SuperSearch.svelte | 44 ++++++++++++++++--- .../supersearch/src/lib/types/superSearch.ts | 13 +++++- packages/supersearch/src/routes/+page.svelte | 29 +++++++++++- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 2a3f58bc1..01eba79ac 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -1,4 +1,5 @@ +{#snippet fallbackResultItem(item: ResultItem)} + {JSON.stringify(item)} +{/snippet} + -
{JSON.stringify(search.paginatedData || search.data, null, 2)}
- {#if search.hasMorePaginatedData} - - {/if} + + + diff --git a/packages/supersearch/src/lib/types/superSearch.ts b/packages/supersearch/src/lib/types/superSearch.ts index 3650a83b1..00b73a4d5 100644 --- a/packages/supersearch/src/lib/types/superSearch.ts +++ b/packages/supersearch/src/lib/types/superSearch.ts @@ -3,4 +3,15 @@ export type PaginationQueryFunction = ( searchParams: URLSearchParams, data?: unknown ) => URLSearchParams | undefined; -export type TransformFunction = (data: unknown) => unknown; +export type TransformFunction = (data: unknown) => SearchQueryResult; + +export interface ResultItem { + id: string; + heading: string; +} + +export interface SearchQueryResult { + '@id'?: string; + context?: string; + items: ResultItem[]; +} diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 1eeccfec3..1a5e186c8 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -1,6 +1,7 @@
@@ -37,14 +25,13 @@ name="q" bind:value={value1} {placeholder} - endpoint={new URL(PUBLIC_ENDPOINT_URL)} + endpoint="/api/find" queryFn={(query) => new URLSearchParams({ _q: query, _limit: '10' })} paginationQueryFn={handlePaginationQuery} - transformFn={handleTransform} > {#snippet resultItem(item)} + {/if} diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 98f2f1911..68bc92c59 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -34,7 +34,7 @@ paginationQueryFn={handlePaginationQuery} > {#snippet resultItem(item)} - {/snippet} @@ -59,7 +59,7 @@ paginationQueryFn={handlePaginationQuery} > {#snippet resultItem(item)} - {/snippet} From a7e0ab6b63b8e01455b0071ded7d1f6c526cd026 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 16:43:55 +0100 Subject: [PATCH 12/20] Add basic supersearch request handling in lxl-web --- lxl-web/src/lib/components/Search.svelte | 27 ++++++++++++++++++- .../api/[[lang=lang]]/supersearch/+server.ts | 25 +++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 lxl-web/src/routes/api/[[lang=lang]]/supersearch/+server.ts diff --git a/lxl-web/src/lib/components/Search.svelte b/lxl-web/src/lib/components/Search.svelte index 38625cbd5..c5db77d4d 100644 --- a/lxl-web/src/lib/components/Search.svelte +++ b/lxl-web/src/lib/components/Search.svelte @@ -42,6 +42,18 @@ q = q.trim(); } } + + function handlePaginationQuery(searchParams: URLSearchParams, prevData: unknown) { + const paginatedSearchParams = new URLSearchParams(Array.from(searchParams.entries())); + const limit = parseInt(searchParams.get('_limit')!, 10); + const offset = limit + parseInt(searchParams.get('_offset') || '0', 10); + + if (prevData && offset < prevData.totalItems) { + paginatedSearchParams.set('_offset', offset.toString()); + return paginatedSearchParams; + } + return undefined; + } @@ -51,7 +63,20 @@ bind:value={q} language={lxlQuery} placeholder={$page.data.t('search.search')} - /> + endpoint={'/api/supersearch'} + queryFn={(query) => + new URLSearchParams({ + _q: query, + _limit: '10' + })} + paginationQueryFn={handlePaginationQuery} + > + {#snippet resultItem(item)} + + {/snippet} + {:else} { + const displayUtil = locals.display; + const locale = getSupportedLocale(params?.lang); + + const findResponse = await fetch(`${env.API_URL}/find?${url.searchParams.toString()}`); + const data = await findResponse.json(); + + return json({ + '@id': data['@id'], + items: data.items?.map((item) => ({ + '@id': item['@id'], + '@type': item['@type'], + heading: toString(displayUtil.lensAndFormat(item, LxlLens.CardHeading, locale)) + })), + totalItems: data.totalItems, + '@context': data['@context'] + }); +}; From 391d5deed80d65654501bd399c9aaeb4dbf29cc7 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 17:04:06 +0100 Subject: [PATCH 13/20] Update readme --- packages/supersearch/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/supersearch/README.md b/packages/supersearch/README.md index ec134e847..bbcd524f5 100644 --- a/packages/supersearch/README.md +++ b/packages/supersearch/README.md @@ -16,13 +16,18 @@ To use `supersearch` in a non-Svelte project ... ## Properties -| Property | Type | Description | Default value | -| ------------- | ----------------- | ----------------------------------------------------------------- | ------------- | -| `name` | `string` | A string specifying a name for the form control. | `undefined` | -| `value` | `string` | The value that will be displayed and edited inside the component. | `""` | -| `form` | `string` | A string matching the `id` of a `` element. | `undefined` | -| `language` | `LanguageSupport` | The language extension that will parse and highlight the value. | `undefined` | -| `placeholder` | `string` | A brief hint which is shown when value is empty. | `""` | +| Property | Type | Description | Default value | +| ------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- | ------------- | +| `name` | `string` | A string specifying a name for the form control. | `undefined` | +| `value` | `string` | The value that will be displayed and edited inside the component. | `""` | +| `form` | `string` | A string matching the `id` of a `` element. | `undefined` | +| `language` | `LanguageSupport` | The language extension that will parse and highlight the value. | `undefined` | +| `placeholder` | `string` | A brief hint which is shown when value is empty. | `""` | +| `endpoint` | `string` or `URL` | The endpoint from which the component should fetch data from (used together with `queryFn`). | `undefined` | +| `queryFn` | `QueryFunction` | A function that converts `value` to `URLSearchParams` (which will be appended to the endpoint). | `undefined` | +| `paginationQueryFn` | `PaginationQueryFunction` | A function which should return `URLSearchParams` used for querying more paginated data (if available) | `undefined` | +| `transformFn` | `TransformFunction` | A generic helper function which can be used to transform data fetched from the endpoint. | `undefined` | +| `resultItem` | `Snippet<[ResultItem]>` | A [Snippet](https://svelte.dev/docs/svelte/snippet) used for customized rendering of result items. | `undefined` | ## Developing From 896e806d6fd7bfc5cad218cdd522bfdc3c125177 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Mon, 25 Nov 2024 17:17:35 +0100 Subject: [PATCH 14/20] Update readme again --- packages/supersearch/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/supersearch/README.md b/packages/supersearch/README.md index bbcd524f5..5f43ea3ec 100644 --- a/packages/supersearch/README.md +++ b/packages/supersearch/README.md @@ -28,6 +28,7 @@ To use `supersearch` in a non-Svelte project ... | `paginationQueryFn` | `PaginationQueryFunction` | A function which should return `URLSearchParams` used for querying more paginated data (if available) | `undefined` | | `transformFn` | `TransformFunction` | A generic helper function which can be used to transform data fetched from the endpoint. | `undefined` | | `resultItem` | `Snippet<[ResultItem]>` | A [Snippet](https://svelte.dev/docs/svelte/snippet) used for customized rendering of result items. | `undefined` | +| `debouncedWait` | `number` | The wait time, in milliseconds that debounce function should wait between invocated search queries. | `300` | ## Developing From 20a0b8ba83030a60035324ee5646cba4935b6666 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Tue, 26 Nov 2024 10:26:33 +0100 Subject: [PATCH 15/20] Update types --- packages/supersearch/src/lib/types/json.ts | 8 ++++++++ .../supersearch/src/lib/types/superSearch.ts | 18 ++++-------------- .../src/lib/utils/useSearchRequest.svelte.ts | 3 ++- packages/supersearch/src/routes/+page.svelte | 17 +++++++++++++++-- .../supersearch/src/routes/api/find/+server.ts | 13 +++++++------ 5 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 packages/supersearch/src/lib/types/json.ts diff --git a/packages/supersearch/src/lib/types/json.ts b/packages/supersearch/src/lib/types/json.ts new file mode 100644 index 000000000..467d6c263 --- /dev/null +++ b/packages/supersearch/src/lib/types/json.ts @@ -0,0 +1,8 @@ +type JSONPrimitive = string | number | boolean | null | undefined; + +export type JSONValue = + | JSONPrimitive + | JSONValue[] + | { + [key: string]: JSONValue; + }; diff --git a/packages/supersearch/src/lib/types/superSearch.ts b/packages/supersearch/src/lib/types/superSearch.ts index b5ddbe528..a0bd89bf8 100644 --- a/packages/supersearch/src/lib/types/superSearch.ts +++ b/packages/supersearch/src/lib/types/superSearch.ts @@ -1,18 +1,8 @@ +import type { JSONValue } from './json.js'; + export type QueryFunction = (value: string) => URLSearchParams; export type PaginationQueryFunction = ( searchParams: URLSearchParams, - data: QueryResponse + data: JSONValue ) => URLSearchParams | undefined; -export type TransformFunction = (data: unknown) => unknown; - -export interface ResultItem { - '@id'?: string; - heading: string; -} - -export interface QueryResponse { - '@id'?: string; - context?: string; - items: ResultItem[]; - totalItems: number; -} +export type TransformFunction = (data: JSONValue) => JSONValue; diff --git a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts index 0e21e845d..ec88ab085 100644 --- a/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts +++ b/packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts @@ -3,6 +3,7 @@ import type { PaginationQueryFunction, TransformFunction } from '$lib/types/superSearch.js'; +import type { JSONValue } from '$lib/types/json.js'; import debounce from '$lib/utils/debounce.js'; export function useSearchRequest({ @@ -38,7 +39,7 @@ export function useSearchRequest({ const response = await fetch(`${endpoint}?${searchParams.toString()}`, { signal: controller.signal }); - const jsonResponse = await response.json(); + const jsonResponse = (await response.json()) as JSONValue; const _data = transformFn?.(jsonResponse) || jsonResponse; moreSearchParams = paginationQueryFn?.(searchParams, _data); diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 68bc92c59..c7d420778 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -1,21 +1,33 @@ @@ -32,6 +44,7 @@ _limit: '10' })} paginationQueryFn={handlePaginationQuery} + transformFn={handleTransform} > {#snippet resultItem(item)} {/snippet} From c8fd311f90b8e1eed046968f65abed65ca385556 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Tue, 26 Nov 2024 10:53:51 +0100 Subject: [PATCH 18/20] Add test case for transformFn --- packages/supersearch/e2e/supersearch.spec.ts | 4 ++++ packages/supersearch/src/routes/+page.svelte | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/supersearch/e2e/supersearch.spec.ts b/packages/supersearch/e2e/supersearch.spec.ts index d6acc67af..9a67f12c7 100644 --- a/packages/supersearch/e2e/supersearch.spec.ts +++ b/packages/supersearch/e2e/supersearch.spec.ts @@ -84,4 +84,8 @@ test('fetches and displays paginated results', async ({ page }) => { await page.locator('.supersearch-show-more').click(); await expect(page.locator('[data-test-id="result-item"]')).toHaveCount(30); await expect(page.locator('.supersearch-show-more')).not.toBeAttached(); + await expect( + page.locator('[data-test-id="result-item"]').first(), + 'to tranform data using transformFn if available' + ).toHaveText('Heading 1 for "Hello"'); }); diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 23ad377f4..3810d859a 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -24,7 +24,7 @@ ...rest, items: items.map((item) => ({ ...item, - heading: `${item.heading} for ${value1}` + heading: `${item.heading} for "${value1}"` })) }; } From b9ebc33ca775c9e5b97d3185e4d9cd9fe16d0037 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Tue, 26 Nov 2024 10:54:16 +0100 Subject: [PATCH 19/20] Add artifical delay in test endpoint --- packages/supersearch/src/routes/api/find/+server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/supersearch/src/routes/api/find/+server.ts b/packages/supersearch/src/routes/api/find/+server.ts index e9af6949d..064d5d662 100644 --- a/packages/supersearch/src/routes/api/find/+server.ts +++ b/packages/supersearch/src/routes/api/find/+server.ts @@ -12,6 +12,8 @@ export const GET: RequestHandler = async ({ url }) => { const limit = parseInt(url.searchParams.get('_limit')!, 10); const offset = parseInt(url.searchParams.get('_offset') || '0', 10); + await new Promise((resolve) => setTimeout(resolve, 200)); + return json({ '@id': `/api/find?${url.searchParams.toString()}`, items: MOCK_ITEMS_DATA.slice(offset, offset + limit).map((item) => ({ From 3a9d99c50cae3cbfba1146dd98ae312d0f441a11 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Tue, 26 Nov 2024 13:17:09 +0100 Subject: [PATCH 20/20] Add conditional check for heading Fixes https://github.com/libris/lxlviewer/pull/1178/files#r1858217914 --- lxl-web/src/lib/components/Search.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxl-web/src/lib/components/Search.svelte b/lxl-web/src/lib/components/Search.svelte index c5db77d4d..8d4ebf161 100644 --- a/lxl-web/src/lib/components/Search.svelte +++ b/lxl-web/src/lib/components/Search.svelte @@ -73,7 +73,7 @@ > {#snippet resultItem(item)} {/snippet}