import type { EpisodeMappingPreview, EpisodeSuggestion } from "@umt/contracts"; import { Wand2 } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useLocation, useSearchParams } from "react-router"; import { useEntityWords, useEpisodeMappingPreview } from "~/api/episode-mapping.ts"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { WordsEditor } from "~/components/identify-words/words-editor.tsx"; import { Chip, Panel } from "~/components/library/parts.tsx"; import { Button } from "~/components/ui/button.tsx"; import { EpisodeIssuesList } from "./issues.tsx"; import { groupMethodsText, lineFromSearch, MAPPING_ANCHOR } from "./model.ts"; import { PreviewTable } from "./preview-table.tsx"; // 作品页「集数映射」分区:季表摘要、作品识别词(与规则页「识别词」同一个编辑器、同一份数据)、 // 按发布组的统计与建议、换算不了的资源,以及近期资源按已保存的识别词的判定。 function SeasonSummary({ p }: { p: EpisodeMappingPreview["seasons"] }) { const main = p.seasons.filter((s) => s.season >= 1); if (!main.length) return

还没有季表,刷新元数据后出现。

; return (
{main.map((s) => ( 第 {s.season} 季 · {s.total != null ? `${s.total} 集` : "总集数未知"} {s.season === p.latestSeason ? " · 最新" : ""} ))} {p.ended ? "已完结" : "未完结"} {p.structure ? ` · 按剧集组「${p.structure.name}」分季` : ""}
); } const pad = (n: number) => String(n).padStart(2, "0"); /** 建议规则写成作品识别词的一行 */ export function suggestionLine(s: EpisodeSuggestion): string { const scope = `${s.season == null ? "*" : `S${pad(s.season)}`}${s.releaseGroup ? ` @${s.releaseGroup}` : ""}`; return `${scope} >> S${pad(s.targetSeason)} ${s.expression}`; } function GroupStats({ groups, onUseSuggestion, }: { groups: EpisodeMappingPreview["groups"]; onUseSuggestion: (line: string) => void; }) { if (!groups.length) return null; return ( ); } export function EpisodeMappingPanel({ entityId }: { entityId: string }) { const [sp] = useSearchParams(); const location = useLocation(); const words = useEntityWords(entityId); const preview = useEpisodeMappingPreview(entityId, {}); const [draft, setDraft] = useState(null); const ref = useRef(null); const saved = words.data?.block.text ?? ""; const text = draft ?? saved; /** 把一行追加进草稿(建议、集数待确认的「修改」、从别处跳来带的一行) */ const append = (line: string) => { setDraft((cur) => { const base = (cur ?? saved).replace(/\n*$/, ""); if (base.split("\n").some((l) => l.trim() === line.trim())) return cur ?? saved; return `${base ? `${base}\n` : ""}${line}\n`; }); ref.current?.scrollIntoView({ block: "start", behavior: "smooth" }); }; // 从集数待确认「修改」跳来:把那一行放进草稿并滚到这里 const fromLink = lineFromSearch(sp); // biome-ignore lint/correctness/useExhaustiveDependencies: 识别词加载好之后只处理一次地址里带的行 useEffect(() => { if (fromLink && words.isSuccess) append(fromLink); if (location.hash === `#${MAPPING_ANCHOR}`) ref.current?.scrollIntoView({ block: "start" }); }, [fromLink, words.isSuccess, location.hash]); // 保存后草稿归零(编辑器已拿到新版本) useEffect(() => { if (draft !== null && draft === saved) setDraft(null); }, [draft, saved]); return (
{preview.data ? : null} {words.isPending ? ( ) : words.isError ? ( words.refetch()} /> ) : ( )} {preview.data ? : null} {preview.isPending ? (
) : preview.isError ? (
preview.refetch()} />
) : ( )}
); }