mirror of
https://github.com/dsec-hub/dsec-notebook.git
synced 2026-09-22 07:24:27 +00:00
Merge pull request #17 from dsec-hub/feature/search-units
added units to search results
This commit is contained in:
commit
89e08db376
9 changed files with 154 additions and 38 deletions
|
|
@ -159,7 +159,7 @@
|
||||||
type="search"
|
type="search"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
placeholder="Search"
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
type="search"
|
type="search"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
placeholder="Search"
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
12
src/lib/search.ts
Normal file
12
src/lib/search.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { UnitDoc } from "$lib/types";
|
||||||
|
|
||||||
|
export function unitMatchesQuery(
|
||||||
|
unit: Pick<UnitDoc, "code" | "code2" | "name" | "description">,
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -88,6 +88,20 @@ function mapQuestion(row: Record<string, any>): Record<string, any> {
|
||||||
return { ...row, solved: !!row.solved };
|
return { ...row, solved: !!row.solved };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unitRecordMatchesQuery(unit: Record<string, any> | 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<string, any>[]): Record<string, any>[] {
|
function addUnits(db: Db, rows: Record<string, any>[]): Record<string, any>[] {
|
||||||
const units = new Map<string, Record<string, any>>();
|
const units = new Map<string, Record<string, any>>();
|
||||||
const getUnit = db.prepare(
|
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 ?`)
|
.prepare(`SELECT ${NOTE_COLUMNS} FROM notes ORDER BY createdAt DESC LIMIT ?`)
|
||||||
.all(args.limit ?? 200) as Record<string, any>[];
|
.all(args.limit ?? 200) as Record<string, any>[];
|
||||||
const q = args.query.toLowerCase();
|
const q = args.query.toLowerCase();
|
||||||
return addUnits(
|
return addUnits(db, all).filter(
|
||||||
db,
|
(n) =>
|
||||||
all.filter(
|
String(n.title).toLowerCase().includes(q) ||
|
||||||
(n) =>
|
String(n.content).toLowerCase().includes(q) ||
|
||||||
String(n.title).toLowerCase().includes(q) ||
|
unitRecordMatchesQuery(n.unit, q),
|
||||||
String(n.content).toLowerCase().includes(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 ?`)
|
.prepare(`SELECT ${QUESTION_COLUMNS} FROM questions ORDER BY createdAt DESC LIMIT ?`)
|
||||||
.all(args.limit ?? 200) as Record<string, any>[];
|
.all(args.limit ?? 200) as Record<string, any>[];
|
||||||
const q = args.query.toLowerCase();
|
const q = args.query.toLowerCase();
|
||||||
return addUnits(
|
return addUnits(db, all.map(mapQuestion)).filter(
|
||||||
db,
|
(qr) =>
|
||||||
all
|
String(qr.title).toLowerCase().includes(q) ||
|
||||||
.filter(
|
String(qr.content).toLowerCase().includes(q) ||
|
||||||
(qr) =>
|
unitRecordMatchesQuery(qr.unit, q),
|
||||||
String(qr.title).toLowerCase().includes(q) ||
|
|
||||||
String(qr.content).toLowerCase().includes(q),
|
|
||||||
)
|
|
||||||
.map(mapQuestion),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unitsSearch(db: Db, args: { query: string; limit?: number }) {
|
||||||
|
const q = args.query.toLowerCase();
|
||||||
|
const all = unitsGetAll(db) as Record<string, any>[];
|
||||||
|
return all.filter((unit) => unitRecordMatchesQuery(unit, q)).slice(0, args.limit ?? 200);
|
||||||
|
}
|
||||||
|
|
||||||
function searchAll(db: Db, args: { query: string; limit?: number }) {
|
function searchAll(db: Db, args: { query: string; limit?: number }) {
|
||||||
const limit = args.limit ?? 200;
|
const limit = args.limit ?? 200;
|
||||||
|
const units = unitsSearch(db, { query: args.query, limit }) as Record<string, any>[];
|
||||||
const notes = notesSearch(db, { query: args.query, limit }) as Record<string, any>[];
|
const notes = notesSearch(db, { query: args.query, limit }) as Record<string, any>[];
|
||||||
const questions = questionsSearch(db, { query: args.query, limit }) as Record<string, any>[];
|
const questions = questionsSearch(db, { query: args.query, limit }) as Record<string, any>[];
|
||||||
const combined: Record<string, any>[] = [
|
const posts: Record<string, any>[] = [
|
||||||
...notes.map((n) => ({ ...n, type: "note" })),
|
...notes.map((n) => ({ ...n, type: "note" })),
|
||||||
...questions.map((q) => ({ ...q, type: "question" })),
|
...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 }) {
|
function questionsMarkSolved(db: Db, args: { token: string; id: string }) {
|
||||||
|
|
@ -1268,6 +1284,7 @@ const handlers: Record<string, Handler> = {
|
||||||
"topics:getAll": topicsGetAll,
|
"topics:getAll": topicsGetAll,
|
||||||
"units:getByCode": unitsGetByCode,
|
"units:getByCode": unitsGetByCode,
|
||||||
"units:getAll": unitsGetAll,
|
"units:getAll": unitsGetAll,
|
||||||
|
"units:search": unitsSearch,
|
||||||
"units:createCustom": unitsCreateCustom,
|
"units:createCustom": unitsCreateCustom,
|
||||||
"units:getPinned": unitsGetPinned,
|
"units:getPinned": unitsGetPinned,
|
||||||
"units:pin": unitsPin,
|
"units:pin": unitsPin,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,10 @@ export type QuestionDoc = Doc<"questions"> & {
|
||||||
unit?: UnitDoc;
|
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"> & {
|
export type CommentDoc = Doc<"comments"> & {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import FeedRow from "$lib/components/FeedRow.svelte";
|
import FeedRow from "$lib/components/FeedRow.svelte";
|
||||||
import UnitFilter from "$lib/components/UnitFilter.svelte";
|
import UnitFilter from "$lib/components/UnitFilter.svelte";
|
||||||
import { postPath } from "$lib/paths";
|
import { postPath } from "$lib/paths";
|
||||||
|
import { unitMatchesQuery } from "$lib/search";
|
||||||
import { timeAgo } from "$lib/time";
|
import { timeAgo } from "$lib/time";
|
||||||
import type { NoteDoc, UnitDoc } from "$lib/types";
|
import type { NoteDoc, UnitDoc } from "$lib/types";
|
||||||
|
|
||||||
|
|
@ -30,7 +31,10 @@
|
||||||
: notes;
|
: notes;
|
||||||
if (q) {
|
if (q) {
|
||||||
list = list.filter(
|
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);
|
if (sort === "top") list = [...list].sort((a, b) => b.voteCount - a.voteCount);
|
||||||
|
|
@ -52,8 +56,8 @@
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
placeholder="Search notes..."
|
placeholder="Search notes and units..."
|
||||||
aria-label="Search notes"
|
aria-label="Search notes and units"
|
||||||
class="field"
|
class="field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import FeedRow from "$lib/components/FeedRow.svelte";
|
import FeedRow from "$lib/components/FeedRow.svelte";
|
||||||
import UnitFilter from "$lib/components/UnitFilter.svelte";
|
import UnitFilter from "$lib/components/UnitFilter.svelte";
|
||||||
import { postPath } from "$lib/paths";
|
import { postPath } from "$lib/paths";
|
||||||
|
import { unitMatchesQuery } from "$lib/search";
|
||||||
import { timeAgo } from "$lib/time";
|
import { timeAgo } from "$lib/time";
|
||||||
import type { QuestionDoc, UnitDoc } from "$lib/types";
|
import type { QuestionDoc, UnitDoc } from "$lib/types";
|
||||||
|
|
||||||
|
|
@ -34,7 +35,8 @@
|
||||||
list = list.filter(
|
list = list.filter(
|
||||||
(question) =>
|
(question) =>
|
||||||
question.title.toLowerCase().includes(q) ||
|
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);
|
if (sort === "top") list = [...list].sort((a, b) => b.voteCount - a.voteCount);
|
||||||
|
|
@ -56,8 +58,8 @@
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
placeholder="Search questions..."
|
placeholder="Search questions and units..."
|
||||||
aria-label="Search questions"
|
aria-label="Search questions and units"
|
||||||
class="field"
|
class="field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { query } from "$lib/api";
|
import { query } from "$lib/api";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import FeedRow from "$lib/components/FeedRow.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 { timeAgo } from "$lib/time";
|
||||||
import type { SearchResult } from "$lib/types";
|
import type { SearchResult } from "$lib/types";
|
||||||
|
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const unitResults = $derived(results.filter((result) => result.type === "unit"));
|
||||||
|
const postResults = $derived(results.filter((result) => result.type !== "unit"));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -41,7 +44,7 @@
|
||||||
id="q"
|
id="q"
|
||||||
type="search"
|
type="search"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
placeholder="Search notes and questions..."
|
placeholder="Search units, notes, and questions..."
|
||||||
class="field"
|
class="field"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -49,7 +52,29 @@
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<p class="kicker py-16">Loading</p>
|
<p class="kicker py-16">Loading</p>
|
||||||
{:else if ranQuery}
|
{:else if ranQuery}
|
||||||
{#each results as result}
|
{#if unitResults.length > 0}
|
||||||
|
<p class="kicker mb-3">Units</p>
|
||||||
|
<div class="mb-8 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{#each unitResults as unit (unit._id)}
|
||||||
|
<a
|
||||||
|
href={unitPath(unit.code)}
|
||||||
|
class="unit-card flex h-full w-full flex-col gap-2"
|
||||||
|
>
|
||||||
|
<span class="text-ink font-serif text-base leading-tight"
|
||||||
|
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}</span
|
||||||
|
>
|
||||||
|
<span class="text-ink text-sm leading-snug">{unit.name}</span>
|
||||||
|
{#if unit.description}
|
||||||
|
<p class="text-muted line-clamp-3 text-xs leading-snug">
|
||||||
|
{unit.description}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each postResults as result}
|
||||||
{#if result.type === "note"}
|
{#if result.type === "note"}
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href={postPath(result.unit!.code, result._id)}
|
href={postPath(result.unit!.code, result._id)}
|
||||||
|
|
@ -66,7 +91,7 @@
|
||||||
targetId={result._id}
|
targetId={result._id}
|
||||||
tag="Note"
|
tag="Note"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else if result.type === "question"}
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href={postPath(result.unit!.code, result._id)}
|
href={postPath(result.unit!.code, result._id)}
|
||||||
title={result.title}
|
title={result.title}
|
||||||
|
|
@ -84,7 +109,9 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-muted text-sm">No results found for “{searchQuery}”.</p>
|
{#if unitResults.length === 0}
|
||||||
|
<p class="text-muted text-sm">No results found for “{searchQuery}”.</p>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { query } from "$lib/api";
|
import { query } from "$lib/api";
|
||||||
import { unitPath } from "$lib/paths";
|
import { unitPath } from "$lib/paths";
|
||||||
|
import { unitMatchesQuery } from "$lib/search";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { UnitDoc } from "$lib/types";
|
import type { UnitDoc } from "$lib/types";
|
||||||
|
|
||||||
let units: UnitDoc[] = $state([]);
|
let units: UnitDoc[] = $state([]);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
|
let searchQuery = $state("");
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -14,6 +16,8 @@
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const visible = $derived(units.filter((unit) => unitMatchesQuery(unit, searchQuery)));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -28,13 +32,25 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="border-rule border-b py-4">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
bind:value={searchQuery}
|
||||||
|
placeholder="Search units..."
|
||||||
|
aria-label="Search units"
|
||||||
|
class="field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<p class="kicker py-16">Loading</p>
|
<p class="kicker py-16">Loading</p>
|
||||||
{:else if units.length === 0}
|
{:else if units.length === 0}
|
||||||
<p class="text-muted py-16 text-sm">No units yet.</p>
|
<p class="text-muted py-16 text-sm">No units yet.</p>
|
||||||
|
{:else if visible.length === 0}
|
||||||
|
<p class="text-muted py-16 text-sm">No units match “{searchQuery.trim()}”.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="grid grid-cols-1 gap-3 pt-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-3 pt-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each units as unit (unit._id)}
|
{#each visible as unit (unit._id)}
|
||||||
<a href={unitPath(unit.code)} class="unit-card flex h-full w-full flex-col gap-2">
|
<a href={unitPath(unit.code)} class="unit-card flex h-full w-full flex-col gap-2">
|
||||||
<span class="text-ink font-serif text-base leading-tight"
|
<span class="text-ink font-serif text-base leading-tight"
|
||||||
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}</span
|
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}</span
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,29 @@
|
||||||
let pinnedIds = $state<string[]>([]);
|
let pinnedIds = $state<string[]>([]);
|
||||||
let pinBusy = $state(false);
|
let pinBusy = $state(false);
|
||||||
let pinError = $state("");
|
let pinError = $state("");
|
||||||
|
let searchQuery = $state("");
|
||||||
|
|
||||||
const isPinned = $derived(unit ? pinnedIds.includes(unit._id) : false);
|
const isPinned = $derived(unit ? pinnedIds.includes(unit._id) : false);
|
||||||
const canPin = $derived(isPinned || pinnedIds.length < MAX_PINS);
|
const canPin = $derived(isPinned || pinnedIds.length < MAX_PINS);
|
||||||
|
|
||||||
|
const visibleNotes = $derived.by(() => {
|
||||||
|
const q = searchQuery.trim().toLowerCase();
|
||||||
|
if (!q) return notes;
|
||||||
|
return notes.filter(
|
||||||
|
(note) =>
|
||||||
|
note.title.toLowerCase().includes(q) || note.content.toLowerCase().includes(q),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const visibleQuestions = $derived.by(() => {
|
||||||
|
const q = searchQuery.trim().toLowerCase();
|
||||||
|
if (!q) return questions;
|
||||||
|
return questions.filter(
|
||||||
|
(question) =>
|
||||||
|
question.title.toLowerCase().includes(q) ||
|
||||||
|
question.content.toLowerCase().includes(q),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await initAuth();
|
await initAuth();
|
||||||
authed = get(isAuthenticated);
|
authed = get(isAuthenticated);
|
||||||
|
|
@ -121,9 +140,21 @@
|
||||||
<p class="text-primary mt-2 text-xs">{pinError}</p>
|
<p class="text-primary mt-2 text-xs">{pinError}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if notes.length > 0}
|
{#if notes.length > 0 || questions.length > 0}
|
||||||
<p class="kicker border-rule mt-10 border-t pt-8">Notes</p>
|
<div class="border-rule mt-8 border-t py-4">
|
||||||
{#each notes as note}
|
<input
|
||||||
|
type="search"
|
||||||
|
bind:value={searchQuery}
|
||||||
|
placeholder="Search this unit..."
|
||||||
|
aria-label="Search this unit"
|
||||||
|
class="field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if visibleNotes.length > 0}
|
||||||
|
<p class="kicker border-rule mt-6 border-t pt-8">Notes</p>
|
||||||
|
{#each visibleNotes as note}
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href={postPath(unit.code, note._id)}
|
href={postPath(unit.code, note._id)}
|
||||||
title={note.title}
|
title={note.title}
|
||||||
|
|
@ -139,9 +170,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if questions.length > 0}
|
{#if visibleQuestions.length > 0}
|
||||||
<p class="kicker border-rule mt-10 border-t pt-8">Questions</p>
|
<p class="kicker border-rule mt-10 border-t pt-8">Questions</p>
|
||||||
{#each questions as question}
|
{#each visibleQuestions as question}
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href={postPath(unit.code, question._id)}
|
href={postPath(unit.code, question._id)}
|
||||||
title={question.title}
|
title={question.title}
|
||||||
|
|
@ -167,6 +198,10 @@
|
||||||
>Ask a question</a
|
>Ask a question</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if searchQuery.trim() && visibleNotes.length === 0 && visibleQuestions.length === 0}
|
||||||
|
<p class="text-muted mt-6 text-sm">
|
||||||
|
No results in this unit for “{searchQuery.trim()}”.
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<h1 class="text-ink font-serif text-3xl">Unit not found</h1>
|
<h1 class="text-ink font-serif text-3xl">Unit not found</h1>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue