Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 2x 25x 40x 25x 25x 25x 81x 28x 23x 23x 19x 6x 13x 2x 11x | import type { Collection } from "@/types";
import { viewHref } from "@/components/ui/viewHref";
export type CollectionsView = "all" | "archived";
export const COLLECTION_PILLS: [CollectionsView, string][] = [["all", "All"], ["archived", "Archived"]];
/** `?status=archived` is the only non-default view; the retired "active" and anything else is All. */
export function collectionsViewFrom(status: unknown): CollectionsView {
return status === "archived" ? "archived" : "all";
}
export const collectionsViewHref = (view: CollectionsView, q: string) => viewHref("/collections", view, q);
/** All = not archived (no status is silently hidden), Archived = archived; plain-text
* search (never a pattern) over name and description. */
export function filterCollections(
collections: Collection[],
opts: { view: CollectionsView; q: string },
): Collection[] {
const wantArchived = opts.view === "archived";
const needle = opts.q.trim().toLocaleLowerCase();
return collections.filter(
(c) =>
(c.status === "archived") === wantArchived &&
(!needle || [c.name, c.description].some((text) => text?.toLocaleLowerCase().includes(needle))),
);
}
export function collectionsEmptyState(opts: { hasAny: boolean; view: CollectionsView; q: string }) {
const q = opts.q.trim();
if (q) return { heading: `Nothing matches “${q}”`, message: "Try another word, or clear the search." };
if (opts.view === "archived")
return { heading: "Nothing archived", message: "Collections you archive rest here, ready to come back whenever you like." };
if (!opts.hasAny)
return { heading: "Nothing here yet", message: "Start a new collection whenever you’re ready — there’s no rush." };
return { heading: "Nothing here right now", message: "Your archived collections are under Archived." };
}
|