@xrn1997/dsh-session-delete 0.3.0 → 0.4.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 +21 -15
- package/lib/client.js +3317 -2173
- package/lib/index.js +1691 -964
- package/lib/types/api/attachment-orphans.d.ts +11 -0
- package/lib/types/api/attachment.d.ts +23 -0
- package/lib/types/api/dispatch.d.ts +9 -29
- package/lib/types/api/request-trust.d.ts +45 -0
- package/lib/types/api/route-table.d.ts +28 -14
- package/lib/types/client/attachment-view.d.ts +27 -0
- package/lib/types/client/batch-bar.d.ts +3 -2
- package/lib/types/client/batch-confirm.d.ts +3 -3
- package/lib/types/client/batch-menu-item.d.ts +1 -1
- package/lib/types/client/batch-select.d.ts +1 -1
- package/lib/types/client/busy-gate.d.ts +6 -0
- package/lib/types/client/confirm-close.d.ts +11 -0
- package/lib/types/client/copy.d.ts +48 -38
- package/lib/types/client/delete-confirm.d.ts +0 -2
- package/lib/types/client/host-rows.d.ts +18 -3
- package/lib/types/client/main-view.d.ts +8 -3
- package/lib/types/client/maintenance-parts.d.ts +25 -0
- package/lib/types/client/maintenance-settings.d.ts +22 -0
- package/lib/types/client/markdown.d.ts +9 -1
- package/lib/types/client/orphan-maintenance.d.ts +45 -0
- package/lib/types/client/receipt.d.ts +75 -5
- package/lib/types/client/remote.d.ts +49 -69
- package/lib/types/client/store.d.ts +31 -0
- package/lib/types/client/trash-confirm.d.ts +2 -15
- package/lib/types/client/trash-nav.d.ts +30 -0
- package/lib/types/client/trash-panel.d.ts +4 -3
- package/lib/types/client/ui/Checkbox.d.ts +1 -1
- package/lib/types/client/ui/Modal.d.ts +2 -2
- package/lib/types/client/ui/icons.d.ts +6 -11
- package/lib/types/client/ui/top-layer.d.ts +10 -0
- package/lib/types/client/undo-notices.d.ts +52 -0
- package/lib/types/client/undo-toast.d.ts +6 -52
- package/lib/types/client/use-column-resize.d.ts +9 -20
- package/lib/types/client/use-maintenance.d.ts +45 -0
- package/lib/types/client/use-panel-width.d.ts +1 -1
- package/lib/types/client/use-trash-panel-controller.d.ts +27 -23
- package/lib/types/client/use-trash-query.d.ts +39 -25
- package/lib/types/client/write-seats.d.ts +44 -0
- package/lib/types/client/write-settlement.d.ts +66 -0
- package/lib/types/client/write.d.ts +54 -0
- package/lib/types/deps.d.ts +20 -9
- package/lib/types/index.d.ts +8 -3
- package/lib/types/log/session-log.d.ts +4 -3
- package/lib/types/log/zstd-frames.d.ts +14 -8
- package/lib/types/ports/agent-handles.d.ts +1 -1
- package/lib/types/ports/connection-port.d.ts +33 -0
- package/lib/types/ports/fs-port.d.ts +13 -1
- package/lib/types/ports/host-port.d.ts +21 -63
- package/lib/types/ports/presence-port.d.ts +22 -0
- package/lib/types/shared/wire.d.ts +77 -26
- package/lib/types/trash/attachments.d.ts +85 -0
- package/lib/types/trash/detail.d.ts +29 -6
- package/lib/types/trash/entry.d.ts +3 -3
- package/lib/types/trash/groups.d.ts +18 -14
- package/lib/types/trash/ledger.d.ts +20 -0
- package/lib/types/trash/manifest.d.ts +2 -1
- package/lib/types/trash/reconcile.d.ts +4 -3
- package/lib/types/trash/storage-manifest.d.ts +1 -1
- package/lib/types/verbs/batch-carried.d.ts +20 -0
- package/lib/types/verbs/delete-session.d.ts +6 -5
- package/lib/types/verbs/errors.d.ts +4 -10
- package/lib/types/verbs/live-admission.d.ts +10 -0
- package/lib/types/verbs/member-journal.d.ts +25 -7
- package/lib/types/verbs/member-presence.d.ts +33 -24
- package/lib/types/verbs/move-group.d.ts +20 -0
- package/lib/types/verbs/orphan-subagents.d.ts +11 -5
- package/lib/types/verbs/permanent-removal.d.ts +31 -24
- package/lib/types/verbs/purge-trash.d.ts +21 -8
- package/lib/types/verbs/restore-session.d.ts +12 -16
- package/package.json +4 -2
- package/lib/types/client/write-commit.d.ts +0 -10
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 孤儿附件的报告段:`GET /attachment-orphans`。
|
|
3
|
+
*
|
|
4
|
+
* 报告把扫描计划折成数量/字节 + 对象清单(`id`/`bytes`,供设置页预览缩略图;最多
|
|
5
|
+
* `ORPHAN_PREVIEW_LIMIT` 条,清理不受预览条数限制)。扫描跳过(引用源或对象列表读不出)时
|
|
6
|
+
* **报告必须失败**——回一份 0 会让界面说"没有孤儿",而这类失败恰恰是"不知道",不是"没有"。
|
|
7
|
+
* 清理是 `POST /attachment-clean`,由路由直接接到 `cleanOrphanImageAttachments`。
|
|
8
|
+
*/
|
|
9
|
+
import { type AttachmentReadDeps } from '../trash/attachments.js';
|
|
10
|
+
import type { AttachmentOrphanReportWire } from '../shared/wire.js';
|
|
11
|
+
export declare function reportAttachmentOrphans(deps: AttachmentReadDeps): Promise<AttachmentOrphanReportWire>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 附件原始字节(`GET /attachment`,全仓唯一的二进制响应段)。
|
|
3
|
+
*
|
|
4
|
+
* 四条硬口径:
|
|
5
|
+
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex>`,路径由 `attachmentPathOf(home, kind, id)`
|
|
6
|
+
* 自己推导——请求里的任何一截都不参与路径拼接(`name` 只做下载名)。
|
|
7
|
+
* ② **kind 只认 image / file**:分别对应宿主附件库的 `objects`(图片)与 `file-objects`(普通文件);
|
|
8
|
+
* 不认 v2、tmp、cache,也不给"读任意文件"留入口。
|
|
9
|
+
* ③ **单文件 ≤ 20 MiB**:超过不读进内存,回 200 + JSON 失败信封。图片的 `onError` 会换成占位;
|
|
10
|
+
* 普通文件走 `<a download>`,没有页面内失败面——浏览器可能把这份错误响应存成文件。
|
|
11
|
+
* ④ 图片按**魔数**给类型(PNG / JPEG / GIF / WebP);认不出就按下载处理(`application/octet-stream` +
|
|
12
|
+
* `attachment`),不猜、也不拿请求里的名字当类型来源。
|
|
13
|
+
*
|
|
14
|
+
* 请求本身不对(id/kind 形状)⇒ 400;对象不在 / 超限 / 读失败 ⇒ 200 + 信封(与其余段同一条错误面)。
|
|
15
|
+
*/
|
|
16
|
+
import type { FsPort } from '../ports/fs-port.js';
|
|
17
|
+
import type { BinaryResponse } from './route-table.js';
|
|
18
|
+
/** 读附件只需要 stat + read:与 `AttachmentRemovalDeps` 同一条收窄纪律,执行面不给。 */
|
|
19
|
+
export interface AttachmentReadPorts {
|
|
20
|
+
fs: Pick<FsPort, 'statFile' | 'readFile'>;
|
|
21
|
+
home: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function readAttachmentResponse(deps: AttachmentReadPorts, query: URLSearchParams): Promise<BinaryResponse>;
|
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* /dsh-session-delete-api 的前缀路由分发:只负责传输。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 全宿主没有任何"按包发现再挂载"的动态机制 ⇒ 第三方要那个命名空间,
|
|
7
|
-
* 只能自挂自(要生成物 + `zod` + 改包布局),代价与收益完全不成比例。改用宿主**自己的 web server**
|
|
8
|
-
* 开一条插件专属前缀路由:真相/账本/判据全在宿主那边,这一层只搬运。
|
|
4
|
+
* 三件事:请求栅栏 → 查路由表 → 回信封。方法、纯度、body 预读与读面的判据都在
|
|
5
|
+
* src/api/route-table.ts(这里只按判据执行);行投影在 src/api/trash-rows.ts。
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - 前面四条口径(不依赖 Cordis / 信封与浏览器半同形 / 状态语义 / 同源栅栏)与到达序链(口径 ⑤)
|
|
15
|
-
* 仍住这里,因为它们是**传输**的事。
|
|
16
|
-
*
|
|
17
|
-
* 信封(口径 ②):`{ok:true,value}` / `{ok:false,error:{code,message,details?}}`。
|
|
18
|
-
* `error.code` 走 `src/remote/map-error.ts` 的**同一张表**(src/shared/wire.ts 的五个码),原文进
|
|
19
|
-
* `error.message`,结构化附带物进可选的 `error.details`。
|
|
20
|
-
*
|
|
21
|
-
* HTTP 状态不是第二套语义(口径 ③):**操作失败**时状态一律 200(含 `ok:false`)——我们与浏览器半之间的
|
|
22
|
-
* 契约是信封本身,判断成败只看 `ok`。**请求本身不对**才用 4xx(400 请求体 / 405 方法 / 404 未知路由),
|
|
23
|
-
* 同源栅栏拒的是**请求**而不是操作,回 403;浏览器半先解信封再看状态。
|
|
24
|
-
*
|
|
25
|
-
* 同源栅栏(口径 ④):本机回环上一条能删会话的写口不能被任意网页用 simple request CSRF 打——没有栅栏时
|
|
26
|
-
* `content-type: text/plain` 的跨源 POST 不触发预检、直接生效。放行的形状见 {@link isTrustedRequest}。
|
|
27
|
-
*
|
|
28
|
-
* 到达序链(口径 ⑤):`list`(含对账)与写动作都是"读清单 + 动文件 + 写清单"的多步,交错跑会互相踩
|
|
29
|
-
* ——删除刚把目录搬进回收站、行还没写上时,对账抢先把这行按目录"重建"出来,再被真实行覆盖成降级
|
|
30
|
-
* 元数据。串行的代价只是排队(单进程单用户,毫秒级);纯读不占这条链(哪几个段名纯、哪个动作纯,
|
|
31
|
-
* 由路由表说,不在这里列)。
|
|
7
|
+
* - 请求本身不对用 4xx(400 请求体 / 405 方法 / 404 未知路由 / 403 栅栏拒绝);
|
|
8
|
+
* 操作失败回 200 + {ok:false,error}。浏览器半先解信封,再看状态。
|
|
9
|
+
* - 请求栅栏(归属 / 身份)归 `src/api/request-trust.ts`,这里只按它的裁决写 403。
|
|
10
|
+
* - purity 为 chained 的请求串行执行,避免读清册/动文件/写清册交错。
|
|
32
11
|
*/
|
|
33
12
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
34
13
|
import type { DeleteDeps } from '../deps.js';
|
|
35
|
-
|
|
14
|
+
import { type RequestTrustFacts } from './request-trust.js';
|
|
15
|
+
export declare function createApiHandler(deps: DeleteDeps, trust: RequestTrustFacts): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求栅栏:判一条打到 `/dsh-session-delete-api` 的请求归不归本插件办。
|
|
3
|
+
*
|
|
4
|
+
* 两件事分开:**归属**(Host / Origin / Referer 是不是本次部署自己的)与**身份**(远程客户端是不是
|
|
5
|
+
* 宿主的已认证 Web 客户端)。绑定策略归宿主 webserver 配置——它只接受具体接口 IP 字面量、拒绝
|
|
6
|
+
* wildcard——所以「谁能到达」不是本插件能替它决定的事;栅栏本身只防 browser confused deputy,
|
|
7
|
+
* 身份留给宿主(见 ②)。口径与宿主 `/api` 的 `isTrustedApiRequest`(asar
|
|
8
|
+
* `dsh-client-connection/lib/index.js`)对齐,差别只有一条:本模块多查 Referer,因为
|
|
9
|
+
* `GET /attachment` 是二进制段(见末段)。
|
|
10
|
+
*
|
|
11
|
+
* 两条放行支,外加一条缺席规则:
|
|
12
|
+
* ① 头栅栏全过 ∧ 源地址回环 ∧ Host 属于本次部署(回环名 / 监听 bind 字面量)——本机工具与转发落在
|
|
13
|
+
* 回环的桌面壳;
|
|
14
|
+
* ② 宿主 `connection` 服务的 `requestRejection` 放行——远程浏览器。它自带宿主的 Host 白名单
|
|
15
|
+
* (含部署声明的 `trustedHosts`)与「已认证 live peer」,身份就是那次 `dsh web` 打印的 URL 换来的
|
|
16
|
+
* 签名 cookie,不在本插件里复刻;
|
|
17
|
+
* ③ 身份层缺席(`src/ports/connection-port.ts` 报 `absent`,宿主无 `connection` 服务或成员改名)
|
|
18
|
+
* ⇒ 远程一律拒(fail-closed:退回「只有本机」,LAN 上表现为 403 而不是静默放行)。
|
|
19
|
+
*
|
|
20
|
+
* **DNS rebinding 由 Host 归属判据挡**:`Origin === Host` 只证两者同名,不证这个名字属于本部署;
|
|
21
|
+
* 回环 socket 也不构成豁免(重绑到 `127.0.0.1` 的攻击正是回环源地址)。对浏览器来说 Host 是重绑伪造
|
|
22
|
+
* 不了的那个头——它按连接目标填,而 `Origin` 是页面自己的名字——所以归属判定先于任何本机 / 远程
|
|
23
|
+
* 分支,别在它前面加 fast-path。
|
|
24
|
+
*
|
|
25
|
+
* 末段:`GET /attachment` 的响应不是 opaque 的「读不出内容」——页面若已知某个 `sha256:…` 内容哈希,
|
|
26
|
+
* 就能把它当 `<img src>` 跨源渲染出来。那条请求不带 Origin,靠 Referer 判同源挡;宿主 `/api` 没有
|
|
27
|
+
* 二进制段,所以它的栅栏不需要这一条。
|
|
28
|
+
*/
|
|
29
|
+
import type { IncomingMessage } from 'node:http';
|
|
30
|
+
import type { ConnectionPort } from '../ports/connection-port.js';
|
|
31
|
+
export type RequestTrustVerdict = 'allowed' | 'untrusted' | 'unauthenticated';
|
|
32
|
+
export interface RequestTrustFacts {
|
|
33
|
+
/** 监听器绑定的那个具体接口 IP 字面量;老代次没有这个 getter 时为 `undefined`(bind 支退化,仍 fail-closed)。 */
|
|
34
|
+
readonly bindHost: string | undefined;
|
|
35
|
+
/** 宿主的 Web 身份面;端口每次请求惰性解析服务,缺席由它报 `absent`。 */
|
|
36
|
+
readonly connection: ConnectionPort;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 这条请求归不归本插件办。`untrusted` = 归属不对(跨源 / 重绑 / 头不成形);`unauthenticated` =
|
|
40
|
+
* 归属对但远程客户端没带宿主身份(原文会提示用 `dsh web` 打印的 URL 打开)。
|
|
41
|
+
*
|
|
42
|
+
* 远程支的两个失败裁决由端口翻译宿主的区分(401 = 缺身份、403 = 归属不对)。身份层缺席(端口报
|
|
43
|
+
* `absent`)一律按归属不对处理:那时没有可换的身份,不能让原文提示用户去补一个不存在的东西。
|
|
44
|
+
*/
|
|
45
|
+
export declare function judgeRequest(req: IncomingMessage, facts: RequestTrustFacts): RequestTrustVerdict;
|
|
@@ -1,35 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 路由表(ADR-0004):段名 → { 方法, 纯度, 读面, body, 处理 }。一条记录,两个消费者。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* - 该写的判成纯 ⇒ 写动作绕开到达序链,与对账交错,回收站出现"目录在、行是降级元数据"的世界;
|
|
9
|
-
* - body 该读没读 ⇒ 下游拿到 `null`;读了两遍 ⇒ 请求流已被消费(Node 的流只能消费一次)。
|
|
4
|
+
* 一致性要求:
|
|
5
|
+
* - 该纯的判成写 ⇒ 探测/正文读/扫描被堵在串行链后;
|
|
6
|
+
* - 该写的判成纯 ⇒ 写动作绕开到达序链,与对账交错;
|
|
7
|
+
* - body 该读没读 ⇒ 下游拿到 null;读两遍 ⇒ 流已被消费。
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `wire.ts`(两半的共同契约),这张表只回答"怎么处理"。
|
|
9
|
+
* 这张表不放 shared/wire.ts:处理函数会 import 动词与 node:fs,而浏览器半要 import wire.ts。
|
|
10
|
+
* 段名与动作仍住 wire.ts;这张表只回答“怎么处理”。
|
|
14
11
|
*/
|
|
15
12
|
import { ROUTES, type ApplyAction } from '../shared/wire.js';
|
|
16
13
|
import type { DeleteDeps } from '../deps.js';
|
|
14
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
17
15
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
18
16
|
export type BodyKind = 'none' | 'json';
|
|
19
17
|
/** 到达序:`pure` 不进链;`chained` 进链;函数形式给 `/apply` 用(`count` 是纯读)。 */
|
|
20
18
|
export type Purity = 'pure' | 'chained' | ((body: JsonBody) => 'pure' | 'chained');
|
|
21
|
-
|
|
19
|
+
/** 清册读面:`none` = 不读;`list` = 真相读(先对账);`raw` = 索引快照(不对账)。
|
|
20
|
+
* 它与纯度是两条独立的轴:`count` 是 pure/不读,`detail` 是 pure/raw,`ping` 是 pure/不读。 */
|
|
21
|
+
export type ReadKind = 'none' | 'list' | 'raw';
|
|
22
|
+
export type JsonBody = Record<string, unknown> | null;
|
|
23
|
+
/** 附件段的二进制落地:处理函数交回字节与安全头,分发器按它写 200(失败仍是 JSON 信封)。 */
|
|
24
|
+
export interface BinaryResponse {
|
|
25
|
+
readonly body: Buffer;
|
|
26
|
+
readonly contentType: string;
|
|
27
|
+
readonly disposition: 'inline' | 'attachment';
|
|
28
|
+
readonly filename?: string;
|
|
29
|
+
}
|
|
30
|
+
/** 路由交给处理函数的读能力:读段与 `/empty` 只能从这一面取行,不能自己挑 `list()` / `raw()`(`/apply` 的写动词读面归动词自己)。 */
|
|
31
|
+
export interface RouteRead {
|
|
32
|
+
rows(): Promise<readonly TrashEntry[]>;
|
|
33
|
+
}
|
|
22
34
|
export interface RouteSpec {
|
|
23
35
|
readonly method: 'GET' | 'POST';
|
|
24
36
|
readonly body: BodyKind;
|
|
25
37
|
readonly purity: Purity;
|
|
26
|
-
|
|
38
|
+
readonly read: ReadKind;
|
|
39
|
+
/** 响应形态:`json` 走信封(`{ok:true,value}`);`binary` 直接把处理函数的 `BinaryResponse` 写出去。 */
|
|
40
|
+
readonly response: 'json' | 'binary';
|
|
41
|
+
run(deps: DeleteDeps, body: JsonBody, read: RouteRead, query: URLSearchParams): Promise<unknown>;
|
|
27
42
|
}
|
|
28
43
|
export declare const APPLY_ACTIONS: readonly ApplyAction[];
|
|
29
44
|
/**
|
|
30
|
-
*
|
|
45
|
+
* 九个段名各一行。`Record<段名, RouteSpec>` 是闸:`wire.ts` 加了段名而这里没加,编译不过。
|
|
31
46
|
*/
|
|
32
47
|
export declare const ROUTE_TABLE: Record<(typeof ROUTES)[keyof typeof ROUTES], RouteSpec>;
|
|
33
48
|
/** 段名 → 那一行;不认识的段名回 `undefined`(调用方抛 404,并带出原路径)。 */
|
|
34
49
|
export declare function routeOf(rest: string): RouteSpec | undefined;
|
|
35
|
-
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type TrashDetailAttachmentWire } from '../shared/wire.js';
|
|
2
|
+
import type { Translate } from './copy.js';
|
|
3
|
+
/**
|
|
4
|
+
* 一张附件的取数地址。**只从校验过的 id 与 kind 派生**:不接受路径、不接受任意查询串,
|
|
5
|
+
* 所以调用面把 URL 交给 `<img src>` / `<a href>` 时不存在"读任意文件"的入口。
|
|
6
|
+
*/
|
|
7
|
+
export declare function attachmentUrl(a: TrashDetailAttachmentWire): string;
|
|
8
|
+
/**
|
|
9
|
+
* 一条消息的全部附件。顺序照日志,不重排:用户先发图后发文件,读起来应是他当时的顺序。
|
|
10
|
+
*
|
|
11
|
+
* `align` 是这一行的对齐(默认 `end`,与用户气泡同侧):用户行靠右、助手行靠左,条跟着自己那条消息走。
|
|
12
|
+
* `failed` 按 `attachmentId` 记:同一个内容哈希在一段会话里可能被引用多次,裂一次就都不再画破图。
|
|
13
|
+
* 四个 `useState` 都在早退之前调用——附件从有到无时 hook 数量不变。
|
|
14
|
+
*/
|
|
15
|
+
export declare function AttachmentStrip({ attachments, t, align, }: {
|
|
16
|
+
attachments: readonly TrashDetailAttachmentWire[];
|
|
17
|
+
t: Translate;
|
|
18
|
+
/** `end`(默认)= 靠右,用户气泡那一侧;`start` = 靠左,助手正文那一侧。 */
|
|
19
|
+
align?: 'start' | 'end';
|
|
20
|
+
}): import("react").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* 附件清理回执那一行提示(「N 个附件未能清理」/「附件未清理:原因」)。表现层只收一句已经拼好的话;
|
|
23
|
+
* 什么时候该有这句话、失败与跳过怎么拼,归接线的那一侧(判据住 `receipt.ts` 与 `use-trash-query.ts`)。
|
|
24
|
+
*/
|
|
25
|
+
export declare function AttachmentNotice({ message }: {
|
|
26
|
+
message: string | null;
|
|
27
|
+
}): import("react").JSX.Element | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的浮条:**多选模式内常驻**(0 条也渲染,既显示模式又给退出入口),
|
|
3
3
|
* 汇总「已选 N 条」+(读得到时)「含 K 个子会话」;「查看已选」展开限高可滚动的标题清单
|
|
4
4
|
* (逐条可移除),回答"到底选了哪些"——清单默认收起,不把底部输入区盖住。
|
|
5
5
|
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `host-rows.ts` 的 `useSelectionRowClickGuard`)。
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* all belong here")。
|
|
11
11
|
*
|
|
12
12
|
* 那个 K 是**勾选变化后防抖 300ms 的一次批量 count**(`/apply` 的 `action:'count'`,一组 id 一次),
|
|
13
|
-
*
|
|
13
|
+
* 并且带**最新票守卫**:在飞的旧响应不许盖掉新名单的读数(compare-and-set 住 `createLatestWins`,
|
|
14
|
+
* 防抖本身仍在下面这个 effect 里)。
|
|
14
15
|
* 读不到就整句不说——K 是合计,缺一条就是假数(「不许拿 0 冒充没有」)。
|
|
15
16
|
*/
|
|
16
17
|
import { type ReactElement } from 'react';
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的确认框——挂在 `shell.overlay` 上的宿主,加它读的那个模块级现场。
|
|
3
3
|
*
|
|
4
4
|
* 为什么又开一个文件而不是把 `delete-confirm.tsx` 撑大:单删那张框是**已交付的面**(docs/ui-spec.md 状态 ②,
|
|
5
5
|
* 用例钉着),批量形与它只有三件不同——标题随条数、目标卡逐条列、执行是一次批量 `apply`。共用一个壳
|
|
6
6
|
* 意味着单删的 `busy` / 失败面要跟着学批量形,那是拿已交付的行为去换省一个文件。卡片样式(`TARGET_CARD`)
|
|
7
7
|
* 与 hint、子会话那两句**复用**单删的,一句不造第二个键。
|
|
8
8
|
*
|
|
9
|
-
* 现场是模块级的(同 `delete-confirm.tsx` / `undo-
|
|
9
|
+
* 现场是模块级的(同 `delete-confirm.tsx` / `undo-notices.ts`):请求从浮条那颗按钮发来,确认框住在
|
|
10
10
|
* `shell.overlay` 上——它必须活在行开关那棵子树之外(槽位行随宿主重排而卸载,见 `delete-confirm.tsx` 头注)。
|
|
11
11
|
*/
|
|
12
12
|
import { type ReactElement } from 'react';
|
|
13
13
|
import { type Translate } from './copy.js';
|
|
14
|
-
import {
|
|
14
|
+
import type { SessionDeleteRemote } from './remote.js';
|
|
15
15
|
import { type PickItem } from './batch-selection-store.js';
|
|
16
16
|
export interface BatchDeleteRequest {
|
|
17
17
|
/** 每次发布递增:换一批就重挂载,busy / 失败面不带着上一批的记忆。 */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 写入在飞时,确认框的关闭/取消都拦住:一次批量在飞时关掉框,用户会以为"没动",而它其实正在动。
|
|
2
|
+
* 各张确认框(单删 / 批量删除 / 回收站恢复与彻底删除 / 孤儿会话清理 / 孤儿附件清理)共用这一条规则;
|
|
3
|
+
* `Modal` 的遮罩 / X / Esc 都走同一个 `onClose`。
|
|
4
|
+
*
|
|
5
|
+
* 返回的 `closeDisabled` 与 `close` 同源:取消键的置灰读数也从这里取,各组件不再各写一遍
|
|
6
|
+
* `disabled={busy}`——否则"拦住"这件事会有两个判据。 */
|
|
7
|
+
export interface IdleClose {
|
|
8
|
+
close(): void;
|
|
9
|
+
readonly closeDisabled: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function useIdleClose(busy: boolean, onClose: () => void): IdleClose;
|
|
@@ -1,39 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 界面文案与宿主 locale 服务的接入。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (宿主技能 `cordis-plugin-development/references/ui-plugin.md`)。宿主自己那套就是这么做的:
|
|
11
|
-
* `ui-sidebar` 声明 `inject = ['slots','layout','uiWorkspace','locale','shortcuts']`,并在
|
|
12
|
-
* `ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries')` 里注册字典
|
|
13
|
-
* (真产物原文)。所以本插件也把文案收成一份带**命名空间**的字典,`zh` 是完整真源、`en` 逐键镜像
|
|
14
|
-
* ——宿主 `register` 的**对象形式要求两套内置语言都齐**(缺一个键就是运行期报错)。
|
|
15
|
-
*
|
|
16
|
-
* ## 服务是**可选读**的,不进 `inject`
|
|
17
|
-
*
|
|
18
|
-
* `ctx.get('locale')` 而不是把 `locale` 写进 `inject`。理由是本仓那条硬约束的另一面:**客户端半的
|
|
19
|
-
* 条目激活失败 = 宿主进程死**(前端启动自检 `web boot: N entry did not activate` ⇒ 桌面壳
|
|
20
|
-
* `reportFatal('web-boot')`)。宿主自己的 `ui-sidebar` 敢硬注入 `locale`(它是宿主的一部分,知道
|
|
21
|
-
* 那一行一定在),而本插件要跨宿主代数活着 ⇒ 服务缺席时**降级到内置 zh 文案继续工作**,
|
|
22
|
-
* 好过整个进程死。这与宿主对可选服务的官方写法同形(`ctx.get('…')?.…`)。
|
|
23
|
-
*
|
|
24
|
-
* 取数面:[`dsh-client-locale`] 的 `LocaleRuntime.register(ns, { zh, en })` 返回一个 disposer
|
|
25
|
-
* (必须挂进 `ctx.effect`,否则卸载后字典留在注册表里,下次挂载会撞 "already has locale"),
|
|
26
|
-
* `bind(ns)` 交回一个**按命名空间记忆化**的 `t(key, params)`(官方明说它"retain stable identity …
|
|
27
|
-
* so they can ride inject surfaces without breaking memoization"——所以我们把它塞进槽位的
|
|
28
|
-
* `inject` 载荷是正当用法)。
|
|
29
|
-
*
|
|
30
|
-
* ## 面板行那一行字为什么要用 thunk
|
|
31
|
-
*
|
|
32
|
-
* `sidebar.panellist` 的行名字由宿主 `syncPanels` 逐字 `label: resolveSlotLabel(options.label) ?? id`
|
|
33
|
-
* 解析,而 `resolveSlotLabel` 的实现是 `typeof label === 'function' ? label() : label`,
|
|
34
|
-
* 其 JSDoc 原文即 "thunks follow the active locale";宿主自己还挂了
|
|
35
|
-
* `ctx.effect(() => ctx.locale.subscribe(syncPanels), 'ui-sidebar: panel labels')` 在语言切换时重读。
|
|
36
|
-
* ⇒ 面板名传 `() => t('trash.title')` 就随语言走;**换成活计数就会 stale**(只在条目或语言变化时重读)。
|
|
4
|
+
* - zh 是完整真源,en 逐键镜像。
|
|
5
|
+
* - locale 服务通过 ctx.get('locale') 可选读,服务缺席时退回内置 zh;为什么不进 inject 见
|
|
6
|
+
* docs/architecture.md §3。
|
|
7
|
+
* - 面板行名传 thunk,语言切换时宿主会重读;其余文案走 t(key, params)。
|
|
8
|
+
* - 渠道错误原文(src/client/receipt.ts 的 missing-id/幂等空值、src/client/remote.ts 的取数通道错误)
|
|
9
|
+
* 不在此字典,它们是协议诊断,固定中文。
|
|
37
10
|
*/
|
|
38
11
|
import { type RelativeTimeUnit } from './ui/format.js';
|
|
39
12
|
import type { ClientContextLike } from './context.js';
|
|
@@ -50,7 +23,7 @@ declare const ZH: {
|
|
|
50
23
|
readonly 'trash.readFailed': "回收站读不到";
|
|
51
24
|
readonly 'trash.empty.title': "回收站是空的";
|
|
52
25
|
readonly 'trash.empty.hint': "在会话条目的「…」菜单里选「删除」,会话会先进到这里,随时可以恢复。";
|
|
53
|
-
readonly 'trash.summary': "{count} 个会话 ·
|
|
26
|
+
readonly 'trash.summary': "{count} 个会话 · 可释放 {size}";
|
|
54
27
|
readonly 'trash.group.count': "{count} 个会话";
|
|
55
28
|
readonly 'trash.project.unknown': "未记录项目";
|
|
56
29
|
readonly 'trash.row.deletedNow': "刚刚删除";
|
|
@@ -70,7 +43,7 @@ declare const ZH: {
|
|
|
70
43
|
readonly 'trash.confirm.all': "清空回收站?";
|
|
71
44
|
readonly 'trash.confirm.one': "彻底删除 1 个会话?";
|
|
72
45
|
readonly 'trash.confirm.one.meta': "{project} · 删除于 {at} · {size}";
|
|
73
|
-
readonly 'trash.confirm.body': "这会从回收站移除文件,{strong}
|
|
46
|
+
readonly 'trash.confirm.body': "这会从回收站移除文件,{strong}。只清理会话独占的图片附件副本(共享副本保留);宿主投影缓存与你磁盘上的原图不受影响。";
|
|
74
47
|
readonly 'trash.confirm.body.strong': "之后没有任何副本可以恢复";
|
|
75
48
|
readonly 'trash.confirm.purge': "彻底删除";
|
|
76
49
|
readonly 'trash.toolbar.collapseAll': "全部折叠";
|
|
@@ -92,14 +65,40 @@ declare const ZH: {
|
|
|
92
65
|
readonly 'trash.orphans.meta': "共 {size},来自 {parents} 个失踪的父会话";
|
|
93
66
|
readonly 'trash.orphans.open': "查看并清理";
|
|
94
67
|
readonly 'trash.orphans.confirm': "清理 {count} 个无主子会话?";
|
|
95
|
-
readonly 'trash.orphans.body': "
|
|
68
|
+
readonly 'trash.orphans.body': "这些子会话的父会话已经不在会话列表或回收站里,清理后不可恢复。正在运行的子会话会被拒绝,不会被删除。只清理会话独占的图片附件副本(共享副本保留);宿主投影缓存与你磁盘上的原图不受影响。若剩余引用源读不出,本次附件清理会跳过(会话目录仍会删除)。";
|
|
96
69
|
readonly 'trash.orphans.group': "{project} · {count} 个 · {size}";
|
|
97
|
-
readonly 'trash.orphans.cacheNote': "
|
|
70
|
+
readonly 'trash.orphans.cacheNote': "本次会一并清理会话独占的图片附件副本;宿主投影缓存不包含在内。若剩余引用源读不出,本次附件清理会跳过(会话目录仍会删除)。";
|
|
98
71
|
readonly 'trash.orphans.clean': "清理";
|
|
99
72
|
readonly 'trash.orphans.failure': "残留检查/清理失败:{message}";
|
|
100
73
|
readonly 'trash.orphans.cleanFailed': "{count} 组未清理:{message}";
|
|
101
74
|
readonly 'trash.orphans.cleanPartial': "已先删掉 {removed} 个会话目录({size})";
|
|
102
75
|
readonly 'trash.orphans.cleanGone': "有 {count} 组已不在了(可能已被别处清理,或父会话已恢复)";
|
|
76
|
+
readonly 'trash.attachments.failed': "已删除会话,但 {count} 个附件未能清理";
|
|
77
|
+
readonly 'trash.attachments.skipped': "附件未清理:{reason}";
|
|
78
|
+
readonly 'trash.attachments.skippedPartial': "部分附件未清理:{reason}(已清理 {removed} 个)";
|
|
79
|
+
readonly 'trash.attachments.alsoFailed': ";另有 {count} 个附件删除失败";
|
|
80
|
+
readonly 'maintenance.title': "维护";
|
|
81
|
+
readonly 'maintenance.orphans.title': "孤儿会话";
|
|
82
|
+
readonly 'maintenance.orphans.hint': "父会话已不在会话列表或回收站里的子会话。检查只读,不会自动运行。";
|
|
83
|
+
readonly 'maintenance.attachments.title': "孤儿附件";
|
|
84
|
+
readonly 'maintenance.attachments.hint': "在 .dsh 里没有任何引用的图片副本。检查只读,清理只动这些副本。";
|
|
85
|
+
readonly 'maintenance.scan': "检查";
|
|
86
|
+
readonly 'maintenance.scanning': "检查中…";
|
|
87
|
+
readonly 'maintenance.cleaning': "清理中…";
|
|
88
|
+
readonly 'maintenance.checkFailed': "检查失败";
|
|
89
|
+
readonly 'maintenance.notScanned': "尚未检查";
|
|
90
|
+
readonly 'maintenance.none': "没有发现";
|
|
91
|
+
readonly 'maintenance.orphans.count': "{count} 个";
|
|
92
|
+
readonly 'maintenance.attachments.count': "{count} 个";
|
|
93
|
+
readonly 'maintenance.attachments.scanFailed': "检查失败:{message}";
|
|
94
|
+
readonly 'maintenance.attachments.cleaned': "已清理 {count} 个({size})";
|
|
95
|
+
readonly 'maintenance.attachments.cleanFailed': "{count} 个附件未能清理(已清理 {removed} 个)";
|
|
96
|
+
readonly 'maintenance.attachments.cleanError': "附件清理失败:{message}";
|
|
97
|
+
readonly 'maintenance.attachments.confirm': "查看并清理 {count} 个孤儿附件?";
|
|
98
|
+
readonly 'maintenance.attachments.body': "只清理 .dsh 内无引用的图片副本;源文件不受影响;共享引用保留。";
|
|
99
|
+
readonly 'maintenance.attachments.previewTruncated': "仅预览前 {shown} 个;清理会处理全部 {total} 个。";
|
|
100
|
+
readonly 'maintenance.attachments.previewHint': "点图片可放大;关闭这个框不会删除任何东西。";
|
|
101
|
+
readonly 'maintenance.attachments.clean': "清理这些附件";
|
|
103
102
|
readonly 'detail.notSelected': "选中一条会话,看它讲了什么";
|
|
104
103
|
readonly 'detail.loading': "正在读这条会话…";
|
|
105
104
|
readonly 'detail.readFailed': "这条会话的正文读不出来";
|
|
@@ -107,6 +106,7 @@ declare const ZH: {
|
|
|
107
106
|
readonly 'detail.field.created': "创建";
|
|
108
107
|
readonly 'detail.field.lastActivity': "最后活动";
|
|
109
108
|
readonly 'detail.field.deletedAt': "删除于";
|
|
109
|
+
readonly 'detail.deletedAt.combined': "{relative}({absolute})";
|
|
110
110
|
readonly 'detail.field.span': "活跃跨度";
|
|
111
111
|
readonly 'detail.field.size': "大小";
|
|
112
112
|
readonly 'detail.field.turns': "轮数";
|
|
@@ -124,6 +124,16 @@ declare const ZH: {
|
|
|
124
124
|
readonly 'detail.value.spanDays': "{n} 天";
|
|
125
125
|
readonly 'detail.messages.hidden': "另有 {count} 条宿主注入的上下文未显示";
|
|
126
126
|
readonly 'detail.messages.omitted': "还有 {count} 条未显示";
|
|
127
|
+
readonly 'detail.image': "[图片]";
|
|
128
|
+
readonly 'detail.image.alt': "[图片 · {alt}]";
|
|
129
|
+
readonly 'detail.attachment.file': "文件";
|
|
130
|
+
readonly 'detail.attachment.preview': "附件预览";
|
|
131
|
+
readonly 'detail.attachment.alt': "附件图片";
|
|
132
|
+
readonly 'detail.attachment.open': "打开图片「{name}」";
|
|
133
|
+
readonly 'detail.attachment.unavailable': "附件不可用";
|
|
134
|
+
readonly 'detail.attachment.save': "另存为";
|
|
135
|
+
readonly 'detail.attachment.saving': "保存中…";
|
|
136
|
+
readonly 'detail.attachment.saveFailed': "保存失败:{message}";
|
|
127
137
|
readonly 'detail.back': "← 返回清单";
|
|
128
138
|
readonly 'detail.list.label': "回收站清单";
|
|
129
139
|
readonly 'detail.role.user': "你";
|
|
@@ -26,8 +26,6 @@ export declare function requestDelete(input: {
|
|
|
26
26
|
sessionId: string;
|
|
27
27
|
title: string;
|
|
28
28
|
}): void;
|
|
29
|
-
/** 收起确认框(用户点「取消」/「关闭」、或删除成功后收工)。 */
|
|
30
|
-
export declare function clearDeleteRequest(): void;
|
|
31
29
|
/** 供宿主组件订阅。 */
|
|
32
30
|
export declare function useDeleteRequest(): DeleteRequest | null;
|
|
33
31
|
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
@@ -5,14 +5,29 @@ export interface HostRowInfo {
|
|
|
5
5
|
/** 在跑的行点整行也不选:与置灰的勾选框同口径(级联遇在跑成员整组拒绝)。 */
|
|
6
6
|
readonly live: boolean;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* 行尾兜底勾选开关登记一行:模式内与模式外都要登记(守卫进模式的那一帧就得有标题)。
|
|
10
|
+
* 只有标题/运行态**真的变了**才标脏:重渲染时的同值登记不该把快照翻新一遍。
|
|
11
|
+
*/
|
|
9
12
|
export declare function registerHostRowInfo(sessionId: string, info: HostRowInfo): void;
|
|
10
13
|
/**
|
|
11
14
|
* 「当前可见」的可选行:侧栏里**已渲染**的会话行(`data-row-key="session:…"`)——折叠组、
|
|
12
15
|
* 归档过滤外的行不在 DOM 里,宿主隐藏的 subagent 也不在;在跑的行跳过(与置灰勾选框同口径)。
|
|
13
16
|
* 标题/运行态来自行尾兜底登记的 `rowInfo`(模式内它对每个非空行都渲染);DOM 序就是侧栏顺序。
|
|
17
|
+
*
|
|
18
|
+
* 这是**当场读**:事件路径(点「全选」那一刻的页面)与测试用它;渲染侧读下面的快照。
|
|
14
19
|
*/
|
|
15
20
|
export declare function readVisibleSelectableRows(): PickItem[];
|
|
21
|
+
/**
|
|
22
|
+
* 渲染侧的快照:脏了才查 DOM;干净时回同一个引用(React 的 `useSyncExternalStore` 契约)。
|
|
23
|
+
* 没变化时连续两次调用必须同引用,否则订阅方会被自己的快照更新推成死循环。
|
|
24
|
+
*/
|
|
25
|
+
export declare function getVisibleSelectableRowsSnapshot(): readonly PickItem[];
|
|
26
|
+
/**
|
|
27
|
+
* 渲染侧只读快照 + DOM 观察订阅;首个订阅者起开始观察,最后一个走了断开。
|
|
28
|
+
* `enabled === false` 不订阅也不读 DOM——行列表只随多选态存在,模式外不必付观察的成本。
|
|
29
|
+
*/
|
|
30
|
+
export declare function useVisibleSelectableRows(enabled: boolean): readonly PickItem[];
|
|
16
31
|
/** 全选:把当前可见可选行按 DOM 序追加;已在名单里的由 `addPicks` 自己跳过,原有选中保持在前。 */
|
|
17
32
|
export declare function selectAllVisible(): void;
|
|
18
33
|
/**
|
|
@@ -21,7 +36,7 @@ export declare function selectAllVisible(): void;
|
|
|
21
36
|
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
22
37
|
* - 其余(含 0 条)⇒ 全选。
|
|
23
38
|
*/
|
|
24
|
-
export declare function
|
|
39
|
+
export declare function areAllVisibleRowsSelected(rows: readonly PickItem[], picks: readonly PickItem[]): boolean;
|
|
25
40
|
/**
|
|
26
41
|
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
27
42
|
*
|
|
@@ -31,5 +46,5 @@ export declare function allVisibleSelected(): boolean;
|
|
|
31
46
|
* 勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
32
47
|
*/
|
|
33
48
|
export declare function useSelectionRowClickGuard(): void;
|
|
34
|
-
/**
|
|
49
|
+
/** 测试专用:登记表清空并标脏(会话 id 不跨用例复用,名字/运行态别串味);观察者归订阅者管。 */
|
|
35
50
|
export declare function resetHostRows(): void;
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* 清掉——**除非把常驻实例本身丢掉**。宿主自己的归档走的就是这条路:`clearArchivedCurrent()`
|
|
9
9
|
* 发现当前会话进了归档集就 `clearMain()`(`dsh-client-ui-workspace/lib/client.js`)。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* 所以关闭/重开是**一段生命周期**,不是一个 id:删除成功后 `afterDelete` 关掉当前
|
|
12
12
|
* 主视图并记下"这一条是我们关掉的";撤销成功后 `afterRestore` 只重开自己记过的那些。调用方不再
|
|
13
13
|
* 各自判定该不该关、也不再把会话 id 塞进撤销提示传一圈——判据全在这一处。两次重开排队时只有最新
|
|
14
|
-
*
|
|
14
|
+
* 那条会打开(最新票守卫),`resetMainView()` 清掉这份记忆并让在等的重开作废。
|
|
15
15
|
*
|
|
16
16
|
* **不 import 宿主客户端包**(AGENTS.md 红线):只通过 `ctx.get('uiWorkspace')` 可选读服务,
|
|
17
17
|
* 形状不符/服务缺席都安静退回,绝不影响插件激活。字段与方法名来自上面两个包的产物原文;
|
|
@@ -31,7 +31,12 @@ export declare function afterDelete(sessionIds: readonly string[]): void;
|
|
|
31
31
|
* 撤销恢复成功后调用:把**我们自己关掉的**那几个会话重新打开(只认 `afterDelete` 记过的)。
|
|
32
32
|
*
|
|
33
33
|
* 一批里只有最后一条会开(`targets[targets.length - 1]`):主视图一次只装一个会话,而撤销恢复
|
|
34
|
-
* 的成功条数可能多于一条;`openWhenListed`
|
|
34
|
+
* 的成功条数可能多于一条;`openWhenListed` 的最新票守卫保证前一条的等待不会后落地抢焦点。
|
|
35
35
|
* 任何更新的删除/恢复都会先作废旧等待;这里只负责「这一次要开哪一条」。
|
|
36
36
|
*/
|
|
37
37
|
export declare function afterRestore(sessionIds: readonly string[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* 面板恢复已经替这些会话完成了“回来”的结算:只忘掉我们关过的那几条,不重开、不抢焦点。
|
|
40
|
+
* 它不动 `reopen`:同一会话不会既在等重开、又躺在回收站里等面板恢复;在等的重开属于另一条动作。
|
|
41
|
+
*/
|
|
42
|
+
export declare function forgetMainView(sessionIds: readonly string[]): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 维护卡两段共用的两块皮:警告条外壳与失败块。
|
|
3
|
+
*
|
|
4
|
+
* 两段(孤儿会话 / 孤儿附件)在结构上只差正文:图标、底色、动作按钮与失败块的「原文 + 重试」
|
|
5
|
+
* 都一样。收在这里,改布局或按钮材质时不必在同一条卡里改两遍;正文仍由各段自己给。
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
8
|
+
import type { Translate } from './copy.js';
|
|
9
|
+
interface MaintenanceChromeProps {
|
|
10
|
+
/** 给测试与界面规格一个稳定的块名;缺省不写 `data-*`。 */
|
|
11
|
+
slot?: string;
|
|
12
|
+
busy: boolean;
|
|
13
|
+
t: Translate;
|
|
14
|
+
/** 旧面板块用过的下边距;设置页段内间距由父级 gap 给,默认不加。 */
|
|
15
|
+
gapBelow?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function MaintenanceBanner({ slot, busy, t, gapBelow, onOpen, children, }: MaintenanceChromeProps & {
|
|
18
|
+
onOpen: () => void;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}): ReactElement;
|
|
21
|
+
export declare function MaintenanceFailure({ slot, message, busy, t, gapBelow, onRetry, }: MaintenanceChromeProps & {
|
|
22
|
+
message: string;
|
|
23
|
+
onRetry: () => void;
|
|
24
|
+
}): ReactElement;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件设置页的「维护」卡(`plugins.bundle.config` 的占用者)。
|
|
3
|
+
*
|
|
4
|
+
* 两张段都是**手动**的:检查 → 预览 → 清理,没有挂载即扫、没有定时器。卡片的职责只有呈现:
|
|
5
|
+
* 状态那一行、检查按钮、以及各段自己的横幅 / 失败 / 提示 / 确认框;读数与动作全归
|
|
6
|
+
* `src/client/use-maintenance.ts`。
|
|
7
|
+
*
|
|
8
|
+
* 与回收站面板的区别:这里没有面板高度、没有清单与详情两栏、没有全局闸。控件只用
|
|
9
|
+
* `CARD` / `Button` / `Modal` / `fileSizeText`,所以同一张卡放在插件详情页的任意宽度下都成立。
|
|
10
|
+
*
|
|
11
|
+
* 孤儿会话段直接复用 `OrphanMaintenance`(警告条 / 失败 / 中性提示 / 预览确认的既有呈现);
|
|
12
|
+
* 孤儿附件段是本文件里的 `AttachmentMaintenance`(横幅 + 失败 + 回执 + 缩略图预览与清理确认)。
|
|
13
|
+
*/
|
|
14
|
+
import type { ReactElement } from 'react';
|
|
15
|
+
import { type Translate } from './copy.js';
|
|
16
|
+
import { type MaintenanceDeps } from './use-maintenance.js';
|
|
17
|
+
export interface MaintenanceSettingsProps {
|
|
18
|
+
deps: MaintenanceDeps;
|
|
19
|
+
/** 文案(缺省 = 内置 zh;宿主 locale 服务缺席时也是它,见 `copy.ts`)。 */
|
|
20
|
+
t?: Translate;
|
|
21
|
+
}
|
|
22
|
+
export declare function MaintenanceSettings({ deps, t }: MaintenanceSettingsProps): ReactElement;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
import type { Translate } from './copy.js';
|
|
1
2
|
import './ui/styles.js';
|
|
2
3
|
/**
|
|
3
4
|
* `memo` 是必需的而不是优化:`react-markdown@10.1.0` 里**一处 memoization 都没有**(读实
|
|
4
5
|
* `node_modules/react-markdown/lib/index.js:175-178`——`Markdown()` 每次调用都 `createProcessor(options)`
|
|
5
6
|
* 再 `runSync(parse(file))`,`MarkdownHooks` 那一支也一样),而面板任何一次重渲(悬停、↑↓、
|
|
6
7
|
* 「刚刚删除」那句要读 `Date.now()`)都会走到这里 ⇒ 不按住就是每条助手回复每拍重解析一遍。
|
|
7
|
-
*
|
|
8
|
+
* 按住的条件是 `text` 与**探针 `localeKey`**(调用方在渲染时算好的一句固定键译文 `t('common.close')`)。
|
|
9
|
+
* `t` 本身是宿主 `bind` 出来的**稳定身份**(`copy.ts` 头注),一次客户端激活只造一个——拿身份当判据,
|
|
10
|
+
* 换语言就漏了:同一个 `t` 换 locale 后吐另一套词,烘进正文树的那句图片占位会滞留旧语言,而周围的
|
|
11
|
+
* 标签那一拍已经改口。探针也不能在 comparator 里现调 `t` 取——宿主 `t` 读的是**当前** locale,
|
|
12
|
+
* prev/next 那一拍都取到新值、恒等;只有渲染时算好存进 props 的那份才是换语言那一刻的读数。
|
|
13
|
+
* 探针没变 = 同语言同 `t` ⇒ 跳过重解析;变了(换语言或换 `t`)⇒ 重解析。
|
|
8
14
|
*/
|
|
9
15
|
export declare const MarkdownBody: import("react").NamedExoticComponent<{
|
|
10
16
|
text: string;
|
|
17
|
+
t: Translate;
|
|
18
|
+
localeKey: string;
|
|
11
19
|
}>;
|