@morlay/dsh-client-ui-primitives 0.0.2-alpha.8 → 0.0.2-alpha.9
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 +250 -14
- package/dist/client.cjs +3548 -302
- package/dist/client.d.cts +1040 -15
- package/dist/client.d.mts +1040 -16
- package/dist/rolldown-runtime-EAsCxrDo.mjs +14 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +26 -10
- package/src/client/index.ts +40 -2
- package/src/client/schema-form/Editor.tsx +777 -0
- package/src/client/schema-form/SchemaForm.tsx +77 -0
- package/src/client/schema-form/controller.ts +457 -0
- package/src/client/schema-form/draft.ts +487 -0
- package/src/client/schema-form/fields.tsx +100 -0
- package/src/client/schema-form/hints.ts +286 -0
- package/src/client/schema-form/index.ts +188 -0
- package/src/client/schema-form/labels.ts +30 -0
- package/src/client/schema-form/lines.ts +334 -0
- package/src/client/schema-form/locales.ts +128 -0
- package/src/client/schema-form/rows.ts +153 -0
- package/src/client/schema-form/schema-node.ts +812 -0
- package/src/client/schema-form/slot-contract.ts +156 -0
- package/src/client/schema-form/styles.ts +310 -0
- package/src/client/schema-form/testing/fake-describe.ts +45 -0
- package/src/client/schema-form/testing/fake-scope.ts +97 -0
- package/src/client/schema-form/value.tsx +146 -0
- package/src/client/settings-form/SettingsForm.styles.ts +2 -1
- package/src/client/settings-form/fields.styles.ts +3 -2
- package/src/client/settings-form/fields.tsx +2 -1
- package/src/client/styling/focus.ts +9 -0
- package/src/client/theme.generated.ts +64 -7
- package/cordis.patch.yml +0 -3
package/dist/client.cjs
CHANGED
|
@@ -16,10 +16,1813 @@ window.__ModuleLoader__.load({
|
|
|
16
16
|
return target;
|
|
17
17
|
};
|
|
18
18
|
//#endregion
|
|
19
|
-
let react = require("react");
|
|
20
19
|
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
21
|
-
let
|
|
20
|
+
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
21
|
+
let react = require("react");
|
|
22
22
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
23
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
24
|
+
/** 空的元数据:动态子节点(无声明 schema 的 dict 键)与空字段树用它。 */
|
|
25
|
+
function emptyMeta() {
|
|
26
|
+
return {
|
|
27
|
+
description: void 0,
|
|
28
|
+
comment: void 0,
|
|
29
|
+
link: void 0,
|
|
30
|
+
role: void 0,
|
|
31
|
+
secret: false,
|
|
32
|
+
required: false,
|
|
33
|
+
disabled: false,
|
|
34
|
+
collapse: false,
|
|
35
|
+
badges: [],
|
|
36
|
+
pattern: void 0,
|
|
37
|
+
min: void 0,
|
|
38
|
+
max: void 0,
|
|
39
|
+
step: void 0,
|
|
40
|
+
hasDefault: false,
|
|
41
|
+
defaultValue: void 0,
|
|
42
|
+
extra: void 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** `badges` 只留文本:配色与文案由字典决定。 */
|
|
46
|
+
function badgesOf(meta) {
|
|
47
|
+
return (meta.badges ?? []).map((badge) => badge.text);
|
|
48
|
+
}
|
|
49
|
+
/** 归一化一个节点的 meta。 */
|
|
50
|
+
function metaOf(schema) {
|
|
51
|
+
const meta = schema.meta ?? {};
|
|
52
|
+
return {
|
|
53
|
+
description: meta.description,
|
|
54
|
+
comment: meta.comment,
|
|
55
|
+
link: meta.link,
|
|
56
|
+
role: meta.role,
|
|
57
|
+
secret: meta.role === "secret",
|
|
58
|
+
required: meta.required === true,
|
|
59
|
+
disabled: meta.disabled === true,
|
|
60
|
+
collapse: meta.collapse === true,
|
|
61
|
+
badges: badgesOf(meta),
|
|
62
|
+
pattern: typeof meta.pattern === "string" ? meta.pattern : meta.pattern?.source ?? void 0,
|
|
63
|
+
min: meta.min,
|
|
64
|
+
max: meta.max,
|
|
65
|
+
step: meta.step,
|
|
66
|
+
hasDefault: meta.default !== void 0,
|
|
67
|
+
defaultValue: meta.default,
|
|
68
|
+
extra: meta.extra
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** 外层 meta 与内层 meta 合并:外层的 undefined 不覆盖内层(transform 的 inner 带着自己的角色)。 */
|
|
72
|
+
function mergeMeta(inner, outer) {
|
|
73
|
+
const merged = { ...inner };
|
|
74
|
+
for (const [key, value] of Object.entries(outer)) if (value !== void 0) merged[key] = value;
|
|
75
|
+
return merged;
|
|
76
|
+
}
|
|
77
|
+
/** 节点自己的路径末段:动态段保持占位,根节点为空串。 */
|
|
78
|
+
function keyOf(path) {
|
|
79
|
+
return path[path.length - 1] ?? "";
|
|
80
|
+
}
|
|
81
|
+
/** 一个纯字符串叶子(dict 没有声明键 schema 时用它)。 */
|
|
82
|
+
function stringLeaf(path, meta) {
|
|
83
|
+
return {
|
|
84
|
+
key: keyOf(path),
|
|
85
|
+
path,
|
|
86
|
+
label: void 0,
|
|
87
|
+
meta,
|
|
88
|
+
readOnly: null,
|
|
89
|
+
type: "string"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** 把 schema 的类型名收口成渲染层认识的那几种。 */
|
|
93
|
+
function leafTypeOf(type) {
|
|
94
|
+
switch (type) {
|
|
95
|
+
case "string": return "string";
|
|
96
|
+
case "number": return "number";
|
|
97
|
+
case "boolean": return "boolean";
|
|
98
|
+
case "function": return "function";
|
|
99
|
+
case "never": return "never";
|
|
100
|
+
default: return "any";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 投影一个 schema 节点。
|
|
105
|
+
* @param schema - rehydrate 之后的 volatile 表单 schema(或它的子节点)。
|
|
106
|
+
* @param path - 该节点相对段根的路径;动态子节点由调用方给占位段。
|
|
107
|
+
* @returns 渲染层要的字段树。
|
|
108
|
+
*/
|
|
109
|
+
function projectNode(schema, path = []) {
|
|
110
|
+
return project(schema, path, []);
|
|
111
|
+
}
|
|
112
|
+
function project(schema, path, ancestors) {
|
|
113
|
+
const base = {
|
|
114
|
+
key: keyOf(path),
|
|
115
|
+
path,
|
|
116
|
+
label: void 0,
|
|
117
|
+
meta: metaOf(schema),
|
|
118
|
+
readOnly: null
|
|
119
|
+
};
|
|
120
|
+
if (ancestors.includes(schema)) return {
|
|
121
|
+
...base,
|
|
122
|
+
type: "recursive",
|
|
123
|
+
origin: schema.type
|
|
124
|
+
};
|
|
125
|
+
const chain = [...ancestors, schema];
|
|
126
|
+
switch (schema.type) {
|
|
127
|
+
case "object": {
|
|
128
|
+
const fields = Object.entries(schema.dict ?? {}).filter(([, child]) => child.meta?.hidden !== true).map(([key, child]) => project(child, [...path, key], chain));
|
|
129
|
+
return {
|
|
130
|
+
...base,
|
|
131
|
+
type: "object",
|
|
132
|
+
fields
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
case "dict": {
|
|
136
|
+
const segment = [...path, "*"];
|
|
137
|
+
const keyNode = schema.sKey === void 0 ? stringLeaf(segment, emptyMeta()) : project(schema.sKey, segment, chain);
|
|
138
|
+
return {
|
|
139
|
+
...base,
|
|
140
|
+
type: "dict",
|
|
141
|
+
keyNode,
|
|
142
|
+
valueNode: project(schema.inner ?? schema, segment, chain)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
case "array": {
|
|
146
|
+
const item = project(schema.inner ?? schema, [...path, "*"], chain);
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
type: "array",
|
|
150
|
+
item,
|
|
151
|
+
mergeKey: mergeKeyOf(schema, item)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
case "tuple": {
|
|
155
|
+
const items = (schema.list ?? []).map((child, index) => project(child, [...path, String(index)], chain));
|
|
156
|
+
return {
|
|
157
|
+
...base,
|
|
158
|
+
type: "tuple",
|
|
159
|
+
items
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
case "union": {
|
|
163
|
+
const members = schema.list ?? [];
|
|
164
|
+
const variants = members.map((child) => project(child, path, chain));
|
|
165
|
+
const literals = members.every((child) => child.type === "const") ? members.map((child) => child.value) : void 0;
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
type: "union",
|
|
169
|
+
choices: literals,
|
|
170
|
+
variants,
|
|
171
|
+
tagKey: tagKeyOf(schema, variants)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
case "intersect": return {
|
|
175
|
+
...base,
|
|
176
|
+
type: "intersect",
|
|
177
|
+
members: (schema.list ?? []).map((child) => project(child, path, chain))
|
|
178
|
+
};
|
|
179
|
+
case "bitset": {
|
|
180
|
+
const bits = Object.entries(schema.bits ?? {}).map(([name, bit]) => ({
|
|
181
|
+
name,
|
|
182
|
+
bit
|
|
183
|
+
}));
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
type: "bitset",
|
|
187
|
+
bits
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
case "const": return {
|
|
191
|
+
...base,
|
|
192
|
+
type: "const",
|
|
193
|
+
value: schema.value
|
|
194
|
+
};
|
|
195
|
+
case "transform": {
|
|
196
|
+
const projected = project(schema.inner ?? schema, path, chain);
|
|
197
|
+
return {
|
|
198
|
+
...projected,
|
|
199
|
+
meta: mergeMeta(projected.meta, base.meta),
|
|
200
|
+
readOnly: "transform"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
case "lazy": {
|
|
204
|
+
const inner = schema.inner;
|
|
205
|
+
return inner === void 0 ? {
|
|
206
|
+
...base,
|
|
207
|
+
type: "any"
|
|
208
|
+
} : {
|
|
209
|
+
...base,
|
|
210
|
+
type: "lazy",
|
|
211
|
+
inner: project(inner, path, chain)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
case "is": return {
|
|
215
|
+
...base,
|
|
216
|
+
type: "any",
|
|
217
|
+
readOnly: "constructor"
|
|
218
|
+
};
|
|
219
|
+
default: return {
|
|
220
|
+
...base,
|
|
221
|
+
type: leafTypeOf(schema.type),
|
|
222
|
+
readOnly: schema.type === "function" ? "function" : null
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** 展开 `lazy` 包装:投影里 lazy 只是一层,值仍在同一路径上。 */
|
|
227
|
+
function unwrapLazy(node) {
|
|
228
|
+
let current = node;
|
|
229
|
+
while (current?.type === "lazy") current = current.inner;
|
|
230
|
+
return current;
|
|
231
|
+
}
|
|
232
|
+
/** 某一层可见的字段:object 直接给;union / intersect 把各变体的对象字段并起来。 */
|
|
233
|
+
function fieldsOf(node) {
|
|
234
|
+
const current = unwrapLazy(node);
|
|
235
|
+
switch (current?.type) {
|
|
236
|
+
case "object": return current.fields;
|
|
237
|
+
case "union": return current.variants.flatMap((variant) => fieldsOf(variant));
|
|
238
|
+
case "intersect": return current.members.flatMap((member) => fieldsOf(member));
|
|
239
|
+
default: return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 按具体路径找回投影节点(动态段匹配真实键或索引)。
|
|
244
|
+
* @param root - 段根的字段树。
|
|
245
|
+
* @param path - 具体路径。
|
|
246
|
+
* @returns 命中的节点,或 `undefined`。
|
|
247
|
+
*/
|
|
248
|
+
function seekNode(root, path) {
|
|
249
|
+
let current = root;
|
|
250
|
+
for (const segment of path) {
|
|
251
|
+
const node = unwrapLazy(current);
|
|
252
|
+
if (node === void 0) return void 0;
|
|
253
|
+
switch (node.type) {
|
|
254
|
+
case "object":
|
|
255
|
+
case "union":
|
|
256
|
+
case "intersect":
|
|
257
|
+
current = fieldsOf(node).find((field) => field.key === segment);
|
|
258
|
+
break;
|
|
259
|
+
case "dict":
|
|
260
|
+
current = node.valueNode;
|
|
261
|
+
break;
|
|
262
|
+
case "array":
|
|
263
|
+
current = node.item;
|
|
264
|
+
break;
|
|
265
|
+
case "tuple":
|
|
266
|
+
current = node.items[Number(segment)];
|
|
267
|
+
break;
|
|
268
|
+
default: return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return unwrapLazy(current);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 按当前值把字段树展开成渲染顺序的扁平列表(前序):容器在前、子项紧随其后。
|
|
275
|
+
*
|
|
276
|
+
* 出现的**只有值里有的东西,加上必填的**:对象的必填字段始终占行(值是空的也要让用户看见"必须填"),非必填的
|
|
277
|
+
* 按值出现——值里没有就不占行,它在这一层的「添加属性」候选里,选中才加。数组按索引、字典按键展开;字面量
|
|
278
|
+
* union 是叶子控件,对象 union 只展开与当前值匹配的那个变体,intersect 的各成员在同一层展开。
|
|
279
|
+
* @param root - 段根的字段树。
|
|
280
|
+
* @param value - 生效值(或草稿后的值)。
|
|
281
|
+
* @returns 渲染顺序的字段列表。
|
|
282
|
+
*/
|
|
283
|
+
function walkFields(root, value) {
|
|
284
|
+
const out = [];
|
|
285
|
+
visit(root, root.path, value, out, true, void 0, /* @__PURE__ */ new Set(), void 0);
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
function visit(node, path, value, out, emit, label, emitted, member) {
|
|
289
|
+
const current = unwrapLazy(node);
|
|
290
|
+
if (current === void 0) return;
|
|
291
|
+
const positioned = {
|
|
292
|
+
...current,
|
|
293
|
+
path,
|
|
294
|
+
...member === void 0 ? {} : { key: member.key },
|
|
295
|
+
...label === void 0 ? {} : { label }
|
|
296
|
+
};
|
|
297
|
+
const key = JSON.stringify(path);
|
|
298
|
+
if (emit && !emitted.has(key)) {
|
|
299
|
+
emitted.add(key);
|
|
300
|
+
out.push({
|
|
301
|
+
path,
|
|
302
|
+
node: positioned,
|
|
303
|
+
...member === void 0 ? {} : { member }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
switch (positioned.type) {
|
|
307
|
+
case "object":
|
|
308
|
+
for (const field of positioned.fields) {
|
|
309
|
+
const child = memberOf(value, field.key);
|
|
310
|
+
if (child === void 0 && field.meta.required !== true) continue;
|
|
311
|
+
visit(field, [...path, field.key], child, out, true, void 0, emitted, void 0);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
case "dict":
|
|
315
|
+
for (const [key, child] of entriesOf(value)) visit(positioned.valueNode, [...path, key], child, out, true, void 0, emitted, {
|
|
316
|
+
parent: path,
|
|
317
|
+
key,
|
|
318
|
+
index: void 0
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
case "array":
|
|
322
|
+
for (const [key, child] of entriesOf(value)) {
|
|
323
|
+
const label = positioned.mergeKey === void 0 ? void 0 : memberLabel(child, positioned.mergeKey);
|
|
324
|
+
visit(positioned.item, [...path, key], child, out, true, label, emitted, {
|
|
325
|
+
parent: path,
|
|
326
|
+
key,
|
|
327
|
+
index: Number(key)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
case "tuple":
|
|
332
|
+
positioned.items.forEach((item, index) => {
|
|
333
|
+
visit(item, [...path, String(index)], memberOf(value, index), out, true, void 0, emitted, void 0);
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
case "union": {
|
|
337
|
+
if (positioned.choices !== void 0) return;
|
|
338
|
+
const variant = pickVariant(positioned, value);
|
|
339
|
+
if (variant !== void 0) visit(variant, path, value, out, false, void 0, emitted, member);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
case "intersect":
|
|
343
|
+
for (const part of positioned.members) visit(part, path, value, out, false, void 0, emitted, member);
|
|
344
|
+
return;
|
|
345
|
+
default: return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/** 数组的项身份:显式声明优先,否则 item 上的 `id` 字段。 */
|
|
349
|
+
function mergeKeyOf(schema, item) {
|
|
350
|
+
const declared = declaredExtra(schema, "mergeKey");
|
|
351
|
+
if (declared !== void 0) return declared;
|
|
352
|
+
const current = unwrapLazy(item);
|
|
353
|
+
return current?.type === "object" && current.fields.some((field) => field.key === "id") ? "id" : void 0;
|
|
354
|
+
}
|
|
355
|
+
/** union 的判别标签:显式声明优先,否则所有对象成员共有的唯一常量字段。 */
|
|
356
|
+
function tagKeyOf(schema, variants) {
|
|
357
|
+
const declared = declaredExtra(schema, "tag");
|
|
358
|
+
if (declared !== void 0) return declared;
|
|
359
|
+
const members = variants.map((variant) => unwrapLazy(variant));
|
|
360
|
+
if (members.length === 0 || members.some((member) => member?.type !== "object")) return void 0;
|
|
361
|
+
const objects = members;
|
|
362
|
+
const candidates = (objects[0]?.fields ?? []).map((field) => unwrapLazy(field)).filter((field) => field?.type === "const").map((field) => field.key).filter((key) => objects.every((object) => unwrapLazy(object.fields.find((field) => field.key === key))?.type === "const"));
|
|
363
|
+
return candidates.length === 1 ? candidates[0] : void 0;
|
|
364
|
+
}
|
|
365
|
+
/** `role(text, { … })` 里声明的字符串约定(项身份 `mergeKey`、判别标签 `tag`)。 */
|
|
366
|
+
function declaredExtra(schema, name) {
|
|
367
|
+
const extra = schema.meta?.extra;
|
|
368
|
+
if (typeof extra !== "object" || extra === null) return void 0;
|
|
369
|
+
const value = Reflect.get(extra, name);
|
|
370
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
371
|
+
}
|
|
372
|
+
/** 一项的项身份值(渲染与冲突检查共用的读法)。 */
|
|
373
|
+
function memberLabel(value, mergeKey) {
|
|
374
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
375
|
+
const member = Reflect.get(value, mergeKey);
|
|
376
|
+
if (typeof member === "string") return member;
|
|
377
|
+
if (typeof member === "number" && Number.isFinite(member)) return String(member);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 项身份冲突:同一层里两个成员同名,或某个成员没有名字。
|
|
381
|
+
*
|
|
382
|
+
* 名字是成员的「身份」,写盘仍按索引——所以这只是保存前的本地校验:重名的数组在页面上是非法草稿,
|
|
383
|
+
* 而不是悄悄写到 host 上。
|
|
384
|
+
* @param root - 段根的字段树。
|
|
385
|
+
* @param value - 草稿后的段值。
|
|
386
|
+
* @returns 第一处冲突,或 `undefined`。
|
|
387
|
+
*/
|
|
388
|
+
function mergeKeyConflict(root, value) {
|
|
389
|
+
return scanConflict(root, root.path, value);
|
|
390
|
+
}
|
|
391
|
+
function scanConflict(node, path, value) {
|
|
392
|
+
const current = unwrapLazy(node);
|
|
393
|
+
if (current === void 0) return void 0;
|
|
394
|
+
switch (current.type) {
|
|
395
|
+
case "object":
|
|
396
|
+
for (const field of current.fields) {
|
|
397
|
+
const conflict = scanConflict(field, [...path, field.key], memberOf(value, field.key));
|
|
398
|
+
if (conflict !== void 0) return conflict;
|
|
399
|
+
}
|
|
400
|
+
return;
|
|
401
|
+
case "dict":
|
|
402
|
+
for (const [key, child] of entriesOf(value)) {
|
|
403
|
+
const conflict = scanConflict(current.valueNode, [...path, key], child);
|
|
404
|
+
if (conflict !== void 0) return conflict;
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
case "array": {
|
|
408
|
+
const names = /* @__PURE__ */ new Set();
|
|
409
|
+
for (const [key, child] of entriesOf(value)) {
|
|
410
|
+
const itemPath = [...path, key];
|
|
411
|
+
if (current.mergeKey !== void 0) {
|
|
412
|
+
const label = memberLabel(child, current.mergeKey);
|
|
413
|
+
if (label === void 0 || label.trim() === "") return {
|
|
414
|
+
kind: "missing",
|
|
415
|
+
path: itemPath,
|
|
416
|
+
mergeKey: current.mergeKey,
|
|
417
|
+
value: ""
|
|
418
|
+
};
|
|
419
|
+
if (names.has(label)) return {
|
|
420
|
+
kind: "duplicate",
|
|
421
|
+
path: itemPath,
|
|
422
|
+
mergeKey: current.mergeKey,
|
|
423
|
+
value: label
|
|
424
|
+
};
|
|
425
|
+
names.add(label);
|
|
426
|
+
}
|
|
427
|
+
const conflict = scanConflict(current.item, itemPath, child);
|
|
428
|
+
if (conflict !== void 0) return conflict;
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
case "tuple":
|
|
433
|
+
for (const [index, item] of current.items.entries()) {
|
|
434
|
+
const conflict = scanConflict(item, [...path, String(index)], memberOf(value, index));
|
|
435
|
+
if (conflict !== void 0) return conflict;
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
case "union": {
|
|
439
|
+
if (current.choices !== void 0) return void 0;
|
|
440
|
+
const variant = pickVariant(current, value);
|
|
441
|
+
return variant === void 0 ? void 0 : scanConflict(variant, path, value);
|
|
442
|
+
}
|
|
443
|
+
case "intersect":
|
|
444
|
+
for (const member of current.members) {
|
|
445
|
+
const conflict = scanConflict(member, path, value);
|
|
446
|
+
if (conflict !== void 0) return conflict;
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
default: return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/** 一个容器值里的成员(对象键或数组索引)。 */
|
|
453
|
+
function entriesOf(value) {
|
|
454
|
+
if (Array.isArray(value)) return value.map((item, index) => [String(index), item]);
|
|
455
|
+
if (typeof value === "object" && value !== null) return Object.entries(value);
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
/** 读一个成员的值。 */
|
|
459
|
+
function memberOf(value, key) {
|
|
460
|
+
if (Array.isArray(value)) return value[Number(key)];
|
|
461
|
+
if (typeof value === "object" && value !== null) return value[String(key)];
|
|
462
|
+
}
|
|
463
|
+
/** 把节点收成 union:渲染层判形状与选支都从这里进。 */
|
|
464
|
+
function unionOf(node) {
|
|
465
|
+
const current = unwrapLazy(node);
|
|
466
|
+
return current?.type === "union" ? current : void 0;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* 当前值落在哪一支:与 {@link walkFields} 的展开同一判据(标签值优先,其次值的形状,最后第一支)。
|
|
470
|
+
* @param node - 字段节点(不是 union 就原样返回)。
|
|
471
|
+
* @param value - 这一层的值。
|
|
472
|
+
* @returns 选中变体的节点。
|
|
473
|
+
*/
|
|
474
|
+
function variantOf(node, value) {
|
|
475
|
+
const union = unionOf(node);
|
|
476
|
+
if (union === void 0) return unwrapLazy(node) ?? node;
|
|
477
|
+
return pickVariant(union, value) ?? union;
|
|
478
|
+
}
|
|
479
|
+
/** 变体里那个判别标签字段的常量值(不是 `const` 就没有)。 */
|
|
480
|
+
function tagValueOf(variant, tagKey) {
|
|
481
|
+
const current = unwrapLazy(variant);
|
|
482
|
+
if (current?.type !== "object" && current?.type !== "intersect") return {
|
|
483
|
+
found: false,
|
|
484
|
+
value: void 0
|
|
485
|
+
};
|
|
486
|
+
for (const field of fieldsOf(current)) {
|
|
487
|
+
if (field.key !== tagKey) continue;
|
|
488
|
+
const candidate = unwrapLazy(field);
|
|
489
|
+
if (candidate?.type === "const") return {
|
|
490
|
+
found: true,
|
|
491
|
+
value: candidate.value
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
found: false,
|
|
496
|
+
value: void 0
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** 一个变体在页面上的名字:能看出"这一支是什么",而不是内部类型名。 */
|
|
500
|
+
function variantLabel(variant, tag) {
|
|
501
|
+
if (tag.found) return JSON.stringify(tag.value);
|
|
502
|
+
switch (unwrapLazy(variant)?.type) {
|
|
503
|
+
case "string": return "\"\"";
|
|
504
|
+
case "number": return "0";
|
|
505
|
+
case "boolean": return "true";
|
|
506
|
+
case "array":
|
|
507
|
+
case "tuple": return "[ ]";
|
|
508
|
+
case "object":
|
|
509
|
+
case "dict":
|
|
510
|
+
case "intersect": return "{ }";
|
|
511
|
+
default: return unwrapLazy(variant)?.type ?? "any";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* 变体切换的选项。
|
|
516
|
+
*
|
|
517
|
+
* 带判别标签的 union 写**标签值**(`session-rdb` 的 `type: sqlite` ↔ `type: postgres`);没有标签的写目标变体的
|
|
518
|
+
* 空值(`access` 在「一段文本」与「一组文本」之间切)。
|
|
519
|
+
* @param union - 投影出来的 union 节点。
|
|
520
|
+
* @returns 每个变体一项,顺序即声明顺序。
|
|
521
|
+
*/
|
|
522
|
+
function variantChoices(union) {
|
|
523
|
+
return union.variants.map((variant) => {
|
|
524
|
+
const tag = union.tagKey === void 0 ? {
|
|
525
|
+
found: false,
|
|
526
|
+
value: void 0
|
|
527
|
+
} : tagValueOf(variant, union.tagKey);
|
|
528
|
+
return {
|
|
529
|
+
label: variantLabel(variant, tag),
|
|
530
|
+
value: tag.found ? tag.value : emptyValueOf(variant)
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/** 按路径读一个值:对象键与数组下标都认(值的读法只有这一处)。 */
|
|
535
|
+
function readPath(root, path) {
|
|
536
|
+
return path.reduce((node, segment) => {
|
|
537
|
+
if (Array.isArray(node)) return node[Number(segment)];
|
|
538
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
539
|
+
return node[segment];
|
|
540
|
+
}, root);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* 这个"默认值"其实不是值——它不该用来当新加进来的那一项的初值。
|
|
544
|
+
*
|
|
545
|
+
* 两种:`null`("没有值"的判断位,例如 `defaultModel` 用 `.default(null)` 表达"没配就没有这个键"),以及
|
|
546
|
+
* schemastery 给容器类型自动塞的空壳(`{}`)。两种都该让容器把字段摆出来。
|
|
547
|
+
* @param node - 字段节点。
|
|
548
|
+
* @returns 是不是空壳。
|
|
549
|
+
*/
|
|
550
|
+
function isBlankDefault(node) {
|
|
551
|
+
const value = node.meta.defaultValue;
|
|
552
|
+
if (value === null) return true;
|
|
553
|
+
if (node.type !== "object" && node.type !== "intersect") return false;
|
|
554
|
+
return typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* 一个新子节点的初值:声明的默认值优先;对象把字段摆出来(见下);字典/数组给空的容器;其余标量给 **`null`**。
|
|
558
|
+
*
|
|
559
|
+
* `null` 在这里是"还没有值"的判断位(`null` / `undefined` 都算),页面据此把它画成一个等着输入的位子——
|
|
560
|
+
* 而不是 `""`、`0` 这种看着像已经填过的值。host 侧也认这个语义:非必填字段拿到 `null` 回退到默认值,
|
|
561
|
+
* 必填字段拿到 `null` 就是"必须填"。
|
|
562
|
+
* @param node - 子节点的字段树。
|
|
563
|
+
* @returns 初值。
|
|
564
|
+
*/
|
|
565
|
+
function emptyValueOf(node) {
|
|
566
|
+
if (node.meta.hasDefault && !isBlankDefault(node)) return structuredClone(node.meta.defaultValue);
|
|
567
|
+
switch (node.type) {
|
|
568
|
+
case "array": return [];
|
|
569
|
+
case "tuple": return node.items.map((item) => emptyValueOf(item));
|
|
570
|
+
case "dict": return {};
|
|
571
|
+
case "object":
|
|
572
|
+
case "intersect": return Object.fromEntries(fieldsOf(node).map((field) => [field.key, emptyValueOf(field)]));
|
|
573
|
+
case "const": return node.value;
|
|
574
|
+
case "bitset": return 0;
|
|
575
|
+
case "lazy": return emptyValueOf(node.inner);
|
|
576
|
+
default: return null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/** 选中与当前值匹配的 union 变体:对象按判别标签的值判,叶子按值类型判。 */
|
|
580
|
+
function pickVariant(node, value) {
|
|
581
|
+
if (node.tagKey !== void 0 && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
582
|
+
const tagValue = Reflect.get(value, node.tagKey);
|
|
583
|
+
for (const variant of node.variants) {
|
|
584
|
+
const current = unwrapLazy(variant);
|
|
585
|
+
if (current?.type !== "object") continue;
|
|
586
|
+
const field = unwrapLazy(current.fields.find((candidate) => candidate.key === node.tagKey));
|
|
587
|
+
if (field?.type === "const" && field.value === tagValue) return variant;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
for (const variant of node.variants) {
|
|
591
|
+
const current = unwrapLazy(variant);
|
|
592
|
+
if (current?.type === "string" && typeof value === "string") return variant;
|
|
593
|
+
if (current?.type === "number" && typeof value === "number") return variant;
|
|
594
|
+
if (current?.type === "boolean" && typeof value === "boolean") return variant;
|
|
595
|
+
if (current?.type === "array" && Array.isArray(value)) return variant;
|
|
596
|
+
}
|
|
597
|
+
return node.variants[0];
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/client/schema-form/draft.ts
|
|
601
|
+
/**
|
|
602
|
+
* 把校验器抛出的错误收成一条失败。
|
|
603
|
+
*
|
|
604
|
+
* schemastery 的 `ValidationError` 把路径留在 `options.path` 上(消息里的 `$…` 前缀是给命令行看的),所以这里
|
|
605
|
+
* 取路径、去前缀——行内报错据此知道挂在哪一行。
|
|
606
|
+
* @param error - 校验器抛出的错误。
|
|
607
|
+
* @returns 消息与路径。
|
|
608
|
+
*/
|
|
609
|
+
function failureOf(error) {
|
|
610
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
611
|
+
const path = (error.options?.path ?? []).filter((segment) => typeof segment === "string" || typeof segment === "number").map(String);
|
|
612
|
+
const message = raw.replace(/^\$\S*\s/, "").trim();
|
|
613
|
+
return {
|
|
614
|
+
message: message === "" ? raw : message,
|
|
615
|
+
path
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 一页配置的草稿。一个命名空间一个实例:它订阅共享配置表单,投影给渲染层读。
|
|
620
|
+
*/
|
|
621
|
+
var SchemaDraftModel = class {
|
|
622
|
+
#scope;
|
|
623
|
+
#root;
|
|
624
|
+
#validate;
|
|
625
|
+
#staged = /* @__PURE__ */ new Map();
|
|
626
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
627
|
+
#unsubscribe;
|
|
628
|
+
#baseline;
|
|
629
|
+
#saving = false;
|
|
630
|
+
#failed = false;
|
|
631
|
+
#violation;
|
|
632
|
+
/**
|
|
633
|
+
* @param options - 本页的共享配置表单、字段树与整段校验。
|
|
634
|
+
*/
|
|
635
|
+
constructor(options) {
|
|
636
|
+
this.#scope = options.scope;
|
|
637
|
+
this.#root = options.root;
|
|
638
|
+
this.#validate = options.validate;
|
|
639
|
+
this.#unsubscribe = this.#scope.subscribe(() => {
|
|
640
|
+
this.#publish();
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* 换一份字段树。
|
|
645
|
+
*
|
|
646
|
+
* 增删项要按它取节点默认值,而 schema 可能随 describe 变(行的 Config 改了)——所以控制器每次投影都同步一次,
|
|
647
|
+
* 否则加进来的项会取不到默认值。
|
|
648
|
+
* @param root - 段根的字段树。
|
|
649
|
+
*/
|
|
650
|
+
setRoot(root) {
|
|
651
|
+
this.#root = root;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* 观察草稿与读数的变化。
|
|
655
|
+
* @param listener - 每次投影都调一次的回调。
|
|
656
|
+
* @returns 取消订阅。
|
|
657
|
+
*/
|
|
658
|
+
subscribe(listener) {
|
|
659
|
+
this.#listeners.add(listener);
|
|
660
|
+
return () => {
|
|
661
|
+
this.#listeners.delete(listener);
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* 投影一份读数给渲染层。
|
|
666
|
+
* @param project - 用模型的当前读数造渲染要的状态。
|
|
667
|
+
* @returns 渲染层按选择器读的 store。
|
|
668
|
+
*/
|
|
669
|
+
bind(project) {
|
|
670
|
+
const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
|
|
671
|
+
this.subscribe(() => {
|
|
672
|
+
store.set(project());
|
|
673
|
+
});
|
|
674
|
+
return store;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* 表单级状态(与上游 `SettingsForm` 的壳同形)。
|
|
678
|
+
* @returns 可用性、可写性、脏、非法、保存中、上次保存失败。
|
|
679
|
+
*/
|
|
680
|
+
shell() {
|
|
681
|
+
const { writes, blocked } = this.#plan();
|
|
682
|
+
return {
|
|
683
|
+
available: this.#scope.getSnapshot().status === "ready",
|
|
684
|
+
writable: this.#scope.getSnapshot().writable,
|
|
685
|
+
dirty: writes.some((item) => item.op !== void 0) || blocked,
|
|
686
|
+
invalid: blocked || this.#violation !== void 0,
|
|
687
|
+
saving: this.#saving,
|
|
688
|
+
failed: this.#failed
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* 保存前的整段校验(保存被 schema 挡下时非空)。
|
|
693
|
+
* @returns 失败消息与它的位置,或 `undefined`。
|
|
694
|
+
*/
|
|
695
|
+
violation() {
|
|
696
|
+
return this.#violation;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* 读一个字段的草稿状态。
|
|
700
|
+
* @param path - 具体路径(动态段已按真实键或索引替换)。
|
|
701
|
+
* @param preview - 本轮已经算好的读数(`preview()` 的结果)。
|
|
702
|
+
*
|
|
703
|
+
* **容器的值要跟着别处的草稿走**:判别式 union 的选支就是容器的值,若这里读生效层,`type` 的切换控件会显示旧
|
|
704
|
+
* 的那一支(字段树却已经换了)。所以有 `preview` 就用它——控制器一次投影里只算一份。
|
|
705
|
+
* @returns 显示值、文本草稿、非法消息、是否覆盖。
|
|
706
|
+
*/
|
|
707
|
+
field(path, preview) {
|
|
708
|
+
const snapshot = this.#scope.getSnapshot();
|
|
709
|
+
const staged = this.#staged.get(pathKey(path));
|
|
710
|
+
const stored = hasPath(snapshot.user, path);
|
|
711
|
+
if (staged === void 0) return {
|
|
712
|
+
value: readPath(preview ?? snapshot.value, path),
|
|
713
|
+
text: void 0,
|
|
714
|
+
invalid: void 0,
|
|
715
|
+
overridden: stored,
|
|
716
|
+
staged: false
|
|
717
|
+
};
|
|
718
|
+
switch (staged.edit.kind) {
|
|
719
|
+
case "value": return {
|
|
720
|
+
value: staged.edit.value,
|
|
721
|
+
text: void 0,
|
|
722
|
+
invalid: void 0,
|
|
723
|
+
overridden: true,
|
|
724
|
+
staged: true
|
|
725
|
+
};
|
|
726
|
+
case "text": {
|
|
727
|
+
const parsed = staged.edit.parse(staged.edit.text);
|
|
728
|
+
return {
|
|
729
|
+
value: parsed.kind === "value" ? parsed.value : readPath(snapshot.value, path),
|
|
730
|
+
text: staged.edit.text,
|
|
731
|
+
invalid: parsed.kind === "invalid" ? parsed.message : void 0,
|
|
732
|
+
overridden: true,
|
|
733
|
+
staged: true
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
case "clear": return {
|
|
737
|
+
value: readPath(snapshot.base, path),
|
|
738
|
+
text: void 0,
|
|
739
|
+
invalid: void 0,
|
|
740
|
+
overridden: false,
|
|
741
|
+
staged: true
|
|
742
|
+
};
|
|
743
|
+
case "drop": return {
|
|
744
|
+
value: void 0,
|
|
745
|
+
text: void 0,
|
|
746
|
+
invalid: void 0,
|
|
747
|
+
overridden: false,
|
|
748
|
+
staged: true
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* 暂存一个合法值。
|
|
754
|
+
* @param path - 具体路径。
|
|
755
|
+
* @param value - 控件给出的值。
|
|
756
|
+
*/
|
|
757
|
+
set(path, value) {
|
|
758
|
+
this.#stage(path, {
|
|
759
|
+
kind: "value",
|
|
760
|
+
value
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* 暂存一段文本,保存时才解析。
|
|
765
|
+
* @param path - 具体路径。
|
|
766
|
+
* @param text - 用户输入的原文。
|
|
767
|
+
* @param parse - 解析规则;失败时给出消息并挡住保存。
|
|
768
|
+
*/
|
|
769
|
+
setText(path, text, parse) {
|
|
770
|
+
this.#stage(path, {
|
|
771
|
+
kind: "text",
|
|
772
|
+
text,
|
|
773
|
+
parse
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* 暂存「恢复默认」。
|
|
778
|
+
* @param path - 具体路径。
|
|
779
|
+
*/
|
|
780
|
+
clear(path) {
|
|
781
|
+
this.#stage(path, { kind: "clear" });
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* 追加一个数组项,值取该项节点的默认值。
|
|
785
|
+
* @param path - 数组字段的具体路径。
|
|
786
|
+
*/
|
|
787
|
+
appendItem(path) {
|
|
788
|
+
const node = seekNode(this.#root, path);
|
|
789
|
+
if (node?.type !== "array") return;
|
|
790
|
+
const current = readPath(this.preview(), path);
|
|
791
|
+
const index = Array.isArray(current) ? current.length : 0;
|
|
792
|
+
this.set([...path, String(index)], emptyValueOf(node.item));
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 移除一个数组项。
|
|
796
|
+
* @param path - 数组字段的具体路径。
|
|
797
|
+
* @param index - 项在数组里的位置。
|
|
798
|
+
*/
|
|
799
|
+
removeItem(path, index) {
|
|
800
|
+
this.#stage([...path, String(index)], { kind: "drop" });
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* 给一层加一项,值取它的默认值。
|
|
804
|
+
*
|
|
805
|
+
* 字典认任意键;对象只认 schema 声明过的字段(写一个没声明的键会被 host 挡下,所以这里直接不写)。
|
|
806
|
+
* @param path - 容器的具体路径。
|
|
807
|
+
* @param key - 要加的键。
|
|
808
|
+
*/
|
|
809
|
+
addKey(path, key) {
|
|
810
|
+
const node = seekNode(this.#root, path);
|
|
811
|
+
if (node === void 0) return;
|
|
812
|
+
if (node.type === "dict") {
|
|
813
|
+
this.set([...path, key], emptyValueOf(node.valueNode));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const field = fieldsOf(node).find((candidate) => candidate.key === key);
|
|
817
|
+
if (field === void 0) return;
|
|
818
|
+
this.set([...path, key], emptyValueOf(field));
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* 删一个字典键。
|
|
822
|
+
* @param path - 字典字段的具体路径。
|
|
823
|
+
* @param key - 要删的键。
|
|
824
|
+
*/
|
|
825
|
+
removeKey(path, key) {
|
|
826
|
+
this.#stage([...path, key], { kind: "drop" });
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* 撤销一个字段的草稿(回到它当前的生效值)——行内编辑取消时用。
|
|
830
|
+
* @param path - 具体路径。
|
|
831
|
+
*/
|
|
832
|
+
revert(path) {
|
|
833
|
+
if (!this.#staged.delete(pathKey(path))) return;
|
|
834
|
+
this.#failed = false;
|
|
835
|
+
this.#violation = void 0;
|
|
836
|
+
this.#publish();
|
|
837
|
+
}
|
|
838
|
+
/** 丢弃全部草稿,回到 host 的读数。 */
|
|
839
|
+
discard() {
|
|
840
|
+
if (this.#staged.size === 0 && !this.#failed && this.#violation === void 0) return;
|
|
841
|
+
this.#staged.clear();
|
|
842
|
+
this.#baseline = void 0;
|
|
843
|
+
this.#failed = false;
|
|
844
|
+
this.#violation = void 0;
|
|
845
|
+
this.#publish();
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* 把全部草稿写成一次带 revision 栅栏的提交。
|
|
849
|
+
*
|
|
850
|
+
* host 是「值是否被接受」的唯一权威(有些约束 schema 表达不出来),所以落盘与否读回来决定,而不是在这里
|
|
851
|
+
* 预测;没落盘的保存保留草稿,让用户改而不是重打。
|
|
852
|
+
* @returns 写完与读回之后的收束。
|
|
853
|
+
*/
|
|
854
|
+
async save() {
|
|
855
|
+
const snapshot = this.#scope.getSnapshot();
|
|
856
|
+
if (this.#saving || !snapshot.writable) return;
|
|
857
|
+
const { writes, blocked } = this.#plan();
|
|
858
|
+
if (blocked) return;
|
|
859
|
+
const ops = writes.flatMap((item) => item.op === void 0 ? [] : [item.op]);
|
|
860
|
+
if (ops.length === 0) {
|
|
861
|
+
this.#violation = void 0;
|
|
862
|
+
this.#publish();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const failure = this.#validate(applyOps(snapshot.value ?? {}, ops));
|
|
866
|
+
if (failure !== void 0) {
|
|
867
|
+
this.#violation = failure;
|
|
868
|
+
this.#publish();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
this.#violation = void 0;
|
|
872
|
+
this.#saving = true;
|
|
873
|
+
this.#failed = false;
|
|
874
|
+
this.#publish();
|
|
875
|
+
try {
|
|
876
|
+
const landed = await this.#scope.mutate(ops, this.#baseline?.revision);
|
|
877
|
+
if (landed) {
|
|
878
|
+
this.#staged.clear();
|
|
879
|
+
this.#baseline = void 0;
|
|
880
|
+
}
|
|
881
|
+
this.#failed = !landed;
|
|
882
|
+
} catch {
|
|
883
|
+
this.#failed = true;
|
|
884
|
+
} finally {
|
|
885
|
+
this.#saving = false;
|
|
886
|
+
this.#publish();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/** 释放对共享配置表单的订阅。 */
|
|
890
|
+
dispose() {
|
|
891
|
+
this.#unsubscribe();
|
|
892
|
+
this.#listeners.clear();
|
|
893
|
+
}
|
|
894
|
+
/** 每条草稿这一轮会写什么;被 schema 挡下的草稿没有 op,但仍然是脏的。 */
|
|
895
|
+
#plan() {
|
|
896
|
+
const snapshot = this.#scope.getSnapshot();
|
|
897
|
+
const writes = [];
|
|
898
|
+
let blocked = false;
|
|
899
|
+
for (const { path, edit } of this.#staged.values()) {
|
|
900
|
+
if (edit.kind === "drop") {
|
|
901
|
+
writes.push({ op: {
|
|
902
|
+
op: "unset",
|
|
903
|
+
path: [...path]
|
|
904
|
+
} });
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (edit.kind === "clear") {
|
|
908
|
+
if (hasPath(snapshot.user, path)) writes.push({ op: {
|
|
909
|
+
op: "unset",
|
|
910
|
+
path: [...path]
|
|
911
|
+
} });
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const parsed = edit.kind === "value" ? {
|
|
915
|
+
kind: "value",
|
|
916
|
+
value: edit.value
|
|
917
|
+
} : edit.parse(edit.text);
|
|
918
|
+
if (parsed.kind === "invalid") {
|
|
919
|
+
blocked = true;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (parsed.kind === "skip") continue;
|
|
923
|
+
if (sameValue$1(readPath(snapshot.value, path), parsed.value)) continue;
|
|
924
|
+
writes.push({ op: {
|
|
925
|
+
op: "set",
|
|
926
|
+
path: [...path],
|
|
927
|
+
value: parsed.value
|
|
928
|
+
} });
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
writes,
|
|
932
|
+
blocked
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
/** 生效值套上本轮草稿后的段值:字段树按它展开(追加的数组项立刻出现),整段校验也看它。 */
|
|
936
|
+
preview() {
|
|
937
|
+
const ops = this.#plan().writes.flatMap((item) => item.op === void 0 ? [] : [item.op]);
|
|
938
|
+
return applyOps(this.#scope.getSnapshot().value ?? {}, ops);
|
|
939
|
+
}
|
|
940
|
+
#stage(path, edit) {
|
|
941
|
+
this.#baseline ??= this.#scope.getSnapshot();
|
|
942
|
+
this.#staged.set(pathKey(path), {
|
|
943
|
+
path: [...path],
|
|
944
|
+
edit
|
|
945
|
+
});
|
|
946
|
+
this.#failed = false;
|
|
947
|
+
this.#violation = void 0;
|
|
948
|
+
this.#publish();
|
|
949
|
+
}
|
|
950
|
+
#publish() {
|
|
951
|
+
for (const listener of this.#listeners) listener();
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
/** 深比较:值相等就不写(避免把「改回原值」也当成一次写)。 */
|
|
955
|
+
function sameValue$1(left, right) {
|
|
956
|
+
if (Object.is(left, right)) return true;
|
|
957
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
958
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
959
|
+
}
|
|
960
|
+
/** 路径的 map 键。 */
|
|
961
|
+
function pathKey(path) {
|
|
962
|
+
return JSON.stringify(path);
|
|
963
|
+
}
|
|
964
|
+
/** 路径末段是否在用户层里存在(覆盖判据)。 */
|
|
965
|
+
function hasPath(root, path) {
|
|
966
|
+
if (path.length === 0) return root !== void 0;
|
|
967
|
+
const parent = readPath(root, path.slice(0, -1));
|
|
968
|
+
const leaf = path[path.length - 1] ?? "";
|
|
969
|
+
if (Array.isArray(parent)) return Number(leaf) < parent.length;
|
|
970
|
+
if (typeof parent !== "object" || parent === null) return false;
|
|
971
|
+
return leaf in parent;
|
|
972
|
+
}
|
|
973
|
+
/** 把一组 op 套到一个段值上(本地读数用,不写 host)。 */
|
|
974
|
+
function applyOps(section, ops) {
|
|
975
|
+
const next = structuredClone(section);
|
|
976
|
+
for (const op of ops) {
|
|
977
|
+
const parent = readPath(next, op.path.slice(0, -1));
|
|
978
|
+
const leaf = op.path[op.path.length - 1] ?? "";
|
|
979
|
+
if (Array.isArray(parent)) {
|
|
980
|
+
if (op.op === "set") parent[Number(leaf)] = op.value;
|
|
981
|
+
else parent.splice(Number(leaf), 1);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (typeof parent !== "object" || parent === null) continue;
|
|
985
|
+
if (op.op === "set") parent[leaf] = op.value;
|
|
986
|
+
else Reflect.deleteProperty(parent, leaf);
|
|
987
|
+
}
|
|
988
|
+
return next;
|
|
989
|
+
}
|
|
990
|
+
//#endregion
|
|
991
|
+
//#region src/client/schema-form/controller.ts
|
|
992
|
+
/** `role('select', { source })` 里声明的候选源名字。 */
|
|
993
|
+
function declaredSource(node) {
|
|
994
|
+
const extra = node?.meta.extra;
|
|
995
|
+
if (typeof extra !== "object" || extra === null) return void 0;
|
|
996
|
+
const name = Reflect.get(extra, "source");
|
|
997
|
+
return typeof name === "string" && name.length > 0 ? name : void 0;
|
|
998
|
+
}
|
|
999
|
+
/** 字段在状态表里的键(具体路径的 JSON)。 */
|
|
1000
|
+
function fieldKey(path) {
|
|
1001
|
+
return JSON.stringify(path);
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* rehydrate 一段配置 schema,并判它能不能当页面的根。
|
|
1005
|
+
*
|
|
1006
|
+
* 能当根的形状有三种:对象段、**相交段**(共享判别字段 + 各分支的 `const` 标记,段值仍是对象)、以及带判别
|
|
1007
|
+
* 标签的 `union`(成员都是对象)。字面量 `union`(整段就是一个标量)不行:那一行的写盘会变成整段替换。
|
|
1008
|
+
* @param serialized - host 发来的 `schema.toJSON()`。
|
|
1009
|
+
* @param rehydrate - host schema 的 rehydrate。
|
|
1010
|
+
* @returns schema 与它的字段树,或 `undefined`(不能当页面根)。
|
|
1011
|
+
*/
|
|
1012
|
+
function projectRoot(serialized, rehydrate) {
|
|
1013
|
+
try {
|
|
1014
|
+
const schema = rehydrate(serialized);
|
|
1015
|
+
const root = projectNode(schema);
|
|
1016
|
+
if (root.type === "object" || root.type === "intersect") return {
|
|
1017
|
+
schema,
|
|
1018
|
+
root
|
|
1019
|
+
};
|
|
1020
|
+
if (root.type === "union" && root.tagKey !== void 0 && root.choices === void 0) return {
|
|
1021
|
+
schema,
|
|
1022
|
+
root
|
|
1023
|
+
};
|
|
1024
|
+
return;
|
|
1025
|
+
} catch {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* 一行能否自动生成配置页:它的 schema 能 rehydrate 且根是 {@link projectRoot} 认的形状。
|
|
1031
|
+
* @param view - host 发来的命名空间视图。
|
|
1032
|
+
* @param rehydrate - host schema 的 rehydrate。
|
|
1033
|
+
* @returns 是否可以渲染。
|
|
1034
|
+
*/
|
|
1035
|
+
function canRender(view, rehydrate) {
|
|
1036
|
+
return view !== void 0 && projectRoot(view.schema, rehydrate) !== void 0;
|
|
1037
|
+
}
|
|
1038
|
+
/** 段根不是可渲染对象时的空字段树。 */
|
|
1039
|
+
const NO_FIELDS = {
|
|
1040
|
+
key: "",
|
|
1041
|
+
path: [],
|
|
1042
|
+
label: void 0,
|
|
1043
|
+
meta: emptyMeta(),
|
|
1044
|
+
readOnly: null,
|
|
1045
|
+
type: "object",
|
|
1046
|
+
fields: []
|
|
1047
|
+
};
|
|
1048
|
+
/** 值里已有的键(数组给索引)。 */
|
|
1049
|
+
function keysOf(value) {
|
|
1050
|
+
if (Array.isArray(value)) return value.map((_item, index) => String(index));
|
|
1051
|
+
if (typeof value === "object" && value !== null) return Object.keys(value);
|
|
1052
|
+
return [];
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* 一个容器当前能加什么。
|
|
1056
|
+
*
|
|
1057
|
+
* 对象层(含相交与 union 的选中变体)是**非必填**、值里又没有的字段——必填的已经占着行了;字典层是业务注册的
|
|
1058
|
+
* 候选键(模式清单这类运行期数据)。数组层不在这里:它的「添加一项」是追加一个空项,没有键名可选。
|
|
1059
|
+
* @param node - 容器节点。
|
|
1060
|
+
* @param path - 该容器的具体路径。
|
|
1061
|
+
* @param value - 该容器自己的值(草稿后的)。
|
|
1062
|
+
* @param hints - 提示面(只用到候选键)。
|
|
1063
|
+
* @returns 可添加项,顺序即 schema / 读数的顺序;列表为空时页面不给输入框。
|
|
1064
|
+
*/
|
|
1065
|
+
function addableAt(node, path, value, hints) {
|
|
1066
|
+
const current = variantOf(node, value);
|
|
1067
|
+
if (current.type === "dict") {
|
|
1068
|
+
const present = new Set(keysOf(value));
|
|
1069
|
+
return (hints?.keysFor(path) ?? []).flatMap((key) => present.has(key) ? [] : [{
|
|
1070
|
+
key,
|
|
1071
|
+
description: void 0
|
|
1072
|
+
}]);
|
|
1073
|
+
}
|
|
1074
|
+
if (current.type !== "object" && current.type !== "intersect") return [];
|
|
1075
|
+
return fieldsOf(current).flatMap((field) => field.meta.required !== true && readPath(value, [field.key]) === void 0 ? [{
|
|
1076
|
+
key: field.key,
|
|
1077
|
+
description: field.meta.description ?? field.meta.comment
|
|
1078
|
+
}] : []);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* 一个字段的可选项。
|
|
1082
|
+
*
|
|
1083
|
+
* 字面量集合的 union(`z.union(["wal", "delete"])`)自己就带着候选;其余看提示面注册的读数——读的时候把依赖的
|
|
1084
|
+
* 兄弟字段值喂进去,所以「选完服务商才列它有哪些模型」是投影的一部分,不是额外的一次订阅。
|
|
1085
|
+
* @param node - 字段节点。
|
|
1086
|
+
* @param path - 该字段的具体路径。
|
|
1087
|
+
* @param value - 段值(草稿后的)。
|
|
1088
|
+
* @param hints - 提示面。
|
|
1089
|
+
* @returns 可选项;没有候选就是 `undefined`。
|
|
1090
|
+
*/
|
|
1091
|
+
function optionsFor(node, path, value, hints) {
|
|
1092
|
+
const current = unwrapLazy(node);
|
|
1093
|
+
if (current?.type === "union" && current.choices !== void 0) return current.choices.map((choice) => ({ value: choice }));
|
|
1094
|
+
const source = declaredSource(current);
|
|
1095
|
+
const spec = source === void 0 ? hints?.selectFor?.(path) : hints?.sourceFor?.(source);
|
|
1096
|
+
if (spec === void 0) return void 0;
|
|
1097
|
+
const parent = path.slice(0, -1);
|
|
1098
|
+
const options = spec.options((relative) => readPath(value, [...parent, ...relative]));
|
|
1099
|
+
return options.length === 0 ? void 0 : options;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* 一行配置页的控制器。
|
|
1103
|
+
*/
|
|
1104
|
+
var SchemaFormController = class {
|
|
1105
|
+
#ns;
|
|
1106
|
+
#deps;
|
|
1107
|
+
#model;
|
|
1108
|
+
#store;
|
|
1109
|
+
#disposers = [];
|
|
1110
|
+
#view;
|
|
1111
|
+
#schema;
|
|
1112
|
+
#root;
|
|
1113
|
+
/**
|
|
1114
|
+
* @param ns - settings 命名空间(行 id)。
|
|
1115
|
+
* @param deps - 该行的共享表单与 schema 面。
|
|
1116
|
+
*/
|
|
1117
|
+
constructor(ns, deps) {
|
|
1118
|
+
this.#ns = ns;
|
|
1119
|
+
this.#deps = deps;
|
|
1120
|
+
this.#model = new SchemaDraftModel({
|
|
1121
|
+
scope: deps.form,
|
|
1122
|
+
root: NO_FIELDS,
|
|
1123
|
+
validate: (value) => this.#validate(deps, value)
|
|
1124
|
+
});
|
|
1125
|
+
this.#store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(this.#project());
|
|
1126
|
+
this.#disposers.push(this.#model.subscribe(() => {
|
|
1127
|
+
this.#store.set(this.#project());
|
|
1128
|
+
}));
|
|
1129
|
+
this.#disposers.push(deps.describe.subscribe(() => {
|
|
1130
|
+
this.#sync();
|
|
1131
|
+
}));
|
|
1132
|
+
deps.describe.ensure();
|
|
1133
|
+
this.#sync();
|
|
1134
|
+
}
|
|
1135
|
+
/** 槽位渲染器要的动作与状态。 */
|
|
1136
|
+
face() {
|
|
1137
|
+
return {
|
|
1138
|
+
hooks: { schemaForm: this.#store },
|
|
1139
|
+
set: (path, value) => {
|
|
1140
|
+
this.#model.set(path, value);
|
|
1141
|
+
},
|
|
1142
|
+
setText: (path, text, parse) => {
|
|
1143
|
+
this.#model.setText(path, text, parse);
|
|
1144
|
+
},
|
|
1145
|
+
clear: (path) => {
|
|
1146
|
+
this.#model.clear(path);
|
|
1147
|
+
},
|
|
1148
|
+
appendItem: (path) => {
|
|
1149
|
+
this.#model.appendItem(path);
|
|
1150
|
+
},
|
|
1151
|
+
removeItem: (path, index) => {
|
|
1152
|
+
this.#model.removeItem(path, index);
|
|
1153
|
+
},
|
|
1154
|
+
addKey: (path, key) => {
|
|
1155
|
+
this.#model.addKey(path, key);
|
|
1156
|
+
},
|
|
1157
|
+
removeKey: (path, key) => {
|
|
1158
|
+
this.#model.removeKey(path, key);
|
|
1159
|
+
},
|
|
1160
|
+
save: () => {
|
|
1161
|
+
this.#model.save();
|
|
1162
|
+
},
|
|
1163
|
+
revert: (path) => {
|
|
1164
|
+
this.#model.revert(path);
|
|
1165
|
+
},
|
|
1166
|
+
discard: () => {
|
|
1167
|
+
this.#model.discard();
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
/** 本行的 settings 命名空间。 */
|
|
1172
|
+
namespace() {
|
|
1173
|
+
return this.#ns;
|
|
1174
|
+
}
|
|
1175
|
+
/** 外部提示(候选键这类)变了:重新投影一次,字段树与草稿都不动。 */
|
|
1176
|
+
refresh() {
|
|
1177
|
+
this.#store.set(this.#project());
|
|
1178
|
+
}
|
|
1179
|
+
/** 释放订阅。 */
|
|
1180
|
+
dispose() {
|
|
1181
|
+
for (const dispose of this.#disposers.splice(0)) dispose();
|
|
1182
|
+
this.#model.dispose();
|
|
1183
|
+
}
|
|
1184
|
+
/** 重新读 schema(describe 变了):只换字段树,草稿与 store 不动。 */
|
|
1185
|
+
#sync() {
|
|
1186
|
+
const view = this.#deps.describe.getSnapshot().view?.namespaces.find((row) => row.ns === this.#ns);
|
|
1187
|
+
this.#view = view;
|
|
1188
|
+
const projected = view === void 0 ? void 0 : projectRoot(view.schema, this.#deps.rehydrate);
|
|
1189
|
+
this.#schema = projected?.schema;
|
|
1190
|
+
this.#root = projected?.root;
|
|
1191
|
+
this.#model.setRoot(projected?.root ?? NO_FIELDS);
|
|
1192
|
+
this.#store.set(this.#project());
|
|
1193
|
+
}
|
|
1194
|
+
/** 整段校验:schema 先说话,再看数组的项身份有没有冲突。 */
|
|
1195
|
+
#validate(deps, value) {
|
|
1196
|
+
if (this.#schema !== void 0) {
|
|
1197
|
+
const failure = deps.validate(this.#schema, value);
|
|
1198
|
+
if (failure !== void 0) return failure;
|
|
1199
|
+
}
|
|
1200
|
+
if (this.#root === void 0) return void 0;
|
|
1201
|
+
const conflict = mergeKeyConflict(this.#root, value);
|
|
1202
|
+
if (conflict === void 0) return void 0;
|
|
1203
|
+
return {
|
|
1204
|
+
message: conflict.kind === "duplicate" ? deps.t("duplicateItem", {
|
|
1205
|
+
key: conflict.mergeKey,
|
|
1206
|
+
value: conflict.value
|
|
1207
|
+
}) : deps.t("missingItem", { key: conflict.mergeKey }),
|
|
1208
|
+
path: conflict.path
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
/** 渲染层读的一份完整投影。 */
|
|
1212
|
+
#project() {
|
|
1213
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1214
|
+
const root = this.#root;
|
|
1215
|
+
const preview = this.#model.preview();
|
|
1216
|
+
const walked = root === void 0 ? [] : walkFields(root, preview);
|
|
1217
|
+
for (const item of walked) fields.set(fieldKey(item.path), this.#model.field(item.path, preview));
|
|
1218
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
1219
|
+
for (const secret of this.#view?.secrets ?? []) secrets.set(fieldKey(secret.path), secret.set);
|
|
1220
|
+
const texts = /* @__PURE__ */ new Map();
|
|
1221
|
+
const options = /* @__PURE__ */ new Map();
|
|
1222
|
+
const addable = /* @__PURE__ */ new Map();
|
|
1223
|
+
for (const item of walked) {
|
|
1224
|
+
const text = this.#deps.hints?.textFor(item.path);
|
|
1225
|
+
if (text !== void 0) texts.set(fieldKey(item.path), text);
|
|
1226
|
+
const candidates = optionsFor(item.node, item.path, preview, this.#deps.hints);
|
|
1227
|
+
if (candidates !== void 0) options.set(fieldKey(item.path), candidates);
|
|
1228
|
+
const addableHere = addableAt(item.node, item.path, readPath(preview, item.path), this.#deps.hints);
|
|
1229
|
+
if (addableHere.length > 0) addable.set(fieldKey(item.path), addableHere);
|
|
1230
|
+
}
|
|
1231
|
+
const violation = this.#model.violation();
|
|
1232
|
+
const invalidAt = /* @__PURE__ */ new Map();
|
|
1233
|
+
if (violation !== void 0 && violation.path.length > 0) invalidAt.set(fieldKey(violation.path), violation.message);
|
|
1234
|
+
return {
|
|
1235
|
+
...this.#model.shell(),
|
|
1236
|
+
configured: root !== void 0,
|
|
1237
|
+
root: root ?? NO_FIELDS,
|
|
1238
|
+
walked,
|
|
1239
|
+
fields,
|
|
1240
|
+
secrets,
|
|
1241
|
+
texts,
|
|
1242
|
+
options,
|
|
1243
|
+
addable,
|
|
1244
|
+
violation,
|
|
1245
|
+
invalidAt
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/client/schema-form/hints.ts
|
|
1251
|
+
/**
|
|
1252
|
+
* 通用表单的提示面:业务知道、而 schema 表达不了的结构信息。
|
|
1253
|
+
*
|
|
1254
|
+
* 两件事:**dict 字段的候选键**(例如「会话模式」那一行,键是运行期清单里的模式 id,schema 里写不出来),以及
|
|
1255
|
+
* **字段的候选值**(选服务商、选模型这类——候选来自部署里的另一份配置,还经常互相依赖)。业务在客户端注册一个
|
|
1256
|
+
* 同步读数,表单一侧在投影时用它补出「未配置」行或把字段画成选择器。
|
|
1257
|
+
*
|
|
1258
|
+
* 读数必须是**同步**的:投影发生在渲染帧里。异步来源(HTTP / remote)由业务自己取好再注册;注册本身就当作一次
|
|
1259
|
+
* 变更通知(所以"取到清单后再注册"是推荐用法)。
|
|
1260
|
+
*/
|
|
1261
|
+
/** 提示面的完整服务(业务用注册侧,表单一侧用读侧)。 */
|
|
1262
|
+
var SchemaFormHints = class extends _deepseek_ai_cordis.Service {
|
|
1263
|
+
#readers = /* @__PURE__ */ new Map();
|
|
1264
|
+
#texts = /* @__PURE__ */ new Map();
|
|
1265
|
+
#selects = /* @__PURE__ */ new Map();
|
|
1266
|
+
#sources = /* @__PURE__ */ new Map();
|
|
1267
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
1268
|
+
/** @param ctx - 提供本服务的插件上下文。 */
|
|
1269
|
+
constructor(ctx) {
|
|
1270
|
+
super(ctx, "schemaFormHints");
|
|
1271
|
+
}
|
|
1272
|
+
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处的键。 */
|
|
1273
|
+
keysFor(ns, path) {
|
|
1274
|
+
return lookup(this.#readers, ns, path)?.() ?? [];
|
|
1275
|
+
}
|
|
1276
|
+
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的文案。 */
|
|
1277
|
+
textFor(ns, path) {
|
|
1278
|
+
return lookup(this.#texts, ns, path)?.() ?? {};
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* @param ns - settings 命名空间。
|
|
1282
|
+
* @param path - 字段路径。
|
|
1283
|
+
* @param spec - 候选取法与依赖。
|
|
1284
|
+
* @returns 注销函数。
|
|
1285
|
+
*/
|
|
1286
|
+
select(ns, path, spec) {
|
|
1287
|
+
const key = locationKey(ns, path);
|
|
1288
|
+
this.#selects.set(key, spec);
|
|
1289
|
+
this.#publish();
|
|
1290
|
+
return () => {
|
|
1291
|
+
if (this.#selects.get(key) !== spec) return;
|
|
1292
|
+
this.#selects.delete(key);
|
|
1293
|
+
this.#publish();
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
/** @param ns - settings 命名空间。 @param path - 字段路径。 @returns 该处注册的候选读数。 */
|
|
1297
|
+
selectFor(ns, path) {
|
|
1298
|
+
return lookup(this.#selects, ns, path);
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* @param name - 源的名字。
|
|
1302
|
+
* @param spec - 候选取法与依赖。
|
|
1303
|
+
* @returns 注销函数。
|
|
1304
|
+
*/
|
|
1305
|
+
source(name, spec) {
|
|
1306
|
+
this.#sources.set(name, spec);
|
|
1307
|
+
this.#publish();
|
|
1308
|
+
return () => {
|
|
1309
|
+
if (this.#sources.get(name) !== spec) return;
|
|
1310
|
+
this.#sources.delete(name);
|
|
1311
|
+
this.#publish();
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
/** @param name - 源的名字。 @returns 该源注册的候选读数。 */
|
|
1315
|
+
sourceFor(name) {
|
|
1316
|
+
return this.#sources.get(name);
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* @param ns - settings 命名空间。
|
|
1320
|
+
* @param path - 字段路径。
|
|
1321
|
+
* @param read - 同步读数。
|
|
1322
|
+
* @returns 注销函数。
|
|
1323
|
+
*/
|
|
1324
|
+
describe(ns, path, read) {
|
|
1325
|
+
const key = locationKey(ns, path);
|
|
1326
|
+
this.#texts.set(key, read);
|
|
1327
|
+
this.#publish();
|
|
1328
|
+
return () => {
|
|
1329
|
+
if (this.#texts.get(key) !== read) return;
|
|
1330
|
+
this.#texts.delete(key);
|
|
1331
|
+
this.#publish();
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* @param ns - settings 命名空间。
|
|
1336
|
+
* @param path - 字段路径。
|
|
1337
|
+
* @param read - 同步读数。
|
|
1338
|
+
* @returns 注销函数。
|
|
1339
|
+
*/
|
|
1340
|
+
suggestKeys(ns, path, read) {
|
|
1341
|
+
const key = locationKey(ns, path);
|
|
1342
|
+
this.#readers.set(key, read);
|
|
1343
|
+
this.#publish();
|
|
1344
|
+
return () => {
|
|
1345
|
+
if (this.#readers.get(key) !== read) return;
|
|
1346
|
+
this.#readers.delete(key);
|
|
1347
|
+
this.#publish();
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* 观察候选键的变化(注册与注销)。
|
|
1352
|
+
* @param listener - 变更回调。
|
|
1353
|
+
* @returns 取消订阅。
|
|
1354
|
+
*/
|
|
1355
|
+
subscribe(listener) {
|
|
1356
|
+
this.#listeners.add(listener);
|
|
1357
|
+
return () => {
|
|
1358
|
+
this.#listeners.delete(listener);
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* 重新通知一次:读数背后的数据变了(业务自己知道),页面据此重算候选行。
|
|
1363
|
+
*/
|
|
1364
|
+
refresh() {
|
|
1365
|
+
this.#publish();
|
|
1366
|
+
}
|
|
1367
|
+
#publish() {
|
|
1368
|
+
for (const listener of this.#listeners) listener();
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
/** 一处字段的定位键(命名空间 + 路径)。 */
|
|
1372
|
+
function locationKey(ns, path) {
|
|
1373
|
+
return `${ns}\u0000${path.join("\0")}`;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* 按具体路径找一处注册:先精确匹配,再试**模板变体**(动态键 / 数组下标的段写成 `*`)。
|
|
1377
|
+
*
|
|
1378
|
+
* 业务因此可以注册一次 `models.*.provider` 就覆盖每个模式,不必知道运行期有哪些键。
|
|
1379
|
+
* @param map - 某类注册表。
|
|
1380
|
+
* @param ns - settings 命名空间。
|
|
1381
|
+
* @param path - 具体路径。
|
|
1382
|
+
* @returns 命中的注册,或 `undefined`。
|
|
1383
|
+
*/
|
|
1384
|
+
function lookup(map, ns, path) {
|
|
1385
|
+
const exact = map.get(locationKey(ns, path));
|
|
1386
|
+
if (exact !== void 0) return exact;
|
|
1387
|
+
for (const masked of templates(path)) {
|
|
1388
|
+
const hit = map.get(locationKey(ns, masked));
|
|
1389
|
+
if (hit !== void 0) return hit;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/** 具体路径的模板变体(首段是顶层字段名,保持原样):通配得多的先试。 */
|
|
1393
|
+
function* templates(path) {
|
|
1394
|
+
const wildcardable = Math.max(0, path.length - 1);
|
|
1395
|
+
for (let bits = (1 << wildcardable) - 1; bits > 0; bits--) yield path.map((segment, index) => index > 0 && (bits & 1 << index - 1) !== 0 ? "*" : segment);
|
|
1396
|
+
}
|
|
1397
|
+
//#endregion
|
|
1398
|
+
//#region src/client/schema-form/locales.ts
|
|
1399
|
+
/**
|
|
1400
|
+
* 本包字典(命名空间 `settings.schema-form`):表单壳、字段控件与只读呈现的文案。
|
|
1401
|
+
*
|
|
1402
|
+
* 字段自身的人话标签不在这里(那是业务方经字段槽或 schema `description` 给的),这里只放**机制**文案。
|
|
1403
|
+
*/
|
|
1404
|
+
/** 中文文案(部署语言偏好是 zh)。 */
|
|
1405
|
+
const zh = {
|
|
1406
|
+
unavailable: "该插件当前未加载,暂时无法配置。",
|
|
1407
|
+
readOnly: "本部署的设置为只读。",
|
|
1408
|
+
save: "保存",
|
|
1409
|
+
confirmEdit: "确认",
|
|
1410
|
+
cancelEdit: "取消",
|
|
1411
|
+
saving: "保存中…",
|
|
1412
|
+
saveFailed: "本部署没有接受这些值,已保留供你修改。",
|
|
1413
|
+
notSaved: "没有保存:{message}",
|
|
1414
|
+
noSchema: "这一行没有可自动生成的配置项。",
|
|
1415
|
+
reset: "恢复默认",
|
|
1416
|
+
revert: "撤回",
|
|
1417
|
+
invalid: "这个值不被接受。",
|
|
1418
|
+
invalidNumber: "需要一个数字。",
|
|
1419
|
+
invalidInteger: "需要一个整数。",
|
|
1420
|
+
invalidJson: "需要一段合法的 JSON。",
|
|
1421
|
+
invalidBoolean: "需要 true 或 false。",
|
|
1422
|
+
invalidConst: "这个字段只接受固定值。",
|
|
1423
|
+
invalidPattern: "要匹配 {pattern}。",
|
|
1424
|
+
invalidRange: "要在 {min} 与 {max} 之间。",
|
|
1425
|
+
invalidMin: "不能小于 {min}。",
|
|
1426
|
+
invalidMax: "不能大于 {max}。",
|
|
1427
|
+
invalidChoice: "只能是 {choices} 之一。",
|
|
1428
|
+
unknownProperty: "这一层没有这个字段:从上面的候选里选,或换个键名。",
|
|
1429
|
+
empty: "(空)",
|
|
1430
|
+
toggleGroup: "展开 / 收起这一层",
|
|
1431
|
+
switchVariant: "切换这一层的形状或分支",
|
|
1432
|
+
copyValue: "复制值",
|
|
1433
|
+
copied: "已复制",
|
|
1434
|
+
addProperty: "添加属性",
|
|
1435
|
+
addItemPaste: "添加数组项(可粘贴 JSON 字符串)",
|
|
1436
|
+
addItem: "添加一项",
|
|
1437
|
+
removeItem: "移除",
|
|
1438
|
+
addKey: "添加键(可粘贴 JSON 字符串)",
|
|
1439
|
+
keyName: "键名",
|
|
1440
|
+
noKeys: "还没有键。",
|
|
1441
|
+
noItems: "还没有项。",
|
|
1442
|
+
deprecated: "已废弃",
|
|
1443
|
+
experimental: "试验中",
|
|
1444
|
+
secretSet: "已配置",
|
|
1445
|
+
secretUnset: "未配置",
|
|
1446
|
+
secretHint: "留空即保留已存的值。",
|
|
1447
|
+
constValue: "固定值",
|
|
1448
|
+
notEditable: "这一项由插件在运行时提供,不能在页面上编辑。",
|
|
1449
|
+
recursive: "递归结构:展开到这一层为止。",
|
|
1450
|
+
fixedLength: "固定长度",
|
|
1451
|
+
bitsetHint: "可多选。",
|
|
1452
|
+
unconfigured: "未配置",
|
|
1453
|
+
readOnlyField: "只读",
|
|
1454
|
+
noDescription: "(无说明)",
|
|
1455
|
+
required: "必填",
|
|
1456
|
+
duplicateItem: "重复的 {key}:{value}",
|
|
1457
|
+
missingItem: "每一项都需要一个 {key}"
|
|
1458
|
+
};
|
|
1459
|
+
/** 英文文案。 */
|
|
1460
|
+
const en = {
|
|
1461
|
+
unavailable: "This plugin is not loaded, so it cannot be configured right now.",
|
|
1462
|
+
readOnly: "This deployment stores settings read-only.",
|
|
1463
|
+
save: "Save",
|
|
1464
|
+
confirmEdit: "Confirm",
|
|
1465
|
+
cancelEdit: "Cancel",
|
|
1466
|
+
saving: "Saving…",
|
|
1467
|
+
saveFailed: "This deployment did not accept these values; they are kept for you to fix.",
|
|
1468
|
+
notSaved: "Not saved: {message}",
|
|
1469
|
+
noSchema: "This row has no configuration to generate.",
|
|
1470
|
+
reset: "Reset",
|
|
1471
|
+
revert: "Revert",
|
|
1472
|
+
invalid: "This value is not accepted.",
|
|
1473
|
+
invalidNumber: "Enter a number.",
|
|
1474
|
+
invalidInteger: "Enter a whole number.",
|
|
1475
|
+
invalidJson: "Enter valid JSON.",
|
|
1476
|
+
invalidBoolean: "Enter true or false.",
|
|
1477
|
+
invalidConst: "This field only accepts its fixed value.",
|
|
1478
|
+
invalidPattern: "Must match {pattern}.",
|
|
1479
|
+
invalidRange: "Must be between {min} and {max}.",
|
|
1480
|
+
invalidMin: "Must not be less than {min}.",
|
|
1481
|
+
invalidMax: "Must not be greater than {max}.",
|
|
1482
|
+
invalidChoice: "Must be one of {choices}.",
|
|
1483
|
+
unknownProperty: "No such field at this level: pick one of the candidates above.",
|
|
1484
|
+
empty: "(empty)",
|
|
1485
|
+
toggleGroup: "Expand or collapse this level",
|
|
1486
|
+
switchVariant: "Switch this level's shape or branch",
|
|
1487
|
+
copyValue: "Copy value",
|
|
1488
|
+
copied: "Copied",
|
|
1489
|
+
addProperty: "Add property",
|
|
1490
|
+
addItemPaste: "Add item (a JSON string can be pasted)",
|
|
1491
|
+
addItem: "Add item",
|
|
1492
|
+
removeItem: "Remove",
|
|
1493
|
+
addKey: "Add key (a JSON string can be pasted)",
|
|
1494
|
+
keyName: "Key",
|
|
1495
|
+
noKeys: "No keys yet.",
|
|
1496
|
+
noItems: "No items yet.",
|
|
1497
|
+
deprecated: "deprecated",
|
|
1498
|
+
experimental: "experimental",
|
|
1499
|
+
secretSet: "configured",
|
|
1500
|
+
secretUnset: "not set",
|
|
1501
|
+
secretHint: "Leave blank to keep the stored value.",
|
|
1502
|
+
constValue: "fixed",
|
|
1503
|
+
notEditable: "This entry is provided by the plugin at runtime and cannot be edited here.",
|
|
1504
|
+
recursive: "Recursive structure: expanded down to this level.",
|
|
1505
|
+
fixedLength: "fixed length",
|
|
1506
|
+
bitsetHint: "Multiple choices.",
|
|
1507
|
+
unconfigured: "not configured",
|
|
1508
|
+
readOnlyField: "read-only",
|
|
1509
|
+
noDescription: "(no description)",
|
|
1510
|
+
required: "required",
|
|
1511
|
+
duplicateItem: "Duplicate {key}: {value}",
|
|
1512
|
+
missingItem: "Every item needs a {key}"
|
|
1513
|
+
};
|
|
1514
|
+
//#endregion
|
|
1515
|
+
//#region src/client/schema-form/rows.ts
|
|
1516
|
+
/**
|
|
1517
|
+
* 配对命名空间与 bundle 行清单。
|
|
1518
|
+
* @param namespaces - host 发来的命名空间视图。
|
|
1519
|
+
* @param bundles - host 的 bundle 清单(含各 bundle 声明的行 id)。
|
|
1520
|
+
* @returns 该自动注册的行,按命名空间视图的顺序。
|
|
1521
|
+
*/
|
|
1522
|
+
function rowRegistrations(namespaces, bundles) {
|
|
1523
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1524
|
+
for (const bundle of bundles) for (const row of bundle.rows) owners.set(row.rowId, bundle.name);
|
|
1525
|
+
const plans = [];
|
|
1526
|
+
for (const view of namespaces) {
|
|
1527
|
+
if (view.autoGenerate !== true) continue;
|
|
1528
|
+
const owner = owners.get(view.ns);
|
|
1529
|
+
if (owner === void 0) continue;
|
|
1530
|
+
plans.push({
|
|
1531
|
+
key: `${owner}#${view.ns}`,
|
|
1532
|
+
ns: view.ns
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
return plans;
|
|
1536
|
+
}
|
|
1537
|
+
/** 自动项的兜底优先级:手写卡片(默认 0)遮住它。 */
|
|
1538
|
+
const FALLBACK_PRIORITY = 100;
|
|
1539
|
+
/**
|
|
1540
|
+
* 自动注册的差分器:按 describe 与 bundle 清单把「该有的行」与「已在的行」对齐。
|
|
1541
|
+
*/
|
|
1542
|
+
var RowRegistration = class {
|
|
1543
|
+
#deps;
|
|
1544
|
+
#live = /* @__PURE__ */ new Map();
|
|
1545
|
+
#pending;
|
|
1546
|
+
#disposed = false;
|
|
1547
|
+
/**
|
|
1548
|
+
* @param deps - 槽位、清单读面与每行的槽项工厂。
|
|
1549
|
+
*/
|
|
1550
|
+
constructor(deps) {
|
|
1551
|
+
this.#deps = deps;
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* 对齐一次:新增该有的行,注销不该有的行。
|
|
1555
|
+
*
|
|
1556
|
+
* 并发调用折叠成一次(bundle 清单只读一趟),后到的调用等同一趟结束。
|
|
1557
|
+
* @returns 对齐完成。
|
|
1558
|
+
*/
|
|
1559
|
+
sync() {
|
|
1560
|
+
if (this.#disposed) return Promise.resolve();
|
|
1561
|
+
this.#pending ??= this.#run().finally(() => {
|
|
1562
|
+
this.#pending = void 0;
|
|
1563
|
+
});
|
|
1564
|
+
return this.#pending;
|
|
1565
|
+
}
|
|
1566
|
+
/** 收掉自己:注销还活着的全部注册并释放它们的控制器。 */
|
|
1567
|
+
dispose() {
|
|
1568
|
+
this.#disposed = true;
|
|
1569
|
+
this.#release();
|
|
1570
|
+
}
|
|
1571
|
+
async #run() {
|
|
1572
|
+
const bundles = await this.#deps.bundles();
|
|
1573
|
+
if (this.#disposed) return;
|
|
1574
|
+
const namespaces = this.#deps.describe.getSnapshot().view?.namespaces ?? [];
|
|
1575
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
1576
|
+
for (const plan of rowRegistrations(namespaces, bundles)) {
|
|
1577
|
+
if (this.#deps.entryFor(plan.ns) === void 0) continue;
|
|
1578
|
+
wanted.set(plan.ns, plan);
|
|
1579
|
+
}
|
|
1580
|
+
for (const [ns, dispose] of this.#live) {
|
|
1581
|
+
if (wanted.has(ns)) continue;
|
|
1582
|
+
dispose();
|
|
1583
|
+
this.#live.delete(ns);
|
|
1584
|
+
this.#deps.release(ns);
|
|
1585
|
+
}
|
|
1586
|
+
for (const [ns, plan] of wanted) {
|
|
1587
|
+
if (this.#live.has(ns)) continue;
|
|
1588
|
+
const entry = this.#deps.entryFor(ns);
|
|
1589
|
+
if (entry === void 0) continue;
|
|
1590
|
+
const dispose = this.#deps.slots.register({
|
|
1591
|
+
name: "plugins.row.config",
|
|
1592
|
+
key: plan.key,
|
|
1593
|
+
priority: FALLBACK_PRIORITY,
|
|
1594
|
+
locale: this.#deps.locale
|
|
1595
|
+
}, entry.component);
|
|
1596
|
+
this.#live.set(ns, dispose);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
/** 注销全部并释放控制器。 */
|
|
1600
|
+
#release() {
|
|
1601
|
+
for (const [ns, dispose] of this.#live) {
|
|
1602
|
+
dispose();
|
|
1603
|
+
this.#live.delete(ns);
|
|
1604
|
+
this.#deps.release(ns);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
//#endregion
|
|
1609
|
+
//#region src/client/schema-form/labels.ts
|
|
1610
|
+
/** 字段的说明(`description` 本地化后 + `comment`),没有就是 undefined。 */
|
|
1611
|
+
function fieldHint(node, resolveText) {
|
|
1612
|
+
const parts = [];
|
|
1613
|
+
if (node.meta.description !== void 0) parts.push(resolveText(node.meta.description));
|
|
1614
|
+
if (node.meta.comment !== void 0) parts.push(node.meta.comment);
|
|
1615
|
+
return parts.length === 0 ? void 0 : parts.join(" ");
|
|
1616
|
+
}
|
|
1617
|
+
//#endregion
|
|
1618
|
+
//#region src/client/schema-form/lines.ts
|
|
1619
|
+
/**
|
|
1620
|
+
* 行模型:把字段树(`walkFields` 的结果)排成**编辑器的行**——结构行(`{` / `[` / `}` / `]`)、字段行、注释行、
|
|
1621
|
+
* 以及容器尾部的添加行。
|
|
1622
|
+
*
|
|
1623
|
+
* 渲染层因此只按行画,不必自己递归:行序 = 视觉顺序,行号连续。折叠在这里生效(折叠的容器只留开启行)。
|
|
1624
|
+
*/
|
|
1625
|
+
/**
|
|
1626
|
+
* 容器的形状:对象类用 `{}`,序列类用 `[]`。
|
|
1627
|
+
*
|
|
1628
|
+
* union 没有自己的形状——它跟着**当前选中那一支**走(`access` 是数组就在页面上画 `[`),这也是
|
|
1629
|
+
* `access: string | string[]` 这类字段以前被画成对象容器的原因。
|
|
1630
|
+
* @param node - 字段节点。
|
|
1631
|
+
* @param value - 这一层的值(选支判据)。
|
|
1632
|
+
* @returns 形状;叶子节点是 `undefined`。
|
|
1633
|
+
*/
|
|
1634
|
+
function containerShape(node, value) {
|
|
1635
|
+
switch (variantOf(node, value).type) {
|
|
1636
|
+
case "object":
|
|
1637
|
+
case "dict":
|
|
1638
|
+
case "intersect": return "object";
|
|
1639
|
+
case "array":
|
|
1640
|
+
case "tuple": return "array";
|
|
1641
|
+
default: return;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
/** 渲染顺序里可见的字段:折叠的容器把整棵子树滤掉,只留它自己的开启行。 */
|
|
1645
|
+
function visibleFields(state, fold) {
|
|
1646
|
+
const containers = new Set(state.walked.filter((item) => containerShape(item.node, valueAt(state, item.path)) !== void 0).map((item) => fieldKey(item.path)));
|
|
1647
|
+
return state.walked.filter((item) => {
|
|
1648
|
+
for (let depth = 1; depth < item.path.length; depth++) {
|
|
1649
|
+
const ancestor = item.path.slice(0, depth);
|
|
1650
|
+
if (containers.has(fieldKey(ancestor)) && fold.collapsed(ancestor)) return false;
|
|
1651
|
+
}
|
|
1652
|
+
return true;
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
/** 这一层的当前值:与字段行显示的是同一份读数(草稿优先)。 */
|
|
1656
|
+
function valueAt(state, path) {
|
|
1657
|
+
return state.fields.get(fieldKey(path))?.value;
|
|
1658
|
+
}
|
|
1659
|
+
/** 这一层能加什么:数组追加空项,字典敲键名(可来自候选),对象从还没配的声明字段里选。 */
|
|
1660
|
+
function addOf(node, shape, options) {
|
|
1661
|
+
if (shape === "array") return { kind: "item" };
|
|
1662
|
+
if (unwrapLazy(node)?.type === "dict") return {
|
|
1663
|
+
kind: "key",
|
|
1664
|
+
options
|
|
1665
|
+
};
|
|
1666
|
+
return options.length === 0 ? void 0 : {
|
|
1667
|
+
kind: "prop",
|
|
1668
|
+
options
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
/** 一个节点上的变体切换控件:不是 union、或只有一支、或成员全是常量(字面量集合是选择器)时没有。 */
|
|
1672
|
+
function variantsOf(node, value) {
|
|
1673
|
+
const union = unionOf(node);
|
|
1674
|
+
if (union === void 0 || union.choices !== void 0 || union.variants.length < 2) return void 0;
|
|
1675
|
+
return {
|
|
1676
|
+
choices: variantChoices(union),
|
|
1677
|
+
selected: union.variants.indexOf(variantOf(union, value))
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* 排成行序列。
|
|
1682
|
+
* @param state - 页面读数。
|
|
1683
|
+
* @param fold - 折叠状态。
|
|
1684
|
+
* @param resolveText - `description` 的本地化。
|
|
1685
|
+
* @param t - 本包字典。
|
|
1686
|
+
* @returns 行序列(行号即下标)。
|
|
1687
|
+
*/
|
|
1688
|
+
function editorLines(state, fold, resolveText, t) {
|
|
1689
|
+
const lines = [];
|
|
1690
|
+
const open = [];
|
|
1691
|
+
const closeOpenUntil = (path) => {
|
|
1692
|
+
while (open.length > 0) {
|
|
1693
|
+
const top = open[open.length - 1];
|
|
1694
|
+
if (isPrefix(top.path, path)) return;
|
|
1695
|
+
open.pop();
|
|
1696
|
+
lines.push({
|
|
1697
|
+
kind: "close",
|
|
1698
|
+
path: top.path,
|
|
1699
|
+
node: top.node,
|
|
1700
|
+
shape: top.shape,
|
|
1701
|
+
depth: top.depth,
|
|
1702
|
+
add: top.add,
|
|
1703
|
+
member: top.member
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
for (const item of visibleFields(state, fold)) {
|
|
1708
|
+
closeOpenUntil(item.path);
|
|
1709
|
+
const node = unwrapLazy(item.node) ?? item.node;
|
|
1710
|
+
const value = valueAt(state, item.path);
|
|
1711
|
+
const depth = item.path.length;
|
|
1712
|
+
const comment = commentFor(state, item, resolveText, t);
|
|
1713
|
+
if (comment !== void 0) lines.push({
|
|
1714
|
+
kind: "comment",
|
|
1715
|
+
path: item.path,
|
|
1716
|
+
depth,
|
|
1717
|
+
text: comment.text,
|
|
1718
|
+
invalid: comment.invalid
|
|
1719
|
+
});
|
|
1720
|
+
const variants = variantsOf(node, value);
|
|
1721
|
+
const shape = containerShape(node, value);
|
|
1722
|
+
if (shape !== void 0) {
|
|
1723
|
+
const collapsed = fold.collapsed(item.path);
|
|
1724
|
+
const add = addOf(node, shape, state.addable.get(fieldKey(item.path)) ?? []);
|
|
1725
|
+
lines.push({
|
|
1726
|
+
kind: "open",
|
|
1727
|
+
path: item.path,
|
|
1728
|
+
node,
|
|
1729
|
+
shape,
|
|
1730
|
+
depth,
|
|
1731
|
+
collapsed,
|
|
1732
|
+
member: item.member,
|
|
1733
|
+
variants
|
|
1734
|
+
});
|
|
1735
|
+
if (!collapsed) open.push({
|
|
1736
|
+
path: item.path,
|
|
1737
|
+
node,
|
|
1738
|
+
shape,
|
|
1739
|
+
depth,
|
|
1740
|
+
add,
|
|
1741
|
+
member: item.member
|
|
1742
|
+
});
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
lines.push({
|
|
1746
|
+
kind: "field",
|
|
1747
|
+
path: item.path,
|
|
1748
|
+
node,
|
|
1749
|
+
depth,
|
|
1750
|
+
field: state.fields.get(fieldKey(item.path)) ?? {
|
|
1751
|
+
value: void 0,
|
|
1752
|
+
text: void 0,
|
|
1753
|
+
invalid: void 0,
|
|
1754
|
+
overridden: false,
|
|
1755
|
+
staged: false
|
|
1756
|
+
},
|
|
1757
|
+
member: item.member,
|
|
1758
|
+
variants,
|
|
1759
|
+
variantsStandIn: false
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
while (open.length > 0) {
|
|
1763
|
+
const top = open.pop();
|
|
1764
|
+
lines.push({
|
|
1765
|
+
kind: "close",
|
|
1766
|
+
path: top.path,
|
|
1767
|
+
node: top.node,
|
|
1768
|
+
shape: top.shape,
|
|
1769
|
+
depth: top.depth,
|
|
1770
|
+
add: top.add,
|
|
1771
|
+
member: top.member
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
return moveVariantControlToTag(lines);
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* 判别式 union 的切换控件挪到**标签字段那一行**上:`session-rdb` 的页面上用户改的就是 `type`,把它放在容器
|
|
1778
|
+
* 行上等于让人在两处看同一件事。没有标签字段(`access` 这种按值形状选的)就留在 union 自己那一行。
|
|
1779
|
+
*/
|
|
1780
|
+
function moveVariantControlToTag(lines) {
|
|
1781
|
+
for (const line of lines) {
|
|
1782
|
+
if (line.kind === "comment" || line.kind === "close") continue;
|
|
1783
|
+
if (line.variants === void 0) continue;
|
|
1784
|
+
const tagKey = unionOf(line.node)?.tagKey;
|
|
1785
|
+
if (tagKey === void 0) continue;
|
|
1786
|
+
const tagPath = [...line.path, tagKey];
|
|
1787
|
+
const tagLine = lines.find((candidate) => candidate.kind === "field" && candidate.path.length === tagPath.length && candidate.path.every((segment, index) => segment === tagPath[index]));
|
|
1788
|
+
if (tagLine === void 0 || tagLine.kind !== "field") continue;
|
|
1789
|
+
tagLine.variants = line.variants;
|
|
1790
|
+
tagLine.variantsStandIn = true;
|
|
1791
|
+
line.variants = void 0;
|
|
1792
|
+
}
|
|
1793
|
+
return lines;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* 一行的注释:业务给的文案(字段槽的 label/hint)优先,其次 schema 的说明。
|
|
1797
|
+
*
|
|
1798
|
+
* 这一行有问题时**注释位让给那条消息**(参考实现也是把错误画在行尾那一段的位置上)——说明换成"哪里不对",
|
|
1799
|
+
* 错误因此不必挤在值后面。
|
|
1800
|
+
*/
|
|
1801
|
+
function commentFor(state, item, resolveText, t) {
|
|
1802
|
+
const key = fieldKey(item.path);
|
|
1803
|
+
const failure = state.fields.get(key)?.invalid ?? state.invalidAt.get(key);
|
|
1804
|
+
if (failure !== void 0) return {
|
|
1805
|
+
text: failure,
|
|
1806
|
+
invalid: true
|
|
1807
|
+
};
|
|
1808
|
+
const node = unwrapLazy(item.node) ?? item.node;
|
|
1809
|
+
const parts = [];
|
|
1810
|
+
const text = state.texts.get(fieldKey(item.path));
|
|
1811
|
+
const hint = [text?.label, text?.hint].filter((part) => part !== void 0 && part !== "").join(":") || fieldHint(node, resolveText);
|
|
1812
|
+
if (hint !== void 0 && hint !== "") parts.push(hint);
|
|
1813
|
+
if (node.meta.disabled) parts.push(t("readOnlyField"));
|
|
1814
|
+
if (node.meta.badges.length > 0) parts.push(node.meta.badges.join(" / "));
|
|
1815
|
+
return parts.length === 0 ? void 0 : {
|
|
1816
|
+
text: parts.join(" · "),
|
|
1817
|
+
invalid: false
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
/** `path` 是不是 `prefix` 的后代(含自身)。 */
|
|
1821
|
+
function isPrefix(prefix, path) {
|
|
1822
|
+
if (prefix.length > path.length) return false;
|
|
1823
|
+
return prefix.every((segment, index) => path[index] === segment);
|
|
1824
|
+
}
|
|
1825
|
+
//#endregion
|
|
23
1826
|
//#region src/client/styling/toolkit.ts
|
|
24
1827
|
function isUndefined(value) {
|
|
25
1828
|
return value === void 0;
|
|
@@ -324,7 +2127,7 @@ window.__ModuleLoader__.load({
|
|
|
324
2127
|
//#endregion
|
|
325
2128
|
//#region src/client/theme.generated.ts
|
|
326
2129
|
/**
|
|
327
|
-
* 官方主题的 `--dsw-*` token 全集(
|
|
2130
|
+
* 官方主题的 `--dsw-*` token 全集(395 个),由 packages/client/ui-primitives/scripts/gen-design-tokens.mts 生成,勿手改。
|
|
328
2131
|
*
|
|
329
2132
|
* 叶子是该变量的**默认值**(官方 light 主题里首次出现的定义):既是类型推导的来源,也可作 fallback。
|
|
330
2133
|
* `"$"` 键出现在「自身也是 token 的分支」上(命名体系里有 40 个短名同时是长名前缀的 token)。
|
|
@@ -334,7 +2137,7 @@ window.__ModuleLoader__.load({
|
|
|
334
2137
|
alias: {
|
|
335
2138
|
bg: {
|
|
336
2139
|
base: "var(--dsw-static-neutral-bluish-00)",
|
|
337
|
-
document: { preview: "var(--dsw-static-neutral-bluish-
|
|
2140
|
+
document: { preview: "var(--dsw-static-neutral-bluish-100)" },
|
|
338
2141
|
layer: {
|
|
339
2142
|
"1": "var(--dsw-static-neutral-bluish-00)",
|
|
340
2143
|
"2": "var(--dsw-static-neutral-bluish-00)",
|
|
@@ -428,7 +2231,7 @@ window.__ModuleLoader__.load({
|
|
|
428
2231
|
label: {
|
|
429
2232
|
caption: "var(--dsw-static-neutral-bluish-400)",
|
|
430
2233
|
dimmed: "var(--dsw-static-neutral-bluish-200)",
|
|
431
|
-
document: { preview: "var(--dsw-static-neutral-bluish-
|
|
2234
|
+
document: { preview: "var(--dsw-static-neutral-bluish-700)" },
|
|
432
2235
|
primary: {
|
|
433
2236
|
$: "var(--dsw-static-neutral-bluish-1000)",
|
|
434
2237
|
bluish: "var(--dsw-static-blue-900)",
|
|
@@ -456,6 +2259,13 @@ window.__ModuleLoader__.load({
|
|
|
456
2259
|
placeholder: "var(--dsw-static-neutral-bluish-60)",
|
|
457
2260
|
tag: "var(--dsw-static-neutral-bluish-75)"
|
|
458
2261
|
},
|
|
2262
|
+
menu: { icon: "var(--dsw-static-neutral-bluish-800)" },
|
|
2263
|
+
onboarding: {
|
|
2264
|
+
accent: "rgb(57, 100, 254)",
|
|
2265
|
+
card: { fill: "color-mix(in srgb, var(--dsw-static-neutral-bluish-00) 80%, transparent)" },
|
|
2266
|
+
checkbox: { border: "color-mix(in srgb, var(--dsw-static-neutral-bluish-1000) 20%, transparent)" },
|
|
2267
|
+
secondary: { fill: "var(--dsw-static-neutral-bluish-00)" }
|
|
2268
|
+
},
|
|
459
2269
|
scrollbar: {
|
|
460
2270
|
bg: {
|
|
461
2271
|
l1: "var(--dsw-static-neutral-200)",
|
|
@@ -466,6 +2276,10 @@ window.__ModuleLoader__.load({
|
|
|
466
2276
|
l2: "var(--dsw-static-neutral-300)"
|
|
467
2277
|
}
|
|
468
2278
|
},
|
|
2279
|
+
settings: { card: {
|
|
2280
|
+
fill: "var(--dsw-alias-bg-layer-2)",
|
|
2281
|
+
stroke: "var(--dsw-alias-border-l4)"
|
|
2282
|
+
} },
|
|
469
2283
|
state: {
|
|
470
2284
|
business: {
|
|
471
2285
|
primary: "var(--dsw-static-deepseek-500)",
|
|
@@ -488,8 +2302,14 @@ window.__ModuleLoader__.load({
|
|
|
488
2302
|
tertiary: "var(--dsw-static-amber-100)"
|
|
489
2303
|
}
|
|
490
2304
|
},
|
|
491
|
-
toast: {
|
|
492
|
-
|
|
2305
|
+
toast: {
|
|
2306
|
+
bg: "var(--dsw-static-neutral-bluish-800)",
|
|
2307
|
+
label: "var(--dsw-static-neutral-bluish-00)"
|
|
2308
|
+
},
|
|
2309
|
+
tooltip: {
|
|
2310
|
+
bg: "var(--dsw-static-neutral-bluish-850)",
|
|
2311
|
+
key: { bg: "color-mix(in srgb, var(--dsw-alias-tooltip-bg), white 18%)" }
|
|
2312
|
+
}
|
|
493
2313
|
},
|
|
494
2314
|
corner: { shape: "superellipse(1.5)" },
|
|
495
2315
|
elevation: {
|
|
@@ -501,6 +2321,10 @@ window.__ModuleLoader__.load({
|
|
|
501
2321
|
color: "var(--dsw-alias-border-l4)"
|
|
502
2322
|
}
|
|
503
2323
|
},
|
|
2324
|
+
focus: { ring: {
|
|
2325
|
+
color: "transparent",
|
|
2326
|
+
width: "2px"
|
|
2327
|
+
} },
|
|
504
2328
|
font: {
|
|
505
2329
|
base: {
|
|
506
2330
|
"16": {
|
|
@@ -524,7 +2348,10 @@ window.__ModuleLoader__.load({
|
|
|
524
2348
|
line: { height: "24px" }
|
|
525
2349
|
} }
|
|
526
2350
|
},
|
|
527
|
-
family:
|
|
2351
|
+
family: {
|
|
2352
|
+
$: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',\n 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif",
|
|
2353
|
+
brand: "'Montserrat', var(--dsw-font-family)"
|
|
2354
|
+
},
|
|
528
2355
|
l: { "20": {
|
|
529
2356
|
$: "500 20px/28px var(--dsw-font-family)",
|
|
530
2357
|
font: {
|
|
@@ -816,12 +2643,28 @@ window.__ModuleLoader__.load({
|
|
|
816
2643
|
} }
|
|
817
2644
|
}
|
|
818
2645
|
},
|
|
2646
|
+
gradient: { onboarding: {
|
|
2647
|
+
blue: { stops: "#3964fe 18.75%, #398efe 51.78%, #6dccff 86.252%, #3964fe" },
|
|
2648
|
+
cyan: { stops: "#0293b4 21.154%, #2dc8eb 50.954%, #aff0ff 85.326%, #0293b4" },
|
|
2649
|
+
violet: { stops: "#2a2fb6 33.102%, #8b76f6 50.954%, #c5c0ff 85.326%, #2a2fb6" }
|
|
2650
|
+
} },
|
|
819
2651
|
linear: {
|
|
820
2652
|
gradient: { think: "linear-gradient(180deg, #fff 20.19%, rgba(255, 255, 255, 0) 100%)" },
|
|
821
2653
|
think: { select: "linear-gradient(180deg, #f5f6f7 20.19%, rgba(245, 246, 247, 0) 100%)" }
|
|
822
2654
|
},
|
|
823
|
-
mask: { blur: "
|
|
824
|
-
menu: {
|
|
2655
|
+
mask: { blur: "none" },
|
|
2656
|
+
menu: {
|
|
2657
|
+
backdrop: { filter: "blur(40px) saturate(150%)" },
|
|
2658
|
+
surface: { fill: "rgba(248, 249, 250, 0.58)" }
|
|
2659
|
+
},
|
|
2660
|
+
radius: {
|
|
2661
|
+
lg: "16px",
|
|
2662
|
+
md: "12px",
|
|
2663
|
+
panel: "28px",
|
|
2664
|
+
sm: "8px",
|
|
2665
|
+
xl: "20px",
|
|
2666
|
+
xs: "4px"
|
|
2667
|
+
},
|
|
825
2668
|
shadow: {
|
|
826
2669
|
lv1: {
|
|
827
2670
|
$: "0 2px 4px 0 rgba(0, 0, 0, 0.05)",
|
|
@@ -837,7 +2680,7 @@ window.__ModuleLoader__.load({
|
|
|
837
2680
|
},
|
|
838
2681
|
input: { major: "var(--dsw-static-neutral-bluish-00)" },
|
|
839
2682
|
login: { input: "var(--dsw-static-neutral-bluish-50)" },
|
|
840
|
-
menu: "
|
|
2683
|
+
menu: "var(--dsw-menu-surface-fill)",
|
|
841
2684
|
selector: "var(--dsw-static-neutral-bluish-60)",
|
|
842
2685
|
sidebar: {
|
|
843
2686
|
fill: "var(--dsw-static-neutral-bluish-50)",
|
|
@@ -894,66 +2737,1277 @@ window.__ModuleLoader__.load({
|
|
|
894
2737
|
a08: "rgb(34 197 94 / 8%)",
|
|
895
2738
|
a12: "rgb(34 197 94 / 12%)"
|
|
896
2739
|
},
|
|
897
|
-
"900": "rgb(35, 60, 44)"
|
|
898
|
-
},
|
|
899
|
-
neutral: {
|
|
900
|
-
"50": "rgb(250, 250, 250)",
|
|
901
|
-
"100": "rgb(245, 245, 245)",
|
|
902
|
-
"150": "rgb(237, 237, 237)",
|
|
903
|
-
"200": "rgb(229, 229, 229)",
|
|
904
|
-
"250": "rgb(220, 220, 220)",
|
|
905
|
-
"300": "rgb(212, 212, 212)",
|
|
906
|
-
"400": "rgb(162, 164, 166)",
|
|
907
|
-
"500": "rgb(127, 130, 135)",
|
|
908
|
-
"550": "rgb(101, 103, 107)",
|
|
909
|
-
"600": "rgb(84, 85, 87)",
|
|
910
|
-
"700": "rgb(60, 60, 61)",
|
|
911
|
-
"800": "rgb(41, 41, 41)",
|
|
912
|
-
"850": "rgb(33, 33, 35)",
|
|
913
|
-
"900": "rgb(15, 15, 15)",
|
|
914
|
-
"1000": "rgb(0, 0, 0)",
|
|
915
|
-
"00": "rgb(255, 255, 255)",
|
|
916
|
-
bluish: {
|
|
917
|
-
"50": "rgb(249, 250, 251)",
|
|
918
|
-
"60": "rgb(245, 246, 247)",
|
|
919
|
-
"75": "rgb(241, 243, 245)",
|
|
920
|
-
"100": "rgb(235, 238, 242)",
|
|
921
|
-
"150": "rgb(233, 236, 242)",
|
|
922
|
-
"200": "rgb(225, 229, 238)",
|
|
923
|
-
"300": "rgb(207, 211, 214)",
|
|
924
|
-
"400": "rgb(173, 178, 184)",
|
|
925
|
-
"500": "rgb(151, 157, 166)",
|
|
926
|
-
"600": "rgb(129, 133, 140)",
|
|
927
|
-
"700": "rgb(97, 102, 107)",
|
|
928
|
-
"750": "rgb(67, 69, 74)",
|
|
929
|
-
"800": "rgb(53, 54, 56)",
|
|
930
|
-
"850": "rgb(44, 44, 46)",
|
|
931
|
-
"875": "rgb(35, 35, 36)",
|
|
932
|
-
"900": "rgb(27, 27, 28)",
|
|
933
|
-
"950": "rgb(21, 21, 23)",
|
|
934
|
-
"1000": "rgb(15, 17, 21)",
|
|
935
|
-
"00": "rgb(255, 255, 255)"
|
|
2740
|
+
"900": "rgb(35, 60, 44)"
|
|
2741
|
+
},
|
|
2742
|
+
neutral: {
|
|
2743
|
+
"50": "rgb(250, 250, 250)",
|
|
2744
|
+
"100": "rgb(245, 245, 245)",
|
|
2745
|
+
"150": "rgb(237, 237, 237)",
|
|
2746
|
+
"200": "rgb(229, 229, 229)",
|
|
2747
|
+
"250": "rgb(220, 220, 220)",
|
|
2748
|
+
"300": "rgb(212, 212, 212)",
|
|
2749
|
+
"400": "rgb(162, 164, 166)",
|
|
2750
|
+
"500": "rgb(127, 130, 135)",
|
|
2751
|
+
"550": "rgb(101, 103, 107)",
|
|
2752
|
+
"600": "rgb(84, 85, 87)",
|
|
2753
|
+
"700": "rgb(60, 60, 61)",
|
|
2754
|
+
"800": "rgb(41, 41, 41)",
|
|
2755
|
+
"850": "rgb(33, 33, 35)",
|
|
2756
|
+
"900": "rgb(15, 15, 15)",
|
|
2757
|
+
"1000": "rgb(0, 0, 0)",
|
|
2758
|
+
"00": "rgb(255, 255, 255)",
|
|
2759
|
+
bluish: {
|
|
2760
|
+
"50": "rgb(249, 250, 251)",
|
|
2761
|
+
"60": "rgb(245, 246, 247)",
|
|
2762
|
+
"75": "rgb(241, 243, 245)",
|
|
2763
|
+
"100": "rgb(235, 238, 242)",
|
|
2764
|
+
"150": "rgb(233, 236, 242)",
|
|
2765
|
+
"200": "rgb(225, 229, 238)",
|
|
2766
|
+
"300": "rgb(207, 211, 214)",
|
|
2767
|
+
"400": "rgb(173, 178, 184)",
|
|
2768
|
+
"500": "rgb(151, 157, 166)",
|
|
2769
|
+
"600": "rgb(129, 133, 140)",
|
|
2770
|
+
"700": "rgb(97, 102, 107)",
|
|
2771
|
+
"750": "rgb(67, 69, 74)",
|
|
2772
|
+
"800": "rgb(53, 54, 56)",
|
|
2773
|
+
"850": "rgb(44, 44, 46)",
|
|
2774
|
+
"875": "rgb(35, 35, 36)",
|
|
2775
|
+
"900": "rgb(27, 27, 28)",
|
|
2776
|
+
"950": "rgb(21, 21, 23)",
|
|
2777
|
+
"1000": "rgb(15, 17, 21)",
|
|
2778
|
+
"00": "rgb(255, 255, 255)"
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
red: {
|
|
2782
|
+
"50": "rgb(254, 242, 242)",
|
|
2783
|
+
"100": "rgb(254, 226, 226)",
|
|
2784
|
+
"400": {
|
|
2785
|
+
$: "rgb(242, 90, 90)",
|
|
2786
|
+
a12: "rgb(242 90 90 / 12%)"
|
|
2787
|
+
},
|
|
2788
|
+
"500": "rgb(239, 68, 68)",
|
|
2789
|
+
"600": {
|
|
2790
|
+
$: "rgb(236, 19, 19)",
|
|
2791
|
+
a08: "rgb(236 19 19 / 8%)"
|
|
2792
|
+
},
|
|
2793
|
+
"900": "rgb(87, 12, 12)"
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
};
|
|
2797
|
+
//#endregion
|
|
2798
|
+
//#region src/client/theme.ts
|
|
2799
|
+
const dsw = Token.vars(designTokens, { prefix: "dsw" });
|
|
2800
|
+
//#endregion
|
|
2801
|
+
//#region src/client/schema-form/styles.ts
|
|
2802
|
+
/**
|
|
2803
|
+
* 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
|
|
2804
|
+
*
|
|
2805
|
+
* 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
|
|
2806
|
+
*/
|
|
2807
|
+
/** 等宽字体栈:配置值的观感与代码一致。 */
|
|
2808
|
+
const MONO = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
|
|
2809
|
+
/** 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。 */
|
|
2810
|
+
const ROW = "22px";
|
|
2811
|
+
/** 行内元素之间的间距。 */
|
|
2812
|
+
const GAP = "6px";
|
|
2813
|
+
/** 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。 */
|
|
2814
|
+
const ACTION = "20px";
|
|
2815
|
+
/** 动作按钮之间的间距(比行内间距紧一档)。 */
|
|
2816
|
+
const ACTION_GAP = "4px";
|
|
2817
|
+
/** 折叠列(chevron 与它的占位)宽度。 */
|
|
2818
|
+
const FOLD = "14px";
|
|
2819
|
+
/** 行号列宽度。 */
|
|
2820
|
+
const NUMBER = "32px";
|
|
2821
|
+
/** 行尾留白。 */
|
|
2822
|
+
const PAD = "8px";
|
|
2823
|
+
/** 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。 */
|
|
2824
|
+
const HAIRLINE = dsw.elevation.stroke;
|
|
2825
|
+
/** 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。 */
|
|
2826
|
+
const LINE = dsw.elevation.stroke.color;
|
|
2827
|
+
/** 行左侧那道「改过」的标记宽度。 */
|
|
2828
|
+
const EDGE = "2px";
|
|
2829
|
+
/** 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。 */
|
|
2830
|
+
const ERROR_WASH = `color-mix(in srgb, ${String(dsw.alias.state.error.secondary)} 12%, transparent)`;
|
|
2831
|
+
/** 编辑器的根:纵向排列的行。 */
|
|
2832
|
+
const EditorRoot = styled("div")({
|
|
2833
|
+
display: "flex",
|
|
2834
|
+
flexDirection: "column",
|
|
2835
|
+
width: "100%",
|
|
2836
|
+
fontFamily: MONO,
|
|
2837
|
+
fontSize: "14px",
|
|
2838
|
+
lineHeight: ROW,
|
|
2839
|
+
color: dsw.alias.label.primary
|
|
2840
|
+
});
|
|
2841
|
+
/** 一行:行号 + 折进 + 行内容。悬停给一层浅底。 */
|
|
2842
|
+
const LineRow = styled("div")({
|
|
2843
|
+
display: "flex",
|
|
2844
|
+
alignItems: "flex-start",
|
|
2845
|
+
gap: GAP,
|
|
2846
|
+
minHeight: ROW,
|
|
2847
|
+
paddingRight: PAD,
|
|
2848
|
+
whiteSpace: "nowrap",
|
|
2849
|
+
"&:hover": { background: dsw.alias.bg.layer["2"] },
|
|
2850
|
+
"&[data-selected='true']": { background: dsw.alias.bg.layer["3"] },
|
|
2851
|
+
"&[data-dirty='true']": { boxShadow: `inset ${EDGE} 0 0 ${String(dsw.alias.brand.primary)}` },
|
|
2852
|
+
"&[data-overridden='true']": { boxShadow: `inset ${EDGE} 0 0 ${String(LINE)}` },
|
|
2853
|
+
"&[data-dirty='true'] [data-role='key']": { color: dsw.alias.brand.primary },
|
|
2854
|
+
"&[data-overridden='true'] [data-role='key']": { color: dsw.alias.brand.primary },
|
|
2855
|
+
"&[data-invalid='true']": {
|
|
2856
|
+
background: ERROR_WASH,
|
|
2857
|
+
boxShadow: `inset ${EDGE} 0 0 ${String(dsw.alias.state.error.primary)}`
|
|
2858
|
+
},
|
|
2859
|
+
"&[data-invalid='true'] [data-role='key']": { color: dsw.alias.state.error.primary },
|
|
2860
|
+
"&:hover [data-role='actions']": { opacity: 1 }
|
|
2861
|
+
});
|
|
2862
|
+
/** 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。 */
|
|
2863
|
+
const LineBody = styled("div")({
|
|
2864
|
+
display: "flex",
|
|
2865
|
+
alignItems: "center",
|
|
2866
|
+
gap: GAP,
|
|
2867
|
+
flex: 1,
|
|
2868
|
+
minWidth: 0,
|
|
2869
|
+
minHeight: ROW
|
|
2870
|
+
});
|
|
2871
|
+
/** 行号列:右对齐、不可选、淡色。 */
|
|
2872
|
+
const LineNumber = styled("span")({
|
|
2873
|
+
flex: "none",
|
|
2874
|
+
width: NUMBER,
|
|
2875
|
+
lineHeight: ROW,
|
|
2876
|
+
textAlign: "right",
|
|
2877
|
+
color: dsw.alias.label.caption,
|
|
2878
|
+
userSelect: "none",
|
|
2879
|
+
cursor: "pointer"
|
|
2880
|
+
});
|
|
2881
|
+
/** 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。 */
|
|
2882
|
+
const LineFold = styled("button")({
|
|
2883
|
+
display: "inline-flex",
|
|
2884
|
+
alignItems: "center",
|
|
2885
|
+
justifyContent: "center",
|
|
2886
|
+
flex: "none",
|
|
2887
|
+
width: FOLD,
|
|
2888
|
+
height: ROW,
|
|
2889
|
+
padding: 0,
|
|
2890
|
+
border: 0,
|
|
2891
|
+
background: "none",
|
|
2892
|
+
color: dsw.alias.label.caption,
|
|
2893
|
+
cursor: "pointer",
|
|
2894
|
+
"&:hover": { color: dsw.alias.label.secondary }
|
|
2895
|
+
});
|
|
2896
|
+
/** 叶子行的折叠占位(保持列对齐)。 */
|
|
2897
|
+
const LineFoldSpacer = styled("span")({
|
|
2898
|
+
flex: "none",
|
|
2899
|
+
width: FOLD
|
|
2900
|
+
});
|
|
2901
|
+
/** 键名(配置键原样;数组下标淡一些)。 */
|
|
2902
|
+
const LineKey = styled("span")({
|
|
2903
|
+
flex: "none",
|
|
2904
|
+
color: dsw.alias.label.primary,
|
|
2905
|
+
"&[data-index='true']": { color: dsw.alias.label.caption }
|
|
2906
|
+
});
|
|
2907
|
+
/** 结构符与标点(`{` `}` `[` `]` `:`)。 */
|
|
2908
|
+
const LineToken = styled("span")({
|
|
2909
|
+
flex: "none",
|
|
2910
|
+
color: dsw.alias.label.caption
|
|
2911
|
+
});
|
|
2912
|
+
/** 值(token):字符串 / 数字 / 布尔各有语法色。 */
|
|
2913
|
+
const LineValue = styled("span")({
|
|
2914
|
+
minWidth: 0,
|
|
2915
|
+
overflow: "hidden",
|
|
2916
|
+
textOverflow: "ellipsis",
|
|
2917
|
+
cursor: "text",
|
|
2918
|
+
color: dsw.alias.state.business.primary,
|
|
2919
|
+
"&[data-tone='number']": { color: dsw.alias.state.warn.primary },
|
|
2920
|
+
"&[data-tone='boolean']": { color: dsw.alias.state.success.primary },
|
|
2921
|
+
"&[data-tone='empty']": { color: dsw.alias.label.caption }
|
|
2922
|
+
});
|
|
2923
|
+
/**
|
|
2924
|
+
* 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
|
|
2925
|
+
*/
|
|
2926
|
+
const ValueTrigger = styled("button")({
|
|
2927
|
+
display: "inline-flex",
|
|
2928
|
+
alignItems: "center",
|
|
2929
|
+
gap: "2px",
|
|
2930
|
+
minWidth: 0,
|
|
2931
|
+
padding: 0,
|
|
2932
|
+
border: 0,
|
|
2933
|
+
background: "none",
|
|
2934
|
+
font: "inherit",
|
|
2935
|
+
textAlign: "left",
|
|
2936
|
+
cursor: "pointer",
|
|
2937
|
+
color: dsw.alias.label.caption,
|
|
2938
|
+
"&:disabled": { cursor: "default" },
|
|
2939
|
+
"&:focus-visible": { outline: "none" }
|
|
2940
|
+
});
|
|
2941
|
+
/**
|
|
2942
|
+
* 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
|
|
2943
|
+
*
|
|
2944
|
+
* 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
|
|
2945
|
+
*/
|
|
2946
|
+
const CompactInput = styled("span")({
|
|
2947
|
+
display: "inline-flex",
|
|
2948
|
+
flex: "1 1 auto",
|
|
2949
|
+
minWidth: 0,
|
|
2950
|
+
"& > span": {
|
|
2951
|
+
width: "100%",
|
|
2952
|
+
height: ROW,
|
|
2953
|
+
padding: "0 6px",
|
|
2954
|
+
borderRadius: "4px"
|
|
2955
|
+
},
|
|
2956
|
+
"& input": {
|
|
2957
|
+
fontSize: "14px",
|
|
2958
|
+
lineHeight: ROW
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2961
|
+
/**
|
|
2962
|
+
* 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
|
|
2963
|
+
* 高度跟着内容长、宽度同样撑满剩下的位置。
|
|
2964
|
+
*/
|
|
2965
|
+
const CompactTextField = styled("span")({
|
|
2966
|
+
display: "inline-flex",
|
|
2967
|
+
flex: "1 1 auto",
|
|
2968
|
+
minWidth: 0,
|
|
2969
|
+
padding: "0 6px",
|
|
2970
|
+
borderRadius: "4px",
|
|
2971
|
+
background: dsw.alias.bg.layer["1"],
|
|
2972
|
+
border: 0,
|
|
2973
|
+
boxShadow: HAIRLINE,
|
|
2974
|
+
"&:focus-within": { "--dsw-elevation-stroke-color": dsw.alias.brand.primary },
|
|
2975
|
+
"& > textarea": {
|
|
2976
|
+
flex: 1,
|
|
2977
|
+
width: "100%",
|
|
2978
|
+
minHeight: ACTION,
|
|
2979
|
+
maxHeight: "240px",
|
|
2980
|
+
padding: 0,
|
|
2981
|
+
border: 0,
|
|
2982
|
+
outline: "none",
|
|
2983
|
+
background: "transparent",
|
|
2984
|
+
resize: "none",
|
|
2985
|
+
fieldSizing: "content",
|
|
2986
|
+
fontFamily: "inherit",
|
|
2987
|
+
fontSize: "14px",
|
|
2988
|
+
lineHeight: ROW,
|
|
2989
|
+
color: dsw.alias.label.primary
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2992
|
+
/** 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。 */
|
|
2993
|
+
const LineComment = styled("span")({
|
|
2994
|
+
flex: "1 1 auto",
|
|
2995
|
+
minWidth: 0,
|
|
2996
|
+
overflow: "hidden",
|
|
2997
|
+
textOverflow: "ellipsis",
|
|
2998
|
+
color: dsw.alias.label.tertiary,
|
|
2999
|
+
fontFamily: "inherit",
|
|
3000
|
+
fontSize: "12px"
|
|
3001
|
+
});
|
|
3002
|
+
/** 行内的校验消息(与注释放同一档字号)。 */
|
|
3003
|
+
const LineInvalid = styled("span")({
|
|
3004
|
+
flex: "none",
|
|
3005
|
+
color: dsw.alias.state.error.primary,
|
|
3006
|
+
fontFamily: "inherit",
|
|
3007
|
+
fontSize: "12px"
|
|
3008
|
+
});
|
|
3009
|
+
/**
|
|
3010
|
+
* 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
|
|
3011
|
+
*/
|
|
3012
|
+
const HoverActions = styled(styled("span")({
|
|
3013
|
+
display: "inline-flex",
|
|
3014
|
+
alignItems: "center",
|
|
3015
|
+
gap: ACTION_GAP,
|
|
3016
|
+
flex: "none",
|
|
3017
|
+
opacity: 0,
|
|
3018
|
+
"& > button": {
|
|
3019
|
+
display: "inline-flex",
|
|
3020
|
+
alignItems: "center",
|
|
3021
|
+
justifyContent: "center",
|
|
3022
|
+
width: ACTION,
|
|
3023
|
+
height: ACTION,
|
|
3024
|
+
padding: 0,
|
|
3025
|
+
border: 0,
|
|
3026
|
+
borderRadius: "4px",
|
|
3027
|
+
background: "none",
|
|
3028
|
+
color: dsw.alias.label.secondary,
|
|
3029
|
+
cursor: "pointer",
|
|
3030
|
+
"&:hover": {
|
|
3031
|
+
background: dsw.alias.bg.layer["3"],
|
|
3032
|
+
color: dsw.alias.label.primary
|
|
3033
|
+
},
|
|
3034
|
+
"&:disabled": {
|
|
3035
|
+
cursor: "not-allowed",
|
|
3036
|
+
color: dsw.alias.label.dimmed
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
}))({
|
|
3040
|
+
/**
|
|
3041
|
+
* 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
|
|
3042
|
+
* 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
|
|
3043
|
+
*/
|
|
3044
|
+
"&[data-editing='true']": {
|
|
3045
|
+
opacity: 1,
|
|
3046
|
+
gap: GAP,
|
|
3047
|
+
"& > button": {
|
|
3048
|
+
boxShadow: HAIRLINE,
|
|
3049
|
+
background: dsw.alias.bg.layer["1"],
|
|
3050
|
+
color: dsw.alias.label.primary,
|
|
3051
|
+
"&:first-of-type": { color: dsw.alias.state.success.primary }
|
|
3052
|
+
}
|
|
3053
|
+
} });
|
|
3054
|
+
/** 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。 */
|
|
3055
|
+
const AddWrap = styled("span")({
|
|
3056
|
+
position: "relative",
|
|
3057
|
+
display: "inline-flex",
|
|
3058
|
+
alignItems: "center",
|
|
3059
|
+
gap: GAP,
|
|
3060
|
+
flex: "1 1 auto",
|
|
3061
|
+
minWidth: 0,
|
|
3062
|
+
marginLeft: GAP,
|
|
3063
|
+
"&[data-invalid='true'] input": { borderColor: dsw.alias.state.error.primary }
|
|
3064
|
+
});
|
|
3065
|
+
/** 一行说明(不可用 / 没有可生成项时用)。 */
|
|
3066
|
+
const Hint$1 = styled("p")({
|
|
3067
|
+
margin: 0,
|
|
3068
|
+
fontSize: "12px",
|
|
3069
|
+
lineHeight: 1.5,
|
|
3070
|
+
color: dsw.alias.label.tertiary
|
|
3071
|
+
});
|
|
3072
|
+
//#endregion
|
|
3073
|
+
//#region src/client/schema-form/value.tsx
|
|
3074
|
+
/**
|
|
3075
|
+
* 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
|
|
3076
|
+
*
|
|
3077
|
+
* 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
|
|
3078
|
+
* 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
|
|
3079
|
+
*/
|
|
3080
|
+
/** 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。 */
|
|
3081
|
+
function valueText(value) {
|
|
3082
|
+
if (value === void 0 || value === null) return "";
|
|
3083
|
+
if (typeof value === "string") return value;
|
|
3084
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3085
|
+
return JSON.stringify(value);
|
|
3086
|
+
}
|
|
3087
|
+
/** 这个值要在多行输入里编辑吗(字符串带换行)。 */
|
|
3088
|
+
function isMultiline(value) {
|
|
3089
|
+
return typeof value === "string" && /[\r\n]/.test(value);
|
|
3090
|
+
}
|
|
3091
|
+
/** 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。 */
|
|
3092
|
+
function tokenText(value) {
|
|
3093
|
+
if (value === void 0 || value === null) return "null";
|
|
3094
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
3095
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3096
|
+
return JSON.stringify(value);
|
|
3097
|
+
}
|
|
3098
|
+
/** 值的语法色分组。 */
|
|
3099
|
+
function tokenTone(value) {
|
|
3100
|
+
if (value === void 0 || value === null) return "empty";
|
|
3101
|
+
if (typeof value === "number") return "number";
|
|
3102
|
+
if (typeof value === "boolean") return "boolean";
|
|
3103
|
+
return "string";
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* 行内值的默认呈现(显示态 token)。
|
|
3107
|
+
* @param props.owner - 字段上下文(值、是否 secret、只读原因)。
|
|
3108
|
+
* @returns 一行里的值节点。
|
|
3109
|
+
*/
|
|
3110
|
+
function InlineValue({ owner }) {
|
|
3111
|
+
if (owner.options !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
|
|
3112
|
+
owner,
|
|
3113
|
+
options: owner.options
|
|
3114
|
+
});
|
|
3115
|
+
if (owner.node.meta.secret) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
|
|
3116
|
+
"data-tone": "empty",
|
|
3117
|
+
title: owner.hint,
|
|
3118
|
+
children: owner.secretConfigured ? "••••••" : ""
|
|
3119
|
+
});
|
|
3120
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
|
|
3121
|
+
"data-tone": owner.node.readOnly === null ? tokenTone(owner.value) : "empty",
|
|
3122
|
+
title: tokenText(owner.value),
|
|
3123
|
+
children: tokenText(owner.value)
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
|
|
3128
|
+
*
|
|
3129
|
+
* 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
|
|
3130
|
+
*/
|
|
3131
|
+
function OptionSelect({ owner, options }) {
|
|
3132
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3133
|
+
const selected = options.findIndex((option) => sameValue(option.value, owner.value));
|
|
3134
|
+
const current = selected < 0 ? void 0 : options[selected];
|
|
3135
|
+
const label = current?.label ?? (owner.value === void 0 ? owner.t("unconfigured") : tokenText(current?.value ?? owner.value));
|
|
3136
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3137
|
+
open,
|
|
3138
|
+
dense: true,
|
|
3139
|
+
compact: true,
|
|
3140
|
+
portal: true,
|
|
3141
|
+
align: "start",
|
|
3142
|
+
items: options.map((option, index) => ({
|
|
3143
|
+
id: String(index),
|
|
3144
|
+
label: option.label ?? tokenText(option.value)
|
|
3145
|
+
})),
|
|
3146
|
+
selectedId: selected < 0 ? void 0 : String(selected),
|
|
3147
|
+
onSelect: (id) => {
|
|
3148
|
+
setOpen(false);
|
|
3149
|
+
const option = options[Number(id)];
|
|
3150
|
+
if (option !== void 0) owner.onChange(option.value);
|
|
3151
|
+
},
|
|
3152
|
+
onClose: () => {
|
|
3153
|
+
setOpen(false);
|
|
3154
|
+
},
|
|
3155
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTrigger, {
|
|
3156
|
+
type: "button",
|
|
3157
|
+
disabled: owner.disabled,
|
|
3158
|
+
"aria-label": owner.label,
|
|
3159
|
+
onClick: () => {
|
|
3160
|
+
setOpen(true);
|
|
3161
|
+
},
|
|
3162
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
|
|
3163
|
+
"data-tone": current === void 0 ? tokenTone(owner.value) : tokenTone(current.value),
|
|
3164
|
+
children: label
|
|
3165
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })]
|
|
3166
|
+
})
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
/** 值是否就是这一项(菜单的高亮判据)。 */
|
|
3170
|
+
function sameValue(left, right) {
|
|
3171
|
+
if (Object.is(left, right)) return true;
|
|
3172
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
3173
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
|
|
3177
|
+
*
|
|
3178
|
+
* 注意行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
|
|
3179
|
+
*/
|
|
3180
|
+
function SchemaFieldDefault({ owner }) {
|
|
3181
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineValue, { owner });
|
|
3182
|
+
}
|
|
3183
|
+
//#endregion
|
|
3184
|
+
//#region src/client/schema-form/fields.tsx
|
|
3185
|
+
/**
|
|
3186
|
+
* 各类型的文本解析规则(消息来自本包字典)。
|
|
3187
|
+
*
|
|
3188
|
+
* 解析不只看类型:schema 上声明的界限(`pattern` / `min` / `max` / `step`)与字面量集合都在这里说话——
|
|
3189
|
+
* 用户敲完当场就知道对不对,而不是等保存时被整段校验退回。
|
|
3190
|
+
* @param node - 字段节点(`meta` 是约束的来源)。
|
|
3191
|
+
* @param t - 本包字典。
|
|
3192
|
+
* @returns 这段文本的解析规则。
|
|
3193
|
+
*/
|
|
3194
|
+
function parseFor(node, t) {
|
|
3195
|
+
switch (node.type) {
|
|
3196
|
+
case "number": return (text) => {
|
|
3197
|
+
const trimmed = text.trim();
|
|
3198
|
+
const value = Number(trimmed);
|
|
3199
|
+
if (trimmed === "" || !Number.isFinite(value)) return {
|
|
3200
|
+
kind: "invalid",
|
|
3201
|
+
message: t("invalidNumber")
|
|
3202
|
+
};
|
|
3203
|
+
if (node.meta.step === 1 && !Number.isInteger(value)) return {
|
|
3204
|
+
kind: "invalid",
|
|
3205
|
+
message: t("invalidInteger")
|
|
3206
|
+
};
|
|
3207
|
+
const { min, max } = node.meta;
|
|
3208
|
+
if (min !== void 0 && max !== void 0 && (value < min || value > max)) return {
|
|
3209
|
+
kind: "invalid",
|
|
3210
|
+
message: t("invalidRange", {
|
|
3211
|
+
min,
|
|
3212
|
+
max
|
|
3213
|
+
})
|
|
3214
|
+
};
|
|
3215
|
+
if (min !== void 0 && value < min) return {
|
|
3216
|
+
kind: "invalid",
|
|
3217
|
+
message: t("invalidMin", { min })
|
|
3218
|
+
};
|
|
3219
|
+
if (max !== void 0 && value > max) return {
|
|
3220
|
+
kind: "invalid",
|
|
3221
|
+
message: t("invalidMax", { max })
|
|
3222
|
+
};
|
|
3223
|
+
return {
|
|
3224
|
+
kind: "value",
|
|
3225
|
+
value
|
|
3226
|
+
};
|
|
3227
|
+
};
|
|
3228
|
+
case "string": {
|
|
3229
|
+
const pattern = node.meta.pattern;
|
|
3230
|
+
if (pattern === void 0) return (text) => ({
|
|
3231
|
+
kind: "value",
|
|
3232
|
+
value: text
|
|
3233
|
+
});
|
|
3234
|
+
return (text) => new RegExp(pattern).test(text) ? {
|
|
3235
|
+
kind: "value",
|
|
3236
|
+
value: text
|
|
3237
|
+
} : {
|
|
3238
|
+
kind: "invalid",
|
|
3239
|
+
message: t("invalidPattern", { pattern })
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
case "boolean": return (text) => {
|
|
3243
|
+
const trimmed = text.trim();
|
|
3244
|
+
if (trimmed === "true") return {
|
|
3245
|
+
kind: "value",
|
|
3246
|
+
value: true
|
|
3247
|
+
};
|
|
3248
|
+
if (trimmed === "false") return {
|
|
3249
|
+
kind: "value",
|
|
3250
|
+
value: false
|
|
3251
|
+
};
|
|
3252
|
+
return {
|
|
3253
|
+
kind: "invalid",
|
|
3254
|
+
message: t("invalidBoolean")
|
|
3255
|
+
};
|
|
3256
|
+
};
|
|
3257
|
+
case "const": return (text) => text === String(node.type === "const" ? node.value : "") || text.trim() === JSON.stringify(node.value) ? {
|
|
3258
|
+
kind: "value",
|
|
3259
|
+
value: node.value
|
|
3260
|
+
} : {
|
|
3261
|
+
kind: "invalid",
|
|
3262
|
+
message: t("invalidConst")
|
|
3263
|
+
};
|
|
3264
|
+
case "bitset": return (text) => {
|
|
3265
|
+
const value = Number(text.trim());
|
|
3266
|
+
return Number.isFinite(value) ? {
|
|
3267
|
+
kind: "value",
|
|
3268
|
+
value
|
|
3269
|
+
} : {
|
|
3270
|
+
kind: "invalid",
|
|
3271
|
+
message: t("invalidNumber")
|
|
3272
|
+
};
|
|
3273
|
+
};
|
|
3274
|
+
case "any": return (text) => {
|
|
3275
|
+
try {
|
|
3276
|
+
return {
|
|
3277
|
+
kind: "value",
|
|
3278
|
+
value: JSON.parse(text)
|
|
3279
|
+
};
|
|
3280
|
+
} catch {
|
|
3281
|
+
return {
|
|
3282
|
+
kind: "invalid",
|
|
3283
|
+
message: t("invalidJson")
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
default:
|
|
3288
|
+
if (node.type === "union" && node.choices !== void 0) {
|
|
3289
|
+
const choices = node.choices;
|
|
3290
|
+
const options = choices.map((choice) => String(choice)).join(" / ");
|
|
3291
|
+
return (text) => choices.some((choice) => String(choice) === text) ? {
|
|
3292
|
+
kind: "value",
|
|
3293
|
+
value: text
|
|
3294
|
+
} : {
|
|
3295
|
+
kind: "invalid",
|
|
3296
|
+
message: t("invalidChoice", { choices: options })
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
return (text) => ({
|
|
3300
|
+
kind: "value",
|
|
3301
|
+
value: text
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
//#endregion
|
|
3306
|
+
//#region src/client/schema-form/Editor.tsx
|
|
3307
|
+
/**
|
|
3308
|
+
* 行式编辑器:按 `editorLines` 排出的行画 JSON 结构视图——行号、折进、`key: value`、结构行、注释行、容器闭合行
|
|
3309
|
+
* 后面的添加入口;值在行内点开编辑(Enter 提交 / Esc 撤销)。
|
|
3310
|
+
*
|
|
3311
|
+
* 缩进只作用在**行内容**上(行号列固定),添加输入与闭合括号同一行;行内控件用官方的 `Input` / `Menu` / `Button`,
|
|
3312
|
+
* 与设置页其余部分同一套视觉与键盘行为。
|
|
3313
|
+
*
|
|
3314
|
+
* 字段槽仍然生效:命中时该字段**值**的位置换成注册方的组件,键名、注释、行号与行为按钮仍由这里画。
|
|
3315
|
+
*/
|
|
3316
|
+
/**
|
|
3317
|
+
* 画一行的配置。
|
|
3318
|
+
* @param props - 读数、动作、字典与字段槽入口。
|
|
3319
|
+
* @returns 行式编辑器。
|
|
3320
|
+
*/
|
|
3321
|
+
function Editor(props) {
|
|
3322
|
+
const { ns, state, face, t, resolveText, renderField } = props;
|
|
3323
|
+
const [toggled, setToggled] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
3324
|
+
const [editing, setEditing] = (0, react.useState)(void 0);
|
|
3325
|
+
const [selected, setSelected] = (0, react.useState)(void 0);
|
|
3326
|
+
const [dismissed, setDismissed] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
3327
|
+
const fold = useFold(state, toggled, setToggled);
|
|
3328
|
+
const lines = (0, react.useMemo)(() => editorLines(state, fold, resolveText, t), [
|
|
3329
|
+
state,
|
|
3330
|
+
fold,
|
|
3331
|
+
resolveText,
|
|
3332
|
+
t
|
|
3333
|
+
]);
|
|
3334
|
+
const disabled = !state.writable || !state.available;
|
|
3335
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditorRoot, {
|
|
3336
|
+
"data-editor": "schema-form",
|
|
3337
|
+
children: lines.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
3338
|
+
line,
|
|
3339
|
+
n: index + 1,
|
|
3340
|
+
ns,
|
|
3341
|
+
state,
|
|
3342
|
+
face,
|
|
3343
|
+
fold,
|
|
3344
|
+
t,
|
|
3345
|
+
resolveText,
|
|
3346
|
+
disabled,
|
|
3347
|
+
editing,
|
|
3348
|
+
selected,
|
|
3349
|
+
onEdit: (key) => {
|
|
3350
|
+
setEditing(key);
|
|
3351
|
+
if (key !== void 0) setDismissed((previous) => {
|
|
3352
|
+
const next = new Set(previous);
|
|
3353
|
+
next.delete(key);
|
|
3354
|
+
return next;
|
|
3355
|
+
});
|
|
3356
|
+
},
|
|
3357
|
+
onAdded: (path) => {
|
|
3358
|
+
setEditing(fieldKey(path));
|
|
3359
|
+
},
|
|
3360
|
+
dismissed,
|
|
3361
|
+
onDismiss: (key) => {
|
|
3362
|
+
setDismissed((previous) => new Set(previous).add(key));
|
|
3363
|
+
},
|
|
3364
|
+
onSelect: (path) => {
|
|
3365
|
+
setSelected(fieldKey(path));
|
|
3366
|
+
},
|
|
3367
|
+
renderField
|
|
3368
|
+
}, `${line.kind}:${fieldKey(line.path)}:${String(index)}`))
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
/** 折叠状态:记「用户切换过的路径」,开合 = 切换过就反转 schema 的默认值。 */
|
|
3372
|
+
function useFold(state, toggled, setToggled) {
|
|
3373
|
+
const defaults = (0, react.useMemo)(() => {
|
|
3374
|
+
const collapsed = /* @__PURE__ */ new Set();
|
|
3375
|
+
for (const item of state.walked) {
|
|
3376
|
+
const value = state.fields.get(fieldKey(item.path))?.value;
|
|
3377
|
+
if (item.node.meta.collapse && containerShape(item.node, value) !== void 0) collapsed.add(fieldKey(item.path));
|
|
3378
|
+
}
|
|
3379
|
+
return collapsed;
|
|
3380
|
+
}, [state.walked, state.fields]);
|
|
3381
|
+
return (0, react.useMemo)(() => ({
|
|
3382
|
+
collapsed: (path) => {
|
|
3383
|
+
const key = fieldKey(path);
|
|
3384
|
+
return toggled.has(key) !== defaults.has(key);
|
|
3385
|
+
},
|
|
3386
|
+
toggle: (path) => {
|
|
3387
|
+
const key = fieldKey(path);
|
|
3388
|
+
const next = new Set(toggled);
|
|
3389
|
+
if (next.has(key)) next.delete(key);
|
|
3390
|
+
else next.add(key);
|
|
3391
|
+
setToggled(next);
|
|
3392
|
+
}
|
|
3393
|
+
}), [
|
|
3394
|
+
defaults,
|
|
3395
|
+
setToggled,
|
|
3396
|
+
toggled
|
|
3397
|
+
]);
|
|
3398
|
+
}
|
|
3399
|
+
/** 一行的渲染分派。 */
|
|
3400
|
+
function Row({ line, n, ns, state, face, fold, t, resolveText, disabled, editing, selected, dismissed, onEdit, onAdded, onDismiss, onSelect, renderField }) {
|
|
3401
|
+
const key = fieldKey(line.path);
|
|
3402
|
+
const indent = { paddingLeft: `${String(line.depth * 14)}px` };
|
|
3403
|
+
const body = line.kind === "comment" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }), line.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineInvalid, {
|
|
3404
|
+
title: line.text,
|
|
3405
|
+
children: `// ${line.text}`
|
|
3406
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineComment, {
|
|
3407
|
+
title: line.text,
|
|
3408
|
+
children: `// ${line.text}`
|
|
3409
|
+
})] }) : line.kind === "open" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3410
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFold, {
|
|
3411
|
+
"data-role": "fold",
|
|
3412
|
+
type: "button",
|
|
3413
|
+
"aria-expanded": !line.collapsed,
|
|
3414
|
+
"aria-label": t("toggleGroup"),
|
|
3415
|
+
onClick: () => {
|
|
3416
|
+
fold.toggle(line.path);
|
|
3417
|
+
},
|
|
3418
|
+
children: line.collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })
|
|
3419
|
+
}),
|
|
3420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinePrefix, {
|
|
3421
|
+
line,
|
|
3422
|
+
face,
|
|
3423
|
+
t,
|
|
3424
|
+
disabled
|
|
3425
|
+
}),
|
|
3426
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: line.shape === "object" ? line.collapsed ? "{…}" : "{" : line.collapsed ? "[…]" : "[" }),
|
|
3427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VariantSelect, {
|
|
3428
|
+
control: line.variants,
|
|
3429
|
+
path: line.path,
|
|
3430
|
+
face,
|
|
3431
|
+
disabled,
|
|
3432
|
+
t
|
|
3433
|
+
})
|
|
3434
|
+
] }) : line.kind === "close" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }),
|
|
3436
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: line.shape === "object" ? "}" : "]" }),
|
|
3437
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddLine, {
|
|
3438
|
+
line,
|
|
3439
|
+
state,
|
|
3440
|
+
face,
|
|
3441
|
+
t,
|
|
3442
|
+
resolveText,
|
|
3443
|
+
disabled,
|
|
3444
|
+
onAdded
|
|
3445
|
+
})
|
|
3446
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldLine, {
|
|
3447
|
+
line,
|
|
3448
|
+
ns,
|
|
3449
|
+
state,
|
|
3450
|
+
face,
|
|
3451
|
+
t,
|
|
3452
|
+
disabled,
|
|
3453
|
+
editing,
|
|
3454
|
+
dismissed,
|
|
3455
|
+
onEdit,
|
|
3456
|
+
onDismiss,
|
|
3457
|
+
renderField
|
|
3458
|
+
});
|
|
3459
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LineRow, {
|
|
3460
|
+
"data-line": line.kind,
|
|
3461
|
+
"data-field-path": line.path.join("."),
|
|
3462
|
+
"data-selected": selected === key ? "true" : void 0,
|
|
3463
|
+
"data-dirty": line.kind === "field" && line.field.staged ? "true" : void 0,
|
|
3464
|
+
"data-overridden": line.kind === "field" && line.field.overridden && !line.field.staged ? "true" : void 0,
|
|
3465
|
+
"data-invalid": line.kind === "field" && (line.field.invalid !== void 0 || state.invalidAt.has(key)) ? "true" : void 0,
|
|
3466
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineNumber, {
|
|
3467
|
+
"data-role": "number",
|
|
3468
|
+
onClick: () => {
|
|
3469
|
+
onSelect(line.path);
|
|
3470
|
+
},
|
|
3471
|
+
children: n
|
|
3472
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineBody, {
|
|
3473
|
+
"data-role": "body",
|
|
3474
|
+
style: indent,
|
|
3475
|
+
children: body
|
|
3476
|
+
})]
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
/** 字段行:`key: value`(数组成员的键名是下标,淡一些)。 */
|
|
3480
|
+
function FieldLine({ line, ns, state, face, t, disabled, editing, dismissed, onEdit, onDismiss, renderField }) {
|
|
3481
|
+
const key = fieldKey(line.path);
|
|
3482
|
+
const member = line.member;
|
|
3483
|
+
const owner = {
|
|
3484
|
+
ns,
|
|
3485
|
+
path: line.path,
|
|
3486
|
+
node: line.node,
|
|
3487
|
+
label: line.node.label ?? member?.key ?? line.node.key,
|
|
3488
|
+
hint: void 0,
|
|
3489
|
+
value: line.field.value,
|
|
3490
|
+
options: state.options.get(key),
|
|
3491
|
+
text: line.field.text,
|
|
3492
|
+
secretConfigured: state.secrets.get(key) === true,
|
|
3493
|
+
depth: line.depth,
|
|
3494
|
+
overridden: line.field.overridden,
|
|
3495
|
+
invalid: line.field.invalid,
|
|
3496
|
+
disabled,
|
|
3497
|
+
t,
|
|
3498
|
+
actions: face,
|
|
3499
|
+
group: void 0,
|
|
3500
|
+
member,
|
|
3501
|
+
onChange: (value) => {
|
|
3502
|
+
face.set(line.path, value);
|
|
3503
|
+
},
|
|
3504
|
+
onEditText: (text, parse) => {
|
|
3505
|
+
face.setText(line.path, text, parse ?? parseFor(line.node, t));
|
|
3506
|
+
},
|
|
3507
|
+
onReset: () => {
|
|
3508
|
+
face.clear(line.path);
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
const readOnly = line.node.meta.disabled || line.node.readOnly !== null;
|
|
3512
|
+
const editable = !disabled && !readOnly && line.node.type !== "const" && owner.options === void 0 && !line.variantsStandIn;
|
|
3513
|
+
const missing = line.field.value === null || line.field.value === void 0;
|
|
3514
|
+
const isEditing = editable && (editing === key || missing && !dismissed.has(key));
|
|
3515
|
+
const text = line.field.text ?? valueText(line.field.value);
|
|
3516
|
+
const multiline = isMultiline(line.field.value) || /[\r\n]/.test(text);
|
|
3517
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3518
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineFoldSpacer, { "data-role": "fold" }),
|
|
3519
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineKey, {
|
|
3520
|
+
"data-role": "key",
|
|
3521
|
+
"data-index": member?.index === void 0 ? void 0 : "true",
|
|
3522
|
+
title: member?.key ?? line.node.key,
|
|
3523
|
+
children: member?.key ?? line.node.key
|
|
3524
|
+
}),
|
|
3525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: member?.index === void 0 ? ": " : " " }),
|
|
3526
|
+
isEditing ? multiline ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactTextField, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
3527
|
+
autoFocus: !missing,
|
|
3528
|
+
rows: 1,
|
|
3529
|
+
"aria-label": owner.label,
|
|
3530
|
+
value: text,
|
|
3531
|
+
onChange: (event) => {
|
|
3532
|
+
owner.onEditText(event.target.value);
|
|
3533
|
+
},
|
|
3534
|
+
onKeyDown: (event) => {
|
|
3535
|
+
if (event.key === "Escape") {
|
|
3536
|
+
face.revert(line.path);
|
|
3537
|
+
onEdit(void 0);
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactInput, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
3541
|
+
autoFocus: !missing,
|
|
3542
|
+
type: "text",
|
|
3543
|
+
"aria-label": owner.label,
|
|
3544
|
+
placeholder: missing ? t("empty") : "",
|
|
3545
|
+
value: text,
|
|
3546
|
+
onBlur: () => {
|
|
3547
|
+
if (!missing) onEdit(void 0);
|
|
3548
|
+
},
|
|
3549
|
+
onChange: (event) => {
|
|
3550
|
+
owner.onEditText(event.target.value);
|
|
3551
|
+
},
|
|
3552
|
+
onKeyDown: (event) => {
|
|
3553
|
+
if (event.key === "Enter" && !missing) onEdit(void 0);
|
|
3554
|
+
if (event.key === "Escape") {
|
|
3555
|
+
face.revert(line.path);
|
|
3556
|
+
onEdit(void 0);
|
|
3557
|
+
}
|
|
936
3558
|
}
|
|
937
|
-
},
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
"400": {
|
|
942
|
-
$: "rgb(242, 90, 90)",
|
|
943
|
-
a12: "rgb(242 90 90 / 12%)"
|
|
3559
|
+
}) }) : null,
|
|
3560
|
+
isEditing || line.variantsStandIn ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3561
|
+
onClick: () => {
|
|
3562
|
+
if (editable) onEdit(key);
|
|
944
3563
|
},
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
3564
|
+
children: renderField(owner)
|
|
3565
|
+
}),
|
|
3566
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VariantSelect, {
|
|
3567
|
+
control: line.variants,
|
|
3568
|
+
path: line.path,
|
|
3569
|
+
face,
|
|
3570
|
+
disabled,
|
|
3571
|
+
t
|
|
3572
|
+
}),
|
|
3573
|
+
isEditing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverActions, {
|
|
3574
|
+
"data-role": "actions",
|
|
3575
|
+
"data-editing": "true",
|
|
3576
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3577
|
+
type: "button",
|
|
3578
|
+
"aria-label": t("confirmEdit"),
|
|
3579
|
+
title: t("confirmEdit"),
|
|
3580
|
+
onClick: () => {
|
|
3581
|
+
onEdit(void 0);
|
|
3582
|
+
},
|
|
3583
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 })
|
|
3584
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3585
|
+
type: "button",
|
|
3586
|
+
"aria-label": t("cancelEdit"),
|
|
3587
|
+
title: t("cancelEdit"),
|
|
3588
|
+
onClick: () => {
|
|
3589
|
+
face.revert(line.path);
|
|
3590
|
+
onEdit(void 0);
|
|
3591
|
+
onDismiss(key);
|
|
3592
|
+
},
|
|
3593
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
|
|
3594
|
+
})]
|
|
3595
|
+
}) : null,
|
|
3596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverActions, {
|
|
3597
|
+
"data-role": "actions",
|
|
3598
|
+
children: [
|
|
3599
|
+
line.field.staged && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3600
|
+
type: "button",
|
|
3601
|
+
"aria-label": t("revert"),
|
|
3602
|
+
title: t("revert"),
|
|
3603
|
+
onClick: () => {
|
|
3604
|
+
face.revert(line.path);
|
|
3605
|
+
},
|
|
3606
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 })
|
|
3607
|
+
}) : null,
|
|
3608
|
+
line.field.overridden && !line.field.staged && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3609
|
+
type: "button",
|
|
3610
|
+
"aria-label": t("reset"),
|
|
3611
|
+
title: t("reset"),
|
|
3612
|
+
onClick: () => {
|
|
3613
|
+
face.clear(line.path);
|
|
3614
|
+
},
|
|
3615
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 })
|
|
3616
|
+
}) : null,
|
|
3617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3618
|
+
type: "button",
|
|
3619
|
+
"aria-label": t("copyValue"),
|
|
3620
|
+
title: t("copyValue"),
|
|
3621
|
+
onClick: () => {
|
|
3622
|
+
navigator.clipboard?.writeText(line.field.text ?? tokenFor(line.field.value)).catch(() => {});
|
|
3623
|
+
},
|
|
3624
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 })
|
|
3625
|
+
}),
|
|
3626
|
+
member === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemoveLineButton, {
|
|
3627
|
+
member,
|
|
3628
|
+
face,
|
|
3629
|
+
t,
|
|
3630
|
+
disabled
|
|
3631
|
+
})
|
|
3632
|
+
]
|
|
3633
|
+
})
|
|
3634
|
+
] });
|
|
3635
|
+
}
|
|
3636
|
+
/** 复制用的文本(字符串带引号,粘到哪里都是合法片段)。 */
|
|
3637
|
+
function tokenFor(value) {
|
|
3638
|
+
return typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value ?? null);
|
|
3639
|
+
}
|
|
3640
|
+
/** 变体切换:判别式 union 挂在标签行上,按形状选的挂在字段行上。触发元素就是当前那一支。 */
|
|
3641
|
+
function VariantSelect({ control, path, face, disabled, t }) {
|
|
3642
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3643
|
+
if (control === void 0) return null;
|
|
3644
|
+
const current = control.choices[control.selected];
|
|
3645
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3646
|
+
open,
|
|
3647
|
+
dense: true,
|
|
3648
|
+
compact: true,
|
|
3649
|
+
portal: true,
|
|
3650
|
+
align: "start",
|
|
3651
|
+
items: control.choices.map((choice, index) => ({
|
|
3652
|
+
id: String(index),
|
|
3653
|
+
label: choice.label
|
|
3654
|
+
})),
|
|
3655
|
+
selectedId: String(control.selected),
|
|
3656
|
+
onSelect: (id) => {
|
|
3657
|
+
setOpen(false);
|
|
3658
|
+
const choice = control.choices[Number(id)];
|
|
3659
|
+
if (choice !== void 0) face.set(path, choice.value);
|
|
3660
|
+
},
|
|
3661
|
+
onClose: () => {
|
|
3662
|
+
setOpen(false);
|
|
3663
|
+
},
|
|
3664
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTrigger, {
|
|
3665
|
+
type: "button",
|
|
3666
|
+
"data-role": "variant",
|
|
3667
|
+
disabled,
|
|
3668
|
+
"aria-label": t("switchVariant"),
|
|
3669
|
+
onClick: () => {
|
|
3670
|
+
setOpen(true);
|
|
949
3671
|
},
|
|
950
|
-
|
|
3672
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineValue, {
|
|
3673
|
+
"data-tone": "empty",
|
|
3674
|
+
children: current?.label ?? ""
|
|
3675
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 })]
|
|
3676
|
+
})
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
/** 开启行的前缀:字段键名(对象里的一层)或数组成员的下标;成员行另外带移除。 */
|
|
3680
|
+
function LinePrefix({ line, face, t, disabled }) {
|
|
3681
|
+
const member = line.member;
|
|
3682
|
+
const key = member?.key ?? line.node.key;
|
|
3683
|
+
if (key === "") return null;
|
|
3684
|
+
const indexed = member !== void 0 && member.index !== void 0;
|
|
3685
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3686
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineKey, {
|
|
3687
|
+
"data-role": "key",
|
|
3688
|
+
"data-index": indexed ? "true" : void 0,
|
|
3689
|
+
title: key,
|
|
3690
|
+
children: key
|
|
3691
|
+
}),
|
|
3692
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineToken, { children: indexed ? " " : ": " }),
|
|
3693
|
+
member === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverActions, {
|
|
3694
|
+
"data-role": "actions",
|
|
3695
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemoveLineButton, {
|
|
3696
|
+
member,
|
|
3697
|
+
face,
|
|
3698
|
+
t,
|
|
3699
|
+
disabled
|
|
3700
|
+
})
|
|
3701
|
+
})
|
|
3702
|
+
] });
|
|
3703
|
+
}
|
|
3704
|
+
/** 成员行的移除(悬停时出现):数组项按索引、字典键按键名。 */
|
|
3705
|
+
function RemoveLineButton({ member, face, t, disabled }) {
|
|
3706
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3707
|
+
type: "button",
|
|
3708
|
+
"aria-label": t("removeItem"),
|
|
3709
|
+
title: t("removeItem"),
|
|
3710
|
+
disabled,
|
|
3711
|
+
onClick: () => {
|
|
3712
|
+
if (member.index === void 0) face.removeKey(member.parent, member.key);
|
|
3713
|
+
else face.removeItem(member.parent, member.index);
|
|
3714
|
+
},
|
|
3715
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* 容器闭合行后面的添加入口。
|
|
3720
|
+
*
|
|
3721
|
+
* 数组是「追加一个空项」;对象/字典给出**候选**——对象列的是 schema 声明了、值里还没有的字段(带说明),
|
|
3722
|
+
* 字典列的是业务注册的候选键(还能自己敲键名)。粘一整段 `{…}` / `[…]` 则整层覆盖。
|
|
3723
|
+
*/
|
|
3724
|
+
function AddLine({ line, state, face, t, resolveText, disabled, onAdded }) {
|
|
3725
|
+
const spec = line.add;
|
|
3726
|
+
const [text, setText] = (0, react.useState)("");
|
|
3727
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3728
|
+
const [invalid, setInvalid] = (0, react.useState)(false);
|
|
3729
|
+
if (spec === void 0 || disabled) return null;
|
|
3730
|
+
const options = spec.kind === "item" ? [] : spec.options;
|
|
3731
|
+
const query = text.trim().toLowerCase();
|
|
3732
|
+
const matched = options.filter((option) => query === "" || option.key.toLowerCase().includes(query));
|
|
3733
|
+
const label = spec.kind === "item" ? t("addItemPaste") : spec.kind === "key" ? t("addKey") : t("addProperty");
|
|
3734
|
+
const commit = (picked) => {
|
|
3735
|
+
const raw = (picked ?? text).trim();
|
|
3736
|
+
if (raw === "") return;
|
|
3737
|
+
setInvalid(false);
|
|
3738
|
+
if (raw.startsWith("{") || raw.startsWith("[")) try {
|
|
3739
|
+
face.set(line.path, JSON.parse(raw));
|
|
3740
|
+
setText("");
|
|
3741
|
+
setOpen(false);
|
|
3742
|
+
return;
|
|
3743
|
+
} catch {}
|
|
3744
|
+
if (spec.kind === "key") {
|
|
3745
|
+
face.addKey(line.path, raw);
|
|
3746
|
+
onAdded([...line.path, raw]);
|
|
3747
|
+
} else if (spec.kind === "item") {
|
|
3748
|
+
const current = state.fields.get(fieldKey(line.path))?.value;
|
|
3749
|
+
const index = Array.isArray(current) ? current.length : 0;
|
|
3750
|
+
face.appendItem(line.path);
|
|
3751
|
+
onAdded([...line.path, String(index)]);
|
|
3752
|
+
} else {
|
|
3753
|
+
if (!spec.options.some((option) => option.key === raw)) {
|
|
3754
|
+
setInvalid(true);
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
face.addKey(line.path, raw);
|
|
3758
|
+
onAdded([...line.path, raw]);
|
|
951
3759
|
}
|
|
952
|
-
|
|
953
|
-
|
|
3760
|
+
setText("");
|
|
3761
|
+
setOpen(false);
|
|
3762
|
+
};
|
|
3763
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AddWrap, {
|
|
3764
|
+
"data-add": spec.kind,
|
|
3765
|
+
"data-invalid": invalid ? "true" : void 0,
|
|
3766
|
+
children: [
|
|
3767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3768
|
+
open: open && spec.kind !== "item" && matched.length > 0,
|
|
3769
|
+
dense: true,
|
|
3770
|
+
compact: true,
|
|
3771
|
+
portal: true,
|
|
3772
|
+
align: "start",
|
|
3773
|
+
items: matched.map((option) => ({
|
|
3774
|
+
id: option.key,
|
|
3775
|
+
label: option.key
|
|
3776
|
+
})),
|
|
3777
|
+
onSelect: (key) => {
|
|
3778
|
+
commit(key);
|
|
3779
|
+
},
|
|
3780
|
+
onClose: () => {
|
|
3781
|
+
setOpen(false);
|
|
3782
|
+
},
|
|
3783
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactInput, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
3784
|
+
value: text,
|
|
3785
|
+
placeholder: label,
|
|
3786
|
+
"aria-label": label,
|
|
3787
|
+
disabled,
|
|
3788
|
+
onFocus: () => {
|
|
3789
|
+
setOpen(true);
|
|
3790
|
+
},
|
|
3791
|
+
onChange: (event) => {
|
|
3792
|
+
setText(event.target.value);
|
|
3793
|
+
setInvalid(false);
|
|
3794
|
+
setOpen(true);
|
|
3795
|
+
},
|
|
3796
|
+
onKeyDown: (event) => {
|
|
3797
|
+
if (event.key === "Enter") {
|
|
3798
|
+
event.preventDefault();
|
|
3799
|
+
commit(matched.length === 1 && spec.kind === "prop" ? matched[0]?.key : void 0);
|
|
3800
|
+
}
|
|
3801
|
+
if (event.key === "Escape") {
|
|
3802
|
+
setText("");
|
|
3803
|
+
setOpen(false);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
}) })
|
|
3807
|
+
}),
|
|
3808
|
+
invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineInvalid, { children: t("unknownProperty") }) : null,
|
|
3809
|
+
!invalid && matched.length === 1 && matched[0]?.description !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineComment, {
|
|
3810
|
+
title: resolveText(matched[0].description),
|
|
3811
|
+
children: resolveText(matched[0].description)
|
|
3812
|
+
}) : null
|
|
3813
|
+
]
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
954
3816
|
//#endregion
|
|
955
|
-
//#region src/client/
|
|
956
|
-
|
|
3817
|
+
//#region src/client/schema-form/SchemaForm.tsx
|
|
3818
|
+
/**
|
|
3819
|
+
* 一行的配置页:官方表单壳(保存 / 丢弃 / 只读 / 不可用)+ 行式 JSON 结构编辑器 + 字段槽。
|
|
3820
|
+
*
|
|
3821
|
+
* 每个可渲染的行注册项经 `renderFactorySlot('settings.schema-form.form', …)` 渲染这个 Factory,字段槽因此只需在
|
|
3822
|
+
* Factory 的 `children` 里声明一次(slots 的 children 声明 per key 唯一,而本包给每个行都注册了一项)。
|
|
3823
|
+
*/
|
|
3824
|
+
/**
|
|
3825
|
+
* 渲染一行的配置页。
|
|
3826
|
+
* @param props - 行 id、控制器面、本地化解析与框架注入的槽位渲染座位。
|
|
3827
|
+
* @returns 表单壳里的行式编辑器,或「没有可自动生成的配置项」一行。
|
|
3828
|
+
*/
|
|
3829
|
+
function SchemaForm(props) {
|
|
3830
|
+
const { ns, face, t, resolveText, renderSlotChain } = props;
|
|
3831
|
+
const store = face.hooks.schemaForm;
|
|
3832
|
+
const state = (0, react.useSyncExternalStore)((listener) => store.subscribe(listener), () => store.getSnapshot());
|
|
3833
|
+
const labels = {
|
|
3834
|
+
unavailable: t("unavailable"),
|
|
3835
|
+
readOnly: t("readOnly"),
|
|
3836
|
+
saveFailed: t("saveFailed"),
|
|
3837
|
+
save: t("save"),
|
|
3838
|
+
saving: t("saving")
|
|
3839
|
+
};
|
|
3840
|
+
if (!state.configured) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hint$1, {
|
|
3841
|
+
role: "status",
|
|
3842
|
+
children: t("noSchema")
|
|
3843
|
+
});
|
|
3844
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
|
|
3845
|
+
labels,
|
|
3846
|
+
state,
|
|
3847
|
+
onSave: () => {
|
|
3848
|
+
face.save();
|
|
3849
|
+
},
|
|
3850
|
+
onDiscard: () => {
|
|
3851
|
+
face.discard();
|
|
3852
|
+
},
|
|
3853
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
|
|
3854
|
+
ns,
|
|
3855
|
+
state,
|
|
3856
|
+
face,
|
|
3857
|
+
t,
|
|
3858
|
+
resolveText,
|
|
3859
|
+
renderField: (owner) => renderSlotChain("settings.schema-form.field", owner, { fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SchemaFieldDefault, { owner: {
|
|
3860
|
+
...owner,
|
|
3861
|
+
onEditText: withParse(owner, t)
|
|
3862
|
+
} }) })
|
|
3863
|
+
}), state.violation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hint$1, {
|
|
3864
|
+
role: "alert",
|
|
3865
|
+
"data-violation": "true",
|
|
3866
|
+
children: t("notSaved", { message: state.violation.message })
|
|
3867
|
+
})]
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
/** 文本草稿的解析规则按字段类型给:行内编辑只上报原文,值在保存那一刻成形。 */
|
|
3871
|
+
function withParse(owner, t) {
|
|
3872
|
+
return (text, parse) => {
|
|
3873
|
+
owner.onEditText(text, parse ?? parseFor(owner.node, t));
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
//#endregion
|
|
3877
|
+
//#region src/client/schema-form/index.ts
|
|
3878
|
+
var schema_form_exports = /* @__PURE__ */ __exportAll({
|
|
3879
|
+
InlineValue: () => InlineValue,
|
|
3880
|
+
NS: () => NS,
|
|
3881
|
+
SchemaFieldDefault: () => SchemaFieldDefault,
|
|
3882
|
+
SchemaFormController: () => SchemaFormController,
|
|
3883
|
+
SchemaFormHints: () => SchemaFormHints,
|
|
3884
|
+
apply: () => apply$1,
|
|
3885
|
+
canRender: () => canRender,
|
|
3886
|
+
containerShape: () => containerShape,
|
|
3887
|
+
editorLines: () => editorLines,
|
|
3888
|
+
failureOf: () => failureOf,
|
|
3889
|
+
inject: () => inject$1,
|
|
3890
|
+
optionsFor: () => optionsFor,
|
|
3891
|
+
parseFor: () => parseFor,
|
|
3892
|
+
projectNode: () => projectNode,
|
|
3893
|
+
projectRoot: () => projectRoot,
|
|
3894
|
+
rowRegistrations: () => rowRegistrations,
|
|
3895
|
+
tokenText: () => tokenText,
|
|
3896
|
+
tokenTone: () => tokenTone,
|
|
3897
|
+
valueText: () => valueText,
|
|
3898
|
+
visibleFields: () => visibleFields,
|
|
3899
|
+
walkFields: () => walkFields
|
|
3900
|
+
});
|
|
3901
|
+
/** 本包字典与槽位文案的命名空间。 */
|
|
3902
|
+
const NS = "settings.schema-form";
|
|
3903
|
+
/** 需要的服务:槽位、字典、共享配置表单、schema 服务与插件管理(bundle 行清单)。 */
|
|
3904
|
+
const inject$1 = [
|
|
3905
|
+
"slots",
|
|
3906
|
+
"locale",
|
|
3907
|
+
"configForms",
|
|
3908
|
+
"settingsSchema",
|
|
3909
|
+
"remote",
|
|
3910
|
+
"remote.pluginManager"
|
|
3911
|
+
];
|
|
3912
|
+
/**
|
|
3913
|
+
* 装上本包:字典、Factory(字段槽的声明者)与按行自动注册。
|
|
3914
|
+
* @param ctx - 浏览器插件上下文。
|
|
3915
|
+
*/
|
|
3916
|
+
function apply$1(ctx) {
|
|
3917
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
3918
|
+
zh,
|
|
3919
|
+
en
|
|
3920
|
+
}), "ui-schema-form: dictionaries");
|
|
3921
|
+
const hints = new SchemaFormHints(ctx);
|
|
3922
|
+
ctx.effect(() => ctx.slots.registerFactory({
|
|
3923
|
+
name: "settings.schema-form.form",
|
|
3924
|
+
scope: "root",
|
|
3925
|
+
children: { "settings.schema-form.field": {
|
|
3926
|
+
kind: "chain",
|
|
3927
|
+
scope: "root"
|
|
3928
|
+
} },
|
|
3929
|
+
locale: NS
|
|
3930
|
+
}, SchemaForm), "ui-schema-form: form factory");
|
|
3931
|
+
const rehydrate = (serialized) => ctx.settingsSchema.rehydrate(serialized);
|
|
3932
|
+
const validate = (schema, value) => {
|
|
3933
|
+
try {
|
|
3934
|
+
schema(value);
|
|
3935
|
+
return;
|
|
3936
|
+
} catch (error) {
|
|
3937
|
+
return failureOf(error);
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
const resolveText = (text) => typeof text === "string" ? text : ctx.locale.resolveText(text);
|
|
3941
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3942
|
+
const entryFor = (ns) => {
|
|
3943
|
+
const describe = ctx.configForms.describe();
|
|
3944
|
+
const view = describe.getSnapshot().view?.namespaces.find((row) => row.ns === ns);
|
|
3945
|
+
if (!canRender(view, rehydrate)) return void 0;
|
|
3946
|
+
const existing = entries.get(ns);
|
|
3947
|
+
if (existing !== void 0) return existing;
|
|
3948
|
+
const controller = new SchemaFormController(ns, {
|
|
3949
|
+
form: ctx.configForms.get(ns),
|
|
3950
|
+
describe,
|
|
3951
|
+
rehydrate,
|
|
3952
|
+
validate,
|
|
3953
|
+
t: ctx.locale.bind(NS),
|
|
3954
|
+
hints: {
|
|
3955
|
+
keysFor: (path) => hints.keysFor(ns, path),
|
|
3956
|
+
textFor: (path) => hints.textFor(ns, path),
|
|
3957
|
+
selectFor: (path) => hints.selectFor(ns, path),
|
|
3958
|
+
sourceFor: (name) => hints.sourceFor(name)
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3961
|
+
const face = controller.face();
|
|
3962
|
+
const props = {
|
|
3963
|
+
ns,
|
|
3964
|
+
face,
|
|
3965
|
+
resolveText
|
|
3966
|
+
};
|
|
3967
|
+
const component = (entryProps) => entryProps.view === "page" ? entryProps.renderFactorySlot("settings.schema-form.form", props) : null;
|
|
3968
|
+
const entry = {
|
|
3969
|
+
controller,
|
|
3970
|
+
face,
|
|
3971
|
+
component
|
|
3972
|
+
};
|
|
3973
|
+
entries.set(ns, entry);
|
|
3974
|
+
return entry;
|
|
3975
|
+
};
|
|
3976
|
+
const registration = new RowRegistration({
|
|
3977
|
+
slots: ctx.slots,
|
|
3978
|
+
describe: ctx.configForms.describe(),
|
|
3979
|
+
bundles: async () => {
|
|
3980
|
+
const response = await ctx.remote.pluginManager.listBundles();
|
|
3981
|
+
return response.ok ? response.value : [];
|
|
3982
|
+
},
|
|
3983
|
+
subscribeBundles: (listener) => ctx.remote.$on("plugin-manager/changed", listener),
|
|
3984
|
+
entryFor,
|
|
3985
|
+
locale: NS,
|
|
3986
|
+
release: (ns) => {
|
|
3987
|
+
const entry = entries.get(ns);
|
|
3988
|
+
entries.delete(ns);
|
|
3989
|
+
entry?.controller.dispose();
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
ctx.effect(() => {
|
|
3993
|
+
const offDescribe = ctx.configForms.describe().subscribe(() => {
|
|
3994
|
+
registration.sync();
|
|
3995
|
+
});
|
|
3996
|
+
const offHints = hints.subscribe(() => {
|
|
3997
|
+
for (const entry of entries.values()) entry.controller.refresh();
|
|
3998
|
+
});
|
|
3999
|
+
registration.sync();
|
|
4000
|
+
return () => {
|
|
4001
|
+
offDescribe();
|
|
4002
|
+
offHints();
|
|
4003
|
+
registration.dispose();
|
|
4004
|
+
for (const [ns, entry] of entries) {
|
|
4005
|
+
entries.delete(ns);
|
|
4006
|
+
entry.controller.dispose();
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
}, "ui-schema-form: row registrations");
|
|
4010
|
+
}
|
|
957
4011
|
//#endregion
|
|
958
4012
|
//#region src/client/markdown-labels.ts
|
|
959
4013
|
function markdownLabels(t) {
|
|
@@ -1256,6 +4310,9 @@ window.__ModuleLoader__.load({
|
|
|
1256
4310
|
}
|
|
1257
4311
|
};
|
|
1258
4312
|
//#endregion
|
|
4313
|
+
//#region src/client/styling/focus.ts
|
|
4314
|
+
const focusRing = `${String(dsw.focus.ring.width)} solid ${Token.collect(Token.fallbackVar(dsw.focus.ring.color, String(dsw.alias.state.business.primary)))}`;
|
|
4315
|
+
//#endregion
|
|
1259
4316
|
//#region src/client/settings-form/fields.styles.ts
|
|
1260
4317
|
const helpButtonHoverBackground = "var(--dsw-alias-bg-layer-4)";
|
|
1261
4318
|
const styles$1 = {
|
|
@@ -1309,7 +4366,7 @@ window.__ModuleLoader__.load({
|
|
|
1309
4366
|
color: dsw.alias.label.secondary
|
|
1310
4367
|
},
|
|
1311
4368
|
"&:focus-visible": {
|
|
1312
|
-
outline:
|
|
4369
|
+
outline: focusRing,
|
|
1313
4370
|
outlineOffset: "1px"
|
|
1314
4371
|
}
|
|
1315
4372
|
},
|
|
@@ -1366,7 +4423,7 @@ window.__ModuleLoader__.load({
|
|
|
1366
4423
|
color: dsw.alias.label.primary,
|
|
1367
4424
|
"&:focus-visible": {
|
|
1368
4425
|
outline: "none",
|
|
1369
|
-
borderColor: dsw.alias.
|
|
4426
|
+
borderColor: dsw.alias.state.business.primary
|
|
1370
4427
|
},
|
|
1371
4428
|
"&:disabled": {
|
|
1372
4429
|
color: dsw.alias.label.tertiary,
|
|
@@ -1492,6 +4549,7 @@ window.__ModuleLoader__.load({
|
|
|
1492
4549
|
* A write-only credential control. The value never rides a response, so the
|
|
1493
4550
|
* control reports only whether one is configured and starts blank; a blank
|
|
1494
4551
|
* draft writes nothing, which keeps the stored key rather than clearing it.
|
|
4552
|
+
* 控件要求浏览器不要把已存的登录密码自动填进来(上游 11c0511271)。
|
|
1495
4553
|
* @param props - the field's copy, its staged text, and the configured state.
|
|
1496
4554
|
* @returns the labelled control.
|
|
1497
4555
|
*/
|
|
@@ -1505,7 +4563,7 @@ window.__ModuleLoader__.load({
|
|
|
1505
4563
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
1506
4564
|
id: props.id,
|
|
1507
4565
|
type: "password",
|
|
1508
|
-
autoComplete: "
|
|
4566
|
+
autoComplete: "new-password",
|
|
1509
4567
|
value: props.text,
|
|
1510
4568
|
disabled: props.disabled,
|
|
1511
4569
|
onChange: (event) => {
|
|
@@ -1556,7 +4614,7 @@ window.__ModuleLoader__.load({
|
|
|
1556
4614
|
cursor: "default"
|
|
1557
4615
|
},
|
|
1558
4616
|
"&:focus-visible": {
|
|
1559
|
-
outline:
|
|
4617
|
+
outline: focusRing,
|
|
1560
4618
|
outlineOffset: "1px"
|
|
1561
4619
|
}
|
|
1562
4620
|
}
|
|
@@ -2113,7 +5171,7 @@ window.__ModuleLoader__.load({
|
|
|
2113
5171
|
}
|
|
2114
5172
|
}
|
|
2115
5173
|
//#endregion
|
|
2116
|
-
//#region ../../../node_modules/.pnpm/micromark-factory-space@2.0
|
|
5174
|
+
//#region ../../../node_modules/.pnpm/micromark-factory-space@2.1.0/node_modules/micromark-factory-space/index.js
|
|
2117
5175
|
/**
|
|
2118
5176
|
* @import {Effects, State, TokenType} from 'micromark-util-types'
|
|
2119
5177
|
*/
|
|
@@ -2143,14 +5201,14 @@ window.__ModuleLoader__.load({
|
|
|
2143
5201
|
* @param {State} ok
|
|
2144
5202
|
* State switched to when successful.
|
|
2145
5203
|
* @param {TokenType} type
|
|
2146
|
-
* Type
|
|
5204
|
+
* Type of the whole whitespace.
|
|
2147
5205
|
* @param {number | undefined} [max=Infinity]
|
|
2148
5206
|
* Max (exclusive).
|
|
2149
5207
|
* @returns {State}
|
|
2150
5208
|
* Start state.
|
|
2151
5209
|
*/
|
|
2152
5210
|
function factorySpace(effects, ok, type, max) {
|
|
2153
|
-
const limit = max ? max - 1 :
|
|
5211
|
+
const limit = max ? max - 1 : Infinity;
|
|
2154
5212
|
let size = 0;
|
|
2155
5213
|
return start;
|
|
2156
5214
|
/** @type {State} */
|
|
@@ -2171,8 +5229,56 @@ window.__ModuleLoader__.load({
|
|
|
2171
5229
|
return ok(code);
|
|
2172
5230
|
}
|
|
2173
5231
|
}
|
|
5232
|
+
/**
|
|
5233
|
+
* Parse spaces and tabs, with a required minimum and maximum, matching
|
|
5234
|
+
* `markdown-rs`’s `space_or_tab_min_max`.
|
|
5235
|
+
*
|
|
5236
|
+
* Unlike `factorySpace`, this can fail: `nok` is used when fewer than
|
|
5237
|
+
* `min` spaces or tabs are found.
|
|
5238
|
+
*
|
|
5239
|
+
* @param {Effects} effects
|
|
5240
|
+
* Context.
|
|
5241
|
+
* @param {State} ok
|
|
5242
|
+
* State switched to when successful.
|
|
5243
|
+
* @param {State} nok
|
|
5244
|
+
* State switched to when unsuccessful.
|
|
5245
|
+
* @param {TokenType} type
|
|
5246
|
+
* Type of the whole whitespace.
|
|
5247
|
+
* @param {number} min
|
|
5248
|
+
* Minimum allowed characters (inclusive).
|
|
5249
|
+
* @param {number} max
|
|
5250
|
+
* Maximum allowed characters (inclusive).
|
|
5251
|
+
* @returns {State}
|
|
5252
|
+
* Start state.
|
|
5253
|
+
*/
|
|
5254
|
+
function factorySpaceMinMax(effects, ok, nok, type, min, max) {
|
|
5255
|
+
let size = 0;
|
|
5256
|
+
return start;
|
|
5257
|
+
/** @type {State} */
|
|
5258
|
+
function start(code) {
|
|
5259
|
+
if (max > 0 && markdownSpace(code)) {
|
|
5260
|
+
effects.enter(type);
|
|
5261
|
+
return prefix(code);
|
|
5262
|
+
}
|
|
5263
|
+
return after(code);
|
|
5264
|
+
}
|
|
5265
|
+
/** @type {State} */
|
|
5266
|
+
function prefix(code) {
|
|
5267
|
+
if (markdownSpace(code) && size < max) {
|
|
5268
|
+
effects.consume(code);
|
|
5269
|
+
size++;
|
|
5270
|
+
return prefix;
|
|
5271
|
+
}
|
|
5272
|
+
effects.exit(type);
|
|
5273
|
+
return after(code);
|
|
5274
|
+
}
|
|
5275
|
+
/** @type {State} */
|
|
5276
|
+
function after(code) {
|
|
5277
|
+
return size >= min ? ok(code) : nok(code);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
2174
5280
|
//#endregion
|
|
2175
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
5281
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/content.js
|
|
2176
5282
|
/**
|
|
2177
5283
|
* @import {
|
|
2178
5284
|
* InitialConstruct,
|
|
@@ -2234,12 +5340,158 @@ window.__ModuleLoader__.load({
|
|
|
2234
5340
|
effects.exit("chunkText");
|
|
2235
5341
|
return lineStart;
|
|
2236
5342
|
}
|
|
2237
|
-
effects.consume(code);
|
|
2238
|
-
return data;
|
|
5343
|
+
effects.consume(code);
|
|
5344
|
+
return data;
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
//#endregion
|
|
5348
|
+
//#region ../../../node_modules/.pnpm/micromark-util-edit-map@1.0.0/node_modules/micromark-util-edit-map/index.js
|
|
5349
|
+
/**
|
|
5350
|
+
* @import {Event} from 'micromark-util-types'
|
|
5351
|
+
*/
|
|
5352
|
+
/**
|
|
5353
|
+
* @typedef {[number, number, Array<Event>]} Change
|
|
5354
|
+
* @typedef {[number, number, number]} Jump
|
|
5355
|
+
*/
|
|
5356
|
+
/**
|
|
5357
|
+
* Tracks a bunch of edits.
|
|
5358
|
+
*
|
|
5359
|
+
* Port of `edit_map.rs` from `markdown-rs`:
|
|
5360
|
+
* <https://github.com/wooorm/markdown-rs/blob/1506572f/src/util/edit_map.rs>.
|
|
5361
|
+
*
|
|
5362
|
+
* Deal with several changes in events, batching them together.
|
|
5363
|
+
*
|
|
5364
|
+
* Preferably, changes should be kept to a minimum.
|
|
5365
|
+
* Sometimes, it’s needed to change the list of events, because parsing can be
|
|
5366
|
+
* messy, and it helps to expose a cleaner interface of events to the compiler
|
|
5367
|
+
* and other users.
|
|
5368
|
+
* It can also help to merge many adjacent similar events.
|
|
5369
|
+
* And, in other cases, it’s needed to parse subcontent: pass some events
|
|
5370
|
+
* through another tokenizer and inject the result.
|
|
5371
|
+
*/
|
|
5372
|
+
var EditMap$1 = class {
|
|
5373
|
+
/**
|
|
5374
|
+
* Create a new edit map.
|
|
5375
|
+
*
|
|
5376
|
+
* @returns
|
|
5377
|
+
* New instance.
|
|
5378
|
+
*/
|
|
5379
|
+
constructor() {
|
|
5380
|
+
/**
|
|
5381
|
+
* Changes by index, so `add` does not need to scan `map` (which is
|
|
5382
|
+
* quadratic for documents with many edits at different places).
|
|
5383
|
+
*
|
|
5384
|
+
* @type {Map<number, Change>}
|
|
5385
|
+
*/
|
|
5386
|
+
this.index = /* @__PURE__ */ new Map();
|
|
5387
|
+
/**
|
|
5388
|
+
* Record of changes.
|
|
5389
|
+
*
|
|
5390
|
+
* @type {Array<Change>}
|
|
5391
|
+
*/
|
|
5392
|
+
this.map = [];
|
|
5393
|
+
}
|
|
5394
|
+
/**
|
|
5395
|
+
* Create an edit: a remove and/or add at a certain place.
|
|
5396
|
+
*
|
|
5397
|
+
* @param {number} index
|
|
5398
|
+
* Index at which to apply the edit.
|
|
5399
|
+
* @param {number} remove
|
|
5400
|
+
* Count of items to remove at the index.
|
|
5401
|
+
* @param {Array<Event>} add
|
|
5402
|
+
* Items to add at the index.
|
|
5403
|
+
* @returns {undefined}
|
|
5404
|
+
* Nothing.
|
|
5405
|
+
*/
|
|
5406
|
+
add(index, remove, add) {
|
|
5407
|
+
addImplementation$1(this, index, remove, add, false);
|
|
5408
|
+
}
|
|
5409
|
+
/**
|
|
5410
|
+
* Create an edit: but insert `add` before existing additions, instead of
|
|
5411
|
+
* after them.
|
|
5412
|
+
*
|
|
5413
|
+
* @param {number} index
|
|
5414
|
+
* Index at which to apply the edit.
|
|
5415
|
+
* @param {number} remove
|
|
5416
|
+
* Count of items to remove at the index.
|
|
5417
|
+
* @param {Array<Event>} add
|
|
5418
|
+
* Items to add at the index.
|
|
5419
|
+
* @returns {undefined}
|
|
5420
|
+
* Nothing.
|
|
5421
|
+
*/
|
|
5422
|
+
addBefore(index, remove, add) {
|
|
5423
|
+
addImplementation$1(this, index, remove, add, true);
|
|
5424
|
+
}
|
|
5425
|
+
/**
|
|
5426
|
+
* Done, change the events.
|
|
5427
|
+
*
|
|
5428
|
+
* @param {Array<Event>} events
|
|
5429
|
+
* List of events to apply the edits to.
|
|
5430
|
+
* @returns {undefined}
|
|
5431
|
+
* Nothing.
|
|
5432
|
+
*/
|
|
5433
|
+
consume(events) {
|
|
5434
|
+
this.map.sort(function(a, b) {
|
|
5435
|
+
return a[0] - b[0];
|
|
5436
|
+
});
|
|
5437
|
+
if (this.map.length === 0) return;
|
|
5438
|
+
let index = this.map.length;
|
|
5439
|
+
/** @type {Array<Array<Event>>} */
|
|
5440
|
+
const vecs = [];
|
|
5441
|
+
while (index > 0) {
|
|
5442
|
+
index -= 1;
|
|
5443
|
+
vecs.push(events.slice(this.map[index][0] + this.map[index][1]), this.map[index][2]);
|
|
5444
|
+
events.length = this.map[index][0];
|
|
5445
|
+
}
|
|
5446
|
+
vecs.push(events.slice());
|
|
5447
|
+
events.length = 0;
|
|
5448
|
+
let slice = vecs.pop();
|
|
5449
|
+
while (slice) {
|
|
5450
|
+
for (const element of slice) events.push(element);
|
|
5451
|
+
slice = vecs.pop();
|
|
5452
|
+
}
|
|
5453
|
+
this.map.length = 0;
|
|
5454
|
+
this.index.clear();
|
|
5455
|
+
}
|
|
5456
|
+
};
|
|
5457
|
+
/**
|
|
5458
|
+
* Create an edit.
|
|
5459
|
+
*
|
|
5460
|
+
* @param {EditMap} editMap
|
|
5461
|
+
* Edit map to apply to.
|
|
5462
|
+
* @param {number} at
|
|
5463
|
+
* Index at which to apply the edit.
|
|
5464
|
+
* @param {number} remove
|
|
5465
|
+
* Count of items to remove at the index.
|
|
5466
|
+
* @param {Array<Event>} add
|
|
5467
|
+
* Items to add at the index.
|
|
5468
|
+
* @param {boolean} before
|
|
5469
|
+
* Insert `add` before existing additions at `at`, instead of after them.
|
|
5470
|
+
* @returns {undefined}
|
|
5471
|
+
* Nothing.
|
|
5472
|
+
*/
|
|
5473
|
+
function addImplementation$1(editMap, at, remove, add, before) {
|
|
5474
|
+
if (remove === 0 && add.length === 0) return;
|
|
5475
|
+
const existing = editMap.index.get(at);
|
|
5476
|
+
if (existing) {
|
|
5477
|
+
existing[1] += remove;
|
|
5478
|
+
if (before) {
|
|
5479
|
+
add.push(...existing[2]);
|
|
5480
|
+
existing[2] = add;
|
|
5481
|
+
} else existing[2].push(...add);
|
|
5482
|
+
return;
|
|
2239
5483
|
}
|
|
5484
|
+
/** @type {Change} */
|
|
5485
|
+
const change = [
|
|
5486
|
+
at,
|
|
5487
|
+
remove,
|
|
5488
|
+
add
|
|
5489
|
+
];
|
|
5490
|
+
editMap.map.push(change);
|
|
5491
|
+
editMap.index.set(at, change);
|
|
2240
5492
|
}
|
|
2241
5493
|
//#endregion
|
|
2242
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
5494
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/document.js
|
|
2243
5495
|
/**
|
|
2244
5496
|
* @import {
|
|
2245
5497
|
* Construct,
|
|
@@ -2308,8 +5560,10 @@ window.__ModuleLoader__.load({
|
|
|
2308
5560
|
self.events[index][1].end = { ...point };
|
|
2309
5561
|
index++;
|
|
2310
5562
|
}
|
|
2311
|
-
|
|
2312
|
-
self.events.
|
|
5563
|
+
const editMap = new EditMap$1();
|
|
5564
|
+
editMap.add(indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
|
|
5565
|
+
editMap.add(indexBeforeExits, index - indexBeforeExits, []);
|
|
5566
|
+
editMap.consume(self.events);
|
|
2313
5567
|
return checkNewContainers(code);
|
|
2314
5568
|
}
|
|
2315
5569
|
return start(code);
|
|
@@ -2419,8 +5673,10 @@ window.__ModuleLoader__.load({
|
|
|
2419
5673
|
self.events[index][1].end = { ...point };
|
|
2420
5674
|
index++;
|
|
2421
5675
|
}
|
|
2422
|
-
|
|
2423
|
-
self.events.
|
|
5676
|
+
const editMap = new EditMap$1();
|
|
5677
|
+
editMap.add(indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
|
|
5678
|
+
editMap.add(indexBeforeExits, index - indexBeforeExits, []);
|
|
5679
|
+
editMap.consume(self.events);
|
|
2424
5680
|
}
|
|
2425
5681
|
}
|
|
2426
5682
|
/**
|
|
@@ -2508,7 +5764,7 @@ window.__ModuleLoader__.load({
|
|
|
2508
5764
|
return events;
|
|
2509
5765
|
}
|
|
2510
5766
|
//#endregion
|
|
2511
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
5767
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/attention.js
|
|
2512
5768
|
/**
|
|
2513
5769
|
* @import {
|
|
2514
5770
|
* Code,
|
|
@@ -2535,47 +5791,33 @@ window.__ModuleLoader__.load({
|
|
|
2535
5791
|
*/
|
|
2536
5792
|
function resolveAllAttention(events, context) {
|
|
2537
5793
|
let index = -1;
|
|
2538
|
-
/** @type {number} */
|
|
2539
|
-
let open;
|
|
2540
|
-
/** @type {Token} */
|
|
2541
|
-
let group;
|
|
2542
|
-
/** @type {Token} */
|
|
2543
|
-
let text;
|
|
2544
|
-
/** @type {Token} */
|
|
2545
|
-
let openingSequence;
|
|
2546
|
-
/** @type {Token} */
|
|
2547
|
-
let closingSequence;
|
|
2548
|
-
/** @type {number} */
|
|
2549
|
-
let use;
|
|
2550
5794
|
/** @type {Array<Event>} */
|
|
2551
5795
|
let nextEvents;
|
|
2552
|
-
/** @type {number} */
|
|
2553
|
-
let offset;
|
|
2554
5796
|
while (++index < events.length) if (events[index][0] === "enter" && events[index][1].type === "attentionSequence" && events[index][1]._close) {
|
|
2555
|
-
open = index;
|
|
5797
|
+
let open = index;
|
|
2556
5798
|
while (open--) if (events[open][0] === "exit" && events[open][1].type === "attentionSequence" && events[open][1]._open && context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
|
|
2557
5799
|
if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) continue;
|
|
2558
|
-
use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
|
|
5800
|
+
const use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
|
|
2559
5801
|
const start = { ...events[open][1].end };
|
|
2560
5802
|
const end = { ...events[index][1].start };
|
|
2561
5803
|
movePoint(start, -use);
|
|
2562
5804
|
movePoint(end, use);
|
|
2563
|
-
openingSequence = {
|
|
5805
|
+
const openingSequence = {
|
|
2564
5806
|
type: use > 1 ? "strongSequence" : "emphasisSequence",
|
|
2565
5807
|
start,
|
|
2566
5808
|
end: { ...events[open][1].end }
|
|
2567
5809
|
};
|
|
2568
|
-
closingSequence = {
|
|
5810
|
+
const closingSequence = {
|
|
2569
5811
|
type: use > 1 ? "strongSequence" : "emphasisSequence",
|
|
2570
5812
|
start: { ...events[index][1].start },
|
|
2571
5813
|
end
|
|
2572
5814
|
};
|
|
2573
|
-
text = {
|
|
5815
|
+
const text = {
|
|
2574
5816
|
type: use > 1 ? "strongText" : "emphasisText",
|
|
2575
5817
|
start: { ...events[open][1].end },
|
|
2576
5818
|
end: { ...events[index][1].start }
|
|
2577
5819
|
};
|
|
2578
|
-
group = {
|
|
5820
|
+
const group = {
|
|
2579
5821
|
type: use > 1 ? "strong" : "emphasis",
|
|
2580
5822
|
start: { ...openingSequence.start },
|
|
2581
5823
|
end: { ...closingSequence.end }
|
|
@@ -2637,6 +5879,8 @@ window.__ModuleLoader__.load({
|
|
|
2637
5879
|
context
|
|
2638
5880
|
]
|
|
2639
5881
|
]);
|
|
5882
|
+
/** @type {number} */
|
|
5883
|
+
let offset = 0;
|
|
2640
5884
|
if (events[index][1].end.offset - events[index][1].start.offset) {
|
|
2641
5885
|
offset = 2;
|
|
2642
5886
|
nextEvents = push(nextEvents, [[
|
|
@@ -2648,7 +5892,7 @@ window.__ModuleLoader__.load({
|
|
|
2648
5892
|
events[index][1],
|
|
2649
5893
|
context
|
|
2650
5894
|
]]);
|
|
2651
|
-
}
|
|
5895
|
+
}
|
|
2652
5896
|
splice(events, open - 1, index - open + 3, nextEvents);
|
|
2653
5897
|
index = open + nextEvents.length - offset - 2;
|
|
2654
5898
|
break;
|
|
@@ -2702,8 +5946,8 @@ window.__ModuleLoader__.load({
|
|
|
2702
5946
|
}
|
|
2703
5947
|
const token = effects.exit("attentionSequence");
|
|
2704
5948
|
const after = classifyCharacter(code);
|
|
2705
|
-
const open = !after || after === 2 && before || attentionMarkers.includes(code);
|
|
2706
|
-
const close = !before || before === 2 && after || attentionMarkers.includes(previous);
|
|
5949
|
+
const open = !after || after === 2 && before || attentionMarkers.includes(code) && code !== 42 && code !== 95;
|
|
5950
|
+
const close = !before || before === 2 && after || attentionMarkers.includes(previous) && previous !== 42 && previous !== 95;
|
|
2707
5951
|
token._open = Boolean(marker === 42 ? open : open && (before || !close));
|
|
2708
5952
|
token._close = Boolean(marker === 42 ? close : close && (after || !open));
|
|
2709
5953
|
return ok(code);
|
|
@@ -2728,7 +5972,7 @@ window.__ModuleLoader__.load({
|
|
|
2728
5972
|
point._bufferIndex += offset;
|
|
2729
5973
|
}
|
|
2730
5974
|
//#endregion
|
|
2731
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
5975
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/autolink.js
|
|
2732
5976
|
/**
|
|
2733
5977
|
* @import {
|
|
2734
5978
|
* Construct,
|
|
@@ -2939,7 +6183,7 @@ window.__ModuleLoader__.load({
|
|
|
2939
6183
|
}
|
|
2940
6184
|
}
|
|
2941
6185
|
//#endregion
|
|
2942
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6186
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/blank-line.js
|
|
2943
6187
|
/**
|
|
2944
6188
|
* @import {
|
|
2945
6189
|
* Construct,
|
|
@@ -2996,7 +6240,7 @@ window.__ModuleLoader__.load({
|
|
|
2996
6240
|
}
|
|
2997
6241
|
}
|
|
2998
6242
|
//#endregion
|
|
2999
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6243
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/block-quote.js
|
|
3000
6244
|
/**
|
|
3001
6245
|
* @import {
|
|
3002
6246
|
* Construct,
|
|
@@ -3123,7 +6367,7 @@ window.__ModuleLoader__.load({
|
|
|
3123
6367
|
effects.exit("blockQuote");
|
|
3124
6368
|
}
|
|
3125
6369
|
//#endregion
|
|
3126
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6370
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/character-escape.js
|
|
3127
6371
|
/**
|
|
3128
6372
|
* @import {
|
|
3129
6373
|
* Construct,
|
|
@@ -3183,7 +6427,7 @@ window.__ModuleLoader__.load({
|
|
|
3183
6427
|
}
|
|
3184
6428
|
}
|
|
3185
6429
|
//#endregion
|
|
3186
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6430
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/character-reference.js
|
|
3187
6431
|
/**
|
|
3188
6432
|
* @import {
|
|
3189
6433
|
* Code,
|
|
@@ -3321,7 +6565,55 @@ window.__ModuleLoader__.load({
|
|
|
3321
6565
|
}
|
|
3322
6566
|
}
|
|
3323
6567
|
//#endregion
|
|
3324
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6568
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.js
|
|
6569
|
+
/** @type {Construct} */
|
|
6570
|
+
const nonLazyContinuation = {
|
|
6571
|
+
partial: true,
|
|
6572
|
+
tokenize: tokenizeNonLazyContinuation
|
|
6573
|
+
};
|
|
6574
|
+
/**
|
|
6575
|
+
* @this {TokenizeContext}
|
|
6576
|
+
* Context.
|
|
6577
|
+
* @type {Tokenizer}
|
|
6578
|
+
*/
|
|
6579
|
+
function tokenizeNonLazyContinuation(effects, ok, nok) {
|
|
6580
|
+
const self = this;
|
|
6581
|
+
return start;
|
|
6582
|
+
/**
|
|
6583
|
+
* At eol, before continuation.
|
|
6584
|
+
*
|
|
6585
|
+
* ```markdown
|
|
6586
|
+
* > | * ```js
|
|
6587
|
+
* ^
|
|
6588
|
+
* | b
|
|
6589
|
+
* ```
|
|
6590
|
+
*
|
|
6591
|
+
* @type {State}
|
|
6592
|
+
*/
|
|
6593
|
+
function start(code) {
|
|
6594
|
+
if (code === null) return nok(code);
|
|
6595
|
+
effects.enter("lineEnding");
|
|
6596
|
+
effects.consume(code);
|
|
6597
|
+
effects.exit("lineEnding");
|
|
6598
|
+
return after;
|
|
6599
|
+
}
|
|
6600
|
+
/**
|
|
6601
|
+
* A continuation.
|
|
6602
|
+
*
|
|
6603
|
+
* ```markdown
|
|
6604
|
+
* | * ```js
|
|
6605
|
+
* > | b
|
|
6606
|
+
* ^
|
|
6607
|
+
* ```
|
|
6608
|
+
*
|
|
6609
|
+
* @type {State}
|
|
6610
|
+
*/
|
|
6611
|
+
function after(code) {
|
|
6612
|
+
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
//#endregion
|
|
6616
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-fenced.js
|
|
3325
6617
|
/**
|
|
3326
6618
|
* @import {
|
|
3327
6619
|
* Code,
|
|
@@ -3332,11 +6624,6 @@ window.__ModuleLoader__.load({
|
|
|
3332
6624
|
* } from 'micromark-util-types'
|
|
3333
6625
|
*/
|
|
3334
6626
|
/** @type {Construct} */
|
|
3335
|
-
const nonLazyContinuation = {
|
|
3336
|
-
partial: true,
|
|
3337
|
-
tokenize: tokenizeNonLazyContinuation
|
|
3338
|
-
};
|
|
3339
|
-
/** @type {Construct} */
|
|
3340
6627
|
const codeFenced = {
|
|
3341
6628
|
concrete: true,
|
|
3342
6629
|
name: "codeFenced",
|
|
@@ -3706,37 +6993,8 @@ window.__ModuleLoader__.load({
|
|
|
3706
6993
|
}
|
|
3707
6994
|
}
|
|
3708
6995
|
}
|
|
3709
|
-
/**
|
|
3710
|
-
* @this {TokenizeContext}
|
|
3711
|
-
* Context.
|
|
3712
|
-
* @type {Tokenizer}
|
|
3713
|
-
*/
|
|
3714
|
-
function tokenizeNonLazyContinuation(effects, ok, nok) {
|
|
3715
|
-
const self = this;
|
|
3716
|
-
return start;
|
|
3717
|
-
/**
|
|
3718
|
-
*
|
|
3719
|
-
*
|
|
3720
|
-
* @type {State}
|
|
3721
|
-
*/
|
|
3722
|
-
function start(code) {
|
|
3723
|
-
if (code === null) return nok(code);
|
|
3724
|
-
effects.enter("lineEnding");
|
|
3725
|
-
effects.consume(code);
|
|
3726
|
-
effects.exit("lineEnding");
|
|
3727
|
-
return lineStart;
|
|
3728
|
-
}
|
|
3729
|
-
/**
|
|
3730
|
-
*
|
|
3731
|
-
*
|
|
3732
|
-
* @type {State}
|
|
3733
|
-
*/
|
|
3734
|
-
function lineStart(code) {
|
|
3735
|
-
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
6996
|
//#endregion
|
|
3739
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
6997
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-indented.js
|
|
3740
6998
|
/**
|
|
3741
6999
|
* @import {
|
|
3742
7000
|
* Construct,
|
|
@@ -3761,7 +7019,6 @@ window.__ModuleLoader__.load({
|
|
|
3761
7019
|
* @type {Tokenizer}
|
|
3762
7020
|
*/
|
|
3763
7021
|
function tokenizeCodeIndented(effects, ok, nok) {
|
|
3764
|
-
const self = this;
|
|
3765
7022
|
return start;
|
|
3766
7023
|
/**
|
|
3767
7024
|
* Start of code (indented).
|
|
@@ -3779,21 +7036,7 @@ window.__ModuleLoader__.load({
|
|
|
3779
7036
|
*/
|
|
3780
7037
|
function start(code) {
|
|
3781
7038
|
effects.enter("codeIndented");
|
|
3782
|
-
return
|
|
3783
|
-
}
|
|
3784
|
-
/**
|
|
3785
|
-
* At start, after 1 or 4 spaces.
|
|
3786
|
-
*
|
|
3787
|
-
* ```markdown
|
|
3788
|
-
* > | aaa
|
|
3789
|
-
* ^
|
|
3790
|
-
* ```
|
|
3791
|
-
*
|
|
3792
|
-
* @type {State}
|
|
3793
|
-
*/
|
|
3794
|
-
function afterPrefix(code) {
|
|
3795
|
-
const tail = self.events[self.events.length - 1];
|
|
3796
|
-
return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? atBreak(code) : nok(code);
|
|
7039
|
+
return factorySpaceMinMax(effects, atBreak, nok, "linePrefix", 4, 4)(code);
|
|
3797
7040
|
}
|
|
3798
7041
|
/**
|
|
3799
7042
|
* At a break.
|
|
@@ -3862,25 +7105,28 @@ window.__ModuleLoader__.load({
|
|
|
3862
7105
|
effects.exit("lineEnding");
|
|
3863
7106
|
return furtherStart;
|
|
3864
7107
|
}
|
|
3865
|
-
return
|
|
7108
|
+
return factorySpaceMinMax(effects, ok, onNotEnoughPrefix, "linePrefix", 4, 4)(code);
|
|
3866
7109
|
}
|
|
3867
7110
|
/**
|
|
3868
|
-
*
|
|
7111
|
+
* After not enough of a prefix.
|
|
7112
|
+
*
|
|
7113
|
+
* A following line ending is another (potentially blank) line to try,
|
|
7114
|
+
* anything else means this isn’t a continuation.
|
|
3869
7115
|
*
|
|
3870
7116
|
* ```markdown
|
|
3871
7117
|
* > | aaa
|
|
3872
|
-
*
|
|
7118
|
+
*
|
|
7119
|
+
* | bbb
|
|
3873
7120
|
* ```
|
|
3874
7121
|
*
|
|
3875
7122
|
* @type {State}
|
|
3876
7123
|
*/
|
|
3877
|
-
function
|
|
3878
|
-
|
|
3879
|
-
return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? ok(code) : markdownLineEnding(code) ? furtherStart(code) : nok(code);
|
|
7124
|
+
function onNotEnoughPrefix(code) {
|
|
7125
|
+
return markdownLineEnding(code) ? furtherStart(code) : nok(code);
|
|
3880
7126
|
}
|
|
3881
7127
|
}
|
|
3882
7128
|
//#endregion
|
|
3883
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
7129
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/code-text.js
|
|
3884
7130
|
/**
|
|
3885
7131
|
* @import {
|
|
3886
7132
|
* Construct,
|
|
@@ -4450,7 +7696,7 @@ window.__ModuleLoader__.load({
|
|
|
4450
7696
|
return gaps;
|
|
4451
7697
|
}
|
|
4452
7698
|
//#endregion
|
|
4453
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
7699
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/content.js
|
|
4454
7700
|
/**
|
|
4455
7701
|
* @import {
|
|
4456
7702
|
* Construct,
|
|
@@ -5086,7 +8332,7 @@ window.__ModuleLoader__.load({
|
|
|
5086
8332
|
}
|
|
5087
8333
|
}
|
|
5088
8334
|
//#endregion
|
|
5089
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
8335
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/definition.js
|
|
5090
8336
|
/**
|
|
5091
8337
|
* @import {
|
|
5092
8338
|
* Construct,
|
|
@@ -5301,7 +8547,7 @@ window.__ModuleLoader__.load({
|
|
|
5301
8547
|
}
|
|
5302
8548
|
}
|
|
5303
8549
|
//#endregion
|
|
5304
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
8550
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/hard-break-escape.js
|
|
5305
8551
|
/**
|
|
5306
8552
|
* @import {
|
|
5307
8553
|
* Construct,
|
|
@@ -5358,7 +8604,7 @@ window.__ModuleLoader__.load({
|
|
|
5358
8604
|
}
|
|
5359
8605
|
}
|
|
5360
8606
|
//#endregion
|
|
5361
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
8607
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/heading-atx.js
|
|
5362
8608
|
/**
|
|
5363
8609
|
* @import {
|
|
5364
8610
|
* Construct,
|
|
@@ -5379,20 +8625,16 @@ window.__ModuleLoader__.load({
|
|
|
5379
8625
|
function resolveHeadingAtx(events, context) {
|
|
5380
8626
|
let contentEnd = events.length - 2;
|
|
5381
8627
|
let contentStart = 3;
|
|
5382
|
-
/** @type {Token} */
|
|
5383
|
-
let content;
|
|
5384
|
-
/** @type {Token} */
|
|
5385
|
-
let text;
|
|
5386
8628
|
if (events[contentStart][1].type === "whitespace") contentStart += 2;
|
|
5387
8629
|
if (contentEnd - 2 > contentStart && events[contentEnd][1].type === "whitespace") contentEnd -= 2;
|
|
5388
8630
|
if (events[contentEnd][1].type === "atxHeadingSequence" && (contentStart === contentEnd - 1 || contentEnd - 4 > contentStart && events[contentEnd - 2][1].type === "whitespace")) contentEnd -= contentStart + 1 === contentEnd ? 2 : 4;
|
|
5389
8631
|
if (contentEnd > contentStart) {
|
|
5390
|
-
content = {
|
|
8632
|
+
const content = {
|
|
5391
8633
|
type: "atxHeadingText",
|
|
5392
8634
|
start: events[contentStart][1].start,
|
|
5393
8635
|
end: events[contentEnd][1].end
|
|
5394
8636
|
};
|
|
5395
|
-
text = {
|
|
8637
|
+
const text = {
|
|
5396
8638
|
type: "chunkText",
|
|
5397
8639
|
start: events[contentStart][1].start,
|
|
5398
8640
|
end: events[contentEnd][1].end,
|
|
@@ -5642,7 +8884,7 @@ window.__ModuleLoader__.load({
|
|
|
5642
8884
|
"textarea"
|
|
5643
8885
|
];
|
|
5644
8886
|
//#endregion
|
|
5645
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
8887
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/html-flow.js
|
|
5646
8888
|
/**
|
|
5647
8889
|
* @import {
|
|
5648
8890
|
* Code,
|
|
@@ -5665,10 +8907,6 @@ window.__ModuleLoader__.load({
|
|
|
5665
8907
|
partial: true,
|
|
5666
8908
|
tokenize: tokenizeBlankLineBefore
|
|
5667
8909
|
};
|
|
5668
|
-
const nonLazyContinuationStart = {
|
|
5669
|
-
partial: true,
|
|
5670
|
-
tokenize: tokenizeNonLazyContinuationStart
|
|
5671
|
-
};
|
|
5672
8910
|
/** @type {Resolver} */
|
|
5673
8911
|
function resolveToHtmlFlow(events) {
|
|
5674
8912
|
let index = events.length;
|
|
@@ -6171,7 +9409,7 @@ window.__ModuleLoader__.load({
|
|
|
6171
9409
|
* @type {State}
|
|
6172
9410
|
*/
|
|
6173
9411
|
function continuationStart(code) {
|
|
6174
|
-
return effects.check(
|
|
9412
|
+
return effects.check(nonLazyContinuation, continuationStartNonLazy, continuationAfter)(code);
|
|
6175
9413
|
}
|
|
6176
9414
|
/**
|
|
6177
9415
|
* In continuation, at eol, before non-lazy content.
|
|
@@ -6351,49 +9589,6 @@ window.__ModuleLoader__.load({
|
|
|
6351
9589
|
* Context.
|
|
6352
9590
|
* @type {Tokenizer}
|
|
6353
9591
|
*/
|
|
6354
|
-
function tokenizeNonLazyContinuationStart(effects, ok, nok) {
|
|
6355
|
-
const self = this;
|
|
6356
|
-
return start;
|
|
6357
|
-
/**
|
|
6358
|
-
* At eol, before continuation.
|
|
6359
|
-
*
|
|
6360
|
-
* ```markdown
|
|
6361
|
-
* > | * ```js
|
|
6362
|
-
* ^
|
|
6363
|
-
* | b
|
|
6364
|
-
* ```
|
|
6365
|
-
*
|
|
6366
|
-
* @type {State}
|
|
6367
|
-
*/
|
|
6368
|
-
function start(code) {
|
|
6369
|
-
if (markdownLineEnding(code)) {
|
|
6370
|
-
effects.enter("lineEnding");
|
|
6371
|
-
effects.consume(code);
|
|
6372
|
-
effects.exit("lineEnding");
|
|
6373
|
-
return after;
|
|
6374
|
-
}
|
|
6375
|
-
return nok(code);
|
|
6376
|
-
}
|
|
6377
|
-
/**
|
|
6378
|
-
* A continuation.
|
|
6379
|
-
*
|
|
6380
|
-
* ```markdown
|
|
6381
|
-
* | * ```js
|
|
6382
|
-
* > | b
|
|
6383
|
-
* ^
|
|
6384
|
-
* ```
|
|
6385
|
-
*
|
|
6386
|
-
* @type {State}
|
|
6387
|
-
*/
|
|
6388
|
-
function after(code) {
|
|
6389
|
-
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
|
|
6390
|
-
}
|
|
6391
|
-
}
|
|
6392
|
-
/**
|
|
6393
|
-
* @this {TokenizeContext}
|
|
6394
|
-
* Context.
|
|
6395
|
-
* @type {Tokenizer}
|
|
6396
|
-
*/
|
|
6397
9592
|
function tokenizeBlankLineBefore(effects, ok, nok) {
|
|
6398
9593
|
return start;
|
|
6399
9594
|
/**
|
|
@@ -6415,7 +9610,7 @@ window.__ModuleLoader__.load({
|
|
|
6415
9610
|
}
|
|
6416
9611
|
}
|
|
6417
9612
|
//#endregion
|
|
6418
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
9613
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/html-text.js
|
|
6419
9614
|
/**
|
|
6420
9615
|
* @import {
|
|
6421
9616
|
* Code,
|
|
@@ -7029,7 +10224,7 @@ window.__ModuleLoader__.load({
|
|
|
7029
10224
|
}
|
|
7030
10225
|
}
|
|
7031
10226
|
//#endregion
|
|
7032
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10227
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-end.js
|
|
7033
10228
|
/**
|
|
7034
10229
|
* @import {
|
|
7035
10230
|
* Construct,
|
|
@@ -7075,8 +10270,6 @@ window.__ModuleLoader__.load({
|
|
|
7075
10270
|
function resolveToLabelEnd(events, context) {
|
|
7076
10271
|
let index = events.length;
|
|
7077
10272
|
let offset = 0;
|
|
7078
|
-
/** @type {Token} */
|
|
7079
|
-
let token;
|
|
7080
10273
|
/** @type {number | undefined} */
|
|
7081
10274
|
let open;
|
|
7082
10275
|
/** @type {number | undefined} */
|
|
@@ -7084,7 +10277,7 @@ window.__ModuleLoader__.load({
|
|
|
7084
10277
|
/** @type {Array<Event>} */
|
|
7085
10278
|
let media;
|
|
7086
10279
|
while (index--) {
|
|
7087
|
-
token = events[index][1];
|
|
10280
|
+
const token = events[index][1];
|
|
7088
10281
|
if (open) {
|
|
7089
10282
|
if (token.type === "link" || token.type === "labelLink" && token._inactive) break;
|
|
7090
10283
|
if (events[index][0] === "enter" && token.type === "labelLink") token._inactive = true;
|
|
@@ -7159,14 +10352,14 @@ window.__ModuleLoader__.load({
|
|
|
7159
10352
|
*/
|
|
7160
10353
|
function tokenizeLabelEnd(effects, ok, nok) {
|
|
7161
10354
|
const self = this;
|
|
7162
|
-
|
|
10355
|
+
const labelStarts = self._labelStarts;
|
|
7163
10356
|
/** @type {Token} */
|
|
7164
10357
|
let labelStart;
|
|
7165
10358
|
/** @type {boolean} */
|
|
7166
10359
|
let defined;
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
10360
|
+
if (labelStarts) {
|
|
10361
|
+
while (labelStarts.length > 0 && labelStarts[labelStarts.length - 1]._balanced) labelStarts.pop();
|
|
10362
|
+
labelStart = labelStarts[labelStarts.length - 1];
|
|
7170
10363
|
}
|
|
7171
10364
|
return start;
|
|
7172
10365
|
/**
|
|
@@ -7253,6 +10446,7 @@ window.__ModuleLoader__.load({
|
|
|
7253
10446
|
* @type {State}
|
|
7254
10447
|
*/
|
|
7255
10448
|
function labelEndOk(code) {
|
|
10449
|
+
labelStarts.pop();
|
|
7256
10450
|
return ok(code);
|
|
7257
10451
|
}
|
|
7258
10452
|
/**
|
|
@@ -7499,11 +10693,12 @@ window.__ModuleLoader__.load({
|
|
|
7499
10693
|
}
|
|
7500
10694
|
}
|
|
7501
10695
|
//#endregion
|
|
7502
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10696
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-start-image.js
|
|
7503
10697
|
/**
|
|
7504
10698
|
* @import {
|
|
7505
10699
|
* Construct,
|
|
7506
10700
|
* State,
|
|
10701
|
+
* Token,
|
|
7507
10702
|
* TokenizeContext,
|
|
7508
10703
|
* Tokenizer
|
|
7509
10704
|
* } from 'micromark-util-types'
|
|
@@ -7521,6 +10716,8 @@ window.__ModuleLoader__.load({
|
|
|
7521
10716
|
*/
|
|
7522
10717
|
function tokenizeLabelStartImage(effects, ok, nok) {
|
|
7523
10718
|
const self = this;
|
|
10719
|
+
/** @type {Token} */
|
|
10720
|
+
let labelImage;
|
|
7524
10721
|
return start;
|
|
7525
10722
|
/**
|
|
7526
10723
|
* Start of label (image) start.
|
|
@@ -7554,7 +10751,7 @@ window.__ModuleLoader__.load({
|
|
|
7554
10751
|
effects.enter("labelMarker");
|
|
7555
10752
|
effects.consume(code);
|
|
7556
10753
|
effects.exit("labelMarker");
|
|
7557
|
-
effects.exit("labelImage");
|
|
10754
|
+
labelImage = effects.exit("labelImage");
|
|
7558
10755
|
return after;
|
|
7559
10756
|
}
|
|
7560
10757
|
return nok(code);
|
|
@@ -7587,16 +10784,20 @@ window.__ModuleLoader__.load({
|
|
|
7587
10784
|
* @type {State}
|
|
7588
10785
|
*/
|
|
7589
10786
|
function after(code) {
|
|
7590
|
-
/* c8 ignore next
|
|
7591
|
-
|
|
10787
|
+
/* c8 ignore next 6 */
|
|
10788
|
+
if (code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs) return nok(code);
|
|
10789
|
+
self._labelStarts = self._labelStarts || [];
|
|
10790
|
+
self._labelStarts.push(labelImage);
|
|
10791
|
+
return ok(code);
|
|
7592
10792
|
}
|
|
7593
10793
|
}
|
|
7594
10794
|
//#endregion
|
|
7595
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10795
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/label-start-link.js
|
|
7596
10796
|
/**
|
|
7597
10797
|
* @import {
|
|
7598
10798
|
* Construct,
|
|
7599
10799
|
* State,
|
|
10800
|
+
* Token,
|
|
7600
10801
|
* TokenizeContext,
|
|
7601
10802
|
* Tokenizer
|
|
7602
10803
|
* } from 'micromark-util-types'
|
|
@@ -7614,6 +10815,8 @@ window.__ModuleLoader__.load({
|
|
|
7614
10815
|
*/
|
|
7615
10816
|
function tokenizeLabelStartLink(effects, ok, nok) {
|
|
7616
10817
|
const self = this;
|
|
10818
|
+
/** @type {Token} */
|
|
10819
|
+
let labelLink;
|
|
7617
10820
|
return start;
|
|
7618
10821
|
/**
|
|
7619
10822
|
* Start of label (link) start.
|
|
@@ -7630,17 +10833,20 @@ window.__ModuleLoader__.load({
|
|
|
7630
10833
|
effects.enter("labelMarker");
|
|
7631
10834
|
effects.consume(code);
|
|
7632
10835
|
effects.exit("labelMarker");
|
|
7633
|
-
effects.exit("labelLink");
|
|
10836
|
+
labelLink = effects.exit("labelLink");
|
|
7634
10837
|
return after;
|
|
7635
10838
|
}
|
|
7636
10839
|
/** @type {State} */
|
|
7637
10840
|
function after(code) {
|
|
7638
|
-
/* c8 ignore next
|
|
7639
|
-
|
|
10841
|
+
/* c8 ignore next 6 */
|
|
10842
|
+
if (code === 94 && "_hiddenFootnoteSupport" in self.parser.constructs) return nok(code);
|
|
10843
|
+
self._labelStarts = self._labelStarts || [];
|
|
10844
|
+
self._labelStarts.push(labelLink);
|
|
10845
|
+
return ok(code);
|
|
7640
10846
|
}
|
|
7641
10847
|
}
|
|
7642
10848
|
//#endregion
|
|
7643
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10849
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/line-ending.js
|
|
7644
10850
|
/**
|
|
7645
10851
|
* @import {
|
|
7646
10852
|
* Construct,
|
|
@@ -7670,7 +10876,7 @@ window.__ModuleLoader__.load({
|
|
|
7670
10876
|
}
|
|
7671
10877
|
}
|
|
7672
10878
|
//#endregion
|
|
7673
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10879
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/thematic-break.js
|
|
7674
10880
|
/**
|
|
7675
10881
|
* @import {
|
|
7676
10882
|
* Code,
|
|
@@ -7765,7 +10971,7 @@ window.__ModuleLoader__.load({
|
|
|
7765
10971
|
}
|
|
7766
10972
|
}
|
|
7767
10973
|
//#endregion
|
|
7768
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
10974
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/list.js
|
|
7769
10975
|
/**
|
|
7770
10976
|
* @import {
|
|
7771
10977
|
* Code,
|
|
@@ -7937,7 +11143,7 @@ window.__ModuleLoader__.load({
|
|
|
7937
11143
|
}
|
|
7938
11144
|
}
|
|
7939
11145
|
//#endregion
|
|
7940
|
-
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.
|
|
11146
|
+
//#region ../../../node_modules/.pnpm/micromark-core-commonmark@2.0.4/node_modules/micromark-core-commonmark/lib/setext-underline.js
|
|
7941
11147
|
/**
|
|
7942
11148
|
* @import {
|
|
7943
11149
|
* Code,
|
|
@@ -7956,6 +11162,7 @@ window.__ModuleLoader__.load({
|
|
|
7956
11162
|
};
|
|
7957
11163
|
/** @type {Resolver} */
|
|
7958
11164
|
function resolveToSetextUnderline(events, context) {
|
|
11165
|
+
const editMap = new EditMap$1();
|
|
7959
11166
|
let index = events.length;
|
|
7960
11167
|
/** @type {number | undefined} */
|
|
7961
11168
|
let content;
|
|
@@ -7970,7 +11177,7 @@ window.__ModuleLoader__.load({
|
|
|
7970
11177
|
}
|
|
7971
11178
|
if (events[index][1].type === "paragraph") text = index;
|
|
7972
11179
|
} else {
|
|
7973
|
-
if (events[index][1].type === "content")
|
|
11180
|
+
if (events[index][1].type === "content") editMap.add(index, 1, []);
|
|
7974
11181
|
if (!definition && events[index][1].type === "definition") definition = index;
|
|
7975
11182
|
}
|
|
7976
11183
|
const heading = {
|
|
@@ -7980,23 +11187,24 @@ window.__ModuleLoader__.load({
|
|
|
7980
11187
|
};
|
|
7981
11188
|
events[text][1].type = "setextHeadingText";
|
|
7982
11189
|
if (definition) {
|
|
7983
|
-
|
|
11190
|
+
editMap.add(text, 0, [[
|
|
7984
11191
|
"enter",
|
|
7985
11192
|
heading,
|
|
7986
11193
|
context
|
|
7987
|
-
]);
|
|
7988
|
-
|
|
11194
|
+
]]);
|
|
11195
|
+
editMap.add(definition + 1, 0, [[
|
|
7989
11196
|
"exit",
|
|
7990
11197
|
events[content][1],
|
|
7991
11198
|
context
|
|
7992
|
-
]);
|
|
11199
|
+
]]);
|
|
7993
11200
|
events[content][1].end = { ...events[definition][1].end };
|
|
7994
11201
|
} else events[content][1] = heading;
|
|
7995
|
-
events.
|
|
11202
|
+
editMap.add(events.length, 0, [[
|
|
7996
11203
|
"exit",
|
|
7997
11204
|
heading,
|
|
7998
11205
|
context
|
|
7999
|
-
]);
|
|
11206
|
+
]]);
|
|
11207
|
+
editMap.consume(events);
|
|
8000
11208
|
return events;
|
|
8001
11209
|
}
|
|
8002
11210
|
/**
|
|
@@ -8089,7 +11297,7 @@ window.__ModuleLoader__.load({
|
|
|
8089
11297
|
}
|
|
8090
11298
|
}
|
|
8091
11299
|
//#endregion
|
|
8092
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
11300
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/flow.js
|
|
8093
11301
|
/**
|
|
8094
11302
|
* @import {
|
|
8095
11303
|
* InitialConstruct,
|
|
@@ -8136,7 +11344,7 @@ window.__ModuleLoader__.load({
|
|
|
8136
11344
|
}
|
|
8137
11345
|
}
|
|
8138
11346
|
//#endregion
|
|
8139
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
11347
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/initialize/text.js
|
|
8140
11348
|
/**
|
|
8141
11349
|
* @import {
|
|
8142
11350
|
* Code,
|
|
@@ -8253,6 +11461,7 @@ window.__ModuleLoader__.load({
|
|
|
8253
11461
|
* @type {Resolver}
|
|
8254
11462
|
*/
|
|
8255
11463
|
function resolveAllLineSuffixes(events, context) {
|
|
11464
|
+
const editMap = new EditMap$1();
|
|
8256
11465
|
let eventIndex = 0;
|
|
8257
11466
|
while (++eventIndex <= events.length) if ((eventIndex === events.length || events[eventIndex][1].type === "lineEnding") && events[eventIndex - 1][1].type === "data") {
|
|
8258
11467
|
const data = events[eventIndex - 1][1];
|
|
@@ -8295,25 +11504,23 @@ window.__ModuleLoader__.load({
|
|
|
8295
11504
|
};
|
|
8296
11505
|
data.end = { ...token.start };
|
|
8297
11506
|
if (data.start.offset === data.end.offset) Object.assign(data, token);
|
|
8298
|
-
else
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
]);
|
|
8308
|
-
eventIndex += 2;
|
|
8309
|
-
}
|
|
11507
|
+
else editMap.add(eventIndex, 0, [[
|
|
11508
|
+
"enter",
|
|
11509
|
+
token,
|
|
11510
|
+
context
|
|
11511
|
+
], [
|
|
11512
|
+
"exit",
|
|
11513
|
+
token,
|
|
11514
|
+
context
|
|
11515
|
+
]]);
|
|
8310
11516
|
}
|
|
8311
11517
|
eventIndex++;
|
|
8312
11518
|
}
|
|
11519
|
+
editMap.consume(events);
|
|
8313
11520
|
return events;
|
|
8314
11521
|
}
|
|
8315
11522
|
//#endregion
|
|
8316
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
11523
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/constructs.js
|
|
8317
11524
|
/**
|
|
8318
11525
|
* @import {Extension} from 'micromark-util-types'
|
|
8319
11526
|
*/
|
|
@@ -8391,7 +11598,7 @@ window.__ModuleLoader__.load({
|
|
|
8391
11598
|
/** @satisfies {Extension['disable']} */
|
|
8392
11599
|
const disable = { null: [] };
|
|
8393
11600
|
//#endregion
|
|
8394
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
11601
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/create-tokenizer.js
|
|
8395
11602
|
/**
|
|
8396
11603
|
* @import {
|
|
8397
11604
|
* Chunk,
|
|
@@ -8547,12 +11754,10 @@ window.__ModuleLoader__.load({
|
|
|
8547
11754
|
* Nothing.
|
|
8548
11755
|
*/
|
|
8549
11756
|
function main() {
|
|
8550
|
-
/** @type {number} */
|
|
8551
|
-
let chunkIndex;
|
|
8552
11757
|
while (point._index < chunks.length) {
|
|
8553
11758
|
const chunk = chunks[point._index];
|
|
8554
11759
|
if (typeof chunk === "string") {
|
|
8555
|
-
chunkIndex = point._index;
|
|
11760
|
+
const chunkIndex = point._index;
|
|
8556
11761
|
if (point._bufferIndex < 0) point._bufferIndex = 0;
|
|
8557
11762
|
while (point._index === chunkIndex && point._bufferIndex < chunk.length) go(chunk.charCodeAt(point._bufferIndex));
|
|
8558
11763
|
} else go(chunk);
|
|
@@ -8860,7 +12065,7 @@ window.__ModuleLoader__.load({
|
|
|
8860
12065
|
return result.join("");
|
|
8861
12066
|
}
|
|
8862
12067
|
//#endregion
|
|
8863
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
12068
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/parse.js
|
|
8864
12069
|
/**
|
|
8865
12070
|
* @import {
|
|
8866
12071
|
* Create,
|
|
@@ -8904,7 +12109,7 @@ window.__ModuleLoader__.load({
|
|
|
8904
12109
|
}
|
|
8905
12110
|
}
|
|
8906
12111
|
//#endregion
|
|
8907
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
12112
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/postprocess.js
|
|
8908
12113
|
/**
|
|
8909
12114
|
* @import {Event} from 'micromark-util-types'
|
|
8910
12115
|
*/
|
|
@@ -8919,7 +12124,7 @@ window.__ModuleLoader__.load({
|
|
|
8919
12124
|
return events;
|
|
8920
12125
|
}
|
|
8921
12126
|
//#endregion
|
|
8922
|
-
//#region ../../../node_modules/.pnpm/micromark@4.0.
|
|
12127
|
+
//#region ../../../node_modules/.pnpm/micromark@4.0.3_supports-color@9.4.0/node_modules/micromark/lib/preprocess.js
|
|
8923
12128
|
/**
|
|
8924
12129
|
* @import {Chunk, Code, Encoding, Value} from 'micromark-util-types'
|
|
8925
12130
|
*/
|
|
@@ -8950,20 +12155,10 @@ window.__ModuleLoader__.load({
|
|
|
8950
12155
|
return preprocessor;
|
|
8951
12156
|
/** @type {Preprocessor} */
|
|
8952
12157
|
function preprocessor(value, encoding, end) {
|
|
12158
|
+
value = buffer + (typeof value === "string" ? value.toString() : new TextDecoder(encoding || void 0).decode(value));
|
|
8953
12159
|
/** @type {Array<Chunk>} */
|
|
8954
12160
|
const chunks = [];
|
|
8955
|
-
|
|
8956
|
-
let match;
|
|
8957
|
-
/** @type {number} */
|
|
8958
|
-
let next;
|
|
8959
|
-
/** @type {number} */
|
|
8960
|
-
let startPosition;
|
|
8961
|
-
/** @type {number} */
|
|
8962
|
-
let endPosition;
|
|
8963
|
-
/** @type {Code} */
|
|
8964
|
-
let code;
|
|
8965
|
-
value = buffer + (typeof value === "string" ? value.toString() : new TextDecoder(encoding || void 0).decode(value));
|
|
8966
|
-
startPosition = 0;
|
|
12161
|
+
let startPosition = 0;
|
|
8967
12162
|
buffer = "";
|
|
8968
12163
|
if (start) {
|
|
8969
12164
|
if (value.charCodeAt(0) === 65279) startPosition++;
|
|
@@ -8971,9 +12166,9 @@ window.__ModuleLoader__.load({
|
|
|
8971
12166
|
}
|
|
8972
12167
|
while (startPosition < value.length) {
|
|
8973
12168
|
search.lastIndex = startPosition;
|
|
8974
|
-
match = search.exec(value);
|
|
8975
|
-
endPosition = match && match.index !== void 0 ? match.index : value.length;
|
|
8976
|
-
code = value.charCodeAt(endPosition);
|
|
12169
|
+
const match = search.exec(value);
|
|
12170
|
+
const endPosition = match && match.index !== void 0 ? match.index : value.length;
|
|
12171
|
+
const code = value.charCodeAt(endPosition);
|
|
8977
12172
|
if (!match) {
|
|
8978
12173
|
buffer = value.slice(startPosition);
|
|
8979
12174
|
break;
|
|
@@ -8995,11 +12190,12 @@ window.__ModuleLoader__.load({
|
|
|
8995
12190
|
chunks.push(65533);
|
|
8996
12191
|
column++;
|
|
8997
12192
|
break;
|
|
8998
|
-
case 9:
|
|
8999
|
-
next = Math.ceil(column / 4) * 4;
|
|
12193
|
+
case 9: {
|
|
12194
|
+
const next = Math.ceil(column / 4) * 4;
|
|
9000
12195
|
chunks.push(-2);
|
|
9001
12196
|
while (column++ < next) chunks.push(-1);
|
|
9002
12197
|
break;
|
|
12198
|
+
}
|
|
9003
12199
|
case 10:
|
|
9004
12200
|
chunks.push(-4);
|
|
9005
12201
|
column = 1;
|
|
@@ -11074,7 +14270,8 @@ window.__ModuleLoader__.load({
|
|
|
11074
14270
|
};
|
|
11075
14271
|
}
|
|
11076
14272
|
//#endregion
|
|
11077
|
-
//#region ../../../node_modules/.pnpm/mdast-util-gfm-strikethrough@2.0.
|
|
14273
|
+
//#region ../../../node_modules/.pnpm/mdast-util-gfm-strikethrough@2.0.1_supports-color@9.4.0/node_modules/mdast-util-gfm-strikethrough/lib/index.js
|
|
14274
|
+
handleDelete.attention = attentionDelete;
|
|
11078
14275
|
handleDelete.peek = peekDelete;
|
|
11079
14276
|
/**
|
|
11080
14277
|
* Create an extension for `mdast-util-from-markdown` to enable GFM
|
|
@@ -11091,16 +14288,21 @@ window.__ModuleLoader__.load({
|
|
|
11091
14288
|
};
|
|
11092
14289
|
}
|
|
11093
14290
|
/**
|
|
14291
|
+
* Enter strikethrough.
|
|
14292
|
+
*
|
|
11094
14293
|
* @this {CompileContext}
|
|
11095
14294
|
* @type {FromMarkdownHandle}
|
|
11096
14295
|
*/
|
|
11097
14296
|
function enterStrikethrough(token) {
|
|
11098
14297
|
this.enter({
|
|
11099
14298
|
type: "delete",
|
|
11100
|
-
children: []
|
|
14299
|
+
children: [],
|
|
14300
|
+
position: void 0
|
|
11101
14301
|
}, token);
|
|
11102
14302
|
}
|
|
11103
14303
|
/**
|
|
14304
|
+
* Exit strikethrough.
|
|
14305
|
+
*
|
|
11104
14306
|
* @this {CompileContext}
|
|
11105
14307
|
* @type {FromMarkdownHandle}
|
|
11106
14308
|
*/
|
|
@@ -11108,23 +14310,43 @@ window.__ModuleLoader__.load({
|
|
|
11108
14310
|
this.exit(token);
|
|
11109
14311
|
}
|
|
11110
14312
|
/**
|
|
14313
|
+
* Serialize a delete node.
|
|
14314
|
+
*
|
|
11111
14315
|
* @type {ToMarkdownHandle}
|
|
11112
14316
|
* @param {Delete} node
|
|
14317
|
+
* Node to serialize.
|
|
11113
14318
|
*/
|
|
11114
14319
|
function handleDelete(node, _, state, info) {
|
|
11115
14320
|
const tracker = state.createTracker(info);
|
|
14321
|
+
const sequence = state.stack.includes("strikethrough") ? "~" : "~~";
|
|
11116
14322
|
const exit = state.enter("strikethrough");
|
|
11117
|
-
let value = tracker.move(
|
|
14323
|
+
let value = tracker.move(sequence);
|
|
11118
14324
|
value += state.containerPhrasing(node, {
|
|
11119
14325
|
...tracker.current(),
|
|
11120
14326
|
before: value,
|
|
11121
14327
|
after: "~"
|
|
11122
14328
|
});
|
|
11123
|
-
value += tracker.move(
|
|
14329
|
+
value += tracker.move(sequence);
|
|
11124
14330
|
exit();
|
|
11125
14331
|
return value;
|
|
11126
14332
|
}
|
|
11127
|
-
/**
|
|
14333
|
+
/**
|
|
14334
|
+
* Serialize a delete node as attention.
|
|
14335
|
+
*
|
|
14336
|
+
* @type {Attention}
|
|
14337
|
+
*/
|
|
14338
|
+
function attentionDelete() {
|
|
14339
|
+
return {
|
|
14340
|
+
construct: "strikethrough",
|
|
14341
|
+
markers: ["~"],
|
|
14342
|
+
sizes: [2, 1]
|
|
14343
|
+
};
|
|
14344
|
+
}
|
|
14345
|
+
/**
|
|
14346
|
+
* Peek the first character of a delete node.
|
|
14347
|
+
*
|
|
14348
|
+
* @type {ToMarkdownHandle}
|
|
14349
|
+
*/
|
|
11128
14350
|
function peekDelete() {
|
|
11129
14351
|
return "~";
|
|
11130
14352
|
}
|
|
@@ -14398,10 +17620,24 @@ window.__ModuleLoader__.load({
|
|
|
14398
17620
|
}
|
|
14399
17621
|
//#endregion
|
|
14400
17622
|
//#region src/client/index.ts
|
|
14401
|
-
|
|
14402
|
-
|
|
17623
|
+
/**
|
|
17624
|
+
* 需要的**服务名**:样式与引用那半不要服务,schema 表单那半要槽位、字典与配置表单。
|
|
17625
|
+
*
|
|
17626
|
+
* 与包清单里的 `dsh.client.inject` 不是一回事:那份是**装配行 id**(谁先到),交给模块系统排 arrival 顺序;
|
|
17627
|
+
* 这里读的是 cordis 服务名(谁已 provide),Loader 拿它决定这一行何时才算 active。写错成包名,这一行就永远
|
|
17628
|
+
* pending(`web boot: ... waiting for services`)。
|
|
17629
|
+
*
|
|
17630
|
+
* 值直接取自 schema 表单那一面,不另抄一份清单。
|
|
17631
|
+
*/
|
|
17632
|
+
const inject = [...inject$1];
|
|
17633
|
+
function apply(ctx) {
|
|
17634
|
+
apply$1(ctx);
|
|
17635
|
+
}
|
|
14403
17636
|
//#endregion
|
|
14404
17637
|
exports.ReferenceMarkdown = ReferenceMarkdown;
|
|
17638
|
+
exports.SCHEMA_FORM_NS = NS;
|
|
17639
|
+
exports.SchemaFormController = SchemaFormController;
|
|
17640
|
+
exports.SchemaFormHints = SchemaFormHints;
|
|
14405
17641
|
exports.SettingsForm = SettingsForm;
|
|
14406
17642
|
exports.SettingsFormModel = SettingsFormModel;
|
|
14407
17643
|
exports.SettingsSecretField = SettingsSecretField;
|
|
@@ -14411,6 +17647,7 @@ window.__ModuleLoader__.load({
|
|
|
14411
17647
|
exports.apply = apply;
|
|
14412
17648
|
exports.designTokens = designTokens;
|
|
14413
17649
|
exports.dsw = dsw;
|
|
17650
|
+
exports.failureOf = failureOf;
|
|
14414
17651
|
exports.findReferences = findReferences;
|
|
14415
17652
|
exports.formatReference = formatReference;
|
|
14416
17653
|
exports.formatReferenceMention = formatReferenceMention;
|
|
@@ -14419,12 +17656,21 @@ window.__ModuleLoader__.load({
|
|
|
14419
17656
|
exports.markdownLabels = markdownLabels;
|
|
14420
17657
|
exports.parseReference = parseReference;
|
|
14421
17658
|
exports.parseReferenceToken = parseReferenceToken;
|
|
17659
|
+
exports.projectRoot = projectRoot;
|
|
14422
17660
|
exports.referenceMentions = referenceMentions;
|
|
17661
|
+
exports.rowRegistrations = rowRegistrations;
|
|
17662
|
+
Object.defineProperty(exports, "schemaForm", {
|
|
17663
|
+
enumerable: true,
|
|
17664
|
+
get: function() {
|
|
17665
|
+
return schema_form_exports;
|
|
17666
|
+
}
|
|
17667
|
+
});
|
|
14423
17668
|
exports.settingsNumberField = settingsNumberField;
|
|
14424
17669
|
exports.settingsTextField = settingsTextField;
|
|
14425
17670
|
exports.styleOf = styleOf;
|
|
14426
17671
|
exports.styled = styled;
|
|
14427
17672
|
exports.styling = styling;
|
|
17673
|
+
exports.walkFields = walkFields;
|
|
14428
17674
|
return module.exports;
|
|
14429
17675
|
}
|
|
14430
17676
|
});
|