feat(web): MP-style media detail with seasons and episodes, shared by library and TMDB preview (MD-1)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:28:16 +08:00
1 parent 9cd5a28943
commit 97ac59c7ac
9 files changed
+1250 -190

No files matched your search

+32 -2
View File
@@ -1,3 +1,4 @@
import { DEFAULT_SETTINGS } from "@umt/contracts";
import { toast } from "sonner";
import { useClearImageCache, useClearTmdbCache, useImageCache, useTmdbCache } from "~/api/cache.ts";
import { useUpdateSettings } from "~/api/settings.ts";
@@ -7,7 +8,23 @@ import { errorMessage } from "~/lib/api.ts";
import { str } from "./resource-section.tsx";
import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx";
// 元数据:TMDB API Key(只写,存 secret)与 Base URL
// 元数据:TMDB API Key(只写,存 secret)、Base URL、语言回退链
const DEFAULT_LANGUAGES = DEFAULT_SETTINGS.metadata.languages;
/** "zh-CN, zh_sg en" → ["zh-CN","zh-SG","en"];不合法的项丢弃,全空回缺省 */
function parseLanguages(v: string): string[] {
const out = v
.split(/[,,、\s]+/)
.map((x) => x.trim().replace("_", "-"))
.filter(Boolean)
.map((x) => {
const [l, r] = x.split("-");
return r ? `${l!.toLowerCase()}-${r.toUpperCase()}` : l!.toLowerCase();
})
.filter((x) => /^[a-z]{2,3}(-[A-Z]{2})?$/.test(x));
return out.length ? [...new Set(out)].slice(0, 8) : [...DEFAULT_LANGUAGES];
}
const TMDB_CARD: CardSpec = {
anchor: "tmdb",
@@ -29,11 +46,24 @@ const TMDB_CARD: CardSpec = {
hint: "自建反代时填写;留空用官方地址",
wide: true,
},
{
name: "metadata.languages",
label: "语言回退链",
kind: "text",
placeholder: DEFAULT_LANGUAGES.join(", "),
hint: "标题、简介和每集信息按顺序取第一个有值的语言;中文名取链中第一个中文值。逗号分隔,如 zh-CN, zh-SG",
wide: true,
},
],
fromSettings: (s) => ({ "metadata.tmdbApiKey": "", "metadata.tmdbBaseUrl": s.metadata.tmdbBaseUrl ?? "" }),
fromSettings: (s) => ({
"metadata.tmdbApiKey": "",
"metadata.tmdbBaseUrl": s.metadata.tmdbBaseUrl ?? "",
"metadata.languages": (s.metadata.languages ?? DEFAULT_LANGUAGES).join(", "),
}),
toPatch: (v) => ({
metadata: {
tmdbBaseUrl: str(v["metadata.tmdbBaseUrl"]) || null,
languages: parseLanguages(str(v["metadata.languages"])),
...(str(v["metadata.tmdbApiKey"]) ? { tmdbApiKey: str(v["metadata.tmdbApiKey"]) } : {}),
},
}),