dsh-life-pack 0.3.2 → 0.3.4

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.
@@ -0,0 +1,24 @@
1
+ /** 目录浏览器的公开门(**六家只 import 这一个名字**)。
2
+ *
3
+ * 为什么需要它:`package.json` 的 exports 一条子路径只能指一个文件,而本件分三份写
4
+ * (契约/状态/视图)。若把 `./directory-browser` 直接指到 `directory-browser-contract.js`,
5
+ * 六家就取不到「开图接线」(它在 state 那份里)。故设这一个门,把两份纯逻辑都转出;
6
+ * 视图那一份走 `./directory-browser-ui`(它要吃 React,单独一条)。
7
+ *
8
+ * 本件不含实现、也不含判断,只做转出——实现见同目录三份。
9
+ */
10
+ export { browseFaceOf, filterEntries, hasPickFn, hiddenCount, isBrowseFace, joinPath, locationLabel, parentOf, pickerModeOf, readBrowseAnswer, readPickAnswer, splitDraft, validateFolderName, visibleEntries, } from './directory-browser-contract.js';
11
+ export { BrowseAnswerError } from './directory-browser-contract.js';
12
+ export type { BrowseFailure, DirectoryBrowseFace, DirectoryCrumb, DirectoryEntry, DirectoryListing, PickEnvelope, PickerMode, PickOutcome, } from './directory-browser-contract.js';
13
+ export { canGoUp, createBaseOf, createBrowseController, createDirectoryRowBrowser, describe, entryPath, openRowBrowser, rowsOf, targetOf, } from './directory-browser-state.js';
14
+ export type { BrowseController, BrowseListener, BrowsePhase, BrowseState, DirectoryRowBrowser, } from './directory-browser-state.js';
15
+ import type { PickerMode } from './directory-browser-contract.js';
16
+ /** 一行目录配置的入口(**六家画按钮就靠这两格**):
17
+ * `mode` 决定按钮写什么(`native`=「选择文件夹…」/`browse`=「浏览…」),
18
+ * `onOpen` 是点下去的动作(系统对话框或应用内浏览器,由各家自己接线)。
19
+ * `mode === 'none'` 时调用方不该画入口——供不了就收起入口,不是失败。 */
20
+ export interface DirectoryRowEntry {
21
+ readonly mode: PickerMode;
22
+ readonly onOpen: (key: string) => Promise<void> | undefined;
23
+ }
24
+ //# sourceMappingURL=directory-browser-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser-api.d.ts","sourceRoot":"","sources":["../src/directory-browser-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,OAAO,EACP,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,MAAM,EACN,QAAQ,GACT,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,WAAW,EACX,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE;;;kDAGkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;CAC7D"}
@@ -0,0 +1,13 @@
1
+ /** 目录浏览器的公开门(**六家只 import 这一个名字**)。
2
+ *
3
+ * 为什么需要它:`package.json` 的 exports 一条子路径只能指一个文件,而本件分三份写
4
+ * (契约/状态/视图)。若把 `./directory-browser` 直接指到 `directory-browser-contract.js`,
5
+ * 六家就取不到「开图接线」(它在 state 那份里)。故设这一个门,把两份纯逻辑都转出;
6
+ * 视图那一份走 `./directory-browser-ui`(它要吃 React,单独一条)。
7
+ *
8
+ * 本件不含实现、也不含判断,只做转出——实现见同目录三份。
9
+ */
10
+ export { browseFaceOf, filterEntries, hasPickFn, hiddenCount, isBrowseFace, joinPath, locationLabel, parentOf, pickerModeOf, readBrowseAnswer, readPickAnswer, splitDraft, validateFolderName, visibleEntries, } from './directory-browser-contract.js';
11
+ export { BrowseAnswerError } from './directory-browser-contract.js';
12
+ export { canGoUp, createBaseOf, createBrowseController, createDirectoryRowBrowser, describe, entryPath, openRowBrowser, rowsOf, targetOf, } from './directory-browser-state.js';
13
+ //# sourceMappingURL=directory-browser-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser-api.js","sourceRoot":"","sources":["../src/directory-browser-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAYpE,OAAO,EACL,OAAO,EACP,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,MAAM,EACN,QAAQ,GACT,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,148 @@
1
+ /** 目录浏览器的纯逻辑半(**不认识宿主**:只认两格原语)。
2
+ *
3
+ * 为什么在这里:六个单品插件的设置页各有一批「值是一个目录」的行,它们要的界面是同一个东西。
4
+ * 界面的**取数**来自宿主给的两个原语(`list`/`createDirectory`),而这两个原语的形状是**调用方喂进来的**
5
+ * ——本件不 import 宿主的任何实现,也不认识 `remote.directoryPicker` 这个名字(那两件住在各家的
6
+ * `dsh-ctx.ts` 镜像里)。于是本件在 Windows 桌面、远程浏览器、SSH 三种部署下是同一份代码。
7
+ *
8
+ * 装什么:本次要浏览的那一层怎么走(进子目录/回上一级/跳面包屑)、路径怎么切出「目录部分」与
9
+ * 「筛选词」、回执怎么认。**不装**任何 IO、不装任何持久化——`list`/`createDirectory` 由调用方执行。
10
+ *
11
+ * 与平台自带浏览器件的关系:`@deepseek-ai/dsh-client-ui-directory-picker-browse` 的 `DirectoryBrowser`
12
+ * 是包内私有件(只经 `ui-workspace` 的流程空位暴露),插件侧取不到,所以这里是一份独立实现,
13
+ * 只对齐它的**交互结果**(进/选/新建),不对齐它的 DOM。
14
+ */
15
+ /** 一层目录里的一个子目录。`hidden` 由宿主判,本件只照传。 */
16
+ export interface DirectoryEntry {
17
+ readonly name: string;
18
+ readonly path: string;
19
+ readonly hidden: boolean;
20
+ }
21
+ /** 面包屑的一格:从文件系统根到当前层,每一格都是一个可跳转目标。 */
22
+ export interface DirectoryCrumb {
23
+ readonly name: string;
24
+ readonly path: string;
25
+ readonly hidden: boolean;
26
+ }
27
+ /** 一次列举的回执(形状照宿主 browse 原语:`path`/`home`/`crumbs`/`entries`/`truncated`)。 */
28
+ export interface DirectoryListing {
29
+ readonly path: string;
30
+ readonly home: string;
31
+ readonly crumbs: readonly DirectoryCrumb[];
32
+ readonly entries: readonly DirectoryEntry[];
33
+ readonly truncated: boolean;
34
+ }
35
+ /** 取数面:由调用方注入。本件只调这两个方法,别的什么都不调。 */
36
+ export interface DirectoryBrowseFace {
37
+ list(path?: string, signal?: AbortSignal): Promise<DirectoryListing>;
38
+ createDirectory(path: string, name: string): Promise<string>;
39
+ }
40
+ /** 浏览失败的人话(`code` 保留给用例断言,`message` 直接上图)。 */
41
+ export interface BrowseFailure {
42
+ readonly code: string;
43
+ readonly message: string;
44
+ }
45
+ /** 宿主拒了一条路的说法随宿主变化,**本件不认识**:调用方把它的回执码传进 `openRowBrowser`。
46
+ *
47
+ * 它为什么是唯一信号:一次组合里只服务一种能力(native 或 browse),另一条路上的动词一律被拒
48
+ * (`@deepseek-ai/dsh-api-workspace-controller/lib/types/directory-picker.js` 的 `requireCapability`);
49
+ * 而三条动词在客户端命名空间上都在(都由 `TYPERT_REMOTE.descriptors` 生成),
50
+ * 所以「有没有 `pick`/有没有 `list`」问不出组合里服务哪种能力,只有真调一次、看它拒没拒才知道。 */
51
+ /** 目录行入口的三态判定结果:**按命名空间上有什么动词**看一眼,不是问组合里服务哪种能力。 */
52
+ export type PickerMode = 'native' | 'browse' | 'none';
53
+ /** 平台回执的信封(各家的 `DirectoryPickerAnswer` 与此同形;本件只认这三格,不认别家的类名)。 */
54
+ export interface PickEnvelope {
55
+ readonly ok: boolean;
56
+ readonly value?: string | null;
57
+ readonly error?: {
58
+ readonly code?: string;
59
+ readonly message?: string;
60
+ };
61
+ }
62
+ /** 一次「唤起选择器」的归一结果:选中/取消/供不了,永不抛。
63
+ *
64
+ * `code` 只在平台给了的时候带上(就是 `error.code`):调用方据它分辨「这条路宿主没给」
65
+ * ({@link CAPABILITY_REFUSED})与「别的失败」——前者该换一条路走,后者才该报出来。 */
66
+ export type PickOutcome = {
67
+ readonly kind: 'picked';
68
+ readonly path: string;
69
+ } | {
70
+ readonly kind: 'cancelled';
71
+ } | {
72
+ readonly kind: 'unavailable';
73
+ readonly code?: string;
74
+ readonly message: string;
75
+ };
76
+ /** 认一认某个值是不是可用的目录取数面(软依赖守卫用:认不出就当没有,绝不把页面带下来)。 */
77
+ export declare function isBrowseFace(raw: unknown): raw is DirectoryBrowseFace;
78
+ /** 认一认某个值有没有 `pick`(对应宿主 composition 里的 `native` 能力)。 */
79
+ export declare function hasPickFn(raw: unknown): boolean;
80
+ /** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。
81
+ *
82
+ * 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,
83
+ * 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,
84
+ * 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在
85
+ * Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */
86
+ export declare function pickerModeOf(raw: unknown): PickerMode;
87
+ /** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。
88
+ *
89
+ * 出处:`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`(失败不抛、成功也不回裸值),
90
+ * 第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js:99-103` 就照这个信封拆。
91
+ * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉(#743 的教训)。 */
92
+ export declare function readPickAnswer(raw: unknown): PickOutcome;
93
+ /** 取数原语失败:带上宿主给的回执码,好让调用方分辨「没这条路」与「这一层读不出来」。
94
+ *
95
+ * `code` 就是 `humanize`(状态那半)读的那一格,人话直接上图。 */
96
+ export declare class BrowseAnswerError extends Error {
97
+ readonly code: string;
98
+ constructor(code: string, message: string);
99
+ }
100
+ /** 宿主 browse 原语的**信封** → 值,或按信封里的 `error` 抛。
101
+ *
102
+ * 为什么必须走这一道:`list`/`createDirectory` 与 `pick` 一样是 Remote 动词,客户端拿到的是
103
+ * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`)。
104
+ * 把信封当值用,图上就会是一层「读出来了但一行都没有」的空目录(#744 实测),
105
+ * 而不是报错——所以宁可在这里抛,也不许把它当列举结果传下去。
106
+ * 第一方消费方同口径:`@deepseek-ai/dsh-client-ui-workspace/lib/client.js:105-113` 也是这么拆的。 */
107
+ export declare function readBrowseAnswer<T>(raw: unknown, what: string): T;
108
+ /** 宿主命名空间 → 本件的取数面:把两条原语的信封拆开,成功给值、失败按码抛。
109
+ *
110
+ * 命名空间上两格原语不在就回 null(调用方据此不画入口)。**透传参数个数照宿主的规矩**:
111
+ * 描述符声明了可选 `AbortSignal`(`cancellation: {parameter:'signal'}`),而客户端按
112
+ * `values.length === 业务参数个数 + 1` 判有没有 signal——显式传一个 `undefined` 会被当成 signal
113
+ * 去 `AbortSignal.any([…, undefined])` 而炸。本件的界面不做取消,故**从不传 signal**。 */
114
+ export declare function browseFaceOf(raw: unknown): DirectoryBrowseFace | null;
115
+ /** 上一层:`'C:\\a\\b\\'` → `'C:\\a'`;已经是根则回自身;相对名(没有任何分隔符)也回自身。
116
+ *
117
+ * 尾分隔符先吃掉再切——否则「回上一级」会在原地打转。**盘根单独认**:`C:\` 掐掉尾分隔符会剩成
118
+ * `C:`,而 `C:` 是「当前盘」不是「上一层」,回它就会多跑一趟、还可能跨到别的盘上去。
119
+ * UNC(`\\server\share`)不特殊处理:切到头就是共享名本身,浏览停在共享根。 */
120
+ export declare function parentOf(path: string): string;
121
+ /** 把「当前路径 + 一段输入」拼成一个目标路径。 */
122
+ export declare function joinPath(directory: string, name: string): string;
123
+ /** 一层列举里,哪些行可进:隐藏目录按开关过滤,其余照宿主给的顺序。 */
124
+ export declare function visibleEntries(listing: DirectoryListing, showHidden: boolean): readonly DirectoryEntry[];
125
+ /** 这一层有多少个被藏起来的目录(用来决定「显示隐藏目录」那个开关要不要出现在图上)。 */
126
+ export declare function hiddenCount(listing: DirectoryListing): number;
127
+ /** 拿一段文本按「最后一个分隔符」切成「目录部分 + 筛选词」。
128
+ *
129
+ * 没打过任何分隔符时目录部分=null(那段文字还不指任何目录,只当筛选词用)。
130
+ * Windows 上 `/` 也当分隔符(宿主那边两种都收),POSIX 上不。 */
131
+ export declare function splitDraft(draft: string): {
132
+ readonly directory: string | null;
133
+ readonly filter: string;
134
+ };
135
+ /** 按筛选词过一层目录(大小写不敏感;空筛选词=全留)。 */
136
+ export declare function filterEntries(entries: readonly DirectoryEntry[], filter: string): readonly DirectoryEntry[];
137
+ /** 给界面用的一句话位置说明:优先显示相对 home 的说法,省得整条绝对路径占满一行。 */
138
+ export declare function locationLabel(listing: DirectoryListing): string;
139
+ /** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。
140
+ *
141
+ * 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */
142
+ export declare function validateFolderName(name: string): {
143
+ readonly ok: true;
144
+ } | {
145
+ readonly ok: false;
146
+ readonly reason: string;
147
+ };
148
+ //# sourceMappingURL=directory-browser-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser-contract.d.ts","sourceRoot":"","sources":["../src/directory-browser-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ /** 目录浏览器的纯逻辑半(**不认识宿主**:只认两格原语)。
2
+ *
3
+ * 为什么在这里:六个单品插件的设置页各有一批「值是一个目录」的行,它们要的界面是同一个东西。
4
+ * 界面的**取数**来自宿主给的两个原语(`list`/`createDirectory`),而这两个原语的形状是**调用方喂进来的**
5
+ * ——本件不 import 宿主的任何实现,也不认识 `remote.directoryPicker` 这个名字(那两件住在各家的
6
+ * `dsh-ctx.ts` 镜像里)。于是本件在 Windows 桌面、远程浏览器、SSH 三种部署下是同一份代码。
7
+ *
8
+ * 装什么:本次要浏览的那一层怎么走(进子目录/回上一级/跳面包屑)、路径怎么切出「目录部分」与
9
+ * 「筛选词」、回执怎么认。**不装**任何 IO、不装任何持久化——`list`/`createDirectory` 由调用方执行。
10
+ *
11
+ * 与平台自带浏览器件的关系:`@deepseek-ai/dsh-client-ui-directory-picker-browse` 的 `DirectoryBrowser`
12
+ * 是包内私有件(只经 `ui-workspace` 的流程空位暴露),插件侧取不到,所以这里是一份独立实现,
13
+ * 只对齐它的**交互结果**(进/选/新建),不对齐它的 DOM。
14
+ */
15
+ /** 认一认某个值是不是可用的目录取数面(软依赖守卫用:认不出就当没有,绝不把页面带下来)。 */
16
+ export function isBrowseFace(raw) {
17
+ if (typeof raw !== 'object' || raw === null)
18
+ return false;
19
+ const face = raw;
20
+ return typeof face.list === 'function' && typeof face.createDirectory === 'function';
21
+ }
22
+ /** 认一认某个值有没有 `pick`(对应宿主 composition 里的 `native` 能力)。 */
23
+ export function hasPickFn(raw) {
24
+ if (typeof raw !== 'object' || raw === null)
25
+ return false;
26
+ return typeof raw.pick === 'function';
27
+ }
28
+ /** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。
29
+ *
30
+ * 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,
31
+ * 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,
32
+ * 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在
33
+ * Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */
34
+ export function pickerModeOf(raw) {
35
+ if (isBrowseFace(raw))
36
+ return 'browse';
37
+ return hasPickFn(raw) ? 'native' : 'none';
38
+ }
39
+ /** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。
40
+ *
41
+ * 出处:`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`(失败不抛、成功也不回裸值),
42
+ * 第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js:99-103` 就照这个信封拆。
43
+ * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉(#743 的教训)。 */
44
+ export function readPickAnswer(raw) {
45
+ if (typeof raw === 'string')
46
+ return raw.trim() === '' ? { kind: 'cancelled' } : { kind: 'picked', path: raw };
47
+ const answer = (typeof raw === 'object' && raw !== null ? raw : {});
48
+ if (answer.ok === true) {
49
+ const value = answer.value;
50
+ return typeof value === 'string' && value.trim() !== '' ? { kind: 'picked', path: value } : { kind: 'cancelled' };
51
+ }
52
+ const detail = answer.error?.message?.trim() ?? '';
53
+ const code = answer.error?.code;
54
+ return {
55
+ kind: 'unavailable',
56
+ ...(typeof code === 'string' && code !== '' ? { code } : {}),
57
+ message: '打不开系统文件夹对话框' + (detail === '' ? '' : '(' + detail + ')') + ':请直接在框里填绝对路径。',
58
+ };
59
+ }
60
+ /** 取数原语失败:带上宿主给的回执码,好让调用方分辨「没这条路」与「这一层读不出来」。
61
+ *
62
+ * `code` 就是 `humanize`(状态那半)读的那一格,人话直接上图。 */
63
+ export class BrowseAnswerError extends Error {
64
+ code;
65
+ constructor(code, message) {
66
+ super(message);
67
+ this.name = 'BrowseAnswerError';
68
+ this.code = code;
69
+ }
70
+ }
71
+ /** 宿主 browse 原语的**信封** → 值,或按信封里的 `error` 抛。
72
+ *
73
+ * 为什么必须走这一道:`list`/`createDirectory` 与 `pick` 一样是 Remote 动词,客户端拿到的是
74
+ * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`)。
75
+ * 把信封当值用,图上就会是一层「读出来了但一行都没有」的空目录(#744 实测),
76
+ * 而不是报错——所以宁可在这里抛,也不许把它当列举结果传下去。
77
+ * 第一方消费方同口径:`@deepseek-ai/dsh-client-ui-workspace/lib/client.js:105-113` 也是这么拆的。 */
78
+ export function readBrowseAnswer(raw, what) {
79
+ const answer = (typeof raw === 'object' && raw !== null ? raw : {});
80
+ if (answer.ok === true && answer.value !== undefined && answer.value !== null)
81
+ return answer.value;
82
+ const detail = answer.error?.message?.trim() ?? '';
83
+ const code = answer.error?.code;
84
+ throw new BrowseAnswerError(typeof code === 'string' && code !== '' ? code : 'browse-failed', detail === '' ? what + '没有回执' : detail);
85
+ }
86
+ /** 宿主命名空间 → 本件的取数面:把两条原语的信封拆开,成功给值、失败按码抛。
87
+ *
88
+ * 命名空间上两格原语不在就回 null(调用方据此不画入口)。**透传参数个数照宿主的规矩**:
89
+ * 描述符声明了可选 `AbortSignal`(`cancellation: {parameter:'signal'}`),而客户端按
90
+ * `values.length === 业务参数个数 + 1` 判有没有 signal——显式传一个 `undefined` 会被当成 signal
91
+ * 去 `AbortSignal.any([…, undefined])` 而炸。本件的界面不做取消,故**从不传 signal**。 */
92
+ export function browseFaceOf(raw) {
93
+ if (!isBrowseFace(raw))
94
+ return null;
95
+ const namespace = raw;
96
+ return {
97
+ list: async (path) => readBrowseAnswer(path === undefined ? await namespace.list() : await namespace.list(path), '列举目录'),
98
+ createDirectory: async (path, name) => readBrowseAnswer(await namespace.createDirectory(path, name), '新建文件夹'),
99
+ };
100
+ }
101
+ /** 一段路径里最后那个分隔符的位置(Windows 上 `\` 与 `/` 都算;POSIX 上 `\` 是合法文件名字符,不算)。 */
102
+ function lastSeparator(path) {
103
+ const back = path.lastIndexOf('\\');
104
+ const slash = path.lastIndexOf('/');
105
+ // 只含 `\` 的路径按 Windows 规则解;两者都有时取更靠后的那个。
106
+ return back > slash ? back : slash;
107
+ }
108
+ /** 上一层:`'C:\\a\\b\\'` → `'C:\\a'`;已经是根则回自身;相对名(没有任何分隔符)也回自身。
109
+ *
110
+ * 尾分隔符先吃掉再切——否则「回上一级」会在原地打转。**盘根单独认**:`C:\` 掐掉尾分隔符会剩成
111
+ * `C:`,而 `C:` 是「当前盘」不是「上一层」,回它就会多跑一趟、还可能跨到别的盘上去。
112
+ * UNC(`\\server\share`)不特殊处理:切到头就是共享名本身,浏览停在共享根。 */
113
+ export function parentOf(path) {
114
+ const trimmed = path.replace(/[\\/]+$/, '');
115
+ if (trimmed === '')
116
+ return path;
117
+ const sep = path.includes('\\') ? '\\' : '/';
118
+ if (/^[A-Za-z]:$/.test(trimmed))
119
+ return trimmed + sep;
120
+ const cut = lastSeparator(trimmed);
121
+ if (cut === -1)
122
+ return trimmed;
123
+ if (cut === 0)
124
+ return path.slice(0, 1);
125
+ if (cut === 2 && /^[A-Za-z]:/.test(trimmed))
126
+ return trimmed.slice(0, 2) + sep;
127
+ return trimmed.slice(0, cut);
128
+ }
129
+ /** 把「当前路径 + 一段输入」拼成一个目标路径。 */
130
+ export function joinPath(directory, name) {
131
+ const sep = directory.includes('\\') ? '\\' : '/';
132
+ const base = directory.replace(/[\\/]+$/, '');
133
+ if (base === '')
134
+ return sep + name;
135
+ return base + sep + name;
136
+ }
137
+ /** 一层列举里,哪些行可进:隐藏目录按开关过滤,其余照宿主给的顺序。 */
138
+ export function visibleEntries(listing, showHidden) {
139
+ return showHidden ? listing.entries : listing.entries.filter((entry) => !entry.hidden);
140
+ }
141
+ /** 这一层有多少个被藏起来的目录(用来决定「显示隐藏目录」那个开关要不要出现在图上)。 */
142
+ export function hiddenCount(listing) {
143
+ return listing.entries.filter((entry) => entry.hidden).length;
144
+ }
145
+ /** 拿一段文本按「最后一个分隔符」切成「目录部分 + 筛选词」。
146
+ *
147
+ * 没打过任何分隔符时目录部分=null(那段文字还不指任何目录,只当筛选词用)。
148
+ * Windows 上 `/` 也当分隔符(宿主那边两种都收),POSIX 上不。 */
149
+ export function splitDraft(draft) {
150
+ const cut = lastSeparator(draft);
151
+ if (cut === -1)
152
+ return { directory: null, filter: draft };
153
+ return { directory: draft.slice(0, cut + 1), filter: draft.slice(cut + 1) };
154
+ }
155
+ /** 按筛选词过一层目录(大小写不敏感;空筛选词=全留)。 */
156
+ export function filterEntries(entries, filter) {
157
+ const needle = filter.trim().toLowerCase();
158
+ if (needle === '')
159
+ return entries;
160
+ return entries.filter((entry) => entry.name.toLowerCase().includes(needle));
161
+ }
162
+ /** 给界面用的一句话位置说明:优先显示相对 home 的说法,省得整条绝对路径占满一行。 */
163
+ export function locationLabel(listing) {
164
+ const home = listing.home.replace(/[\\/]+$/, '');
165
+ if (home !== '' && listing.path !== home && listing.path.startsWith(home)) {
166
+ const tail = listing.path.slice(home.length).replace(/^[\\/]+/, '');
167
+ if (tail !== '')
168
+ return '~' + (listing.path.includes('\\') ? '\\' : '/') + tail;
169
+ }
170
+ return listing.path;
171
+ }
172
+ /** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。
173
+ *
174
+ * 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */
175
+ export function validateFolderName(name) {
176
+ const trimmed = name.trim();
177
+ if (trimmed === '')
178
+ return { ok: false, reason: '文件夹名不能为空。' };
179
+ if (/[\\/]/.test(trimmed))
180
+ return { ok: false, reason: '文件夹名里不能带路径分隔符。' };
181
+ if (trimmed === '.' || trimmed === '..')
182
+ return { ok: false, reason: '「.」与「..」不是文件夹名。' };
183
+ return { ok: true };
184
+ }
185
+ //# sourceMappingURL=directory-browser-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser-contract.js","sourceRoot":"","sources":["../src/directory-browser-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ /** 文件浏览器的操作半:把「进哪一层、这一层显示什么、选到了什么」算成状态,**IO 全部由调用方注入**。
2
+ *
3
+ * 分两层的原因:本件(操作)在 Node 里就能直测——喂一个假的取数面,就能把「进目录/回上一级/
4
+ * 跳面包屑/筛选/新建文件夹/失败出人话」全跑一遍;视图那半(`directory-browser-ui.ts`)
5
+ * 只负责把这份状态画成 DOM。两边都不认识宿主。
6
+ *
7
+ * 数据来源只有两格:`list(path?)` 与 `createDirectory(path, name)`(调用方注入的 `DirectoryBrowseFace`)。
8
+ */
9
+ import type { BrowseFailure, DirectoryBrowseFace, DirectoryEntry, DirectoryListing } from './directory-browser-contract.js';
10
+ /** 一层列举的三态:加载中/拿到了/这一层读不出来。 */
11
+ export type BrowsePhase = 'idle' | 'loading' | 'ready' | 'failed';
12
+ /** 图上的全部状态。视图只读它,不改它。 */
13
+ export interface BrowseState {
14
+ readonly phase: BrowsePhase;
15
+ readonly listing: DirectoryListing | null;
16
+ /** 这一层读失败时的人话;`phase==='failed'` 时一定有。 */
17
+ readonly failure: BrowseFailure | null;
18
+ /** 当前选中的子目录(点行选中;选中后再点「打开」就是它)。 */
19
+ readonly selected: string | null;
20
+ /** 面包屑上那一层的编号,只用于画图。 */
21
+ readonly showHidden: boolean;
22
+ /** 路径框里的草稿(用户可能正在编辑)。 */
23
+ readonly draft: string;
24
+ /** 草稿指向的那一层已经列举过之后,用来按末段过滤的词。 */
25
+ readonly filter: string;
26
+ /** 正在建的文件夹名;null=没在建。 */
27
+ readonly creating: string | null;
28
+ /** 上一次操作的失败人话(新建失败等);看一眼就好,下一次操作清掉。 */
29
+ readonly notice: string | null;
30
+ }
31
+ /** 订阅者拿到的状态(不可变,每次变更是新对象)。 */
32
+ export type BrowseListener = (state: BrowseState) => void;
33
+ /** 操作面:视图与用例都只用这些方法。 */
34
+ export interface BrowseController {
35
+ getState(): BrowseState;
36
+ subscribe(listener: BrowseListener): () => void;
37
+ /** 开图:先试配置里那个目录(空则直接家目录),读不出来(不存在、没权限)退回宿主家目录。
38
+ *
39
+ * 回**第一份落定的状态**:调用方据此分辨「这条路宿主没给」与「这一层读不出来」。 */
40
+ open(): Promise<BrowseState>;
41
+ /** 进某一层(绝对路径,或 `list()` 的默认家目录当入参为 null)。 */
42
+ enter(path: string | null): Promise<void>;
43
+ /** 回上一级(当前层就是根时原地不动)。 */
44
+ up(): Promise<void>;
45
+ /** 采纳当前选择(选中行,没有选中就用当前层)并回调 `onPicked`。 */
46
+ pick(): void;
47
+ /** 取消:关图,什么都不回调。 */
48
+ cancel(): void;
49
+ /** 选中一个子目录(再点一次同一条=取消选中)。 */
50
+ select(path: string): void;
51
+ /** 切换隐藏目录。 */
52
+ toggleHidden(): void;
53
+ /** 改路径框草稿(不列举,只更新草稿与筛选词)。 */
54
+ setDraft(draft: string): void;
55
+ /** 提交路径框草稿:指到哪一层就去哪一层。 */
56
+ commitDraft(): Promise<void>;
57
+ /** 开「新建文件夹」;传 null 收起。 */
58
+ setCreating(name: string | null): void;
59
+ /** 在当前层(或选中的那一层)下建一个新文件夹,成功后进它并选中它。 */
60
+ createFolder(name: string): Promise<void>;
61
+ /** 关图(与 `cancel` 同义,供宿主侧统一收口)。 */
62
+ close(): void;
63
+ }
64
+ /** 建一个浏览器控制器。取数、回调、初值全部由调用方给。 */
65
+ export declare function createBrowseController(deps: {
66
+ readonly face: DirectoryBrowseFace;
67
+ readonly initialPath: string;
68
+ readonly onPicked: (path: string) => void;
69
+ readonly onClose: () => void;
70
+ /** 失败人话的前缀,默认「浏览失败」。 */
71
+ readonly failureLabel?: string;
72
+ }): BrowseController;
73
+ /** 图上这一层要画的行(按隐藏开关与草稿末段过滤之后的)。 */
74
+ export declare function rowsOf(state: BrowseState): readonly DirectoryEntry[];
75
+ /** 图上这一层能不能回上一级(根目录不能)。 */
76
+ export declare function canGoUp(state: BrowseState): boolean;
77
+ /** 「打开」这颗按钮的目标:选中行优先,没选中就是当前层。 */
78
+ export declare function targetOf(state: BrowseState): string | null;
79
+ /** 新建文件夹那一行要展示的位置("在 <这里> 下新建")。 */
80
+ export declare function createBaseOf(state: BrowseState): string | null;
81
+ /** 一段路径拼成可读的一句话(给用例与调试用;图上不直接显示)。 */
82
+ export declare function describe(state: BrowseState): string;
83
+ /** 一行子目录的完整路径(图上点行时用)。 */
84
+ export declare function entryPath(directory: string, entry: DirectoryEntry): string;
85
+ /** 目录行接线的返回:**不碰 React**——`actions` 原样喂给 `directory-browser-ui` 的组件 props,
86
+ * `isOpen`/`setOpen` 由调用方自己接进它的状态(宿主怎么重画是它的事,本件不管)。 */
87
+ export interface DirectoryRowBrowser {
88
+ readonly actions: {
89
+ readonly onPick: () => void;
90
+ readonly onClose: () => void;
91
+ readonly onEnter: (path: string) => void;
92
+ readonly onUp: () => void;
93
+ readonly onSelect: (path: string) => void;
94
+ readonly onToggleHidden: () => void;
95
+ readonly onDraft: (draft: string) => void;
96
+ readonly onCommitDraft: () => void;
97
+ readonly onCreate: (name: string) => void;
98
+ readonly onCreatingChange: (name: string | null) => void;
99
+ };
100
+ /** 开图(先试配置里那个目录,读不出来退回家目录),回第一份落定的状态。 */
101
+ open(): Promise<BrowseState>;
102
+ /** 状态变更订阅:视图那半靠 `useSyncExternalStore` 用它重画。
103
+ *
104
+ * **没有它图上只有第一帧**——列举是异步落定的,React 不订阅就永远停在开图那一刻的状态。 */
105
+ subscribe(listener: BrowseListener): () => void;
106
+ state(): BrowseState;
107
+ /** 一段可读的当前状态(用例与调试用)。 */
108
+ summary(): string;
109
+ }
110
+ /** 建一份「目录行接线」:给一个取数面与初值,回一组动作。
111
+ *
112
+ * 只做接线:把控制器算出来的状态与回调打包好,不取数、不画图、不认宿主。 */
113
+ export declare function createDirectoryRowBrowser(deps: {
114
+ readonly face: DirectoryBrowseFace;
115
+ readonly initialPath: string;
116
+ readonly onPicked: (path: string) => void;
117
+ readonly onClosed?: () => void;
118
+ readonly failureLabel?: string;
119
+ }): DirectoryRowBrowser;
120
+ /** 一条目录行点下去该走哪条路(**六家共用这一处策略**,单独写在各家就会各错各的)。
121
+ *
122
+ * 顺序是「先应用内浏览,被拒再换系统对话框」,因为客户端命名空间上三条动词都在、问不出组合里服务哪种
123
+ * 能力(见 `pickerModeOf`)。回执:
124
+ * - `undefined`=连两格浏览原语都没有(这条路没有,调用方自己想办法);
125
+ * - `'refused'`=宿主回了 `refusalCode`(组合里是系统对话框),图已收起;
126
+ * - `'open'`=图开着,后面的事都在图里。
127
+ *
128
+ * **状态由调用方持有**:`onRow` 收到刚建好的那条(或 null=收起),React 那边据此决定画不画图。
129
+ *
130
+ * `refusalCode` 是**宿主的回执码**(各家自己从宿主镜像里拿,本件只照比不认名):
131
+ * 它换名字本件照样跑,六家一起换。 */
132
+ export declare function openRowBrowser(input: {
133
+ readonly picker: unknown;
134
+ readonly initialPath: string;
135
+ readonly onChange: (next: string) => void;
136
+ readonly onRow: (row: DirectoryRowBrowser | null) => void;
137
+ readonly refusalCode: string;
138
+ }): Promise<'open' | 'refused'> | undefined;
139
+ //# sourceMappingURL=directory-browser-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser-state.d.ts","sourceRoot":"","sources":["../src/directory-browser-state.ts"],"names":[],"mappings":"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"}