@morehao/dsh-skills-console 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/client.js +212 -128
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ DSH(DeepSeek Harness)的**技能控制台**:装、更新、删除、接入
|
|
|
37
37
|
- **两个入口**:默认在「设置 → 技能控制台」;页头 ⚙ 里可切到左侧栏独立面板,切换立即生效。
|
|
38
38
|
- **一页两 tab**:已安装 / 订阅。页头右侧是「自动接入」(多选 Agent)与「添加技能 / 添加订阅」。
|
|
39
39
|
- **每行五个动作**:开关(启停)、查看、更新、接入设置、删除;窄面板下隐藏「查看」,技能名本身仍是入口。状态标签覆盖最新 / 可更新 / 本地已改 / 上游已移除 / 软链断裂 / 无上游等。
|
|
40
|
-
-
|
|
40
|
+
- **窄面板不跳版**:页头按内容自然换行——放得下时动作组在标题行右侧,放不下(约 630px 以下,如设置页)就整组换到下一行并靠右,插件名始终完整;列表按面板自身宽度在 860px 处切两列;滚动只发生在列表区(页头、工具栏、筛选条固定不动),滚动条出现/消失、切 tab、窗口微调都不会让整页翻面。
|
|
41
41
|
- **Agent 多了不挤**:每行最多展示 3 个,其余折成 `+n`,悬浮或聚焦即可看全。
|
|
42
42
|
- **找得到**:支持按名称 / 来源 / Agent 搜索,按「可更新 / 待检查 / 已停用」筛选,平铺或按 Agent 分组。
|
|
43
43
|
- **待检查 vs 无上游**:「待检查」只表示*这次*没拿到上游结论(限流、网络、仓库 404 等);再次点「检查更新」时,如果上次没有结论、上游又只回了 304,会无视 ETag 完整重读一次树,不会把「待检查」永久粘住。没有可用订阅来源的技能标「无上游」——它们无法参与更新检查,不计入待检查。
|
package/client.js
CHANGED
|
@@ -470,8 +470,45 @@ try {
|
|
|
470
470
|
} catch {
|
|
471
471
|
ReactDOM = null;
|
|
472
472
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
473
|
+
function overlayHost() {
|
|
474
|
+
if (typeof document === "undefined") return null;
|
|
475
|
+
const root = document.querySelector?.(".dshsc-root");
|
|
476
|
+
const dialog = root?.closest?.('[role="dialog"]');
|
|
477
|
+
return dialog || document.querySelector?.("body") || null;
|
|
478
|
+
}
|
|
479
|
+
function portalize(node, target) {
|
|
480
|
+
const host = target || (typeof document !== "undefined" ? document.body : null);
|
|
481
|
+
return ReactDOM?.createPortal && host ? ReactDOM.createPortal(node, host) : node;
|
|
482
|
+
}
|
|
483
|
+
var overlayLayers = [];
|
|
484
|
+
var escapeGuarded = false;
|
|
485
|
+
function onOverlayEscape(event) {
|
|
486
|
+
if (event.key !== "Escape" || overlayLayers.length === 0) return;
|
|
487
|
+
event.preventDefault?.();
|
|
488
|
+
event.stopPropagation?.();
|
|
489
|
+
event.stopImmediatePropagation?.();
|
|
490
|
+
overlayLayers[overlayLayers.length - 1].close();
|
|
491
|
+
}
|
|
492
|
+
function useOverlayLayer(active, close) {
|
|
493
|
+
const closeRef = import_react.default.useRef(close);
|
|
494
|
+
closeRef.current = close;
|
|
495
|
+
import_react.default.useEffect(() => {
|
|
496
|
+
if (!active || typeof document === "undefined") return void 0;
|
|
497
|
+
const layer = { close: () => closeRef.current?.() };
|
|
498
|
+
overlayLayers.push(layer);
|
|
499
|
+
if (!escapeGuarded) {
|
|
500
|
+
document.addEventListener("keydown", onOverlayEscape, true);
|
|
501
|
+
escapeGuarded = true;
|
|
502
|
+
}
|
|
503
|
+
return () => {
|
|
504
|
+
const index = overlayLayers.indexOf(layer);
|
|
505
|
+
if (index >= 0) overlayLayers.splice(index, 1);
|
|
506
|
+
if (overlayLayers.length === 0 && escapeGuarded) {
|
|
507
|
+
document.removeEventListener("keydown", onOverlayEscape, true);
|
|
508
|
+
escapeGuarded = false;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}, [active]);
|
|
475
512
|
}
|
|
476
513
|
|
|
477
514
|
// client/icons.js
|
|
@@ -815,32 +852,45 @@ var import_react5 = __toESM(require("react"), 1);
|
|
|
815
852
|
|
|
816
853
|
// client/styles.js
|
|
817
854
|
var CSS = `
|
|
818
|
-
/*
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
855
|
+
/* 主面板形态:页面本身固定不滚,滚动条只出现在列表区(.dshsc-listscroll)。
|
|
856
|
+
页头 / 工具栏 / 筛选条是 flex:none 的固定行,行区域 flex:1 + overflow:auto。
|
|
857
|
+
scrollbar-gutter 跟着滚动条一起挪到列表区:行区域变长/变短都不改变自身宽度,
|
|
858
|
+
贴着 860 断点的布局不会因为滚动条出现整页翻面。 */
|
|
859
|
+
.dshsc-page { box-sizing: border-box; width: 100%; height: 100%; overflow: hidden;
|
|
823
860
|
padding: calc(28px + var(--dsh-frame-top-clearance, 0px)) clamp(24px, 4vw, 48px) 48px; }
|
|
824
861
|
.dshsc-page .dshsc-root { max-width: 980px; margin: 0 auto; }
|
|
825
862
|
.dshsc-page .dshsc-title { font-size: 20px; font-weight: 500; line-height: 28px; }
|
|
826
863
|
.dshsc-page .dshsc-sub { font-size: 13px; line-height: 20px; }
|
|
827
|
-
|
|
864
|
+
/* 高度取满挂载面:侧栏 main 是 100%,设置页是宿主 .options 的内容高。
|
|
865
|
+
宿主没给确定高度时(height:100% 解析为 auto)退化成外层滚动,不会把内容裁掉。 */
|
|
866
|
+
.dshsc-root { display: flex; flex-direction: column; gap: 14px; height: 100%; min-height: 0;
|
|
867
|
+
color: var(--dsw-alias-label-primary);
|
|
828
868
|
container-type: inline-size; container-name: dshsc; }
|
|
829
|
-
/*
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
.dshsc-
|
|
833
|
-
.dshsc-
|
|
869
|
+
/* 页头 / 工具栏 / 顶部提示条高度不参与压缩;tab 面板吃满剩余高度,
|
|
870
|
+
面板里的筛选条与说明固定,只有行区域(.dshsc-listscroll)自己出现滚动条。 */
|
|
871
|
+
.dshsc-root > .dshsc-head, .dshsc-root > .dshsc-toolbar,
|
|
872
|
+
.dshsc-root > .dshsc-banner, .dshsc-root > .dshsc-meta { flex: none; }
|
|
873
|
+
.dshsc-tabpane { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
874
|
+
.dshsc-tabpane > .dshsc-banner, .dshsc-tabpane > .dshsc-notice,
|
|
875
|
+
.dshsc-tabpane > .dshsc-listbar { flex: none; }
|
|
876
|
+
.dshsc-listscroll { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain;
|
|
877
|
+
scrollbar-gutter: stable; }
|
|
878
|
+
/* 页头:左标题块、右业务动作。动作组不靠固定断点换行——标题块的 flex-basis 用内容宽度
|
|
879
|
+
(auto),放得下就同排、放不下(约 630px 以下)动作组自然换到第二行并靠右。
|
|
880
|
+
这样窄面板(设置页 ~565px)会回到「动作在下、标识在上」,身份胶囊永远是完整的,
|
|
881
|
+
也不会像 900px 断点那样被滚动条/几像素窗口变化推过临界点。 */
|
|
882
|
+
.dshsc-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
|
|
883
|
+
.dshsc-heading { display: flex; align-items: flex-start; gap: 10px; min-width: 0; flex: 1 1 auto; }
|
|
834
884
|
.dshsc-heading-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
835
885
|
.dshsc-logo { flex: none; display: block; border-radius: 8px; }
|
|
836
886
|
/* 标题不参与收窄:窄面板里让身份胶囊先省略,标题本身不折行 */
|
|
837
887
|
.dshsc-title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; margin: 0; flex: none; white-space: nowrap; }
|
|
838
888
|
.dshsc-sub { font-size: 12px; line-height: 1.55; color: var(--dsw-alias-label-secondary); margin: 0; max-width: 64ch; }
|
|
839
889
|
.dshsc-spacer { flex: 1; }
|
|
840
|
-
.dshsc-head-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; }
|
|
841
|
-
/*
|
|
890
|
+
.dshsc-head-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; margin-left: auto; }
|
|
891
|
+
/* 标题行恒为一行:身份胶囊只在面板极窄(连标题行都放不下)时才省略,工具组不被挤走。
|
|
842
892
|
不用 wrap 是因为一 wrap 就会按「身份胶囊整段」的宽度断行,窄面板里会折成 2~3 行、
|
|
843
|
-
|
|
893
|
+
把页头撑高;行内换行交给外层页头(动作组下移),标识本身保持完整。 */
|
|
844
894
|
.dshsc-title-row { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; min-width: 0; }
|
|
845
895
|
|
|
846
896
|
/* 身份:按「安装时输入的包名」显示(@scope/name@version),等宽只读、不描边 */
|
|
@@ -1081,13 +1131,6 @@ var CSS = `
|
|
|
1081
1131
|
.dshsc-row .dshsc-actions { grid-column: 2; grid-row: 1 / span 2; }
|
|
1082
1132
|
.dshsc-row .dshsc-btn { padding: 0 5px; }
|
|
1083
1133
|
}
|
|
1084
|
-
/* 页头自己的断点,和上面的页面断点解耦:只有面板窄到放不下「标题块 + 动作组」才上下堆叠。
|
|
1085
|
-
520 远低于设置面板/侧栏面板的实际宽度,正常窗口里页头永远是右上角那一组。 */
|
|
1086
|
-
@container dshsc (max-width: 520px) {
|
|
1087
|
-
.dshsc-head { grid-template-columns: minmax(0, 1fr); }
|
|
1088
|
-
.dshsc-head-actions { justify-content: flex-end; }
|
|
1089
|
-
.dshsc-tools { border-left: 0; padding-left: 0; margin-left: 0; }
|
|
1090
|
-
}
|
|
1091
1134
|
.dshsc-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1092
1135
|
|
|
1093
1136
|
/* 与 DSH 自带开关同尺寸/同 token:关是 border-l3 轨道,开是 brand-primary 轨道 */
|
|
@@ -1319,8 +1362,11 @@ var CSS = `
|
|
|
1319
1362
|
.dshsc-md-table th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }
|
|
1320
1363
|
.dshsc-md-table tr:last-child td { border-bottom: 0; }
|
|
1321
1364
|
|
|
1322
|
-
/* 整页(A1):返回条 + hero + 目录 + 720px
|
|
1323
|
-
|
|
1365
|
+
/* 整页(A1):返回条 + hero + 目录 + 720px 正文。
|
|
1366
|
+
root 高度固定后,整页阅读器也自己撑满:顶栏 / hero 固定,正文列内部滚动,
|
|
1367
|
+
返回按钮不会跟着长文滚走。 */
|
|
1368
|
+
.dshsc-reader-page { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
1369
|
+
.dshsc-reader-page > .dshsc-reader-topbar, .dshsc-reader-page > .dshsc-reader-hero { flex: none; }
|
|
1324
1370
|
.dshsc-reader-topbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1325
1371
|
.dshsc-reader-topbar .dshsc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
|
1326
1372
|
box-sizing: border-box; height: 28px; padding: 0 10px; font-size: 12px; line-height: 1; white-space: nowrap; }
|
|
@@ -1340,7 +1386,8 @@ var CSS = `
|
|
|
1340
1386
|
background: var(--dsw-alias-bg-layer-1); }
|
|
1341
1387
|
.dshsc-reader-hero .dshsc-reader-head { border-bottom: 0; }
|
|
1342
1388
|
.dshsc-reader-hero .dshsc-reader-desc { -webkit-line-clamp: 3; max-width: 78ch; }
|
|
1343
|
-
.dshsc-reader-layout { display: grid; grid-template-columns: 180px minmax(0, 1fr);
|
|
1389
|
+
.dshsc-reader-layout { flex: 1; min-height: 0; display: grid; grid-template-columns: 180px minmax(0, 1fr);
|
|
1390
|
+
gap: 18px; align-items: stretch; }
|
|
1344
1391
|
.dshsc-reader-toc { position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px;
|
|
1345
1392
|
max-height: 70vh; overflow: auto; padding-right: 4px; }
|
|
1346
1393
|
.dshsc-reader-toc-title { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
|
|
@@ -1354,7 +1401,8 @@ var CSS = `
|
|
|
1354
1401
|
.dshsc-reader-tocitem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1355
1402
|
color: var(--dsw-alias-brand-primary); }
|
|
1356
1403
|
.dshsc-reader-tocitem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1357
|
-
.dshsc-reader-col { display: flex; flex-direction: column; min-width: 0;
|
|
1404
|
+
.dshsc-reader-col { display: flex; flex-direction: column; min-width: 0; min-height: 0;
|
|
1405
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
1358
1406
|
border-radius: 12px; background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1359
1407
|
.dshsc-reader-col .dshsc-reader-tabs { border-top: 0; }
|
|
1360
1408
|
.dshsc-reader-page .dshsc-reader-body { max-height: none; }
|
|
@@ -2203,17 +2251,35 @@ function InstalledTab({
|
|
|
2203
2251
|
) : null;
|
|
2204
2252
|
}
|
|
2205
2253
|
function content() {
|
|
2206
|
-
if (!data)
|
|
2254
|
+
if (!data) {
|
|
2255
|
+
return h(
|
|
2256
|
+
"div",
|
|
2257
|
+
{ className: "dshsc-tabpane" },
|
|
2258
|
+
h(
|
|
2259
|
+
"div",
|
|
2260
|
+
{ className: "dshsc-listscroll" },
|
|
2261
|
+
h("div", { className: "dshsc-empty" }, t("empty.loading"))
|
|
2262
|
+
)
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2207
2265
|
if (rows.length === 0) {
|
|
2208
2266
|
return h(
|
|
2209
2267
|
"div",
|
|
2210
|
-
{ className: "dshsc-
|
|
2211
|
-
h("div", { className: "dshsc-name" }, t("empty.skills")),
|
|
2212
|
-
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.skillsHint")),
|
|
2268
|
+
{ className: "dshsc-tabpane" },
|
|
2213
2269
|
h(
|
|
2214
2270
|
"div",
|
|
2215
|
-
{
|
|
2216
|
-
h(
|
|
2271
|
+
{ className: "dshsc-listscroll" },
|
|
2272
|
+
h(
|
|
2273
|
+
"div",
|
|
2274
|
+
{ className: "dshsc-empty" },
|
|
2275
|
+
h("div", { className: "dshsc-name" }, t("empty.skills")),
|
|
2276
|
+
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.skillsHint")),
|
|
2277
|
+
h(
|
|
2278
|
+
"div",
|
|
2279
|
+
{ style: { marginTop: 10 } },
|
|
2280
|
+
h("button", { className: "dshsc-btn primary", disabled: !writable, onClick: actions.onAddSkill }, t("action.addSkill"))
|
|
2281
|
+
)
|
|
2282
|
+
)
|
|
2217
2283
|
)
|
|
2218
2284
|
);
|
|
2219
2285
|
}
|
|
@@ -2231,11 +2297,15 @@ function InstalledTab({
|
|
|
2231
2297
|
))) : h("div", { className: "dshsc-list" }, visibleRows.map(rowView));
|
|
2232
2298
|
return h(
|
|
2233
2299
|
"div",
|
|
2234
|
-
{
|
|
2300
|
+
{ className: "dshsc-tabpane" },
|
|
2235
2301
|
visibleOnlyBanners(),
|
|
2236
2302
|
builtinNotice(),
|
|
2237
2303
|
listBar(),
|
|
2238
|
-
|
|
2304
|
+
h(
|
|
2305
|
+
"div",
|
|
2306
|
+
{ className: "dshsc-listscroll" },
|
|
2307
|
+
visibleRows.length === 0 ? filteredEmpty() : body
|
|
2308
|
+
)
|
|
2239
2309
|
);
|
|
2240
2310
|
}
|
|
2241
2311
|
return content();
|
|
@@ -2287,90 +2357,100 @@ function SubscriptionsTab({
|
|
|
2287
2357
|
if (subscriptions.length === 0) {
|
|
2288
2358
|
return h(
|
|
2289
2359
|
"div",
|
|
2290
|
-
{ className: "dshsc-
|
|
2291
|
-
h("div", { className: "dshsc-name" }, t("empty.subs")),
|
|
2292
|
-
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.subsHint")),
|
|
2360
|
+
{ className: "dshsc-tabpane" },
|
|
2293
2361
|
h(
|
|
2294
2362
|
"div",
|
|
2295
|
-
{
|
|
2296
|
-
h(
|
|
2363
|
+
{ className: "dshsc-listscroll" },
|
|
2364
|
+
h(
|
|
2365
|
+
"div",
|
|
2366
|
+
{ className: "dshsc-empty" },
|
|
2367
|
+
h("div", { className: "dshsc-name" }, t("empty.subs")),
|
|
2368
|
+
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.subsHint")),
|
|
2369
|
+
h(
|
|
2370
|
+
"div",
|
|
2371
|
+
{ style: { marginTop: 10 } },
|
|
2372
|
+
h("button", { className: "dshsc-btn primary", disabled: !writable, onClick: onAdd }, t("action.addSubscription"))
|
|
2373
|
+
)
|
|
2374
|
+
)
|
|
2297
2375
|
)
|
|
2298
2376
|
);
|
|
2299
2377
|
}
|
|
2300
|
-
return h(
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
return h(
|
|
2378
|
+
return h(
|
|
2379
|
+
"div",
|
|
2380
|
+
{ className: "dshsc-tabpane" },
|
|
2381
|
+
h(
|
|
2305
2382
|
"div",
|
|
2306
|
-
{ className: "dshsc-
|
|
2307
|
-
h(
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
className: "dshsc-caret",
|
|
2312
|
-
"aria-expanded": String(open),
|
|
2313
|
-
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
2314
|
-
onClick: () => onToggle(subscription.id)
|
|
2315
|
-
}, h(CaretIcon, { open })),
|
|
2316
|
-
h("span", { className: "dshsc-name" }, subscription.source),
|
|
2317
|
-
h("span", { className: "dshsc-tag" }, subscription.ref || t("subscription.defaultRef")),
|
|
2318
|
-
h(
|
|
2319
|
-
"span",
|
|
2320
|
-
{ className: "dshsc-substat" },
|
|
2321
|
-
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedNames.size}`
|
|
2322
|
-
),
|
|
2323
|
-
h(
|
|
2324
|
-
"div",
|
|
2325
|
-
{ className: "dshsc-actions" },
|
|
2326
|
-
h("button", {
|
|
2327
|
-
className: "dshsc-btn sq",
|
|
2328
|
-
disabled: busy !== null,
|
|
2329
|
-
title: t("action.checkUpdates"),
|
|
2330
|
-
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
2331
|
-
onClick: () => void onRefresh(subscription)
|
|
2332
|
-
}, h(RefreshIcon, { size: 13 })),
|
|
2333
|
-
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
2334
|
-
)
|
|
2335
|
-
),
|
|
2336
|
-
open ? h("div", { className: "dshsc-sub-list" }, entries.map((entry) => {
|
|
2337
|
-
const installedAlready = installedNames.has(entry.name);
|
|
2383
|
+
{ className: "dshsc-listscroll" },
|
|
2384
|
+
h("div", { className: "dshsc-list" }, subscriptions.map((subscription) => {
|
|
2385
|
+
const entries = subscription.snapshot?.entries || [];
|
|
2386
|
+
const installedNames = new Set(rows.filter((row) => row.subscriptionId === subscription.id).map((row) => row.name));
|
|
2387
|
+
const open = Boolean(expanded[subscription.id]);
|
|
2338
2388
|
return h(
|
|
2339
2389
|
"div",
|
|
2340
|
-
{ className: "dshsc-sub-
|
|
2341
|
-
h("span", { className: "dshsc-name" }, entry.name),
|
|
2342
|
-
h(
|
|
2343
|
-
"span",
|
|
2344
|
-
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
2345
|
-
installedAlready ? "✓ " : "",
|
|
2346
|
-
installedAlready ? t("installed") : t("notInstalled")
|
|
2347
|
-
),
|
|
2390
|
+
{ className: "dshsc-sub-block", key: subscription.id },
|
|
2348
2391
|
h(
|
|
2349
2392
|
"div",
|
|
2350
|
-
{ className: "dshsc-
|
|
2351
|
-
|
|
2352
|
-
className: "dshsc-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2393
|
+
{ className: "dshsc-sub-row" },
|
|
2394
|
+
h("button", {
|
|
2395
|
+
className: "dshsc-caret",
|
|
2396
|
+
"aria-expanded": String(open),
|
|
2397
|
+
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
2398
|
+
onClick: () => onToggle(subscription.id)
|
|
2399
|
+
}, h(CaretIcon, { open })),
|
|
2400
|
+
h("span", { className: "dshsc-name" }, subscription.source),
|
|
2401
|
+
h("span", { className: "dshsc-tag" }, subscription.ref || t("subscription.defaultRef")),
|
|
2402
|
+
h(
|
|
2403
|
+
"span",
|
|
2404
|
+
{ className: "dshsc-substat" },
|
|
2405
|
+
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedNames.size}`
|
|
2406
|
+
),
|
|
2407
|
+
h(
|
|
2408
|
+
"div",
|
|
2409
|
+
{ className: "dshsc-actions" },
|
|
2410
|
+
h("button", {
|
|
2411
|
+
className: "dshsc-btn sq",
|
|
2412
|
+
disabled: busy !== null,
|
|
2413
|
+
title: t("action.checkUpdates"),
|
|
2414
|
+
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
2415
|
+
onClick: () => void onRefresh(subscription)
|
|
2416
|
+
}, h(RefreshIcon, { size: 13 })),
|
|
2417
|
+
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
2418
|
+
)
|
|
2419
|
+
),
|
|
2420
|
+
open ? h("div", { className: "dshsc-sub-list" }, entries.map((entry) => {
|
|
2421
|
+
const installedAlready = installedNames.has(entry.name);
|
|
2422
|
+
return h(
|
|
2423
|
+
"div",
|
|
2424
|
+
{ className: "dshsc-sub-entry", key: entry.name },
|
|
2425
|
+
h("span", { className: "dshsc-name" }, entry.name),
|
|
2426
|
+
h(
|
|
2427
|
+
"span",
|
|
2428
|
+
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
2429
|
+
installedAlready ? "✓ " : "",
|
|
2430
|
+
installedAlready ? t("installed") : t("notInstalled")
|
|
2431
|
+
),
|
|
2432
|
+
h(
|
|
2433
|
+
"div",
|
|
2434
|
+
{ className: "dshsc-actions" },
|
|
2435
|
+
installedAlready ? null : h("button", {
|
|
2436
|
+
className: "dshsc-btn",
|
|
2437
|
+
disabled: !writable || busy !== null,
|
|
2438
|
+
onClick: () => void onInstall(subscription, entry)
|
|
2439
|
+
}, t("action.install"))
|
|
2440
|
+
)
|
|
2441
|
+
);
|
|
2442
|
+
})) : null
|
|
2357
2443
|
);
|
|
2358
|
-
}))
|
|
2359
|
-
)
|
|
2360
|
-
|
|
2444
|
+
}))
|
|
2445
|
+
)
|
|
2446
|
+
);
|
|
2361
2447
|
}
|
|
2362
2448
|
|
|
2363
2449
|
// client/views/modals.js
|
|
2364
2450
|
var import_react3 = __toESM(require("react"), 1);
|
|
2365
2451
|
function Overlay({ t, title, children, footer, onCancel }) {
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
if (event.key === "Escape") onCancel?.();
|
|
2369
|
-
};
|
|
2370
|
-
window.addEventListener("keydown", onKey);
|
|
2371
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
2372
|
-
}, [onCancel]);
|
|
2373
|
-
return h(
|
|
2452
|
+
useOverlayLayer(true, onCancel);
|
|
2453
|
+
return portalize(h(
|
|
2374
2454
|
"div",
|
|
2375
2455
|
{ className: "dshsc-overlay", onClick: (event) => {
|
|
2376
2456
|
if (event.target === event.currentTarget) onCancel?.();
|
|
@@ -2390,7 +2470,7 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
2390
2470
|
children,
|
|
2391
2471
|
h("div", { className: "dshsc-modal-foot" }, footer)
|
|
2392
2472
|
)
|
|
2393
|
-
);
|
|
2473
|
+
), overlayHost());
|
|
2394
2474
|
}
|
|
2395
2475
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
2396
2476
|
const [form, setForm] = import_react3.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
@@ -3133,20 +3213,21 @@ var MARKDOWN = /\.(md|markdown)$/i;
|
|
|
3133
3213
|
function isMarkdownPath(relPath) {
|
|
3134
3214
|
return MARKDOWN.test(String(relPath || ""));
|
|
3135
3215
|
}
|
|
3216
|
+
function isScroller(node) {
|
|
3217
|
+
if (!node || typeof getComputedStyle !== "function") return false;
|
|
3218
|
+
const style = getComputedStyle(node);
|
|
3219
|
+
return Boolean(style && /(auto|scroll)/.test(style.overflowY || "") && node.scrollHeight > node.clientHeight + 4);
|
|
3220
|
+
}
|
|
3136
3221
|
function nearestScroller(node) {
|
|
3137
3222
|
let current = node?.parentElement || null;
|
|
3138
3223
|
while (current) {
|
|
3139
|
-
if (
|
|
3140
|
-
const style = getComputedStyle(current);
|
|
3141
|
-
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight + 4) {
|
|
3142
|
-
return current;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3224
|
+
if (isScroller(current)) return current;
|
|
3145
3225
|
current = current.parentElement || null;
|
|
3146
3226
|
}
|
|
3147
3227
|
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3148
3228
|
}
|
|
3149
3229
|
function scrollHost(node) {
|
|
3230
|
+
if (isScroller(node)) return node;
|
|
3150
3231
|
return node && nearestScroller(node) || node;
|
|
3151
3232
|
}
|
|
3152
3233
|
var STATUS_KEY = {
|
|
@@ -3338,6 +3419,7 @@ function SkillReader({
|
|
|
3338
3419
|
const bodyRef = import_react4.default.useRef(null);
|
|
3339
3420
|
const dialogRef = import_react4.default.useRef(null);
|
|
3340
3421
|
const scrollRef = import_react4.default.useRef(0);
|
|
3422
|
+
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
3341
3423
|
const name = row?.name || "";
|
|
3342
3424
|
const tree = state.tree;
|
|
3343
3425
|
const files = tree?.files || [];
|
|
@@ -3364,11 +3446,6 @@ function SkillReader({
|
|
|
3364
3446
|
const node = dialogRef.current;
|
|
3365
3447
|
node?.focus?.();
|
|
3366
3448
|
const onKeyDown = (event) => {
|
|
3367
|
-
if (event?.key === "Escape") {
|
|
3368
|
-
event.stopPropagation?.();
|
|
3369
|
-
onClose?.();
|
|
3370
|
-
return;
|
|
3371
|
-
}
|
|
3372
3449
|
if (event?.key !== "Tab" || !node?.querySelectorAll) return;
|
|
3373
3450
|
const focusables = Array.from(
|
|
3374
3451
|
node.querySelectorAll('a[href],button:not([disabled]),[tabindex]:not([tabindex="-1"])')
|
|
@@ -3384,9 +3461,9 @@ function SkillReader({
|
|
|
3384
3461
|
first.focus?.();
|
|
3385
3462
|
}
|
|
3386
3463
|
};
|
|
3387
|
-
document.addEventListener("keydown", onKeyDown);
|
|
3464
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
3388
3465
|
return () => {
|
|
3389
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
3466
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
3390
3467
|
previous?.focus?.();
|
|
3391
3468
|
};
|
|
3392
3469
|
}, [variant, onClose]);
|
|
@@ -3811,7 +3888,7 @@ function SkillReader({
|
|
|
3811
3888
|
ref: dialogRef,
|
|
3812
3889
|
tabIndex: -1
|
|
3813
3890
|
}, header, tabsBar, body, actionsFooter)
|
|
3814
|
-
));
|
|
3891
|
+
), overlayHost());
|
|
3815
3892
|
}
|
|
3816
3893
|
|
|
3817
3894
|
// client/section.js
|
|
@@ -3828,6 +3905,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3828
3905
|
const [viewer, setViewer] = import_react5.default.useState(null);
|
|
3829
3906
|
const rootRef = import_react5.default.useRef(null);
|
|
3830
3907
|
const scrollPos = import_react5.default.useRef(null);
|
|
3908
|
+
const restoreScroll = import_react5.default.useRef(null);
|
|
3831
3909
|
const [filter, setFilter] = import_react5.default.useState("all");
|
|
3832
3910
|
const [builtinOpen, setBuiltinOpen] = import_react5.default.useState(false);
|
|
3833
3911
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
@@ -3875,30 +3953,36 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3875
3953
|
}
|
|
3876
3954
|
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3877
3955
|
}
|
|
3956
|
+
function listScroller() {
|
|
3957
|
+
const node = rootRef.current?.querySelector?.(".dshsc-listscroll");
|
|
3958
|
+
if (node && node.scrollHeight > node.clientHeight) return node;
|
|
3959
|
+
return getScrollParent(rootRef.current);
|
|
3960
|
+
}
|
|
3878
3961
|
function openViewer(row, mode = "drawer") {
|
|
3879
3962
|
if (!row) return;
|
|
3880
3963
|
if (mode === "page") {
|
|
3881
|
-
scrollPos.current =
|
|
3964
|
+
scrollPos.current = listScroller()?.scrollTop ?? 0;
|
|
3882
3965
|
}
|
|
3883
3966
|
setViewer({ name: row.name, mode });
|
|
3884
3967
|
}
|
|
3885
3968
|
function openPage() {
|
|
3886
3969
|
setViewer((prev) => {
|
|
3887
3970
|
if (!prev) return prev;
|
|
3888
|
-
scrollPos.current =
|
|
3971
|
+
scrollPos.current = listScroller()?.scrollTop ?? 0;
|
|
3889
3972
|
return { ...prev, mode: "page" };
|
|
3890
3973
|
});
|
|
3891
3974
|
}
|
|
3892
3975
|
function closeViewer() {
|
|
3893
|
-
|
|
3976
|
+
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
3894
3977
|
setViewer(null);
|
|
3895
|
-
if (restore === null || typeof window === "undefined") return;
|
|
3896
|
-
const apply2 = () => {
|
|
3897
|
-
const scroller = getScrollParent(rootRef.current);
|
|
3898
|
-
if (scroller) scroller.scrollTop = restore;
|
|
3899
|
-
};
|
|
3900
|
-
if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(apply2);
|
|
3901
3978
|
}
|
|
3979
|
+
import_react5.default.useLayoutEffect(() => {
|
|
3980
|
+
if (restoreScroll.current === null) return;
|
|
3981
|
+
const target = restoreScroll.current;
|
|
3982
|
+
restoreScroll.current = null;
|
|
3983
|
+
const scroller = listScroller();
|
|
3984
|
+
if (scroller) scroller.scrollTop = target;
|
|
3985
|
+
}, [viewer]);
|
|
3902
3986
|
async function changePlacement(next) {
|
|
3903
3987
|
if (next === placement || placementBusy) return;
|
|
3904
3988
|
setPlacementBusy(next);
|
|
@@ -4027,7 +4111,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4027
4111
|
h("style", null, CSS),
|
|
4028
4112
|
viewer?.mode === "page" ? null : listNode,
|
|
4029
4113
|
readerNode,
|
|
4030
|
-
toast ? h("div", { className: `dshsc-toast ${toast.kind}` }, toast.text) : null,
|
|
4114
|
+
toast ? portalize(h("div", { className: `dshsc-toast ${toast.kind}` }, toast.text), overlayHost()) : null,
|
|
4031
4115
|
modal ? h(Modals, {
|
|
4032
4116
|
t,
|
|
4033
4117
|
modal,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morehao/dsh-skills-console",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "统一管理本地技能的安装、更新、删除与接入,让同一份技能在多个 Agent 间复用。A skill manager for DeepSeek Harness: install, update, remove, link and toggle agent skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "morehao <morehao@qq.com>",
|