From 828871dfb119098202eb9312a92fb1334b3016ff Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Thu, 28 Mar 2024 15:32:13 -0400 Subject: [PATCH 1/8] Basic infinite scroll for search results --- src/lib/api/documents.ts | 22 +++++-- src/lib/api/types.d.ts | 9 ++- src/lib/components/Search.svelte | 12 ++-- .../components/documents/ResultsList.svelte | 60 +++++++++++++---- .../stories/ResultsList.stories.svelte | 18 ++---- src/routes/app/+page.svelte | 64 ++----------------- src/routes/app/+page.ts | 25 +++++++- src/style/kit.css | 18 ++++++ 8 files changed, 130 insertions(+), 98 deletions(-) diff --git a/src/lib/api/documents.ts b/src/lib/api/documents.ts index 3dfb4baaa..b6b88f99f 100644 --- a/src/lib/api/documents.ts +++ b/src/lib/api/documents.ts @@ -1,7 +1,7 @@ /** API helpers related to documents. * Lots of duplicated code here that should get consolidated at some point. */ -import type { Document, sizes } from "./types"; +import type { Document, DocumentResults, SearchOptions, sizes } from "./types"; import { error } from "@sveltejs/kit"; @@ -10,17 +10,29 @@ import { isOrg } from "@/api/types/orgAndUser"; import { APP_URL, BASE_API_URL } from "@/config/config.js"; import { isErrorCode } from "../utils"; -/** Search documents */ +/** + * Search documents + * https://www.documentcloud.org/help/search/ + * + * */ export async function search( query = "", - highlight = false, + options: SearchOptions = { + hl: Boolean(query), + per_page: 25, + cursor: "", + version: "2.0", + }, fetch = globalThis.fetch, -) { +): Promise { const endpoint = new URL("documents/search/", BASE_API_URL); endpoint.searchParams.set("expand", DEFAULT_EXPAND); endpoint.searchParams.set("q", query); - endpoint.searchParams.set("hl", String(highlight)); + + for (const [k, v] of Object.entries(options)) { + endpoint.searchParams.set(k, String(v)); + } const resp = await fetch(endpoint, { credentials: "include" }); diff --git a/src/lib/api/types.d.ts b/src/lib/api/types.d.ts index d686d6680..474da49ee 100644 --- a/src/lib/api/types.d.ts +++ b/src/lib/api/types.d.ts @@ -105,7 +105,6 @@ export interface Document { note_highlights?: Record; } -// export type DocumentResults = Page; export interface DocumentResults extends Page {} export interface Note { @@ -135,6 +134,14 @@ export interface Section { export type SectionResults = Page
; +export interface SearchOptions { + hl?: boolean; + per_page?: number; + cursor?: string; + expand?: string; + version?: number | string; +} + export interface OEmbed { version: "1.0"; provider_name: "DocumentCloud"; diff --git a/src/lib/components/Search.svelte b/src/lib/components/Search.svelte index b60959d78..a6e59aa50 100644 --- a/src/lib/components/Search.svelte +++ b/src/lib/components/Search.svelte @@ -1,6 +1,5 @@ -
+ + {/if} + diff --git a/src/lib/components/documents/stories/ResultsList.stories.svelte b/src/lib/components/documents/stories/ResultsList.stories.svelte index 886307168..360d7dc3b 100644 --- a/src/lib/components/documents/stories/ResultsList.stories.svelte +++ b/src/lib/components/documents/stories/ResultsList.stories.svelte @@ -1,11 +1,13 @@ -
+
-
+
diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 08702a3fd..2a1f76f74 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -6,35 +6,11 @@ import PageToolbar from "$lib/components/common/PageToolbar.svelte"; import Search from "$lib/components/Search.svelte"; import Empty from "$lib/components/common/Empty.svelte"; - import Paginator from "@/common/Paginator.svelte"; - import type { DocumentResults } from "@/lib/api/types"; - export let data: { - query: string; - searchResults: Promise; - }; - - let page = 1; - let per_page = 25; - let error: Error; + export let data; $: searchResults = data.searchResults; $: query = data.query; - - async function load(url) { - const res = await fetch(url, { credentials: "include" }).catch((e) => { - error = e; - throw e; // if something went wrong here, something broke - }); - - if (!res.ok) { - // 404 or something similar - console.error(res.statusText); - error = { name: "Loading error", message: res.statusText }; - } - - data.searchResults = res.json(); - } @@ -44,7 +20,11 @@ {#await searchResults} Loading… {:then results} - + {/await} @@ -52,37 +32,5 @@ Select all - - - {#await searchResults then sr} - {@const count = sr.count} - {@const total_pages = Math.ceil(count / per_page)} - {@const next = sr.next} - {@const previous = sr.previous} - { - page = Math.min(total_pages, page + 1); - load(next); - }} - on:previous={(e) => { - page = Math.max(1, page - 1); - load(previous); - }} - /> - {/await} - - - diff --git a/src/routes/app/+page.ts b/src/routes/app/+page.ts index 2798376c6..cf9984f05 100644 --- a/src/routes/app/+page.ts +++ b/src/routes/app/+page.ts @@ -1,11 +1,30 @@ -import { search } from "$lib/api/documents.js"; +import type { SearchOptions } from "$lib/api/types"; +import { search } from "$lib/api/documents"; export async function load({ url, fetch }) { const query = url.searchParams.get("q") || ""; - const searchResults = search(query, true, fetch); + const per_page = +url.searchParams.get("per_page") || 25; + const cursor = url.searchParams.get("cursor") || ""; + + const options: SearchOptions = { + hl: Boolean(query), + version: "2.0", + }; + + if (per_page) { + options.per_page = per_page; + } + + if (cursor) { + options.cursor = cursor; + } + + const searchResults = search(query, options, fetch); return { - searchResults, query, + per_page, + cursor, + searchResults, }; } diff --git a/src/style/kit.css b/src/style/kit.css index d0b920685..1965f00cd 100644 --- a/src/style/kit.css +++ b/src/style/kit.css @@ -139,3 +139,21 @@ dd { font-size: var(--font-s, 0.875rem); font-weight: var(--font-regular, 400); } + +/* +Utility classes +*/ + +/* https://css-tricks.com/inclusively-hidden/ + * Hiding class, making content visible only to screen readers but not visually + * "sr" meaning "screen-reader" + */ +.sr-only:not(:focus):not(:active) { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} From d94710a632c5013f00ac8ae0430a5a46d4635379 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Thu, 28 Mar 2024 22:10:12 -0400 Subject: [PATCH 2/8] intersection observer --- .../components/documents/ResultsList.svelte | 35 ++++++++++++++++++- src/routes/app/+page.svelte | 1 + 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/lib/components/documents/ResultsList.svelte b/src/lib/components/documents/ResultsList.svelte index a1a679fe3..ffc3dd0eb 100644 --- a/src/lib/components/documents/ResultsList.svelte +++ b/src/lib/components/documents/ResultsList.svelte @@ -8,6 +8,7 @@
@@ -56,7 +89,7 @@

{$_("noDocuments.queryNoResults")}

{/each} -
+
{#if next} {/if} diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 2a1f76f74..8fb0cb914 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -24,6 +24,7 @@ results={results.results} count={results.count} next={results.next} + auto /> {/await} From c20023168662a66e59e9eb7c30b0ba53d9d7a570 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Thu, 28 Mar 2024 22:17:12 -0400 Subject: [PATCH 3/8] disable button while loading --- src/lib/components/documents/ResultsList.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/documents/ResultsList.svelte b/src/lib/components/documents/ResultsList.svelte index ffc3dd0eb..c85707982 100644 --- a/src/lib/components/documents/ResultsList.svelte +++ b/src/lib/components/documents/ResultsList.svelte @@ -91,7 +91,9 @@ {/each}
{#if next} - + {/if}
From 0577b6f189cdd49c135c48e990c8c6e9fa21a6ee Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Thu, 28 Mar 2024 22:22:26 -0400 Subject: [PATCH 4/8] Only watch in auto mode --- src/lib/components/documents/ResultsList.svelte | 12 ++++++++---- .../documents/stories/ResultsList.stories.svelte | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/components/documents/ResultsList.svelte b/src/lib/components/documents/ResultsList.svelte index c85707982..f07bfa7e6 100644 --- a/src/lib/components/documents/ResultsList.svelte +++ b/src/lib/components/documents/ResultsList.svelte @@ -42,7 +42,7 @@ count = r.count; next = r.next; loading = false; - watch(); + if (auto) watch(); } function watch() { @@ -91,9 +91,13 @@ {/each}
{#if next} - + {/if}
diff --git a/src/lib/components/documents/stories/ResultsList.stories.svelte b/src/lib/components/documents/stories/ResultsList.stories.svelte index 360d7dc3b..9e31ad2a5 100644 --- a/src/lib/components/documents/stories/ResultsList.stories.svelte +++ b/src/lib/components/documents/stories/ResultsList.stories.svelte @@ -24,3 +24,7 @@
+ + +
+
From 83b535eccb09fddc41a1a88c9b3b980cab119d7b Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Fri, 29 Mar 2024 10:57:23 -0400 Subject: [PATCH 5/8] Connect results to toolbar and implement select-all --- .../components/documents/ResultsList.svelte | 36 +++++++++++++------ src/routes/app/+page.svelte | 34 ++++++++++++++++-- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/lib/components/documents/ResultsList.svelte b/src/lib/components/documents/ResultsList.svelte index f07bfa7e6..2143cf802 100644 --- a/src/lib/components/documents/ResultsList.svelte +++ b/src/lib/components/documents/ResultsList.svelte @@ -2,7 +2,12 @@ import { writable, type Writable } from "svelte/store"; import Button from "../common/Button.svelte"; - export const selected: Writable<(number | string)[]> = writable([]); + // IDs might be strings or numbers, depending on the API endpoint + // enforce type consistency here to avoid comparison bugs later + export const selected: Writable = writable([]); + export let visible: Writable> = writable(new Set()); + + export let total: Writable = writable(0); @@ -79,7 +89,11 @@ diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 8fb0cb914..45118d32c 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -1,7 +1,11 @@ @@ -30,8 +42,24 @@ + + + {#if $visible && $total} + Showing {$visible.size} of {$total} results + {/if} + From a958e760b7cf344d1c69a2a67f0c20792bb0a332 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Fri, 29 Mar 2024 11:06:05 -0400 Subject: [PATCH 6/8] locale strings because we can --- src/routes/app/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 45118d32c..ff28f8ddc 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -50,7 +50,7 @@ on:change={selectAll} /> {#if $selected.length > 0} - {$selected.length} selected + {$selected.length.toLocaleString()} selected {:else} Select all {/if} @@ -58,7 +58,7 @@ {#if $visible && $total} - Showing {$visible.size} of {$total} results + Showing {$visible.size.toLocaleString()} of {$total.toLocaleString()} results {/if} From 249223ac961ac6b7afc5f8d8b9bbc42b9e967d15 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Fri, 29 Mar 2024 16:57:28 -0400 Subject: [PATCH 7/8] style tweaks --- src/config/config.js | 1 + .../components/documents/ResultsList.svelte | 2 +- src/routes/app/+page.svelte | 18 ++++++++++++++++-- src/routes/app/+page.ts | 4 +++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 73ff826d5..473668561 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -63,6 +63,7 @@ export const IMAGE_WIDTHS = IMAGE_WIDTHS_ENTRIES.map(([name, width]) => [ export const IMAGE_WIDTHS_MAP = new Map(IMAGE_WIDTHS_ENTRIES); +export const DEFAULT_PER_PAGE = 25; export const MAX_PER_PAGE = 100; export const PDF_SIZE_LIMIT = 525336576; diff --git a/src/lib/components/documents/ResultsList.svelte b/src/lib/components/documents/ResultsList.svelte index 2143cf802..59a7c10c5 100644 --- a/src/lib/components/documents/ResultsList.svelte +++ b/src/lib/components/documents/ResultsList.svelte @@ -1,6 +1,5 @@