feat(files): 行尾悬停露出常用操作图标,面包屑带登记目录类型

学 MP 文件管理:鼠标悬停或键盘焦点所在行在行尾浮出 2–3 个图标按钮(未入库视频 / 目录:整理、识别、刮削;已入库:打开作品、刷新元数据、重命名;其余:重命名、移动到),带 tooltip 与 aria-label,被状态阻止的灰显写原因;浮出时让出修改时间列;焦点行可 Tab 进入、Esc 回列表;窄屏仍只用 ⋯ 菜单。
面包屑前加「下载目录 / 媒体库」说明段。顺带修正 Shift+F10 菜单定位按行键找行时被 CSS.escape 改坏的问题。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 12:06:27 +08:00
1 parent d98899a5f8
commit 67f05895d3
6 files changed
+165 -39

No files matched your search

+40 -3
View File
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { resolveFilesMock } from "~/api/files.ts";
import {
applyIdentifyCompleted,
breadcrumbItems,
clickSelect,
collapseCrumbs,
cueTime,
@@ -10,6 +11,7 @@ import {
entryStatusView,
entrySubline,
formatEpisodes,
hoverOpLabel,
hoverOps,
initialLocation,
menuModel,
@@ -220,10 +222,45 @@ describe("v2 菜单与副行", () => {
expect(m && !("separator" in m) ? m.disabled : null).toMatch(/H&R/);
});
it("hover 主操作按状态", () => {
expect(hoverOps(video)).toEqual(["organize", "identify"]);
expect(hoverOps(registered)).toEqual(["rename", "openMedia"]);
it("悬停常用操作按文件 / 目录类型选 2–3 个,都在菜单里能找到", () => {
expect(hoverOps(video)).toEqual(["organize", "identify", "writeNfo"]);
expect(hoverOps({ ...base, type: "dir" as const, fileKind: null })).toEqual(["organize", "identify", "writeNfo"]);
expect(hoverOps(registered)).toEqual(["openMedia", "scrape", "rename"]);
// 未完成的视频不能整理:退为识别 / 重命名 / 移动
expect(hoverOps({ ...video, status: "temp" as const })).toEqual(["identify", "rename", "move"]);
expect(hoverOps({ ...base, fileKind: "nfo" as const })).toEqual(["rename", "move"]);
expect(hoverOps({ ...base, fileKind: "subtitle" as const })).toEqual(["rename", "move"]);
for (const e of [video, registered, { ...base, fileKind: "nfo" as const }]) {
const ops = hoverOps(e);
expect(ops.length).toBeGreaterThanOrEqual(2);
expect(ops.length).toBeLessThanOrEqual(3);
const menu = menuModel([e], lib).flatMap((x) => ("separator" in x ? [] : [x.key]));
for (const op of ops) expect(menu).toContain(op);
}
});
it("悬停图标的 tooltip / aria-label 文字去掉菜单里的省略号", () => {
expect(hoverOpLabel("organize")).toBe("整理");
expect(hoverOpLabel("writeNfo")).toBe("刮削");
expect(hoverOpLabel("move")).toBe("移动到");
expect(hoverOpLabel("scrape")).toBe("刷新元数据");
expect(hoverOpLabel("openMedia")).toBe("打开作品");
});
it("面包屑:登记目录类型 > 登记目录 > 各级子目录,每段带可跳转的相对路径", () => {
expect(breadcrumbItems({ name: "qBittorrent · 下载", kind: "download" }, "")).toEqual({
group: "下载目录",
crumbs: [{ label: "qBittorrent · 下载", path: "" }],
});
expect(breadcrumbItems({ name: "剧集库", kind: "library" }, "亚洲剧集/繁花 (2023)/Season 1/")).toEqual({
group: "媒体库",
crumbs: [
{ label: "剧集库", path: "" },
{ label: "亚洲剧集", path: "亚洲剧集" },
{ label: "繁花 (2023)", path: "亚洲剧集/繁花 (2023)" },
{ label: "Season 1", path: "亚洲剧集/繁花 (2023)/Season 1" },
],
});
});
it("副行:已登记作品、待识别解析、目录计数", () => {