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