@xrn1997/dsh-session-delete 0.3.1 → 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 +20 -14
- package/lib/client.js +1686 -1053
- package/lib/index.js +1387 -771
- 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 +1 -1
- package/lib/types/client/batch-confirm.d.ts +2 -2
- 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/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/main-view.d.ts +1 -1
- 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 +29 -11
- package/lib/types/client/receipt.d.ts +42 -2
- package/lib/types/client/remote.d.ts +32 -48
- package/lib/types/client/trash-confirm.d.ts +2 -15
- 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 +5 -59
- 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 +24 -7
- package/lib/types/client/use-trash-query.d.ts +35 -32
- package/lib/types/client/write-seats.d.ts +5 -13
- package/lib/types/client/write-settlement.d.ts +10 -16
- package/lib/types/client/write.d.ts +8 -5
- package/lib/types/deps.d.ts +18 -7
- package/lib/types/index.d.ts +6 -2
- 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 +17 -45
- package/lib/types/ports/presence-port.d.ts +22 -0
- package/lib/types/shared/wire.d.ts +70 -26
- package/lib/types/trash/attachments.d.ts +85 -0
- package/lib/types/trash/detail.d.ts +28 -8
- package/lib/types/trash/entry.d.ts +3 -3
- package/lib/types/trash/groups.d.ts +4 -11
- package/lib/types/trash/storage-manifest.d.ts +1 -1
- package/lib/types/verbs/delete-session.d.ts +2 -2
- package/lib/types/verbs/errors.d.ts +2 -0
- package/lib/types/verbs/live-admission.d.ts +4 -7
- package/lib/types/verbs/member-journal.d.ts +3 -4
- package/lib/types/verbs/member-presence.d.ts +14 -37
- package/lib/types/verbs/orphan-subagents.d.ts +9 -8
- package/lib/types/verbs/permanent-removal.d.ts +24 -18
- package/lib/types/verbs/purge-trash.d.ts +8 -3
- package/lib/types/verbs/restore-session.d.ts +7 -13
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Translate } from './copy.js';
|
|
2
|
+
import { type ApplyAction, type ApplyOutcome, type ApplyValueByAction, type AttachmentCleanupWire, type RemoteFailure } from '../shared/wire.js';
|
|
2
3
|
/**
|
|
3
4
|
* 一条 `/apply` 结果对一个请求 id 的含义。各写入面不许再各自推导:
|
|
4
5
|
* 缺 id 是通道错误,`null` 是幂等成功,orphan-clean 的 `not-found` 是中性提示而非失败,
|
|
@@ -31,6 +32,8 @@ export type ReceiptVerdict<A extends ApplyAction> = {
|
|
|
31
32
|
readonly message: string;
|
|
32
33
|
};
|
|
33
34
|
export interface Receipt<A extends ApplyAction> {
|
|
35
|
+
/** 产生这份回执的动作:摘要据此只派发该动作真的有的那几种落地 id。 */
|
|
36
|
+
readonly action: A;
|
|
34
37
|
readonly verdicts: readonly ReceiptVerdict<A>[];
|
|
35
38
|
readonly byId: ReadonlyMap<string, ReceiptVerdict<A>>;
|
|
36
39
|
}
|
|
@@ -52,7 +55,6 @@ export declare function missingOutcomeMessage(action: ApplyAction, id: string):
|
|
|
52
55
|
export type FailureVerdict<A extends ApplyAction> = Extract<ReceiptVerdict<A>, {
|
|
53
56
|
kind: 'failed' | 'neutral' | 'missing';
|
|
54
57
|
}>;
|
|
55
|
-
export declare function isFailureVerdict<A extends ApplyAction>(verdict: ReceiptVerdict<A>): verdict is FailureVerdict<A>;
|
|
56
58
|
/** failed / neutral 取服务端原文 `error.message`;missing 取通道自己写的那句。 */
|
|
57
59
|
export declare function failureMessage<A extends ApplyAction>(verdict: FailureVerdict<A>): string;
|
|
58
60
|
export declare function readReceipt<A extends ApplyAction>(action: A, requestedIds: readonly string[], outcomes: readonly ApplyOutcome[], options?: ReceiptOptions): Receipt<A>;
|
|
@@ -61,6 +63,44 @@ export declare function readReceipt<A extends ApplyAction>(action: A, requestedI
|
|
|
61
63
|
* single 读法运行期不会产出它)。`applyOne` 用它之后只剩「按 id 取一条 + 抛失败」。
|
|
62
64
|
*/
|
|
63
65
|
export declare function readSingleReceipt<A extends ApplyAction>(action: A, id: string, outcomes: readonly ApplyOutcome[]): SingleReceipt<A>;
|
|
66
|
+
/** 一次写入回执的**一个读法**:成功、失败、中性、部分计数与落地 id 都从这里出。
|
|
67
|
+
* 座位与收尾不再自己 walk `verdicts`——那会把同一套判据在调用点再组装一遍。 */
|
|
68
|
+
export interface ReceiptSummary {
|
|
69
|
+
readonly successIds: readonly string[];
|
|
70
|
+
readonly neutralIds: readonly string[];
|
|
71
|
+
/** failed + missing(通道没回);neutral 单独在 `neutralIds`,部分计数只从 failed 的 details 取。 */
|
|
72
|
+
readonly failed: readonly {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly error: RemoteFailure;
|
|
75
|
+
}[];
|
|
76
|
+
readonly partial: {
|
|
77
|
+
readonly removed: number;
|
|
78
|
+
readonly freedBytes: number;
|
|
79
|
+
};
|
|
80
|
+
/** 这次动作真正落地的 id。**域由 `action` 定**:delete 是条目 id(撤销名单),restore 是会话 id
|
|
81
|
+
* (回盘名单);purge / count / orphan-clean 没有可撤销的落地 id,恒为空。
|
|
82
|
+
* 两个域不混在一个数组里靠调用方猜,也不各留一个永远为空的字段。 */
|
|
83
|
+
readonly landedIds: readonly string[];
|
|
84
|
+
readonly anySuccess: boolean;
|
|
85
|
+
/** purge / orphan-clean 的 `ok` 回执里那半份附件读数,**按批相加**(一段 `/apply` 可能带多条 ok)。
|
|
86
|
+
* `skipped` 取第一条有的:它说的是"这一轮为什么没敢清",不是可累加的计数。
|
|
87
|
+
* 两种动作都没有读数(或这一批一条都没落地)时缺席——不拿零冒充"清过"。 */
|
|
88
|
+
readonly attachmentCleanup?: AttachmentCleanupWire;
|
|
89
|
+
}
|
|
90
|
+
/** 一次摘要派生的两样产物:聚合摘要与逐条失败原文。
|
|
91
|
+
* 失败表只挂在 `WriteSuccess` 上(成败两支的统一读法),摘要自己不再留第二份。 */
|
|
92
|
+
export interface ReceiptRead {
|
|
93
|
+
readonly summary: ReceiptSummary;
|
|
94
|
+
readonly failures: ReadonlyMap<string, string>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 附件清理回执的「跳过半句」:`skipped` 说的是"这一轮为什么没敢清",不是失败——原因上屏,
|
|
98
|
+
* `removed > 0` 时说「部分」,`failed` 件数追加在句尾(不被跳过原因吞掉)。
|
|
99
|
+
* 面板(`use-trash-query.ts`)与维护卡(`use-maintenance.ts`)共用这一判据,不许各拼一份。
|
|
100
|
+
* 没 `skipped` 时回 `null`:后半句(全清/失败怎么说)两个面各有各的口径,由调用方自己接。
|
|
101
|
+
*/
|
|
102
|
+
export declare function attachmentSkippedSentence(a: AttachmentCleanupWire, t: Translate): string | null;
|
|
103
|
+
export declare function summarizeReceipt<A extends ApplyAction>(receipt: Receipt<A>, sessionIdOfEntry?: (entryId: string) => string | undefined): ReceiptRead;
|
|
64
104
|
/**
|
|
65
105
|
* `/apply` 成功支回执里的条目 id(delete / restore 的 `value.id`);purge 的聚合值与 count 的数字值跳过。
|
|
66
106
|
* 撤销名单只收 `ok` 支的条目 id:already-gone 是「已被同批前一条带走」的幂等条,跟着组走,不单列。
|
|
@@ -1,42 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 加上信封判别与注入面那一处「展开成裸值」的适配。
|
|
2
|
+
* 浏览器半的取数通道:同源 fetch /dsh-session-delete-api,并处理信封与失败原文。
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 1. **信封刻意与官方 unary 面同形**:`{ ok: true, value }` / `{ ok: false, error }`。这不是"还留着
|
|
15
|
-
* 官方通道"——它现在是**我们两半之间**的 wire 契约(Node 半按它产出,见 `src/api/dispatch.ts`),
|
|
16
|
-
* 选它是为了让组件面、`unwrapRemoteResult` 与既有用例一行都不用改。失败是**返回**而不是
|
|
17
|
-
* reject(同宿主网关原文:"Every unary call resolves to `RemoteResult<T>` … and never rejects for a
|
|
18
|
-
* carrier problem");只有往返本身失败(打不通、回的不是信封)才 reject,那是**通道缺席**。
|
|
19
|
-
* 2. **不许值 import 跨插件包**。`error` 在线上就是一段 JSON(`{code, message, details?}`),不是
|
|
20
|
-
* `RemoteError` 实例——所以判别**所依据的那一个标记位**只镜一份,家在两半共用的
|
|
21
|
-
* `src/shared/wire.ts`(`isDSHRemoteError`)。浏览器半落到本地时用 `throwRemoteFailure` 就地补上
|
|
22
|
-
* Error 本体与标记位;Node 半(`src/remote/map-error.ts`)只用它做「这已经是一次远程失败」的判别,
|
|
23
|
-
* 产出的是线上那三件(`details` 可缺席)、不造任何 Error,更不 import 宿主那个类。
|
|
24
|
-
* 3. **失败原文必须能上屏**。调用方(`callRemote`)只把**抛出来的失败**收成判别式状态——
|
|
25
|
-
* `{ ok: false, error }` 不是第三种落地:它在注入面那一处已经被 `unwrapRemoteResult` 抛成了异常,
|
|
26
|
-
* 所以到了组件面前只剩「裸值 / throw」两支,调用方只写 `if (!outcome.ok)`,正常流里没有 try/catch;
|
|
27
|
-
* 非本信封形状的异常(网络层、TypeError…)也不丢原文(见 `failureOf`)。
|
|
28
|
-
*
|
|
29
|
-
* 面(`list` / `ping` / `orphans` / `detail` / `apply` / `empty`)与 Node 半路由的六个段名一一对应(段名本身的家是
|
|
30
|
-
* `src/shared/wire.ts`);`list` 回一行行的条目,`ping` 只回一个探针值,`orphans` 回一份**纯读**的
|
|
31
|
-
* 残留扫描报告,`detail` 回单条正文,
|
|
32
|
-
* `apply` 回**一组逐条结果**,`empty` 回聚合回执。2026-10-17 之前是六个动作段(一条一段),
|
|
33
|
-
* 批量只能由调用方循环拼——那正是「恢复 100 条很慢」的形状来源,收成一段之后这条通道不再有
|
|
34
|
-
* "第二条执行路径"。**清理无主子会话也不另开一段**:它是 `apply` 的第五个动作,`orphans` 只负责
|
|
35
|
-
* "有多少、在哪、多大"。
|
|
4
|
+
* - 路由是宿主 web server 上的插件前缀;官方 remote.<namespace> 需要生成的 Typert 描述符,
|
|
5
|
+
* 第三方无法动态挂载,因此走这条薄线(ADR-0009)。
|
|
6
|
+
* - 九段:list / ping / orphans / detail / apply / empty / attachment / attachment-orphans / attachment-clean;
|
|
7
|
+
* 写动作统一走 apply;`attachment` 是唯一二进制段,由 `src/client/attachment-view.tsx` 直接拼地址,
|
|
8
|
+
* 不经过本文件的信封通道;`attachment-orphans`(纯读)与 `attachment-clean`(聚合写)是孤儿附件的两段。
|
|
9
|
+
* - 信封与官方 unary 面同形:{ok:true,value} / {ok:false,error}。HTTP 往返本身失败才 reject;
|
|
10
|
+
* 业务失败以信封返回,由 unwrapRemoteResult 在注入面抛出。
|
|
11
|
+
* - error 在线上是 {code,message,details?} 普通对象;判别依据 isDSHRemoteError 住在
|
|
12
|
+
* src/shared/wire.ts,这里不 import 任何宿主客户端包。
|
|
36
13
|
*/
|
|
37
|
-
import { type ApplyAction, type ApplyOutcome, type ApplyValueByAction, type OrphanReportWire, type PurgeStatsWire, type RemoteFailure, type RemoteResult, type TrashDetailWire, type TrashRowWire } from '../shared/wire.js';
|
|
14
|
+
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';
|
|
38
15
|
/**
|
|
39
|
-
* 回收站条目(`/list` 那一行)。**形状的家在 `shared/wire.ts`**(ADR-0003
|
|
16
|
+
* 回收站条目(`/list` 那一行)。**形状的家在 `shared/wire.ts`**(ADR-0003):这里只是别名,
|
|
40
17
|
* 所以浏览器半不再自己抄一遍字段;下面几处"只读面"的镜像同理。
|
|
41
18
|
*/
|
|
42
19
|
export type TrashEntryLike = TrashRowWire;
|
|
@@ -72,11 +49,9 @@ export type RemoteOutcome<T> = {
|
|
|
72
49
|
*/
|
|
73
50
|
export declare function callRemote<T>(run: () => Promise<T>): Promise<RemoteOutcome<T>>;
|
|
74
51
|
/** 本插件前缀路由那套**响应信封**的窄镜像(不是宿主服务 `ctx.remote.*`——那条 typert 通道为什么用不了,见 `AGENTS.md` 的 `remote.<namespace>` 那条):
|
|
75
|
-
* 每个方法 resolve 成 `RemoteResult<T
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* 再加一段**纯探测** `ping`;2026-10-19 又加一段**纯读扫描** `orphans` ⇒ 面是六段:
|
|
79
|
-
* `list` / `ping` / `orphans` / `detail` / `apply` / `empty`(原先六个动作段)。 */
|
|
52
|
+
* 每个方法 resolve 成 `RemoteResult<T>`。九个段名里这层走八个 JSON 段:`list` / `ping` / `orphans` / `detail` / `apply` / `empty` /
|
|
53
|
+
* `attachment-orphans` / `attachment-clean`;写动作统一走 `apply`(一组 id 进、逐条结果出);
|
|
54
|
+
* `/attachment` 是二进制段,由 `src/client/attachment-view.tsx` 直接拼地址。 */
|
|
80
55
|
export interface RawSessionDeleteRemote {
|
|
81
56
|
list(): Promise<RemoteResult<TrashEntryLike[]>>;
|
|
82
57
|
/** 通道的"在不在":一次零成本的 GET(探测专用,见 `src/api/dispatch.ts` 的 `ping` 支)。 */
|
|
@@ -84,6 +59,10 @@ export interface RawSessionDeleteRemote {
|
|
|
84
59
|
/** 纯读:回收站之外的无主子会话扫描。它**不清理**——清理是 `apply` 的 `orphan-clean` 动作,
|
|
85
60
|
* 这一段只交回"有多少、在哪、多大"(读不到语料时报 `io`,调用方不许把它折成"零条残留")。 */
|
|
86
61
|
orphans(): Promise<RemoteResult<OrphanReportWire>>;
|
|
62
|
+
/** 纯读:`.dsh` 内无引用图片对象的数量、字节与预览清单(引用源读不出时报 `io`,不折成 0)。 */
|
|
63
|
+
attachmentOrphans(): Promise<RemoteResult<AttachmentOrphanReportWire>>;
|
|
64
|
+
/** 聚合写:只删上一条报告那一刻无引用的图片对象,回 `AttachmentCleanupWire`。 */
|
|
65
|
+
attachmentClean(): Promise<RemoteResult<AttachmentCleanupWire>>;
|
|
87
66
|
detail(entryId: string): Promise<RemoteResult<TrashDetailLike>>;
|
|
88
67
|
/** 一组 id 上的同一个动作。顶层信封的 `ok:false` 只表示**整次调用**没办成(路由自检、栅栏之外);
|
|
89
68
|
* 单条动作的成败住在 `ApplyOutcome` 里——这是批量接口的判据,别在信封层找它。 */
|
|
@@ -93,8 +72,9 @@ export interface RawSessionDeleteRemote {
|
|
|
93
72
|
}
|
|
94
73
|
/**
|
|
95
74
|
* 前缀路由的 fetch 实现。**相对路径**:不拼 base、不认 origin——页面本身就是从宿主 web server
|
|
96
|
-
*
|
|
97
|
-
*
|
|
75
|
+
* 加载的,同源是这条薄线的全部前提。本机(含转发落在回环的桌面壳)靠回环 + Origin/Referer 过栅栏;
|
|
76
|
+
* 远程(LAN / 反代)客户端另由宿主 `connection` 判「已认证的 Web 身份」,所以这条通道**只对**
|
|
77
|
+
* `dsh web` 打印的 URL 打开过的页面成立(凭据是那行 URL 换来的 cookie,不是插件自己的秘密)。
|
|
98
78
|
*/
|
|
99
79
|
export declare function createHttpRemote(prefix?: string): RawSessionDeleteRemote;
|
|
100
80
|
/**
|
|
@@ -104,12 +84,11 @@ export declare function createHttpRemote(prefix?: string): RawSessionDeleteRemot
|
|
|
104
84
|
* 错误映射也生效了),那时该做的是把入口显示出来、让原文上屏;只有往返本身失败才算通道缺席 ⇒
|
|
105
85
|
* 一个槽位贡献都不注册(docs/architecture.md §3「通道探测决定入口出现与否」)。
|
|
106
86
|
*
|
|
107
|
-
*
|
|
108
|
-
* 而"信封到不到"这件事一次零成本的 GET 就答得出来。
|
|
87
|
+
* 探测用一次零成本的 `GET /ping`,不读清单,避免每次页面加载都跑全表对账。
|
|
109
88
|
*/
|
|
110
89
|
export declare function probeChannel(raw: RawSessionDeleteRemote): Promise<boolean>;
|
|
111
|
-
/** `shared/wire.ts` 的 `ApplyOutcome
|
|
112
|
-
export type { ApplyAction, ApplyOutcome, OrphanReportWire } from '../shared/wire.js';
|
|
90
|
+
/** `shared/wire.ts` 的 `ApplyOutcome`、扫描报告与附件回执从这里转出去——浏览器半只认 `remote.js` 这一扇门。 */
|
|
91
|
+
export type { ApplyAction, ApplyOutcome, AttachmentCleanupWire, AttachmentOrphanReportWire, OrphanReportWire, } from '../shared/wire.js';
|
|
113
92
|
/**
|
|
114
93
|
* N=1 的成功支:`null` 已被 `receipt.ts` 的单条读法折成协议错,所以 `value` 必然非空。
|
|
115
94
|
* 不再需要条件类型把 `delete` / `restore` 的 null 抠掉——那份判据只住回执读法一处。
|
|
@@ -121,7 +100,8 @@ export type OneShotSuccess<A extends ApplyAction> = {
|
|
|
121
100
|
readonly value: Exclude<ApplyValueByAction[A], null>;
|
|
122
101
|
};
|
|
123
102
|
/**
|
|
124
|
-
* 注入面(`deps`)的 interface
|
|
103
|
+
* 注入面(`deps`)的 interface:**七个方法**(raw 层另有 `ping` 供探测;九段里的 `/attachment` 由
|
|
104
|
+
* `src/client/attachment-view.tsx` 直接拼地址,也不在这层)。
|
|
125
105
|
*
|
|
126
106
|
* **不再有 N=1 的便捷方法**:`delete` / `restore` / `countSubagents` 曾是同一段
|
|
127
107
|
* `/apply` 的三种写法,于是一个 `module` 里同一个动作有两条路(回收站面板的 deps 同时躺着
|
|
@@ -133,8 +113,12 @@ export interface SessionDeleteRemote {
|
|
|
133
113
|
/** 回收站之外的无主子会话报告(纯读)。读不到就是 throw —— 界面上那是**一次失败的检查**,
|
|
134
114
|
* 不是"零条残留"(把 `io` 折成空报告等于把"不知道"说成"没有")。 */
|
|
135
115
|
scanOrphans(): Promise<OrphanReportWire>;
|
|
116
|
+
/** `.dsh` 内无引用图片对象的报告(纯读;含最多 200 条预览清单)。读不到就是 throw,同样不许折成 0。 */
|
|
117
|
+
scanAttachmentOrphans(): Promise<AttachmentOrphanReportWire>;
|
|
118
|
+
/** 清掉报告那一刻无引用的图片副本(聚合回执)。 */
|
|
119
|
+
cleanAttachmentOrphans(): Promise<AttachmentCleanupWire>;
|
|
136
120
|
detail(entryId: string): Promise<TrashDetailLike>;
|
|
137
|
-
/**
|
|
121
|
+
/** 一段管所有写动作:一次调用把整批办完,逐条结果原样交回组件。 */
|
|
138
122
|
applyOutcomes(action: ApplyAction, ids: readonly string[]): Promise<ApplyOutcome[]>;
|
|
139
123
|
/** 清空回收站(聚合回执)。 */
|
|
140
124
|
emptyTrash(): Promise<PurgeResultLike>;
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import { type Translate } from './copy.js';
|
|
2
2
|
import type { OrphanReportWire, TrashEntryLike } from './remote.js';
|
|
3
|
-
|
|
4
|
-
* `purge` 与 `restore` 的确认框共用这一个壳——两者都是"点之前先说清这一批有哪些、要去哪"。 */
|
|
5
|
-
export type PendingAction = {
|
|
6
|
-
kind: 'entry';
|
|
7
|
-
entry: TrashEntryLike;
|
|
8
|
-
} | {
|
|
9
|
-
kind: 'all';
|
|
10
|
-
} | {
|
|
11
|
-
kind: 'entries';
|
|
12
|
-
ids: readonly string[];
|
|
13
|
-
} | {
|
|
14
|
-
kind: 'restore';
|
|
15
|
-
ids: readonly string[];
|
|
16
|
-
};
|
|
3
|
+
import type { PendingAction } from './use-trash-panel-controller.js';
|
|
17
4
|
/**
|
|
18
5
|
* 两种批量动作共用这一个确认框壳:`purge`(不可恢复)与 `restore`(加法,但一批条目多、去向是
|
|
19
6
|
* 各自的旧项目目录,点之前也要过一道)。标题、正文、确认键的措辞各处一套;批量恢复把选中条目
|
|
@@ -29,7 +16,7 @@ export declare function TrashConfirm({ pending, restoreEntries, busy, t, onCance
|
|
|
29
16
|
onConfirmPurge: () => void;
|
|
30
17
|
onConfirmRestore: (ids: readonly string[]) => void;
|
|
31
18
|
}): import("react").JSX.Element;
|
|
32
|
-
/**
|
|
19
|
+
/** 无主子会话的清理确认框。载荷是刚扫出来的那份报告,界面不重算。 */
|
|
33
20
|
export declare function OrphanConfirm({ report, busy, t, onCancel, onConfirm, }: {
|
|
34
21
|
report: OrphanReportWire;
|
|
35
22
|
busy: boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Translate } from './copy.js';
|
|
2
2
|
import { type SessionDeleteRemote } from './remote.js';
|
|
3
|
-
/**
|
|
3
|
+
/** 面板的依赖面:两个读(`list` / `detail`)+ **一段 `apply`** + 清空。
|
|
4
4
|
* **没有第二条路**:「恢复一条」也走 `apply`(N=1),面板不再同时吃
|
|
5
|
-
* `applyOutcomes` 与一个便捷的 `restore
|
|
6
|
-
|
|
5
|
+
* `applyOutcomes` 与一个便捷的 `restore`。无主子会话的检查/清理不在这里——它是插件设置页
|
|
6
|
+
* 维护卡的手动动作(`use-maintenance.ts`),面板一次都不自动扫(ADR-0021)。 */
|
|
7
|
+
export type TrashPanelDeps = Pick<SessionDeleteRemote, 'list' | 'detail' | 'applyOutcomes' | 'emptyTrash'>;
|
|
7
8
|
export interface TrashPanelProps {
|
|
8
9
|
deps: TrashPanelDeps;
|
|
9
10
|
/** 文案(缺省 = 内置 zh,见 `copy.ts`:这不是"忘了传",而是"这次没有 locale 服务")。 */
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* - **官方的多层栈**(`layers` WeakMap + `closeTopModal` / `isBehindModal`):那是给"对话框里还能再开
|
|
15
15
|
* 对话框、且宿主菜单也在抢 Escape"的场景准备的。本插件同一时刻只会有一个确认框,所以这里只保留它
|
|
16
16
|
* 的**判据本身**——Escape 只在"自己就是文档里最后一个 `[role="dialog"][aria-modal="true"]`
|
|
17
|
-
* 或 `[role="menu"]`"时生效(`isTopLayer
|
|
18
|
-
* "谁在最上面谁收 Escape"是用户能看见的规矩。
|
|
17
|
+
* 或 `[role="menu"]`"时生效(`isTopLayer`,名单与判据住 `ui/top-layer.ts`)。这一条不能省:
|
|
18
|
+
* 菜单/对话框先后打开时,"谁在最上面谁收 Escape"是用户能看见的规矩。
|
|
19
19
|
* - **输入法合成守卫**(`observeComposition`):本插件的对话框里没有文本输入框,不可能处在合成态。
|
|
20
20
|
*/
|
|
21
21
|
import { type ReactElement, type ReactNode } from 'react';
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 本插件 UI
|
|
3
|
-
*
|
|
2
|
+
* 本插件 UI 层要用的图标(逐条抄自 `@deepseek-ai/dsh-client-ui-primitives` 的明文产物;
|
|
3
|
+
* 出处与改写规则见 `ui.css` 头注)。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* 关闭叉(对话框)、圆圈对勾(撤销提示的 success 档)、对勾(行尾兜底勾选开关,2026-10-17 加;
|
|
7
|
-
* 行首主勾选走原生 Checkbox)、下折与右折 chevron(清单的分组折叠头,
|
|
8
|
-
* 2026-10-15 加:`DisclosureRow` 那套折叠行的两态 glyph)。「刷新」那枚随按钮一起删了
|
|
9
|
-
* (2026-10-16,见 `trash-panel.tsx` 工具排的注释)——本文件不留无人消费的几何。
|
|
5
|
+
* 只留本插件用到的:垃圾桶、警示、打开的项目文件夹、关闭叉、圆圈对勾、对勾、下折/右折 chevron。
|
|
10
6
|
* **几何、`viewBox`、`strokeWidth` 逐字不动**——它们是官方图标集的形状,改了就不再与宿主同屏同形。
|
|
11
7
|
*
|
|
12
8
|
* 两条与官方实现的差异(都是减法,不是改写):
|
|
13
|
-
* - 只暴露 `size
|
|
14
|
-
*
|
|
15
|
-
* - 不做 `ICON_REGULAR_STROKE` / `ICON_MEDIUM_STROKE` 常量:只有一个档位,直接写 1。
|
|
9
|
+
* - 只暴露 `size`:调用处只传 `size`,不摊开官方整个图标 API。
|
|
10
|
+
* - 不做 stroke 常量:只有一个档位,直接写 1。
|
|
16
11
|
*/
|
|
17
12
|
import type { ReactElement } from 'react';
|
|
18
13
|
/** 宿主图标组件的实际使用面:一个方形边长,色走 `currentColor`。 */
|
|
@@ -25,7 +20,7 @@ export declare function IconFolderOpenOutlineRegular({ size }: IconProps): React
|
|
|
25
20
|
export declare function IconCloseOutlineRegular({ size }: IconProps): ReactElement;
|
|
26
21
|
/** 撤销提示 success 档的前导 glyph(官方 `Toast` 在 `tone="success"` 时自带的那一枚)。 */
|
|
27
22
|
export declare function IconCheckCircleOutlineRegular({ size }: IconProps): ReactElement;
|
|
28
|
-
/**
|
|
23
|
+
/** 行尾兜底勾选开关那枚对勾(状态点/归档行;官方几何逐字)。 */
|
|
29
24
|
export declare function IconCheckOutlineRegular({ size }: IconProps): ReactElement;
|
|
30
25
|
/** 分组头「展开中」那一枚(官方 `IconChevronDownOutlineRegular`,`viewBox`/`d` 逐字)。 */
|
|
31
26
|
export declare function IconChevronDownOutlineRegular({ size }: IconProps): ReactElement;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「谁在最上层收 Escape」的名单与判据。`Modal` 的关闭键和清单的 Esc 退下钻共用这一份:
|
|
3
|
+
* 两份各写一次选择器,菜单/对话框换代时必然只剩一处跟上。
|
|
4
|
+
*
|
|
5
|
+
* 名单与官方 `modalSelector` 同形:模态对话框 + 菜单。文档序里最后一个是「最上层」。
|
|
6
|
+
*/
|
|
7
|
+
/** 文档里是否还有打开的顶层浮层。清单只问「有没有」,它自己不是其中一员。 */
|
|
8
|
+
export declare function hasTopLayer(doc: Document): boolean;
|
|
9
|
+
/** 自己是不是文档里最上面的那一层。Modal 只在这时收 Escape。 */
|
|
10
|
+
export declare function isTopLayer(element: HTMLElement): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次待撤销的事。两形:单条删除(主语是会话名)/批量删除(主语是条数)。
|
|
3
|
+
* 同一个 Toast 宿主、同一停留时长;撤销动作按形分派(单条 / 批量),两条都走同一个外部恢复座位
|
|
4
|
+
* (`write-seats.ts` 的 `restoreFromUndo`),单条带 `single: true` 的读法(null 是协议错,不冒充幂等成功)。
|
|
5
|
+
*/
|
|
6
|
+
export type UndoNotice = {
|
|
7
|
+
/** 每次发布递增:Toast 靠它重挂载,同一段文案连续出现两次也能重新计一次停留。 */
|
|
8
|
+
seq: number;
|
|
9
|
+
/** 撤销失败时把原因顶上来:这时不再有「撤销」动作,只剩原文。 */
|
|
10
|
+
failure?: string;
|
|
11
|
+
} & ({
|
|
12
|
+
kind: 'one';
|
|
13
|
+
/** 回收站条目 id——撤销就是拿它去 `restore`。 */
|
|
14
|
+
entryId: string;
|
|
15
|
+
/** 删除那一刻的会话名(菜单槽投影的 `displayTitle`;取不到时是空串)。 */
|
|
16
|
+
title: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'batch';
|
|
19
|
+
/** 撤销名单:只收成功支里非 null 的条目 id(「被同批带走」的成员跟着组走,不单列)。 */
|
|
20
|
+
entryIds: readonly string[];
|
|
21
|
+
/** 横幅那句的条数 = 那一次成功支的条数(含幂等带走的)——与撤销名单是两个数,各说各的。 */
|
|
22
|
+
n: number;
|
|
23
|
+
});
|
|
24
|
+
/** 删除成功后发布一条撤销提示(单条)。 */
|
|
25
|
+
export declare function publishUndoNotice(input: {
|
|
26
|
+
entryId: string;
|
|
27
|
+
title: string;
|
|
28
|
+
}): void;
|
|
29
|
+
/** 批量删除成功后发布一条撤销提示:覆盖上一条,只报这一次移走的条数。 */
|
|
30
|
+
export declare function publishUndoBatchNotice(input: {
|
|
31
|
+
entryIds: readonly string[];
|
|
32
|
+
n: number;
|
|
33
|
+
}): void;
|
|
34
|
+
/** 整条横幅不再留:撤销成了、停留时间走完,或清空回收站把所有落点一起清了。
|
|
35
|
+
* 与 `dismissUndoNotice` 的分别是「整条不再留」与「按条目匹配」——清空之后没有条目还能撤销,
|
|
36
|
+
* 所以那一路不需要名单。 */
|
|
37
|
+
export declare function clearUndoNotice(): void;
|
|
38
|
+
/**
|
|
39
|
+
* 某个条目已经在别处恢复:把指向它的横幅熄掉。
|
|
40
|
+
* 批量里任一命中就清整条——名单少了、计数没变,留着就是一条会骗人的「撤销」动作。
|
|
41
|
+
*/
|
|
42
|
+
export declare function dismissUndoNotice(entryIds: readonly string[]): void;
|
|
43
|
+
/** 供组件订阅。 */
|
|
44
|
+
export declare function useUndoNotice(): UndoNotice | null;
|
|
45
|
+
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
46
|
+
export declare function resetUndoNotice(): void;
|
|
47
|
+
/** 提示停留时长。**6 秒** 定死在本行(比 Toast 默认的 3 秒长)——`docs/ui-spec.md` §2 状态 ③ 只指到这里,不抄这个数。 */
|
|
48
|
+
export declare const UNDO_HOLD_MS = 6000;
|
|
49
|
+
/** 这次撤销开始时的那条横幅还在吗——期间被新删除覆盖、或由面板恢复熄灭时,旧请求不许改写它。 */
|
|
50
|
+
export declare function ownsNotice(current: UndoNotice): boolean;
|
|
51
|
+
/** 撤销失败:把原因换到同一条横幅上(seq 递增 ⇒ Toast 重挂载、重新计停留)。 */
|
|
52
|
+
export declare function failNotice(current: UndoNotice, message: string): void;
|
|
@@ -1,71 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 撤销提示(docs/ui-spec.md 状态 ③):删除成功后一条可撤销的横幅。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* inject: rowToastInjected … }, RowActionToast)`,而
|
|
9
|
-
* `rowToastInjected = () => ({ hooks: { toast: rowToast }, dismissToast, undoArchive: unarchiveSession })`)。
|
|
10
|
-
* 本文件就是那份 store + 那个常驻组件。
|
|
11
|
-
*
|
|
12
|
-
* 状态源走 `store.ts` 的 `createStore`(内部用 React 自带的 `useSyncExternalStore`)——
|
|
13
|
-
* 不引 `@deepseek-ai/dsh-client-store`(它在平台表内,但这里用不着;React 就够,少一条平台依赖)。
|
|
4
|
+
* 现场与发布住在 `undo-notices.ts`;本文件只做组件,并把通知台与外部恢复座位组合起来。
|
|
5
|
+
* 为什么组件要独立:宿主的菜单行随菜单**一起卸载**(真产物 Menu 的注释原文
|
|
6
|
+
* "the rows unmount with the list"),而提示要在菜单关掉之后还活着。宿主的官方配方同形——
|
|
7
|
+
* 会话行的动作把请求写进一个模块级 store,`shell.overlay` 里的 `workspace.row-toast` 读它。
|
|
14
8
|
*/
|
|
15
9
|
import { type Translate } from './copy.js';
|
|
16
10
|
import type { OneShotDeps } from './remote.js';
|
|
17
11
|
export type UndoToastDeps = OneShotDeps;
|
|
18
|
-
/**
|
|
19
|
-
* 一次待撤销的事。两形(2026-10-17 起):单条删除(主语是会话名)/批量删除(主语是条数)。
|
|
20
|
-
* 同一个 Toast 宿主、同一停留时长;撤销动作按形分派(单条 / 批量),两条都走同一个外部恢复座位
|
|
21
|
-
* (`write-seats.ts` 的 `restoreFromUndo`),单条带 `single: true` 的读法(null 是协议错,不冒充幂等成功)。
|
|
22
|
-
*/
|
|
23
|
-
export type UndoNotice = {
|
|
24
|
-
/** 每次发布递增:Toast 靠它重挂载,同一段文案连续出现两次也能重新计一次停留。 */
|
|
25
|
-
seq: number;
|
|
26
|
-
/** 撤销失败时把原因顶上来:这时不再有「撤销」动作,只剩原文。 */
|
|
27
|
-
failure?: string;
|
|
28
|
-
} & ({
|
|
29
|
-
kind: 'one';
|
|
30
|
-
/** 回收站条目 id——撤销就是拿它去 `restore`。 */
|
|
31
|
-
entryId: string;
|
|
32
|
-
/** 删除那一刻的会话名(菜单槽投影的 `displayTitle`;取不到时是空串)。 */
|
|
33
|
-
title: string;
|
|
34
|
-
} | {
|
|
35
|
-
kind: 'batch';
|
|
36
|
-
/** 撤销名单:只收成功支里非 null 的条目 id(「被同批带走」的成员跟着组走,不单列)。 */
|
|
37
|
-
entryIds: readonly string[];
|
|
38
|
-
/** 横幅那句的条数 = 那一次成功支的条数(含幂等带走的)——与撤销名单是两个数,各说各的。 */
|
|
39
|
-
n: number;
|
|
40
|
-
});
|
|
41
|
-
/** 删除成功后发布一条撤销提示(单条)。 */
|
|
42
|
-
export declare function publishUndoNotice(input: {
|
|
43
|
-
entryId: string;
|
|
44
|
-
title: string;
|
|
45
|
-
}): void;
|
|
46
|
-
/** 批量删除成功后发布一条撤销提示(2026-10-17):覆盖上一条,只报这一次移走的条数。 */
|
|
47
|
-
export declare function publishUndoBatchNotice(input: {
|
|
48
|
-
entryIds: readonly string[];
|
|
49
|
-
n: number;
|
|
50
|
-
}): void;
|
|
51
|
-
/** 整条横幅不再留:撤销成了、停留时间走完,或清空回收站把所有落点一起清了。
|
|
52
|
-
* 与下面 `dismissUndoNotice` 的分别是「整条不再留」与「按条目匹配」——清空之后没有条目还能撤销,
|
|
53
|
-
* 所以那一路不需要名单。 */
|
|
54
|
-
export declare function clearUndoNotice(): void;
|
|
55
|
-
/**
|
|
56
|
-
* 某个条目已经在别处恢复:把指向它的横幅熄掉。
|
|
57
|
-
* 批量里任一命中就清整条——名单少了、计数没变,留着就是一条会骗人的「撤销」动作。
|
|
58
|
-
*/
|
|
59
|
-
export declare function dismissUndoNotice(entryIds: readonly string[]): void;
|
|
60
|
-
/** 供组件订阅。 */
|
|
61
|
-
export declare function useUndoNotice(): UndoNotice | null;
|
|
62
|
-
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
63
|
-
export declare function resetUndoNotice(): void;
|
|
64
|
-
/** 提示停留时长。**6 秒** 定死在本行(比 Toast 默认的 3 秒长)——`docs/ui-spec.md` §2 状态 ③ 只指到这里,不抄这个数。 */
|
|
65
|
-
export declare const UNDO_HOLD_MS = 6000;
|
|
66
12
|
/**
|
|
67
13
|
* 常驻的撤销提示宿主。挂 `shell.overlay`(框架级浮层;槽位说明原文 "a toast stack or a status
|
|
68
|
-
* pill all belong here"
|
|
14
|
+
* pill all belong here"):没有待撤销的事时什么都不渲染。
|
|
69
15
|
*/
|
|
70
16
|
export declare function UndoToast({ deps, t }: {
|
|
71
17
|
deps: UndoToastDeps;
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 清单列与详情列之间那根可拖的宽度把手(docs/ui-spec.md 的「面板与清单」)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* ①
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 它就成了假数,而 900 那档的真实读数是**清单被夹到 411**(比默认窄 9px,还能往里拖)。
|
|
11
|
-
* ② **夹在"读"的时候也做一次**。窗口变窄不触发任何写入,若只在写入时夹,之前拖出来的超宽
|
|
12
|
-
* 清单会一直留到下次交互 ⇒ `listWidth` 每次渲染都按当前 `panelWidth` 重算。
|
|
13
|
-
* ③ **按下才算在拖**。指针捕获挂在把手元素上(`setPointerCapture`),move 只可能来自这一根把手;
|
|
14
|
-
* `origin === null` 时 move 直接返回——鼠标扫过不该改布局。抬起不用显式 release:规范里
|
|
15
|
-
* pointerup/pointercancel 会隐式释放捕获,多写那一行只会引入"指针已不活跃"的抛错面。
|
|
16
|
-
* **但"结束"不能只挂在把手上**:拖动途中把手会被卸载(面板窄过断点、或清单被读空 ⇒ `split`
|
|
17
|
-
* 翻了),而卸载后的节点收不到任何事件 ⇒ `resizing` 永远挂着:面板根留着 `.sd-resizing`
|
|
18
|
-
* (关文字选中),下次变宽时把手还带着 `data-dragging` 长回来。所以拖住期间另在 window 上兜
|
|
19
|
-
* 一层 `pointerup`/`pointercancel`(复用同一个 `stop`)。move 仍然只认把手,捕获那套一点没动。
|
|
4
|
+
* 三件事住在这里:
|
|
5
|
+
* ① 宽度夹在两个界之间:下限由调用方给,上限 = 面板宽 − reserve。
|
|
6
|
+
* reserve 的数值与来历住在调用方(DETAIL_RESERVE),这一层不复制算式。
|
|
7
|
+
* ② 夹在“读”的时候也做一次:窗口变窄不触发写入,listWidth 每次渲染按当前 panelWidth 重算。
|
|
8
|
+
* ③ 按下才算在拖:指针捕获挂在把手元素上;拖动途中把手可能卸载,所以 window 上另兜一层
|
|
9
|
+
* pointerup/pointercancel 复用 stop;move 仍只认把手。
|
|
20
10
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* 所以焦点态的可见性由 `ui.css` 里 `.sd-width-handle:focus-visible::after` 那一条负责。
|
|
11
|
+
* 键盘:role=separator、←→ 各一格、Home 回默认、End 到上限;焦点态可见性由 ui.css 的
|
|
12
|
+
* .sd-width-handle:focus-visible::after 负责。
|
|
24
13
|
*/
|
|
25
14
|
import { type KeyboardEvent, type PointerEvent } from 'react';
|
|
26
15
|
export interface ColumnResizeArgs {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Translate } from './copy.js';
|
|
2
|
+
import { type AttachmentOrphanReportWire, type OrphanReportWire, type SessionDeleteRemote } from './remote.js';
|
|
3
|
+
/** 维护卡只需要这四段:两份报告 + 孤儿会话的批量写 + 孤儿附件的聚合写。 */
|
|
4
|
+
export type MaintenanceDeps = Pick<SessionDeleteRemote, 'scanOrphans' | 'applyOutcomes' | 'scanAttachmentOrphans' | 'cleanAttachmentOrphans'>;
|
|
5
|
+
/** 一段在飞的动作:检查只读,清理会写。状态行靠它分开说「检查中…」与「清理中…」;`null` = 空闲。 */
|
|
6
|
+
export type MaintenancePhase = 'scanning' | 'cleaning';
|
|
7
|
+
export interface Maintenance {
|
|
8
|
+
readonly orphanReport: OrphanReportWire | null;
|
|
9
|
+
readonly orphanFailure: string | null;
|
|
10
|
+
/** 「这一组已经不在」不是失败:单独一条中性提示。 */
|
|
11
|
+
readonly orphanNotice: string | null;
|
|
12
|
+
/** 上一趟孤儿会话清理顺带清掉的附件读数(`.dsh` 内独占图片副本)。重查读不出来,和
|
|
13
|
+
* `orphanNotice` 一样是过去那一趟的回执;下一次检查 / 清理会被收掉。 */
|
|
14
|
+
readonly orphanAttachmentNotice: string | null;
|
|
15
|
+
readonly orphanConfirm: boolean;
|
|
16
|
+
readonly orphanBusy: boolean;
|
|
17
|
+
/** 在飞的是检查还是清理;`null` = 空闲。写动作在飞时状态行据此说「清理中…」。 */
|
|
18
|
+
readonly orphanPhase: MaintenancePhase | null;
|
|
19
|
+
/** 手动检查(`GET /orphans`)。失败回 `null` + 原文,不折成零条。 */
|
|
20
|
+
scanOrphans(): Promise<void>;
|
|
21
|
+
/** 「查看并清理」:先收掉旧提示再开确认框。 */
|
|
22
|
+
openOrphanPreview(): void;
|
|
23
|
+
/** `/apply` 的 `orphan-clean`:清完必重查;中性 / 失败 / 部分计数按回执说。 */
|
|
24
|
+
cleanOrphans(): Promise<void>;
|
|
25
|
+
setOrphanConfirm(open: boolean): void;
|
|
26
|
+
readonly attachmentReport: AttachmentOrphanReportWire | null;
|
|
27
|
+
readonly attachmentFailure: string | null;
|
|
28
|
+
/** 上一趟清理的回执(已清 / 跳过 / 逐文件失败),重查读不出来。 */
|
|
29
|
+
readonly attachmentNotice: string | null;
|
|
30
|
+
readonly attachmentConfirm: boolean;
|
|
31
|
+
readonly attachmentBusy: boolean;
|
|
32
|
+
/** 在飞的是检查还是清理;`null` = 空闲。 */
|
|
33
|
+
readonly attachmentPhase: MaintenancePhase | null;
|
|
34
|
+
/** 手动检查(`GET /attachment-orphans`)。 */
|
|
35
|
+
scanAttachmentOrphans(): Promise<void>;
|
|
36
|
+
/** 「查看并清理」:先收掉旧提示再开确认框。 */
|
|
37
|
+
openAttachmentPreview(): void;
|
|
38
|
+
/** `POST /attachment-clean`:清完必重查;`removed` / `failed` / `skipped` 按回执说。 */
|
|
39
|
+
cleanAttachmentOrphans(): Promise<void>;
|
|
40
|
+
setAttachmentConfirm(open: boolean): void;
|
|
41
|
+
}
|
|
42
|
+
export declare function useMaintenance({ deps, t }: {
|
|
43
|
+
deps: MaintenanceDeps;
|
|
44
|
+
t: Translate;
|
|
45
|
+
}): Maintenance;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 量的是**盒子的 border-box 宽**(`getBoundingClientRect().width`),不是 `contentRect.width`:
|
|
8
8
|
* 后者已经扣掉内边距,而"多宽才分两栏"那个阈值说的是整块盒子——拿扣过内边垫的数去比就是**扣两次**,
|
|
9
|
-
*
|
|
9
|
+
* 结果明明够宽却退化成下钻(真机反馈后定的判据)。
|
|
10
10
|
*
|
|
11
11
|
* 首帧读一次当前宽度,之后跟着 RO 走;读到 0 就是窄档 ⇒ **窄档是默认、宽档要被证明**。
|
|
12
12
|
* RO 缺席(不可能的宿主)时停在窄档:功能一条不少,只是同屏两栏变成下钻。
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
import type { BusyGate } from './busy-gate.js';
|
|
2
2
|
import { type SessionDeleteRemote, type TrashEntryLike } from './remote.js';
|
|
3
|
-
import type { PendingAction } from './trash-confirm.js';
|
|
4
3
|
import type { TrashDetailState } from './trash-detail.js';
|
|
5
|
-
import type
|
|
4
|
+
import { type TrashQuery } from './use-trash-query.js';
|
|
5
|
+
/** 待确认的动作:一条记录、整个回收站、或多选挑出来的一批(`ids` 已按清单顺序排好)。
|
|
6
|
+
* 类型归状态机,视图只 import 它来渲染。 */
|
|
7
|
+
export type PendingAction = {
|
|
8
|
+
kind: 'entry';
|
|
9
|
+
entry: TrashEntryLike;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'all';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'entries';
|
|
14
|
+
ids: readonly string[];
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'restore';
|
|
17
|
+
ids: readonly string[];
|
|
18
|
+
};
|
|
6
19
|
/**
|
|
7
|
-
* 控制器要的面。`query`
|
|
8
|
-
* `refreshAfterWrite
|
|
9
|
-
* "屏幕上的顺序"(只含展开的组),
|
|
20
|
+
* 控制器要的面。`query` 取五样:清单(选中从它里面找)、`refreshAll`(挂载与显式重试)、
|
|
21
|
+
* `refreshAfterWrite`(失效与写后)、清空(面板的写动作那一半)、`noteAttachmentCleanup`(purge 回执里
|
|
22
|
+
* 附件那半句交给查询侧拼句子上屏)。`ordered` 是键盘/自动选中用的"屏幕上的顺序"(只含展开的组),
|
|
10
23
|
* 由面板算好传进来——布局常量不搬进控制器。
|
|
11
24
|
*/
|
|
12
25
|
export interface TrashPanelControllerInput {
|
|
13
26
|
deps: Pick<SessionDeleteRemote, 'detail' | 'applyOutcomes'>;
|
|
14
27
|
gate: BusyGate;
|
|
15
|
-
query: Pick<TrashQuery, '
|
|
28
|
+
query: Pick<TrashQuery, 'list' | 'refreshAll' | 'refreshAfterWrite' | 'emptyTrash' | 'noteAttachmentCleanup'>;
|
|
16
29
|
ordered: readonly TrashEntryLike[];
|
|
17
30
|
canAutoSelect: boolean;
|
|
18
31
|
}
|
|
@@ -24,6 +37,8 @@ export interface TrashPanelController {
|
|
|
24
37
|
readonly detail: TrashDetailState;
|
|
25
38
|
/** 待确认的那一批(确认框的开合);确认落地后由控制器收起。 */
|
|
26
39
|
readonly pending: PendingAction | null;
|
|
40
|
+
/** 恢复确认框要摆的这一批(按勾选顺序;清单里已找不到的 id 滤掉)。 */
|
|
41
|
+
readonly pendingEntries: readonly TrashEntryLike[];
|
|
27
42
|
/** 恢复/彻底删除/批量 的失败原文,按条目 id 存:切走再回来还在。 */
|
|
28
43
|
readonly actionFailure: Readonly<Record<string, string>>;
|
|
29
44
|
select(entry: TrashEntryLike, options?: {
|
|
@@ -31,7 +46,9 @@ export interface TrashPanelController {
|
|
|
31
46
|
}): Promise<void>;
|
|
32
47
|
retry(): void;
|
|
33
48
|
back(): void;
|
|
34
|
-
|
|
49
|
+
/** 构造待确认动作(唯一入口:新增一种确认只改 `PendingAction` 与确认框,不再加一串转调)。 */
|
|
50
|
+
request(action: PendingAction): void;
|
|
51
|
+
cancelPending(): void;
|
|
35
52
|
/** 单条恢复(`/apply` 的 N=1):成功后丢缓存,写完无论成败都重读清单,失败原文落回这一行。 */
|
|
36
53
|
restore(entry: TrashEntryLike): Promise<void>;
|
|
37
54
|
/** 确认框上的「确认」:按 `pending.kind` 分派到空清或 `confirmApply`。 */
|