From f0fcdf87643c63b3b35a70627987c6ff5fc8057a Mon Sep 17 00:00:00 2001 From: liyunze <50455574+liyunze-coding@users.noreply.github.com> Date: Fri, 4 Sep 2026 13:57:39 +0800 Subject: [PATCH] added units to search results --- src/lib/components/Navbar.svelte | 4 +-- src/lib/search.ts | 12 +++++++ src/lib/server/api.ts | 53 ++++++++++++++++++---------- src/lib/types.ts | 5 ++- src/routes/notes/+page.svelte | 10 ++++-- src/routes/questions/+page.svelte | 8 +++-- src/routes/search/+page.svelte | 37 ++++++++++++++++--- src/routes/units/+page.svelte | 18 +++++++++- src/routes/units/[code]/+page.svelte | 45 ++++++++++++++++++++--- 9 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 src/lib/search.ts diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 0e45b5c..a75c9bf 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -159,7 +159,7 @@ type="search" bind:value={searchQuery} placeholder="Search" - aria-label="Search notes and questions" + aria-label="Search units, notes, and questions" class="border-rule text-ink placeholder:text-faint focus:border-primary bg-surface w-40 rounded-sm border px-2.5 py-1.5 text-[11px] tracking-wide transition-colors outline-none" /> @@ -196,7 +196,7 @@ type="search" bind:value={searchQuery} placeholder="Search" - aria-label="Search notes and questions" + aria-label="Search units, notes, and questions" class="border-rule text-ink placeholder:text-faint focus:border-primary bg-surface w-full rounded-sm border px-3 py-2 text-sm tracking-wide transition-colors outline-none" /> diff --git a/src/lib/search.ts b/src/lib/search.ts new file mode 100644 index 0000000..30dfcd2 --- /dev/null +++ b/src/lib/search.ts @@ -0,0 +1,12 @@ +import type { UnitDoc } from "$lib/types"; + +export function unitMatchesQuery( + unit: Pick, + query: string, +): boolean { + const q = query.trim().toLowerCase(); + if (!q) return true; + return [unit.code, unit.code2, unit.name, unit.description].some((value) => + value?.toLowerCase().includes(q), + ); +} diff --git a/src/lib/server/api.ts b/src/lib/server/api.ts index 45d6f94..3e08379 100644 --- a/src/lib/server/api.ts +++ b/src/lib/server/api.ts @@ -88,6 +88,20 @@ function mapQuestion(row: Record): Record { return { ...row, solved: !!row.solved }; } +function unitRecordMatchesQuery(unit: Record | null | undefined, q: string) { + if (!unit) return false; + return ( + String(unit.code).toLowerCase().includes(q) || + String(unit.code2 ?? "") + .toLowerCase() + .includes(q) || + String(unit.name).toLowerCase().includes(q) || + String(unit.description ?? "") + .toLowerCase() + .includes(q) + ); +} + function addUnits(db: Db, rows: Record[]): Record[] { const units = new Map>(); const getUnit = db.prepare( @@ -529,13 +543,11 @@ function notesSearch(db: Db, args: { query: string; limit?: number }) { .prepare(`SELECT ${NOTE_COLUMNS} FROM notes ORDER BY createdAt DESC LIMIT ?`) .all(args.limit ?? 200) as Record[]; const q = args.query.toLowerCase(); - return addUnits( - db, - all.filter( - (n) => - String(n.title).toLowerCase().includes(q) || - String(n.content).toLowerCase().includes(q), - ), + return addUnits(db, all).filter( + (n) => + String(n.title).toLowerCase().includes(q) || + String(n.content).toLowerCase().includes(q) || + unitRecordMatchesQuery(n.unit, q), ); } @@ -641,27 +653,31 @@ function questionsSearch(db: Db, args: { query: string; limit?: number }) { .prepare(`SELECT ${QUESTION_COLUMNS} FROM questions ORDER BY createdAt DESC LIMIT ?`) .all(args.limit ?? 200) as Record[]; const q = args.query.toLowerCase(); - return addUnits( - db, - all - .filter( - (qr) => - String(qr.title).toLowerCase().includes(q) || - String(qr.content).toLowerCase().includes(q), - ) - .map(mapQuestion), + return addUnits(db, all.map(mapQuestion)).filter( + (qr) => + String(qr.title).toLowerCase().includes(q) || + String(qr.content).toLowerCase().includes(q) || + unitRecordMatchesQuery(qr.unit, q), ); } +function unitsSearch(db: Db, args: { query: string; limit?: number }) { + const q = args.query.toLowerCase(); + const all = unitsGetAll(db) as Record[]; + return all.filter((unit) => unitRecordMatchesQuery(unit, q)).slice(0, args.limit ?? 200); +} + function searchAll(db: Db, args: { query: string; limit?: number }) { const limit = args.limit ?? 200; + const units = unitsSearch(db, { query: args.query, limit }) as Record[]; const notes = notesSearch(db, { query: args.query, limit }) as Record[]; const questions = questionsSearch(db, { query: args.query, limit }) as Record[]; - const combined: Record[] = [ + const posts: Record[] = [ ...notes.map((n) => ({ ...n, type: "note" })), ...questions.map((q) => ({ ...q, type: "question" })), ]; - return combined.sort((a, b) => b.createdAt - a.createdAt); + posts.sort((a, b) => b.createdAt - a.createdAt); + return [...units.map((unit) => ({ ...unit, type: "unit" })), ...posts]; } function questionsMarkSolved(db: Db, args: { token: string; id: string }) { @@ -1268,6 +1284,7 @@ const handlers: Record = { "topics:getAll": topicsGetAll, "units:getByCode": unitsGetByCode, "units:getAll": unitsGetAll, + "units:search": unitsSearch, "units:createCustom": unitsCreateCustom, "units:getPinned": unitsGetPinned, "units:pin": unitsPin, diff --git a/src/lib/types.ts b/src/lib/types.ts index af0ab8c..6cab0e2 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -55,7 +55,10 @@ export type QuestionDoc = Doc<"questions"> & { unit?: UnitDoc; }; -export type SearchResult = (NoteDoc & { type: "note" }) | (QuestionDoc & { type: "question" }); +export type SearchResult = + | (NoteDoc & { type: "note" }) + | (QuestionDoc & { type: "question" }) + | (UnitDoc & { type: "unit" }); export type CommentDoc = Doc<"comments"> & { content: string; diff --git a/src/routes/notes/+page.svelte b/src/routes/notes/+page.svelte index 97a53ff..cd78cfb 100644 --- a/src/routes/notes/+page.svelte +++ b/src/routes/notes/+page.svelte @@ -4,6 +4,7 @@ import FeedRow from "$lib/components/FeedRow.svelte"; import UnitFilter from "$lib/components/UnitFilter.svelte"; import { postPath } from "$lib/paths"; + import { unitMatchesQuery } from "$lib/search"; import { timeAgo } from "$lib/time"; import type { NoteDoc, UnitDoc } from "$lib/types"; @@ -30,7 +31,10 @@ : notes; if (q) { list = list.filter( - (n) => n.title.toLowerCase().includes(q) || n.content.toLowerCase().includes(q), + (n) => + n.title.toLowerCase().includes(q) || + n.content.toLowerCase().includes(q) || + (n.unit ? unitMatchesQuery(n.unit, q) : false), ); } if (sort === "top") list = [...list].sort((a, b) => b.voteCount - a.voteCount); @@ -52,8 +56,8 @@ diff --git a/src/routes/questions/+page.svelte b/src/routes/questions/+page.svelte index cd7785b..2970f9d 100644 --- a/src/routes/questions/+page.svelte +++ b/src/routes/questions/+page.svelte @@ -4,6 +4,7 @@ import FeedRow from "$lib/components/FeedRow.svelte"; import UnitFilter from "$lib/components/UnitFilter.svelte"; import { postPath } from "$lib/paths"; + import { unitMatchesQuery } from "$lib/search"; import { timeAgo } from "$lib/time"; import type { QuestionDoc, UnitDoc } from "$lib/types"; @@ -34,7 +35,8 @@ list = list.filter( (question) => question.title.toLowerCase().includes(q) || - question.content.toLowerCase().includes(q), + question.content.toLowerCase().includes(q) || + (question.unit ? unitMatchesQuery(question.unit, q) : false), ); } if (sort === "top") list = [...list].sort((a, b) => b.voteCount - a.voteCount); @@ -56,8 +58,8 @@ diff --git a/src/routes/search/+page.svelte b/src/routes/search/+page.svelte index 747514d..1114e00 100644 --- a/src/routes/search/+page.svelte +++ b/src/routes/search/+page.svelte @@ -2,7 +2,7 @@ import { query } from "$lib/api"; import { onMount } from "svelte"; import FeedRow from "$lib/components/FeedRow.svelte"; - import { postPath } from "$lib/paths"; + import { postPath, unitPath } from "$lib/paths"; import { timeAgo } from "$lib/time"; import type { SearchResult } from "$lib/types"; @@ -19,6 +19,9 @@ } loading = false; }); + + const unitResults = $derived(results.filter((result) => result.type === "unit")); + const postResults = $derived(results.filter((result) => result.type !== "unit")); @@ -41,7 +44,7 @@ id="q" type="search" bind:value={searchQuery} - placeholder="Search notes and questions..." + placeholder="Search units, notes, and questions..." class="field" /> @@ -49,7 +52,29 @@ {#if loading}

Loading

{:else if ranQuery} - {#each results as result} + {#if unitResults.length > 0} +

Units

+ + {/if} + + {#each postResults as result} {#if result.type === "note"} - {:else} + {:else if result.type === "question"} {/if} {:else} -

No results found for “{searchQuery}”.

+ {#if unitResults.length === 0} +

No results found for “{searchQuery}”.

+ {/if} {/each} {/if} diff --git a/src/routes/units/+page.svelte b/src/routes/units/+page.svelte index d67b999..fe098e8 100644 --- a/src/routes/units/+page.svelte +++ b/src/routes/units/+page.svelte @@ -1,11 +1,13 @@ @@ -28,13 +32,25 @@

+
+ +
+ {#if loading}

Loading

{:else if units.length === 0}

No units yet.

+ {:else if visible.length === 0} +

No units match “{searchQuery.trim()}”.

{:else} + {:else if searchQuery.trim() && visibleNotes.length === 0 && visibleQuestions.length === 0} +

+ No results in this unit for “{searchQuery.trim()}”. +

{/if} {:else}

Unit not found