@morehao/dsh-skills-console 0.3.1 → 0.3.3
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 +21 -5
- package/client.js +1628 -41
- package/lib/core/inventory.js +9 -2
- package/lib/core/service.js +87 -15
- package/lib/core/skill-files.js +336 -0
- package/lib/routes.js +12 -4
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -99,7 +99,9 @@ var DICT = {
|
|
|
99
99
|
"view.agent": "按 Agent",
|
|
100
100
|
autoLink: "自动接入",
|
|
101
101
|
"settings.title": "插件设置",
|
|
102
|
-
"
|
|
102
|
+
"id.copy": "复制安装标识",
|
|
103
|
+
"id.copied": "已复制安装标识",
|
|
104
|
+
"id.copyFailed": "复制失败,请重试",
|
|
103
105
|
"placement.label": "侧边栏入口",
|
|
104
106
|
"placement.effect": "切换后立即生效",
|
|
105
107
|
"placement.on": "已开启",
|
|
@@ -114,6 +116,8 @@ var DICT = {
|
|
|
114
116
|
"status.broken": "异常:软链断裂",
|
|
115
117
|
"status.unmanaged": "未纳入追踪",
|
|
116
118
|
"status.unknown": "待检查",
|
|
119
|
+
"status.noUpstream": "无上游",
|
|
120
|
+
"status.noUpstreamHint": "没有可用的订阅来源,无法检查更新;重新接管或添加订阅后再试",
|
|
117
121
|
"status.off": "已停用",
|
|
118
122
|
"status.brokenHint": "软链目标不存在:重新接入,或删除后重装",
|
|
119
123
|
"adopt.switchHint": "还未纳入追踪,不能启停",
|
|
@@ -200,7 +204,62 @@ var DICT = {
|
|
|
200
204
|
visibleOnlyBuiltin: "本插件仅管理 ~/.agents/skills 下的技能,DSH 内置技能不在管理范围内",
|
|
201
205
|
"builtin.view": "查看内置技能({n})",
|
|
202
206
|
"builtin.hide": "收起内置技能",
|
|
203
|
-
external: "非本插件创建"
|
|
207
|
+
external: "非本插件创建",
|
|
208
|
+
// 查看器:抽屉 / 整页共用的文案
|
|
209
|
+
"viewer.title": "技能内容",
|
|
210
|
+
"viewer.close": "关闭",
|
|
211
|
+
"viewer.openPage": "在整页打开",
|
|
212
|
+
"viewer.back": "返回列表",
|
|
213
|
+
"viewer.tab.doc": "正文",
|
|
214
|
+
"viewer.tab.files": "文件",
|
|
215
|
+
"viewer.tab.info": "信息",
|
|
216
|
+
"viewer.rendered": "渲染",
|
|
217
|
+
"viewer.raw": "原文",
|
|
218
|
+
"viewer.loading": "正在读取 SKILL.md…",
|
|
219
|
+
"viewer.retry": "重试",
|
|
220
|
+
"viewer.copyPath": "复制路径",
|
|
221
|
+
"viewer.copyBody": "复制正文",
|
|
222
|
+
"viewer.copied": "已复制",
|
|
223
|
+
"viewer.copyFailed": "复制失败,请手动选中后复制",
|
|
224
|
+
"viewer.gone": "这个技能已不在本地,查看器已关闭",
|
|
225
|
+
"viewer.noSkill": "这个技能里没有 SKILL.md",
|
|
226
|
+
"viewer.noSkillHint": "DSH 解析不到它,但目录里的其他文件仍可以看",
|
|
227
|
+
"viewer.binary": "暂不支持预览",
|
|
228
|
+
"viewer.binaryHint": "二进制或非文本文件,只显示文件名与大小",
|
|
229
|
+
"viewer.textNote": "只读,直接显示源码",
|
|
230
|
+
"viewer.tooLarge": "超过 {size},只显示前一部分",
|
|
231
|
+
"viewer.emptyFiles": "这个技能目录里没有可读的文件",
|
|
232
|
+
"viewer.fileCount": "{n} 个文件",
|
|
233
|
+
"viewer.size": "{size}",
|
|
234
|
+
"viewer.backToBody": "回到 SKILL.md",
|
|
235
|
+
"viewer.openInDoc": "在正文中打开",
|
|
236
|
+
"viewer.toc": "目录",
|
|
237
|
+
"viewer.readonlyNote": "只读,内容来自本机 SKILL.md",
|
|
238
|
+
"viewer.info.basic": "标识",
|
|
239
|
+
"viewer.info.name": "名称",
|
|
240
|
+
"viewer.info.desc": "描述",
|
|
241
|
+
"viewer.info.path": "本地路径",
|
|
242
|
+
"viewer.info.source": "来源",
|
|
243
|
+
"viewer.info.repo": "仓库",
|
|
244
|
+
"viewer.info.ref": "基准",
|
|
245
|
+
"viewer.info.commit": "commit",
|
|
246
|
+
"viewer.info.skillPath": "skillPath",
|
|
247
|
+
"viewer.info.hash": "内容哈希",
|
|
248
|
+
"viewer.info.installedAt": "安装",
|
|
249
|
+
"viewer.info.updatedAt": "更新",
|
|
250
|
+
"viewer.info.size": "大小",
|
|
251
|
+
"viewer.info.local": "本地",
|
|
252
|
+
"viewer.info.localChange": "本地改动",
|
|
253
|
+
"viewer.info.localChangeNone": "无(与安装时一致)",
|
|
254
|
+
"viewer.info.localChangeYes": "有本地改动",
|
|
255
|
+
"viewer.info.links": "接入",
|
|
256
|
+
"viewer.info.agents": "Agent",
|
|
257
|
+
"viewer.info.scope": "范围",
|
|
258
|
+
"viewer.info.scopeGlobal": "全局",
|
|
259
|
+
"viewer.info.scopeProject": "仅项目",
|
|
260
|
+
"viewer.info.mode": "状态",
|
|
261
|
+
"viewer.info.status": "更新",
|
|
262
|
+
"viewer.info.none": "无"
|
|
204
263
|
},
|
|
205
264
|
en: {
|
|
206
265
|
"section.title": "Skills Console",
|
|
@@ -230,7 +289,9 @@ var DICT = {
|
|
|
230
289
|
"view.agent": "By agent",
|
|
231
290
|
"autoLink": "Auto-link",
|
|
232
291
|
"settings.title": "Plugin settings",
|
|
233
|
-
"
|
|
292
|
+
"id.copy": "Copy install specifier",
|
|
293
|
+
"id.copied": "Install specifier copied",
|
|
294
|
+
"id.copyFailed": "Copy failed, please retry",
|
|
234
295
|
"placement.label": "Sidebar entry",
|
|
235
296
|
"placement.effect": "Applies immediately",
|
|
236
297
|
"placement.on": "on",
|
|
@@ -245,6 +306,8 @@ var DICT = {
|
|
|
245
306
|
"status.broken": "Broken link",
|
|
246
307
|
"status.unmanaged": "Untracked",
|
|
247
308
|
"status.unknown": "Not checked",
|
|
309
|
+
"status.noUpstream": "No upstream",
|
|
310
|
+
"status.noUpstreamHint": "No usable subscription source, so update checks are impossible; re-adopt or add a subscription",
|
|
248
311
|
"status.off": "Disabled",
|
|
249
312
|
"status.brokenHint": "The link target is missing: relink, or delete and reinstall",
|
|
250
313
|
"adopt.switchHint": "Not tracked yet, cannot be enabled or disabled",
|
|
@@ -331,9 +394,75 @@ var DICT = {
|
|
|
331
394
|
"builtin.view": "View built-ins ({n})",
|
|
332
395
|
"builtin.hide": "Hide built-ins",
|
|
333
396
|
external: "created outside this plugin",
|
|
334
|
-
"adopt.done": "now tracked"
|
|
397
|
+
"adopt.done": "now tracked",
|
|
398
|
+
// Viewer: shared by the drawer and the full page
|
|
399
|
+
"viewer.title": "Skill content",
|
|
400
|
+
"viewer.close": "Close",
|
|
401
|
+
"viewer.openPage": "Open as page",
|
|
402
|
+
"viewer.back": "Back to list",
|
|
403
|
+
"viewer.tab.doc": "Content",
|
|
404
|
+
"viewer.tab.files": "Files",
|
|
405
|
+
"viewer.tab.info": "Info",
|
|
406
|
+
"viewer.rendered": "Rendered",
|
|
407
|
+
"viewer.raw": "Raw",
|
|
408
|
+
"viewer.loading": "Reading SKILL.md…",
|
|
409
|
+
"viewer.retry": "Retry",
|
|
410
|
+
"viewer.copyPath": "Copy path",
|
|
411
|
+
"viewer.copyBody": "Copy content",
|
|
412
|
+
"viewer.copied": "Copied",
|
|
413
|
+
"viewer.copyFailed": "Copy failed, select the text manually",
|
|
414
|
+
"viewer.gone": "This skill is gone from disk; the viewer was closed",
|
|
415
|
+
"viewer.noSkill": "This skill has no SKILL.md",
|
|
416
|
+
"viewer.noSkillHint": "DSH cannot parse it, but the other files are still readable",
|
|
417
|
+
"viewer.binary": "Preview not supported",
|
|
418
|
+
"viewer.binaryHint": "Binary or non-text file: only the name and size are shown",
|
|
419
|
+
"viewer.textNote": "Read-only source",
|
|
420
|
+
"viewer.tooLarge": "Larger than {size}; showing the first part only",
|
|
421
|
+
"viewer.emptyFiles": "No readable files in this skill folder",
|
|
422
|
+
"viewer.fileCount": "{n} files",
|
|
423
|
+
"viewer.size": "{size}",
|
|
424
|
+
"viewer.backToBody": "Back to SKILL.md",
|
|
425
|
+
"viewer.openInDoc": "Open in content",
|
|
426
|
+
"viewer.toc": "Contents",
|
|
427
|
+
"viewer.readonlyNote": "Read-only, content comes from the local SKILL.md",
|
|
428
|
+
"viewer.info.basic": "Identity",
|
|
429
|
+
"viewer.info.name": "Name",
|
|
430
|
+
"viewer.info.desc": "Description",
|
|
431
|
+
"viewer.info.path": "Local path",
|
|
432
|
+
"viewer.info.source": "Origin",
|
|
433
|
+
"viewer.info.repo": "Repository",
|
|
434
|
+
"viewer.info.ref": "Baseline",
|
|
435
|
+
"viewer.info.commit": "commit",
|
|
436
|
+
"viewer.info.skillPath": "skillPath",
|
|
437
|
+
"viewer.info.hash": "Content hash",
|
|
438
|
+
"viewer.info.installedAt": "Installed",
|
|
439
|
+
"viewer.info.updatedAt": "Updated",
|
|
440
|
+
"viewer.info.size": "Size",
|
|
441
|
+
"viewer.info.local": "Local",
|
|
442
|
+
"viewer.info.localChange": "Local changes",
|
|
443
|
+
"viewer.info.localChangeNone": "None (matches install)",
|
|
444
|
+
"viewer.info.localChangeYes": "Modified locally",
|
|
445
|
+
"viewer.info.links": "Linked",
|
|
446
|
+
"viewer.info.agents": "Agents",
|
|
447
|
+
"viewer.info.scope": "Scope",
|
|
448
|
+
"viewer.info.scopeGlobal": "Global",
|
|
449
|
+
"viewer.info.scopeProject": "Project only",
|
|
450
|
+
"viewer.info.mode": "State",
|
|
451
|
+
"viewer.info.status": "Updates",
|
|
452
|
+
"viewer.info.none": "None"
|
|
335
453
|
}
|
|
336
454
|
};
|
|
455
|
+
async function copyText(text) {
|
|
456
|
+
const value = String(text ?? "");
|
|
457
|
+
if (!value) return false;
|
|
458
|
+
try {
|
|
459
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) return false;
|
|
460
|
+
await navigator.clipboard.writeText(value);
|
|
461
|
+
return true;
|
|
462
|
+
} catch {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
337
466
|
function buildTranslate(locale) {
|
|
338
467
|
const active = () => {
|
|
339
468
|
try {
|
|
@@ -617,9 +746,89 @@ function SearchIcon({ size = 13 }) {
|
|
|
617
746
|
h("path", { d: "M16 16l4.3 4.3" })
|
|
618
747
|
);
|
|
619
748
|
}
|
|
749
|
+
var STROKE_ICON = {
|
|
750
|
+
fill: "none",
|
|
751
|
+
stroke: "currentColor",
|
|
752
|
+
strokeWidth: 1.8,
|
|
753
|
+
strokeLinecap: "round",
|
|
754
|
+
strokeLinejoin: "round"
|
|
755
|
+
};
|
|
756
|
+
function EyeIcon({ size = 13 }) {
|
|
757
|
+
return h(
|
|
758
|
+
"svg",
|
|
759
|
+
{
|
|
760
|
+
width: size,
|
|
761
|
+
height: size,
|
|
762
|
+
viewBox: "0 0 24 24",
|
|
763
|
+
...STROKE_ICON,
|
|
764
|
+
"aria-hidden": true,
|
|
765
|
+
style: { display: "block" }
|
|
766
|
+
},
|
|
767
|
+
h("path", { d: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z" }),
|
|
768
|
+
h("circle", { cx: 12, cy: 12, r: 3 })
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
function CopyIcon({ size = 13 }) {
|
|
772
|
+
return h(
|
|
773
|
+
"svg",
|
|
774
|
+
{
|
|
775
|
+
width: size,
|
|
776
|
+
height: size,
|
|
777
|
+
viewBox: "0 0 24 24",
|
|
778
|
+
...STROKE_ICON,
|
|
779
|
+
"aria-hidden": true,
|
|
780
|
+
style: { display: "block" }
|
|
781
|
+
},
|
|
782
|
+
h("rect", { x: 8, y: 8, width: 14, height: 14, rx: 2 }),
|
|
783
|
+
h("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
function ChevronLeftIcon({ size = 14 }) {
|
|
787
|
+
return h(
|
|
788
|
+
"svg",
|
|
789
|
+
{
|
|
790
|
+
width: size,
|
|
791
|
+
height: size,
|
|
792
|
+
viewBox: "0 0 24 24",
|
|
793
|
+
...STROKE_ICON,
|
|
794
|
+
"aria-hidden": true,
|
|
795
|
+
style: { display: "block" }
|
|
796
|
+
},
|
|
797
|
+
h("path", { d: "m15 18-6-6 6-6" })
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
function FileIcon({ size = 12 }) {
|
|
801
|
+
return h(
|
|
802
|
+
"svg",
|
|
803
|
+
{
|
|
804
|
+
width: size,
|
|
805
|
+
height: size,
|
|
806
|
+
viewBox: "0 0 24 24",
|
|
807
|
+
...STROKE_ICON,
|
|
808
|
+
"aria-hidden": true,
|
|
809
|
+
style: { display: "block", flex: "none" }
|
|
810
|
+
},
|
|
811
|
+
h("path", { d: "M6 3.5h7l5 5V20a1.5 1.5 0 0 1-1.5 1.5h-10A1.5 1.5 0 0 1 5 20V5a1.5 1.5 0 0 1 1-1.5z" }),
|
|
812
|
+
h("path", { d: "M13 3.5V9h5" })
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
function FolderIcon({ size = 12 }) {
|
|
816
|
+
return h(
|
|
817
|
+
"svg",
|
|
818
|
+
{
|
|
819
|
+
width: size,
|
|
820
|
+
height: size,
|
|
821
|
+
viewBox: "0 0 24 24",
|
|
822
|
+
...STROKE_ICON,
|
|
823
|
+
"aria-hidden": true,
|
|
824
|
+
style: { display: "block", flex: "none" }
|
|
825
|
+
},
|
|
826
|
+
h("path", { d: "M3.5 7.5A1.5 1.5 0 0 1 5 6h4l1.8 2.2H19a1.5 1.5 0 0 1 1.5 1.5V18A1.5 1.5 0 0 1 19 19.5H5A1.5 1.5 0 0 1 3.5 18z" })
|
|
827
|
+
);
|
|
828
|
+
}
|
|
620
829
|
|
|
621
830
|
// client/section.js
|
|
622
|
-
var
|
|
831
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
623
832
|
|
|
624
833
|
// client/styles.js
|
|
625
834
|
var CSS = `
|
|
@@ -641,13 +850,19 @@ var CSS = `
|
|
|
641
850
|
.dshsc-head-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
|
|
642
851
|
.dshsc-title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
|
|
643
852
|
|
|
644
|
-
/*
|
|
645
|
-
.dshsc-id { display: inline-flex; align-items: center; gap:
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
853
|
+
/* 身份:按「安装时输入的包名」显示(@scope/name@version),等宽 + 可点复制 */
|
|
854
|
+
.dshsc-id { display: inline-flex; align-items: center; gap: 5px; box-sizing: border-box; min-width: 0;
|
|
855
|
+
max-width: 100%; height: 22px; padding: 0 6px 0 7px; border: 1px solid var(--dsw-alias-border-l1);
|
|
856
|
+
border-radius: 6px; background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-tertiary);
|
|
857
|
+
font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
858
|
+
font-size: 11px; line-height: 1; white-space: nowrap; cursor: pointer;
|
|
859
|
+
transition: background .12s ease, border-color .12s ease, color .12s ease; }
|
|
860
|
+
.dshsc-id:hover { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2);
|
|
861
|
+
color: var(--dsw-alias-label-primary); }
|
|
862
|
+
.dshsc-id:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
863
|
+
.dshsc-spec { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
|
|
864
|
+
.dshsc-idcopy { display: inline-flex; flex: none; opacity: .5; transition: opacity .12s ease; }
|
|
865
|
+
.dshsc-id:hover .dshsc-idcopy, .dshsc-id:focus-visible .dshsc-idcopy { opacity: 1; }
|
|
651
866
|
|
|
652
867
|
/* 插件级工具(设置 / GitHub):跟在身份胶囊后面,用细线分组;鬼影按钮不和业务按钮抢权重 */
|
|
653
868
|
.dshsc-tools { display: inline-flex; align-items: center; gap: 2px; flex: none; margin-left: 2px;
|
|
@@ -745,7 +960,7 @@ var CSS = `
|
|
|
745
960
|
|
|
746
961
|
.dshsc-list { display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
747
962
|
background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
748
|
-
.dshsc-row { display: grid; grid-template-columns: 300px minmax(0, 1fr)
|
|
963
|
+
.dshsc-row { display: grid; grid-template-columns: 300px minmax(0, 1fr) 268px; gap: 14px; align-items: center;
|
|
749
964
|
padding: 8px 12px 8px 14px; min-height: 54px; border-top: 1px solid var(--dsw-alias-border-l1);
|
|
750
965
|
transition: background .12s ease; }
|
|
751
966
|
.dshsc-list > .dshsc-row:first-child { border-top: 0; }
|
|
@@ -755,6 +970,11 @@ var CSS = `
|
|
|
755
970
|
.dshsc-namerow { display: flex; align-items: center; gap: 7px; min-width: 0; }
|
|
756
971
|
.dshsc-name { font-size: 13px; font-weight: 600; letter-spacing: -.005em; min-width: 0;
|
|
757
972
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
973
|
+
/* 技能名本身是查看入口:清掉 button 默认外观,保留 hover 下划线提示可点 */
|
|
974
|
+
.dshsc-namebtn { border: 0; background: none; padding: 0; font: inherit; font-size: 13px; font-weight: 600;
|
|
975
|
+
letter-spacing: -.005em; color: inherit; text-align: left; cursor: pointer; }
|
|
976
|
+
.dshsc-namebtn:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
977
|
+
.dshsc-namebtn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; border-radius: 4px; }
|
|
758
978
|
.dshsc-src { display: flex; align-items: center; gap: 5px; min-width: 0; font-size: 11.5px; line-height: 16px;
|
|
759
979
|
color: var(--dsw-alias-label-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
760
980
|
.dshsc-src .dshsc-code { color: var(--dsw-alias-label-secondary); font-size: 11px; }
|
|
@@ -950,6 +1170,215 @@ var CSS = `
|
|
|
950
1170
|
.dshsc-panel .dshsc-check.locked:hover { background: transparent; }
|
|
951
1171
|
|
|
952
1172
|
.dshsc-modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1173
|
+
/* ---------- 查看器:抽屉(B1)/ 整页(A1)共用 ---------- */
|
|
1174
|
+
.dshsc-drawer-wrap { position: fixed; inset: 0; z-index: 55; }
|
|
1175
|
+
.dshsc-drawer-scrim { position: absolute; inset: 0; background: rgba(4,6,9,.55); }
|
|
1176
|
+
.dshsc-drawer { position: absolute; top: 0; right: 0; bottom: 0; width: min(700px, 100vw); display: flex;
|
|
1177
|
+
flex-direction: column; min-height: 0; background: var(--dsw-alias-bg-layer-1);
|
|
1178
|
+
border-left: 1px solid var(--dsw-alias-border-l2); box-shadow: -30px 0 70px rgba(0,0,0,.38); outline: none; }
|
|
1179
|
+
.dshsc-reader-head { flex: none; padding: 15px 20px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1180
|
+
.dshsc-reader-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1181
|
+
.dshsc-reader-title h2 { flex: 1; min-width: 0; margin: 0; font-size: 18px; line-height: 1.3; font-weight: 650;
|
|
1182
|
+
letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1183
|
+
.dshsc-reader-close { flex: none; width: 28px; height: 28px; border: 0; border-radius: 7px; background: transparent;
|
|
1184
|
+
color: var(--dsw-alias-label-tertiary); font-size: 18px; line-height: 1; cursor: pointer; }
|
|
1185
|
+
.dshsc-reader-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1186
|
+
.dshsc-reader-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1187
|
+
.dshsc-reader-desc { margin: 7px 0 0; font-size: 13px; line-height: 1.6; color: var(--dsw-alias-label-secondary);
|
|
1188
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
1189
|
+
.dshsc-reader-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px;
|
|
1190
|
+
font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
|
|
1191
|
+
.dshsc-reader-tabs { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 20px;
|
|
1192
|
+
border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1193
|
+
.dshsc-rtabs { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--dsw-alias-border-l1);
|
|
1194
|
+
border-radius: 9px; background: var(--dsw-alias-bg-layer-1); }
|
|
1195
|
+
.dshsc-rtab { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px; border: 0;
|
|
1196
|
+
border-radius: 7px; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px;
|
|
1197
|
+
cursor: pointer; white-space: nowrap; }
|
|
1198
|
+
.dshsc-rtab:hover { color: var(--dsw-alias-label-primary); }
|
|
1199
|
+
.dshsc-rtab.active { background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary);
|
|
1200
|
+
box-shadow: 0 0 0 1px var(--dsw-alias-border-l1), 0 1px 2px rgba(0,0,0,.12); }
|
|
1201
|
+
.dshsc-rtab .n { min-width: 15px; height: 15px; padding: 0 4px; box-sizing: border-box; border-radius: 999px;
|
|
1202
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 20%, transparent); color: var(--dsw-alias-label-secondary);
|
|
1203
|
+
font-size: 10px; line-height: 15px; text-align: center; font-style: normal; font-variant-numeric: tabular-nums; }
|
|
1204
|
+
.dshsc-rtab:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1205
|
+
.dshsc-reader-seg { margin-left: auto; }
|
|
1206
|
+
.dshsc-reader-seg .dshsc-btn { height: 22px; font-size: 11.5px; padding: 0 8px; }
|
|
1207
|
+
|
|
1208
|
+
.dshsc-reader-body { flex: 1; min-height: 0; overflow: auto; padding: 16px 20px 22px; outline: none; }
|
|
1209
|
+
.dshsc-reader-doc { display: flex; flex-direction: column; gap: 10px; }
|
|
1210
|
+
.dshsc-reader-filebar { display: flex; align-items: center; gap: 10px; font-size: 11.5px;
|
|
1211
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1212
|
+
.dshsc-reader-filebar .dshsc-btn { margin-left: auto; height: 24px; font-size: 11.5px; }
|
|
1213
|
+
.dshsc-reader-raw { margin: 0; padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px;
|
|
1214
|
+
background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); overflow: auto;
|
|
1215
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.65; white-space: pre-wrap;
|
|
1216
|
+
word-break: break-word; }
|
|
1217
|
+
.dshsc-reader-actions { flex: none; display: flex; align-items: center; gap: 7px; padding: 9px 20px;
|
|
1218
|
+
border-top: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); flex-wrap: wrap; }
|
|
1219
|
+
/* 底栏按钮统一 28px 高;图标按钮做成 28×28 的方正位,避免图标在方框里偏 */
|
|
1220
|
+
.dshsc-reader-actions .dshsc-btn { box-sizing: border-box; height: 28px; font-size: 12px;
|
|
1221
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
|
|
1222
|
+
.dshsc-reader-actions .dshsc-btn.sq { width: 28px; padding: 0; }
|
|
1223
|
+
.dshsc-reader-actions .dshsc-btn.sq svg { width: 14px; height: 14px; }
|
|
1224
|
+
.dshsc-reader-note { margin-right: auto; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
|
|
1225
|
+
.dshsc-reader-skeleton { display: flex; flex-direction: column; gap: 8px; }
|
|
1226
|
+
.dshsc-reader-skeleton i { display: block; height: 12px; border-radius: 5px;
|
|
1227
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 16%, transparent); }
|
|
1228
|
+
.dshsc-reader-skeleton i:nth-child(3) { width: 82%; }
|
|
1229
|
+
.dshsc-reader-skeleton i:nth-child(4) { width: 64%; }
|
|
1230
|
+
.dshsc-reader-skeleton span { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
1231
|
+
.dshsc-reader-placeholder { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 22px;
|
|
1232
|
+
border: 1px dashed var(--dsw-alias-border-l2); border-radius: 12px;
|
|
1233
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 55%, transparent); font-size: 12.5px;
|
|
1234
|
+
color: var(--dsw-alias-label-secondary); line-height: 1.6; }
|
|
1235
|
+
.dshsc-reader-placeholder b { color: var(--dsw-alias-label-primary); font-size: 13px; }
|
|
1236
|
+
|
|
1237
|
+
/* 文件 tab:左树右预览;抽屉里上下排 */
|
|
1238
|
+
.dshsc-reader-files { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
|
1239
|
+
.dshsc-reader-tree { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: auto;
|
|
1240
|
+
max-height: 300px; background: var(--dsw-alias-bg-layer-1); }
|
|
1241
|
+
.dshsc-reader-preview { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden;
|
|
1242
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1243
|
+
.dshsc-reader-tree-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: 8px;
|
|
1244
|
+
padding: 7px 10px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1245
|
+
font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1246
|
+
.dshsc-reader-tree-head .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
1247
|
+
.dshsc-reader-tree-head .dshsc-btn { height: 22px; font-size: 11px; padding: 0 7px; }
|
|
1248
|
+
/* 预览头的动作组:整组贴右,组内紧挨(渲染 / 原文 + 在正文中打开) */
|
|
1249
|
+
.dshsc-reader-tools { margin-left: auto; flex: none; display: flex; align-items: center; gap: 6px; }
|
|
1250
|
+
.dshsc-reader-tools:empty { display: none; }
|
|
1251
|
+
.dshsc-reader-tools .dshsc-segment { height: 22px; }
|
|
1252
|
+
.dshsc-reader-tools .dshsc-seg { margin-left: 0; }
|
|
1253
|
+
/* 预览头在深色 bg-layer-2 上,默认边框太淡:抬高一级,让它一眼是按钮 */
|
|
1254
|
+
.dshsc-reader-tools > .dshsc-btn { border: 1px solid var(--dsw-alias-border-l2);
|
|
1255
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-weight: 500; }
|
|
1256
|
+
.dshsc-reader-tools > .dshsc-btn:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-3);
|
|
1257
|
+
border-color: var(--dsw-alias-border-l3); }
|
|
1258
|
+
.dshsc-reader-tools > .dshsc-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1259
|
+
.dshsc-reader-filebar .dshsc-btn { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); }
|
|
1260
|
+
.dshsc-reader-tdir { display: flex; align-items: center; gap: 6px; padding: 5px 10px; font-size: 11.5px;
|
|
1261
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1262
|
+
.dshsc-reader-tdir .n { margin-left: auto; font-variant-numeric: tabular-nums; }
|
|
1263
|
+
.dshsc-reader-titem { display: flex; align-items: center; gap: 6px; width: 100%; box-sizing: border-box;
|
|
1264
|
+
padding: 5px 10px; border: 0; background: transparent; color: var(--dsw-alias-label-secondary);
|
|
1265
|
+
font: inherit; font-size: 12px; text-align: left; cursor: pointer; }
|
|
1266
|
+
.dshsc-reader-titem:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1267
|
+
.dshsc-reader-titem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1268
|
+
color: var(--dsw-alias-brand-primary); }
|
|
1269
|
+
.dshsc-reader-titem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
|
|
1270
|
+
.dshsc-reader-titem .nname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
|
|
1271
|
+
.dshsc-reader-titem .nsize { flex: none; font-size: 10.5px; color: var(--dsw-alias-label-tertiary);
|
|
1272
|
+
font-variant-numeric: tabular-nums; }
|
|
1273
|
+
.dshsc-reader-preview .dshsc-reader-raw { border: 0; border-radius: 0; background: transparent;
|
|
1274
|
+
max-height: 320px; }
|
|
1275
|
+
/* 文件 tab 里能渲染的文件(markdown)直接出正文,不让人先读源码 */
|
|
1276
|
+
.dshsc-reader-preview-doc { padding: 12px 14px; max-height: 320px; overflow: auto; }
|
|
1277
|
+
.dshsc-reader-preview-doc .dshsc-md { font-size: 12.5px; }
|
|
1278
|
+
.dshsc-reader-preview-doc .dshsc-md h1.dshsc-md-h { font-size: 18px; }
|
|
1279
|
+
.dshsc-reader-preview-doc .dshsc-md h2.dshsc-md-h { font-size: 15px; }
|
|
1280
|
+
|
|
1281
|
+
/* 信息 tab:四段 dl */
|
|
1282
|
+
.dshsc-reader-info { display: flex; flex-direction: column; gap: 14px; }
|
|
1283
|
+
.dshsc-info-sec { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 11px 13px;
|
|
1284
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1285
|
+
.dshsc-info-sec h3 { margin: 0 0 8px; font-size: 11px; font-weight: 600; letter-spacing: .04em;
|
|
1286
|
+
color: var(--dsw-alias-label-tertiary); text-transform: uppercase; }
|
|
1287
|
+
.dshsc-info-sec dl { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 5px 10px; margin: 0;
|
|
1288
|
+
font-size: 12px; line-height: 1.55; }
|
|
1289
|
+
.dshsc-info-sec dt { color: var(--dsw-alias-label-tertiary); }
|
|
1290
|
+
.dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
1291
|
+
.dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1292
|
+
|
|
1293
|
+
/* 正文排版:只用 token,不写死深浅色 */
|
|
1294
|
+
.dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
|
|
1295
|
+
.dshsc-md-h { margin: 20px 0 8px; line-height: 1.35; font-weight: 650; letter-spacing: -.008em; }
|
|
1296
|
+
.dshsc-md-h:first-child { margin-top: 0; }
|
|
1297
|
+
.dshsc-md h1.dshsc-md-h { font-size: 21px; }
|
|
1298
|
+
.dshsc-md h2.dshsc-md-h { font-size: 16.5px; padding-top: 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1299
|
+
.dshsc-md h3.dshsc-md-h { font-size: 14px; }
|
|
1300
|
+
.dshsc-md h4.dshsc-md-h, .dshsc-md h5.dshsc-md-h, .dshsc-md h6.dshsc-md-h { font-size: 13px;
|
|
1301
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1302
|
+
.dshsc-md-p { margin: 0 0 10px; }
|
|
1303
|
+
.dshsc-md-inline { padding: 1px 5px; border-radius: 5px; background: var(--dsw-alias-bg-layer-3);
|
|
1304
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
|
|
1305
|
+
.dshsc-md-code { margin: 0 0 12px; padding: 11px 13px; border-radius: 9px; overflow: auto;
|
|
1306
|
+
background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1);
|
|
1307
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.65; white-space: pre; }
|
|
1308
|
+
.dshsc-md-list { margin: 0 0 10px; padding-left: 20px; }
|
|
1309
|
+
.dshsc-md-list .dshsc-md-list { margin: 4px 0 0; }
|
|
1310
|
+
.dshsc-md-list li { margin-bottom: 4px; }
|
|
1311
|
+
.dshsc-md-task { list-style: none; margin-left: -18px; }
|
|
1312
|
+
.dshsc-md-check { display: inline-block; width: 12px; height: 12px; margin-right: 6px; border-radius: 3px;
|
|
1313
|
+
border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); vertical-align: -1px; }
|
|
1314
|
+
.dshsc-md-check.on { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
1315
|
+
.dshsc-md-quote { margin: 0 0 12px; padding: 6px 0 6px 12px; border-left: 2px solid var(--dsw-alias-border-l3);
|
|
1316
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1317
|
+
.dshsc-md-quote .dshsc-md-p:last-child { margin-bottom: 0; }
|
|
1318
|
+
.dshsc-md-hr { height: 0; margin: 16px 0; border: 0; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1319
|
+
.dshsc-md-link { color: var(--dsw-alias-brand-primary); text-decoration: none; }
|
|
1320
|
+
.dshsc-md-link:hover { text-decoration: underline; }
|
|
1321
|
+
.dshsc-md-nolink { color: inherit; border-bottom: 1px dotted var(--dsw-alias-border-l3); }
|
|
1322
|
+
.dshsc-md-image { display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px; border-radius: 6px;
|
|
1323
|
+
border: 1px dashed var(--dsw-alias-border-l2); color: var(--dsw-alias-label-tertiary); font-size: 11.5px; }
|
|
1324
|
+
.dshsc-md-image-tag { font-size: 9.5px; letter-spacing: .06em; }
|
|
1325
|
+
.dshsc-md-table { margin: 0 0 12px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px; }
|
|
1326
|
+
.dshsc-md-table table { border-collapse: collapse; width: 100%; font-size: 12.5px; }
|
|
1327
|
+
.dshsc-md-table th, .dshsc-md-table td { padding: 7px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1328
|
+
text-align: left; }
|
|
1329
|
+
.dshsc-md-table th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }
|
|
1330
|
+
.dshsc-md-table tr:last-child td { border-bottom: 0; }
|
|
1331
|
+
|
|
1332
|
+
/* 整页(A1):返回条 + hero + 目录 + 720px 正文 */
|
|
1333
|
+
.dshsc-reader-page { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
|
|
1334
|
+
.dshsc-reader-topbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1335
|
+
.dshsc-reader-topbar .dshsc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
|
1336
|
+
box-sizing: border-box; height: 28px; padding: 0 10px; font-size: 12px; line-height: 1; white-space: nowrap; }
|
|
1337
|
+
.dshsc-reader-topbar .dshsc-btn.ghost { padding: 0 9px; }
|
|
1338
|
+
.dshsc-reader-topbar .dshsc-btn.sq { width: 28px; padding: 0; }
|
|
1339
|
+
.dshsc-reader-topbar .dshsc-btn.sq svg { width: 14px; height: 14px; }
|
|
1340
|
+
.dshsc-reader-crumb { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
|
|
1341
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1342
|
+
.dshsc-reader-crumb .sep { color: var(--dsw-alias-border-l3); }
|
|
1343
|
+
.dshsc-reader-topbar .spacer { flex: 1; }
|
|
1344
|
+
/* 顶栏复用抽屉底栏那份动作组:去掉边框 / 底色 / 内边距,只留一排等高的按钮 */
|
|
1345
|
+
.dshsc-reader-topbar .dshsc-reader-actions { border: 0; background: transparent; padding: 0; gap: 6px;
|
|
1346
|
+
flex-wrap: nowrap; }
|
|
1347
|
+
.dshsc-reader-topbar .dshsc-reader-note { display: none; }
|
|
1348
|
+
.dshsc-reader-topbar .dshsc-reader-actions .dshsc-btn:not(.ghost) { padding: 0 11px; }
|
|
1349
|
+
.dshsc-reader-hero { border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
1350
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1351
|
+
.dshsc-reader-hero .dshsc-reader-head { border-bottom: 0; }
|
|
1352
|
+
.dshsc-reader-hero .dshsc-reader-desc { -webkit-line-clamp: 3; max-width: 78ch; }
|
|
1353
|
+
.dshsc-reader-layout { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 18px; align-items: start; }
|
|
1354
|
+
.dshsc-reader-toc { position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px;
|
|
1355
|
+
max-height: 70vh; overflow: auto; padding-right: 4px; }
|
|
1356
|
+
.dshsc-reader-toc-title { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
|
|
1357
|
+
color: var(--dsw-alias-label-tertiary); margin-bottom: 5px; }
|
|
1358
|
+
.dshsc-reader-tocitem { border: 0; background: transparent; padding: 3px 7px; border-radius: 6px; text-align: left;
|
|
1359
|
+
font: inherit; font-size: 11.5px; line-height: 1.45; color: var(--dsw-alias-label-secondary); cursor: pointer;
|
|
1360
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1361
|
+
.dshsc-reader-tocitem.l2 { padding-left: 15px; }
|
|
1362
|
+
.dshsc-reader-tocitem.l3, .dshsc-reader-tocitem.l4 { padding-left: 23px; font-size: 11px; }
|
|
1363
|
+
.dshsc-reader-tocitem:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1364
|
+
.dshsc-reader-tocitem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1365
|
+
color: var(--dsw-alias-brand-primary); }
|
|
1366
|
+
.dshsc-reader-tocitem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1367
|
+
.dshsc-reader-col { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--dsw-alias-border-l1);
|
|
1368
|
+
border-radius: 12px; background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1369
|
+
.dshsc-reader-col .dshsc-reader-tabs { border-top: 0; }
|
|
1370
|
+
.dshsc-reader-page .dshsc-reader-body { max-height: none; }
|
|
1371
|
+
.dshsc-reader-page .dshsc-md { max-width: 720px; }
|
|
1372
|
+
.dshsc-reader-page .dshsc-reader-actions { border-radius: 0 0 12px 12px; }
|
|
1373
|
+
|
|
1374
|
+
@container dshsc (max-width: 900px) {
|
|
1375
|
+
/* 行内「查看」先让位:技能名本身仍是入口 */
|
|
1376
|
+
.dshsc-row .dshsc-view { display: none; }
|
|
1377
|
+
.dshsc-reader-layout { grid-template-columns: minmax(0, 1fr); }
|
|
1378
|
+
.dshsc-reader-toc { display: none; }
|
|
1379
|
+
.dshsc-reader-page .dshsc-reader-desc { -webkit-line-clamp: 2; }
|
|
1380
|
+
}
|
|
1381
|
+
|
|
953
1382
|
.dshsc-toast { position: fixed; right: 18px; bottom: 18px; z-index: 70; font-size: 12px; padding: 10px 13px; border-radius: 9px;
|
|
954
1383
|
background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l2);
|
|
955
1384
|
box-shadow: 0 8px 24px rgba(0,0,0,.22); max-width: 420px; }
|
|
@@ -1260,8 +1689,80 @@ function useAgentTip() {
|
|
|
1260
1689
|
}
|
|
1261
1690
|
return { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip };
|
|
1262
1691
|
}
|
|
1692
|
+
function useSkillReader(row) {
|
|
1693
|
+
const name = row?.name || null;
|
|
1694
|
+
const stamp = row ? [row.commit || "", row.currentHash || "", row.updatedAt || "", row.status || ""].join("|") : "";
|
|
1695
|
+
const [state, setState] = import_react2.default.useState({ loading: true, body: null, tree: null, bodyError: null, treeError: null });
|
|
1696
|
+
const [file, setFile] = import_react2.default.useState({ relPath: null, content: "", size: 0, truncated: false, loading: false, error: null });
|
|
1697
|
+
const [tick, setTick] = import_react2.default.useState(0);
|
|
1698
|
+
const seq = import_react2.default.useRef(0);
|
|
1699
|
+
const fileSeq = import_react2.default.useRef(0);
|
|
1700
|
+
import_react2.default.useEffect(() => {
|
|
1701
|
+
if (!name) {
|
|
1702
|
+
setState({ loading: false, body: null, tree: null, bodyError: null, treeError: null });
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const ticket = seq.current + 1;
|
|
1706
|
+
seq.current = ticket;
|
|
1707
|
+
setState({ loading: true, body: null, tree: null, bodyError: null, treeError: null });
|
|
1708
|
+
const encoded = encodeURIComponent(name);
|
|
1709
|
+
void (async () => {
|
|
1710
|
+
const [bodyResult, treeResult] = await Promise.allSettled([
|
|
1711
|
+
apiCall("GET", `/skills/${encoded}`),
|
|
1712
|
+
apiCall("GET", `/skills/${encoded}/files`)
|
|
1713
|
+
]);
|
|
1714
|
+
if (ticket !== seq.current) return;
|
|
1715
|
+
setState({
|
|
1716
|
+
loading: false,
|
|
1717
|
+
body: bodyResult.status === "fulfilled" ? bodyResult.value : null,
|
|
1718
|
+
tree: treeResult.status === "fulfilled" ? treeResult.value : null,
|
|
1719
|
+
// 没有 SKILL.md 时正文失败但树还在:文件 tab 仍可用
|
|
1720
|
+
bodyError: bodyResult.status === "rejected" ? { code: bodyResult.reason?.code || null, message: bodyResult.reason?.message || "读取失败" } : null,
|
|
1721
|
+
treeError: treeResult.status === "rejected" ? { code: treeResult.reason?.code || null, message: treeResult.reason?.message || "读取失败" } : null
|
|
1722
|
+
});
|
|
1723
|
+
})();
|
|
1724
|
+
}, [name, stamp, tick]);
|
|
1725
|
+
const loadFile = import_react2.default.useCallback(async (relPath) => {
|
|
1726
|
+
if (!name || !relPath) return;
|
|
1727
|
+
const ticket = fileSeq.current + 1;
|
|
1728
|
+
fileSeq.current = ticket;
|
|
1729
|
+
setFile({ relPath, content: "", size: 0, truncated: false, loading: true, error: null });
|
|
1730
|
+
try {
|
|
1731
|
+
const result = await apiCall("GET", `/skills/${encodeURIComponent(name)}?path=${encodeURIComponent(relPath)}`);
|
|
1732
|
+
if (ticket !== fileSeq.current) return;
|
|
1733
|
+
setFile({
|
|
1734
|
+
relPath: result.relPath || relPath,
|
|
1735
|
+
content: result.content || "",
|
|
1736
|
+
size: result.size || 0,
|
|
1737
|
+
truncated: Boolean(result.truncated),
|
|
1738
|
+
loading: false,
|
|
1739
|
+
error: null,
|
|
1740
|
+
mtime: result.mtime || null
|
|
1741
|
+
});
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
if (ticket !== fileSeq.current) return;
|
|
1744
|
+
setFile({
|
|
1745
|
+
relPath,
|
|
1746
|
+
content: "",
|
|
1747
|
+
size: 0,
|
|
1748
|
+
truncated: false,
|
|
1749
|
+
loading: false,
|
|
1750
|
+
error: { code: error?.code || null, message: error?.message || "读取失败" }
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
}, [name]);
|
|
1754
|
+
const retry = import_react2.default.useCallback(() => {
|
|
1755
|
+
setTick((value) => value + 1);
|
|
1756
|
+
}, []);
|
|
1757
|
+
return { state, file, loadFile, retry };
|
|
1758
|
+
}
|
|
1263
1759
|
|
|
1264
1760
|
// client/views/header.js
|
|
1761
|
+
function installSpec(plugin) {
|
|
1762
|
+
const name = plugin?.fullName || plugin?.name;
|
|
1763
|
+
if (!name) return null;
|
|
1764
|
+
return plugin.version ? `${name}@${plugin.version}` : name;
|
|
1765
|
+
}
|
|
1265
1766
|
function Header({
|
|
1266
1767
|
t,
|
|
1267
1768
|
page,
|
|
@@ -1280,8 +1781,14 @@ function Header({
|
|
|
1280
1781
|
onChangePlacement,
|
|
1281
1782
|
onOpenAutoLink,
|
|
1282
1783
|
onAddSkill,
|
|
1283
|
-
onAddSubscription
|
|
1784
|
+
onAddSubscription,
|
|
1785
|
+
onToast
|
|
1284
1786
|
}) {
|
|
1787
|
+
const spec = installSpec(plugin);
|
|
1788
|
+
async function copySpec() {
|
|
1789
|
+
const ok = await copyText(spec);
|
|
1790
|
+
onToast?.(ok ? { kind: "ok", text: t("id.copied") } : { kind: "err", text: t("id.copyFailed") });
|
|
1791
|
+
}
|
|
1285
1792
|
const headTools = h(
|
|
1286
1793
|
"div",
|
|
1287
1794
|
{ className: "dshsc-tools" },
|
|
@@ -1353,17 +1860,18 @@ function Header({
|
|
|
1353
1860
|
"div",
|
|
1354
1861
|
{ className: "dshsc-title-row" },
|
|
1355
1862
|
h("h2", { className: "dshsc-title" }, page ? t("page.title") : t("section.title")),
|
|
1356
|
-
//
|
|
1357
|
-
|
|
1358
|
-
"
|
|
1359
|
-
{
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
"
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1863
|
+
// 身份:按「安装时输入的包名」显示,点一下整段复制(Host 从 package.json 注入)
|
|
1864
|
+
spec ? h(
|
|
1865
|
+
"button",
|
|
1866
|
+
{
|
|
1867
|
+
type: "button",
|
|
1868
|
+
className: "dshsc-id",
|
|
1869
|
+
title: `${t("id.copy")}:${spec}`,
|
|
1870
|
+
"aria-label": `${t("id.copy")}:${spec}`,
|
|
1871
|
+
onClick: () => void copySpec()
|
|
1872
|
+
},
|
|
1873
|
+
h("span", { className: "dshsc-spec" }, spec),
|
|
1874
|
+
h("span", { className: "dshsc-idcopy", "aria-hidden": "true" }, h(CopyIcon, { size: 11 }))
|
|
1367
1875
|
) : null,
|
|
1368
1876
|
headTools
|
|
1369
1877
|
),
|
|
@@ -1472,6 +1980,9 @@ function InstalledTab({
|
|
|
1472
1980
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
1473
1981
|
function statusTag(row) {
|
|
1474
1982
|
if (row.status === "unknown") return null;
|
|
1983
|
+
if (row.status === "no-upstream") {
|
|
1984
|
+
return row.source ? h("span", { className: "dshsc-tag", title: t("status.noUpstreamHint") }, t("status.noUpstream")) : null;
|
|
1985
|
+
}
|
|
1475
1986
|
const map = {
|
|
1476
1987
|
current: ["status.current", "ok", true],
|
|
1477
1988
|
"update-available": ["status.update", "warn"],
|
|
@@ -1550,7 +2061,13 @@ function InstalledTab({
|
|
|
1550
2061
|
h(
|
|
1551
2062
|
"div",
|
|
1552
2063
|
{ className: "dshsc-namerow" },
|
|
1553
|
-
h("
|
|
2064
|
+
h("button", {
|
|
2065
|
+
type: "button",
|
|
2066
|
+
className: "dshsc-name dshsc-namebtn",
|
|
2067
|
+
title: row.name,
|
|
2068
|
+
"aria-haspopup": "dialog",
|
|
2069
|
+
onClick: () => actions.onView(row)
|
|
2070
|
+
}, row.name),
|
|
1554
2071
|
statusTag(row),
|
|
1555
2072
|
off ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null
|
|
1556
2073
|
),
|
|
@@ -1572,6 +2089,13 @@ function InstalledTab({
|
|
|
1572
2089
|
onClick: () => void actions.onToggleMode(row),
|
|
1573
2090
|
title: tracked ? row.mode === "on" ? t("mode.off") : t("mode.on") : t("adopt.switchHint")
|
|
1574
2091
|
}, h("span", { className: "dshsc-knob" })),
|
|
2092
|
+
h("button", {
|
|
2093
|
+
className: "dshsc-btn ghost dshsc-view",
|
|
2094
|
+
disabled: busy !== null,
|
|
2095
|
+
"aria-haspopup": "dialog",
|
|
2096
|
+
title: t("action.view"),
|
|
2097
|
+
onClick: () => actions.onView(row)
|
|
2098
|
+
}, h(EyeIcon, { size: 12 }), t("action.view")),
|
|
1575
2099
|
row.status === "unmanaged" ? h("button", {
|
|
1576
2100
|
className: "dshsc-btn ghost",
|
|
1577
2101
|
disabled: !writable || busy !== null,
|
|
@@ -2315,19 +2839,1017 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
|
2315
2839
|
return null;
|
|
2316
2840
|
}
|
|
2317
2841
|
|
|
2842
|
+
// client/views/skill-reader.js
|
|
2843
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2844
|
+
|
|
2845
|
+
// client/markdown.js
|
|
2846
|
+
function classifyHref(href) {
|
|
2847
|
+
const value = String(href ?? "").trim();
|
|
2848
|
+
if (!value) return { kind: "unsafe", href: value };
|
|
2849
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) return { kind: "unsafe", href: value };
|
|
2850
|
+
if (value.startsWith("#")) return { kind: "anchor", href: value.slice(1) };
|
|
2851
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value);
|
|
2852
|
+
if (scheme) {
|
|
2853
|
+
const name = scheme[1].toLowerCase();
|
|
2854
|
+
if (name === "http" || name === "https" || name === "mailto") return { kind: "external", href: value };
|
|
2855
|
+
return { kind: "unsafe", href: value };
|
|
2856
|
+
}
|
|
2857
|
+
if (value.startsWith("//")) return { kind: "unsafe", href: value };
|
|
2858
|
+
return { kind: "relative", href: value };
|
|
2859
|
+
}
|
|
2860
|
+
function slugify(text) {
|
|
2861
|
+
return String(text ?? "").trim().toLowerCase().replace(/[`*_~]/g, "").replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2862
|
+
}
|
|
2863
|
+
function stripFrontmatter(text) {
|
|
2864
|
+
const source = String(text ?? "");
|
|
2865
|
+
const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(source);
|
|
2866
|
+
if (!match) return { frontmatter: null, body: source };
|
|
2867
|
+
return { frontmatter: match[1], body: source.slice(match[0].length) };
|
|
2868
|
+
}
|
|
2869
|
+
var ESCAPABLE = /* @__PURE__ */ new Set(["\\", "`", "*", "_", "[", "]", "(", ")", "#", "+", "-", ".", "!", "|", ">", "~"]);
|
|
2870
|
+
function matchBracketLink(text, start) {
|
|
2871
|
+
const close = text.indexOf("]", start + 1);
|
|
2872
|
+
if (close < 0) return null;
|
|
2873
|
+
if (text[close + 1] !== "(") return null;
|
|
2874
|
+
let depth = 0;
|
|
2875
|
+
let index = close + 1;
|
|
2876
|
+
for (; index < text.length; index += 1) {
|
|
2877
|
+
if (text[index] === "(") depth += 1;
|
|
2878
|
+
else if (text[index] === ")") {
|
|
2879
|
+
depth -= 1;
|
|
2880
|
+
if (depth === 0) break;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (index >= text.length) return null;
|
|
2884
|
+
const label = text.slice(start + 1, close);
|
|
2885
|
+
const rawTarget = text.slice(close + 2, index).trim();
|
|
2886
|
+
const href = rawTarget.split(/\s+/)[0] || "";
|
|
2887
|
+
return { label, href, end: index + 1 };
|
|
2888
|
+
}
|
|
2889
|
+
function parseInline(text) {
|
|
2890
|
+
const source = String(text ?? "");
|
|
2891
|
+
const nodes = [];
|
|
2892
|
+
let buffer = "";
|
|
2893
|
+
const flush = () => {
|
|
2894
|
+
if (buffer) {
|
|
2895
|
+
nodes.push({ type: "text", value: buffer });
|
|
2896
|
+
buffer = "";
|
|
2897
|
+
}
|
|
2898
|
+
};
|
|
2899
|
+
let index = 0;
|
|
2900
|
+
while (index < source.length) {
|
|
2901
|
+
const char = source[index];
|
|
2902
|
+
if (char === "\\" && ESCAPABLE.has(source[index + 1])) {
|
|
2903
|
+
buffer += source[index + 1];
|
|
2904
|
+
index += 2;
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
if (char === "`") {
|
|
2908
|
+
const end = source.indexOf("`", index + 1);
|
|
2909
|
+
if (end > index + 1) {
|
|
2910
|
+
flush();
|
|
2911
|
+
nodes.push({ type: "code", value: source.slice(index + 1, end) });
|
|
2912
|
+
index = end + 1;
|
|
2913
|
+
continue;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (char === "!" && source[index + 1] === "[") {
|
|
2917
|
+
const match = matchBracketLink(source, index + 1);
|
|
2918
|
+
if (match) {
|
|
2919
|
+
flush();
|
|
2920
|
+
const target = classifyHref(match.href);
|
|
2921
|
+
nodes.push({ type: "image", alt: match.label, src: match.href, kind: target.kind });
|
|
2922
|
+
index = match.end;
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (char === "[") {
|
|
2927
|
+
const match = matchBracketLink(source, index);
|
|
2928
|
+
if (match) {
|
|
2929
|
+
flush();
|
|
2930
|
+
const target = classifyHref(match.href);
|
|
2931
|
+
nodes.push({
|
|
2932
|
+
type: "link",
|
|
2933
|
+
href: target.href,
|
|
2934
|
+
kind: target.kind,
|
|
2935
|
+
children: parseInline(match.label)
|
|
2936
|
+
});
|
|
2937
|
+
index = match.end;
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
if (char === "*" || char === "_") {
|
|
2942
|
+
const doubled = source[index + 1] === char;
|
|
2943
|
+
const marker = doubled ? char + char : char;
|
|
2944
|
+
const end = source.indexOf(marker, index + marker.length);
|
|
2945
|
+
if (end > index + marker.length) {
|
|
2946
|
+
flush();
|
|
2947
|
+
nodes.push({
|
|
2948
|
+
type: doubled ? "strong" : "em",
|
|
2949
|
+
children: parseInline(source.slice(index + marker.length, end))
|
|
2950
|
+
});
|
|
2951
|
+
index = end + marker.length;
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
buffer += char;
|
|
2956
|
+
index += 1;
|
|
2957
|
+
}
|
|
2958
|
+
flush();
|
|
2959
|
+
return nodes;
|
|
2960
|
+
}
|
|
2961
|
+
function inlineToText(nodes) {
|
|
2962
|
+
if (!Array.isArray(nodes)) return String(nodes ?? "");
|
|
2963
|
+
return nodes.map((node) => {
|
|
2964
|
+
if (node.type === "text" || node.type === "code") return node.value;
|
|
2965
|
+
if (node.type === "image") return node.alt || "";
|
|
2966
|
+
return inlineToText(node.children);
|
|
2967
|
+
}).join("");
|
|
2968
|
+
}
|
|
2969
|
+
var FENCE = /^(\s*)(`{3,}|~{3,})\s*([^`]*)$/;
|
|
2970
|
+
var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
2971
|
+
var HR = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
|
|
2972
|
+
var LIST_ITEM = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/;
|
|
2973
|
+
var QUOTE = /^\s{0,3}>\s?(.*)$/;
|
|
2974
|
+
var TABLE_SEPARATOR = /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/;
|
|
2975
|
+
function splitRow(line) {
|
|
2976
|
+
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
2977
|
+
return trimmed.split("|").map((cell) => cell.trim());
|
|
2978
|
+
}
|
|
2979
|
+
function alignOf(cell) {
|
|
2980
|
+
const value = cell.trim();
|
|
2981
|
+
if (/^:-+:$/.test(value)) return "center";
|
|
2982
|
+
if (/^:-+$/.test(value)) return "left";
|
|
2983
|
+
if (/^-+:$/.test(value)) return "right";
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
function parseMarkdown(text) {
|
|
2987
|
+
const lines = String(text ?? "").replace(/\r\n?/g, "\n").split("\n");
|
|
2988
|
+
const blocks = [];
|
|
2989
|
+
const headings = [];
|
|
2990
|
+
const usedIds = /* @__PURE__ */ new Map();
|
|
2991
|
+
const headingId = (plain) => {
|
|
2992
|
+
const base = slugify(plain) || "section";
|
|
2993
|
+
const seen = usedIds.get(base) || 0;
|
|
2994
|
+
usedIds.set(base, seen + 1);
|
|
2995
|
+
return seen === 0 ? base : `${base}-${seen + 1}`;
|
|
2996
|
+
};
|
|
2997
|
+
const isBlockStart = (line) => FENCE.test(line) || HEADING.test(line) || HR.test(line) || LIST_ITEM.test(line) || QUOTE.test(line) || line.includes("|");
|
|
2998
|
+
let index = 0;
|
|
2999
|
+
while (index < lines.length) {
|
|
3000
|
+
const line = lines[index];
|
|
3001
|
+
if (!line.trim()) {
|
|
3002
|
+
index += 1;
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
const fence = FENCE.exec(line);
|
|
3006
|
+
if (fence) {
|
|
3007
|
+
const marker = fence[2];
|
|
3008
|
+
const info = fence[3].trim();
|
|
3009
|
+
const body = [];
|
|
3010
|
+
index += 1;
|
|
3011
|
+
while (index < lines.length) {
|
|
3012
|
+
const current = lines[index];
|
|
3013
|
+
if (current.trim().startsWith(marker[0].repeat(marker.length)) && current.trim().replace(/[`~]/g, "") === "") {
|
|
3014
|
+
index += 1;
|
|
3015
|
+
break;
|
|
3016
|
+
}
|
|
3017
|
+
body.push(current);
|
|
3018
|
+
index += 1;
|
|
3019
|
+
}
|
|
3020
|
+
blocks.push({ type: "code", lang: info.split(/\s+/)[0] || "", text: body.join("\n") });
|
|
3021
|
+
continue;
|
|
3022
|
+
}
|
|
3023
|
+
const heading = HEADING.exec(line);
|
|
3024
|
+
if (heading) {
|
|
3025
|
+
const level = heading[1].length;
|
|
3026
|
+
const inlines = parseInline(heading[2]);
|
|
3027
|
+
const plain = inlineToText(inlines).trim();
|
|
3028
|
+
const id = headingId(plain);
|
|
3029
|
+
blocks.push({ type: "heading", level, id, inlines, text: plain });
|
|
3030
|
+
headings.push({ id, level, text: plain });
|
|
3031
|
+
index += 1;
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
if (HR.test(line)) {
|
|
3035
|
+
blocks.push({ type: "hr" });
|
|
3036
|
+
index += 1;
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
if (QUOTE.test(line)) {
|
|
3040
|
+
const quoted = [];
|
|
3041
|
+
while (index < lines.length && QUOTE.test(lines[index])) {
|
|
3042
|
+
quoted.push(QUOTE.exec(lines[index])[1]);
|
|
3043
|
+
index += 1;
|
|
3044
|
+
}
|
|
3045
|
+
blocks.push({ type: "quote", blocks: parseMarkdown(quoted.join("\n")).blocks });
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (line.includes("|") && index + 1 < lines.length && TABLE_SEPARATOR.test(lines[index + 1])) {
|
|
3049
|
+
const header = splitRow(line).map((cell) => parseInline(cell));
|
|
3050
|
+
const align = splitRow(lines[index + 1]).map(alignOf);
|
|
3051
|
+
index += 2;
|
|
3052
|
+
const rows = [];
|
|
3053
|
+
while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
|
|
3054
|
+
rows.push(splitRow(lines[index]).map((cell) => parseInline(cell)));
|
|
3055
|
+
index += 1;
|
|
3056
|
+
}
|
|
3057
|
+
blocks.push({ type: "table", align, header, rows });
|
|
3058
|
+
continue;
|
|
3059
|
+
}
|
|
3060
|
+
if (LIST_ITEM.test(line)) {
|
|
3061
|
+
const first = LIST_ITEM.exec(line);
|
|
3062
|
+
const ordered = /\d/.test(first[2]);
|
|
3063
|
+
const items = [];
|
|
3064
|
+
while (index < lines.length) {
|
|
3065
|
+
const match = LIST_ITEM.exec(lines[index]);
|
|
3066
|
+
if (!match) break;
|
|
3067
|
+
const indent = match[1].replace(/\t/g, " ").length;
|
|
3068
|
+
if (indent > 3) break;
|
|
3069
|
+
const content = match[3];
|
|
3070
|
+
let checked = null;
|
|
3071
|
+
const task = /^\[([ xX])\]\s*(.*)$/.exec(content);
|
|
3072
|
+
const text2 = task ? task[2] : content;
|
|
3073
|
+
if (task) checked = task[1].toLowerCase() === "x";
|
|
3074
|
+
index += 1;
|
|
3075
|
+
const nested = [];
|
|
3076
|
+
while (index < lines.length) {
|
|
3077
|
+
const child = LIST_ITEM.exec(lines[index]);
|
|
3078
|
+
if (!child) break;
|
|
3079
|
+
const childIndent = child[1].replace(/\t/g, " ").length;
|
|
3080
|
+
if (childIndent === 0) break;
|
|
3081
|
+
nested.push(lines[index]);
|
|
3082
|
+
index += 1;
|
|
3083
|
+
}
|
|
3084
|
+
while (index < lines.length && lines[index].trim() && !LIST_ITEM.test(lines[index]) && !isBlockStart(lines[index])) {
|
|
3085
|
+
index += 1;
|
|
3086
|
+
}
|
|
3087
|
+
const item = { inlines: parseInline(text2), checked };
|
|
3088
|
+
if (nested.length > 0) {
|
|
3089
|
+
const sub = parseMarkdown(nested.map((row) => row.replace(/^\s{1,4}/, "")).join("\n")).blocks;
|
|
3090
|
+
item.children = sub;
|
|
3091
|
+
}
|
|
3092
|
+
items.push(item);
|
|
3093
|
+
}
|
|
3094
|
+
blocks.push({ type: "list", ordered, items });
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
const paragraph = [line];
|
|
3098
|
+
index += 1;
|
|
3099
|
+
while (index < lines.length && lines[index].trim() && !isBlockStart(lines[index])) {
|
|
3100
|
+
paragraph.push(lines[index]);
|
|
3101
|
+
index += 1;
|
|
3102
|
+
}
|
|
3103
|
+
blocks.push({ type: "paragraph", inlines: parseInline(paragraph.map((row) => row.trim()).join(" ")) });
|
|
3104
|
+
}
|
|
3105
|
+
return { blocks, headings };
|
|
3106
|
+
}
|
|
3107
|
+
function resolveRelativeHref(currentDir, href) {
|
|
3108
|
+
const target = classifyHref(href);
|
|
3109
|
+
if (target.kind === "anchor") return { relPath: "", anchor: target.href };
|
|
3110
|
+
const raw = String(target.href || "").split("#")[0].split("?")[0];
|
|
3111
|
+
const base = String(currentDir || "").replace(/^\/+|\/+$/g, "");
|
|
3112
|
+
if (!raw) return { relPath: "", anchor: target.kind === "anchor" ? target.href : "" };
|
|
3113
|
+
const segments = `${base ? `${base}/` : ""}${raw}`.split("/");
|
|
3114
|
+
const out = [];
|
|
3115
|
+
for (const segment of segments) {
|
|
3116
|
+
if (!segment || segment === ".") continue;
|
|
3117
|
+
if (segment === "..") {
|
|
3118
|
+
out.pop();
|
|
3119
|
+
continue;
|
|
3120
|
+
}
|
|
3121
|
+
out.push(segment);
|
|
3122
|
+
}
|
|
3123
|
+
return { relPath: out.join("/"), anchor: target.kind === "anchor" ? target.href : "" };
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
// client/views/skill-reader.js
|
|
3127
|
+
var PARSE_CACHE = /* @__PURE__ */ new Map();
|
|
3128
|
+
function parseCached(text) {
|
|
3129
|
+
const key = String(text ?? "");
|
|
3130
|
+
if (PARSE_CACHE.has(key)) return PARSE_CACHE.get(key);
|
|
3131
|
+
const parsed = parseMarkdown(stripFrontmatter(key).body);
|
|
3132
|
+
PARSE_CACHE.set(key, parsed);
|
|
3133
|
+
if (PARSE_CACHE.size > 8) {
|
|
3134
|
+
const oldest = PARSE_CACHE.keys().next().value;
|
|
3135
|
+
PARSE_CACHE.delete(oldest);
|
|
3136
|
+
}
|
|
3137
|
+
return parsed;
|
|
3138
|
+
}
|
|
3139
|
+
function formatBytes(bytes) {
|
|
3140
|
+
const value = Number(bytes) || 0;
|
|
3141
|
+
if (value < 1024) return `${value} B`;
|
|
3142
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3143
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3144
|
+
}
|
|
3145
|
+
function formatWhen(iso) {
|
|
3146
|
+
if (!iso) return null;
|
|
3147
|
+
const date = new Date(iso);
|
|
3148
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3149
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3150
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3151
|
+
}
|
|
3152
|
+
var MARKDOWN = /\.(md|markdown)$/i;
|
|
3153
|
+
function isMarkdownPath(relPath) {
|
|
3154
|
+
return MARKDOWN.test(String(relPath || ""));
|
|
3155
|
+
}
|
|
3156
|
+
function nearestScroller(node) {
|
|
3157
|
+
let current = node?.parentElement || null;
|
|
3158
|
+
while (current) {
|
|
3159
|
+
if (typeof getComputedStyle === "function") {
|
|
3160
|
+
const style = getComputedStyle(current);
|
|
3161
|
+
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight + 4) {
|
|
3162
|
+
return current;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
current = current.parentElement || null;
|
|
3166
|
+
}
|
|
3167
|
+
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3168
|
+
}
|
|
3169
|
+
function scrollHost(node) {
|
|
3170
|
+
return node && nearestScroller(node) || node;
|
|
3171
|
+
}
|
|
3172
|
+
var STATUS_KEY = {
|
|
3173
|
+
current: "status.current",
|
|
3174
|
+
"update-available": "status.update",
|
|
3175
|
+
"locally-modified": "status.local",
|
|
3176
|
+
conflict: "status.conflict",
|
|
3177
|
+
"upstream-deleted": "status.deleted",
|
|
3178
|
+
"broken-link": "status.broken",
|
|
3179
|
+
unmanaged: "status.unmanaged"
|
|
3180
|
+
};
|
|
3181
|
+
function inlineNodes(nodes, { onOpenFile, onOpenAnchor, keyPrefix = "i" }) {
|
|
3182
|
+
if (!Array.isArray(nodes)) return null;
|
|
3183
|
+
return nodes.map((node, index) => {
|
|
3184
|
+
const key = `${keyPrefix}-${index}`;
|
|
3185
|
+
switch (node.type) {
|
|
3186
|
+
case "text":
|
|
3187
|
+
return node.value;
|
|
3188
|
+
case "code":
|
|
3189
|
+
return h("code", { className: "dshsc-md-inline", key }, node.value);
|
|
3190
|
+
case "strong":
|
|
3191
|
+
return h("strong", { key }, inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key }));
|
|
3192
|
+
case "em":
|
|
3193
|
+
return h("em", { key }, inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key }));
|
|
3194
|
+
case "link": {
|
|
3195
|
+
const children = inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key });
|
|
3196
|
+
if (node.kind === "external") {
|
|
3197
|
+
return h("a", {
|
|
3198
|
+
className: "dshsc-md-link",
|
|
3199
|
+
key,
|
|
3200
|
+
href: node.href,
|
|
3201
|
+
target: "_blank",
|
|
3202
|
+
rel: "noopener noreferrer"
|
|
3203
|
+
}, children);
|
|
3204
|
+
}
|
|
3205
|
+
if (node.kind === "anchor") {
|
|
3206
|
+
return h("a", {
|
|
3207
|
+
className: "dshsc-md-link",
|
|
3208
|
+
key,
|
|
3209
|
+
href: `#${node.href}`,
|
|
3210
|
+
onClick: (event) => {
|
|
3211
|
+
event?.preventDefault?.();
|
|
3212
|
+
onOpenAnchor?.(node.href);
|
|
3213
|
+
}
|
|
3214
|
+
}, children);
|
|
3215
|
+
}
|
|
3216
|
+
if (node.kind === "relative") {
|
|
3217
|
+
return h("a", {
|
|
3218
|
+
className: "dshsc-md-link",
|
|
3219
|
+
key,
|
|
3220
|
+
href: "#",
|
|
3221
|
+
title: node.href,
|
|
3222
|
+
onClick: (event) => {
|
|
3223
|
+
event?.preventDefault?.();
|
|
3224
|
+
onOpenFile?.(node.href);
|
|
3225
|
+
}
|
|
3226
|
+
}, children);
|
|
3227
|
+
}
|
|
3228
|
+
return h("span", { className: "dshsc-md-nolink", key }, children);
|
|
3229
|
+
}
|
|
3230
|
+
case "image":
|
|
3231
|
+
return h(
|
|
3232
|
+
"span",
|
|
3233
|
+
{ className: "dshsc-md-image", key, title: node.src },
|
|
3234
|
+
h("span", { className: "dshsc-md-image-tag" }, "IMG"),
|
|
3235
|
+
node.alt || node.src
|
|
3236
|
+
);
|
|
3237
|
+
default:
|
|
3238
|
+
return null;
|
|
3239
|
+
}
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
function blockNodes(blocks, options) {
|
|
3243
|
+
return blocks.map((block, index) => {
|
|
3244
|
+
const key = `b-${index}`;
|
|
3245
|
+
switch (block.type) {
|
|
3246
|
+
case "heading": {
|
|
3247
|
+
const tag = `h${Math.min(block.level, 6)}`;
|
|
3248
|
+
return h(
|
|
3249
|
+
tag,
|
|
3250
|
+
{ className: "dshsc-md-h", key, id: block.id, "data-md-id": block.id },
|
|
3251
|
+
inlineNodes(block.inlines, { ...options, keyPrefix: key })
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
case "paragraph":
|
|
3255
|
+
return h(
|
|
3256
|
+
"p",
|
|
3257
|
+
{ className: "dshsc-md-p", key },
|
|
3258
|
+
inlineNodes(block.inlines, { ...options, keyPrefix: key })
|
|
3259
|
+
);
|
|
3260
|
+
case "code":
|
|
3261
|
+
return h(
|
|
3262
|
+
"pre",
|
|
3263
|
+
{ className: "dshsc-md-code", key, "data-lang": block.lang || void 0 },
|
|
3264
|
+
h("code", null, block.text)
|
|
3265
|
+
);
|
|
3266
|
+
case "hr":
|
|
3267
|
+
return h("hr", { className: "dshsc-md-hr", key });
|
|
3268
|
+
case "quote":
|
|
3269
|
+
return h("blockquote", { className: "dshsc-md-quote", key }, blockNodes(block.blocks, options));
|
|
3270
|
+
case "list": {
|
|
3271
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
3272
|
+
return h(tag, { className: "dshsc-md-list", key }, block.items.map((item, itemIndex) => h(
|
|
3273
|
+
"li",
|
|
3274
|
+
{
|
|
3275
|
+
key: `${key}-${itemIndex}`,
|
|
3276
|
+
className: item.checked === null || item.checked === void 0 ? void 0 : "dshsc-md-task"
|
|
3277
|
+
},
|
|
3278
|
+
item.checked === null || item.checked === void 0 ? null : h("span", { className: `dshsc-md-check${item.checked ? " on" : ""}`, "aria-hidden": true }),
|
|
3279
|
+
inlineNodes(item.inlines, { ...options, keyPrefix: `${key}-${itemIndex}` }),
|
|
3280
|
+
Array.isArray(item.children) && item.children.length > 0 ? blockNodes(item.children, options) : null
|
|
3281
|
+
)));
|
|
3282
|
+
}
|
|
3283
|
+
case "table":
|
|
3284
|
+
return h(
|
|
3285
|
+
"div",
|
|
3286
|
+
{ className: "dshsc-md-table", key },
|
|
3287
|
+
h(
|
|
3288
|
+
"table",
|
|
3289
|
+
null,
|
|
3290
|
+
h("thead", null, h("tr", null, block.header.map((cell, cellIndex) => h("th", {
|
|
3291
|
+
key: `h-${cellIndex}`,
|
|
3292
|
+
style: { textAlign: block.align?.[cellIndex] || "left" }
|
|
3293
|
+
}, inlineNodes(cell, { ...options, keyPrefix: `${key}-h-${cellIndex}` }))))),
|
|
3294
|
+
h("tbody", null, block.rows.map((row, rowIndex) => h(
|
|
3295
|
+
"tr",
|
|
3296
|
+
{ key: `r-${rowIndex}` },
|
|
3297
|
+
row.map((cell, cellIndex) => h("td", {
|
|
3298
|
+
key: `c-${cellIndex}`,
|
|
3299
|
+
style: { textAlign: block.align?.[cellIndex] || "left" }
|
|
3300
|
+
}, inlineNodes(cell, { ...options, keyPrefix: `${key}-${rowIndex}-${cellIndex}` })))
|
|
3301
|
+
)))
|
|
3302
|
+
)
|
|
3303
|
+
);
|
|
3304
|
+
default:
|
|
3305
|
+
return null;
|
|
3306
|
+
}
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
function Markdown({ text, onOpenFile, onOpenAnchor }) {
|
|
3310
|
+
const { blocks } = parseCached(text);
|
|
3311
|
+
return h("div", { className: "dshsc-md" }, blockNodes(blocks, { onOpenFile, onOpenAnchor }));
|
|
3312
|
+
}
|
|
3313
|
+
function TabButton({ id, label, count, active, onSelect }) {
|
|
3314
|
+
return h("button", {
|
|
3315
|
+
type: "button",
|
|
3316
|
+
role: "tab",
|
|
3317
|
+
id: `dshsc-tab-${id}`,
|
|
3318
|
+
"aria-selected": String(active),
|
|
3319
|
+
"aria-controls": `dshsc-panel-${id}`,
|
|
3320
|
+
tabIndex: active ? 0 : -1,
|
|
3321
|
+
className: `dshsc-rtab${active ? " active" : ""}`,
|
|
3322
|
+
onClick: () => onSelect(id)
|
|
3323
|
+
}, label, count === null || count === void 0 ? null : h("i", { className: "n" }, count));
|
|
3324
|
+
}
|
|
3325
|
+
function InfoSection({ t, title, rows }) {
|
|
3326
|
+
return h(
|
|
3327
|
+
"section",
|
|
3328
|
+
{ className: "dshsc-info-sec" },
|
|
3329
|
+
h("h3", null, title),
|
|
3330
|
+
h("dl", null, rows.map((row, index) => [
|
|
3331
|
+
h("dt", { key: `t-${index}` }, t(row[0])),
|
|
3332
|
+
h("dd", { key: `d-${index}`, className: row[2] === "mono" ? "mono" : void 0 }, row[1])
|
|
3333
|
+
]))
|
|
3334
|
+
);
|
|
3335
|
+
}
|
|
3336
|
+
function SkillReader({
|
|
3337
|
+
t,
|
|
3338
|
+
variant = "drawer",
|
|
3339
|
+
row,
|
|
3340
|
+
writable,
|
|
3341
|
+
busy,
|
|
3342
|
+
readOnly,
|
|
3343
|
+
version,
|
|
3344
|
+
initialTab = "doc",
|
|
3345
|
+
initialFile = null,
|
|
3346
|
+
onClose,
|
|
3347
|
+
onOpenPage,
|
|
3348
|
+
onUpdate,
|
|
3349
|
+
onLinks,
|
|
3350
|
+
onToast
|
|
3351
|
+
}) {
|
|
3352
|
+
const { state, file, loadFile, retry } = useSkillReader(row);
|
|
3353
|
+
const [tab, setTab] = import_react4.default.useState(initialTab);
|
|
3354
|
+
const [format, setFormat] = import_react4.default.useState("rendered");
|
|
3355
|
+
const [docOverride, setDocOverride] = import_react4.default.useState(null);
|
|
3356
|
+
const [fileFormat, setFileFormat] = import_react4.default.useState("rendered");
|
|
3357
|
+
const [tocActive, setTocActive] = import_react4.default.useState(null);
|
|
3358
|
+
const bodyRef = import_react4.default.useRef(null);
|
|
3359
|
+
const dialogRef = import_react4.default.useRef(null);
|
|
3360
|
+
const scrollRef = import_react4.default.useRef(0);
|
|
3361
|
+
const name = row?.name || "";
|
|
3362
|
+
const tree = state.tree;
|
|
3363
|
+
const files = tree?.files || [];
|
|
3364
|
+
const fileCount = files.filter((entry) => entry.kind === "file").length;
|
|
3365
|
+
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
3366
|
+
const description = row?.description || state.body?.meta?.description || "";
|
|
3367
|
+
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
3368
|
+
import_react4.default.useEffect(() => {
|
|
3369
|
+
setDocOverride(null);
|
|
3370
|
+
scrollRef.current = 0;
|
|
3371
|
+
}, [name]);
|
|
3372
|
+
import_react4.default.useEffect(() => {
|
|
3373
|
+
if (!initialFile) return;
|
|
3374
|
+
setTab("files");
|
|
3375
|
+
void loadFile(initialFile);
|
|
3376
|
+
}, [initialFile, loadFile]);
|
|
3377
|
+
import_react4.default.useEffect(() => {
|
|
3378
|
+
const node = bodyRef.current;
|
|
3379
|
+
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
3380
|
+
});
|
|
3381
|
+
import_react4.default.useEffect(() => {
|
|
3382
|
+
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
3383
|
+
const previous = document.activeElement;
|
|
3384
|
+
const node = dialogRef.current;
|
|
3385
|
+
node?.focus?.();
|
|
3386
|
+
const onKeyDown = (event) => {
|
|
3387
|
+
if (event?.key === "Escape") {
|
|
3388
|
+
event.stopPropagation?.();
|
|
3389
|
+
onClose?.();
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
if (event?.key !== "Tab" || !node?.querySelectorAll) return;
|
|
3393
|
+
const focusables = Array.from(
|
|
3394
|
+
node.querySelectorAll('a[href],button:not([disabled]),[tabindex]:not([tabindex="-1"])')
|
|
3395
|
+
).filter((item) => item.offsetParent !== null || item === document.activeElement);
|
|
3396
|
+
if (focusables.length === 0) return;
|
|
3397
|
+
const first = focusables[0];
|
|
3398
|
+
const last = focusables[focusables.length - 1];
|
|
3399
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3400
|
+
event.preventDefault?.();
|
|
3401
|
+
last.focus?.();
|
|
3402
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3403
|
+
event.preventDefault?.();
|
|
3404
|
+
first.focus?.();
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3408
|
+
return () => {
|
|
3409
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
3410
|
+
previous?.focus?.();
|
|
3411
|
+
};
|
|
3412
|
+
}, [variant, onClose]);
|
|
3413
|
+
import_react4.default.useEffect(() => {
|
|
3414
|
+
if (variant !== "page" || typeof document === "undefined") return;
|
|
3415
|
+
const node = document.getElementById("dshsc-reader-name");
|
|
3416
|
+
node?.focus?.();
|
|
3417
|
+
}, [variant, name]);
|
|
3418
|
+
import_react4.default.useEffect(() => {
|
|
3419
|
+
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
3420
|
+
const node = bodyRef.current;
|
|
3421
|
+
if (!node?.querySelectorAll) return void 0;
|
|
3422
|
+
const scroller = scrollHost(node);
|
|
3423
|
+
const sync = () => {
|
|
3424
|
+
const line = (scroller?.getBoundingClientRect?.().top ?? 0) + 96;
|
|
3425
|
+
let current = null;
|
|
3426
|
+
for (const element of Array.from(node.querySelectorAll("[data-md-id]"))) {
|
|
3427
|
+
const top = element.getBoundingClientRect?.().top ?? 0;
|
|
3428
|
+
if (top <= line) current = element.getAttribute("data-md-id");
|
|
3429
|
+
}
|
|
3430
|
+
setTocActive(current || headings[0]?.id || null);
|
|
3431
|
+
};
|
|
3432
|
+
sync();
|
|
3433
|
+
scroller?.addEventListener?.("scroll", sync, { passive: true });
|
|
3434
|
+
return () => scroller?.removeEventListener?.("scroll", sync);
|
|
3435
|
+
}, [variant, headings.length, tab, bodyText]);
|
|
3436
|
+
function scrollToAnchor(id) {
|
|
3437
|
+
const node = bodyRef.current;
|
|
3438
|
+
if (!node?.querySelectorAll) return;
|
|
3439
|
+
const target = Array.from(node.querySelectorAll("[data-md-id]")).find((element) => element.getAttribute("data-md-id") === id);
|
|
3440
|
+
if (!target) return;
|
|
3441
|
+
const scroller = scrollHost(node);
|
|
3442
|
+
const offset = target.getBoundingClientRect().top - (scroller?.getBoundingClientRect?.().top ?? 0);
|
|
3443
|
+
if (scroller) scroller.scrollTop = Math.max(0, (scroller.scrollTop || 0) + offset - 12);
|
|
3444
|
+
setTocActive(id);
|
|
3445
|
+
}
|
|
3446
|
+
function openLinkedFile(href, fromPath) {
|
|
3447
|
+
const source = fromPath || docOverride?.relPath || "";
|
|
3448
|
+
const base = source.split("/").slice(0, -1).join("/");
|
|
3449
|
+
const resolved = resolveRelativeHref(base, href);
|
|
3450
|
+
if (!resolved.relPath) return;
|
|
3451
|
+
setTab("files");
|
|
3452
|
+
void loadFile(resolved.relPath);
|
|
3453
|
+
}
|
|
3454
|
+
async function copyTextWithToast(text, okKey) {
|
|
3455
|
+
let ok = false;
|
|
3456
|
+
try {
|
|
3457
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
3458
|
+
await navigator.clipboard.writeText(text);
|
|
3459
|
+
ok = true;
|
|
3460
|
+
}
|
|
3461
|
+
} catch {
|
|
3462
|
+
ok = false;
|
|
3463
|
+
}
|
|
3464
|
+
onToast?.(ok ? { kind: "ok", text: t(okKey) } : { kind: "err", text: t("viewer.copyFailed") });
|
|
3465
|
+
}
|
|
3466
|
+
function selectTab(next) {
|
|
3467
|
+
if (next === tab) return;
|
|
3468
|
+
if (bodyRef.current) scrollRef.current = 0;
|
|
3469
|
+
setTab(next);
|
|
3470
|
+
}
|
|
3471
|
+
const header = h(
|
|
3472
|
+
"header",
|
|
3473
|
+
{ className: "dshsc-reader-head" },
|
|
3474
|
+
h(
|
|
3475
|
+
"div",
|
|
3476
|
+
{ className: "dshsc-reader-title" },
|
|
3477
|
+
h("h2", { id: "dshsc-reader-name", tabIndex: -1 }, name || t("viewer.title")),
|
|
3478
|
+
row?.status && STATUS_KEY[row.status] ? h("span", { className: `dshsc-tag ${row.status === "current" ? "ok" : "warn"}` }, t(STATUS_KEY[row.status])) : null,
|
|
3479
|
+
row?.mode === "off" ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null,
|
|
3480
|
+
variant === "drawer" ? h("button", {
|
|
3481
|
+
type: "button",
|
|
3482
|
+
className: "dshsc-reader-close",
|
|
3483
|
+
"aria-label": t("viewer.close"),
|
|
3484
|
+
title: t("viewer.close"),
|
|
3485
|
+
onClick: () => onClose?.()
|
|
3486
|
+
}, "×") : null
|
|
3487
|
+
),
|
|
3488
|
+
description ? h("p", { className: "dshsc-reader-desc" }, description) : null,
|
|
3489
|
+
h(
|
|
3490
|
+
"div",
|
|
3491
|
+
{ className: "dshsc-reader-meta" },
|
|
3492
|
+
h(
|
|
3493
|
+
"span",
|
|
3494
|
+
null,
|
|
3495
|
+
`${t("meta.from")} `,
|
|
3496
|
+
h("span", { className: "dshsc-code" }, row?.source ? `${row.source}${row.commit ? `#${short(row.commit)}` : row.ref ? `#${row.ref}` : ""}` : t("meta.noUpstream"))
|
|
3497
|
+
),
|
|
3498
|
+
tree ? h("span", null, t("viewer.fileCount").replace("{n}", String(fileCount))) : null,
|
|
3499
|
+
row?.updatedAt ? h("span", null, formatWhen(row.updatedAt)) : null
|
|
3500
|
+
)
|
|
3501
|
+
);
|
|
3502
|
+
const tabsBar = h(
|
|
3503
|
+
"div",
|
|
3504
|
+
{ className: "dshsc-reader-tabs" },
|
|
3505
|
+
h(
|
|
3506
|
+
"div",
|
|
3507
|
+
{ className: "dshsc-rtabs", role: "tablist", "aria-label": t("viewer.title") },
|
|
3508
|
+
h(TabButton, { id: "doc", label: t("viewer.tab.doc"), active: tab === "doc", onSelect: selectTab }),
|
|
3509
|
+
h(TabButton, { id: "files", label: t("viewer.tab.files"), count: tree ? fileCount : null, active: tab === "files", onSelect: selectTab }),
|
|
3510
|
+
h(TabButton, { id: "info", label: t("viewer.tab.info"), active: tab === "info", onSelect: selectTab })
|
|
3511
|
+
),
|
|
3512
|
+
tab === "doc" && !docOverride ? h(
|
|
3513
|
+
"div",
|
|
3514
|
+
{ className: "dshsc-segment dshsc-reader-seg", role: "group", "aria-label": t("viewer.rendered") },
|
|
3515
|
+
h("button", {
|
|
3516
|
+
type: "button",
|
|
3517
|
+
className: "dshsc-btn",
|
|
3518
|
+
"aria-pressed": String(format === "rendered"),
|
|
3519
|
+
onClick: () => setFormat("rendered")
|
|
3520
|
+
}, t("viewer.rendered")),
|
|
3521
|
+
h("button", {
|
|
3522
|
+
type: "button",
|
|
3523
|
+
className: "dshsc-btn",
|
|
3524
|
+
"aria-pressed": String(format === "raw"),
|
|
3525
|
+
onClick: () => setFormat("raw")
|
|
3526
|
+
}, t("viewer.raw"))
|
|
3527
|
+
) : null
|
|
3528
|
+
);
|
|
3529
|
+
function docPanel() {
|
|
3530
|
+
if (state.loading) {
|
|
3531
|
+
return h(
|
|
3532
|
+
"div",
|
|
3533
|
+
{ className: "dshsc-reader-skeleton" },
|
|
3534
|
+
h("span", null, t("viewer.loading")),
|
|
3535
|
+
h("i"),
|
|
3536
|
+
h("i"),
|
|
3537
|
+
h("i")
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
if (!bodyText) {
|
|
3541
|
+
const error = state.bodyError;
|
|
3542
|
+
const missing = error?.code === "error.skills.no-skill-md";
|
|
3543
|
+
return h(
|
|
3544
|
+
"div",
|
|
3545
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3546
|
+
h("b", null, missing ? t("viewer.noSkill") : error?.message || t("viewer.noSkill")),
|
|
3547
|
+
missing ? h("span", null, t("viewer.noSkillHint")) : null,
|
|
3548
|
+
error && !missing ? h("button", { type: "button", className: "dshsc-btn", onClick: retry }, t("viewer.retry")) : null
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
if (docOverride) {
|
|
3552
|
+
return h(
|
|
3553
|
+
"div",
|
|
3554
|
+
{ className: "dshsc-reader-doc" },
|
|
3555
|
+
h(
|
|
3556
|
+
"div",
|
|
3557
|
+
{ className: "dshsc-reader-filebar" },
|
|
3558
|
+
h("span", { className: "mono" }, docOverride.relPath),
|
|
3559
|
+
h("button", {
|
|
3560
|
+
type: "button",
|
|
3561
|
+
className: "dshsc-btn",
|
|
3562
|
+
onClick: () => setDocOverride(null)
|
|
3563
|
+
}, t("viewer.backToBody"))
|
|
3564
|
+
),
|
|
3565
|
+
h(Markdown, { text: docOverride.content, onOpenFile: openLinkedFile, onOpenAnchor: scrollToAnchor })
|
|
3566
|
+
);
|
|
3567
|
+
}
|
|
3568
|
+
const truncated = Boolean(state.body?.truncated);
|
|
3569
|
+
return h(
|
|
3570
|
+
"div",
|
|
3571
|
+
{ className: "dshsc-reader-doc" },
|
|
3572
|
+
truncated ? h("div", { className: "dshsc-banner warn" }, t("viewer.tooLarge").replace("{size}", formatBytes(256 * 1024))) : null,
|
|
3573
|
+
format === "raw" ? h("pre", { className: "dshsc-reader-raw" }, h("code", null, bodyText)) : h(Markdown, { text: bodyText, onOpenFile: openLinkedFile, onOpenAnchor: scrollToAnchor })
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
function filesPanel() {
|
|
3577
|
+
if (state.treeError) {
|
|
3578
|
+
return h(
|
|
3579
|
+
"div",
|
|
3580
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3581
|
+
h("b", null, state.treeError.message),
|
|
3582
|
+
h("button", { type: "button", className: "dshsc-btn", onClick: retry }, t("viewer.retry"))
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
if (!tree) return h("div", { className: "dshsc-reader-skeleton" }, h("i"), h("i"));
|
|
3586
|
+
if (files.length === 0) {
|
|
3587
|
+
return h("div", { className: "dshsc-reader-placeholder" }, h("b", null, t("viewer.emptyFiles")));
|
|
3588
|
+
}
|
|
3589
|
+
const active = file.relPath;
|
|
3590
|
+
const activeEntry = files.find((entry) => entry.kind === "file" && entry.path === active) || null;
|
|
3591
|
+
const renderable = Boolean(active) && isMarkdownPath(active) && !file.error && !file.loading;
|
|
3592
|
+
const preview = file.loading ? h("div", { className: "dshsc-reader-skeleton" }, h("i"), h("i"), h("i")) : file.error ? h(
|
|
3593
|
+
"div",
|
|
3594
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3595
|
+
h("b", null, file.error.code === "error.skills.binary" ? t("viewer.binary") : file.error.message),
|
|
3596
|
+
h(
|
|
3597
|
+
"span",
|
|
3598
|
+
{ className: "mono" },
|
|
3599
|
+
[active, activeEntry ? formatBytes(activeEntry.size) : null].filter(Boolean).join(" · ")
|
|
3600
|
+
),
|
|
3601
|
+
file.error.code === "error.skills.binary" ? h("span", null, t("viewer.binaryHint")) : null
|
|
3602
|
+
) : renderable && fileFormat === "rendered" ? h(
|
|
3603
|
+
"div",
|
|
3604
|
+
{ className: "dshsc-reader-preview-doc" },
|
|
3605
|
+
h(Markdown, {
|
|
3606
|
+
text: file.content,
|
|
3607
|
+
onOpenFile: (href) => openLinkedFile(href, file.relPath || active),
|
|
3608
|
+
onOpenAnchor: scrollToAnchor
|
|
3609
|
+
})
|
|
3610
|
+
) : h(
|
|
3611
|
+
"pre",
|
|
3612
|
+
{ className: "dshsc-reader-raw" },
|
|
3613
|
+
h("code", null, file.content + (file.truncated ? `
|
|
3614
|
+
|
|
3615
|
+
… ${t("viewer.tooLarge").replace("{size}", formatBytes(256 * 1024))}` : ""))
|
|
3616
|
+
);
|
|
3617
|
+
return h(
|
|
3618
|
+
"div",
|
|
3619
|
+
{ className: "dshsc-reader-files" },
|
|
3620
|
+
h(
|
|
3621
|
+
"div",
|
|
3622
|
+
{ className: "dshsc-reader-tree" },
|
|
3623
|
+
h(
|
|
3624
|
+
"div",
|
|
3625
|
+
{ className: "dshsc-reader-tree-head" },
|
|
3626
|
+
h("span", null, name),
|
|
3627
|
+
h("span", { className: "mono" }, tree.kind === "file" ? tree.body : `~/.agents/skills/${name}`)
|
|
3628
|
+
),
|
|
3629
|
+
files.map((entry) => {
|
|
3630
|
+
if (entry.kind === "dir") {
|
|
3631
|
+
const count = files.filter((item) => item.kind === "file" && item.path.startsWith(`${entry.path}/`)).length;
|
|
3632
|
+
return h(
|
|
3633
|
+
"div",
|
|
3634
|
+
{ className: "dshsc-reader-tdir", key: `d:${entry.path}` },
|
|
3635
|
+
h(FolderIcon, null),
|
|
3636
|
+
h("span", { className: "mono" }, `${entry.path}/`),
|
|
3637
|
+
h("span", { className: "n" }, String(count))
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
const depth = entry.path.split("/").length - 1;
|
|
3641
|
+
return h(
|
|
3642
|
+
"button",
|
|
3643
|
+
{
|
|
3644
|
+
type: "button",
|
|
3645
|
+
key: `f:${entry.path}`,
|
|
3646
|
+
className: `dshsc-reader-titem${active === entry.path ? " on" : ""}`,
|
|
3647
|
+
style: { paddingLeft: 8 + depth * 12 },
|
|
3648
|
+
title: entry.path,
|
|
3649
|
+
onClick: () => {
|
|
3650
|
+
setFileFormat("rendered");
|
|
3651
|
+
void loadFile(entry.path);
|
|
3652
|
+
}
|
|
3653
|
+
},
|
|
3654
|
+
h(FileIcon, null),
|
|
3655
|
+
h("span", { className: "nname" }, entry.path.split("/").pop()),
|
|
3656
|
+
h("span", { className: "nsize" }, formatBytes(entry.size))
|
|
3657
|
+
);
|
|
3658
|
+
})
|
|
3659
|
+
),
|
|
3660
|
+
h(
|
|
3661
|
+
"div",
|
|
3662
|
+
{ className: "dshsc-reader-preview" },
|
|
3663
|
+
h(
|
|
3664
|
+
"div",
|
|
3665
|
+
{ className: "dshsc-reader-tree-head" },
|
|
3666
|
+
h("span", null, t("viewer.tab.files")),
|
|
3667
|
+
h("span", { className: "mono" }, active || "—"),
|
|
3668
|
+
// 预览的三个动作放在一起,靠右成一排,而不是一个贴路径、一个飘到最右
|
|
3669
|
+
h(
|
|
3670
|
+
"div",
|
|
3671
|
+
{ className: "dshsc-reader-tools" },
|
|
3672
|
+
renderable ? h(
|
|
3673
|
+
"div",
|
|
3674
|
+
{ className: "dshsc-segment dshsc-reader-seg", role: "group", "aria-label": t("viewer.rendered") },
|
|
3675
|
+
h("button", {
|
|
3676
|
+
type: "button",
|
|
3677
|
+
className: "dshsc-btn",
|
|
3678
|
+
"aria-pressed": String(fileFormat === "rendered"),
|
|
3679
|
+
onClick: () => setFileFormat("rendered")
|
|
3680
|
+
}, t("viewer.rendered")),
|
|
3681
|
+
h("button", {
|
|
3682
|
+
type: "button",
|
|
3683
|
+
className: "dshsc-btn",
|
|
3684
|
+
"aria-pressed": String(fileFormat === "raw"),
|
|
3685
|
+
onClick: () => setFileFormat("raw")
|
|
3686
|
+
}, t("viewer.raw"))
|
|
3687
|
+
) : null,
|
|
3688
|
+
active && !file.loading && !file.error && isMarkdownPath(active) ? h("button", {
|
|
3689
|
+
type: "button",
|
|
3690
|
+
className: "dshsc-btn",
|
|
3691
|
+
onClick: () => {
|
|
3692
|
+
setDocOverride({ relPath: file.relPath || active, content: file.content });
|
|
3693
|
+
setTab("doc");
|
|
3694
|
+
}
|
|
3695
|
+
}, t("viewer.openInDoc")) : null
|
|
3696
|
+
)
|
|
3697
|
+
),
|
|
3698
|
+
preview
|
|
3699
|
+
)
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
function infoPanel() {
|
|
3703
|
+
const agentLabels = (row?.links || []).filter((link) => !link.broken).map((link) => link.target === "dsh-global" ? "DSH" : link.label);
|
|
3704
|
+
const projects = row?.scopeProjects || [];
|
|
3705
|
+
const hash = row?.lockHash || row?.currentHash || null;
|
|
3706
|
+
const rows = [];
|
|
3707
|
+
rows.push(["viewer.info.name", h("span", { className: "mono" }, name), "mono"]);
|
|
3708
|
+
if (description) rows.push(["viewer.info.desc", description]);
|
|
3709
|
+
rows.push(["viewer.info.path", h("span", { className: "mono" }, row?.path || (tree?.root ?? t("viewer.info.none"))), "mono"]);
|
|
3710
|
+
const sourceRows = [
|
|
3711
|
+
["viewer.info.repo", row?.source ? h("span", { className: "mono" }, row.source) : t("viewer.info.none"), "mono"],
|
|
3712
|
+
["viewer.info.ref", row?.commit ? h("span", { className: "mono" }, short(row.commit)) : row?.ref || t("viewer.info.none"), "mono"],
|
|
3713
|
+
["viewer.info.skillPath", row?.skillPath ? h("span", { className: "mono" }, row.skillPath) : t("viewer.info.none"), "mono"],
|
|
3714
|
+
["viewer.info.hash", hash ? h("span", { className: "mono" }, hash) : t("viewer.info.none"), "mono"]
|
|
3715
|
+
];
|
|
3716
|
+
const localRows = [
|
|
3717
|
+
["viewer.info.installedAt", formatWhen(row?.installedAt) || t("viewer.info.none")],
|
|
3718
|
+
["viewer.info.updatedAt", formatWhen(row?.updatedAt) || t("viewer.info.none")],
|
|
3719
|
+
["viewer.info.size", tree ? `${formatBytes(tree.totalBytes)} · ${t("viewer.fileCount").replace("{n}", String(fileCount))}` : t("viewer.info.none")],
|
|
3720
|
+
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
3721
|
+
];
|
|
3722
|
+
const linkRows = [
|
|
3723
|
+
["viewer.info.agents", agentLabels.length > 0 ? agentLabels.join("、") : t("meta.unlinked")],
|
|
3724
|
+
["viewer.info.scope", projects.length > 0 ? `${t("viewer.info.scopeProject")}:${projects.map((item) => item.split("/").pop()).join("、")}` : t("viewer.info.scopeGlobal")],
|
|
3725
|
+
["viewer.info.mode", row?.mode === "off" ? t("status.off") : t("mode.on")],
|
|
3726
|
+
["viewer.info.status", STATUS_KEY[row?.status] ? t(STATUS_KEY[row.status]) : t("status.unknown")]
|
|
3727
|
+
];
|
|
3728
|
+
return h(
|
|
3729
|
+
"div",
|
|
3730
|
+
{ className: "dshsc-reader-info" },
|
|
3731
|
+
h(InfoSection, { t, title: t("viewer.info.basic"), rows }),
|
|
3732
|
+
h(InfoSection, { t, title: t("viewer.info.source"), rows: sourceRows }),
|
|
3733
|
+
h(InfoSection, { t, title: t("viewer.info.local"), rows: localRows }),
|
|
3734
|
+
h(InfoSection, { t, title: t("viewer.info.links"), rows: linkRows })
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
function panel() {
|
|
3738
|
+
if (tab === "files") return filesPanel();
|
|
3739
|
+
if (tab === "info") return infoPanel();
|
|
3740
|
+
return docPanel();
|
|
3741
|
+
}
|
|
3742
|
+
const body = h("div", {
|
|
3743
|
+
className: "dshsc-reader-body",
|
|
3744
|
+
ref: bodyRef,
|
|
3745
|
+
role: "tabpanel",
|
|
3746
|
+
id: `dshsc-panel-${tab}`,
|
|
3747
|
+
"aria-labelledby": `dshsc-tab-${tab}`,
|
|
3748
|
+
tabIndex: -1
|
|
3749
|
+
}, panel());
|
|
3750
|
+
const canUpdate = Boolean(writable) && Boolean(row) && !busy;
|
|
3751
|
+
const actionsFooter = h(
|
|
3752
|
+
"div",
|
|
3753
|
+
{ className: "dshsc-reader-actions" },
|
|
3754
|
+
h("span", { className: "dshsc-reader-note" }, readOnly ? t("banner.readonly") : t("viewer.readonlyNote")),
|
|
3755
|
+
h("button", {
|
|
3756
|
+
type: "button",
|
|
3757
|
+
// 抽屉底栏空间紧,图标按钮;整页顶栏跟一排文字按钮并排,用文字更齐
|
|
3758
|
+
className: variant === "page" ? "dshsc-btn ghost" : "dshsc-btn ghost sq",
|
|
3759
|
+
title: t("viewer.copyBody"),
|
|
3760
|
+
"aria-label": t("viewer.copyBody"),
|
|
3761
|
+
disabled: !bodyText,
|
|
3762
|
+
onClick: () => void copyTextWithToast(bodyText, "viewer.copied")
|
|
3763
|
+
}, h(CopyIcon, null), variant === "page" ? t("viewer.copyBody") : null),
|
|
3764
|
+
h("button", {
|
|
3765
|
+
type: "button",
|
|
3766
|
+
className: "dshsc-btn ghost",
|
|
3767
|
+
title: row?.path || "",
|
|
3768
|
+
disabled: !row?.path,
|
|
3769
|
+
onClick: () => void copyTextWithToast(row?.path || "", "viewer.copied")
|
|
3770
|
+
}, t("viewer.copyPath")),
|
|
3771
|
+
variant === "drawer" && onOpenPage ? h("button", { type: "button", className: "dshsc-btn ghost", onClick: () => onOpenPage() }, t("viewer.openPage")) : null,
|
|
3772
|
+
onLinks ? h("button", { type: "button", className: "dshsc-btn", onClick: () => onLinks?.(row) }, t("action.links")) : null,
|
|
3773
|
+
onUpdate ? h("button", {
|
|
3774
|
+
type: "button",
|
|
3775
|
+
className: "dshsc-btn tint",
|
|
3776
|
+
disabled: !canUpdate,
|
|
3777
|
+
onClick: () => onUpdate?.(row)
|
|
3778
|
+
}, t("action.update")) : null
|
|
3779
|
+
);
|
|
3780
|
+
if (variant === "page") {
|
|
3781
|
+
return h(
|
|
3782
|
+
"div",
|
|
3783
|
+
{ className: "dshsc-reader-page" },
|
|
3784
|
+
h(
|
|
3785
|
+
"div",
|
|
3786
|
+
{ className: "dshsc-reader-topbar" },
|
|
3787
|
+
h(
|
|
3788
|
+
"button",
|
|
3789
|
+
{ type: "button", className: "dshsc-btn ghost", "data-autofocus": true, onClick: () => onClose?.() },
|
|
3790
|
+
h(ChevronLeftIcon, null),
|
|
3791
|
+
t("viewer.back")
|
|
3792
|
+
),
|
|
3793
|
+
h(
|
|
3794
|
+
"span",
|
|
3795
|
+
{ className: "dshsc-reader-crumb" },
|
|
3796
|
+
h("span", null, t("tab.installed")),
|
|
3797
|
+
h("span", { className: "sep" }, "/"),
|
|
3798
|
+
h("span", { className: "mono" }, name)
|
|
3799
|
+
),
|
|
3800
|
+
h("span", { className: "spacer" }),
|
|
3801
|
+
actionsFooter
|
|
3802
|
+
),
|
|
3803
|
+
h("div", { className: "dshsc-reader-hero" }, header),
|
|
3804
|
+
h(
|
|
3805
|
+
"div",
|
|
3806
|
+
{ className: "dshsc-reader-layout" },
|
|
3807
|
+
headings.length > 1 ? h(
|
|
3808
|
+
"nav",
|
|
3809
|
+
{ className: "dshsc-reader-toc", "aria-label": t("viewer.toc") },
|
|
3810
|
+
h("div", { className: "dshsc-reader-toc-title" }, t("viewer.toc")),
|
|
3811
|
+
headings.map((heading) => h("button", {
|
|
3812
|
+
type: "button",
|
|
3813
|
+
key: heading.id,
|
|
3814
|
+
className: `dshsc-reader-tocitem l${heading.level}${tocActive === heading.id ? " on" : ""}`,
|
|
3815
|
+
onClick: () => scrollToAnchor(heading.id)
|
|
3816
|
+
}, heading.text))
|
|
3817
|
+
) : null,
|
|
3818
|
+
h("div", { className: "dshsc-reader-col" }, tabsBar, body)
|
|
3819
|
+
)
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3822
|
+
return portalize(h(
|
|
3823
|
+
"div",
|
|
3824
|
+
{ className: "dshsc-drawer-wrap" },
|
|
3825
|
+
h("div", { className: "dshsc-drawer-scrim", onClick: () => onClose?.() }),
|
|
3826
|
+
h("aside", {
|
|
3827
|
+
className: "dshsc-drawer",
|
|
3828
|
+
role: "dialog",
|
|
3829
|
+
"aria-modal": "true",
|
|
3830
|
+
"aria-labelledby": "dshsc-reader-name",
|
|
3831
|
+
ref: dialogRef,
|
|
3832
|
+
tabIndex: -1
|
|
3833
|
+
}, header, tabsBar, body, actionsFooter)
|
|
3834
|
+
));
|
|
3835
|
+
}
|
|
3836
|
+
|
|
2318
3837
|
// client/section.js
|
|
2319
3838
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
2320
3839
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
2321
3840
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
2322
|
-
const [tab, setTab] =
|
|
2323
|
-
const [view, setView] =
|
|
2324
|
-
const [modal, setModal] =
|
|
2325
|
-
const [expanded, setExpanded] =
|
|
2326
|
-
const [placementBusy, setPlacementBusy] =
|
|
2327
|
-
const [placementOverride, setPlacementOverride] =
|
|
2328
|
-
const [query, setQuery] =
|
|
2329
|
-
const [
|
|
2330
|
-
const
|
|
3841
|
+
const [tab, setTab] = import_react5.default.useState("installed");
|
|
3842
|
+
const [view, setView] = import_react5.default.useState("flat");
|
|
3843
|
+
const [modal, setModal] = import_react5.default.useState(null);
|
|
3844
|
+
const [expanded, setExpanded] = import_react5.default.useState({});
|
|
3845
|
+
const [placementBusy, setPlacementBusy] = import_react5.default.useState(null);
|
|
3846
|
+
const [placementOverride, setPlacementOverride] = import_react5.default.useState(null);
|
|
3847
|
+
const [query, setQuery] = import_react5.default.useState("");
|
|
3848
|
+
const [viewer, setViewer] = import_react5.default.useState(null);
|
|
3849
|
+
const rootRef = import_react5.default.useRef(null);
|
|
3850
|
+
const scrollPos = import_react5.default.useRef(null);
|
|
3851
|
+
const [filter, setFilter] = import_react5.default.useState("all");
|
|
3852
|
+
const [builtinOpen, setBuiltinOpen] = import_react5.default.useState(false);
|
|
2331
3853
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
2332
3854
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
2333
3855
|
const rows = data?.installs || [];
|
|
@@ -2348,12 +3870,55 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2348
3870
|
const agentLabels = row.links.map((link) => link.target === "dsh-global" ? "DSH" : link.label);
|
|
2349
3871
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
2350
3872
|
});
|
|
3873
|
+
import_react5.default.useEffect(() => {
|
|
3874
|
+
if (!viewer || !data) return;
|
|
3875
|
+
if (rows.some((row) => row.name === viewer.name)) return;
|
|
3876
|
+
setViewer(null);
|
|
3877
|
+
setToast({ kind: "err", text: t("viewer.gone") });
|
|
3878
|
+
}, [data, rows, t, viewer]);
|
|
2351
3879
|
const autolinkTargets = (data?.agents?.detected || []).filter((agent) => agent.kind === "agent");
|
|
2352
3880
|
const autoLinkSelection = data?.settings?.autoLinkTargets;
|
|
2353
3881
|
const autoLinkAgents = Array.isArray(autoLinkSelection) ? autolinkTargets.filter((agent) => autoLinkSelection.includes(agent.target)) : autolinkTargets;
|
|
2354
3882
|
const autoLinkDsh = (data?.agents?.all || []).find((agent) => agent.target === "dsh-global") || null;
|
|
2355
3883
|
const autoLinkLabel = [t("links.dshGlobal"), ...autoLinkAgents.map((agent) => agent.label)].join("、");
|
|
2356
3884
|
const placement = (placementOverride ?? data?.settings?.placement) === "sidebar" ? "sidebar" : "settings";
|
|
3885
|
+
function getScrollParent(node) {
|
|
3886
|
+
let current = node?.parentElement || null;
|
|
3887
|
+
while (current) {
|
|
3888
|
+
if (typeof getComputedStyle === "function") {
|
|
3889
|
+
const style = getComputedStyle(current);
|
|
3890
|
+
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight) {
|
|
3891
|
+
return current;
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
current = current.parentElement || null;
|
|
3895
|
+
}
|
|
3896
|
+
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3897
|
+
}
|
|
3898
|
+
function openViewer(row, mode = "drawer") {
|
|
3899
|
+
if (!row) return;
|
|
3900
|
+
if (mode === "page") {
|
|
3901
|
+
scrollPos.current = getScrollParent(rootRef.current)?.scrollTop ?? 0;
|
|
3902
|
+
}
|
|
3903
|
+
setViewer({ name: row.name, mode });
|
|
3904
|
+
}
|
|
3905
|
+
function openPage() {
|
|
3906
|
+
setViewer((prev) => {
|
|
3907
|
+
if (!prev) return prev;
|
|
3908
|
+
scrollPos.current = getScrollParent(rootRef.current)?.scrollTop ?? 0;
|
|
3909
|
+
return { ...prev, mode: "page" };
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
function closeViewer() {
|
|
3913
|
+
const restore = viewer?.mode === "page" ? scrollPos.current : null;
|
|
3914
|
+
setViewer(null);
|
|
3915
|
+
if (restore === null || typeof window === "undefined") return;
|
|
3916
|
+
const apply2 = () => {
|
|
3917
|
+
const scroller = getScrollParent(rootRef.current);
|
|
3918
|
+
if (scroller) scroller.scrollTop = restore;
|
|
3919
|
+
};
|
|
3920
|
+
if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(apply2);
|
|
3921
|
+
}
|
|
2357
3922
|
async function changePlacement(next) {
|
|
2358
3923
|
if (next === placement || placementBusy) return;
|
|
2359
3924
|
setPlacementBusy(next);
|
|
@@ -2373,6 +3938,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2373
3938
|
await run("settings", () => apiCall("POST", "/settings", { autoLinkTargets: targets }));
|
|
2374
3939
|
}
|
|
2375
3940
|
const installedActions = {
|
|
3941
|
+
onView: (row) => openViewer(row),
|
|
2376
3942
|
onAdopt: actions.adopt,
|
|
2377
3943
|
onToggleMode: actions.toggleMode,
|
|
2378
3944
|
onUpdate: actions.requestUpdate,
|
|
@@ -2398,10 +3964,23 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2398
3964
|
onInstall: actions.installFromSubscription,
|
|
2399
3965
|
onAdd: actions.requestAddSubscription
|
|
2400
3966
|
};
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
3967
|
+
const viewerRow = viewer ? rows.find((row) => row.name === viewer.name) || null : null;
|
|
3968
|
+
const readerNode = viewerRow ? h(SkillReader, {
|
|
3969
|
+
t,
|
|
3970
|
+
variant: viewer.mode,
|
|
3971
|
+
row: viewerRow,
|
|
3972
|
+
writable,
|
|
3973
|
+
busy,
|
|
3974
|
+
readOnly: Boolean(data?.readOnly),
|
|
3975
|
+
onClose: closeViewer,
|
|
3976
|
+
onOpenPage: viewer.mode === "drawer" ? openPage : null,
|
|
3977
|
+
onUpdate: actions.requestUpdate,
|
|
3978
|
+
onLinks: (row) => setModal({ type: "links", row }),
|
|
3979
|
+
onToast: (next) => setToast(next)
|
|
3980
|
+
}) : null;
|
|
3981
|
+
const listNode = h(
|
|
3982
|
+
import_react5.default.Fragment,
|
|
3983
|
+
null,
|
|
2405
3984
|
h(Header, {
|
|
2406
3985
|
t,
|
|
2407
3986
|
page,
|
|
@@ -2420,7 +3999,8 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2420
3999
|
onChangePlacement: (next) => void changePlacement(next),
|
|
2421
4000
|
onOpenAutoLink: () => setModal({ type: "autolink" }),
|
|
2422
4001
|
onAddSkill: actions.requestAddSkill,
|
|
2423
|
-
onAddSubscription: actions.requestAddSubscription
|
|
4002
|
+
onAddSubscription: actions.requestAddSubscription,
|
|
4003
|
+
onToast: (next) => setToast(next)
|
|
2424
4004
|
}),
|
|
2425
4005
|
error ? h("div", { className: "dshsc-banner err" }, `${error} · `, h("button", { className: "dshsc-btn ghost", onClick: () => void reload() }, t("action.retry"))) : null,
|
|
2426
4006
|
data?.readOnly ? h("div", { className: "dshsc-banner warn" }, t("banner.readonly")) : null,
|
|
@@ -2460,7 +4040,14 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2460
4040
|
writable,
|
|
2461
4041
|
...subscriptionActions
|
|
2462
4042
|
}),
|
|
2463
|
-
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|
|
4043
|
+
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|
|
4044
|
+
);
|
|
4045
|
+
const root = h(
|
|
4046
|
+
"div",
|
|
4047
|
+
{ className: "dshsc-root", ref: rootRef },
|
|
4048
|
+
h("style", null, CSS),
|
|
4049
|
+
viewer?.mode === "page" ? null : listNode,
|
|
4050
|
+
readerNode,
|
|
2464
4051
|
toast ? h("div", { className: `dshsc-toast ${toast.kind}` }, toast.text) : null,
|
|
2465
4052
|
modal ? h(Modals, {
|
|
2466
4053
|
t,
|