From 21d62f8c6f89b61ade9a9cfd2fcedeefa4a04d79 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 12 Feb 2026 23:43:49 -0800 Subject: [PATCH] list: prototype with combined list field --- src/lib/utils/list/index.ts | 36 ++++++++++++++++++++++++++++++++++++ src/routes/+layout.svelte | 1 + src/routes/list/+page.svelte | 35 +++++++++++++++++++++++++++++++++++ src/routes/list/+page.ts | 7 +++++++ src/routes/list/schema.ts | 19 +++++++++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 src/lib/utils/list/index.ts create mode 100644 src/routes/list/+page.svelte create mode 100644 src/routes/list/+page.ts create mode 100644 src/routes/list/schema.ts diff --git a/src/lib/utils/list/index.ts b/src/lib/utils/list/index.ts new file mode 100644 index 0000000..d784c53 --- /dev/null +++ b/src/lib/utils/list/index.ts @@ -0,0 +1,36 @@ +import z from "zod"; + +export const MAL_URL = "https://myanimelist.net"; +export const ANILIST_URL = "https://anilist.co"; +export const KITSU_URL = "https://kitsu.io"; + +export const AnimeListKind = z.enum([ + "mal", + "anilist", + "kitsu", +]) + +export const AnimeListWatchStatus = z.enum({ + "completed": "c", + "watching": "w", + "plan_to_watch": "p", + "on_hold": "h", + "dropped": "d", +} as const) + +export const AnimeList = z.object({ + kind: AnimeListKind, + username: z.string(), + status: z.array(AnimeListWatchStatus).default([]), +}); + +export function listExternalUrl(list: z.infer) { + switch (list.kind) { + case "mal": + return `${MAL_URL}/profile/${encodeURIComponent(list.username)}`; + case "anilist": + return `${ANILIST_URL}/user/${encodeURIComponent(list.username)}`; + case "kitsu": + return `${KITSU_URL}/username/${encodeURIComponent(list.username)}`; + } +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 5137655..2b3a70c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -29,6 +29,7 @@ diff --git a/src/routes/list/+page.svelte b/src/routes/list/+page.svelte new file mode 100644 index 0000000..540ce87 --- /dev/null +++ b/src/routes/list/+page.svelte @@ -0,0 +1,35 @@ + + +

List Search WIP

+ +
+ + + + + + ({ + label: v.toUpperCase(), + value: v, + }))} + bind:value={params.list.status} + /> + diff --git a/src/routes/list/+page.ts b/src/routes/list/+page.ts new file mode 100644 index 0000000..e0dca86 --- /dev/null +++ b/src/routes/list/+page.ts @@ -0,0 +1,7 @@ +import type { PageLoad } from "./$types"; +import { SearchParamsSchema } from "./schema"; + +export const load: PageLoad = async ({ url, fetch, depends }) => { + const parsed = SearchParamsSchema.safeParse(url.searchParams); + console.log(parsed); +} \ No newline at end of file diff --git a/src/routes/list/schema.ts b/src/routes/list/schema.ts new file mode 100644 index 0000000..425652d --- /dev/null +++ b/src/routes/list/schema.ts @@ -0,0 +1,19 @@ +import { AnimeList, AnimeListWatchStatus } from "$lib/utils/list"; +import { z } from "zod"; + +const fieldSep = ":"; +const valueSep = ","; + +const listCodec = z.codec(z.string(), AnimeList, { + decode: (s) => { + const [kind, ...rest] = decodeURIComponent(s).split(fieldSep); + const status = rest.pop()?.split(valueSep).map((v) => AnimeListWatchStatus.parse(v)) ?? []; + const username = rest.join(""); + return AnimeList.parse({ kind, username, status }); + }, + encode: (list) => encodeURIComponent(`${list.kind}${fieldSep}${list.username}${fieldSep}${list.status?.join(valueSep)}`), +}); + +export const SearchParamsSchema = z.object({ + list: listCodec.default({ kind: "mal", username: "", status: [] }), +})