feat(copy): 处理状态的「入库」改为「整理」,术语表与 check:copy 同步
真正入库取决于媒体服务器有没有扫到,本工具只负责整理(元数据、NFO 与图片、按规则归到目录)。 界面与面向用户的服务端文案里「已入库 / 未入库 / 部分入库 / 入库失败 / 入库中 / 入库后 / 原地入库…」 改为「已整理 / 未整理 / 部分整理 / 整理失败 / 整理中 / 整理后 / 原地整理…」;文件管理快速筛选「已整理」、 目录副行「n 个已整理 / 未整理」、季卡片「已整理 x/y」。代码标识符不改,「Emby 已有」这类媒体服务器状态保留。 glossary 加「整理」口径条目;check:copy 拦处理状态语境的「入库」搭配,其它线待替换的文件暂列 TERM_PENDING。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
79c3b8c9b9
commit
550ac285f4
67 files changed
+171
-143
No files matched your search
@@ -450,7 +450,7 @@ export function RenameDialog({
|
||||
<Notice tone="neutral">同步 {pv.assetLocations} 条文件位置,写一条整理记录。</Notice>
|
||||
) : null}
|
||||
{pv.bindings.length > 0 ? (
|
||||
<Notice tone="warning">这是作品或季的绑定目录。改名后旧绑定标为已替代,之后入库用新目录。</Notice>
|
||||
<Notice tone="warning">这是作品或季的绑定目录。改名后旧绑定标为已替代,之后整理用新目录。</Notice>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
@@ -629,7 +629,7 @@ export function TransferDialog({
|
||||
<StatusBadge tone="warning">冲突 {conflicts.length}(跳过,不覆盖)</StatusBadge>
|
||||
) : null}
|
||||
{plan.pairs.some((p) => p.registered) ? (
|
||||
<StatusBadge>含已入库文件,执行后同步位置</StatusBadge>
|
||||
<StatusBadge>含已整理文件,执行后同步位置</StatusBadge>
|
||||
) : null}
|
||||
</div>
|
||||
{hnrBlocked ? (
|
||||
@@ -790,7 +790,7 @@ export function DeleteDialog({
|
||||
<DialogContent className="max-h-[92dvh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{single ? `删除 ${single}?` : `删除 ${items.length} 项?`}</DialogTitle>
|
||||
<DialogDescription>已检查做种任务、已入库文件和路径绑定。下载器里的任务不受影响。</DialogDescription>
|
||||
<DialogDescription>已检查做种任务、已整理文件和路径绑定。下载器里的任务不受影响。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="min-h-0 space-y-3 overflow-y-auto">
|
||||
{check.isPending ? (
|
||||
@@ -837,7 +837,7 @@ export function DeleteDialog({
|
||||
</p>
|
||||
))}
|
||||
{c.assets.length ? (
|
||||
<p>已入库文件 {c.assets.length} 个(删除后从媒体库移除,已满足的收录版本退回待处理)</p>
|
||||
<p>已整理文件 {c.assets.length} 个(删除后从媒体库移除,已满足的收录版本退回待处理)</p>
|
||||
) : null}
|
||||
{c.bindings.map((bd) => (
|
||||
<p key={bd.bindingId}>
|
||||
@@ -869,7 +869,7 @@ export function DeleteDialog({
|
||||
<span>
|
||||
解除路径绑定
|
||||
<span className="block text-meta text-muted-foreground">
|
||||
绑定标为已替代,该作品下次入库重新选目录。
|
||||
绑定标为已替代,该作品下次整理重新选目录。
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -1139,7 +1139,7 @@ export function ScrapeDialog({ items, root, onClose }: { items: FileEntry[]; roo
|
||||
<DialogHeader>
|
||||
<DialogTitle>刮削{single ? ` · ${single.name}` : ` · ${items.length} 项`}</DialogTitle>
|
||||
<DialogDescription>
|
||||
按路径识别作品,在所在目录写 NFO 和图片,Emby、Jellyfin、Kodi 直接读取。没入库的也能刮削。
|
||||
按路径识别作品,在所在目录写 NFO 和图片,Emby、Jellyfin、Kodi 直接读取。没整理的也能刮削。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{!started ? (
|
||||
|
||||
@@ -110,7 +110,7 @@ export function FileDetail({
|
||||
value:
|
||||
e.type === "dir"
|
||||
? e.dirSummary
|
||||
? `${e.dirSummary.videoCount}${e.dirSummary.partial ? "+" : ""} 个视频 · ${e.dirSummary.registeredCount} 个已入库`
|
||||
? `${e.dirSummary.videoCount}${e.dirSummary.partial ? "+" : ""} 个视频 · ${e.dirSummary.registeredCount} 个已整理`
|
||||
: "—"
|
||||
: formatBytes(e.size, 2),
|
||||
},
|
||||
@@ -135,7 +135,7 @@ export function FileDetail({
|
||||
</DetailSection>
|
||||
|
||||
{e.asset ? (
|
||||
<DetailSection title="已入库文件">
|
||||
<DetailSection title="已整理文件">
|
||||
<KvList
|
||||
items={[
|
||||
{
|
||||
|
||||
@@ -94,7 +94,7 @@ const base = {
|
||||
|
||||
describe("状态与禁用原因", () => {
|
||||
it("状态徽标带文字,H&R 未满足为 warning,逃逸为 destructive", () => {
|
||||
expect(entryStatusView({ ...base, status: "registered" })?.label).toBe("已入库");
|
||||
expect(entryStatusView({ ...base, status: "registered" })?.label).toBe("已整理");
|
||||
expect(
|
||||
entryStatusView({
|
||||
...base,
|
||||
@@ -125,7 +125,7 @@ describe("状态与禁用原因", () => {
|
||||
expect(opDisabledReason("move", seeding, lib)).toMatch(/H&R/);
|
||||
expect(opDisabledReason("move", base, { kind: "library", writable: false })).toMatch(/只读/);
|
||||
expect(opDisabledReason("rename", base, { kind: "download", writable: false })).toBeNull();
|
||||
expect(opDisabledReason("scrape", base, lib)).toMatch(/未登记/);
|
||||
expect(opDisabledReason("scrape", base, lib)).toMatch(/整理后才能/);
|
||||
expect(opDisabledReason("writeNfo", base, lib)).toBeNull();
|
||||
expect(opDisabledReason("writeNfo", base, { kind: "library", writable: false })).toMatch(/只读/);
|
||||
expect(opDisabledReason("copyPath", { ...base, escape: true }, lib)).toBeNull();
|
||||
@@ -272,7 +272,7 @@ describe("v2 菜单与副行", () => {
|
||||
type: "dir" as const,
|
||||
dirSummary: { videoCount: 13, registeredCount: 0, partial: false },
|
||||
})?.text,
|
||||
).toBe("13 个视频 · 未入库");
|
||||
).toBe("13 个视频 · 未整理");
|
||||
// 没有 parsedHint(服务端解析不出标题)时不显示副行,前端不自己解析
|
||||
expect(entrySubline({ ...video, parsedHint: null })).toBeNull();
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export function entryStatusView(e: Pick<FileEntry, "status" | "download" | "esca
|
||||
tone: "neutral",
|
||||
};
|
||||
case "registered":
|
||||
return { label: "已入库", tone: "success" };
|
||||
return { label: "已整理", tone: "success" };
|
||||
case "seeding":
|
||||
return e.download?.hnr === "unsatisfied"
|
||||
? { label: "做种中·H&R 未满足", tone: "warning", hint: "H&R 满足前不能改名、删除或移动" }
|
||||
@@ -42,7 +42,7 @@ export const FILTER_CHIPS = [
|
||||
{ value: "", label: "全部" },
|
||||
{ value: "video", label: "视频" },
|
||||
{ value: "unidentified", label: "未识别" },
|
||||
{ value: "registered", label: "已入库" },
|
||||
{ value: "registered", label: "已整理" },
|
||||
{ value: "downloading", label: "下载中" },
|
||||
{ value: "temp", label: "未完成下载" },
|
||||
] as const;
|
||||
@@ -98,7 +98,7 @@ export function opDisabledReason(op: FileOp, e: FileEntry, root: Pick<FileRoot,
|
||||
if (e.type === "file" && e.fileKind !== "video") return "只有视频或目录能整理";
|
||||
return null;
|
||||
case "scrape":
|
||||
return e.asset ? null : "未登记,先整理入库";
|
||||
return e.asset ? null : "整理后才能刷新元数据";
|
||||
case "writeNfo":
|
||||
if (root.kind === "library" && !root.writable) return "只读目录,不写 NFO";
|
||||
if (e.status === "temp") return "未完成的文件不能刮削";
|
||||
@@ -314,7 +314,7 @@ export function deleteBlockText(b: string | null): { text: string; soft: boolean
|
||||
case "is_root":
|
||||
return { text: "不能删除登记目录本身", soft: false };
|
||||
case "needs_retire":
|
||||
return { text: "含已入库文件,勾选「同时从媒体库移除」后才能删除", soft: true };
|
||||
return { text: "含已整理文件,勾选「同时从媒体库移除」后才能删除", soft: true };
|
||||
case "needs_unbind":
|
||||
return { text: "是作品或季的绑定目录,勾选「解除路径绑定」后才能删除", soft: true };
|
||||
case "root_unavailable":
|
||||
@@ -431,7 +431,7 @@ export function menuModel(list: FileEntry[], root: Pick<FileRoot, "kind" | "writ
|
||||
|
||||
/**
|
||||
* 行内 hover / 键盘聚焦时浮出的常用操作(桌面,§5.1;照 MP 的悬停图标,但只露 2–3 个,其余留在 ⋯ 菜单):
|
||||
* 已入库 → 打开作品、刷新元数据、重命名;未入库视频 / 目录 → 整理、识别、刮削;
|
||||
* 已整理 → 打开作品、刷新元数据、重命名;未整理视频 / 目录 → 整理、识别、刮削;
|
||||
* 其余能识别的(临时文件里的视频等)→ 识别、重命名、移动到;别的文件 → 重命名、移动到。
|
||||
*/
|
||||
export function hoverOps(e: FileEntry): MenuKey[] {
|
||||
@@ -472,7 +472,7 @@ export function entrySubline(e: FileEntry): { text: string; tone: "link" | "hint
|
||||
const s = e.dirSummary;
|
||||
if (!s.videoCount && !s.registeredCount) return null;
|
||||
return {
|
||||
text: `${s.videoCount}${s.partial ? "+" : ""} 个视频${s.registeredCount ? ` · ${s.registeredCount} 个已入库` : s.videoCount ? " · 未入库" : ""}`,
|
||||
text: `${s.videoCount}${s.partial ? "+" : ""} 个视频${s.registeredCount ? ` · ${s.registeredCount} 个已整理` : s.videoCount ? " · 未整理" : ""}`,
|
||||
tone: "meta",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user