@michengai/dsh-skills-manager 0.1.16 → 0.1.18
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 +1 -0
- package/README.zh-CN.md +1 -0
- package/lib/client.js +267 -6
- package/lib/core.js +12 -6
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,3 +165,4 @@ npm run pack:check
|
|
|
165
165
|
Project status, usage boundaries, architecture, and iteration records begin at the [documentation entry point](docs/00-交接入口/00-阅读导航.md). The detailed operational guide is `docs\02-产品与业务\01-使用说明.md`.
|
|
166
166
|
|
|
167
167
|
Licensed under [Apache License 2.0](LICENSE).
|
|
168
|
+
|
package/README.zh-CN.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -40,6 +40,16 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
"summary.enabled.one": "个已启用",
|
|
41
41
|
"summary.enabled.other": "个已启用",
|
|
42
42
|
"summary.group": "{count} 个",
|
|
43
|
+
"filter.category": "分类",
|
|
44
|
+
"filter.all": "全部",
|
|
45
|
+
"filter.option": "{name}({count})",
|
|
46
|
+
"filter.showing.one": "显示 {count} 个",
|
|
47
|
+
"filter.showing.other": "显示 {count} 个",
|
|
48
|
+
"search": "搜索",
|
|
49
|
+
"search.placeholder": "名称、形态或简介",
|
|
50
|
+
"search.clear": "清除搜索",
|
|
51
|
+
"empty.search": "没有匹配的插件。试试其他关键词,或切换到「{all}」。",
|
|
52
|
+
"empty.search.reset": "清除筛选",
|
|
43
53
|
"note.missing": "未提供简介",
|
|
44
54
|
"empty.dir.uncreated": "目录尚未创建,上传第一个插件后会自动创建。",
|
|
45
55
|
"empty.dir.missing": "公共目录不存在。",
|
|
@@ -55,6 +65,8 @@ window.__ModuleLoader__.load({
|
|
|
55
65
|
"result.done": "上传完成:{names}",
|
|
56
66
|
"result.skipped": "未导入:同名插件已跳过:{names}",
|
|
57
67
|
"result.none": "未导入任何插件。",
|
|
68
|
+
"result.warnings": "警告:{warnings}",
|
|
69
|
+
"warning.backupUncleaned": "旧版本备份未清理: {path}({error})",
|
|
58
70
|
"upload.title": "上传插件",
|
|
59
71
|
"upload.close": "关闭上传弹窗",
|
|
60
72
|
"upload.drop.title": "拖放 SKILL.md 文件到这里",
|
|
@@ -83,7 +95,9 @@ window.__ModuleLoader__.load({
|
|
|
83
95
|
"error.import.invalidName": "无法生成合法 kebab-case 名称(原始名: {name})",
|
|
84
96
|
"error.import.duplicateName": "批量来源中存在多个同名插件: {name}",
|
|
85
97
|
"error.import.failed": "导入失败",
|
|
98
|
+
"error.import.rollbackFailed": "覆盖导入回滚失败,备份保留在: {path}({error})",
|
|
86
99
|
"error.proto.forbidden": "禁止的修改请求(缺少客户端标记)",
|
|
100
|
+
"error.proto.forbiddenHost": "禁止的请求来源(非法 Host)",
|
|
87
101
|
"error.proto.contentType": "请求体必须是 application/json",
|
|
88
102
|
"error.proto.method": "不支持的请求方法",
|
|
89
103
|
"error.proto.unknownAction": "未知操作",
|
|
@@ -124,6 +138,16 @@ window.__ModuleLoader__.load({
|
|
|
124
138
|
"summary.enabled.one": "enabled",
|
|
125
139
|
"summary.enabled.other": "enabled",
|
|
126
140
|
"summary.group": "{count} items",
|
|
141
|
+
"filter.category": "Category",
|
|
142
|
+
"filter.all": "All",
|
|
143
|
+
"filter.option": "{name} ({count})",
|
|
144
|
+
"filter.showing.one": "Showing {count} skill",
|
|
145
|
+
"filter.showing.other": "Showing {count} skills",
|
|
146
|
+
"search": "Search",
|
|
147
|
+
"search.placeholder": "Name, kind, or description",
|
|
148
|
+
"search.clear": "Clear search",
|
|
149
|
+
"empty.search": "No matching plugins. Try another keyword, or switch to {all}.",
|
|
150
|
+
"empty.search.reset": "Clear filters",
|
|
127
151
|
"note.missing": "No description provided",
|
|
128
152
|
"empty.dir.uncreated": "The directory has not been created yet; it will be created automatically after the first upload.",
|
|
129
153
|
"empty.dir.missing": "The public directory does not exist.",
|
|
@@ -138,6 +162,8 @@ window.__ModuleLoader__.load({
|
|
|
138
162
|
"result.done": "Upload complete: {names}",
|
|
139
163
|
"result.skipped": "Not imported: same-name plugins were skipped: {names}",
|
|
140
164
|
"result.none": "Nothing was imported.",
|
|
165
|
+
"result.warnings": "Warning: {warnings}",
|
|
166
|
+
"warning.backupUncleaned": "Old version backup was not cleaned up: {path} ({error})",
|
|
141
167
|
"upload.title": "Upload plugin",
|
|
142
168
|
"upload.close": "Close upload dialog",
|
|
143
169
|
"upload.drop.title": "Drop SKILL.md file here",
|
|
@@ -166,7 +192,9 @@ window.__ModuleLoader__.load({
|
|
|
166
192
|
"error.import.invalidName": "Cannot generate a valid kebab-case name (original name: {name})",
|
|
167
193
|
"error.import.duplicateName": "Batch source contains multiple plugins with the same name: {name}",
|
|
168
194
|
"error.import.failed": "Import failed",
|
|
195
|
+
"error.import.rollbackFailed": "Overwrite import rollback failed; backups kept at: {path} ({error})",
|
|
169
196
|
"error.proto.forbidden": "Forbidden mutation request (missing client marker)",
|
|
197
|
+
"error.proto.forbiddenHost": "Forbidden request origin (invalid host)",
|
|
170
198
|
"error.proto.contentType": "Content type must be application/json",
|
|
171
199
|
"error.proto.method": "Method not allowed",
|
|
172
200
|
"error.proto.unknownAction": "Unknown action",
|
|
@@ -194,7 +222,8 @@ window.__ModuleLoader__.load({
|
|
|
194
222
|
.dssm-row{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dssm-row:last-child{border-bottom:0}.dssm-row-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:3px}.dssm-row-id{display:flex;align-items:center;gap:7px;min-width:0}.dssm-row-name{overflow:hidden;font-size:13px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.dssm-tag{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.dssm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dssm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dssm-note{overflow:hidden;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dssm-empty{padding:28px 16px;color:var(--dsw-alias-label-tertiary);font-size:13px;text-align:center}.dssm-error{color:var(--dsw-alias-state-error-primary);font-size:13px;line-height:20px}
|
|
195
223
|
.dssm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;padding:24px;background:rgba(0,0,0,.58)}.dssm-modal{box-sizing:border-box;display:flex;width:min(480px,100%)!important;height:auto!important;max-width:100%;max-height:100%;min-width:0;min-height:0;flex:0 0 auto!important;align-self:center!important;flex-direction:column;gap:18px;padding:24px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dssm-modal-upload{width:min(480px,100%)!important;height:auto!important;max-width:100%;max-height:100%;align-self:center!important;flex:0 0 auto!important;overflow:auto}.dssm-modal-head{display:flex;align-items:center;gap:12px}.dssm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:650}.dssm-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:20px;line-height:1;cursor:pointer}.dssm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
196
224
|
.dssm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:144px;flex:none;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color 180ms ease,background 180ms ease}.dssm-dropzone:hover,.dssm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dssm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:560}.dssm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dssm-file{display:flex;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-size:13px}.dssm-file-name{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dssm-modal-head,.dssm-file,.dssm-modal-actions{flex:none}.dssm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dssm-hidden-input{display:none}
|
|
197
|
-
|
|
225
|
+
.dssm-filters{display:flex;align-items:flex-end;gap:10px}.dssm-field{display:flex;min-width:0;flex:1;flex-direction:column;gap:6px}.dssm-field-category{flex:0 1 220px}.dssm-field-search{flex:1 1 240px}.dssm-label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}.dssm-control{box-sizing:border-box;width:100%;min-height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dssm-control:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-select{position:relative}.dssm-select-trigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dssm-select-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dssm-select-trigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-select-trigger[aria-expanded="true"]{border-color:var(--dsw-alias-state-success-primary)}.dssm-select-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dssm-select-caret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dssm-select-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dssm-select-option{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dssm-select-option:hover,.dssm-select-option[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover)}.dssm-select-option[aria-selected="true"]{color:var(--dsw-alias-state-success-primary)}.dssm-search-wrap{position:relative;display:flex;align-items:center}.dssm-search{padding-right:32px}.dssm-search::-webkit-search-cancel-button,.dssm-search::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.dssm-search-clear{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:16px;line-height:1;cursor:pointer}.dssm-search-clear:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dssm-search-clear:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-empty-filter{display:flex;flex-direction:column;align-items:center;gap:12px;padding:28px 16px;border:1px dashed var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;text-align:center}.dssm-filter-meta{margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
226
|
+
@media (max-width:560px){.dssm-mask{padding:12px;align-items:center}.dssm-modal{width:100%!important;height:auto!important;padding:16px;gap:12px;align-self:center!important}.dssm-modal-upload{width:100%!important;align-self:center!important}.dssm-toolbar{flex-wrap:wrap}.dssm-actions{margin-left:0}.dssm-path{display:none}.dssm-row{align-items:flex-start;flex-wrap:wrap}.dssm-row>.dssm-btn{margin-left:auto}.dssm-filters{flex-direction:column;align-items:stretch}.dssm-field-category,.dssm-field-search{flex:none}.dssm-filter-meta{margin-left:0}.dssm-modal-actions{justify-content:stretch}.dssm-modal-actions .dssm-btn{flex:1 1 auto}}@media (prefers-reduced-motion:reduce){.dssm-btn,.dssm-dropzone{transition:none}}
|
|
198
227
|
`;
|
|
199
228
|
|
|
200
229
|
function callApi(path, options) {
|
|
@@ -250,6 +279,50 @@ window.__ModuleLoader__.load({
|
|
|
250
279
|
return skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable;
|
|
251
280
|
}
|
|
252
281
|
|
|
282
|
+
/** 规范化检索词:去首尾空白并转小写,便于名称、形态和简介统一匹配。 */
|
|
283
|
+
function normalizeSkillQuery(query) {
|
|
284
|
+
return String(query == null ? "" : query).trim().toLowerCase();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** 按名称、简介、形态或分类标签做包含匹配;空检索视为全部命中。 */
|
|
288
|
+
function matchSkillQuery(skill, query) {
|
|
289
|
+
var q = normalizeSkillQuery(query);
|
|
290
|
+
if (q === "") return true;
|
|
291
|
+
return [
|
|
292
|
+
skill.name,
|
|
293
|
+
skill.description,
|
|
294
|
+
skill.kind,
|
|
295
|
+
skill.kindLabel,
|
|
296
|
+
skill.statusLabel,
|
|
297
|
+
skill.rootKey,
|
|
298
|
+
skill.rootLabel,
|
|
299
|
+
].some(function (field) {
|
|
300
|
+
return String(field == null ? "" : field).toLowerCase().includes(q);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** 先按目录分类收窄,再按检索词过滤。rootKey 为空表示全部分类。 */
|
|
305
|
+
function filterSkills(list, options) {
|
|
306
|
+
var query = options && options.query;
|
|
307
|
+
var rootKey = options && options.rootKey != null ? options.rootKey : "";
|
|
308
|
+
return list.filter(function (skill) {
|
|
309
|
+
return (rootKey === "" || skill.rootKey === rootKey) && matchSkillQuery(skill, query);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function skillSearchMeta(skill, root, t) {
|
|
314
|
+
var enabled = isSkillEnabled(skill);
|
|
315
|
+
return {
|
|
316
|
+
name: skill.name,
|
|
317
|
+
description: skill.description,
|
|
318
|
+
kind: skill.kind,
|
|
319
|
+
kindLabel: t(skill.kind === "bundle" ? "status.bundle" : "status.single"),
|
|
320
|
+
statusLabel: skill.invocationPolicyValid ? (enabled ? t("status.enabled") : t("status.disabled")) : t("status.invalid"),
|
|
321
|
+
rootKey: root.key,
|
|
322
|
+
rootLabel: translateOrFallback(t, "root." + root.key, root.label),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
253
326
|
function modalFocusable(modal) {
|
|
254
327
|
return Array.prototype.slice.call(modal.querySelectorAll("button:not(:disabled), [href], input:not([type=hidden]):not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])"));
|
|
255
328
|
}
|
|
@@ -265,6 +338,122 @@ window.__ModuleLoader__.load({
|
|
|
265
338
|
}
|
|
266
339
|
}
|
|
267
340
|
|
|
341
|
+
function CategorySelect(props) {
|
|
342
|
+
var openState = react.useState(false);
|
|
343
|
+
var open = openState[0];
|
|
344
|
+
var setOpen = openState[1];
|
|
345
|
+
var selectedIndex = 0;
|
|
346
|
+
for (var i = 0; i < props.options.length; i++) {
|
|
347
|
+
if (props.options[i].value === props.value) { selectedIndex = i; break; }
|
|
348
|
+
}
|
|
349
|
+
var activeState = react.useState(selectedIndex);
|
|
350
|
+
var active = activeState[0];
|
|
351
|
+
var setActive = activeState[1];
|
|
352
|
+
var rootRef = react.useRef(null);
|
|
353
|
+
var triggerRef = react.useRef(null);
|
|
354
|
+
var listRef = react.useRef(null);
|
|
355
|
+
var wasOpen = react.useRef(false);
|
|
356
|
+
var selected = props.options[selectedIndex];
|
|
357
|
+
|
|
358
|
+
react.useEffect(function () {
|
|
359
|
+
if (!open) return undefined;
|
|
360
|
+
setActive(selectedIndex);
|
|
361
|
+
function onPointerDown(event) {
|
|
362
|
+
var node = rootRef.current;
|
|
363
|
+
if (node && event.target && node.contains(event.target)) return;
|
|
364
|
+
setOpen(false);
|
|
365
|
+
}
|
|
366
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
367
|
+
return function () { document.removeEventListener("pointerdown", onPointerDown); };
|
|
368
|
+
}, [open, selectedIndex]);
|
|
369
|
+
|
|
370
|
+
react.useEffect(function () {
|
|
371
|
+
if (open) {
|
|
372
|
+
if (listRef.current) listRef.current.focus();
|
|
373
|
+
wasOpen.current = true;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (wasOpen.current) {
|
|
377
|
+
if (triggerRef.current) triggerRef.current.focus();
|
|
378
|
+
wasOpen.current = false;
|
|
379
|
+
}
|
|
380
|
+
}, [open]);
|
|
381
|
+
|
|
382
|
+
react.useEffect(function () {
|
|
383
|
+
if (!open) return;
|
|
384
|
+
var option = document.getElementById(props.id + "-opt-" + String(active));
|
|
385
|
+
if (option) option.scrollIntoView({ block: "nearest" });
|
|
386
|
+
}, [active, open, props.id]);
|
|
387
|
+
|
|
388
|
+
function choose(value) {
|
|
389
|
+
props.onChange(value);
|
|
390
|
+
setOpen(false);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function move(next) {
|
|
394
|
+
if (!props.options.length) return;
|
|
395
|
+
setActive(Math.min(props.options.length - 1, Math.max(0, next)));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function onTriggerKeyDown(event) {
|
|
399
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Enter" || event.key === " ") {
|
|
400
|
+
event.preventDefault();
|
|
401
|
+
setOpen(true);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function onListKeyDown(event) {
|
|
406
|
+
if (event.key === "ArrowDown") { event.preventDefault(); move(active + 1); return; }
|
|
407
|
+
if (event.key === "ArrowUp") { event.preventDefault(); move(active - 1); return; }
|
|
408
|
+
if (event.key === "Home") { event.preventDefault(); move(0); return; }
|
|
409
|
+
if (event.key === "End") { event.preventDefault(); move(props.options.length - 1); return; }
|
|
410
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
411
|
+
event.preventDefault();
|
|
412
|
+
var option = props.options[active];
|
|
413
|
+
if (option) choose(option.value);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (event.key === "Escape" || event.key === "Tab") setOpen(false);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return h("div", { className: "dssm-select", ref: rootRef },
|
|
420
|
+
h("button", {
|
|
421
|
+
id: props.id,
|
|
422
|
+
ref: triggerRef,
|
|
423
|
+
type: "button",
|
|
424
|
+
className: "dssm-select-trigger",
|
|
425
|
+
"aria-haspopup": "listbox",
|
|
426
|
+
"aria-expanded": open,
|
|
427
|
+
"aria-controls": props.id + "-list",
|
|
428
|
+
onClick: function () { setOpen(function (current) { return !current; }); },
|
|
429
|
+
onKeyDown: onTriggerKeyDown,
|
|
430
|
+
},
|
|
431
|
+
h("span", { className: "dssm-select-value" }, selected ? selected.label : ""),
|
|
432
|
+
h("svg", { className: "dssm-select-caret", viewBox: "0 0 12 12", "aria-hidden": true, focusable: false },
|
|
433
|
+
h("path", { d: "M2.5 4.5L6 8l3.5-3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))),
|
|
434
|
+
open ? h("div", {
|
|
435
|
+
id: props.id + "-list",
|
|
436
|
+
ref: listRef,
|
|
437
|
+
className: "dssm-select-menu",
|
|
438
|
+
role: "listbox",
|
|
439
|
+
tabIndex: 0,
|
|
440
|
+
"aria-activedescendant": props.id + "-opt-" + String(active),
|
|
441
|
+
onKeyDown: onListKeyDown,
|
|
442
|
+
}, props.options.map(function (option, index) {
|
|
443
|
+
return h("button", {
|
|
444
|
+
key: option.value === "" ? "all" : option.value,
|
|
445
|
+
id: props.id + "-opt-" + String(index),
|
|
446
|
+
type: "button",
|
|
447
|
+
role: "option",
|
|
448
|
+
className: "dssm-select-option",
|
|
449
|
+
"aria-selected": option.value === props.value,
|
|
450
|
+
"data-active": index === active,
|
|
451
|
+
onMouseEnter: function () { setActive(index); },
|
|
452
|
+
onClick: function () { choose(option.value); },
|
|
453
|
+
}, option.label);
|
|
454
|
+
})) : null);
|
|
455
|
+
}
|
|
456
|
+
|
|
268
457
|
// 渲染机制按 register 的 locale 字段把命名空间绑定的 t 注入组件 props。
|
|
269
458
|
function SkillManagerSection(props) {
|
|
270
459
|
var t = props.t;
|
|
@@ -289,6 +478,12 @@ window.__ModuleLoader__.load({
|
|
|
289
478
|
var deleteState = react.useState(null);
|
|
290
479
|
var confirmDelete = deleteState[0];
|
|
291
480
|
var setConfirmDelete = deleteState[1];
|
|
481
|
+
var queryState = react.useState("");
|
|
482
|
+
var query = queryState[0];
|
|
483
|
+
var setQuery = queryState[1];
|
|
484
|
+
var categoryState = react.useState("");
|
|
485
|
+
var category = categoryState[0];
|
|
486
|
+
var setCategory = categoryState[1];
|
|
292
487
|
var inputRef = react.useRef(null);
|
|
293
488
|
var modalRef = react.useRef(null);
|
|
294
489
|
|
|
@@ -435,15 +630,65 @@ window.__ModuleLoader__.load({
|
|
|
435
630
|
h("div", { className: "dssm-actions" },
|
|
436
631
|
h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: refresh }, t("btn.refresh")),
|
|
437
632
|
h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { setImportResult(null); setUploadOpen(true); } }, t("btn.upload")))));
|
|
633
|
+
var searchable = roots.reduce(function (all, root) {
|
|
634
|
+
return all.concat((root.skills || []).map(function (skill) { return skillSearchMeta(skill, root, t); }));
|
|
635
|
+
}, []);
|
|
636
|
+
var filtered = filterSkills(searchable, { query: query, rootKey: category });
|
|
637
|
+
var filteredCount = filtered.length;
|
|
638
|
+
var hasQuery = normalizeSkillQuery(query) !== "";
|
|
639
|
+
var hasFilter = hasQuery || category !== "";
|
|
640
|
+
var resetFilters = function () { setQuery(""); setCategory(""); };
|
|
438
641
|
// 复数按 en 的 one/other 二元规则选择;zh/en 均适用,扩展多复数语言时需改 CLDR 规则。
|
|
439
642
|
nodes.push(h("div", { key: "summary", className: "dssm-summary" },
|
|
440
|
-
h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, t(skills.length === 1 ? "summary.total.one" : "summary.total.other", { count: skills.length })), h("span", { className: "dssm-summary-separator" }), h("span", { className: "dssm-summary-count" }, enabled), h("span", { className: "dssm-summary-label" }, t(enabled === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabled }))
|
|
643
|
+
h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, t(skills.length === 1 ? "summary.total.one" : "summary.total.other", { count: skills.length })), h("span", { className: "dssm-summary-separator" }), h("span", { className: "dssm-summary-count" }, enabled), h("span", { className: "dssm-summary-label" }, t(enabled === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabled })),
|
|
644
|
+
hasFilter ? h("span", { className: "dssm-filter-meta", "aria-live": "polite" }, t(filteredCount === 1 ? "filter.showing.one" : "filter.showing.other", { count: filteredCount })) : null));
|
|
645
|
+
nodes.push(h("div", { key: "filters", className: "dssm-filters" },
|
|
646
|
+
h("div", { className: "dssm-field dssm-field-category" },
|
|
647
|
+
h("label", { className: "dssm-label", htmlFor: "dssm-filter-category" }, t("filter.category")),
|
|
648
|
+
h(CategorySelect, {
|
|
649
|
+
id: "dssm-filter-category",
|
|
650
|
+
value: category,
|
|
651
|
+
onChange: setCategory,
|
|
652
|
+
options: [{ value: "", label: t("filter.option", { name: t("filter.all"), count: skills.length }) }].concat(roots.map(function (root) {
|
|
653
|
+
return { value: root.key, label: t("filter.option", { name: translateOrFallback(t, "root." + root.key, root.label), count: (root.skills || []).length }) };
|
|
654
|
+
})),
|
|
655
|
+
})),
|
|
656
|
+
h("div", { className: "dssm-field dssm-field-search" },
|
|
657
|
+
h("label", { className: "dssm-label", htmlFor: "dssm-filter-search" }, t("search")),
|
|
658
|
+
h("div", { className: "dssm-search-wrap" },
|
|
659
|
+
h("input", {
|
|
660
|
+
id: "dssm-filter-search",
|
|
661
|
+
className: "dssm-control dssm-search",
|
|
662
|
+
type: "search",
|
|
663
|
+
value: query,
|
|
664
|
+
autoComplete: "off",
|
|
665
|
+
spellCheck: false,
|
|
666
|
+
placeholder: t("search.placeholder"),
|
|
667
|
+
onChange: function (event) { setQuery(event.currentTarget.value); },
|
|
668
|
+
}),
|
|
669
|
+
query !== "" ? h("button", { type: "button", className: "dssm-search-clear", "aria-label": t("search.clear"), onClick: function () { setQuery(""); } }, "×") : null))));
|
|
670
|
+
var visibleRoots = [];
|
|
441
671
|
roots.forEach(function (root) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
672
|
+
if (category !== "" && root.key !== category) return;
|
|
673
|
+
var groupSkills = (root.skills || []).filter(function (skill) {
|
|
674
|
+
return matchSkillQuery(skillSearchMeta(skill, root, t), query);
|
|
675
|
+
});
|
|
676
|
+
if (hasQuery && groupSkills.length === 0) return;
|
|
677
|
+
visibleRoots.push({ root: root, groupSkills: groupSkills });
|
|
446
678
|
});
|
|
679
|
+
if (hasQuery && visibleRoots.length === 0) {
|
|
680
|
+
nodes.push(h("div", { key: "empty-search", className: "dssm-empty-filter" },
|
|
681
|
+
h("div", null, t("empty.search", { all: t("filter.all") })),
|
|
682
|
+
h("button", { type: "button", className: "dssm-btn dssm-btn-secondary", onClick: resetFilters }, t("empty.search.reset"))));
|
|
683
|
+
} else {
|
|
684
|
+
visibleRoots.forEach(function (item) {
|
|
685
|
+
var root = item.root;
|
|
686
|
+
var groupSkills = item.groupSkills;
|
|
687
|
+
nodes.push(h("div", { key: root.key, className: "dssm-group" },
|
|
688
|
+
h("div", { className: "dssm-group-head" }, h("h3", { className: "dssm-group-title" }, translateOrFallback(t, "root." + root.key, root.label)), h("span", { className: "dssm-count" }, t("summary.group", { count: groupSkills.length })), h("span", { className: "dssm-path", title: root.path }, root.path)),
|
|
689
|
+
root.exists === false ? h("div", { className: "dssm-empty" }, root.mutable ? t("empty.dir.uncreated") : t("empty.dir.missing")) : (groupSkills.length ? groupSkills.map(function (skill) { return skillRow(skill, root); }) : h("div", { className: "dssm-empty" }, t("empty.skills.none")))));
|
|
690
|
+
});
|
|
691
|
+
}
|
|
447
692
|
} else if (snapshot.loading) {
|
|
448
693
|
nodes.push(h("div", { key: "loading", className: "dssm-note" }, t("loading")));
|
|
449
694
|
}
|
|
@@ -457,6 +702,13 @@ window.__ModuleLoader__.load({
|
|
|
457
702
|
var failedItems = importResult.failed || (importResult.error && importResult.error.failed) || [];
|
|
458
703
|
var skippedNames = (importResult.skipped || []).map(function (item) { return item.name; });
|
|
459
704
|
var failedText = failedItems.map(function (item) { return translateError(t, item); }).join(errorSeparator);
|
|
705
|
+
var warningItems = [];
|
|
706
|
+
(importResult.imported || []).forEach(function (item) {
|
|
707
|
+
if (item.warnings && item.warnings.length) {
|
|
708
|
+
for (var i = 0; i < item.warnings.length; i++) warningItems.push(item.warnings[i]);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
var warningText = warningItems.map(function (item) { return translateError(t, item); }).join(errorSeparator);
|
|
460
712
|
var resultText;
|
|
461
713
|
if (isImportError) {
|
|
462
714
|
// 顶层聚合错误优先展示已翻译的失败明细;无明细时由 translateError 兜底(含 Error 对象)。
|
|
@@ -470,6 +722,7 @@ window.__ModuleLoader__.load({
|
|
|
470
722
|
} else {
|
|
471
723
|
resultText = t("result.none");
|
|
472
724
|
}
|
|
725
|
+
if (warningText) resultText = resultText + errorSeparator + t("result.warnings", { warnings: warningText });
|
|
473
726
|
nodes.push(h("div", { key: "import-result", className: isImportError || failedItems.length ? "dssm-error" : "dssm-note", role: "status" }, resultText));
|
|
474
727
|
}
|
|
475
728
|
if (uploadOpen) {
|
|
@@ -506,8 +759,16 @@ window.__ModuleLoader__.load({
|
|
|
506
759
|
module.exports.DICT = DICT;
|
|
507
760
|
module.exports.translateError = translateError;
|
|
508
761
|
module.exports.isSkillEnabled = isSkillEnabled;
|
|
762
|
+
module.exports.normalizeSkillQuery = normalizeSkillQuery;
|
|
763
|
+
module.exports.matchSkillQuery = matchSkillQuery;
|
|
764
|
+
module.exports.filterSkills = filterSkills;
|
|
509
765
|
module.exports.parseApiResponse = parseApiResponse;
|
|
510
766
|
module.exports.trapModalFocus = trapModalFocus;
|
|
511
767
|
return module.exports;
|
|
512
768
|
}
|
|
513
769
|
});
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
|
package/lib/core.js
CHANGED
|
@@ -426,14 +426,16 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
|
426
426
|
try {
|
|
427
427
|
await fs.rename(item.backup, item.path);
|
|
428
428
|
} catch (rollbackError) {
|
|
429
|
-
rollbackFailures.push(
|
|
429
|
+
rollbackFailures.push(item.backup);
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
if (rollbackFailures.length) {
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
433
|
+
const causeText = String(error && error.message ? error.message : error);
|
|
434
|
+
throw codedError(
|
|
435
|
+
`${causeText};覆盖导入回滚失败,备份保留在: ${rollbackFailures.join("、")}`,
|
|
436
|
+
"error.import.rollbackFailed",
|
|
437
|
+
{ path: rollbackFailures.join("; "), error: causeText },
|
|
438
|
+
);
|
|
437
439
|
}
|
|
438
440
|
throw error;
|
|
439
441
|
}
|
|
@@ -442,7 +444,11 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
|
442
444
|
try {
|
|
443
445
|
await fs.rm(item.backup, { recursive: true, force: true });
|
|
444
446
|
} catch (error) {
|
|
445
|
-
warnings.push(
|
|
447
|
+
warnings.push({
|
|
448
|
+
code: "warning.backupUncleaned",
|
|
449
|
+
params: { path: item.backup, error: String(error && error.message ? error.message : error) },
|
|
450
|
+
error: `旧版本备份未清理: ${item.backup}(${String(error && error.message ? error.message : error)})`,
|
|
451
|
+
});
|
|
446
452
|
}
|
|
447
453
|
}
|
|
448
454
|
return warnings;
|
package/lib/index.js
CHANGED
|
@@ -76,7 +76,7 @@ function readBody(req, limit = 1 << 20) {
|
|
|
76
76
|
/** 只允许 loopback Host,避免 DNS rebinding 读取 /state 或改写本地技能。 */
|
|
77
77
|
function validateLoopbackHost(req) {
|
|
78
78
|
const host = String(req.headers.host || "").toLowerCase();
|
|
79
|
-
if (!/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host)) return { statusCode: 403, code: "error.proto.
|
|
79
|
+
if (!/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host)) return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
82
82
|
|
package/package.json
CHANGED