@morehao/dsh-skills-console 0.3.0 → 0.3.2
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 +1615 -36
- 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 +6 -1
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -114,6 +114,8 @@ var DICT = {
|
|
|
114
114
|
"status.broken": "异常:软链断裂",
|
|
115
115
|
"status.unmanaged": "未纳入追踪",
|
|
116
116
|
"status.unknown": "待检查",
|
|
117
|
+
"status.noUpstream": "无上游",
|
|
118
|
+
"status.noUpstreamHint": "没有可用的订阅来源,无法检查更新;重新接管或添加订阅后再试",
|
|
117
119
|
"status.off": "已停用",
|
|
118
120
|
"status.brokenHint": "软链目标不存在:重新接入,或删除后重装",
|
|
119
121
|
"adopt.switchHint": "还未纳入追踪,不能启停",
|
|
@@ -138,7 +140,7 @@ var DICT = {
|
|
|
138
140
|
"links.hint": "保存后按勾选建立或移除接入;技能本体保留在原位置,不会移动。",
|
|
139
141
|
"autoLink.detected": "安装后自动接入",
|
|
140
142
|
"autoLink.none": "本机没有检测到其他 Agent",
|
|
141
|
-
"autoLink.hint": "dsh
|
|
143
|
+
"autoLink.hint": "列表里的「dsh(全局)」固定勾选、不能取消;这里只影响之后安装时的自动接入,已安装的技能请在「接入设置」里改。",
|
|
142
144
|
"empty.skills": "还没有安装任何技能",
|
|
143
145
|
"empty.skillsHint": "在终端跑 `npx skills add owner/repo`,或点右上角「添加技能」",
|
|
144
146
|
"empty.subs": "还没有添加订阅",
|
|
@@ -200,7 +202,62 @@ var DICT = {
|
|
|
200
202
|
visibleOnlyBuiltin: "本插件仅管理 ~/.agents/skills 下的技能,DSH 内置技能不在管理范围内",
|
|
201
203
|
"builtin.view": "查看内置技能({n})",
|
|
202
204
|
"builtin.hide": "收起内置技能",
|
|
203
|
-
external: "非本插件创建"
|
|
205
|
+
external: "非本插件创建",
|
|
206
|
+
// 查看器:抽屉 / 整页共用的文案
|
|
207
|
+
"viewer.title": "技能内容",
|
|
208
|
+
"viewer.close": "关闭",
|
|
209
|
+
"viewer.openPage": "在整页打开",
|
|
210
|
+
"viewer.back": "返回列表",
|
|
211
|
+
"viewer.tab.doc": "正文",
|
|
212
|
+
"viewer.tab.files": "文件",
|
|
213
|
+
"viewer.tab.info": "信息",
|
|
214
|
+
"viewer.rendered": "渲染",
|
|
215
|
+
"viewer.raw": "原文",
|
|
216
|
+
"viewer.loading": "正在读取 SKILL.md…",
|
|
217
|
+
"viewer.retry": "重试",
|
|
218
|
+
"viewer.copyPath": "复制路径",
|
|
219
|
+
"viewer.copyBody": "复制正文",
|
|
220
|
+
"viewer.copied": "已复制",
|
|
221
|
+
"viewer.copyFailed": "复制失败,请手动选中后复制",
|
|
222
|
+
"viewer.gone": "这个技能已不在本地,查看器已关闭",
|
|
223
|
+
"viewer.noSkill": "这个技能里没有 SKILL.md",
|
|
224
|
+
"viewer.noSkillHint": "DSH 解析不到它,但目录里的其他文件仍可以看",
|
|
225
|
+
"viewer.binary": "暂不支持预览",
|
|
226
|
+
"viewer.binaryHint": "二进制或非文本文件,只显示文件名与大小",
|
|
227
|
+
"viewer.textNote": "只读,直接显示源码",
|
|
228
|
+
"viewer.tooLarge": "超过 {size},只显示前一部分",
|
|
229
|
+
"viewer.emptyFiles": "这个技能目录里没有可读的文件",
|
|
230
|
+
"viewer.fileCount": "{n} 个文件",
|
|
231
|
+
"viewer.size": "{size}",
|
|
232
|
+
"viewer.backToBody": "回到 SKILL.md",
|
|
233
|
+
"viewer.openInDoc": "在正文中打开",
|
|
234
|
+
"viewer.toc": "目录",
|
|
235
|
+
"viewer.readonlyNote": "只读,内容来自本机 SKILL.md",
|
|
236
|
+
"viewer.info.basic": "标识",
|
|
237
|
+
"viewer.info.name": "名称",
|
|
238
|
+
"viewer.info.desc": "描述",
|
|
239
|
+
"viewer.info.path": "本地路径",
|
|
240
|
+
"viewer.info.source": "来源",
|
|
241
|
+
"viewer.info.repo": "仓库",
|
|
242
|
+
"viewer.info.ref": "基准",
|
|
243
|
+
"viewer.info.commit": "commit",
|
|
244
|
+
"viewer.info.skillPath": "skillPath",
|
|
245
|
+
"viewer.info.hash": "内容哈希",
|
|
246
|
+
"viewer.info.installedAt": "安装",
|
|
247
|
+
"viewer.info.updatedAt": "更新",
|
|
248
|
+
"viewer.info.size": "大小",
|
|
249
|
+
"viewer.info.local": "本地",
|
|
250
|
+
"viewer.info.localChange": "本地改动",
|
|
251
|
+
"viewer.info.localChangeNone": "无(与安装时一致)",
|
|
252
|
+
"viewer.info.localChangeYes": "有本地改动",
|
|
253
|
+
"viewer.info.links": "接入",
|
|
254
|
+
"viewer.info.agents": "Agent",
|
|
255
|
+
"viewer.info.scope": "范围",
|
|
256
|
+
"viewer.info.scopeGlobal": "全局",
|
|
257
|
+
"viewer.info.scopeProject": "仅项目",
|
|
258
|
+
"viewer.info.mode": "状态",
|
|
259
|
+
"viewer.info.status": "更新",
|
|
260
|
+
"viewer.info.none": "无"
|
|
204
261
|
},
|
|
205
262
|
en: {
|
|
206
263
|
"section.title": "Skills Console",
|
|
@@ -245,6 +302,8 @@ var DICT = {
|
|
|
245
302
|
"status.broken": "Broken link",
|
|
246
303
|
"status.unmanaged": "Untracked",
|
|
247
304
|
"status.unknown": "Not checked",
|
|
305
|
+
"status.noUpstream": "No upstream",
|
|
306
|
+
"status.noUpstreamHint": "No usable subscription source, so update checks are impossible; re-adopt or add a subscription",
|
|
248
307
|
"status.off": "Disabled",
|
|
249
308
|
"status.brokenHint": "The link target is missing: relink, or delete and reinstall",
|
|
250
309
|
"adopt.switchHint": "Not tracked yet, cannot be enabled or disabled",
|
|
@@ -268,7 +327,7 @@ var DICT = {
|
|
|
268
327
|
"links.hint": "Saving only adds or removes links; the skill itself stays where it is.",
|
|
269
328
|
"autoLink.detected": "Auto-link after install",
|
|
270
329
|
"autoLink.none": "No other agents detected on this machine",
|
|
271
|
-
"autoLink.hint": 'dsh (global) is always
|
|
330
|
+
"autoLink.hint": 'The "dsh (global)" entry is always checked and cannot be unchecked; this only affects future installs. Change existing skills under "Link settings".',
|
|
272
331
|
"empty.skills": "No skills installed yet",
|
|
273
332
|
"empty.skillsHint": 'Run `npx skills add owner/repo` or use "Add skill".',
|
|
274
333
|
"empty.subs": "No subscriptions yet",
|
|
@@ -331,7 +390,62 @@ var DICT = {
|
|
|
331
390
|
"builtin.view": "View built-ins ({n})",
|
|
332
391
|
"builtin.hide": "Hide built-ins",
|
|
333
392
|
external: "created outside this plugin",
|
|
334
|
-
"adopt.done": "now tracked"
|
|
393
|
+
"adopt.done": "now tracked",
|
|
394
|
+
// Viewer: shared by the drawer and the full page
|
|
395
|
+
"viewer.title": "Skill content",
|
|
396
|
+
"viewer.close": "Close",
|
|
397
|
+
"viewer.openPage": "Open as page",
|
|
398
|
+
"viewer.back": "Back to list",
|
|
399
|
+
"viewer.tab.doc": "Content",
|
|
400
|
+
"viewer.tab.files": "Files",
|
|
401
|
+
"viewer.tab.info": "Info",
|
|
402
|
+
"viewer.rendered": "Rendered",
|
|
403
|
+
"viewer.raw": "Raw",
|
|
404
|
+
"viewer.loading": "Reading SKILL.md…",
|
|
405
|
+
"viewer.retry": "Retry",
|
|
406
|
+
"viewer.copyPath": "Copy path",
|
|
407
|
+
"viewer.copyBody": "Copy content",
|
|
408
|
+
"viewer.copied": "Copied",
|
|
409
|
+
"viewer.copyFailed": "Copy failed, select the text manually",
|
|
410
|
+
"viewer.gone": "This skill is gone from disk; the viewer was closed",
|
|
411
|
+
"viewer.noSkill": "This skill has no SKILL.md",
|
|
412
|
+
"viewer.noSkillHint": "DSH cannot parse it, but the other files are still readable",
|
|
413
|
+
"viewer.binary": "Preview not supported",
|
|
414
|
+
"viewer.binaryHint": "Binary or non-text file: only the name and size are shown",
|
|
415
|
+
"viewer.textNote": "Read-only source",
|
|
416
|
+
"viewer.tooLarge": "Larger than {size}; showing the first part only",
|
|
417
|
+
"viewer.emptyFiles": "No readable files in this skill folder",
|
|
418
|
+
"viewer.fileCount": "{n} files",
|
|
419
|
+
"viewer.size": "{size}",
|
|
420
|
+
"viewer.backToBody": "Back to SKILL.md",
|
|
421
|
+
"viewer.openInDoc": "Open in content",
|
|
422
|
+
"viewer.toc": "Contents",
|
|
423
|
+
"viewer.readonlyNote": "Read-only, content comes from the local SKILL.md",
|
|
424
|
+
"viewer.info.basic": "Identity",
|
|
425
|
+
"viewer.info.name": "Name",
|
|
426
|
+
"viewer.info.desc": "Description",
|
|
427
|
+
"viewer.info.path": "Local path",
|
|
428
|
+
"viewer.info.source": "Origin",
|
|
429
|
+
"viewer.info.repo": "Repository",
|
|
430
|
+
"viewer.info.ref": "Baseline",
|
|
431
|
+
"viewer.info.commit": "commit",
|
|
432
|
+
"viewer.info.skillPath": "skillPath",
|
|
433
|
+
"viewer.info.hash": "Content hash",
|
|
434
|
+
"viewer.info.installedAt": "Installed",
|
|
435
|
+
"viewer.info.updatedAt": "Updated",
|
|
436
|
+
"viewer.info.size": "Size",
|
|
437
|
+
"viewer.info.local": "Local",
|
|
438
|
+
"viewer.info.localChange": "Local changes",
|
|
439
|
+
"viewer.info.localChangeNone": "None (matches install)",
|
|
440
|
+
"viewer.info.localChangeYes": "Modified locally",
|
|
441
|
+
"viewer.info.links": "Linked",
|
|
442
|
+
"viewer.info.agents": "Agents",
|
|
443
|
+
"viewer.info.scope": "Scope",
|
|
444
|
+
"viewer.info.scopeGlobal": "Global",
|
|
445
|
+
"viewer.info.scopeProject": "Project only",
|
|
446
|
+
"viewer.info.mode": "State",
|
|
447
|
+
"viewer.info.status": "Updates",
|
|
448
|
+
"viewer.info.none": "None"
|
|
335
449
|
}
|
|
336
450
|
};
|
|
337
451
|
function buildTranslate(locale) {
|
|
@@ -617,9 +731,89 @@ function SearchIcon({ size = 13 }) {
|
|
|
617
731
|
h("path", { d: "M16 16l4.3 4.3" })
|
|
618
732
|
);
|
|
619
733
|
}
|
|
734
|
+
var STROKE_ICON = {
|
|
735
|
+
fill: "none",
|
|
736
|
+
stroke: "currentColor",
|
|
737
|
+
strokeWidth: 1.8,
|
|
738
|
+
strokeLinecap: "round",
|
|
739
|
+
strokeLinejoin: "round"
|
|
740
|
+
};
|
|
741
|
+
function EyeIcon({ size = 13 }) {
|
|
742
|
+
return h(
|
|
743
|
+
"svg",
|
|
744
|
+
{
|
|
745
|
+
width: size,
|
|
746
|
+
height: size,
|
|
747
|
+
viewBox: "0 0 24 24",
|
|
748
|
+
...STROKE_ICON,
|
|
749
|
+
"aria-hidden": true,
|
|
750
|
+
style: { display: "block" }
|
|
751
|
+
},
|
|
752
|
+
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" }),
|
|
753
|
+
h("circle", { cx: 12, cy: 12, r: 3 })
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
function CopyIcon({ 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("rect", { x: 8, y: 8, width: 14, height: 14, rx: 2 }),
|
|
768
|
+
h("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
function ChevronLeftIcon({ size = 14 }) {
|
|
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("path", { d: "m15 18-6-6 6-6" })
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
function FileIcon({ size = 12 }) {
|
|
786
|
+
return h(
|
|
787
|
+
"svg",
|
|
788
|
+
{
|
|
789
|
+
width: size,
|
|
790
|
+
height: size,
|
|
791
|
+
viewBox: "0 0 24 24",
|
|
792
|
+
...STROKE_ICON,
|
|
793
|
+
"aria-hidden": true,
|
|
794
|
+
style: { display: "block", flex: "none" }
|
|
795
|
+
},
|
|
796
|
+
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" }),
|
|
797
|
+
h("path", { d: "M13 3.5V9h5" })
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
function FolderIcon({ 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: "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" })
|
|
812
|
+
);
|
|
813
|
+
}
|
|
620
814
|
|
|
621
815
|
// client/section.js
|
|
622
|
-
var
|
|
816
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
623
817
|
|
|
624
818
|
// client/styles.js
|
|
625
819
|
var CSS = `
|
|
@@ -745,7 +939,7 @@ var CSS = `
|
|
|
745
939
|
|
|
746
940
|
.dshsc-list { display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
747
941
|
background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
748
|
-
.dshsc-row { display: grid; grid-template-columns: 300px minmax(0, 1fr)
|
|
942
|
+
.dshsc-row { display: grid; grid-template-columns: 300px minmax(0, 1fr) 268px; gap: 14px; align-items: center;
|
|
749
943
|
padding: 8px 12px 8px 14px; min-height: 54px; border-top: 1px solid var(--dsw-alias-border-l1);
|
|
750
944
|
transition: background .12s ease; }
|
|
751
945
|
.dshsc-list > .dshsc-row:first-child { border-top: 0; }
|
|
@@ -755,6 +949,11 @@ var CSS = `
|
|
|
755
949
|
.dshsc-namerow { display: flex; align-items: center; gap: 7px; min-width: 0; }
|
|
756
950
|
.dshsc-name { font-size: 13px; font-weight: 600; letter-spacing: -.005em; min-width: 0;
|
|
757
951
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
952
|
+
/* 技能名本身是查看入口:清掉 button 默认外观,保留 hover 下划线提示可点 */
|
|
953
|
+
.dshsc-namebtn { border: 0; background: none; padding: 0; font: inherit; font-size: 13px; font-weight: 600;
|
|
954
|
+
letter-spacing: -.005em; color: inherit; text-align: left; cursor: pointer; }
|
|
955
|
+
.dshsc-namebtn:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
956
|
+
.dshsc-namebtn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; border-radius: 4px; }
|
|
758
957
|
.dshsc-src { display: flex; align-items: center; gap: 5px; min-width: 0; font-size: 11.5px; line-height: 16px;
|
|
759
958
|
color: var(--dsw-alias-label-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
760
959
|
.dshsc-src .dshsc-code { color: var(--dsw-alias-label-secondary); font-size: 11px; }
|
|
@@ -945,8 +1144,220 @@ var CSS = `
|
|
|
945
1144
|
.dshsc-panel { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-1);
|
|
946
1145
|
padding: 4px; max-height: 300px; overflow: auto; display: flex; flex-direction: column; }
|
|
947
1146
|
.dshsc-panel .dshsc-check:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
1147
|
+
/* 必选项(dsh 全局):勾着但不可改,别让它看起来能点 */
|
|
1148
|
+
.dshsc-panel .dshsc-check.locked { cursor: default; }
|
|
1149
|
+
.dshsc-panel .dshsc-check.locked:hover { background: transparent; }
|
|
948
1150
|
|
|
949
1151
|
.dshsc-modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1152
|
+
/* ---------- 查看器:抽屉(B1)/ 整页(A1)共用 ---------- */
|
|
1153
|
+
.dshsc-drawer-wrap { position: fixed; inset: 0; z-index: 55; }
|
|
1154
|
+
.dshsc-drawer-scrim { position: absolute; inset: 0; background: rgba(4,6,9,.55); }
|
|
1155
|
+
.dshsc-drawer { position: absolute; top: 0; right: 0; bottom: 0; width: min(700px, 100vw); display: flex;
|
|
1156
|
+
flex-direction: column; min-height: 0; background: var(--dsw-alias-bg-layer-1);
|
|
1157
|
+
border-left: 1px solid var(--dsw-alias-border-l2); box-shadow: -30px 0 70px rgba(0,0,0,.38); outline: none; }
|
|
1158
|
+
.dshsc-reader-head { flex: none; padding: 15px 20px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1159
|
+
.dshsc-reader-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1160
|
+
.dshsc-reader-title h2 { flex: 1; min-width: 0; margin: 0; font-size: 18px; line-height: 1.3; font-weight: 650;
|
|
1161
|
+
letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1162
|
+
.dshsc-reader-close { flex: none; width: 28px; height: 28px; border: 0; border-radius: 7px; background: transparent;
|
|
1163
|
+
color: var(--dsw-alias-label-tertiary); font-size: 18px; line-height: 1; cursor: pointer; }
|
|
1164
|
+
.dshsc-reader-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1165
|
+
.dshsc-reader-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1166
|
+
.dshsc-reader-desc { margin: 7px 0 0; font-size: 13px; line-height: 1.6; color: var(--dsw-alias-label-secondary);
|
|
1167
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
1168
|
+
.dshsc-reader-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px;
|
|
1169
|
+
font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
|
|
1170
|
+
.dshsc-reader-tabs { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 20px;
|
|
1171
|
+
border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1172
|
+
.dshsc-rtabs { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--dsw-alias-border-l1);
|
|
1173
|
+
border-radius: 9px; background: var(--dsw-alias-bg-layer-1); }
|
|
1174
|
+
.dshsc-rtab { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px; border: 0;
|
|
1175
|
+
border-radius: 7px; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px;
|
|
1176
|
+
cursor: pointer; white-space: nowrap; }
|
|
1177
|
+
.dshsc-rtab:hover { color: var(--dsw-alias-label-primary); }
|
|
1178
|
+
.dshsc-rtab.active { background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary);
|
|
1179
|
+
box-shadow: 0 0 0 1px var(--dsw-alias-border-l1), 0 1px 2px rgba(0,0,0,.12); }
|
|
1180
|
+
.dshsc-rtab .n { min-width: 15px; height: 15px; padding: 0 4px; box-sizing: border-box; border-radius: 999px;
|
|
1181
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 20%, transparent); color: var(--dsw-alias-label-secondary);
|
|
1182
|
+
font-size: 10px; line-height: 15px; text-align: center; font-style: normal; font-variant-numeric: tabular-nums; }
|
|
1183
|
+
.dshsc-rtab:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1184
|
+
.dshsc-reader-seg { margin-left: auto; }
|
|
1185
|
+
.dshsc-reader-seg .dshsc-btn { height: 22px; font-size: 11.5px; padding: 0 8px; }
|
|
1186
|
+
|
|
1187
|
+
.dshsc-reader-body { flex: 1; min-height: 0; overflow: auto; padding: 16px 20px 22px; outline: none; }
|
|
1188
|
+
.dshsc-reader-doc { display: flex; flex-direction: column; gap: 10px; }
|
|
1189
|
+
.dshsc-reader-filebar { display: flex; align-items: center; gap: 10px; font-size: 11.5px;
|
|
1190
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1191
|
+
.dshsc-reader-filebar .dshsc-btn { margin-left: auto; height: 24px; font-size: 11.5px; }
|
|
1192
|
+
.dshsc-reader-raw { margin: 0; padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px;
|
|
1193
|
+
background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); overflow: auto;
|
|
1194
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.65; white-space: pre-wrap;
|
|
1195
|
+
word-break: break-word; }
|
|
1196
|
+
.dshsc-reader-actions { flex: none; display: flex; align-items: center; gap: 7px; padding: 9px 20px;
|
|
1197
|
+
border-top: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); flex-wrap: wrap; }
|
|
1198
|
+
/* 底栏按钮统一 28px 高;图标按钮做成 28×28 的方正位,避免图标在方框里偏 */
|
|
1199
|
+
.dshsc-reader-actions .dshsc-btn { box-sizing: border-box; height: 28px; font-size: 12px;
|
|
1200
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
|
|
1201
|
+
.dshsc-reader-actions .dshsc-btn.sq { width: 28px; padding: 0; }
|
|
1202
|
+
.dshsc-reader-actions .dshsc-btn.sq svg { width: 14px; height: 14px; }
|
|
1203
|
+
.dshsc-reader-note { margin-right: auto; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
|
|
1204
|
+
.dshsc-reader-skeleton { display: flex; flex-direction: column; gap: 8px; }
|
|
1205
|
+
.dshsc-reader-skeleton i { display: block; height: 12px; border-radius: 5px;
|
|
1206
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 16%, transparent); }
|
|
1207
|
+
.dshsc-reader-skeleton i:nth-child(3) { width: 82%; }
|
|
1208
|
+
.dshsc-reader-skeleton i:nth-child(4) { width: 64%; }
|
|
1209
|
+
.dshsc-reader-skeleton span { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
1210
|
+
.dshsc-reader-placeholder { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 22px;
|
|
1211
|
+
border: 1px dashed var(--dsw-alias-border-l2); border-radius: 12px;
|
|
1212
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 55%, transparent); font-size: 12.5px;
|
|
1213
|
+
color: var(--dsw-alias-label-secondary); line-height: 1.6; }
|
|
1214
|
+
.dshsc-reader-placeholder b { color: var(--dsw-alias-label-primary); font-size: 13px; }
|
|
1215
|
+
|
|
1216
|
+
/* 文件 tab:左树右预览;抽屉里上下排 */
|
|
1217
|
+
.dshsc-reader-files { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
|
1218
|
+
.dshsc-reader-tree { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: auto;
|
|
1219
|
+
max-height: 300px; background: var(--dsw-alias-bg-layer-1); }
|
|
1220
|
+
.dshsc-reader-preview { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden;
|
|
1221
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1222
|
+
.dshsc-reader-tree-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: 8px;
|
|
1223
|
+
padding: 7px 10px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1224
|
+
font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1225
|
+
.dshsc-reader-tree-head .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
1226
|
+
.dshsc-reader-tree-head .dshsc-btn { height: 22px; font-size: 11px; padding: 0 7px; }
|
|
1227
|
+
/* 预览头的动作组:整组贴右,组内紧挨(渲染 / 原文 + 在正文中打开) */
|
|
1228
|
+
.dshsc-reader-tools { margin-left: auto; flex: none; display: flex; align-items: center; gap: 6px; }
|
|
1229
|
+
.dshsc-reader-tools:empty { display: none; }
|
|
1230
|
+
.dshsc-reader-tools .dshsc-segment { height: 22px; }
|
|
1231
|
+
.dshsc-reader-tools .dshsc-seg { margin-left: 0; }
|
|
1232
|
+
/* 预览头在深色 bg-layer-2 上,默认边框太淡:抬高一级,让它一眼是按钮 */
|
|
1233
|
+
.dshsc-reader-tools > .dshsc-btn { border: 1px solid var(--dsw-alias-border-l2);
|
|
1234
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-weight: 500; }
|
|
1235
|
+
.dshsc-reader-tools > .dshsc-btn:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-3);
|
|
1236
|
+
border-color: var(--dsw-alias-border-l3); }
|
|
1237
|
+
.dshsc-reader-tools > .dshsc-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1238
|
+
.dshsc-reader-filebar .dshsc-btn { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); }
|
|
1239
|
+
.dshsc-reader-tdir { display: flex; align-items: center; gap: 6px; padding: 5px 10px; font-size: 11.5px;
|
|
1240
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1241
|
+
.dshsc-reader-tdir .n { margin-left: auto; font-variant-numeric: tabular-nums; }
|
|
1242
|
+
.dshsc-reader-titem { display: flex; align-items: center; gap: 6px; width: 100%; box-sizing: border-box;
|
|
1243
|
+
padding: 5px 10px; border: 0; background: transparent; color: var(--dsw-alias-label-secondary);
|
|
1244
|
+
font: inherit; font-size: 12px; text-align: left; cursor: pointer; }
|
|
1245
|
+
.dshsc-reader-titem:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1246
|
+
.dshsc-reader-titem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1247
|
+
color: var(--dsw-alias-brand-primary); }
|
|
1248
|
+
.dshsc-reader-titem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
|
|
1249
|
+
.dshsc-reader-titem .nname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
|
|
1250
|
+
.dshsc-reader-titem .nsize { flex: none; font-size: 10.5px; color: var(--dsw-alias-label-tertiary);
|
|
1251
|
+
font-variant-numeric: tabular-nums; }
|
|
1252
|
+
.dshsc-reader-preview .dshsc-reader-raw { border: 0; border-radius: 0; background: transparent;
|
|
1253
|
+
max-height: 320px; }
|
|
1254
|
+
/* 文件 tab 里能渲染的文件(markdown)直接出正文,不让人先读源码 */
|
|
1255
|
+
.dshsc-reader-preview-doc { padding: 12px 14px; max-height: 320px; overflow: auto; }
|
|
1256
|
+
.dshsc-reader-preview-doc .dshsc-md { font-size: 12.5px; }
|
|
1257
|
+
.dshsc-reader-preview-doc .dshsc-md h1.dshsc-md-h { font-size: 18px; }
|
|
1258
|
+
.dshsc-reader-preview-doc .dshsc-md h2.dshsc-md-h { font-size: 15px; }
|
|
1259
|
+
|
|
1260
|
+
/* 信息 tab:四段 dl */
|
|
1261
|
+
.dshsc-reader-info { display: flex; flex-direction: column; gap: 14px; }
|
|
1262
|
+
.dshsc-info-sec { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 11px 13px;
|
|
1263
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1264
|
+
.dshsc-info-sec h3 { margin: 0 0 8px; font-size: 11px; font-weight: 600; letter-spacing: .04em;
|
|
1265
|
+
color: var(--dsw-alias-label-tertiary); text-transform: uppercase; }
|
|
1266
|
+
.dshsc-info-sec dl { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 5px 10px; margin: 0;
|
|
1267
|
+
font-size: 12px; line-height: 1.55; }
|
|
1268
|
+
.dshsc-info-sec dt { color: var(--dsw-alias-label-tertiary); }
|
|
1269
|
+
.dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
1270
|
+
.dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1271
|
+
|
|
1272
|
+
/* 正文排版:只用 token,不写死深浅色 */
|
|
1273
|
+
.dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
|
|
1274
|
+
.dshsc-md-h { margin: 20px 0 8px; line-height: 1.35; font-weight: 650; letter-spacing: -.008em; }
|
|
1275
|
+
.dshsc-md-h:first-child { margin-top: 0; }
|
|
1276
|
+
.dshsc-md h1.dshsc-md-h { font-size: 21px; }
|
|
1277
|
+
.dshsc-md h2.dshsc-md-h { font-size: 16.5px; padding-top: 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1278
|
+
.dshsc-md h3.dshsc-md-h { font-size: 14px; }
|
|
1279
|
+
.dshsc-md h4.dshsc-md-h, .dshsc-md h5.dshsc-md-h, .dshsc-md h6.dshsc-md-h { font-size: 13px;
|
|
1280
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1281
|
+
.dshsc-md-p { margin: 0 0 10px; }
|
|
1282
|
+
.dshsc-md-inline { padding: 1px 5px; border-radius: 5px; background: var(--dsw-alias-bg-layer-3);
|
|
1283
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
|
|
1284
|
+
.dshsc-md-code { margin: 0 0 12px; padding: 11px 13px; border-radius: 9px; overflow: auto;
|
|
1285
|
+
background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1);
|
|
1286
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.65; white-space: pre; }
|
|
1287
|
+
.dshsc-md-list { margin: 0 0 10px; padding-left: 20px; }
|
|
1288
|
+
.dshsc-md-list .dshsc-md-list { margin: 4px 0 0; }
|
|
1289
|
+
.dshsc-md-list li { margin-bottom: 4px; }
|
|
1290
|
+
.dshsc-md-task { list-style: none; margin-left: -18px; }
|
|
1291
|
+
.dshsc-md-check { display: inline-block; width: 12px; height: 12px; margin-right: 6px; border-radius: 3px;
|
|
1292
|
+
border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); vertical-align: -1px; }
|
|
1293
|
+
.dshsc-md-check.on { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
1294
|
+
.dshsc-md-quote { margin: 0 0 12px; padding: 6px 0 6px 12px; border-left: 2px solid var(--dsw-alias-border-l3);
|
|
1295
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1296
|
+
.dshsc-md-quote .dshsc-md-p:last-child { margin-bottom: 0; }
|
|
1297
|
+
.dshsc-md-hr { height: 0; margin: 16px 0; border: 0; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1298
|
+
.dshsc-md-link { color: var(--dsw-alias-brand-primary); text-decoration: none; }
|
|
1299
|
+
.dshsc-md-link:hover { text-decoration: underline; }
|
|
1300
|
+
.dshsc-md-nolink { color: inherit; border-bottom: 1px dotted var(--dsw-alias-border-l3); }
|
|
1301
|
+
.dshsc-md-image { display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px; border-radius: 6px;
|
|
1302
|
+
border: 1px dashed var(--dsw-alias-border-l2); color: var(--dsw-alias-label-tertiary); font-size: 11.5px; }
|
|
1303
|
+
.dshsc-md-image-tag { font-size: 9.5px; letter-spacing: .06em; }
|
|
1304
|
+
.dshsc-md-table { margin: 0 0 12px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px; }
|
|
1305
|
+
.dshsc-md-table table { border-collapse: collapse; width: 100%; font-size: 12.5px; }
|
|
1306
|
+
.dshsc-md-table th, .dshsc-md-table td { padding: 7px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1307
|
+
text-align: left; }
|
|
1308
|
+
.dshsc-md-table th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }
|
|
1309
|
+
.dshsc-md-table tr:last-child td { border-bottom: 0; }
|
|
1310
|
+
|
|
1311
|
+
/* 整页(A1):返回条 + hero + 目录 + 720px 正文 */
|
|
1312
|
+
.dshsc-reader-page { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
|
|
1313
|
+
.dshsc-reader-topbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1314
|
+
.dshsc-reader-topbar .dshsc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
|
1315
|
+
box-sizing: border-box; height: 28px; padding: 0 10px; font-size: 12px; line-height: 1; white-space: nowrap; }
|
|
1316
|
+
.dshsc-reader-topbar .dshsc-btn.ghost { padding: 0 9px; }
|
|
1317
|
+
.dshsc-reader-topbar .dshsc-btn.sq { width: 28px; padding: 0; }
|
|
1318
|
+
.dshsc-reader-topbar .dshsc-btn.sq svg { width: 14px; height: 14px; }
|
|
1319
|
+
.dshsc-reader-crumb { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
|
|
1320
|
+
color: var(--dsw-alias-label-tertiary); }
|
|
1321
|
+
.dshsc-reader-crumb .sep { color: var(--dsw-alias-border-l3); }
|
|
1322
|
+
.dshsc-reader-topbar .spacer { flex: 1; }
|
|
1323
|
+
/* 顶栏复用抽屉底栏那份动作组:去掉边框 / 底色 / 内边距,只留一排等高的按钮 */
|
|
1324
|
+
.dshsc-reader-topbar .dshsc-reader-actions { border: 0; background: transparent; padding: 0; gap: 6px;
|
|
1325
|
+
flex-wrap: nowrap; }
|
|
1326
|
+
.dshsc-reader-topbar .dshsc-reader-note { display: none; }
|
|
1327
|
+
.dshsc-reader-topbar .dshsc-reader-actions .dshsc-btn:not(.ghost) { padding: 0 11px; }
|
|
1328
|
+
.dshsc-reader-hero { border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
1329
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1330
|
+
.dshsc-reader-hero .dshsc-reader-head { border-bottom: 0; }
|
|
1331
|
+
.dshsc-reader-hero .dshsc-reader-desc { -webkit-line-clamp: 3; max-width: 78ch; }
|
|
1332
|
+
.dshsc-reader-layout { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 18px; align-items: start; }
|
|
1333
|
+
.dshsc-reader-toc { position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px;
|
|
1334
|
+
max-height: 70vh; overflow: auto; padding-right: 4px; }
|
|
1335
|
+
.dshsc-reader-toc-title { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
|
|
1336
|
+
color: var(--dsw-alias-label-tertiary); margin-bottom: 5px; }
|
|
1337
|
+
.dshsc-reader-tocitem { border: 0; background: transparent; padding: 3px 7px; border-radius: 6px; text-align: left;
|
|
1338
|
+
font: inherit; font-size: 11.5px; line-height: 1.45; color: var(--dsw-alias-label-secondary); cursor: pointer;
|
|
1339
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1340
|
+
.dshsc-reader-tocitem.l2 { padding-left: 15px; }
|
|
1341
|
+
.dshsc-reader-tocitem.l3, .dshsc-reader-tocitem.l4 { padding-left: 23px; font-size: 11px; }
|
|
1342
|
+
.dshsc-reader-tocitem:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1343
|
+
.dshsc-reader-tocitem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1344
|
+
color: var(--dsw-alias-brand-primary); }
|
|
1345
|
+
.dshsc-reader-tocitem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1346
|
+
.dshsc-reader-col { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--dsw-alias-border-l1);
|
|
1347
|
+
border-radius: 12px; background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1348
|
+
.dshsc-reader-col .dshsc-reader-tabs { border-top: 0; }
|
|
1349
|
+
.dshsc-reader-page .dshsc-reader-body { max-height: none; }
|
|
1350
|
+
.dshsc-reader-page .dshsc-md { max-width: 720px; }
|
|
1351
|
+
.dshsc-reader-page .dshsc-reader-actions { border-radius: 0 0 12px 12px; }
|
|
1352
|
+
|
|
1353
|
+
@container dshsc (max-width: 900px) {
|
|
1354
|
+
/* 行内「查看」先让位:技能名本身仍是入口 */
|
|
1355
|
+
.dshsc-row .dshsc-view { display: none; }
|
|
1356
|
+
.dshsc-reader-layout { grid-template-columns: minmax(0, 1fr); }
|
|
1357
|
+
.dshsc-reader-toc { display: none; }
|
|
1358
|
+
.dshsc-reader-page .dshsc-reader-desc { -webkit-line-clamp: 2; }
|
|
1359
|
+
}
|
|
1360
|
+
|
|
950
1361
|
.dshsc-toast { position: fixed; right: 18px; bottom: 18px; z-index: 70; font-size: 12px; padding: 10px 13px; border-radius: 9px;
|
|
951
1362
|
background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l2);
|
|
952
1363
|
box-shadow: 0 8px 24px rgba(0,0,0,.22); max-width: 420px; }
|
|
@@ -1257,6 +1668,73 @@ function useAgentTip() {
|
|
|
1257
1668
|
}
|
|
1258
1669
|
return { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip };
|
|
1259
1670
|
}
|
|
1671
|
+
function useSkillReader(row) {
|
|
1672
|
+
const name = row?.name || null;
|
|
1673
|
+
const stamp = row ? [row.commit || "", row.currentHash || "", row.updatedAt || "", row.status || ""].join("|") : "";
|
|
1674
|
+
const [state, setState] = import_react2.default.useState({ loading: true, body: null, tree: null, bodyError: null, treeError: null });
|
|
1675
|
+
const [file, setFile] = import_react2.default.useState({ relPath: null, content: "", size: 0, truncated: false, loading: false, error: null });
|
|
1676
|
+
const [tick, setTick] = import_react2.default.useState(0);
|
|
1677
|
+
const seq = import_react2.default.useRef(0);
|
|
1678
|
+
const fileSeq = import_react2.default.useRef(0);
|
|
1679
|
+
import_react2.default.useEffect(() => {
|
|
1680
|
+
if (!name) {
|
|
1681
|
+
setState({ loading: false, body: null, tree: null, bodyError: null, treeError: null });
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const ticket = seq.current + 1;
|
|
1685
|
+
seq.current = ticket;
|
|
1686
|
+
setState({ loading: true, body: null, tree: null, bodyError: null, treeError: null });
|
|
1687
|
+
const encoded = encodeURIComponent(name);
|
|
1688
|
+
void (async () => {
|
|
1689
|
+
const [bodyResult, treeResult] = await Promise.allSettled([
|
|
1690
|
+
apiCall("GET", `/skills/${encoded}`),
|
|
1691
|
+
apiCall("GET", `/skills/${encoded}/files`)
|
|
1692
|
+
]);
|
|
1693
|
+
if (ticket !== seq.current) return;
|
|
1694
|
+
setState({
|
|
1695
|
+
loading: false,
|
|
1696
|
+
body: bodyResult.status === "fulfilled" ? bodyResult.value : null,
|
|
1697
|
+
tree: treeResult.status === "fulfilled" ? treeResult.value : null,
|
|
1698
|
+
// 没有 SKILL.md 时正文失败但树还在:文件 tab 仍可用
|
|
1699
|
+
bodyError: bodyResult.status === "rejected" ? { code: bodyResult.reason?.code || null, message: bodyResult.reason?.message || "读取失败" } : null,
|
|
1700
|
+
treeError: treeResult.status === "rejected" ? { code: treeResult.reason?.code || null, message: treeResult.reason?.message || "读取失败" } : null
|
|
1701
|
+
});
|
|
1702
|
+
})();
|
|
1703
|
+
}, [name, stamp, tick]);
|
|
1704
|
+
const loadFile = import_react2.default.useCallback(async (relPath) => {
|
|
1705
|
+
if (!name || !relPath) return;
|
|
1706
|
+
const ticket = fileSeq.current + 1;
|
|
1707
|
+
fileSeq.current = ticket;
|
|
1708
|
+
setFile({ relPath, content: "", size: 0, truncated: false, loading: true, error: null });
|
|
1709
|
+
try {
|
|
1710
|
+
const result = await apiCall("GET", `/skills/${encodeURIComponent(name)}?path=${encodeURIComponent(relPath)}`);
|
|
1711
|
+
if (ticket !== fileSeq.current) return;
|
|
1712
|
+
setFile({
|
|
1713
|
+
relPath: result.relPath || relPath,
|
|
1714
|
+
content: result.content || "",
|
|
1715
|
+
size: result.size || 0,
|
|
1716
|
+
truncated: Boolean(result.truncated),
|
|
1717
|
+
loading: false,
|
|
1718
|
+
error: null,
|
|
1719
|
+
mtime: result.mtime || null
|
|
1720
|
+
});
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
if (ticket !== fileSeq.current) return;
|
|
1723
|
+
setFile({
|
|
1724
|
+
relPath,
|
|
1725
|
+
content: "",
|
|
1726
|
+
size: 0,
|
|
1727
|
+
truncated: false,
|
|
1728
|
+
loading: false,
|
|
1729
|
+
error: { code: error?.code || null, message: error?.message || "读取失败" }
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
}, [name]);
|
|
1733
|
+
const retry = import_react2.default.useCallback(() => {
|
|
1734
|
+
setTick((value) => value + 1);
|
|
1735
|
+
}, []);
|
|
1736
|
+
return { state, file, loadFile, retry };
|
|
1737
|
+
}
|
|
1260
1738
|
|
|
1261
1739
|
// client/views/header.js
|
|
1262
1740
|
function Header({
|
|
@@ -1469,6 +1947,9 @@ function InstalledTab({
|
|
|
1469
1947
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
1470
1948
|
function statusTag(row) {
|
|
1471
1949
|
if (row.status === "unknown") return null;
|
|
1950
|
+
if (row.status === "no-upstream") {
|
|
1951
|
+
return row.source ? h("span", { className: "dshsc-tag", title: t("status.noUpstreamHint") }, t("status.noUpstream")) : null;
|
|
1952
|
+
}
|
|
1472
1953
|
const map = {
|
|
1473
1954
|
current: ["status.current", "ok", true],
|
|
1474
1955
|
"update-available": ["status.update", "warn"],
|
|
@@ -1547,7 +2028,13 @@ function InstalledTab({
|
|
|
1547
2028
|
h(
|
|
1548
2029
|
"div",
|
|
1549
2030
|
{ className: "dshsc-namerow" },
|
|
1550
|
-
h("
|
|
2031
|
+
h("button", {
|
|
2032
|
+
type: "button",
|
|
2033
|
+
className: "dshsc-name dshsc-namebtn",
|
|
2034
|
+
title: row.name,
|
|
2035
|
+
"aria-haspopup": "dialog",
|
|
2036
|
+
onClick: () => actions.onView(row)
|
|
2037
|
+
}, row.name),
|
|
1551
2038
|
statusTag(row),
|
|
1552
2039
|
off ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null
|
|
1553
2040
|
),
|
|
@@ -1569,6 +2056,13 @@ function InstalledTab({
|
|
|
1569
2056
|
onClick: () => void actions.onToggleMode(row),
|
|
1570
2057
|
title: tracked ? row.mode === "on" ? t("mode.off") : t("mode.on") : t("adopt.switchHint")
|
|
1571
2058
|
}, h("span", { className: "dshsc-knob" })),
|
|
2059
|
+
h("button", {
|
|
2060
|
+
className: "dshsc-btn ghost dshsc-view",
|
|
2061
|
+
disabled: busy !== null,
|
|
2062
|
+
"aria-haspopup": "dialog",
|
|
2063
|
+
title: t("action.view"),
|
|
2064
|
+
onClick: () => actions.onView(row)
|
|
2065
|
+
}, h(EyeIcon, { size: 12 }), t("action.view")),
|
|
1572
2066
|
row.status === "unmanaged" ? h("button", {
|
|
1573
2067
|
className: "dshsc-btn ghost",
|
|
1574
2068
|
disabled: !writable || busy !== null,
|
|
@@ -1976,7 +2470,7 @@ function LinksModal({ t, row, agents, workspaces, pickDirectory, onCancel, onSav
|
|
|
1976
2470
|
h("p", { className: "dshsc-sub" }, t("links.canonical").replace("{path}", `~/.agents/skills/${row.name}`)),
|
|
1977
2471
|
...candidates.map((agent) => h(
|
|
1978
2472
|
"label",
|
|
1979
|
-
{ className:
|
|
2473
|
+
{ className: `dshsc-check${agent.target === "dsh-global" ? " locked" : ""}`, key: agent.target },
|
|
1980
2474
|
h("input", {
|
|
1981
2475
|
type: "checkbox",
|
|
1982
2476
|
checked: agent.target === "dsh-global" ? true : targets.has(agent.target),
|
|
@@ -2034,7 +2528,7 @@ function LinksModal({ t, row, agents, workspaces, pickDirectory, onCancel, onSav
|
|
|
2034
2528
|
h("p", { className: "dshsc-sub" }, t("links.hint"))
|
|
2035
2529
|
);
|
|
2036
2530
|
}
|
|
2037
|
-
function AutoLinkModal({ t, settings, agents, onCancel, onSave }) {
|
|
2531
|
+
function AutoLinkModal({ t, settings, agents, dsh, onCancel, onSave }) {
|
|
2038
2532
|
const [targets, setTargets] = import_react3.default.useState(() => new Set(
|
|
2039
2533
|
Array.isArray(settings.autoLinkTargets) ? settings.autoLinkTargets : agents.map((agent) => agent.target)
|
|
2040
2534
|
));
|
|
@@ -2055,19 +2549,39 @@ function AutoLinkModal({ t, settings, agents, onCancel, onSave }) {
|
|
|
2055
2549
|
h("button", { key: "s", className: "dshsc-btn primary", onClick: () => onSave([...targets]) }, t("action.save"))
|
|
2056
2550
|
]
|
|
2057
2551
|
},
|
|
2058
|
-
h("p", { className: "dshsc-sub" }, t("links.dshGlobal") + " · " + t("links.required")),
|
|
2059
2552
|
h("div", { className: "dshsc-group" }, t("autoLink.detected")),
|
|
2060
|
-
|
|
2061
|
-
"
|
|
2062
|
-
{ className: "dshsc-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2553
|
+
h(
|
|
2554
|
+
"div",
|
|
2555
|
+
{ className: "dshsc-panel" },
|
|
2556
|
+
// dsh(全局)始终接入:列表里也勾上,只是不能改,勾选数与页头的计数器才对得上
|
|
2557
|
+
h(
|
|
2558
|
+
"label",
|
|
2559
|
+
{ className: "dshsc-check locked", key: "dsh-global" },
|
|
2560
|
+
h("input", {
|
|
2561
|
+
type: "checkbox",
|
|
2562
|
+
checked: true,
|
|
2563
|
+
disabled: true,
|
|
2564
|
+
readOnly: true,
|
|
2565
|
+
onChange: () => {
|
|
2566
|
+
}
|
|
2567
|
+
}),
|
|
2568
|
+
h("span", null, dsh?.label || t("links.dshGlobal")),
|
|
2569
|
+
h("small", null, t("links.required")),
|
|
2570
|
+
dsh?.dir ? h("small", { title: dsh.dir }, dsh.dir) : null
|
|
2571
|
+
),
|
|
2572
|
+
...agents.map((agent) => h(
|
|
2573
|
+
"label",
|
|
2574
|
+
{ className: "dshsc-check", key: agent.target },
|
|
2575
|
+
h("input", {
|
|
2576
|
+
type: "checkbox",
|
|
2577
|
+
checked: targets.has(agent.target),
|
|
2578
|
+
onChange: () => toggle(agent.target)
|
|
2579
|
+
}),
|
|
2580
|
+
h("span", null, agent.label),
|
|
2581
|
+
h("small", null, agent.dir)
|
|
2582
|
+
))
|
|
2583
|
+
),
|
|
2584
|
+
agents.length === 0 ? h("p", { className: "dshsc-sub" }, t("autoLink.none")) : null,
|
|
2071
2585
|
h("p", { className: "dshsc-sub" }, t("autoLink.hint"))
|
|
2072
2586
|
);
|
|
2073
2587
|
}
|
|
@@ -2211,7 +2725,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
2211
2725
|
h("p", { className: "dshsc-sub" }, t("form.hint"))
|
|
2212
2726
|
);
|
|
2213
2727
|
}
|
|
2214
|
-
function Modals({ t, modal, data, autoLinkAgents, pickDirectory, actions, onClose, onSaveAutoLink }) {
|
|
2728
|
+
function Modals({ t, modal, data, autoLinkAgents, autoLinkDsh, pickDirectory, actions, onClose, onSaveAutoLink }) {
|
|
2215
2729
|
if (modal.type === "add-skill") {
|
|
2216
2730
|
return h(AddSkillModal, {
|
|
2217
2731
|
t,
|
|
@@ -2253,6 +2767,7 @@ function Modals({ t, modal, data, autoLinkAgents, pickDirectory, actions, onClos
|
|
|
2253
2767
|
t,
|
|
2254
2768
|
settings: data?.settings || {},
|
|
2255
2769
|
agents: autoLinkAgents,
|
|
2770
|
+
dsh: autoLinkDsh,
|
|
2256
2771
|
onCancel: onClose,
|
|
2257
2772
|
onSave: (targets) => void onSaveAutoLink(targets)
|
|
2258
2773
|
});
|
|
@@ -2291,19 +2806,1017 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
|
2291
2806
|
return null;
|
|
2292
2807
|
}
|
|
2293
2808
|
|
|
2809
|
+
// client/views/skill-reader.js
|
|
2810
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2811
|
+
|
|
2812
|
+
// client/markdown.js
|
|
2813
|
+
function classifyHref(href) {
|
|
2814
|
+
const value = String(href ?? "").trim();
|
|
2815
|
+
if (!value) return { kind: "unsafe", href: value };
|
|
2816
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) return { kind: "unsafe", href: value };
|
|
2817
|
+
if (value.startsWith("#")) return { kind: "anchor", href: value.slice(1) };
|
|
2818
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value);
|
|
2819
|
+
if (scheme) {
|
|
2820
|
+
const name = scheme[1].toLowerCase();
|
|
2821
|
+
if (name === "http" || name === "https" || name === "mailto") return { kind: "external", href: value };
|
|
2822
|
+
return { kind: "unsafe", href: value };
|
|
2823
|
+
}
|
|
2824
|
+
if (value.startsWith("//")) return { kind: "unsafe", href: value };
|
|
2825
|
+
return { kind: "relative", href: value };
|
|
2826
|
+
}
|
|
2827
|
+
function slugify(text) {
|
|
2828
|
+
return String(text ?? "").trim().toLowerCase().replace(/[`*_~]/g, "").replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2829
|
+
}
|
|
2830
|
+
function stripFrontmatter(text) {
|
|
2831
|
+
const source = String(text ?? "");
|
|
2832
|
+
const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(source);
|
|
2833
|
+
if (!match) return { frontmatter: null, body: source };
|
|
2834
|
+
return { frontmatter: match[1], body: source.slice(match[0].length) };
|
|
2835
|
+
}
|
|
2836
|
+
var ESCAPABLE = /* @__PURE__ */ new Set(["\\", "`", "*", "_", "[", "]", "(", ")", "#", "+", "-", ".", "!", "|", ">", "~"]);
|
|
2837
|
+
function matchBracketLink(text, start) {
|
|
2838
|
+
const close = text.indexOf("]", start + 1);
|
|
2839
|
+
if (close < 0) return null;
|
|
2840
|
+
if (text[close + 1] !== "(") return null;
|
|
2841
|
+
let depth = 0;
|
|
2842
|
+
let index = close + 1;
|
|
2843
|
+
for (; index < text.length; index += 1) {
|
|
2844
|
+
if (text[index] === "(") depth += 1;
|
|
2845
|
+
else if (text[index] === ")") {
|
|
2846
|
+
depth -= 1;
|
|
2847
|
+
if (depth === 0) break;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
if (index >= text.length) return null;
|
|
2851
|
+
const label = text.slice(start + 1, close);
|
|
2852
|
+
const rawTarget = text.slice(close + 2, index).trim();
|
|
2853
|
+
const href = rawTarget.split(/\s+/)[0] || "";
|
|
2854
|
+
return { label, href, end: index + 1 };
|
|
2855
|
+
}
|
|
2856
|
+
function parseInline(text) {
|
|
2857
|
+
const source = String(text ?? "");
|
|
2858
|
+
const nodes = [];
|
|
2859
|
+
let buffer = "";
|
|
2860
|
+
const flush = () => {
|
|
2861
|
+
if (buffer) {
|
|
2862
|
+
nodes.push({ type: "text", value: buffer });
|
|
2863
|
+
buffer = "";
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
let index = 0;
|
|
2867
|
+
while (index < source.length) {
|
|
2868
|
+
const char = source[index];
|
|
2869
|
+
if (char === "\\" && ESCAPABLE.has(source[index + 1])) {
|
|
2870
|
+
buffer += source[index + 1];
|
|
2871
|
+
index += 2;
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
if (char === "`") {
|
|
2875
|
+
const end = source.indexOf("`", index + 1);
|
|
2876
|
+
if (end > index + 1) {
|
|
2877
|
+
flush();
|
|
2878
|
+
nodes.push({ type: "code", value: source.slice(index + 1, end) });
|
|
2879
|
+
index = end + 1;
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (char === "!" && source[index + 1] === "[") {
|
|
2884
|
+
const match = matchBracketLink(source, index + 1);
|
|
2885
|
+
if (match) {
|
|
2886
|
+
flush();
|
|
2887
|
+
const target = classifyHref(match.href);
|
|
2888
|
+
nodes.push({ type: "image", alt: match.label, src: match.href, kind: target.kind });
|
|
2889
|
+
index = match.end;
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
if (char === "[") {
|
|
2894
|
+
const match = matchBracketLink(source, index);
|
|
2895
|
+
if (match) {
|
|
2896
|
+
flush();
|
|
2897
|
+
const target = classifyHref(match.href);
|
|
2898
|
+
nodes.push({
|
|
2899
|
+
type: "link",
|
|
2900
|
+
href: target.href,
|
|
2901
|
+
kind: target.kind,
|
|
2902
|
+
children: parseInline(match.label)
|
|
2903
|
+
});
|
|
2904
|
+
index = match.end;
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
if (char === "*" || char === "_") {
|
|
2909
|
+
const doubled = source[index + 1] === char;
|
|
2910
|
+
const marker = doubled ? char + char : char;
|
|
2911
|
+
const end = source.indexOf(marker, index + marker.length);
|
|
2912
|
+
if (end > index + marker.length) {
|
|
2913
|
+
flush();
|
|
2914
|
+
nodes.push({
|
|
2915
|
+
type: doubled ? "strong" : "em",
|
|
2916
|
+
children: parseInline(source.slice(index + marker.length, end))
|
|
2917
|
+
});
|
|
2918
|
+
index = end + marker.length;
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
buffer += char;
|
|
2923
|
+
index += 1;
|
|
2924
|
+
}
|
|
2925
|
+
flush();
|
|
2926
|
+
return nodes;
|
|
2927
|
+
}
|
|
2928
|
+
function inlineToText(nodes) {
|
|
2929
|
+
if (!Array.isArray(nodes)) return String(nodes ?? "");
|
|
2930
|
+
return nodes.map((node) => {
|
|
2931
|
+
if (node.type === "text" || node.type === "code") return node.value;
|
|
2932
|
+
if (node.type === "image") return node.alt || "";
|
|
2933
|
+
return inlineToText(node.children);
|
|
2934
|
+
}).join("");
|
|
2935
|
+
}
|
|
2936
|
+
var FENCE = /^(\s*)(`{3,}|~{3,})\s*([^`]*)$/;
|
|
2937
|
+
var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
2938
|
+
var HR = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
|
|
2939
|
+
var LIST_ITEM = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/;
|
|
2940
|
+
var QUOTE = /^\s{0,3}>\s?(.*)$/;
|
|
2941
|
+
var TABLE_SEPARATOR = /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/;
|
|
2942
|
+
function splitRow(line) {
|
|
2943
|
+
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
2944
|
+
return trimmed.split("|").map((cell) => cell.trim());
|
|
2945
|
+
}
|
|
2946
|
+
function alignOf(cell) {
|
|
2947
|
+
const value = cell.trim();
|
|
2948
|
+
if (/^:-+:$/.test(value)) return "center";
|
|
2949
|
+
if (/^:-+$/.test(value)) return "left";
|
|
2950
|
+
if (/^-+:$/.test(value)) return "right";
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
function parseMarkdown(text) {
|
|
2954
|
+
const lines = String(text ?? "").replace(/\r\n?/g, "\n").split("\n");
|
|
2955
|
+
const blocks = [];
|
|
2956
|
+
const headings = [];
|
|
2957
|
+
const usedIds = /* @__PURE__ */ new Map();
|
|
2958
|
+
const headingId = (plain) => {
|
|
2959
|
+
const base = slugify(plain) || "section";
|
|
2960
|
+
const seen = usedIds.get(base) || 0;
|
|
2961
|
+
usedIds.set(base, seen + 1);
|
|
2962
|
+
return seen === 0 ? base : `${base}-${seen + 1}`;
|
|
2963
|
+
};
|
|
2964
|
+
const isBlockStart = (line) => FENCE.test(line) || HEADING.test(line) || HR.test(line) || LIST_ITEM.test(line) || QUOTE.test(line) || line.includes("|");
|
|
2965
|
+
let index = 0;
|
|
2966
|
+
while (index < lines.length) {
|
|
2967
|
+
const line = lines[index];
|
|
2968
|
+
if (!line.trim()) {
|
|
2969
|
+
index += 1;
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
const fence = FENCE.exec(line);
|
|
2973
|
+
if (fence) {
|
|
2974
|
+
const marker = fence[2];
|
|
2975
|
+
const info = fence[3].trim();
|
|
2976
|
+
const body = [];
|
|
2977
|
+
index += 1;
|
|
2978
|
+
while (index < lines.length) {
|
|
2979
|
+
const current = lines[index];
|
|
2980
|
+
if (current.trim().startsWith(marker[0].repeat(marker.length)) && current.trim().replace(/[`~]/g, "") === "") {
|
|
2981
|
+
index += 1;
|
|
2982
|
+
break;
|
|
2983
|
+
}
|
|
2984
|
+
body.push(current);
|
|
2985
|
+
index += 1;
|
|
2986
|
+
}
|
|
2987
|
+
blocks.push({ type: "code", lang: info.split(/\s+/)[0] || "", text: body.join("\n") });
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
const heading = HEADING.exec(line);
|
|
2991
|
+
if (heading) {
|
|
2992
|
+
const level = heading[1].length;
|
|
2993
|
+
const inlines = parseInline(heading[2]);
|
|
2994
|
+
const plain = inlineToText(inlines).trim();
|
|
2995
|
+
const id = headingId(plain);
|
|
2996
|
+
blocks.push({ type: "heading", level, id, inlines, text: plain });
|
|
2997
|
+
headings.push({ id, level, text: plain });
|
|
2998
|
+
index += 1;
|
|
2999
|
+
continue;
|
|
3000
|
+
}
|
|
3001
|
+
if (HR.test(line)) {
|
|
3002
|
+
blocks.push({ type: "hr" });
|
|
3003
|
+
index += 1;
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
if (QUOTE.test(line)) {
|
|
3007
|
+
const quoted = [];
|
|
3008
|
+
while (index < lines.length && QUOTE.test(lines[index])) {
|
|
3009
|
+
quoted.push(QUOTE.exec(lines[index])[1]);
|
|
3010
|
+
index += 1;
|
|
3011
|
+
}
|
|
3012
|
+
blocks.push({ type: "quote", blocks: parseMarkdown(quoted.join("\n")).blocks });
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
if (line.includes("|") && index + 1 < lines.length && TABLE_SEPARATOR.test(lines[index + 1])) {
|
|
3016
|
+
const header = splitRow(line).map((cell) => parseInline(cell));
|
|
3017
|
+
const align = splitRow(lines[index + 1]).map(alignOf);
|
|
3018
|
+
index += 2;
|
|
3019
|
+
const rows = [];
|
|
3020
|
+
while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
|
|
3021
|
+
rows.push(splitRow(lines[index]).map((cell) => parseInline(cell)));
|
|
3022
|
+
index += 1;
|
|
3023
|
+
}
|
|
3024
|
+
blocks.push({ type: "table", align, header, rows });
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (LIST_ITEM.test(line)) {
|
|
3028
|
+
const first = LIST_ITEM.exec(line);
|
|
3029
|
+
const ordered = /\d/.test(first[2]);
|
|
3030
|
+
const items = [];
|
|
3031
|
+
while (index < lines.length) {
|
|
3032
|
+
const match = LIST_ITEM.exec(lines[index]);
|
|
3033
|
+
if (!match) break;
|
|
3034
|
+
const indent = match[1].replace(/\t/g, " ").length;
|
|
3035
|
+
if (indent > 3) break;
|
|
3036
|
+
const content = match[3];
|
|
3037
|
+
let checked = null;
|
|
3038
|
+
const task = /^\[([ xX])\]\s*(.*)$/.exec(content);
|
|
3039
|
+
const text2 = task ? task[2] : content;
|
|
3040
|
+
if (task) checked = task[1].toLowerCase() === "x";
|
|
3041
|
+
index += 1;
|
|
3042
|
+
const nested = [];
|
|
3043
|
+
while (index < lines.length) {
|
|
3044
|
+
const child = LIST_ITEM.exec(lines[index]);
|
|
3045
|
+
if (!child) break;
|
|
3046
|
+
const childIndent = child[1].replace(/\t/g, " ").length;
|
|
3047
|
+
if (childIndent === 0) break;
|
|
3048
|
+
nested.push(lines[index]);
|
|
3049
|
+
index += 1;
|
|
3050
|
+
}
|
|
3051
|
+
while (index < lines.length && lines[index].trim() && !LIST_ITEM.test(lines[index]) && !isBlockStart(lines[index])) {
|
|
3052
|
+
index += 1;
|
|
3053
|
+
}
|
|
3054
|
+
const item = { inlines: parseInline(text2), checked };
|
|
3055
|
+
if (nested.length > 0) {
|
|
3056
|
+
const sub = parseMarkdown(nested.map((row) => row.replace(/^\s{1,4}/, "")).join("\n")).blocks;
|
|
3057
|
+
item.children = sub;
|
|
3058
|
+
}
|
|
3059
|
+
items.push(item);
|
|
3060
|
+
}
|
|
3061
|
+
blocks.push({ type: "list", ordered, items });
|
|
3062
|
+
continue;
|
|
3063
|
+
}
|
|
3064
|
+
const paragraph = [line];
|
|
3065
|
+
index += 1;
|
|
3066
|
+
while (index < lines.length && lines[index].trim() && !isBlockStart(lines[index])) {
|
|
3067
|
+
paragraph.push(lines[index]);
|
|
3068
|
+
index += 1;
|
|
3069
|
+
}
|
|
3070
|
+
blocks.push({ type: "paragraph", inlines: parseInline(paragraph.map((row) => row.trim()).join(" ")) });
|
|
3071
|
+
}
|
|
3072
|
+
return { blocks, headings };
|
|
3073
|
+
}
|
|
3074
|
+
function resolveRelativeHref(currentDir, href) {
|
|
3075
|
+
const target = classifyHref(href);
|
|
3076
|
+
if (target.kind === "anchor") return { relPath: "", anchor: target.href };
|
|
3077
|
+
const raw = String(target.href || "").split("#")[0].split("?")[0];
|
|
3078
|
+
const base = String(currentDir || "").replace(/^\/+|\/+$/g, "");
|
|
3079
|
+
if (!raw) return { relPath: "", anchor: target.kind === "anchor" ? target.href : "" };
|
|
3080
|
+
const segments = `${base ? `${base}/` : ""}${raw}`.split("/");
|
|
3081
|
+
const out = [];
|
|
3082
|
+
for (const segment of segments) {
|
|
3083
|
+
if (!segment || segment === ".") continue;
|
|
3084
|
+
if (segment === "..") {
|
|
3085
|
+
out.pop();
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
out.push(segment);
|
|
3089
|
+
}
|
|
3090
|
+
return { relPath: out.join("/"), anchor: target.kind === "anchor" ? target.href : "" };
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// client/views/skill-reader.js
|
|
3094
|
+
var PARSE_CACHE = /* @__PURE__ */ new Map();
|
|
3095
|
+
function parseCached(text) {
|
|
3096
|
+
const key = String(text ?? "");
|
|
3097
|
+
if (PARSE_CACHE.has(key)) return PARSE_CACHE.get(key);
|
|
3098
|
+
const parsed = parseMarkdown(stripFrontmatter(key).body);
|
|
3099
|
+
PARSE_CACHE.set(key, parsed);
|
|
3100
|
+
if (PARSE_CACHE.size > 8) {
|
|
3101
|
+
const oldest = PARSE_CACHE.keys().next().value;
|
|
3102
|
+
PARSE_CACHE.delete(oldest);
|
|
3103
|
+
}
|
|
3104
|
+
return parsed;
|
|
3105
|
+
}
|
|
3106
|
+
function formatBytes(bytes) {
|
|
3107
|
+
const value = Number(bytes) || 0;
|
|
3108
|
+
if (value < 1024) return `${value} B`;
|
|
3109
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3110
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3111
|
+
}
|
|
3112
|
+
function formatWhen(iso) {
|
|
3113
|
+
if (!iso) return null;
|
|
3114
|
+
const date = new Date(iso);
|
|
3115
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3116
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3117
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3118
|
+
}
|
|
3119
|
+
var MARKDOWN = /\.(md|markdown)$/i;
|
|
3120
|
+
function isMarkdownPath(relPath) {
|
|
3121
|
+
return MARKDOWN.test(String(relPath || ""));
|
|
3122
|
+
}
|
|
3123
|
+
function nearestScroller(node) {
|
|
3124
|
+
let current = node?.parentElement || null;
|
|
3125
|
+
while (current) {
|
|
3126
|
+
if (typeof getComputedStyle === "function") {
|
|
3127
|
+
const style = getComputedStyle(current);
|
|
3128
|
+
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight + 4) {
|
|
3129
|
+
return current;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
current = current.parentElement || null;
|
|
3133
|
+
}
|
|
3134
|
+
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3135
|
+
}
|
|
3136
|
+
function scrollHost(node) {
|
|
3137
|
+
return node && nearestScroller(node) || node;
|
|
3138
|
+
}
|
|
3139
|
+
var STATUS_KEY = {
|
|
3140
|
+
current: "status.current",
|
|
3141
|
+
"update-available": "status.update",
|
|
3142
|
+
"locally-modified": "status.local",
|
|
3143
|
+
conflict: "status.conflict",
|
|
3144
|
+
"upstream-deleted": "status.deleted",
|
|
3145
|
+
"broken-link": "status.broken",
|
|
3146
|
+
unmanaged: "status.unmanaged"
|
|
3147
|
+
};
|
|
3148
|
+
function inlineNodes(nodes, { onOpenFile, onOpenAnchor, keyPrefix = "i" }) {
|
|
3149
|
+
if (!Array.isArray(nodes)) return null;
|
|
3150
|
+
return nodes.map((node, index) => {
|
|
3151
|
+
const key = `${keyPrefix}-${index}`;
|
|
3152
|
+
switch (node.type) {
|
|
3153
|
+
case "text":
|
|
3154
|
+
return node.value;
|
|
3155
|
+
case "code":
|
|
3156
|
+
return h("code", { className: "dshsc-md-inline", key }, node.value);
|
|
3157
|
+
case "strong":
|
|
3158
|
+
return h("strong", { key }, inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key }));
|
|
3159
|
+
case "em":
|
|
3160
|
+
return h("em", { key }, inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key }));
|
|
3161
|
+
case "link": {
|
|
3162
|
+
const children = inlineNodes(node.children, { onOpenFile, onOpenAnchor, keyPrefix: key });
|
|
3163
|
+
if (node.kind === "external") {
|
|
3164
|
+
return h("a", {
|
|
3165
|
+
className: "dshsc-md-link",
|
|
3166
|
+
key,
|
|
3167
|
+
href: node.href,
|
|
3168
|
+
target: "_blank",
|
|
3169
|
+
rel: "noopener noreferrer"
|
|
3170
|
+
}, children);
|
|
3171
|
+
}
|
|
3172
|
+
if (node.kind === "anchor") {
|
|
3173
|
+
return h("a", {
|
|
3174
|
+
className: "dshsc-md-link",
|
|
3175
|
+
key,
|
|
3176
|
+
href: `#${node.href}`,
|
|
3177
|
+
onClick: (event) => {
|
|
3178
|
+
event?.preventDefault?.();
|
|
3179
|
+
onOpenAnchor?.(node.href);
|
|
3180
|
+
}
|
|
3181
|
+
}, children);
|
|
3182
|
+
}
|
|
3183
|
+
if (node.kind === "relative") {
|
|
3184
|
+
return h("a", {
|
|
3185
|
+
className: "dshsc-md-link",
|
|
3186
|
+
key,
|
|
3187
|
+
href: "#",
|
|
3188
|
+
title: node.href,
|
|
3189
|
+
onClick: (event) => {
|
|
3190
|
+
event?.preventDefault?.();
|
|
3191
|
+
onOpenFile?.(node.href);
|
|
3192
|
+
}
|
|
3193
|
+
}, children);
|
|
3194
|
+
}
|
|
3195
|
+
return h("span", { className: "dshsc-md-nolink", key }, children);
|
|
3196
|
+
}
|
|
3197
|
+
case "image":
|
|
3198
|
+
return h(
|
|
3199
|
+
"span",
|
|
3200
|
+
{ className: "dshsc-md-image", key, title: node.src },
|
|
3201
|
+
h("span", { className: "dshsc-md-image-tag" }, "IMG"),
|
|
3202
|
+
node.alt || node.src
|
|
3203
|
+
);
|
|
3204
|
+
default:
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
function blockNodes(blocks, options) {
|
|
3210
|
+
return blocks.map((block, index) => {
|
|
3211
|
+
const key = `b-${index}`;
|
|
3212
|
+
switch (block.type) {
|
|
3213
|
+
case "heading": {
|
|
3214
|
+
const tag = `h${Math.min(block.level, 6)}`;
|
|
3215
|
+
return h(
|
|
3216
|
+
tag,
|
|
3217
|
+
{ className: "dshsc-md-h", key, id: block.id, "data-md-id": block.id },
|
|
3218
|
+
inlineNodes(block.inlines, { ...options, keyPrefix: key })
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
case "paragraph":
|
|
3222
|
+
return h(
|
|
3223
|
+
"p",
|
|
3224
|
+
{ className: "dshsc-md-p", key },
|
|
3225
|
+
inlineNodes(block.inlines, { ...options, keyPrefix: key })
|
|
3226
|
+
);
|
|
3227
|
+
case "code":
|
|
3228
|
+
return h(
|
|
3229
|
+
"pre",
|
|
3230
|
+
{ className: "dshsc-md-code", key, "data-lang": block.lang || void 0 },
|
|
3231
|
+
h("code", null, block.text)
|
|
3232
|
+
);
|
|
3233
|
+
case "hr":
|
|
3234
|
+
return h("hr", { className: "dshsc-md-hr", key });
|
|
3235
|
+
case "quote":
|
|
3236
|
+
return h("blockquote", { className: "dshsc-md-quote", key }, blockNodes(block.blocks, options));
|
|
3237
|
+
case "list": {
|
|
3238
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
3239
|
+
return h(tag, { className: "dshsc-md-list", key }, block.items.map((item, itemIndex) => h(
|
|
3240
|
+
"li",
|
|
3241
|
+
{
|
|
3242
|
+
key: `${key}-${itemIndex}`,
|
|
3243
|
+
className: item.checked === null || item.checked === void 0 ? void 0 : "dshsc-md-task"
|
|
3244
|
+
},
|
|
3245
|
+
item.checked === null || item.checked === void 0 ? null : h("span", { className: `dshsc-md-check${item.checked ? " on" : ""}`, "aria-hidden": true }),
|
|
3246
|
+
inlineNodes(item.inlines, { ...options, keyPrefix: `${key}-${itemIndex}` }),
|
|
3247
|
+
Array.isArray(item.children) && item.children.length > 0 ? blockNodes(item.children, options) : null
|
|
3248
|
+
)));
|
|
3249
|
+
}
|
|
3250
|
+
case "table":
|
|
3251
|
+
return h(
|
|
3252
|
+
"div",
|
|
3253
|
+
{ className: "dshsc-md-table", key },
|
|
3254
|
+
h(
|
|
3255
|
+
"table",
|
|
3256
|
+
null,
|
|
3257
|
+
h("thead", null, h("tr", null, block.header.map((cell, cellIndex) => h("th", {
|
|
3258
|
+
key: `h-${cellIndex}`,
|
|
3259
|
+
style: { textAlign: block.align?.[cellIndex] || "left" }
|
|
3260
|
+
}, inlineNodes(cell, { ...options, keyPrefix: `${key}-h-${cellIndex}` }))))),
|
|
3261
|
+
h("tbody", null, block.rows.map((row, rowIndex) => h(
|
|
3262
|
+
"tr",
|
|
3263
|
+
{ key: `r-${rowIndex}` },
|
|
3264
|
+
row.map((cell, cellIndex) => h("td", {
|
|
3265
|
+
key: `c-${cellIndex}`,
|
|
3266
|
+
style: { textAlign: block.align?.[cellIndex] || "left" }
|
|
3267
|
+
}, inlineNodes(cell, { ...options, keyPrefix: `${key}-${rowIndex}-${cellIndex}` })))
|
|
3268
|
+
)))
|
|
3269
|
+
)
|
|
3270
|
+
);
|
|
3271
|
+
default:
|
|
3272
|
+
return null;
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
function Markdown({ text, onOpenFile, onOpenAnchor }) {
|
|
3277
|
+
const { blocks } = parseCached(text);
|
|
3278
|
+
return h("div", { className: "dshsc-md" }, blockNodes(blocks, { onOpenFile, onOpenAnchor }));
|
|
3279
|
+
}
|
|
3280
|
+
function TabButton({ id, label, count, active, onSelect }) {
|
|
3281
|
+
return h("button", {
|
|
3282
|
+
type: "button",
|
|
3283
|
+
role: "tab",
|
|
3284
|
+
id: `dshsc-tab-${id}`,
|
|
3285
|
+
"aria-selected": String(active),
|
|
3286
|
+
"aria-controls": `dshsc-panel-${id}`,
|
|
3287
|
+
tabIndex: active ? 0 : -1,
|
|
3288
|
+
className: `dshsc-rtab${active ? " active" : ""}`,
|
|
3289
|
+
onClick: () => onSelect(id)
|
|
3290
|
+
}, label, count === null || count === void 0 ? null : h("i", { className: "n" }, count));
|
|
3291
|
+
}
|
|
3292
|
+
function InfoSection({ t, title, rows }) {
|
|
3293
|
+
return h(
|
|
3294
|
+
"section",
|
|
3295
|
+
{ className: "dshsc-info-sec" },
|
|
3296
|
+
h("h3", null, title),
|
|
3297
|
+
h("dl", null, rows.map((row, index) => [
|
|
3298
|
+
h("dt", { key: `t-${index}` }, t(row[0])),
|
|
3299
|
+
h("dd", { key: `d-${index}`, className: row[2] === "mono" ? "mono" : void 0 }, row[1])
|
|
3300
|
+
]))
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
function SkillReader({
|
|
3304
|
+
t,
|
|
3305
|
+
variant = "drawer",
|
|
3306
|
+
row,
|
|
3307
|
+
writable,
|
|
3308
|
+
busy,
|
|
3309
|
+
readOnly,
|
|
3310
|
+
version,
|
|
3311
|
+
initialTab = "doc",
|
|
3312
|
+
initialFile = null,
|
|
3313
|
+
onClose,
|
|
3314
|
+
onOpenPage,
|
|
3315
|
+
onUpdate,
|
|
3316
|
+
onLinks,
|
|
3317
|
+
onToast
|
|
3318
|
+
}) {
|
|
3319
|
+
const { state, file, loadFile, retry } = useSkillReader(row);
|
|
3320
|
+
const [tab, setTab] = import_react4.default.useState(initialTab);
|
|
3321
|
+
const [format, setFormat] = import_react4.default.useState("rendered");
|
|
3322
|
+
const [docOverride, setDocOverride] = import_react4.default.useState(null);
|
|
3323
|
+
const [fileFormat, setFileFormat] = import_react4.default.useState("rendered");
|
|
3324
|
+
const [tocActive, setTocActive] = import_react4.default.useState(null);
|
|
3325
|
+
const bodyRef = import_react4.default.useRef(null);
|
|
3326
|
+
const dialogRef = import_react4.default.useRef(null);
|
|
3327
|
+
const scrollRef = import_react4.default.useRef(0);
|
|
3328
|
+
const name = row?.name || "";
|
|
3329
|
+
const tree = state.tree;
|
|
3330
|
+
const files = tree?.files || [];
|
|
3331
|
+
const fileCount = files.filter((entry) => entry.kind === "file").length;
|
|
3332
|
+
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
3333
|
+
const description = row?.description || state.body?.meta?.description || "";
|
|
3334
|
+
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
3335
|
+
import_react4.default.useEffect(() => {
|
|
3336
|
+
setDocOverride(null);
|
|
3337
|
+
scrollRef.current = 0;
|
|
3338
|
+
}, [name]);
|
|
3339
|
+
import_react4.default.useEffect(() => {
|
|
3340
|
+
if (!initialFile) return;
|
|
3341
|
+
setTab("files");
|
|
3342
|
+
void loadFile(initialFile);
|
|
3343
|
+
}, [initialFile, loadFile]);
|
|
3344
|
+
import_react4.default.useEffect(() => {
|
|
3345
|
+
const node = bodyRef.current;
|
|
3346
|
+
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
3347
|
+
});
|
|
3348
|
+
import_react4.default.useEffect(() => {
|
|
3349
|
+
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
3350
|
+
const previous = document.activeElement;
|
|
3351
|
+
const node = dialogRef.current;
|
|
3352
|
+
node?.focus?.();
|
|
3353
|
+
const onKeyDown = (event) => {
|
|
3354
|
+
if (event?.key === "Escape") {
|
|
3355
|
+
event.stopPropagation?.();
|
|
3356
|
+
onClose?.();
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
if (event?.key !== "Tab" || !node?.querySelectorAll) return;
|
|
3360
|
+
const focusables = Array.from(
|
|
3361
|
+
node.querySelectorAll('a[href],button:not([disabled]),[tabindex]:not([tabindex="-1"])')
|
|
3362
|
+
).filter((item) => item.offsetParent !== null || item === document.activeElement);
|
|
3363
|
+
if (focusables.length === 0) return;
|
|
3364
|
+
const first = focusables[0];
|
|
3365
|
+
const last = focusables[focusables.length - 1];
|
|
3366
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3367
|
+
event.preventDefault?.();
|
|
3368
|
+
last.focus?.();
|
|
3369
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3370
|
+
event.preventDefault?.();
|
|
3371
|
+
first.focus?.();
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3375
|
+
return () => {
|
|
3376
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
3377
|
+
previous?.focus?.();
|
|
3378
|
+
};
|
|
3379
|
+
}, [variant, onClose]);
|
|
3380
|
+
import_react4.default.useEffect(() => {
|
|
3381
|
+
if (variant !== "page" || typeof document === "undefined") return;
|
|
3382
|
+
const node = document.getElementById("dshsc-reader-name");
|
|
3383
|
+
node?.focus?.();
|
|
3384
|
+
}, [variant, name]);
|
|
3385
|
+
import_react4.default.useEffect(() => {
|
|
3386
|
+
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
3387
|
+
const node = bodyRef.current;
|
|
3388
|
+
if (!node?.querySelectorAll) return void 0;
|
|
3389
|
+
const scroller = scrollHost(node);
|
|
3390
|
+
const sync = () => {
|
|
3391
|
+
const line = (scroller?.getBoundingClientRect?.().top ?? 0) + 96;
|
|
3392
|
+
let current = null;
|
|
3393
|
+
for (const element of Array.from(node.querySelectorAll("[data-md-id]"))) {
|
|
3394
|
+
const top = element.getBoundingClientRect?.().top ?? 0;
|
|
3395
|
+
if (top <= line) current = element.getAttribute("data-md-id");
|
|
3396
|
+
}
|
|
3397
|
+
setTocActive(current || headings[0]?.id || null);
|
|
3398
|
+
};
|
|
3399
|
+
sync();
|
|
3400
|
+
scroller?.addEventListener?.("scroll", sync, { passive: true });
|
|
3401
|
+
return () => scroller?.removeEventListener?.("scroll", sync);
|
|
3402
|
+
}, [variant, headings.length, tab, bodyText]);
|
|
3403
|
+
function scrollToAnchor(id) {
|
|
3404
|
+
const node = bodyRef.current;
|
|
3405
|
+
if (!node?.querySelectorAll) return;
|
|
3406
|
+
const target = Array.from(node.querySelectorAll("[data-md-id]")).find((element) => element.getAttribute("data-md-id") === id);
|
|
3407
|
+
if (!target) return;
|
|
3408
|
+
const scroller = scrollHost(node);
|
|
3409
|
+
const offset = target.getBoundingClientRect().top - (scroller?.getBoundingClientRect?.().top ?? 0);
|
|
3410
|
+
if (scroller) scroller.scrollTop = Math.max(0, (scroller.scrollTop || 0) + offset - 12);
|
|
3411
|
+
setTocActive(id);
|
|
3412
|
+
}
|
|
3413
|
+
function openLinkedFile(href, fromPath) {
|
|
3414
|
+
const source = fromPath || docOverride?.relPath || "";
|
|
3415
|
+
const base = source.split("/").slice(0, -1).join("/");
|
|
3416
|
+
const resolved = resolveRelativeHref(base, href);
|
|
3417
|
+
if (!resolved.relPath) return;
|
|
3418
|
+
setTab("files");
|
|
3419
|
+
void loadFile(resolved.relPath);
|
|
3420
|
+
}
|
|
3421
|
+
async function copyTextWithToast(text, okKey) {
|
|
3422
|
+
let ok = false;
|
|
3423
|
+
try {
|
|
3424
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
3425
|
+
await navigator.clipboard.writeText(text);
|
|
3426
|
+
ok = true;
|
|
3427
|
+
}
|
|
3428
|
+
} catch {
|
|
3429
|
+
ok = false;
|
|
3430
|
+
}
|
|
3431
|
+
onToast?.(ok ? { kind: "ok", text: t(okKey) } : { kind: "err", text: t("viewer.copyFailed") });
|
|
3432
|
+
}
|
|
3433
|
+
function selectTab(next) {
|
|
3434
|
+
if (next === tab) return;
|
|
3435
|
+
if (bodyRef.current) scrollRef.current = 0;
|
|
3436
|
+
setTab(next);
|
|
3437
|
+
}
|
|
3438
|
+
const header = h(
|
|
3439
|
+
"header",
|
|
3440
|
+
{ className: "dshsc-reader-head" },
|
|
3441
|
+
h(
|
|
3442
|
+
"div",
|
|
3443
|
+
{ className: "dshsc-reader-title" },
|
|
3444
|
+
h("h2", { id: "dshsc-reader-name", tabIndex: -1 }, name || t("viewer.title")),
|
|
3445
|
+
row?.status && STATUS_KEY[row.status] ? h("span", { className: `dshsc-tag ${row.status === "current" ? "ok" : "warn"}` }, t(STATUS_KEY[row.status])) : null,
|
|
3446
|
+
row?.mode === "off" ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null,
|
|
3447
|
+
variant === "drawer" ? h("button", {
|
|
3448
|
+
type: "button",
|
|
3449
|
+
className: "dshsc-reader-close",
|
|
3450
|
+
"aria-label": t("viewer.close"),
|
|
3451
|
+
title: t("viewer.close"),
|
|
3452
|
+
onClick: () => onClose?.()
|
|
3453
|
+
}, "×") : null
|
|
3454
|
+
),
|
|
3455
|
+
description ? h("p", { className: "dshsc-reader-desc" }, description) : null,
|
|
3456
|
+
h(
|
|
3457
|
+
"div",
|
|
3458
|
+
{ className: "dshsc-reader-meta" },
|
|
3459
|
+
h(
|
|
3460
|
+
"span",
|
|
3461
|
+
null,
|
|
3462
|
+
`${t("meta.from")} `,
|
|
3463
|
+
h("span", { className: "dshsc-code" }, row?.source ? `${row.source}${row.commit ? `#${short(row.commit)}` : row.ref ? `#${row.ref}` : ""}` : t("meta.noUpstream"))
|
|
3464
|
+
),
|
|
3465
|
+
tree ? h("span", null, t("viewer.fileCount").replace("{n}", String(fileCount))) : null,
|
|
3466
|
+
row?.updatedAt ? h("span", null, formatWhen(row.updatedAt)) : null
|
|
3467
|
+
)
|
|
3468
|
+
);
|
|
3469
|
+
const tabsBar = h(
|
|
3470
|
+
"div",
|
|
3471
|
+
{ className: "dshsc-reader-tabs" },
|
|
3472
|
+
h(
|
|
3473
|
+
"div",
|
|
3474
|
+
{ className: "dshsc-rtabs", role: "tablist", "aria-label": t("viewer.title") },
|
|
3475
|
+
h(TabButton, { id: "doc", label: t("viewer.tab.doc"), active: tab === "doc", onSelect: selectTab }),
|
|
3476
|
+
h(TabButton, { id: "files", label: t("viewer.tab.files"), count: tree ? fileCount : null, active: tab === "files", onSelect: selectTab }),
|
|
3477
|
+
h(TabButton, { id: "info", label: t("viewer.tab.info"), active: tab === "info", onSelect: selectTab })
|
|
3478
|
+
),
|
|
3479
|
+
tab === "doc" && !docOverride ? h(
|
|
3480
|
+
"div",
|
|
3481
|
+
{ className: "dshsc-segment dshsc-reader-seg", role: "group", "aria-label": t("viewer.rendered") },
|
|
3482
|
+
h("button", {
|
|
3483
|
+
type: "button",
|
|
3484
|
+
className: "dshsc-btn",
|
|
3485
|
+
"aria-pressed": String(format === "rendered"),
|
|
3486
|
+
onClick: () => setFormat("rendered")
|
|
3487
|
+
}, t("viewer.rendered")),
|
|
3488
|
+
h("button", {
|
|
3489
|
+
type: "button",
|
|
3490
|
+
className: "dshsc-btn",
|
|
3491
|
+
"aria-pressed": String(format === "raw"),
|
|
3492
|
+
onClick: () => setFormat("raw")
|
|
3493
|
+
}, t("viewer.raw"))
|
|
3494
|
+
) : null
|
|
3495
|
+
);
|
|
3496
|
+
function docPanel() {
|
|
3497
|
+
if (state.loading) {
|
|
3498
|
+
return h(
|
|
3499
|
+
"div",
|
|
3500
|
+
{ className: "dshsc-reader-skeleton" },
|
|
3501
|
+
h("span", null, t("viewer.loading")),
|
|
3502
|
+
h("i"),
|
|
3503
|
+
h("i"),
|
|
3504
|
+
h("i")
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
if (!bodyText) {
|
|
3508
|
+
const error = state.bodyError;
|
|
3509
|
+
const missing = error?.code === "error.skills.no-skill-md";
|
|
3510
|
+
return h(
|
|
3511
|
+
"div",
|
|
3512
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3513
|
+
h("b", null, missing ? t("viewer.noSkill") : error?.message || t("viewer.noSkill")),
|
|
3514
|
+
missing ? h("span", null, t("viewer.noSkillHint")) : null,
|
|
3515
|
+
error && !missing ? h("button", { type: "button", className: "dshsc-btn", onClick: retry }, t("viewer.retry")) : null
|
|
3516
|
+
);
|
|
3517
|
+
}
|
|
3518
|
+
if (docOverride) {
|
|
3519
|
+
return h(
|
|
3520
|
+
"div",
|
|
3521
|
+
{ className: "dshsc-reader-doc" },
|
|
3522
|
+
h(
|
|
3523
|
+
"div",
|
|
3524
|
+
{ className: "dshsc-reader-filebar" },
|
|
3525
|
+
h("span", { className: "mono" }, docOverride.relPath),
|
|
3526
|
+
h("button", {
|
|
3527
|
+
type: "button",
|
|
3528
|
+
className: "dshsc-btn",
|
|
3529
|
+
onClick: () => setDocOverride(null)
|
|
3530
|
+
}, t("viewer.backToBody"))
|
|
3531
|
+
),
|
|
3532
|
+
h(Markdown, { text: docOverride.content, onOpenFile: openLinkedFile, onOpenAnchor: scrollToAnchor })
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
const truncated = Boolean(state.body?.truncated);
|
|
3536
|
+
return h(
|
|
3537
|
+
"div",
|
|
3538
|
+
{ className: "dshsc-reader-doc" },
|
|
3539
|
+
truncated ? h("div", { className: "dshsc-banner warn" }, t("viewer.tooLarge").replace("{size}", formatBytes(256 * 1024))) : null,
|
|
3540
|
+
format === "raw" ? h("pre", { className: "dshsc-reader-raw" }, h("code", null, bodyText)) : h(Markdown, { text: bodyText, onOpenFile: openLinkedFile, onOpenAnchor: scrollToAnchor })
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3543
|
+
function filesPanel() {
|
|
3544
|
+
if (state.treeError) {
|
|
3545
|
+
return h(
|
|
3546
|
+
"div",
|
|
3547
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3548
|
+
h("b", null, state.treeError.message),
|
|
3549
|
+
h("button", { type: "button", className: "dshsc-btn", onClick: retry }, t("viewer.retry"))
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3552
|
+
if (!tree) return h("div", { className: "dshsc-reader-skeleton" }, h("i"), h("i"));
|
|
3553
|
+
if (files.length === 0) {
|
|
3554
|
+
return h("div", { className: "dshsc-reader-placeholder" }, h("b", null, t("viewer.emptyFiles")));
|
|
3555
|
+
}
|
|
3556
|
+
const active = file.relPath;
|
|
3557
|
+
const activeEntry = files.find((entry) => entry.kind === "file" && entry.path === active) || null;
|
|
3558
|
+
const renderable = Boolean(active) && isMarkdownPath(active) && !file.error && !file.loading;
|
|
3559
|
+
const preview = file.loading ? h("div", { className: "dshsc-reader-skeleton" }, h("i"), h("i"), h("i")) : file.error ? h(
|
|
3560
|
+
"div",
|
|
3561
|
+
{ className: "dshsc-reader-placeholder" },
|
|
3562
|
+
h("b", null, file.error.code === "error.skills.binary" ? t("viewer.binary") : file.error.message),
|
|
3563
|
+
h(
|
|
3564
|
+
"span",
|
|
3565
|
+
{ className: "mono" },
|
|
3566
|
+
[active, activeEntry ? formatBytes(activeEntry.size) : null].filter(Boolean).join(" · ")
|
|
3567
|
+
),
|
|
3568
|
+
file.error.code === "error.skills.binary" ? h("span", null, t("viewer.binaryHint")) : null
|
|
3569
|
+
) : renderable && fileFormat === "rendered" ? h(
|
|
3570
|
+
"div",
|
|
3571
|
+
{ className: "dshsc-reader-preview-doc" },
|
|
3572
|
+
h(Markdown, {
|
|
3573
|
+
text: file.content,
|
|
3574
|
+
onOpenFile: (href) => openLinkedFile(href, file.relPath || active),
|
|
3575
|
+
onOpenAnchor: scrollToAnchor
|
|
3576
|
+
})
|
|
3577
|
+
) : h(
|
|
3578
|
+
"pre",
|
|
3579
|
+
{ className: "dshsc-reader-raw" },
|
|
3580
|
+
h("code", null, file.content + (file.truncated ? `
|
|
3581
|
+
|
|
3582
|
+
… ${t("viewer.tooLarge").replace("{size}", formatBytes(256 * 1024))}` : ""))
|
|
3583
|
+
);
|
|
3584
|
+
return h(
|
|
3585
|
+
"div",
|
|
3586
|
+
{ className: "dshsc-reader-files" },
|
|
3587
|
+
h(
|
|
3588
|
+
"div",
|
|
3589
|
+
{ className: "dshsc-reader-tree" },
|
|
3590
|
+
h(
|
|
3591
|
+
"div",
|
|
3592
|
+
{ className: "dshsc-reader-tree-head" },
|
|
3593
|
+
h("span", null, name),
|
|
3594
|
+
h("span", { className: "mono" }, tree.kind === "file" ? tree.body : `~/.agents/skills/${name}`)
|
|
3595
|
+
),
|
|
3596
|
+
files.map((entry) => {
|
|
3597
|
+
if (entry.kind === "dir") {
|
|
3598
|
+
const count = files.filter((item) => item.kind === "file" && item.path.startsWith(`${entry.path}/`)).length;
|
|
3599
|
+
return h(
|
|
3600
|
+
"div",
|
|
3601
|
+
{ className: "dshsc-reader-tdir", key: `d:${entry.path}` },
|
|
3602
|
+
h(FolderIcon, null),
|
|
3603
|
+
h("span", { className: "mono" }, `${entry.path}/`),
|
|
3604
|
+
h("span", { className: "n" }, String(count))
|
|
3605
|
+
);
|
|
3606
|
+
}
|
|
3607
|
+
const depth = entry.path.split("/").length - 1;
|
|
3608
|
+
return h(
|
|
3609
|
+
"button",
|
|
3610
|
+
{
|
|
3611
|
+
type: "button",
|
|
3612
|
+
key: `f:${entry.path}`,
|
|
3613
|
+
className: `dshsc-reader-titem${active === entry.path ? " on" : ""}`,
|
|
3614
|
+
style: { paddingLeft: 8 + depth * 12 },
|
|
3615
|
+
title: entry.path,
|
|
3616
|
+
onClick: () => {
|
|
3617
|
+
setFileFormat("rendered");
|
|
3618
|
+
void loadFile(entry.path);
|
|
3619
|
+
}
|
|
3620
|
+
},
|
|
3621
|
+
h(FileIcon, null),
|
|
3622
|
+
h("span", { className: "nname" }, entry.path.split("/").pop()),
|
|
3623
|
+
h("span", { className: "nsize" }, formatBytes(entry.size))
|
|
3624
|
+
);
|
|
3625
|
+
})
|
|
3626
|
+
),
|
|
3627
|
+
h(
|
|
3628
|
+
"div",
|
|
3629
|
+
{ className: "dshsc-reader-preview" },
|
|
3630
|
+
h(
|
|
3631
|
+
"div",
|
|
3632
|
+
{ className: "dshsc-reader-tree-head" },
|
|
3633
|
+
h("span", null, t("viewer.tab.files")),
|
|
3634
|
+
h("span", { className: "mono" }, active || "—"),
|
|
3635
|
+
// 预览的三个动作放在一起,靠右成一排,而不是一个贴路径、一个飘到最右
|
|
3636
|
+
h(
|
|
3637
|
+
"div",
|
|
3638
|
+
{ className: "dshsc-reader-tools" },
|
|
3639
|
+
renderable ? h(
|
|
3640
|
+
"div",
|
|
3641
|
+
{ className: "dshsc-segment dshsc-reader-seg", role: "group", "aria-label": t("viewer.rendered") },
|
|
3642
|
+
h("button", {
|
|
3643
|
+
type: "button",
|
|
3644
|
+
className: "dshsc-btn",
|
|
3645
|
+
"aria-pressed": String(fileFormat === "rendered"),
|
|
3646
|
+
onClick: () => setFileFormat("rendered")
|
|
3647
|
+
}, t("viewer.rendered")),
|
|
3648
|
+
h("button", {
|
|
3649
|
+
type: "button",
|
|
3650
|
+
className: "dshsc-btn",
|
|
3651
|
+
"aria-pressed": String(fileFormat === "raw"),
|
|
3652
|
+
onClick: () => setFileFormat("raw")
|
|
3653
|
+
}, t("viewer.raw"))
|
|
3654
|
+
) : null,
|
|
3655
|
+
active && !file.loading && !file.error && isMarkdownPath(active) ? h("button", {
|
|
3656
|
+
type: "button",
|
|
3657
|
+
className: "dshsc-btn",
|
|
3658
|
+
onClick: () => {
|
|
3659
|
+
setDocOverride({ relPath: file.relPath || active, content: file.content });
|
|
3660
|
+
setTab("doc");
|
|
3661
|
+
}
|
|
3662
|
+
}, t("viewer.openInDoc")) : null
|
|
3663
|
+
)
|
|
3664
|
+
),
|
|
3665
|
+
preview
|
|
3666
|
+
)
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
function infoPanel() {
|
|
3670
|
+
const agentLabels = (row?.links || []).filter((link) => !link.broken).map((link) => link.target === "dsh-global" ? "DSH" : link.label);
|
|
3671
|
+
const projects = row?.scopeProjects || [];
|
|
3672
|
+
const hash = row?.lockHash || row?.currentHash || null;
|
|
3673
|
+
const rows = [];
|
|
3674
|
+
rows.push(["viewer.info.name", h("span", { className: "mono" }, name), "mono"]);
|
|
3675
|
+
if (description) rows.push(["viewer.info.desc", description]);
|
|
3676
|
+
rows.push(["viewer.info.path", h("span", { className: "mono" }, row?.path || (tree?.root ?? t("viewer.info.none"))), "mono"]);
|
|
3677
|
+
const sourceRows = [
|
|
3678
|
+
["viewer.info.repo", row?.source ? h("span", { className: "mono" }, row.source) : t("viewer.info.none"), "mono"],
|
|
3679
|
+
["viewer.info.ref", row?.commit ? h("span", { className: "mono" }, short(row.commit)) : row?.ref || t("viewer.info.none"), "mono"],
|
|
3680
|
+
["viewer.info.skillPath", row?.skillPath ? h("span", { className: "mono" }, row.skillPath) : t("viewer.info.none"), "mono"],
|
|
3681
|
+
["viewer.info.hash", hash ? h("span", { className: "mono" }, hash) : t("viewer.info.none"), "mono"]
|
|
3682
|
+
];
|
|
3683
|
+
const localRows = [
|
|
3684
|
+
["viewer.info.installedAt", formatWhen(row?.installedAt) || t("viewer.info.none")],
|
|
3685
|
+
["viewer.info.updatedAt", formatWhen(row?.updatedAt) || t("viewer.info.none")],
|
|
3686
|
+
["viewer.info.size", tree ? `${formatBytes(tree.totalBytes)} · ${t("viewer.fileCount").replace("{n}", String(fileCount))}` : t("viewer.info.none")],
|
|
3687
|
+
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
3688
|
+
];
|
|
3689
|
+
const linkRows = [
|
|
3690
|
+
["viewer.info.agents", agentLabels.length > 0 ? agentLabels.join("、") : t("meta.unlinked")],
|
|
3691
|
+
["viewer.info.scope", projects.length > 0 ? `${t("viewer.info.scopeProject")}:${projects.map((item) => item.split("/").pop()).join("、")}` : t("viewer.info.scopeGlobal")],
|
|
3692
|
+
["viewer.info.mode", row?.mode === "off" ? t("status.off") : t("mode.on")],
|
|
3693
|
+
["viewer.info.status", STATUS_KEY[row?.status] ? t(STATUS_KEY[row.status]) : t("status.unknown")]
|
|
3694
|
+
];
|
|
3695
|
+
return h(
|
|
3696
|
+
"div",
|
|
3697
|
+
{ className: "dshsc-reader-info" },
|
|
3698
|
+
h(InfoSection, { t, title: t("viewer.info.basic"), rows }),
|
|
3699
|
+
h(InfoSection, { t, title: t("viewer.info.source"), rows: sourceRows }),
|
|
3700
|
+
h(InfoSection, { t, title: t("viewer.info.local"), rows: localRows }),
|
|
3701
|
+
h(InfoSection, { t, title: t("viewer.info.links"), rows: linkRows })
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3704
|
+
function panel() {
|
|
3705
|
+
if (tab === "files") return filesPanel();
|
|
3706
|
+
if (tab === "info") return infoPanel();
|
|
3707
|
+
return docPanel();
|
|
3708
|
+
}
|
|
3709
|
+
const body = h("div", {
|
|
3710
|
+
className: "dshsc-reader-body",
|
|
3711
|
+
ref: bodyRef,
|
|
3712
|
+
role: "tabpanel",
|
|
3713
|
+
id: `dshsc-panel-${tab}`,
|
|
3714
|
+
"aria-labelledby": `dshsc-tab-${tab}`,
|
|
3715
|
+
tabIndex: -1
|
|
3716
|
+
}, panel());
|
|
3717
|
+
const canUpdate = Boolean(writable) && Boolean(row) && !busy;
|
|
3718
|
+
const actionsFooter = h(
|
|
3719
|
+
"div",
|
|
3720
|
+
{ className: "dshsc-reader-actions" },
|
|
3721
|
+
h("span", { className: "dshsc-reader-note" }, readOnly ? t("banner.readonly") : t("viewer.readonlyNote")),
|
|
3722
|
+
h("button", {
|
|
3723
|
+
type: "button",
|
|
3724
|
+
// 抽屉底栏空间紧,图标按钮;整页顶栏跟一排文字按钮并排,用文字更齐
|
|
3725
|
+
className: variant === "page" ? "dshsc-btn ghost" : "dshsc-btn ghost sq",
|
|
3726
|
+
title: t("viewer.copyBody"),
|
|
3727
|
+
"aria-label": t("viewer.copyBody"),
|
|
3728
|
+
disabled: !bodyText,
|
|
3729
|
+
onClick: () => void copyTextWithToast(bodyText, "viewer.copied")
|
|
3730
|
+
}, h(CopyIcon, null), variant === "page" ? t("viewer.copyBody") : null),
|
|
3731
|
+
h("button", {
|
|
3732
|
+
type: "button",
|
|
3733
|
+
className: "dshsc-btn ghost",
|
|
3734
|
+
title: row?.path || "",
|
|
3735
|
+
disabled: !row?.path,
|
|
3736
|
+
onClick: () => void copyTextWithToast(row?.path || "", "viewer.copied")
|
|
3737
|
+
}, t("viewer.copyPath")),
|
|
3738
|
+
variant === "drawer" && onOpenPage ? h("button", { type: "button", className: "dshsc-btn ghost", onClick: () => onOpenPage() }, t("viewer.openPage")) : null,
|
|
3739
|
+
onLinks ? h("button", { type: "button", className: "dshsc-btn", onClick: () => onLinks?.(row) }, t("action.links")) : null,
|
|
3740
|
+
onUpdate ? h("button", {
|
|
3741
|
+
type: "button",
|
|
3742
|
+
className: "dshsc-btn tint",
|
|
3743
|
+
disabled: !canUpdate,
|
|
3744
|
+
onClick: () => onUpdate?.(row)
|
|
3745
|
+
}, t("action.update")) : null
|
|
3746
|
+
);
|
|
3747
|
+
if (variant === "page") {
|
|
3748
|
+
return h(
|
|
3749
|
+
"div",
|
|
3750
|
+
{ className: "dshsc-reader-page" },
|
|
3751
|
+
h(
|
|
3752
|
+
"div",
|
|
3753
|
+
{ className: "dshsc-reader-topbar" },
|
|
3754
|
+
h(
|
|
3755
|
+
"button",
|
|
3756
|
+
{ type: "button", className: "dshsc-btn ghost", "data-autofocus": true, onClick: () => onClose?.() },
|
|
3757
|
+
h(ChevronLeftIcon, null),
|
|
3758
|
+
t("viewer.back")
|
|
3759
|
+
),
|
|
3760
|
+
h(
|
|
3761
|
+
"span",
|
|
3762
|
+
{ className: "dshsc-reader-crumb" },
|
|
3763
|
+
h("span", null, t("tab.installed")),
|
|
3764
|
+
h("span", { className: "sep" }, "/"),
|
|
3765
|
+
h("span", { className: "mono" }, name)
|
|
3766
|
+
),
|
|
3767
|
+
h("span", { className: "spacer" }),
|
|
3768
|
+
actionsFooter
|
|
3769
|
+
),
|
|
3770
|
+
h("div", { className: "dshsc-reader-hero" }, header),
|
|
3771
|
+
h(
|
|
3772
|
+
"div",
|
|
3773
|
+
{ className: "dshsc-reader-layout" },
|
|
3774
|
+
headings.length > 1 ? h(
|
|
3775
|
+
"nav",
|
|
3776
|
+
{ className: "dshsc-reader-toc", "aria-label": t("viewer.toc") },
|
|
3777
|
+
h("div", { className: "dshsc-reader-toc-title" }, t("viewer.toc")),
|
|
3778
|
+
headings.map((heading) => h("button", {
|
|
3779
|
+
type: "button",
|
|
3780
|
+
key: heading.id,
|
|
3781
|
+
className: `dshsc-reader-tocitem l${heading.level}${tocActive === heading.id ? " on" : ""}`,
|
|
3782
|
+
onClick: () => scrollToAnchor(heading.id)
|
|
3783
|
+
}, heading.text))
|
|
3784
|
+
) : null,
|
|
3785
|
+
h("div", { className: "dshsc-reader-col" }, tabsBar, body)
|
|
3786
|
+
)
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
return portalize(h(
|
|
3790
|
+
"div",
|
|
3791
|
+
{ className: "dshsc-drawer-wrap" },
|
|
3792
|
+
h("div", { className: "dshsc-drawer-scrim", onClick: () => onClose?.() }),
|
|
3793
|
+
h("aside", {
|
|
3794
|
+
className: "dshsc-drawer",
|
|
3795
|
+
role: "dialog",
|
|
3796
|
+
"aria-modal": "true",
|
|
3797
|
+
"aria-labelledby": "dshsc-reader-name",
|
|
3798
|
+
ref: dialogRef,
|
|
3799
|
+
tabIndex: -1
|
|
3800
|
+
}, header, tabsBar, body, actionsFooter)
|
|
3801
|
+
));
|
|
3802
|
+
}
|
|
3803
|
+
|
|
2294
3804
|
// client/section.js
|
|
2295
3805
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
2296
3806
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
2297
3807
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
2298
|
-
const [tab, setTab] =
|
|
2299
|
-
const [view, setView] =
|
|
2300
|
-
const [modal, setModal] =
|
|
2301
|
-
const [expanded, setExpanded] =
|
|
2302
|
-
const [placementBusy, setPlacementBusy] =
|
|
2303
|
-
const [placementOverride, setPlacementOverride] =
|
|
2304
|
-
const [query, setQuery] =
|
|
2305
|
-
const [
|
|
2306
|
-
const
|
|
3808
|
+
const [tab, setTab] = import_react5.default.useState("installed");
|
|
3809
|
+
const [view, setView] = import_react5.default.useState("flat");
|
|
3810
|
+
const [modal, setModal] = import_react5.default.useState(null);
|
|
3811
|
+
const [expanded, setExpanded] = import_react5.default.useState({});
|
|
3812
|
+
const [placementBusy, setPlacementBusy] = import_react5.default.useState(null);
|
|
3813
|
+
const [placementOverride, setPlacementOverride] = import_react5.default.useState(null);
|
|
3814
|
+
const [query, setQuery] = import_react5.default.useState("");
|
|
3815
|
+
const [viewer, setViewer] = import_react5.default.useState(null);
|
|
3816
|
+
const rootRef = import_react5.default.useRef(null);
|
|
3817
|
+
const scrollPos = import_react5.default.useRef(null);
|
|
3818
|
+
const [filter, setFilter] = import_react5.default.useState("all");
|
|
3819
|
+
const [builtinOpen, setBuiltinOpen] = import_react5.default.useState(false);
|
|
2307
3820
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
2308
3821
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
2309
3822
|
const rows = data?.installs || [];
|
|
@@ -2324,11 +3837,55 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2324
3837
|
const agentLabels = row.links.map((link) => link.target === "dsh-global" ? "DSH" : link.label);
|
|
2325
3838
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
2326
3839
|
});
|
|
3840
|
+
import_react5.default.useEffect(() => {
|
|
3841
|
+
if (!viewer || !data) return;
|
|
3842
|
+
if (rows.some((row) => row.name === viewer.name)) return;
|
|
3843
|
+
setViewer(null);
|
|
3844
|
+
setToast({ kind: "err", text: t("viewer.gone") });
|
|
3845
|
+
}, [data, rows, t, viewer]);
|
|
2327
3846
|
const autolinkTargets = (data?.agents?.detected || []).filter((agent) => agent.kind === "agent");
|
|
2328
3847
|
const autoLinkSelection = data?.settings?.autoLinkTargets;
|
|
2329
3848
|
const autoLinkAgents = Array.isArray(autoLinkSelection) ? autolinkTargets.filter((agent) => autoLinkSelection.includes(agent.target)) : autolinkTargets;
|
|
3849
|
+
const autoLinkDsh = (data?.agents?.all || []).find((agent) => agent.target === "dsh-global") || null;
|
|
2330
3850
|
const autoLinkLabel = [t("links.dshGlobal"), ...autoLinkAgents.map((agent) => agent.label)].join("、");
|
|
2331
3851
|
const placement = (placementOverride ?? data?.settings?.placement) === "sidebar" ? "sidebar" : "settings";
|
|
3852
|
+
function getScrollParent(node) {
|
|
3853
|
+
let current = node?.parentElement || null;
|
|
3854
|
+
while (current) {
|
|
3855
|
+
if (typeof getComputedStyle === "function") {
|
|
3856
|
+
const style = getComputedStyle(current);
|
|
3857
|
+
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight) {
|
|
3858
|
+
return current;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
current = current.parentElement || null;
|
|
3862
|
+
}
|
|
3863
|
+
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3864
|
+
}
|
|
3865
|
+
function openViewer(row, mode = "drawer") {
|
|
3866
|
+
if (!row) return;
|
|
3867
|
+
if (mode === "page") {
|
|
3868
|
+
scrollPos.current = getScrollParent(rootRef.current)?.scrollTop ?? 0;
|
|
3869
|
+
}
|
|
3870
|
+
setViewer({ name: row.name, mode });
|
|
3871
|
+
}
|
|
3872
|
+
function openPage() {
|
|
3873
|
+
setViewer((prev) => {
|
|
3874
|
+
if (!prev) return prev;
|
|
3875
|
+
scrollPos.current = getScrollParent(rootRef.current)?.scrollTop ?? 0;
|
|
3876
|
+
return { ...prev, mode: "page" };
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
function closeViewer() {
|
|
3880
|
+
const restore = viewer?.mode === "page" ? scrollPos.current : null;
|
|
3881
|
+
setViewer(null);
|
|
3882
|
+
if (restore === null || typeof window === "undefined") return;
|
|
3883
|
+
const apply2 = () => {
|
|
3884
|
+
const scroller = getScrollParent(rootRef.current);
|
|
3885
|
+
if (scroller) scroller.scrollTop = restore;
|
|
3886
|
+
};
|
|
3887
|
+
if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(apply2);
|
|
3888
|
+
}
|
|
2332
3889
|
async function changePlacement(next) {
|
|
2333
3890
|
if (next === placement || placementBusy) return;
|
|
2334
3891
|
setPlacementBusy(next);
|
|
@@ -2348,6 +3905,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2348
3905
|
await run("settings", () => apiCall("POST", "/settings", { autoLinkTargets: targets }));
|
|
2349
3906
|
}
|
|
2350
3907
|
const installedActions = {
|
|
3908
|
+
onView: (row) => openViewer(row),
|
|
2351
3909
|
onAdopt: actions.adopt,
|
|
2352
3910
|
onToggleMode: actions.toggleMode,
|
|
2353
3911
|
onUpdate: actions.requestUpdate,
|
|
@@ -2373,10 +3931,23 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2373
3931
|
onInstall: actions.installFromSubscription,
|
|
2374
3932
|
onAdd: actions.requestAddSubscription
|
|
2375
3933
|
};
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
3934
|
+
const viewerRow = viewer ? rows.find((row) => row.name === viewer.name) || null : null;
|
|
3935
|
+
const readerNode = viewerRow ? h(SkillReader, {
|
|
3936
|
+
t,
|
|
3937
|
+
variant: viewer.mode,
|
|
3938
|
+
row: viewerRow,
|
|
3939
|
+
writable,
|
|
3940
|
+
busy,
|
|
3941
|
+
readOnly: Boolean(data?.readOnly),
|
|
3942
|
+
onClose: closeViewer,
|
|
3943
|
+
onOpenPage: viewer.mode === "drawer" ? openPage : null,
|
|
3944
|
+
onUpdate: actions.requestUpdate,
|
|
3945
|
+
onLinks: (row) => setModal({ type: "links", row }),
|
|
3946
|
+
onToast: (next) => setToast(next)
|
|
3947
|
+
}) : null;
|
|
3948
|
+
const listNode = h(
|
|
3949
|
+
import_react5.default.Fragment,
|
|
3950
|
+
null,
|
|
2380
3951
|
h(Header, {
|
|
2381
3952
|
t,
|
|
2382
3953
|
page,
|
|
@@ -2435,13 +4006,21 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2435
4006
|
writable,
|
|
2436
4007
|
...subscriptionActions
|
|
2437
4008
|
}),
|
|
2438
|
-
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|
|
4009
|
+
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|
|
4010
|
+
);
|
|
4011
|
+
const root = h(
|
|
4012
|
+
"div",
|
|
4013
|
+
{ className: "dshsc-root", ref: rootRef },
|
|
4014
|
+
h("style", null, CSS),
|
|
4015
|
+
viewer?.mode === "page" ? null : listNode,
|
|
4016
|
+
readerNode,
|
|
2439
4017
|
toast ? h("div", { className: `dshsc-toast ${toast.kind}` }, toast.text) : null,
|
|
2440
4018
|
modal ? h(Modals, {
|
|
2441
4019
|
t,
|
|
2442
4020
|
modal,
|
|
2443
4021
|
data,
|
|
2444
4022
|
autoLinkAgents: autolinkTargets,
|
|
4023
|
+
autoLinkDsh,
|
|
2445
4024
|
pickDirectory,
|
|
2446
4025
|
actions: {
|
|
2447
4026
|
probeSkills: actions.probeSkills,
|