@xrn1997/dsh-session-delete 0.5.0 → 0.6.1
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/LICENSE +201 -201
- package/README.md +165 -141
- package/cordis.patch.yml +7 -3
- package/icon.svg +18 -18
- package/lib/client.js +749 -430
- package/lib/index.js +850 -484
- package/lib/types/api/attachment-orphans.d.ts +9 -3
- package/lib/types/api/attachment.d.ts +17 -7
- package/lib/types/api/connection-route.d.ts +23 -0
- package/lib/types/api/dispatch.d.ts +24 -17
- package/lib/types/api/route-table.d.ts +25 -9
- package/lib/types/client/api-base.d.ts +19 -0
- package/lib/types/client/batch-bar.d.ts +1 -1
- package/lib/types/client/batch-pick.d.ts +19 -0
- package/lib/types/client/batch-select.d.ts +7 -5
- package/lib/types/client/context-menu-actions.d.ts +47 -0
- package/lib/types/client/copy.d.ts +8 -0
- package/lib/types/client/delete-confirm.d.ts +2 -2
- package/lib/types/client/host-rows.d.ts +27 -32
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/main-view.d.ts +2 -1
- package/lib/types/client/maintenance-settings.d.ts +3 -1
- package/lib/types/client/pending-open.d.ts +17 -0
- package/lib/types/client/remote.d.ts +22 -19
- package/lib/types/client/store.d.ts +1 -7
- package/lib/types/client/trash-nav.d.ts +5 -1
- package/lib/types/client/trash-view.d.ts +3 -0
- package/lib/types/client/ui/MenuItemButton.d.ts +1 -1
- package/lib/types/client/ui/Toast.d.ts +4 -1
- package/lib/types/client/use-trash-panel-controller.d.ts +1 -1
- package/lib/types/config.d.ts +45 -0
- package/lib/types/deps.d.ts +8 -3
- package/lib/types/domain/closure.d.ts +3 -0
- package/lib/types/index.d.ts +18 -16
- package/lib/types/ports/attachments-port.d.ts +40 -0
- 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 +18 -5
- package/lib/types/trash/attachments.d.ts +17 -2
- package/lib/types/verbs/errors.d.ts +3 -1
- package/lib/types/verbs/orphan-subagents.d.ts +3 -2
- package/lib/types/verbs/permanent-removal.d.ts +10 -5
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +122 -117
- package/lib/types/api/request-trust.d.ts +0 -45
- package/lib/types/ports/connection-port.d.ts +0 -33
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* 孤儿附件的报告段:`GET /attachment-orphans`。
|
|
3
3
|
*
|
|
4
4
|
* 报告把扫描计划折成数量/字节 + 对象清单(`id`/`bytes`,供设置页预览缩略图;最多
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
5
|
+
* `src/config.ts` 的 `orphanPreviewLimit`(默认 200,ADR-0037),清理不受预览条数限制)。
|
|
6
|
+
* 扫描跳过(引用源或对象列表读不出)时**报告必须失败**——回一份 0 会让界面说"没有孤儿",
|
|
7
|
+
* 而这类失败恰恰是"不知道",不是"没有"。
|
|
7
8
|
* 清理是 `POST /attachment-clean`,由路由直接接到 `cleanOrphanImageAttachments`。
|
|
8
9
|
*/
|
|
9
10
|
import { type AttachmentReadDeps } from '../trash/attachments.js';
|
|
11
|
+
import type { SessionDeleteConfig } from '../config.js';
|
|
10
12
|
import type { AttachmentOrphanReportWire } from '../shared/wire.js';
|
|
11
|
-
|
|
13
|
+
/** 报告比扫描多读一只旋钮(预览条数);扫描本体仍只认 `maxJsonRefBytes`。 */
|
|
14
|
+
export interface AttachmentOrphanReportDeps extends AttachmentReadDeps {
|
|
15
|
+
config: AttachmentReadDeps['config'] & Pick<SessionDeleteConfig, 'orphanPreviewLimit'>;
|
|
16
|
+
}
|
|
17
|
+
export declare function reportAttachmentOrphans(deps: AttachmentOrphanReportDeps): Promise<AttachmentOrphanReportWire>;
|
|
@@ -2,22 +2,32 @@
|
|
|
2
2
|
* 附件原始字节(`GET /attachment`,全仓唯一的二进制响应段)。
|
|
3
3
|
*
|
|
4
4
|
* 四条硬口径:
|
|
5
|
-
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex
|
|
6
|
-
*
|
|
5
|
+
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex>`,路径不来自请求里的任何一截
|
|
6
|
+
* (`name` 只做下载名;媒体元数据只给宿主 `attachments` 服务做引用校验)。
|
|
7
7
|
* ② **kind 只认 image / file**:分别对应宿主附件库的 `objects`(图片)与 `file-objects`(普通文件);
|
|
8
8
|
* 不认 v2、tmp、cache,也不给"读任意文件"留入口。
|
|
9
|
-
* ③ **单文件 ≤ 20 MiB
|
|
10
|
-
*
|
|
11
|
-
* ④
|
|
12
|
-
* `attachment
|
|
9
|
+
* ③ **单文件 ≤ `maxAttachmentBytes`(默认 20 MiB,可配,ADR-0037)**:所有路径共用同一道
|
|
10
|
+
* `assertWithinLimit`;服务路径先按引用字节拦一道、再按实际字节复核,流式读在累计处停下。
|
|
11
|
+
* ④ **直接 fs 路径**按**魔数**给图片类型(PNG / JPEG / GIF / WebP),认不出就按下载处理
|
|
12
|
+
* (`application/octet-stream` + `attachment`),不猜、也不拿请求里的名字当类型来源;服务路径用
|
|
13
|
+
* 宿主归一化后的 `mediaType`(ADR-0035)。
|
|
14
|
+
*
|
|
15
|
+
* **读取优先走宿主 `attachments` 服务**(ADR-0035):`readImage` / `readFileStream` 会按记录里的
|
|
16
|
+
* 引用校验字节;`attachments` 缺席或请求里没有足够的引用元数据时,才退回直接 fs(旧宿主)。
|
|
17
|
+
* 两种"接不了这次读"(服务缺席 / 元数据不够)在调用方看来是同一个 `undefined`,不各写一条 fallback。
|
|
18
|
+
* 直接 fs 的路径仍只由 `attachmentPathOf(home, kind, id)` 推导。
|
|
13
19
|
*
|
|
14
20
|
* 请求本身不对(id/kind 形状)⇒ 400;对象不在 / 超限 / 读失败 ⇒ 200 + 信封(与其余段同一条错误面)。
|
|
15
21
|
*/
|
|
16
22
|
import type { FsPort } from '../ports/fs-port.js';
|
|
23
|
+
import type { SessionDeleteConfig } from '../config.js';
|
|
24
|
+
import type { AttachmentsPort } from '../ports/attachments-port.js';
|
|
17
25
|
import type { BinaryResponse } from './route-table.js';
|
|
18
|
-
/** 读附件只需要 stat + read
|
|
26
|
+
/** 读附件只需要 stat + read、宿主 `attachments` 读面与响应上限;执行面(removeDir/removeFile)不给。 */
|
|
19
27
|
export interface AttachmentReadPorts {
|
|
20
28
|
fs: Pick<FsPort, 'statFile' | 'readFile'>;
|
|
21
29
|
home: string;
|
|
30
|
+
attachments: AttachmentsPort;
|
|
31
|
+
config: Pick<SessionDeleteConfig, 'maxAttachmentBytes'>;
|
|
22
32
|
}
|
|
23
33
|
export declare function readAttachmentResponse(deps: AttachmentReadPorts, query: URLSearchParams): Promise<BinaryResponse>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Dispatch } from './dispatch.js';
|
|
2
|
+
/** 请求体字节上限:这条通道只收几个 id,1MiB 是"够用且不给自己开一条内存放大器"的量级。 */
|
|
3
|
+
export declare const JSON_BODY_MAX_BYTES: number;
|
|
4
|
+
/** 宿主 `connection.fetch.register` 的入参形状(只声明本适配器用到的成员)。 */
|
|
5
|
+
export interface ConnectionFetchRouteLike {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly methods: readonly ('GET' | 'HEAD' | 'POST')[];
|
|
8
|
+
readonly requestBody: 'buffered' | 'streaming';
|
|
9
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
10
|
+
}
|
|
11
|
+
/** 宿主 `connection` 服务的窄镜像:只要求 exact Fetch 注册点。 */
|
|
12
|
+
export interface ConnectionFetchServiceLike {
|
|
13
|
+
readonly fetch?: {
|
|
14
|
+
register(route: ConnectionFetchRouteLike): () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 注册九段 exact Fetch 路由。返回一个组合 disposer;调用方把它挂在自己的 effect 上。
|
|
19
|
+
* `connection` 是插件的硬依赖(web UI / 桌面端都装 `client-connection`);`fetch.register`
|
|
20
|
+
* 缺席属于宿主形状漂移,直接抛——静默返回空 disposer 会让插件「在但没有入口」。
|
|
21
|
+
* `dispatch` 与分发器同源。
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerConnectionRoutes(service: ConnectionFetchServiceLike, dispatch: Dispatch): () => Promise<void>;
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /dsh-session-delete-api 的前缀路由分发:只负责传输。
|
|
3
|
-
*
|
|
4
|
-
* 四件事:请求栅栏 → 查路由表 → 按段注入窄面 → 回信封。纯度、body 预读与读面的判据都在
|
|
5
|
-
* src/api/route-table.ts(方法也从那张表读,字面量家在 `shared/wire.ts` 的段名表;这里只按判据执行);
|
|
6
|
-
* 行投影在 src/api/trash-rows.ts。
|
|
7
|
-
*
|
|
8
|
-
* - 请求本身不对用 4xx(400 请求体 / 405 方法 / 404 未知路由 / 403 栅栏拒绝);
|
|
9
|
-
* 操作失败回 200 + {ok:false,error}。浏览器半先解信封,再看状态。
|
|
10
|
-
* - 请求栅栏(归属 / 身份)归 `src/api/request-trust.ts`,这里只按它的裁决写 403。
|
|
11
|
-
* - purity 为 chained 的请求串行执行,避免读清册/动文件/写清册交错。
|
|
12
|
-
*/
|
|
13
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
14
1
|
import type { DeleteDeps } from '../deps.js';
|
|
15
|
-
import { type
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
import { type BinaryResponse, type JsonBody, type RouteSpec, type SliceDeps } from './route-table.js';
|
|
3
|
+
/** 适配器归一后的一次请求:`rest` 是插件前缀之后的段名(如 `/list`)。 */
|
|
4
|
+
export interface ApiInput {
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly rest: string;
|
|
7
|
+
readonly search: URLSearchParams;
|
|
8
|
+
readonly body: JsonBody;
|
|
9
|
+
}
|
|
10
|
+
/** 适配器归一后的一次响应:JSON 信封或二进制字节,状态码与头由适配器写。 */
|
|
11
|
+
export type ApiResult = {
|
|
12
|
+
readonly kind: 'json';
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly body: unknown;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'binary';
|
|
17
|
+
readonly body: BinaryResponse;
|
|
18
|
+
};
|
|
19
|
+
/** 段名的 404 与方法的 405 只在这里判一次;空段名按插件前缀显示,避免 404 原文出现一个空串。 */
|
|
20
|
+
export declare function resolveRoute(method: string, rest: string): RouteSpec;
|
|
21
|
+
/** 四格只建一次:收窄对象在 dispatcher 构造期固定,请求路径只做引用选择。 */
|
|
18
22
|
export declare function slicesOf(full: DeleteDeps): SliceDeps;
|
|
19
|
-
|
|
23
|
+
/** 分发函数:查段 → 方法闸 → 读面 → 处理。 */
|
|
24
|
+
export type Dispatch = (input: ApiInput) => Promise<ApiResult>;
|
|
25
|
+
/** 路由执行器:查段 → 方法闸 → 读面 → 处理;返回归一结果。组合根只建一次。 */
|
|
26
|
+
export declare function createDispatcher(deps: DeleteDeps): Dispatch;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { type ApplyAction, type RouteName } from '../shared/wire.js';
|
|
15
15
|
import type { CoreDeps, DeleteDeps, RemovalDeps } from '../deps.js';
|
|
16
|
+
import type { AttachmentsPort } from '../ports/attachments-port.js';
|
|
16
17
|
import type { TrashEntry } from '../trash/entry.js';
|
|
17
18
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
18
19
|
export type BodyKind = 'none' | 'json';
|
|
@@ -33,29 +34,43 @@ export interface BinaryResponse {
|
|
|
33
34
|
export interface RouteRead {
|
|
34
35
|
rows(): Promise<readonly TrashEntry[]>;
|
|
35
36
|
}
|
|
36
|
-
/** deps 切片(ADR-0032
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
export type RouteSlice = 'core' | 'removal' | 'full';
|
|
37
|
+
/** deps 切片(ADR-0032):每一段只声明自己要的那一面。读与探测五段拿基础面,`attachment` 段另多一条
|
|
38
|
+
* 宿主 `attachments` 读面,两个删盘段多删除能力,`/apply` 是唯一拿全量面的行——段的 run 里长出超面
|
|
39
|
+
* 调用就是编译错,dispatch 也只按这一格注入(运行期收窄住 `src/api/dispatch.ts` 的 `slicesOf`)。 */
|
|
40
|
+
export type RouteSlice = 'core' | 'removal' | 'full' | 'attachment';
|
|
40
41
|
/** 切片名 → 依赖面:{@link RouteSpec.run} 的 deps 参数类型与 `depsFor` 的返回类型都从这一格取。 */
|
|
41
42
|
export type SliceDeps = {
|
|
42
43
|
core: CoreDeps;
|
|
43
44
|
removal: RemovalDeps;
|
|
44
45
|
full: DeleteDeps;
|
|
46
|
+
attachment: CoreDeps & {
|
|
47
|
+
readonly attachments: AttachmentsPort;
|
|
48
|
+
};
|
|
45
49
|
};
|
|
46
|
-
|
|
50
|
+
interface BaseRouteSpec<S extends RouteSlice> {
|
|
47
51
|
/** 这一行吃哪一格 deps:dispatch 按它注入,`run` 的 deps 参数类型也跟着它走。 */
|
|
48
52
|
readonly slice: S;
|
|
49
53
|
readonly method: 'GET' | 'POST';
|
|
50
54
|
readonly body: BodyKind;
|
|
51
55
|
readonly purity: Purity;
|
|
52
56
|
readonly read: ReadKind;
|
|
53
|
-
/**
|
|
54
|
-
|
|
57
|
+
/** 同源取数闸(ADR-0033):只有能被别的页面渲染消费的段需要;判据住 `src/api/connection-route.ts`。
|
|
58
|
+
* 缺省不设闸——JSON 响应跨源读不到(没有 CORS),不需要它。 */
|
|
59
|
+
readonly fetchGate?: 'same-origin';
|
|
60
|
+
}
|
|
61
|
+
/** JSON 段:处理函数拿请求体与读面;`query` 不在这里(九行里只有二进制段需要查询串)。 */
|
|
62
|
+
export interface JsonRouteSpec<S extends RouteSlice = RouteSlice> extends BaseRouteSpec<S> {
|
|
63
|
+
readonly response: 'json';
|
|
55
64
|
/** 声明成函数属性而不是方法:方法签名是双变的,显式标注更宽的 deps 能绕过切片闸;属性按逆变校验。 */
|
|
56
|
-
readonly run: (deps: SliceDeps[S], body: JsonBody, read: RouteRead
|
|
65
|
+
readonly run: (deps: SliceDeps[S], body: JsonBody, read: RouteRead) => Promise<unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** 二进制段:只有 `/attachment`;处理函数直接拿查询串,交回字节与安全头。 */
|
|
68
|
+
export interface BinaryRouteSpec<S extends RouteSlice = RouteSlice> extends BaseRouteSpec<S> {
|
|
69
|
+
readonly response: 'binary';
|
|
70
|
+
readonly run: (deps: SliceDeps[S], query: URLSearchParams) => Promise<BinaryResponse>;
|
|
57
71
|
}
|
|
58
|
-
|
|
72
|
+
export type RouteSpec<S extends RouteSlice = RouteSlice> = JsonRouteSpec<S> | BinaryRouteSpec<S>;
|
|
73
|
+
/** 九行的并集(表的值类型):每行由自己的 `slice` 与 `response` 字面量定型,`run` 的 deps 面随之收窄。 */
|
|
59
74
|
export type AnyRouteSpec = {
|
|
60
75
|
[S in RouteSlice]: RouteSpec<S>;
|
|
61
76
|
}[RouteSlice];
|
|
@@ -67,3 +82,4 @@ export declare const APPLY_ACTIONS: readonly ApplyAction[];
|
|
|
67
82
|
export declare const ROUTE_TABLE: Record<RouteName, AnyRouteSpec>;
|
|
68
83
|
/** 段名 → 那一行;不认识的段名回 `undefined`(调用方抛 404,并带出原路径)。 */
|
|
69
84
|
export declare function routeOf(rest: string): RouteSpec | undefined;
|
|
85
|
+
export {};
|
|
@@ -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;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 批量删除的浮条:**多选模式内常驻**(0 条也渲染,既显示模式又给退出入口),
|
|
3
3
|
* 汇总「已选 N 条」+(读得到时)「含 K 个子会话」;「查看已选」展开限高可滚动的标题清单
|
|
4
4
|
* (逐条可移除),回答"到底选了哪些"——清单默认收起,不把底部输入区盖住。
|
|
5
|
-
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `
|
|
5
|
+
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `batch-pick.ts` 的 `useSelectionRowClickGuard`)。
|
|
6
6
|
*
|
|
7
7
|
* 为什么单独一个文件而不是并进 `batch-select.tsx`:那份是"行开关控件",而浮条要点开确认框
|
|
8
8
|
* (要 import 它的现场)——两个方向合到一个文件上就是一圈。浮条住在
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PickItem } from './batch-selection-store.js';
|
|
2
|
+
/** 全选:把当前可见可选行按登记序追加;已在名单里的由 `addPicks` 自己跳过,原有选中保持在前。 */
|
|
3
|
+
export declare function selectAllVisible(): void;
|
|
4
|
+
/**
|
|
5
|
+
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
6
|
+
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
7
|
+
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
8
|
+
* - 其余(含 0 条)⇒ 全选。
|
|
9
|
+
*/
|
|
10
|
+
export declare function areAllVisibleRowsSelected(rows: readonly PickItem[], picks: readonly PickItem[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
13
|
+
*
|
|
14
|
+
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
15
|
+
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
16
|
+
* 认行靠 `data-row-key="session:…"`——**这是本文件唯一一处 DOM 读**(ADR-0034):宿主将来改掉这个
|
|
17
|
+
* 属性,最差是模式内点行不生效,勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
18
|
+
*/
|
|
19
|
+
export declare function useSelectionRowClickGuard(): void;
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 入口在每行的「⋯」菜单里(`batch-menu-item.tsx`):**按下才进入多选模式**,模式内才渲染勾选框、
|
|
6
6
|
* 才把整行点击变成"勾/取消";退出模式清空名单。选中集只住 `batch-selection-store.ts`(模式 + 名单一份
|
|
7
|
-
* 合并快照);"
|
|
8
|
-
* store
|
|
7
|
+
* 合并快照);"哪些行现在可见"住 `host-rows.ts`,"点行算不算选中"住 `batch-pick.ts`。四处隔着
|
|
8
|
+
* 槽位边界,只有模块级 store 能让他们看见同一份名单;本文件不持有任何现场。
|
|
9
9
|
*
|
|
10
10
|
* **为什么主勾选在行首**:宿主对行尾 `.rowActions` 是 `display:none`,只有 `row:hover` / 菜单打开
|
|
11
11
|
* 才出现(app.asar 0.2.0-rc.2 的 Rows 渲染);行首 16px 格在无状态点时是空的,模式内可见。
|
|
12
12
|
* 宿主在「有状态点(运行中/待确认/未读完成)」与归档行不渲染行首格 ⇒ 这些行走行尾悬停兜底,
|
|
13
|
-
* 但模式内**点整行**也能选(`
|
|
13
|
+
* 但模式内**点整行**也能选(`batch-pick.ts` 的 `useSelectionRowClickGuard`),所以不依赖那颗小框。
|
|
14
14
|
* 两条勾选不会同时出现(`ui.css` 的 `:has` 兜底隐藏规则,靠宿主行的 `data-row-key`)。
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import type { ReactElement } from 'react';
|
|
17
17
|
import { type Translate } from './copy.js';
|
|
18
18
|
import { type SessionDeleteRemote } from './remote.js';
|
|
19
19
|
/**
|
|
@@ -27,6 +27,8 @@ export interface BatchPickCheckboxProps {
|
|
|
27
27
|
sessionId: string;
|
|
28
28
|
/** 槽位只投 `sessionId`;标题由宿主侧的 root hook 读出来再传进来(见 `index.tsx` 的 `PickLeadingRow`)。 */
|
|
29
29
|
title: string;
|
|
30
|
+
/** 运行态同样由 root hook 读:登记表里的 `live` 与行尾兜底保持同一口径。 */
|
|
31
|
+
live?: boolean;
|
|
30
32
|
/** 文案(缺省 = 内置 zh,见 `copy.ts`)。 */
|
|
31
33
|
t?: Translate;
|
|
32
34
|
}
|
|
@@ -37,7 +39,7 @@ export interface BatchPickCheckboxProps {
|
|
|
37
39
|
* (行首格在宿主行那条 `onClick` 里面)。宿主对「有状态点」和归档行不渲染这一格,
|
|
38
40
|
* 那些行走 `BatchPickButton` 兜底;两条不会同时出现(见 `ui.css` 的兜底隐藏规则)。
|
|
39
41
|
*/
|
|
40
|
-
export declare function BatchPickCheckbox({ sessionId, title, t, }: BatchPickCheckboxProps): ReactElement | null;
|
|
42
|
+
export declare function BatchPickCheckbox({ sessionId, title, live, t, }: BatchPickCheckboxProps): ReactElement | null;
|
|
41
43
|
export interface BatchPickButtonProps {
|
|
42
44
|
sessionId: string;
|
|
43
45
|
title: string;
|
|
@@ -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 {};
|
|
@@ -170,6 +170,7 @@ declare const ZH: {
|
|
|
170
170
|
readonly 'detail.role.assistant': "助手";
|
|
171
171
|
readonly 'menu.delete': "删除";
|
|
172
172
|
readonly 'menu.live': "会话正在运行,先停止再删除";
|
|
173
|
+
readonly 'menu.deleteLive': "删除(运行中)";
|
|
173
174
|
readonly 'dialog.title': "删除这个会话?";
|
|
174
175
|
readonly 'dialog.subagents': "含 {n} 个子会话,将一起移入回收站。";
|
|
175
176
|
readonly 'dialog.hint': "会话会移入回收站,之后可以随时恢复。彻底删除是回收站里的另一个动作。";
|
|
@@ -201,6 +202,13 @@ export declare const zhTranslate: Translate;
|
|
|
201
202
|
/**
|
|
202
203
|
* 注册字典并交回本插件的 `t`。服务不在时返回 `zhTranslate`(不抛、不 pending)。
|
|
203
204
|
*
|
|
205
|
+
* **空值判据同时认 `undefined` 与 `null`**:宿主 `ctx.get` 可以回 null(同一条窄镜像的读法见
|
|
206
|
+
* `main-view.ts` 的 `isReadyUiWorkspace`)。这里只判 `undefined` 的版本会把 null 当成"服务在",
|
|
207
|
+
* 下一句 `service.register` 抛错——而本函数跑在**任何槽位注册之前**,抛出去等于整个客户端条目
|
|
208
|
+
* 从页面消失(`apply` 只打一条 warn)。注册 / 绑定自身失败同样退回内置 zh:文案降级远好过没有插件。
|
|
209
|
+
* `bind` 在注册之后才跑,它失败时字典已挂上但没人用——留着无害(宿主只是多一份不用的词典),
|
|
210
|
+
* 不为它补撤销路径再引入新的失败面。
|
|
211
|
+
*
|
|
204
212
|
* 注册必须挂在 `ctx.effect` 上:`register` 返回的 disposer 是**官方契约**(卸载时要把字典摘掉),
|
|
205
213
|
* 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 "already has locale"。
|
|
206
214
|
*/
|
|
@@ -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 {
|
|
@@ -1,54 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PickItem } from './batch-selection-store.js';
|
|
2
2
|
export interface HostRowInfo {
|
|
3
|
-
/**
|
|
3
|
+
/** 槽位宿主投影的标题(`PickLeadingRow` / `PickRow` 从 root hook 读);守卫只读登记,不读 DOM 文本。 */
|
|
4
4
|
readonly title: string;
|
|
5
5
|
/** 在跑的行点整行也不选:与置灰的勾选框同口径(级联遇在跑成员整组拒绝)。 */
|
|
6
6
|
readonly live: boolean;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 不该把快照翻新一遍。
|
|
9
|
+
* 行槽位开关登记一行:模式内与模式外都要登记(守卫进模式的那一帧就得有标题)。
|
|
10
|
+
* 每次登记算**多一个挂载实例**;不给 info 时只占位,随后由 {@link updateHostRowInfo} 填值。
|
|
12
11
|
*/
|
|
13
|
-
export declare function registerHostRowInfo(sessionId: string, info
|
|
14
|
-
/**
|
|
12
|
+
export declare function registerHostRowInfo(sessionId: string, info?: HostRowInfo): void;
|
|
13
|
+
/**
|
|
14
|
+
* 标题/运行态变了就地更新:**不注销、不重登记**,否则这一行会被挪到登记序末尾(ADR-0034 决定 2)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateHostRowInfo(sessionId: string, info: HostRowInfo): void;
|
|
17
|
+
/**
|
|
18
|
+
* 槽位组件共用的登记生命周期:挂载登记、卸载注销;标题/运行态变化只走更新口。
|
|
19
|
+
* 两次 effect 分工是顺序契约:先占住首次登记序,再填值。
|
|
20
|
+
*/
|
|
21
|
+
export declare function useHostRowRegistration(sessionId: string, info: HostRowInfo): void;
|
|
22
|
+
/** 行槽位开关卸载时调用:那一行已经离开渲染树,登记跟着走。
|
|
15
23
|
* 还有别的挂载实例在用同一行(重复行)就只减计数,不摘登记。 */
|
|
16
24
|
export declare function unregisterHostRowInfo(sessionId: string): void;
|
|
17
25
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
26
|
+
* 守卫与全选共用的一行读数:这一行此刻可勾选时交回登记(含标题);未登记或在跑回 `undefined`。
|
|
27
|
+
* 登记表是可见行的事实源,所以"没登记"就是"这一行此刻不在可见集合里"。
|
|
28
|
+
*/
|
|
29
|
+
export declare function pickableRowOf(sessionId: string): HostRowInfo | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* 「当前可见」的可选行:**已登记**的会话行(槽位开关还挂在渲染树上)——折叠组、归档过滤外的行
|
|
32
|
+
* 没有挂载,因此没有登记;在跑的行跳过(与置灰勾选框同口径)。标题/运行态来自登记值;
|
|
33
|
+
* 顺序是登记顺序(宿主按列表顺序挂载行)。
|
|
21
34
|
*
|
|
22
35
|
* 这是**当场读**:事件路径(点「全选」那一刻的页面)与测试用它;渲染侧读下面的快照。
|
|
23
36
|
*/
|
|
24
37
|
export declare function readVisibleSelectableRows(): PickItem[];
|
|
25
38
|
/**
|
|
26
|
-
*
|
|
39
|
+
* 渲染侧的快照:脏了才重读登记;干净时回同一个引用(React 的 `useSyncExternalStore` 契约)。
|
|
27
40
|
* 没变化时连续两次调用必须同引用,否则订阅方会被自己的快照更新推成死循环。
|
|
28
41
|
*/
|
|
29
42
|
export declare function getVisibleSelectableRowsSnapshot(): readonly PickItem[];
|
|
30
43
|
/**
|
|
31
|
-
* 渲染侧只读快照 +
|
|
32
|
-
*
|
|
44
|
+
* 渲染侧只读快照 + 登记订阅;`enabled === false` 不订阅也不读登记——行列表只随多选态存在,
|
|
45
|
+
* 模式外不必付订阅的成本。
|
|
33
46
|
*/
|
|
34
47
|
export declare function useVisibleSelectableRows(enabled: boolean): readonly PickItem[];
|
|
35
|
-
/**
|
|
36
|
-
export declare function selectAllVisible(): void;
|
|
37
|
-
/**
|
|
38
|
-
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
39
|
-
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
40
|
-
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
41
|
-
* - 其余(含 0 条)⇒ 全选。
|
|
42
|
-
*/
|
|
43
|
-
export declare function areAllVisibleRowsSelected(rows: readonly PickItem[], picks: readonly PickItem[]): boolean;
|
|
44
|
-
/**
|
|
45
|
-
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
46
|
-
*
|
|
47
|
-
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
48
|
-
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
49
|
-
* 认行靠 `data-row-key="session:…"`——宿主将来改掉这个属性,最差是模式内点行不生效,
|
|
50
|
-
* 勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
51
|
-
*/
|
|
52
|
-
export declare function useSelectionRowClickGuard(): void;
|
|
53
|
-
/** 测试专用:登记表清空并标脏(会话 id 不跨用例复用,名字/运行态别串味);观察者归订阅者管。 */
|
|
48
|
+
/** 测试专用:登记表清空并标脏(会话 id 不跨用例复用,名字/运行态别串味)。 */
|
|
54
49
|
export declare function resetHostRows(): void;
|
|
@@ -6,6 +6,8 @@ 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')` 探测,包本身在 package.json 的
|
|
10
|
+
* `dsh.client.inject` 里排到前面;旧宿主没有这个服务时回退 legacy 槽位。
|
|
9
11
|
*/
|
|
10
12
|
export declare const inject: string[];
|
|
11
13
|
/**
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* 所以关闭/重开是**一段生命周期**,不是一个 id:删除成功后 `afterDelete` 关掉当前
|
|
12
12
|
* 主视图并记下"这一条是我们关掉的";撤销成功后 `afterRestore` 只重开自己记过的那些。调用方不再
|
|
13
13
|
* 各自判定该不该关、也不再把会话 id 塞进撤销提示传一圈——判据全在这一处。两次重开排队时只有最新
|
|
14
|
-
*
|
|
14
|
+
* 那条会打开(等待本身住 `src/client/pending-open.ts` 的票与订阅),`resetMainView()` 清掉这份记忆
|
|
15
|
+
* 并取消在等的重开。
|
|
15
16
|
*
|
|
16
17
|
* **不 import 宿主客户端包**(AGENTS.md 红线):只通过 `ctx.get('uiWorkspace')` 可选读服务,
|
|
17
18
|
* 形状不符/服务缺席都安静退回,绝不影响插件激活。字段与方法名来自上面两个包的产物原文;
|
|
@@ -18,5 +18,7 @@ export interface MaintenanceSettingsProps {
|
|
|
18
18
|
deps: MaintenanceDeps;
|
|
19
19
|
/** 文案(缺省 = 内置 zh;宿主 locale 服务缺席时也是它,见 `copy.ts`)。 */
|
|
20
20
|
t?: Translate;
|
|
21
|
+
/** 宿主 `plugins.bundle.config` 的视图:`summary` 是插件行的一行摘要,本卡只属于详情页。 */
|
|
22
|
+
view?: 'summary' | 'page';
|
|
21
23
|
}
|
|
22
|
-
export declare function MaintenanceSettings({ deps, t }: MaintenanceSettingsProps): ReactElement;
|
|
24
|
+
export declare function MaintenanceSettings({ deps, t, view }: MaintenanceSettingsProps): ReactElement | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PendingOpenInput {
|
|
2
|
+
/** 订阅「清单变了」;返回退订。旧 store 可能没有——那时只有"已经出现"这一支能开。 */
|
|
3
|
+
readonly subscribe?: ((listener: () => void) => () => void) | undefined;
|
|
4
|
+
/** 目标是否已经出现在清单里。 */
|
|
5
|
+
readonly isReady: () => boolean;
|
|
6
|
+
/** 出现的这一拍还该不该抢焦点(用户可能已经选了别的会话)。 */
|
|
7
|
+
readonly canOpen: () => boolean;
|
|
8
|
+
/** 打开目标;调用方自己收宿主抛错。 */
|
|
9
|
+
readonly open: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface PendingOpen {
|
|
12
|
+
/** 取消在等的等待(没有也安全);后一次 `schedule` 会先取消前一次。 */
|
|
13
|
+
cancel(): void;
|
|
14
|
+
/** 等目标出现再打开;已经在清单里就立即打开。 */
|
|
15
|
+
schedule(input: PendingOpenInput): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createPendingOpen(giveUpMs?: number): PendingOpen;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 浏览器半的取数通道:同源 fetch
|
|
2
|
+
* 浏览器半的取数通道:同源 fetch 宿主 `connection.fetch` 的 exact Fetch 路由(`/api` + 插件前缀)。
|
|
3
|
+
* 信封与失败原文在这一层收口。
|
|
3
4
|
*
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
5
|
+
* - 九段注册在宿主 `connection.fetch.register` 上,鉴权归宿主 `/api` bridge(ADR-0033);
|
|
6
|
+
* 没有旧前缀 fallback——支持面是 web UI 与桌面端,二者的标准组合都装 `client-connection`。
|
|
7
|
+
* 官方 remote.<namespace> 仍不可用(第三方拿不到 Typert 描述符的动态发现挂载点)。
|
|
6
8
|
* - 九段的**段名与方法**住 `src/shared/wire.ts` 的结构化段名表(`ROUTES` 每项 `{ path, method }`),
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
+
* 附件的两段。
|
|
11
15
|
* - 信封与官方 unary 面同形:{ok:true,value} / {ok:false,error}。HTTP 往返本身失败才 reject;
|
|
12
16
|
* 业务失败以信封返回,由 unwrapRemoteResult 在注入面抛出。
|
|
13
17
|
* - error 在线上是 {code,message,details?} 普通对象;判别依据 isDSHRemoteError 住在
|
|
@@ -75,20 +79,19 @@ export interface RawSessionDeleteRemote {
|
|
|
75
79
|
empty(): Promise<RemoteResult<PurgeResultLike>>;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
+
* exact Fetch 路由的 fetch 实现。**同源、带挂载点前缀**:不拼 host、不认 origin——页面本身就是从
|
|
83
|
+
* 宿主页面加载的,同源是这条通道的全部前提;路径只由 `api-base.ts` 从 `document.baseURI` 的目录
|
|
84
|
+
* 与官方 `/api` 前缀拼出(根部署下与旧版逐字相同,反代到 `/dsh/` 时得到 `/dsh/api/...`)。
|
|
85
|
+
* 鉴权归宿主 `/api` bridge(ADR-0033)。所以这条通道**只对**宿主页面打开过、带宿主身份的客户端
|
|
86
|
+
* 成立(凭据是那次访问换来的 cookie,不是插件自己的秘密)。
|
|
82
87
|
*/
|
|
83
|
-
export declare function createHttpRemote(
|
|
88
|
+
export declare function createHttpRemote(): RawSessionDeleteRemote;
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
90
|
+
* 探测唯一通道:一次 `GET /ping` 回成功信封(`pong`)才注册入口。
|
|
86
91
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* 探测用一次零成本的 `GET /ping`,不读清单,避免每次页面加载都跑全表对账。
|
|
92
|
+
* - 401/403 是宿主 `/api` 的纯文本身份/归属失败:入口不出现,不装禁用态。
|
|
93
|
+
* - 失败信封 / 404 / 非信封同样按不可用处理:`/api` 网关对未知路径也可能回错误形状,
|
|
94
|
+
* 不能据此把入口摆出来让每个动作都失败。
|
|
92
95
|
*/
|
|
93
96
|
export declare function probeChannel(raw: RawSessionDeleteRemote): Promise<boolean>;
|
|
94
97
|
/** `shared/wire.ts` 的 `ApplyOutcome`、扫描报告与附件回执从这里转出去——浏览器半只认 `remote.js` 这一扇门。 */
|
|
@@ -117,7 +120,7 @@ export interface SessionDeleteRemote {
|
|
|
117
120
|
/** 回收站之外的无主子会话报告(纯读)。读不到就是 throw —— 界面上那是**一次失败的检查**,
|
|
118
121
|
* 不是"零条残留"(把 `io` 折成空报告等于把"不知道"说成"没有")。 */
|
|
119
122
|
scanOrphans(): Promise<OrphanReportWire>;
|
|
120
|
-
/** `.dsh`
|
|
123
|
+
/** `.dsh` 内无引用图片对象的报告(纯读;预览清单条数由 Node 半配置,默认 200)。读不到就是 throw,同样不许折成 0。 */
|
|
121
124
|
scanAttachmentOrphans(): Promise<AttachmentOrphanReportWire>;
|
|
122
125
|
/** 清掉报告那一刻无引用的图片副本(聚合回执)。 */
|
|
123
126
|
cleanAttachmentOrphans(): Promise<AttachmentCleanupWire>;
|
|
@@ -24,13 +24,7 @@ export interface LazyStore<T> {
|
|
|
24
24
|
invalidate: () => void;
|
|
25
25
|
subscribe: (listener: () => void) => () => void;
|
|
26
26
|
}
|
|
27
|
-
export
|
|
28
|
-
/** 第一个订阅者到来时跑(观察者在这里接上)。 */
|
|
29
|
-
onFirstSubscribe?(): void;
|
|
30
|
-
/** 最后一个订阅者退订时跑(观察者在这里断开)。 */
|
|
31
|
-
onLastUnsubscribe?(): void;
|
|
32
|
-
}
|
|
33
|
-
export declare function createLazyStore<T>(initial: T, read: () => T, lifecycle?: LazyStoreLifecycle): LazyStore<T>;
|
|
27
|
+
export declare function createLazyStore<T>(initial: T, read: () => T): LazyStore<T>;
|
|
34
28
|
/**
|
|
35
29
|
* 最新票句柄:每张票记着签发时的序号,只有序号仍等于当前的那张才 `current`。
|
|
36
30
|
* 调用方在发起异步读时 `issue()` 一张票,落地前问 `ticket.current`——旧读因此不会盖掉新状态。
|
|
@@ -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`。 */
|
|
@@ -17,6 +17,9 @@ export declare const CARD_SELECTED: CSSProperties;
|
|
|
17
17
|
/**
|
|
18
18
|
* **整页唯一那块实心红**的写法:`variant="primary"` + 在元素上覆盖 primary 那对填充 token。
|
|
19
19
|
* 宿主插件管理页的 danger 按钮就是这个写法(asar @21658274),所以 hover 仍走 `.primary:hover` 类。
|
|
20
|
+
*
|
|
21
|
+
* 覆盖的是**本插件别名层**(`--sd-*`,见 `ui.css` 头注)而不是宿主私有 token:别名在 `body` 上
|
|
22
|
+
* 就把 `var(--dsw-…, 兜底)` 算完并继承下来了,元素上再覆盖底层私有 token 已经传不进继承值。
|
|
20
23
|
*/
|
|
21
24
|
export declare const BULK_DANGER: CSSProperties;
|
|
22
25
|
/**
|