@xrn1997/dsh-session-delete 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/THIRD_PARTY_NOTICES.md +179 -0
- package/cordis.patch.yml +1 -1
- package/docs/screenshots/screenshot-01.png +0 -0
- package/docs/screenshots/screenshot-02.png +0 -0
- package/docs/screenshots/screenshot-03.png +0 -0
- package/icon.svg +24 -18
- package/lib/client.js +1439 -410
- package/lib/index.js +475 -244
- package/lib/types/api/connection-route.d.ts +1 -1
- package/lib/types/api/dispatch.d.ts +1 -1
- package/lib/types/api/route-table.d.ts +12 -7
- package/lib/types/client/api-base.d.ts +19 -0
- package/lib/types/client/config-settings.d.ts +13 -0
- package/lib/types/client/context-menu-actions.d.ts +47 -0
- package/lib/types/client/context.d.ts +3 -0
- package/lib/types/client/copy.d.ts +42 -7
- package/lib/types/client/delete-confirm.d.ts +2 -2
- package/lib/types/client/detail-blocks.d.ts +25 -0
- package/lib/types/client/index.d.ts +3 -0
- package/lib/types/client/maintenance-settings.d.ts +5 -4
- package/lib/types/client/remote.d.ts +43 -15
- package/lib/types/client/settings-cards.d.ts +19 -0
- package/lib/types/client/settings-view.d.ts +9 -0
- package/lib/types/client/trash-detail.d.ts +1 -1
- package/lib/types/client/trash-nav.d.ts +5 -1
- package/lib/types/client/trash-view.d.ts +34 -5
- package/lib/types/client/ui/MenuItemButton.d.ts +1 -1
- package/lib/types/client/ui/Toast.d.ts +4 -1
- package/lib/types/client/ui/icons.d.ts +23 -7
- package/lib/types/client/ui/recipe.d.ts +2 -0
- package/lib/types/client/ui/styles.d.ts +2 -0
- package/lib/types/client/use-detail-pager.d.ts +33 -0
- package/lib/types/config.d.ts +10 -11
- package/lib/types/deps.d.ts +9 -1
- package/lib/types/index.d.ts +8 -5
- package/lib/types/ports/agent-handles.d.ts +10 -2
- package/lib/types/ports/fs-port.d.ts +10 -0
- package/lib/types/ports/host-port.d.ts +4 -2
- package/lib/types/shared/wire.d.ts +37 -5
- package/lib/types/trash/attachments.d.ts +15 -2
- package/lib/types/verbs/config.d.ts +25 -0
- package/lib/types/verbs/errors.d.ts +3 -1
- package/lib/types/verbs/permanent-removal.d.ts +10 -5
- package/locale/en.json +1 -1
- package/locale/zh.json +6 -6
- package/package.json +30 -19
|
@@ -15,7 +15,7 @@ export interface ConnectionFetchServiceLike {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* 注册十段 exact Fetch 路由。返回一个组合 disposer;调用方把它挂在自己的 effect 上。
|
|
19
19
|
* `connection` 是插件的硬依赖(web UI / 桌面端都装 `client-connection`);`fetch.register`
|
|
20
20
|
* 缺席属于宿主形状漂移,直接抛——静默返回空 disposer 会让插件「在但没有入口」。
|
|
21
21
|
* `dispatch` 与分发器同源。
|
|
@@ -18,7 +18,7 @@ export type ApiResult = {
|
|
|
18
18
|
};
|
|
19
19
|
/** 段名的 404 与方法的 405 只在这里判一次;空段名按插件前缀显示,避免 404 原文出现一个空串。 */
|
|
20
20
|
export declare function resolveRoute(method: string, rest: string): RouteSpec;
|
|
21
|
-
/**
|
|
21
|
+
/** 五格只建一次:收窄对象在 dispatcher 构造期固定,请求路径只做引用选择。 */
|
|
22
22
|
export declare function slicesOf(full: DeleteDeps): SliceDeps;
|
|
23
23
|
/** 分发函数:查段 → 方法闸 → 读面 → 处理。 */
|
|
24
24
|
export type Dispatch = (input: ApiInput) => Promise<ApiResult>;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 这张表只回答“怎么处理”。
|
|
13
13
|
*/
|
|
14
14
|
import { type ApplyAction, type RouteName } from '../shared/wire.js';
|
|
15
|
-
import type { CoreDeps, DeleteDeps, RemovalDeps } from '../deps.js';
|
|
15
|
+
import type { ConfigEditorLike, CoreDeps, DeleteDeps, RemovalDeps } from '../deps.js';
|
|
16
16
|
import type { AttachmentsPort } from '../ports/attachments-port.js';
|
|
17
17
|
import type { TrashEntry } from '../trash/entry.js';
|
|
18
18
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
@@ -35,9 +35,10 @@ export interface RouteRead {
|
|
|
35
35
|
rows(): Promise<readonly TrashEntry[]>;
|
|
36
36
|
}
|
|
37
37
|
/** deps 切片(ADR-0032):每一段只声明自己要的那一面。读与探测五段拿基础面,`attachment` 段另多一条
|
|
38
|
-
* 宿主 `attachments` 读面,两个删盘段多删除能力,`/apply`
|
|
39
|
-
*
|
|
40
|
-
|
|
38
|
+
* 宿主 `attachments` 读面,两个删盘段多删除能力,`/apply` 是唯一拿全量面的行,`/config` 只多
|
|
39
|
+
* configEditor + entry 两件(段的 run 里长出超面调用就是编译错;dispatch 也只按这一格注入,
|
|
40
|
+
* 运行期收窄住 `src/api/dispatch.ts` 的 `slicesOf`)。 */
|
|
41
|
+
export type RouteSlice = 'core' | 'removal' | 'full' | 'attachment' | 'config';
|
|
41
42
|
/** 切片名 → 依赖面:{@link RouteSpec.run} 的 deps 参数类型与 `depsFor` 的返回类型都从这一格取。 */
|
|
42
43
|
export type SliceDeps = {
|
|
43
44
|
core: CoreDeps;
|
|
@@ -46,6 +47,10 @@ export type SliceDeps = {
|
|
|
46
47
|
attachment: CoreDeps & {
|
|
47
48
|
readonly attachments: AttachmentsPort;
|
|
48
49
|
};
|
|
50
|
+
config: CoreDeps & {
|
|
51
|
+
readonly configEditor?: ConfigEditorLike;
|
|
52
|
+
readonly entry?: unknown;
|
|
53
|
+
};
|
|
49
54
|
};
|
|
50
55
|
interface BaseRouteSpec<S extends RouteSlice> {
|
|
51
56
|
/** 这一行吃哪一格 deps:dispatch 按它注入,`run` 的 deps 参数类型也跟着它走。 */
|
|
@@ -58,7 +63,7 @@ interface BaseRouteSpec<S extends RouteSlice> {
|
|
|
58
63
|
* 缺省不设闸——JSON 响应跨源读不到(没有 CORS),不需要它。 */
|
|
59
64
|
readonly fetchGate?: 'same-origin';
|
|
60
65
|
}
|
|
61
|
-
/** JSON 段:处理函数拿请求体与读面;`query`
|
|
66
|
+
/** JSON 段:处理函数拿请求体与读面;`query` 不在这里(十行里只有二进制段需要查询串)。 */
|
|
62
67
|
export interface JsonRouteSpec<S extends RouteSlice = RouteSlice> extends BaseRouteSpec<S> {
|
|
63
68
|
readonly response: 'json';
|
|
64
69
|
/** 声明成函数属性而不是方法:方法签名是双变的,显式标注更宽的 deps 能绕过切片闸;属性按逆变校验。 */
|
|
@@ -70,13 +75,13 @@ export interface BinaryRouteSpec<S extends RouteSlice = RouteSlice> extends Base
|
|
|
70
75
|
readonly run: (deps: SliceDeps[S], query: URLSearchParams) => Promise<BinaryResponse>;
|
|
71
76
|
}
|
|
72
77
|
export type RouteSpec<S extends RouteSlice = RouteSlice> = JsonRouteSpec<S> | BinaryRouteSpec<S>;
|
|
73
|
-
/**
|
|
78
|
+
/** 十行的并集(表的值类型):每行由自己的 `slice` 与 `response` 字面量定型,`run` 的 deps 面随之收窄。 */
|
|
74
79
|
export type AnyRouteSpec = {
|
|
75
80
|
[S in RouteSlice]: RouteSpec<S>;
|
|
76
81
|
}[RouteSlice];
|
|
77
82
|
export declare const APPLY_ACTIONS: readonly ApplyAction[];
|
|
78
83
|
/**
|
|
79
|
-
*
|
|
84
|
+
* 十个段名各一行。`Record<段名, AnyRouteSpec>` 是闸:`wire.ts` 加了段名而这里没加,编译不过;
|
|
80
85
|
* 每行的 `slice` 又把该行的 `run` 钉在自己那一格 deps 上(ADR-0032)。
|
|
81
86
|
*/
|
|
82
87
|
export declare const ROUTE_TABLE: Record<RouteName, AnyRouteSpec>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 取数路径的挂载点:官方 `/api` 前缀 + **当前文档所在的目录**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不能写死根绝对路径:宿主自己的客户端模块按「app-directory-relative form」下发 bundle 与
|
|
5
|
+
* chunk 描述符,浏览器在**自己的挂载点**下解析(`dsh-client-modules` README 原文:*"which a browser
|
|
6
|
+
* document resolves under its own mount"*);同一个部署可以被反代到 `/dsh/`,此时 `/api/...` 会打到
|
|
7
|
+
* 反代根、而不是宿主。路由注册侧仍然必须写 `/api/...`(宿主 `connection.fetch.register` 用
|
|
8
|
+
* `endpointFromPath('/api', path)` 校验),所以本文件只给**浏览器半**投影一层挂载点前缀。
|
|
9
|
+
*
|
|
10
|
+
* 根部署(`http://host/`)下 `mountPathOf` 返回空串,拼出来的 URL 与旧版逐字相同;只有挂载部署才
|
|
11
|
+
* 多出 `/dsh` 这一段。目录取自 `new URL('.', baseURI)`:页面是 `/dsh/`、`/dsh/index.html` 都归到
|
|
12
|
+
* `/dsh/`;`about:blank` 这类不透明 base 退回根路径(此时相对地址本来也无法成立)。
|
|
13
|
+
*/
|
|
14
|
+
/** `baseURI` 所在目录的路径,去掉尾斜杠;根路径回空串。 */
|
|
15
|
+
export declare function mountPathOf(baseURI: string): string;
|
|
16
|
+
/** 一段相对段名 → 宿主 `/api` 前缀 + 挂载点。`path` 以 `/` 开头,与 `ROUTES` 的形状一致。 */
|
|
17
|
+
export declare function apiPathOf(path: string, baseURI: string): string;
|
|
18
|
+
/** 当前文档的取数路径;Node 侧(无 `document`)退回根路径。 */
|
|
19
|
+
export declare function apiPath(path: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { type Translate } from './copy.js';
|
|
3
|
+
import type { ConfigDeps } from './remote.js';
|
|
4
|
+
export interface ConfigSettingsProps {
|
|
5
|
+
/** 整套 remote 的切片;测试可以只给维护那四件(缺这两件时本卡不渲染)。 */
|
|
6
|
+
readonly deps: Partial<ConfigDeps>;
|
|
7
|
+
readonly t?: Translate;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 卡片入口:两个方法都在才渲染。`useMaintenance` 与 `MaintenanceSettings` 的测试替身只造维护四件,
|
|
11
|
+
* 所以在那些用例里本卡整体缺席——不让"没给配置 deps"变成一次假渲染。
|
|
12
|
+
*/
|
|
13
|
+
export declare function ConfigSettings({ deps, t }: ConfigSettingsProps): ReactElement | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Translate } from './copy.js';
|
|
2
|
+
interface MenuTarget {
|
|
3
|
+
readonly sessionId: string;
|
|
4
|
+
readonly displayTitle: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MenuActionLike {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
icon?: {
|
|
11
|
+
key: string;
|
|
12
|
+
};
|
|
13
|
+
danger?: boolean;
|
|
14
|
+
separatorBefore?: boolean;
|
|
15
|
+
afterContent?: boolean;
|
|
16
|
+
run: () => void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface ObservableSnapshotLike<T> {
|
|
19
|
+
getSnapshot(): T;
|
|
20
|
+
subscribe(listener: () => void): () => void;
|
|
21
|
+
}
|
|
22
|
+
interface SessionsStateLike {
|
|
23
|
+
byId?: Record<string, {
|
|
24
|
+
running?: boolean;
|
|
25
|
+
} | undefined>;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionsServiceLike {
|
|
28
|
+
list?: ObservableSnapshotLike<SessionsStateLike>;
|
|
29
|
+
}
|
|
30
|
+
export interface MenuContributionLike {
|
|
31
|
+
id: string;
|
|
32
|
+
target: 'session';
|
|
33
|
+
order: number;
|
|
34
|
+
observe?: (target: MenuTarget) => readonly ObservableSnapshotLike<unknown>[];
|
|
35
|
+
resolve: (target: MenuTarget) => readonly MenuActionLike[] | Promise<readonly MenuActionLike[]>;
|
|
36
|
+
}
|
|
37
|
+
export interface ContextMenusLike {
|
|
38
|
+
register(contribution: MenuContributionLike): () => void;
|
|
39
|
+
}
|
|
40
|
+
export declare const ICON_BATCH = "xrn1997.session-delete.batch-select";
|
|
41
|
+
export declare const ICON_DELETE = "xrn1997.session-delete.delete";
|
|
42
|
+
/**
|
|
43
|
+
* 造 0.2.2+ 的会话菜单贡献。order 700 落在内置 Archive(600) 之后;两行都是 `afterContent`,
|
|
44
|
+
* 所以它们与 Archive 一起排掉 legacy 组件行,恢复「归档在多选/删除之前」。
|
|
45
|
+
*/
|
|
46
|
+
export declare function createSessionMenuContribution(t: Translate, sessionList?: ObservableSnapshotLike<SessionsStateLike>): MenuContributionLike;
|
|
47
|
+
export {};
|
|
@@ -26,6 +26,9 @@ export interface ClientContextLike {
|
|
|
26
26
|
slots: SlotsLike;
|
|
27
27
|
/** 可选服务的读面(宿主自己的写法:`ctx.get('…')?.…`)。**缺省调用**:老宿主上没有这个方法。 */
|
|
28
28
|
get?(name: string): unknown;
|
|
29
|
+
/** 可选服务迟到时的安装面(`ctx.inject(['…'], child => …)`):服务就绪才回调,缺席就一直等。
|
|
30
|
+
* **缺省调用**:老宿主上没有这个方法。 */
|
|
31
|
+
inject?(deps: readonly string[], callback: (child: ClientContextLike) => unknown): unknown;
|
|
29
32
|
/** 挂一个随本插件卸载而撤销的副作用(返回的清理函数由框架接管)。
|
|
30
33
|
* 属性声明:测试会把它当成员读(`expect(ctx.effect).not.toHaveBeenCalled()`),实现不读 `this`。 */
|
|
31
34
|
effect: (execute: () => unknown, label?: string) => unknown;
|
|
@@ -25,6 +25,7 @@ declare const ZH: {
|
|
|
25
25
|
readonly 'trash.empty.hint': "在会话条目的「…」菜单里选「删除」,会话会先进到这里,随时可以恢复。";
|
|
26
26
|
readonly 'trash.summary': "{count} 个会话 · 可释放 {size}";
|
|
27
27
|
readonly 'trash.group.count': "{count} 个会话";
|
|
28
|
+
readonly 'trash.group.ungrouped': "未分组";
|
|
28
29
|
readonly 'trash.project.unknown': "未记录项目";
|
|
29
30
|
readonly 'trash.row.deletedNow': "刚刚删除";
|
|
30
31
|
readonly 'trash.row.deletedAgo': "删除于 {n}{unit}前";
|
|
@@ -37,7 +38,8 @@ declare const ZH: {
|
|
|
37
38
|
readonly 'trash.unit.months': "个月";
|
|
38
39
|
readonly 'trash.unit.years': "年";
|
|
39
40
|
readonly 'trash.row.restore': "恢复";
|
|
40
|
-
readonly 'trash.row.purge': "
|
|
41
|
+
readonly 'trash.row.purge': "彻底删除";
|
|
42
|
+
readonly 'trash.row.purgeOne': "彻底删除「{title}」";
|
|
41
43
|
readonly 'trash.footer.note': "回收站本体在宿主数据根下的 {dir} 里,不自动过期;清空后不可恢复。";
|
|
42
44
|
readonly 'trash.emptyAll': "清空回收站…";
|
|
43
45
|
readonly 'trash.confirm.all': "清空回收站?";
|
|
@@ -77,6 +79,22 @@ declare const ZH: {
|
|
|
77
79
|
readonly 'trash.attachments.skipped': "附件未清理:{reason}";
|
|
78
80
|
readonly 'trash.attachments.skippedPartial': "部分附件未清理:{reason}(已清理 {removed} 个)";
|
|
79
81
|
readonly 'trash.attachments.alsoFailed': ";另有 {count} 个附件删除失败";
|
|
82
|
+
readonly 'config.title': "可调项";
|
|
83
|
+
readonly 'config.hint': "保存后插件会重启;本页用保存回执回填,重开本页会重新读取。也可以直接改 profile 的 cordis.patch.yml。";
|
|
84
|
+
readonly 'config.orphanPreviewLimit': "孤儿附件预览条数";
|
|
85
|
+
readonly 'config.maxAttachmentBytes': "附件单次响应上限";
|
|
86
|
+
readonly 'config.maxJsonRefBytes': "附件引用扫描的单 JSON 上限";
|
|
87
|
+
readonly 'config.unit.items': "条";
|
|
88
|
+
readonly 'config.unit.bytes': "字节";
|
|
89
|
+
readonly 'config.loading': "正在读当前值…";
|
|
90
|
+
readonly 'config.loadFailed': "读取失败:{message}";
|
|
91
|
+
readonly 'config.save': "保存";
|
|
92
|
+
readonly 'config.saving': "保存中…";
|
|
93
|
+
readonly 'config.saved': "已保存;插件正在重启。";
|
|
94
|
+
readonly 'config.saveFailed': "保存失败:{message}";
|
|
95
|
+
readonly 'config.retry': "重试";
|
|
96
|
+
readonly 'config.invalid': "需为 {min}–{max} 的整数";
|
|
97
|
+
readonly 'config.unchanged': "没有改动";
|
|
80
98
|
readonly 'maintenance.title': "维护";
|
|
81
99
|
readonly 'maintenance.orphans.title': "孤儿会话";
|
|
82
100
|
readonly 'maintenance.orphans.hint': "父会话已不在会话列表或回收站里的子会话。检查只读,不会自动运行。";
|
|
@@ -105,8 +123,6 @@ declare const ZH: {
|
|
|
105
123
|
readonly 'detail.noBody': "这条会话没有可显示的内容";
|
|
106
124
|
readonly 'detail.field.created': "创建";
|
|
107
125
|
readonly 'detail.field.lastActivity': "最后活动";
|
|
108
|
-
readonly 'detail.field.deletedAt': "删除于";
|
|
109
|
-
readonly 'detail.deletedAt.combined': "{relative}({absolute})";
|
|
110
126
|
readonly 'detail.field.span': "活跃跨度";
|
|
111
127
|
readonly 'detail.field.size': "大小";
|
|
112
128
|
readonly 'detail.field.turns': "轮数";
|
|
@@ -138,8 +154,16 @@ declare const ZH: {
|
|
|
138
154
|
readonly 'detail.block.truncated': "已截断";
|
|
139
155
|
readonly 'detail.loadOlder': "加载更早";
|
|
140
156
|
readonly 'detail.loadOlder.loading': "正在加载更早…";
|
|
141
|
-
readonly 'detail.
|
|
142
|
-
readonly 'detail.
|
|
157
|
+
readonly 'detail.viewMode.label': "工作步骤展示";
|
|
158
|
+
readonly 'detail.viewMode.compact': "简洁";
|
|
159
|
+
readonly 'detail.viewMode.standard': "标准";
|
|
160
|
+
readonly 'detail.viewMode.detailed': "详细";
|
|
161
|
+
readonly 'detail.viewMode.verbose': "完全展开";
|
|
162
|
+
readonly 'detail.compact.empty': "这段会话只有过程内容;切到「标准」可以看到。";
|
|
163
|
+
readonly 'detail.compact.pageOnly': "这一页只有过程内容;点「加载更早」继续往前看。";
|
|
164
|
+
readonly 'detail.process.header': "工作步骤";
|
|
165
|
+
readonly 'detail.process.step': "{n} 步";
|
|
166
|
+
readonly 'detail.process.steps': "{n} 步";
|
|
143
167
|
readonly 'detail.injection.runtimeContext': "运行上下文";
|
|
144
168
|
readonly 'detail.injection.timeContext': "时间上下文";
|
|
145
169
|
readonly 'detail.injection.agentInstructions': "Agent 指令";
|
|
@@ -162,7 +186,8 @@ declare const ZH: {
|
|
|
162
186
|
readonly 'detail.attachment.saving': "保存中…";
|
|
163
187
|
readonly 'detail.attachment.saveFailed': "保存失败:{message}";
|
|
164
188
|
readonly 'detail.back': "← 返回清单";
|
|
165
|
-
readonly 'detail.subagents.header': "子会话({n}) · {size}";
|
|
189
|
+
readonly 'detail.subagents.header': "子会话({n}) · {size},随本条一起恢复或删除";
|
|
190
|
+
readonly 'detail.subagents.open': "查看子会话详情";
|
|
166
191
|
readonly 'detail.subagents.truncated': "仅列出前 {shown} 条,共 {total} 条";
|
|
167
192
|
readonly 'detail.backToParent': "← 返回父会话";
|
|
168
193
|
readonly 'detail.list.label': "回收站清单";
|
|
@@ -170,6 +195,7 @@ declare const ZH: {
|
|
|
170
195
|
readonly 'detail.role.assistant': "助手";
|
|
171
196
|
readonly 'menu.delete': "删除";
|
|
172
197
|
readonly 'menu.live': "会话正在运行,先停止再删除";
|
|
198
|
+
readonly 'menu.deleteLive': "删除(运行中)";
|
|
173
199
|
readonly 'dialog.title': "删除这个会话?";
|
|
174
200
|
readonly 'dialog.subagents': "含 {n} 个子会话,将一起移入回收站。";
|
|
175
201
|
readonly 'dialog.hint': "会话会移入回收站,之后可以随时恢复。彻底删除是回收站里的另一个动作。";
|
|
@@ -201,6 +227,13 @@ export declare const zhTranslate: Translate;
|
|
|
201
227
|
/**
|
|
202
228
|
* 注册字典并交回本插件的 `t`。服务不在时返回 `zhTranslate`(不抛、不 pending)。
|
|
203
229
|
*
|
|
230
|
+
* **空值判据同时认 `undefined` 与 `null`**:宿主 `ctx.get` 可以回 null(同一条窄镜像的读法见
|
|
231
|
+
* `main-view.ts` 的 `isReadyUiWorkspace`)。这里只判 `undefined` 的版本会把 null 当成"服务在",
|
|
232
|
+
* 下一句 `service.register` 抛错——而本函数跑在**任何槽位注册之前**,抛出去等于整个客户端条目
|
|
233
|
+
* 从页面消失(`apply` 只打一条 warn)。注册 / 绑定自身失败同样退回内置 zh:文案降级远好过没有插件。
|
|
234
|
+
* `bind` 在注册之后才跑,它失败时字典已挂上但没人用——留着无害(宿主只是多一份不用的词典),
|
|
235
|
+
* 不为它补撤销路径再引入新的失败面。
|
|
236
|
+
*
|
|
204
237
|
* 注册必须挂在 `ctx.effect` 上:`register` 返回的 disposer 是**官方契约**(卸载时要把字典摘掉),
|
|
205
238
|
* 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 "already has locale"。
|
|
206
239
|
*/
|
|
@@ -232,12 +265,14 @@ export declare function localeProbe(t: Translate): string;
|
|
|
232
265
|
*/
|
|
233
266
|
export declare function splitOn(sentence: string, inner: string): [string, string, string];
|
|
234
267
|
/**
|
|
235
|
-
* 绝对时间那一句串。清单行的 `title`、确认框里的meta
|
|
268
|
+
* 绝对时间那一句串。清单行的 `title`、确认框里的meta、详情的会话信息三处要的是同一个写法,
|
|
236
269
|
* 各自 `new Date(...).toLocaleString()` 一遍迟早会漂成两种样子。
|
|
237
270
|
*/
|
|
238
271
|
export declare function absoluteText(at: number): string;
|
|
239
272
|
/** 单位词本身(调用方把它填进各自语言的整句模板里,语序由各语言自己钉)。 */
|
|
240
273
|
export declare function unitWord(unit: RelativeTimeUnit, t: Translate): string;
|
|
274
|
+
/** 「工作步骤 N 步」:英文 n=1 要单数、中文两条同形;选哪条键只住这里,调用方只报数。 */
|
|
275
|
+
export declare function stepsText(n: number, t: Translate): string;
|
|
241
276
|
/** 「7分钟前」这类**不带谓语**的相对短语;`now` 桶不套模板——"刚刚前"不是话。 */
|
|
242
277
|
export declare function agoText(at: number, now: number, t: Translate): string;
|
|
243
278
|
export {};
|
|
@@ -9,8 +9,8 @@ import { type DeleteConfirmDeps } from './remote.js';
|
|
|
9
9
|
export declare const TARGET_CARD: {
|
|
10
10
|
readonly padding: "10px 12px";
|
|
11
11
|
readonly background: "var(--dsw-alias-bg-layer-2)";
|
|
12
|
-
readonly border: "0.5px solid var(--
|
|
13
|
-
readonly borderRadius: "var(--
|
|
12
|
+
readonly border: "0.5px solid var(--sd-border-l4)";
|
|
13
|
+
readonly borderRadius: "var(--sd-radius-lg)";
|
|
14
14
|
};
|
|
15
15
|
/** 菜单行写进现场的一条删除请求(槽位投影原样带过来,浏览器半不自己拼 id)。 */
|
|
16
16
|
export interface DeleteRequest {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 详情正文的**块视图词汇**:哪些块算过程内容、标准档怎么把连续过程块收成一组。
|
|
3
|
+
* 折叠状态与分页不在这里——这里只回答「这一页的块怎么读」。
|
|
4
|
+
*/
|
|
5
|
+
import type { TrashDetailBlockWire, TrashDetailInjectionBlockWire, TrashDetailTextBlockWire, TrashDetailToolBlockWire } from '../shared/wire.js';
|
|
6
|
+
/** 除用户与助手之外的块:思考、工具、注入、系统消息、压缩摘要——详情按过程内容对待。
|
|
7
|
+
* 文本块是一个 `kind` 联合的接口,`Exclude` 摘不掉其中两个成员;这里按 `kind` 收窄后并上另两种块。 */
|
|
8
|
+
export type DisclosureBlockWire = (TrashDetailTextBlockWire & {
|
|
9
|
+
kind: 'reasoning' | 'system' | 'compaction';
|
|
10
|
+
}) | TrashDetailInjectionBlockWire | TrashDetailToolBlockWire;
|
|
11
|
+
export declare function isDisclosureBlock(block: TrashDetailBlockWire): block is DisclosureBlockWire;
|
|
12
|
+
/** 这一批块里有没有对话内容(用户或助手);简洁档靠它决定还要不要继续往前翻。 */
|
|
13
|
+
export declare function hasDialogue(blocks: readonly TrashDetailBlockWire[]): boolean;
|
|
14
|
+
/** 标准档的线程单元:对话块照原位;**连续的**过程块收成一组。
|
|
15
|
+
* 组的 `id` 取它**后面那条对话块**的 id(没有对话块就 `after:end`)——prepend 更早一页只会
|
|
16
|
+
* 往组头添过程块,组的结束边界不变,已展开的组不会因为合并而收回。 */
|
|
17
|
+
export type ThreadItem = {
|
|
18
|
+
readonly kind: 'block';
|
|
19
|
+
readonly block: TrashDetailBlockWire;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'process';
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly blocks: readonly DisclosureBlockWire[];
|
|
24
|
+
};
|
|
25
|
+
export declare function threadItems(blocks: readonly TrashDetailBlockWire[]): ThreadItem[];
|
|
@@ -6,6 +6,9 @@ export declare const name = "@xrn1997/dsh-session-delete/client";
|
|
|
6
6
|
* **`locale` 也不在这里**:宿主自己的 `ui-sidebar` 是硬注入它的,但那是"宿主知道自己那一代"的前提;
|
|
7
7
|
* 本插件要跨代活着 ⇒ 走 `ctx.get('locale')` 可选读,缺席时降级到内置 zh 文案(见 `copy.ts` 头注)。
|
|
8
8
|
* 写进 inject 就等于"这一代没有 locale 就不让整个客户端条目激活",而那等于宿主进程死。
|
|
9
|
+
* **`contextMenus` 同理**:0.2.2+ 用 `ctx.get('contextMenus')` 探测,服务比本条目晚到时按
|
|
10
|
+
* `ctx.inject(['contextMenus'])` 的 scoped 等待切过去;旧宿主没有这个服务时回调不来,legacy 槽位留着。
|
|
11
|
+
* 条目自己的 `inject` 仍然只有 `slots`——把可能迟到的服务写进去等于让整个客户端条目 pending。
|
|
9
12
|
*/
|
|
10
13
|
export declare const inject: string[];
|
|
11
14
|
/**
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 插件设置页的「维护」卡(ADR-0021):那张槽位上的两张卡里,本文件只拥有下面这一张。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 两段都是**手动**的:检查 → 预览 → 清理,没有挂载即扫、没有定时器。卡片的职责只有呈现:
|
|
5
5
|
* 状态那一行、检查按钮、以及各段自己的横幅 / 失败 / 提示 / 确认框;读数与动作全归
|
|
6
|
-
* `src/client/use-maintenance.ts`。
|
|
6
|
+
* `src/client/use-maintenance.ts`。两张卡的先后与 `summary` 守卫在 `src/client/settings-cards.tsx`。
|
|
7
7
|
*
|
|
8
8
|
* 与回收站面板的区别:这里没有面板高度、没有清单与详情两栏、没有全局闸。控件只用
|
|
9
|
-
* `
|
|
9
|
+
* `settingsCardShell` / `Button` / `Modal` / `fileSizeText`,所以同一张卡放在插件详情页的任意宽度下都成立。
|
|
10
10
|
*
|
|
11
11
|
* 孤儿会话段直接复用 `OrphanMaintenance`(警告条 / 失败 / 中性提示 / 预览确认的既有呈现);
|
|
12
12
|
* 孤儿附件段是本文件里的 `AttachmentMaintenance`(横幅 + 失败 + 回执 + 缩略图预览与清理确认)。
|
|
@@ -15,6 +15,7 @@ import type { ReactElement } from 'react';
|
|
|
15
15
|
import { type Translate } from './copy.js';
|
|
16
16
|
import { type MaintenanceDeps } from './use-maintenance.js';
|
|
17
17
|
export interface MaintenanceSettingsProps {
|
|
18
|
+
/** 维护四件(读数与动作);可调项两件由占用者另发给「可调项」卡。 */
|
|
18
19
|
deps: MaintenanceDeps;
|
|
19
20
|
/** 文案(缺省 = 内置 zh;宿主 locale 服务缺席时也是它,见 `copy.ts`)。 */
|
|
20
21
|
t?: Translate;
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
* 浏览器半的取数通道:同源 fetch 宿主 `connection.fetch` 的 exact Fetch 路由(`/api` + 插件前缀)。
|
|
3
3
|
* 信封与失败原文在这一层收口。
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
5
|
+
* - 十段注册在宿主 `connection.fetch.register` 上,鉴权归宿主 `/api` bridge(ADR-0033);
|
|
6
6
|
* 没有旧前缀 fallback——支持面是 web UI 与桌面端,二者的标准组合都装 `client-connection`。
|
|
7
7
|
* 官方 remote.<namespace> 仍不可用(第三方拿不到 Typert 描述符的动态发现挂载点)。
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - 十段的**段名与方法**住 `src/shared/wire.ts` 的结构化段名表(`ROUTES` 每项 `{ path, method }`),
|
|
9
|
+
* 本文件只投影段名与方法(GET 段直接取 `ROUTES.x.method`,写段把它交给 `post()`)——方法字面量
|
|
10
|
+
* 不在本文件出现第二份;取数地址(官方 `/api` 前缀 + 文档挂载点)归 `src/client/api-base.ts`
|
|
11
|
+
* (根部署下与旧版逐字相同)。
|
|
12
|
+
* 写动作统一走 apply;`attachment` 是唯一二进制段,由 `src/client/attachment-view.tsx` 经同一投影
|
|
13
|
+
* 拼地址,不经过本文件的信封通道;`attachment-orphans`(纯读)与 `attachment-clean`(聚合写)是孤儿
|
|
14
|
+
* 附件的两段。
|
|
13
15
|
* - 信封与官方 unary 面同形:{ok:true,value} / {ok:false,error}。HTTP 往返本身失败才 reject;
|
|
14
16
|
* 业务失败以信封返回,由 unwrapRemoteResult 在注入面抛出。
|
|
15
17
|
* - error 在线上是 {code,message,details?} 普通对象;判别依据 isDSHRemoteError 住在
|
|
16
18
|
* src/shared/wire.ts,这里不 import 任何宿主客户端包。
|
|
17
19
|
*/
|
|
18
|
-
import { type ApplyAction, type ApplyOutcome, type ApplyValueByAction, type AttachmentCleanupWire, type AttachmentOrphanReportWire, type OrphanReportWire, type PurgeStatsWire, type RemoteFailure, type RemoteResult, type TrashDetailWire, type TrashRowWire } from '../shared/wire.js';
|
|
20
|
+
import { type ApplyAction, type ApplyOutcome, type ApplyValueByAction, type AttachmentCleanupWire, type AttachmentOrphanReportWire, type OrphanReportWire, type PurgeStatsWire, type RemoteFailure, type RemoteResult, type SessionDeleteConfigWire, type TrashDetailWire, type TrashRowWire } from '../shared/wire.js';
|
|
19
21
|
/**
|
|
20
22
|
* 回收站条目(`/list` 那一行)。**形状的家在 `shared/wire.ts`**(ADR-0003):这里只是别名,
|
|
21
23
|
* 所以浏览器半不再自己抄一遍字段;下面几处"只读面"的镜像同理。
|
|
@@ -55,13 +57,18 @@ export type RemoteOutcome<T> = {
|
|
|
55
57
|
*/
|
|
56
58
|
export declare function callRemote<T>(run: () => Promise<T>): Promise<RemoteOutcome<T>>;
|
|
57
59
|
/** 本插件前缀路由那套**响应信封**的窄镜像(不是宿主服务 `ctx.remote.*`——那条 typert 通道为什么用不了,见 `AGENTS.md` 的 `remote.<namespace>` 那条):
|
|
58
|
-
* 每个方法 resolve 成 `RemoteResult<T
|
|
60
|
+
* 每个方法 resolve 成 `RemoteResult<T>`。十个段名里这层走九个 JSON 段:`list` / `ping` / `config` / `orphans` / `detail` / `apply` / `empty` /
|
|
59
61
|
* `attachment-orphans` / `attachment-clean`;写动作统一走 `apply`(一组 id 进、逐条结果出);
|
|
60
62
|
* `/attachment` 是二进制段,由 `src/client/attachment-view.tsx` 直接拼地址。 */
|
|
61
63
|
export interface RawSessionDeleteRemote {
|
|
62
64
|
list(): Promise<RemoteResult<TrashEntryLike[]>>;
|
|
63
|
-
/** 通道的"在不在":一次零成本的 GET(探测专用,见 `src/api/dispatch.ts` 的 `ping` 支)。
|
|
64
|
-
|
|
65
|
+
/** 通道的"在不在":一次零成本的 GET(探测专用,见 `src/api/dispatch.ts` 的 `ping` 支)。
|
|
66
|
+
* `signal` 由探测方给:半开连接不能把 `activate` 永远吊在这里。 */
|
|
67
|
+
ping(signal?: AbortSignal): Promise<RemoteResult<string>>;
|
|
68
|
+
/** 读三只可调项的当前值:空体 POST(与 `/detail` 的“POST 表达读”同法)。 */
|
|
69
|
+
config(): Promise<RemoteResult<SessionDeleteConfigWire>>;
|
|
70
|
+
/** 写回三只可调项:走宿主 `configEditor.edit`,这一行会重启;失败以 `io` 信封回来。 */
|
|
71
|
+
saveConfig(config: SessionDeleteConfigWire): Promise<RemoteResult<SessionDeleteConfigWire>>;
|
|
65
72
|
/** 纯读:回收站之外的无主子会话扫描。它**不清理**——清理是 `apply` 的 `orphan-clean` 动作,
|
|
66
73
|
* 这一段只交回"有多少、在哪、多大"(读不到语料时报 `io`,调用方不许把它折成"零条残留")。 */
|
|
67
74
|
orphans(): Promise<RemoteResult<OrphanReportWire>>;
|
|
@@ -77,19 +84,34 @@ export interface RawSessionDeleteRemote {
|
|
|
77
84
|
empty(): Promise<RemoteResult<PurgeResultLike>>;
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
80
|
-
* exact Fetch 路由的 fetch
|
|
81
|
-
*
|
|
82
|
-
*
|
|
87
|
+
* exact Fetch 路由的 fetch 实现。**同源、带挂载点前缀**:不拼 host、不认 origin——页面本身就是从
|
|
88
|
+
* 宿主页面加载的,同源是这条通道的全部前提;路径只由 `api-base.ts` 从 `document.baseURI` 的目录
|
|
89
|
+
* 与官方 `/api` 前缀拼出(根部署下与旧版逐字相同,反代到 `/dsh/` 时得到 `/dsh/api/...`)。
|
|
90
|
+
* 鉴权归宿主 `/api` bridge(ADR-0033)。所以这条通道**只对**宿主页面打开过、带宿主身份的客户端
|
|
91
|
+
* 成立(凭据是那次访问换来的 cookie,不是插件自己的秘密)。
|
|
83
92
|
*/
|
|
84
93
|
export declare function createHttpRemote(): RawSessionDeleteRemote;
|
|
94
|
+
/** 探测默认上限:5s。超时经 `AbortController` 打断 fetch,`activate` 不会永远吊在一条半开连接上。 */
|
|
95
|
+
export declare const PROBE_TIMEOUT_MS = 5000;
|
|
96
|
+
/**
|
|
97
|
+
* 探测的可选覆盖。**生产调用方不传**:注册路径只用 {@link PROBE_TIMEOUT_MS}。
|
|
98
|
+
* 这一格存在是为了让「超时算探测不过」这条支能在测试里真的跑到——不然它要等 5 秒墙上时间。
|
|
99
|
+
*/
|
|
100
|
+
export interface ProbeOptions {
|
|
101
|
+
/** 单次往返的上限;超时经 `AbortController` 打断 fetch。 */
|
|
102
|
+
readonly timeoutMs?: number;
|
|
103
|
+
}
|
|
85
104
|
/**
|
|
86
105
|
* 探测唯一通道:一次 `GET /ping` 回成功信封(`pong`)才注册入口。
|
|
87
106
|
*
|
|
88
107
|
* - 401/403 是宿主 `/api` 的纯文本身份/归属失败:入口不出现,不装禁用态。
|
|
89
108
|
* - 失败信封 / 404 / 非信封同样按不可用处理:`/api` 网关对未知路径也可能回错误形状,
|
|
90
109
|
* 不能据此把入口摆出来让每个动作都失败。
|
|
110
|
+
* - **往返有 5s 上限**:半开连接或宿主没起来时 `activate` 不悬着;超时与网络失败同样是
|
|
111
|
+
* 「这一代不可用」,不自动重试(一次探测一个结论,刷新页面才会再探)。三种失败都在
|
|
112
|
+
* console 留下原因——入口不出现是有意的,但"为什么没出现"不能只活在脑子里。
|
|
91
113
|
*/
|
|
92
|
-
export declare function probeChannel(raw: RawSessionDeleteRemote): Promise<boolean>;
|
|
114
|
+
export declare function probeChannel(raw: RawSessionDeleteRemote, options?: ProbeOptions): Promise<boolean>;
|
|
93
115
|
/** `shared/wire.ts` 的 `ApplyOutcome`、扫描报告与附件回执从这里转出去——浏览器半只认 `remote.js` 这一扇门。 */
|
|
94
116
|
export type { ApplyAction, ApplyOutcome, AttachmentCleanupWire, AttachmentOrphanReportWire, OrphanReportWire, } from '../shared/wire.js';
|
|
95
117
|
/**
|
|
@@ -103,7 +125,7 @@ export type OneShotSuccess<A extends ApplyAction> = {
|
|
|
103
125
|
readonly value: Exclude<ApplyValueByAction[A], null>;
|
|
104
126
|
};
|
|
105
127
|
/**
|
|
106
|
-
* 注入面(`deps`)的 interface
|
|
128
|
+
* 注入面(`deps`)的 interface:**九个方法**(raw 层另有 `ping` 供探测;十段里的 `/attachment` 由
|
|
107
129
|
* `src/client/attachment-view.tsx` 直接拼地址,也不在这层)。
|
|
108
130
|
*
|
|
109
131
|
* **不再有 N=1 的便捷方法**:`delete` / `restore` / `countSubagents` 曾是同一段
|
|
@@ -113,6 +135,10 @@ export type OneShotSuccess<A extends ApplyAction> = {
|
|
|
113
135
|
*/
|
|
114
136
|
export interface SessionDeleteRemote {
|
|
115
137
|
list(): Promise<TrashEntryLike[]>;
|
|
138
|
+
/** 三只可调项的当前值(`apply` 归一后的那一份)。 */
|
|
139
|
+
config(): Promise<SessionDeleteConfigWire>;
|
|
140
|
+
/** 写回三只可调项:宿主 `configEditor.edit` 落盘并重启这一行;失败就是 throw。 */
|
|
141
|
+
saveConfig(config: SessionDeleteConfigWire): Promise<SessionDeleteConfigWire>;
|
|
116
142
|
/** 回收站之外的无主子会话报告(纯读)。读不到就是 throw —— 界面上那是**一次失败的检查**,
|
|
117
143
|
* 不是"零条残留"(把 `io` 折成空报告等于把"不知道"说成"没有")。 */
|
|
118
144
|
scanOrphans(): Promise<OrphanReportWire>;
|
|
@@ -126,6 +152,8 @@ export interface SessionDeleteRemote {
|
|
|
126
152
|
/** 清空回收站(聚合回执)。 */
|
|
127
153
|
emptyTrash(): Promise<PurgeResultLike>;
|
|
128
154
|
}
|
|
155
|
+
/** 插件设置页「可调项」卡只要这两件。 */
|
|
156
|
+
export type ConfigDeps = Pick<SessionDeleteRemote, 'config' | 'saveConfig'>;
|
|
129
157
|
/** 需要那一段 `/apply` 的组件只要这一段 deps(单条非写调用见 {@link applyOne},写动作走 `./write.js` 的 `runWrite`)。 */
|
|
130
158
|
export type OneShotDeps = Pick<SessionDeleteRemote, 'applyOutcomes'>;
|
|
131
159
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `plugins.bundle.config` 的槽位占用者:上「可调项」、下「维护」(ADR-0043 决定 4)。
|
|
3
|
+
*
|
|
4
|
+
* 本文件只有两件事:按槽位契约守住视图(`summary` 不渲染,理由见 ADR-0021),以及排两张卡的先后。
|
|
5
|
+
* 各自的呈现、状态机与读数仍归 `config-settings.tsx` 与 `maintenance-settings.tsx`。
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactElement } from 'react';
|
|
8
|
+
import { type Translate } from './copy.js';
|
|
9
|
+
import type { ConfigDeps } from './remote.js';
|
|
10
|
+
import type { MaintenanceDeps } from './use-maintenance.js';
|
|
11
|
+
export interface SettingsCardsProps {
|
|
12
|
+
/** 维护四件 + 可调项两件;只造维护四件的测试替身会让「可调项」卡整体缺席。 */
|
|
13
|
+
deps: MaintenanceDeps & Partial<ConfigDeps>;
|
|
14
|
+
/** 文案(缺省 = 内置 zh;宿主 locale 服务缺席时也是它,见 `copy.ts`)。 */
|
|
15
|
+
t?: Translate;
|
|
16
|
+
/** 宿主 `plugins.bundle.config` 的视图:`summary` 是插件行的一行摘要,两张卡都只属于详情页。 */
|
|
17
|
+
view?: 'summary' | 'page';
|
|
18
|
+
}
|
|
19
|
+
export declare function SettingsCards({ deps, t, view }: SettingsCardsProps): ReactElement | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件设置页两张卡共用的**视图词汇**:卡片外壳配方。
|
|
3
|
+
*
|
|
4
|
+
* 与 `trash-view.ts` 同一个理由:两张卡共用宽度、内距与底色,只有内容节奏(`gap`)不同——各写一份
|
|
5
|
+
* 就会一张改了另一张没改。按内容高、铺满宿主给的宽度,不假设自己住在中央面板里(没有 `height:100%`)。
|
|
6
|
+
*/
|
|
7
|
+
import type { CSSProperties } from 'react';
|
|
8
|
+
/** 设置页卡片外壳:`gap` 是这一张卡自己的内容节奏,其余两张贴一样。 */
|
|
9
|
+
export declare const settingsCardShell: (gap: number) => CSSProperties;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - 标题优先用清册标题,空串才回落到日志的 session/title。
|
|
7
7
|
* - 正文是**块**:用户/助手文字常显;思考、工具、注入、系统消息、压缩摘要默认折叠,折叠头给
|
|
8
8
|
* 标签 + 首行摘要(工具另给状态)。注入也铺进正文——这是对宿主 Chat 的有意偏离(它隐藏普通注入,
|
|
9
|
-
*
|
|
9
|
+
* 而这里的目标是「删之前看全」);条数另进计量行。
|
|
10
10
|
*
|
|
11
11
|
* 助手正文与思考走 Markdown,用户提问保持原话;工具参数/结果走等宽纯文本(限高可滚)。
|
|
12
12
|
* 附件随块位置渲染:用户气泡之上、助手正文之后、工具展开体末尾。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
1
|
+
import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
|
|
2
2
|
import type { TrashEntryLike } from './remote.js';
|
|
3
3
|
/**
|
|
4
4
|
* 导航要的面。`ordered` 是**屏幕上的顺序**(只含展开的组),由面板/选择面算好传进来——
|
|
@@ -22,6 +22,10 @@ export interface TrashNavInput {
|
|
|
22
22
|
toggleCheck: (entryId: string) => void;
|
|
23
23
|
back: () => void;
|
|
24
24
|
focusRow: (entryId: string) => void;
|
|
25
|
+
/** Esc 的作用域(面板根节点;生产入口必须传,测试可传空引用对象)。下钻时清单不在 DOM,
|
|
26
|
+
* 但详情在面板里;**焦点在面板外**的 Esc 不归清单——否则在侧栏或别的面板按 Esc 会退出下钻,
|
|
27
|
+
* 还会把焦点拽进回收站。根还没挂上时无从判断包含关系,Esc 不处理。 */
|
|
28
|
+
scope: RefObject<HTMLElement | null>;
|
|
25
29
|
}
|
|
26
30
|
export interface TrashNav {
|
|
27
31
|
/** `aria-activedescendant` 的那条 id:选中行不在 DOM 里时是 `null`。 */
|
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 回收站界面共用的**视图词汇**:卡片配方、清单行、危险红、项目名与分组的念法。
|
|
3
3
|
*
|
|
4
4
|
* 它从 `trash-panel.tsx` 拆出来,因为清单面板与两个确认框(`trash-confirm.tsx`)都要用这几样——
|
|
5
5
|
* 拆之前它们同住一个文件、只能靠"同一个文件里的常量"共享。拆之后两处读的是同一份,不是两份抄写。
|
|
6
6
|
*
|
|
7
7
|
* **配色与圆角一律走 token,字号是字面量 px**(宿主同类面也这么写):卡片用宿主设置面的底层 token
|
|
8
|
-
* (`bg-layer-2` + `border-l4` + `radius-lg
|
|
8
|
+
* (`bg-layer-2` + `border-l4` + `radius-lg`),清单行不铺底不描边、选中态只换宿主"选中"那一档底色。
|
|
9
9
|
*/
|
|
10
10
|
import type { CSSProperties } from 'react';
|
|
11
11
|
import type { Translate } from './copy.js';
|
|
12
12
|
import type { TrashEntryLike } from './remote.js';
|
|
13
13
|
/** 卡片配方:宿主设置面的 fill/stroke 底层 token + 官方 `lg` 圆角。 */
|
|
14
14
|
export declare const CARD: CSSProperties;
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* 清单条目行:与 `CARD` 同族但**不铺底、不描边**。
|
|
17
|
+
*
|
|
18
|
+
* 为什么和 `CARD` 分成两份而不改 `CARD`:`CARD` 还被读数失败块与附件回执块借用(`trash-panel.tsx`
|
|
19
|
+
* 的 `role="status"`、`attachment-view.tsx`),那一类是"要打断你视线的一块",带底色和边框才对;
|
|
20
|
+
* 清单行是"一列可以扫的东西",边框叠出来就是 2010 年代后台面板的长相。
|
|
21
|
+
*
|
|
22
|
+
* 悬停底色与动作列的低强调/提亮住在 `ui/layout.css`:inline style 表达不了 `:hover` /
|
|
23
|
+
* `:focus-within`,而 `ui.css` 是照宿主明文逐字抄的对照表,混进自造规则会把保真复核弄脏
|
|
24
|
+
* (同 `forms.css` 文件头给的理由)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const ROW: CSSProperties;
|
|
27
|
+
/**
|
|
28
|
+
* 选中行:底色用 inline style **故意压在** `layout.css` 的悬停底色之上——元素上的声明优先于
|
|
29
|
+
* 样式表里的 `:hover` 规则,选中与悬停同时成立时看到的是选中那一档,不会闪。
|
|
30
|
+
*/
|
|
31
|
+
export declare const ROW_SELECTED: CSSProperties;
|
|
17
32
|
/**
|
|
18
33
|
* **整页唯一那块实心红**的写法:`variant="primary"` + 在元素上覆盖 primary 那对填充 token。
|
|
19
34
|
* 宿主插件管理页的 danger 按钮就是这个写法(asar @21658274),所以 hover 仍走 `.primary:hover` 类。
|
|
35
|
+
*
|
|
36
|
+
* 覆盖的是**本插件别名层**(`--sd-*`,见 `ui.css` 头注)而不是宿主私有 token:别名在 `body` 上
|
|
37
|
+
* 就把 `var(--dsw-…, 兜底)` 算完并继承下来了,元素上再覆盖底层私有 token 已经传不进继承值。
|
|
20
38
|
*/
|
|
21
39
|
export declare const BULK_DANGER: CSSProperties;
|
|
22
40
|
/**
|
|
@@ -30,9 +48,20 @@ export declare function projectLabel(projectDir: string, t: Translate): string;
|
|
|
30
48
|
export interface ProjectGroup {
|
|
31
49
|
projectDir: string;
|
|
32
50
|
label: string;
|
|
51
|
+
/** 组头悬停的全文:未分组组用组名,其余用有损的原始 `projectDir`。 */
|
|
52
|
+
title: string;
|
|
53
|
+
/** 「未分组」组:工作区已不在账本里的条目都归这里,永远沉底。 */
|
|
54
|
+
ungrouped: boolean;
|
|
33
55
|
/** 组内最新一条的删除时间——组间排序的键。 */
|
|
34
56
|
latest: number;
|
|
35
57
|
entries: readonly TrashEntryLike[];
|
|
36
58
|
}
|
|
37
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* 「未分组」组的键:工作区已从宿主账本删掉(`workspaceMissing`)的条目都归这里。
|
|
61
|
+
* 哨兵值不与真实 `projectDir` 同形——空 cwd 已经由 `projectLabel` 念成「未记录项目」,
|
|
62
|
+
* 两者不是同一件事,不能撞键。
|
|
63
|
+
*/
|
|
64
|
+
export declare const UNGROUPED_PROJECT_DIR = "__dsh-session-delete__ungrouped";
|
|
65
|
+
/** 按项目分组:组内按删除时间倒序,组间按各组最新一条倒序(刚删掉的那个项目排在最上面)。
|
|
66
|
+
* 工作区已不存在的条目统一进「未分组」组,且该组永远沉底——它不是一个可比较的项目。 */
|
|
38
67
|
export declare function groupByProject(entries: readonly TrashEntryLike[], t: Translate): ProjectGroup[];
|