dsh-memo-ilife 0.2.1 → 0.3.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/dist/client.js +168 -47
- package/dist/client.js.map +1 -1
- package/dist/dsh-ctx.d.ts +45 -0
- package/dist/dsh-ctx.d.ts.map +1 -1
- package/dist/dsh-ctx.js +2 -1
- package/dist/dsh-ctx.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/settings.d.ts +11 -5
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +17 -13
- package/dist/settings.js.map +1 -1
- package/dist/skill-tool.d.ts +21 -0
- package/dist/skill-tool.d.ts.map +1 -0
- package/dist/skill-tool.js +109 -0
- package/dist/skill-tool.js.map +1 -0
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -71,29 +71,30 @@ window.__ModuleLoader__.load({
|
|
|
71
71
|
key: "db.dir",
|
|
72
72
|
title: "数据目录",
|
|
73
73
|
tier: "common",
|
|
74
|
-
control: "
|
|
75
|
-
|
|
74
|
+
control: "directory",
|
|
75
|
+
prefillFrom: "dataDir",
|
|
76
|
+
hint: "库文件与产物的根目录。留空=用默认目录。"
|
|
76
77
|
},
|
|
77
78
|
{
|
|
78
79
|
key: "db.name",
|
|
79
80
|
title: "库文件名",
|
|
80
81
|
tier: "common",
|
|
81
82
|
control: "text",
|
|
82
|
-
hint: "
|
|
83
|
+
hint: "数据目录下的数据库文件名(默认 memo.db)。改名=换库,旧笔记不会被读入。"
|
|
83
84
|
},
|
|
84
85
|
{
|
|
85
86
|
key: "html.dir",
|
|
86
87
|
title: "HTML 产物目录名",
|
|
87
88
|
tier: "common",
|
|
88
89
|
control: "text",
|
|
89
|
-
hint: "
|
|
90
|
+
hint: "数据目录下存放 HELP 页面的子目录名(默认 memo_html)。"
|
|
90
91
|
},
|
|
91
92
|
{
|
|
92
93
|
key: "media.dir",
|
|
93
94
|
title: "附件目录",
|
|
94
95
|
tier: "common",
|
|
95
|
-
control: "
|
|
96
|
-
hint: "
|
|
96
|
+
control: "directory",
|
|
97
|
+
hint: "附件的存放根目录。留空=工作目录下的 media。"
|
|
97
98
|
}
|
|
98
99
|
];
|
|
99
100
|
/** 高级项:默认收起。改错了多半只是自己撞上麻烦,不确定就别动。 */
|
|
@@ -103,28 +104,28 @@ window.__ModuleLoader__.load({
|
|
|
103
104
|
title: "产物文件名主体 · HELP",
|
|
104
105
|
tier: "advanced",
|
|
105
106
|
control: "text",
|
|
106
|
-
hint: "HELP
|
|
107
|
+
hint: "HELP 文件名前缀,后面自动加时间戳。"
|
|
107
108
|
},
|
|
108
109
|
{
|
|
109
110
|
key: "files.lookup",
|
|
110
111
|
title: "产物文件名主体 · 速查表",
|
|
111
112
|
tier: "advanced",
|
|
112
113
|
control: "text",
|
|
113
|
-
hint: "
|
|
114
|
+
hint: "速查表文件名前缀。"
|
|
114
115
|
},
|
|
115
116
|
{
|
|
116
117
|
key: "lark.cliPath",
|
|
117
118
|
title: "飞书 CLI 路径",
|
|
118
119
|
tier: "advanced",
|
|
119
120
|
control: "text",
|
|
120
|
-
hint: "飞书 CLI
|
|
121
|
+
hint: "飞书 CLI 的可执行文件路径。留空=自动探测。"
|
|
121
122
|
},
|
|
122
123
|
{
|
|
123
124
|
key: "lark.qrDir",
|
|
124
125
|
title: "飞书授权二维码落点",
|
|
125
126
|
tier: "advanced",
|
|
126
|
-
control: "
|
|
127
|
-
hint: "飞书授权二维码 PNG
|
|
127
|
+
control: "directory",
|
|
128
|
+
hint: "飞书授权二维码 PNG 的存放目录,缺就现建。留空=系统临时目录。"
|
|
128
129
|
}
|
|
129
130
|
];
|
|
130
131
|
/** 设置页的行表,顺序即页面顺序。 */
|
|
@@ -133,7 +134,7 @@ window.__ModuleLoader__.load({
|
|
|
133
134
|
const COMMON_ITEM_COUNT = COMMON.length;
|
|
134
135
|
/** 高级组标题与副文案。 */
|
|
135
136
|
const ADVANCED_GROUP_TITLE = "高级";
|
|
136
|
-
const ADVANCED_GROUP_NOTE = "
|
|
137
|
+
const ADVANCED_GROUP_NOTE = "不常改。留空=用默认值。";
|
|
137
138
|
/** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */
|
|
138
139
|
function readPath(values, key) {
|
|
139
140
|
const parts = key.split(".");
|
|
@@ -157,6 +158,10 @@ window.__ModuleLoader__.load({
|
|
|
157
158
|
cur[parts[parts.length - 1]] = value;
|
|
158
159
|
}
|
|
159
160
|
//#endregion
|
|
161
|
+
//#region src/dsh-ctx.ts
|
|
162
|
+
/** 宿主目录选择命名空间的服务名(#736;出处与禁令见 cookbook §13)。 */
|
|
163
|
+
const REMOTE_DIRECTORY_PICKER = "remote.directoryPicker";
|
|
164
|
+
//#endregion
|
|
160
165
|
//#region src/client.ts
|
|
161
166
|
/** dsh-memo-ilife client 适配器(六边形:port=contract+dsh-ctx 镜像,adapter=本文件)。
|
|
162
167
|
*
|
|
@@ -173,9 +178,11 @@ window.__ModuleLoader__.load({
|
|
|
173
178
|
* dsh-mattpocock-skills-deck/src/client/panelAssembly.js:100-108
|
|
174
179
|
* (betterSidebar 服务可能晚于本模块到达,最多 10 次、间隔 1000ms;卸载清理)。
|
|
175
180
|
* 只用 dsh-ctx 镜像内成员(slots.inject/register、connection.rpc.call 经闭包现取、
|
|
176
|
-
* betterSidebar
|
|
181
|
+
* betterSidebar 与 #736 的目录选择命名空间经 ctx.get 运行时取);取数只经 connection.rpc.call 进 host 通道;
|
|
177
182
|
* 缺席/错误纯条件渲染,不返空冒充。组件 React.createElement 手写,不引入 JSX。
|
|
178
183
|
*/
|
|
184
|
+
/** client 短名声明:只有这两个(#736 的目录选择走**可选查找**,不写进来——
|
|
185
|
+
* 写进来=硬依赖,提供方缺席时整包被停靠,设置页会跟着装不上;见 cookbook §13)。 */
|
|
179
186
|
const inject = ["slots", "connection"];
|
|
180
187
|
/** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */
|
|
181
188
|
const S = {
|
|
@@ -185,11 +192,10 @@ window.__ModuleLoader__.load({
|
|
|
185
192
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.35))",
|
|
186
193
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
187
194
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
188
|
-
fontSize: 13,
|
|
189
195
|
lineHeight: 1.6
|
|
190
196
|
},
|
|
191
197
|
title: {
|
|
192
|
-
fontSize:
|
|
198
|
+
fontSize: "1.08em",
|
|
193
199
|
fontWeight: 700,
|
|
194
200
|
marginBottom: 8
|
|
195
201
|
},
|
|
@@ -200,16 +206,16 @@ window.__ModuleLoader__.load({
|
|
|
200
206
|
},
|
|
201
207
|
muted: {
|
|
202
208
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)",
|
|
203
|
-
fontSize:
|
|
209
|
+
fontSize: "0.92em"
|
|
204
210
|
},
|
|
205
211
|
error: {
|
|
206
212
|
color: "var(--dsw-alias-state-error-primary, #ff6b6b)",
|
|
207
|
-
fontSize:
|
|
213
|
+
fontSize: "1em",
|
|
208
214
|
whiteSpace: "pre-wrap"
|
|
209
215
|
},
|
|
210
216
|
okText: {
|
|
211
217
|
color: "var(--dsw-alias-state-success-primary, #12805c)",
|
|
212
|
-
fontSize:
|
|
218
|
+
fontSize: "0.96em"
|
|
213
219
|
},
|
|
214
220
|
rows: {
|
|
215
221
|
marginTop: 8,
|
|
@@ -217,13 +223,10 @@ window.__ModuleLoader__.load({
|
|
|
217
223
|
paddingTop: 8
|
|
218
224
|
},
|
|
219
225
|
row: { marginTop: 10 },
|
|
220
|
-
label: {
|
|
221
|
-
fontSize: 12.5,
|
|
222
|
-
fontWeight: 600
|
|
223
|
-
},
|
|
226
|
+
label: { fontWeight: 600 },
|
|
224
227
|
hint: {
|
|
225
228
|
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
226
|
-
fontSize:
|
|
229
|
+
fontSize: "0.92em",
|
|
227
230
|
margin: "1px 0 4px"
|
|
228
231
|
},
|
|
229
232
|
input: {
|
|
@@ -233,14 +236,27 @@ window.__ModuleLoader__.load({
|
|
|
233
236
|
borderRadius: 6,
|
|
234
237
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
235
238
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
239
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
240
|
+
},
|
|
241
|
+
pickRow: {
|
|
242
|
+
display: "flex",
|
|
243
|
+
gap: 6,
|
|
244
|
+
alignItems: "center"
|
|
245
|
+
},
|
|
246
|
+
btnPick: {
|
|
247
|
+
flex: "0 0 auto",
|
|
248
|
+
padding: "4px 10px",
|
|
249
|
+
borderRadius: 6,
|
|
250
|
+
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
251
|
+
background: "var(--dsw-alias-bg-base, transparent)",
|
|
236
252
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
237
|
-
|
|
253
|
+
whiteSpace: "nowrap",
|
|
254
|
+
cursor: "pointer"
|
|
238
255
|
},
|
|
239
256
|
info: {
|
|
240
|
-
|
|
241
|
-
font: "12px/1.5 ui-monospace,Consolas,monospace",
|
|
257
|
+
fontSize: "0.92em",
|
|
242
258
|
color: "var(--dsw-alias-label-secondary, #9a9a9a)",
|
|
243
|
-
|
|
259
|
+
overflowWrap: "anywhere"
|
|
244
260
|
},
|
|
245
261
|
bar: {
|
|
246
262
|
marginTop: 14,
|
|
@@ -255,7 +271,6 @@ window.__ModuleLoader__.load({
|
|
|
255
271
|
border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.45))",
|
|
256
272
|
background: "var(--dsw-alias-bg-base, transparent)",
|
|
257
273
|
color: "var(--dsw-alias-label-primary, inherit)",
|
|
258
|
-
fontSize: 12.5,
|
|
259
274
|
cursor: "pointer"
|
|
260
275
|
},
|
|
261
276
|
btnPrimary: {
|
|
@@ -264,7 +279,6 @@ window.__ModuleLoader__.load({
|
|
|
264
279
|
border: "1px solid var(--dsw-alias-brand-primary, #2f6fed)",
|
|
265
280
|
background: "var(--dsw-alias-brand-primary, #2f6fed)",
|
|
266
281
|
color: "#fff",
|
|
267
|
-
fontSize: 12.5,
|
|
268
282
|
cursor: "pointer"
|
|
269
283
|
},
|
|
270
284
|
adv: {
|
|
@@ -274,7 +288,6 @@ window.__ModuleLoader__.load({
|
|
|
274
288
|
},
|
|
275
289
|
advSummary: {
|
|
276
290
|
cursor: "pointer",
|
|
277
|
-
fontSize: 12.5,
|
|
278
291
|
fontWeight: 600
|
|
279
292
|
},
|
|
280
293
|
version: {
|
|
@@ -282,7 +295,7 @@ window.__ModuleLoader__.load({
|
|
|
282
295
|
paddingTop: 8,
|
|
283
296
|
borderTop: "1px dashed var(--dsw-alias-border, rgba(128,128,128,.25))",
|
|
284
297
|
color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
|
|
285
|
-
fontSize:
|
|
298
|
+
fontSize: "0.92em"
|
|
286
299
|
}
|
|
287
300
|
};
|
|
288
301
|
/** 版本行:插件与技能双版本号(用户要求:每技能设置页自报家门)。 */
|
|
@@ -418,11 +431,11 @@ window.__ModuleLoader__.load({
|
|
|
418
431
|
*/
|
|
419
432
|
function humanizeConfigFailure(code, message) {
|
|
420
433
|
const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;
|
|
421
|
-
if (/解析|YAML|parse/i.test(text)) return `${text}\n改回「键:
|
|
422
|
-
if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\n
|
|
423
|
-
if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\n
|
|
424
|
-
if (/缺席|missing-cli/i.test(text)) return `${text}\n
|
|
425
|
-
return `${text}\n
|
|
434
|
+
if (/解析|YAML|parse/i.test(text)) return `${text}\n改回「键: 值」的写法,或点「重置为默认」。`;
|
|
435
|
+
if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\n删掉页面上没有的行,或点「重置为默认」。`;
|
|
436
|
+
if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\n按本页的控件形状填,或点「重置为默认」。`;
|
|
437
|
+
if (/缺席|missing-cli/i.test(text)) return `${text}\n技能出口没找到:先确认技能包已装好,再点「重新读取」。`;
|
|
438
|
+
return `${text}\n改不动就点「重置为默认」,或照上面那条路径手工改配置文件。`;
|
|
426
439
|
}
|
|
427
440
|
/** 一次配置 RPC(同通道过三个端点;永不抛,失败落字)。 */
|
|
428
441
|
async function configRpc(call, method, payload) {
|
|
@@ -472,12 +485,17 @@ window.__ModuleLoader__.load({
|
|
|
472
485
|
function resetConfigSurface(call) {
|
|
473
486
|
return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});
|
|
474
487
|
}
|
|
475
|
-
/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。
|
|
476
|
-
|
|
488
|
+
/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。
|
|
489
|
+
*
|
|
490
|
+
* `prefill` 是**落点回执**(配置面那几格解析出来的绝对路径):标了 `prefillFrom` 的行在取值空着时
|
|
491
|
+
* 直接显示那条绝对路径——用户不必自己拼路径(#743),显示的就是技能真会用的那个目录(逐字相同)。 */
|
|
492
|
+
function toDraft(values, prefill = {}) {
|
|
477
493
|
const draft = {};
|
|
478
494
|
for (const item of CONFIG_ITEMS) {
|
|
479
495
|
const v = readPath(values, item.key);
|
|
480
|
-
|
|
496
|
+
const raw = v === void 0 || v === null ? "" : String(v);
|
|
497
|
+
const fallback = item.prefillFrom === void 0 ? void 0 : prefill[item.prefillFrom];
|
|
498
|
+
draft[item.key] = raw === "" && typeof fallback === "string" && fallback !== "" ? fallback : raw;
|
|
481
499
|
}
|
|
482
500
|
return draft;
|
|
483
501
|
}
|
|
@@ -494,7 +512,73 @@ window.__ModuleLoader__.load({
|
|
|
494
512
|
}
|
|
495
513
|
return values;
|
|
496
514
|
}
|
|
497
|
-
/**
|
|
515
|
+
/** 形状守卫:拿到的那个东西像不像目录选择命名空间(本包只认 `pick` 是函数)。 */
|
|
516
|
+
function isDirectoryPicker(raw) {
|
|
517
|
+
if (typeof raw !== "object" || raw === null) return false;
|
|
518
|
+
return typeof raw.pick === "function";
|
|
519
|
+
}
|
|
520
|
+
/** 现取目录选择命名空间:**软依赖**,拿不到回 null(页面据此不出入口)。
|
|
521
|
+
*
|
|
522
|
+
* 为什么不用 `inject` 声明:见 cookbook §13——写进声明=硬依赖,提供方缺席时整包被停靠,
|
|
523
|
+
* 设置页会跟着装不上。这里按平台给的「可选查找」办:缺席只是没有入口。
|
|
524
|
+
* 守卫拒绝或提供方中途卸载会抛,一律当「没有」,绝不把设置页带下来。 */
|
|
525
|
+
function resolveDirectoryPicker(getService) {
|
|
526
|
+
if (typeof getService !== "function") return null;
|
|
527
|
+
try {
|
|
528
|
+
const raw = getService(REMOTE_DIRECTORY_PICKER);
|
|
529
|
+
return isDirectoryPicker(raw) ? raw : null;
|
|
530
|
+
} catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/** 平台回执 → 三种结果(**永不抛**)。回执是信封 `{ok, value|error}`(见 dsh-ctx.ts 的
|
|
535
|
+
* `DirectoryPickerAnswer`):成功回的是 `value` 不是路径本身,被拒回的是 `ok:false` 不是抛——
|
|
536
|
+
* 照裸值解会把两种情况都误判成「用户取消」(#743 真机现象:点「选择文件夹」什么都没发生)。
|
|
537
|
+
* 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉。 */
|
|
538
|
+
function readPickAnswer(raw) {
|
|
539
|
+
if (typeof raw === "string") return raw.trim() === "" ? { kind: "cancelled" } : {
|
|
540
|
+
kind: "picked",
|
|
541
|
+
path: raw
|
|
542
|
+
};
|
|
543
|
+
const answer = typeof raw === "object" && raw !== null ? raw : {};
|
|
544
|
+
if (answer.ok === true) {
|
|
545
|
+
const value = answer.value;
|
|
546
|
+
return typeof value === "string" && value.trim() !== "" ? {
|
|
547
|
+
kind: "picked",
|
|
548
|
+
path: value
|
|
549
|
+
} : { kind: "cancelled" };
|
|
550
|
+
}
|
|
551
|
+
const detail = answer.error?.message?.trim() ?? "";
|
|
552
|
+
return {
|
|
553
|
+
kind: "unavailable",
|
|
554
|
+
message: "打不开系统文件夹对话框" + (detail === "" ? "" : "(" + detail + ")") + ":请直接在框里填绝对路径。"
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/** 唤起一次系统文件夹选择器并归一结果(**永不抛**)。 */
|
|
558
|
+
async function pickDirectory(picker) {
|
|
559
|
+
try {
|
|
560
|
+
return readPickAnswer(await picker.pick());
|
|
561
|
+
} catch (e) {
|
|
562
|
+
return {
|
|
563
|
+
kind: "unavailable",
|
|
564
|
+
message: "打不开系统文件夹对话框(" + (e instanceof Error ? e.message : String(e)) + "):请直接在框里填绝对路径。"
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/** 「选择文件夹」按钮的真装配函数(目录行渲染出来的按钮,onClick 就是它)。
|
|
569
|
+
*
|
|
570
|
+
* 回 Promise 是为了本地可判(用例直接 await);接进 React 时调用方 `void` 掉。 */
|
|
571
|
+
function createBrowseHandler(deps) {
|
|
572
|
+
return async (key) => {
|
|
573
|
+
const outcome = await pickDirectory(deps.picker);
|
|
574
|
+
if (outcome.kind === "picked") deps.onChange(key, outcome.path);
|
|
575
|
+
else if (outcome.kind === "unavailable") deps.onUnavailable(outcome.message);
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。
|
|
579
|
+
*
|
|
580
|
+
* 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、
|
|
581
|
+
* 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。 */
|
|
498
582
|
function Row(props) {
|
|
499
583
|
const { item } = props;
|
|
500
584
|
const control = item.control === "switch" ? react.createElement("input", {
|
|
@@ -510,7 +594,13 @@ window.__ModuleLoader__.load({
|
|
|
510
594
|
spellCheck: false,
|
|
511
595
|
onChange: (e) => props.onChange(item.key, e.target.value)
|
|
512
596
|
});
|
|
513
|
-
|
|
597
|
+
const browse = item.control === "directory" && props.onBrowse !== void 0 ? react.createElement("button", {
|
|
598
|
+
style: S.btnPick,
|
|
599
|
+
type: "button",
|
|
600
|
+
disabled: props.disabled,
|
|
601
|
+
onClick: () => props.onBrowse?.(item.key)
|
|
602
|
+
}, "选择文件夹…") : null;
|
|
603
|
+
return react.createElement("div", { style: S.row }, react.createElement("div", { style: S.label }, item.title), react.createElement("div", { style: S.hint }, item.hint), browse === null ? control : react.createElement("div", { style: S.pickRow }, control, browse));
|
|
514
604
|
}
|
|
515
605
|
/** 技能设置页:承载备忘录自己的全部可配置项(只配置,不干活)。
|
|
516
606
|
*
|
|
@@ -518,6 +608,8 @@ window.__ModuleLoader__.load({
|
|
|
518
608
|
* 保存与重置之后都**重新读一份整面**再提示(写回执不带 dataDir/created,头部那两行要的是读整面)。 */
|
|
519
609
|
function MemoConfig(props) {
|
|
520
610
|
const [state, setState] = react.useState({ kind: "loading" });
|
|
611
|
+
const [pickerGone, setPickerGone] = react.useState(false);
|
|
612
|
+
const [picking, setPicking] = react.useState(false);
|
|
521
613
|
const [draft, setDraft] = react.useState({});
|
|
522
614
|
const [busy, setBusy] = react.useState(false);
|
|
523
615
|
const [notice, setNotice] = react.useState(null);
|
|
@@ -527,7 +619,7 @@ window.__ModuleLoader__.load({
|
|
|
527
619
|
kind: "ready",
|
|
528
620
|
surface
|
|
529
621
|
});
|
|
530
|
-
setDraft(toDraft(surface.values));
|
|
622
|
+
setDraft(toDraft(surface.values, surface));
|
|
531
623
|
}, []);
|
|
532
624
|
const load = react.useCallback(async () => {
|
|
533
625
|
setNotice(null);
|
|
@@ -562,8 +654,26 @@ window.__ModuleLoader__.load({
|
|
|
562
654
|
setNotice(null);
|
|
563
655
|
setError(null);
|
|
564
656
|
}, []);
|
|
657
|
+
/** 目录选择:拿不到命名空间就没有入口;被拒一次即收起入口(不留死按钮)。 */
|
|
658
|
+
const picker = pickerGone ? null : props.getPicker();
|
|
659
|
+
const onBrowse = react.useMemo(() => {
|
|
660
|
+
if (picker === null) return void 0;
|
|
661
|
+
const browse = createBrowseHandler({
|
|
662
|
+
picker,
|
|
663
|
+
onChange,
|
|
664
|
+
onUnavailable: (message) => {
|
|
665
|
+
setError(message);
|
|
666
|
+
setPickerGone(true);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
return (key) => {
|
|
670
|
+
setPicking(true);
|
|
671
|
+
setError(null);
|
|
672
|
+
browse(key).finally(() => setPicking(false));
|
|
673
|
+
};
|
|
674
|
+
}, [picker, onChange]);
|
|
565
675
|
const surface = state.kind === "ready" ? state.surface : null;
|
|
566
|
-
const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values)[i.key] ?? ""));
|
|
676
|
+
const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values, surface)[i.key] ?? ""));
|
|
567
677
|
/** 写完(保存/重置)之后重新读一份整面,读到了才敢提示「已完成」。 */
|
|
568
678
|
const writeThenReload = react.useCallback(async (done) => {
|
|
569
679
|
setBusy(true);
|
|
@@ -601,7 +711,7 @@ window.__ModuleLoader__.load({
|
|
|
601
711
|
if (r.ok) await writeThenReload("已重置为默认(原配置已另存一份 .bak)");
|
|
602
712
|
else setError(r.message);
|
|
603
713
|
}, [props.getCall, writeThenReload]);
|
|
604
|
-
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "
|
|
714
|
+
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(配置文件刚按默认值生成)") : null) : null);
|
|
605
715
|
if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中…"), react.createElement(VersionLine, null));
|
|
606
716
|
if (state.kind === "failed") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), react.createElement("div", { style: S.error }, state.message), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
607
717
|
style: S.btn,
|
|
@@ -619,7 +729,8 @@ window.__ModuleLoader__.load({
|
|
|
619
729
|
item,
|
|
620
730
|
value: draft[item.key] ?? "",
|
|
621
731
|
disabled: busy,
|
|
622
|
-
onChange
|
|
732
|
+
onChange,
|
|
733
|
+
onBrowse
|
|
623
734
|
});
|
|
624
735
|
return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.rows }, common.map(renderRow)), react.createElement("details", { style: S.adv }, react.createElement("summary", { style: S.advSummary }, ADVANCED_GROUP_TITLE), react.createElement("div", { style: S.muted }, ADVANCED_GROUP_NOTE), advanced.map(renderRow)), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
625
736
|
style: dirty ? S.btnPrimary : S.btn,
|
|
@@ -636,17 +747,21 @@ window.__ModuleLoader__.load({
|
|
|
636
747
|
type: "button",
|
|
637
748
|
disabled: busy,
|
|
638
749
|
onClick: () => void load()
|
|
639
|
-
}, "重新读取")), notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement("div", { style: S.muted }, `${PLUGIN}
|
|
750
|
+
}, "重新读取")), picking ? react.createElement("div", { style: S.muted }, "已唤起系统文件夹对话框:选中后自动填上,取消则不动。") : null, notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement("div", { style: S.muted }, `${PLUGIN} 本页只配置;记笔记、搜笔记在对话里说。`), react.createElement(VersionLine, null));
|
|
640
751
|
}
|
|
641
752
|
function apply(ctx) {
|
|
642
753
|
const getCall = () => ctx.connection?.rpc?.call ?? null;
|
|
754
|
+
const getPicker = () => typeof ctx.get === "function" ? resolveDirectoryPicker((name) => ctx.get?.(name)) : null;
|
|
643
755
|
ctx.slots.inject("ilife.config-tab", () => ctx.slots.register({
|
|
644
756
|
name: "ilife.config-tab",
|
|
645
757
|
id: "dsh-memo-ilife",
|
|
646
758
|
order: 70,
|
|
647
759
|
label: () => SLOT_TITLE,
|
|
648
760
|
channel: RPC_CHANNEL
|
|
649
|
-
}, () => react.createElement(MemoConfig, {
|
|
761
|
+
}, () => react.createElement(MemoConfig, {
|
|
762
|
+
getCall,
|
|
763
|
+
getPicker
|
|
764
|
+
})));
|
|
650
765
|
const ensureWorkTab = () => {
|
|
651
766
|
try {
|
|
652
767
|
const get = ctx.get;
|
|
@@ -681,12 +796,18 @@ window.__ModuleLoader__.load({
|
|
|
681
796
|
}
|
|
682
797
|
}
|
|
683
798
|
//#endregion
|
|
799
|
+
exports.Row = Row;
|
|
684
800
|
exports.apply = apply;
|
|
801
|
+
exports.createBrowseHandler = createBrowseHandler;
|
|
685
802
|
exports.fetchConfigSurface = fetchConfigSurface;
|
|
686
803
|
exports.fromDraft = fromDraft;
|
|
687
804
|
exports.humanizeConfigFailure = humanizeConfigFailure;
|
|
688
805
|
exports.inject = inject;
|
|
806
|
+
exports.isDirectoryPicker = isDirectoryPicker;
|
|
807
|
+
exports.pickDirectory = pickDirectory;
|
|
808
|
+
exports.readPickAnswer = readPickAnswer;
|
|
689
809
|
exports.resetConfigSurface = resetConfigSurface;
|
|
810
|
+
exports.resolveDirectoryPicker = resolveDirectoryPicker;
|
|
690
811
|
exports.saveConfigSurface = saveConfigSurface;
|
|
691
812
|
exports.toDraft = toDraft;
|
|
692
813
|
return module.exports;
|