@morehao/dsh-skills-console 0.3.6 → 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 +203 -113
- 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
|
-
- **窄面板不跳版**:页头按内容自然换行——放得下时动作组在标题行右侧,放不下(约 630px 以下,如设置页)就整组换到下一行并靠右,插件名始终完整;列表按面板自身宽度在 860px
|
|
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,17 +852,29 @@ 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; }
|
|
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; }
|
|
829
878
|
/* 页头:左标题块、右业务动作。动作组不靠固定断点换行——标题块的 flex-basis 用内容宽度
|
|
830
879
|
(auto),放得下就同排、放不下(约 630px 以下)动作组自然换到第二行并靠右。
|
|
831
880
|
这样窄面板(设置页 ~565px)会回到「动作在下、标识在上」,身份胶囊永远是完整的,
|
|
@@ -1313,8 +1362,11 @@ var CSS = `
|
|
|
1313
1362
|
.dshsc-md-table th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }
|
|
1314
1363
|
.dshsc-md-table tr:last-child td { border-bottom: 0; }
|
|
1315
1364
|
|
|
1316
|
-
/* 整页(A1):返回条 + hero + 目录 + 720px
|
|
1317
|
-
|
|
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; }
|
|
1318
1370
|
.dshsc-reader-topbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1319
1371
|
.dshsc-reader-topbar .dshsc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
|
1320
1372
|
box-sizing: border-box; height: 28px; padding: 0 10px; font-size: 12px; line-height: 1; white-space: nowrap; }
|
|
@@ -1334,7 +1386,8 @@ var CSS = `
|
|
|
1334
1386
|
background: var(--dsw-alias-bg-layer-1); }
|
|
1335
1387
|
.dshsc-reader-hero .dshsc-reader-head { border-bottom: 0; }
|
|
1336
1388
|
.dshsc-reader-hero .dshsc-reader-desc { -webkit-line-clamp: 3; max-width: 78ch; }
|
|
1337
|
-
.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; }
|
|
1338
1391
|
.dshsc-reader-toc { position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px;
|
|
1339
1392
|
max-height: 70vh; overflow: auto; padding-right: 4px; }
|
|
1340
1393
|
.dshsc-reader-toc-title { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
|
|
@@ -1348,7 +1401,8 @@ var CSS = `
|
|
|
1348
1401
|
.dshsc-reader-tocitem.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
|
|
1349
1402
|
color: var(--dsw-alias-brand-primary); }
|
|
1350
1403
|
.dshsc-reader-tocitem:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1351
|
-
.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);
|
|
1352
1406
|
border-radius: 12px; background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1353
1407
|
.dshsc-reader-col .dshsc-reader-tabs { border-top: 0; }
|
|
1354
1408
|
.dshsc-reader-page .dshsc-reader-body { max-height: none; }
|
|
@@ -2197,17 +2251,35 @@ function InstalledTab({
|
|
|
2197
2251
|
) : null;
|
|
2198
2252
|
}
|
|
2199
2253
|
function content() {
|
|
2200
|
-
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
|
+
}
|
|
2201
2265
|
if (rows.length === 0) {
|
|
2202
2266
|
return h(
|
|
2203
2267
|
"div",
|
|
2204
|
-
{ className: "dshsc-
|
|
2205
|
-
h("div", { className: "dshsc-name" }, t("empty.skills")),
|
|
2206
|
-
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.skillsHint")),
|
|
2268
|
+
{ className: "dshsc-tabpane" },
|
|
2207
2269
|
h(
|
|
2208
2270
|
"div",
|
|
2209
|
-
{
|
|
2210
|
-
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
|
+
)
|
|
2211
2283
|
)
|
|
2212
2284
|
);
|
|
2213
2285
|
}
|
|
@@ -2225,11 +2297,15 @@ function InstalledTab({
|
|
|
2225
2297
|
))) : h("div", { className: "dshsc-list" }, visibleRows.map(rowView));
|
|
2226
2298
|
return h(
|
|
2227
2299
|
"div",
|
|
2228
|
-
{
|
|
2300
|
+
{ className: "dshsc-tabpane" },
|
|
2229
2301
|
visibleOnlyBanners(),
|
|
2230
2302
|
builtinNotice(),
|
|
2231
2303
|
listBar(),
|
|
2232
|
-
|
|
2304
|
+
h(
|
|
2305
|
+
"div",
|
|
2306
|
+
{ className: "dshsc-listscroll" },
|
|
2307
|
+
visibleRows.length === 0 ? filteredEmpty() : body
|
|
2308
|
+
)
|
|
2233
2309
|
);
|
|
2234
2310
|
}
|
|
2235
2311
|
return content();
|
|
@@ -2281,90 +2357,100 @@ function SubscriptionsTab({
|
|
|
2281
2357
|
if (subscriptions.length === 0) {
|
|
2282
2358
|
return h(
|
|
2283
2359
|
"div",
|
|
2284
|
-
{ className: "dshsc-
|
|
2285
|
-
h("div", { className: "dshsc-name" }, t("empty.subs")),
|
|
2286
|
-
h("div", { className: "dshsc-meta", style: { justifyContent: "center" } }, t("empty.subsHint")),
|
|
2360
|
+
{ className: "dshsc-tabpane" },
|
|
2287
2361
|
h(
|
|
2288
2362
|
"div",
|
|
2289
|
-
{
|
|
2290
|
-
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
|
+
)
|
|
2291
2375
|
)
|
|
2292
2376
|
);
|
|
2293
2377
|
}
|
|
2294
|
-
return h(
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
return h(
|
|
2378
|
+
return h(
|
|
2379
|
+
"div",
|
|
2380
|
+
{ className: "dshsc-tabpane" },
|
|
2381
|
+
h(
|
|
2299
2382
|
"div",
|
|
2300
|
-
{ className: "dshsc-
|
|
2301
|
-
h(
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
className: "dshsc-caret",
|
|
2306
|
-
"aria-expanded": String(open),
|
|
2307
|
-
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
2308
|
-
onClick: () => onToggle(subscription.id)
|
|
2309
|
-
}, h(CaretIcon, { open })),
|
|
2310
|
-
h("span", { className: "dshsc-name" }, subscription.source),
|
|
2311
|
-
h("span", { className: "dshsc-tag" }, subscription.ref || t("subscription.defaultRef")),
|
|
2312
|
-
h(
|
|
2313
|
-
"span",
|
|
2314
|
-
{ className: "dshsc-substat" },
|
|
2315
|
-
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedNames.size}`
|
|
2316
|
-
),
|
|
2317
|
-
h(
|
|
2318
|
-
"div",
|
|
2319
|
-
{ className: "dshsc-actions" },
|
|
2320
|
-
h("button", {
|
|
2321
|
-
className: "dshsc-btn sq",
|
|
2322
|
-
disabled: busy !== null,
|
|
2323
|
-
title: t("action.checkUpdates"),
|
|
2324
|
-
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
2325
|
-
onClick: () => void onRefresh(subscription)
|
|
2326
|
-
}, h(RefreshIcon, { size: 13 })),
|
|
2327
|
-
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
2328
|
-
)
|
|
2329
|
-
),
|
|
2330
|
-
open ? h("div", { className: "dshsc-sub-list" }, entries.map((entry) => {
|
|
2331
|
-
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]);
|
|
2332
2388
|
return h(
|
|
2333
2389
|
"div",
|
|
2334
|
-
{ className: "dshsc-sub-
|
|
2335
|
-
h("span", { className: "dshsc-name" }, entry.name),
|
|
2336
|
-
h(
|
|
2337
|
-
"span",
|
|
2338
|
-
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
2339
|
-
installedAlready ? "✓ " : "",
|
|
2340
|
-
installedAlready ? t("installed") : t("notInstalled")
|
|
2341
|
-
),
|
|
2390
|
+
{ className: "dshsc-sub-block", key: subscription.id },
|
|
2342
2391
|
h(
|
|
2343
2392
|
"div",
|
|
2344
|
-
{ className: "dshsc-
|
|
2345
|
-
|
|
2346
|
-
className: "dshsc-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
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
|
|
2351
2443
|
);
|
|
2352
|
-
}))
|
|
2353
|
-
)
|
|
2354
|
-
|
|
2444
|
+
}))
|
|
2445
|
+
)
|
|
2446
|
+
);
|
|
2355
2447
|
}
|
|
2356
2448
|
|
|
2357
2449
|
// client/views/modals.js
|
|
2358
2450
|
var import_react3 = __toESM(require("react"), 1);
|
|
2359
2451
|
function Overlay({ t, title, children, footer, onCancel }) {
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
if (event.key === "Escape") onCancel?.();
|
|
2363
|
-
};
|
|
2364
|
-
window.addEventListener("keydown", onKey);
|
|
2365
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
2366
|
-
}, [onCancel]);
|
|
2367
|
-
return h(
|
|
2452
|
+
useOverlayLayer(true, onCancel);
|
|
2453
|
+
return portalize(h(
|
|
2368
2454
|
"div",
|
|
2369
2455
|
{ className: "dshsc-overlay", onClick: (event) => {
|
|
2370
2456
|
if (event.target === event.currentTarget) onCancel?.();
|
|
@@ -2384,7 +2470,7 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
2384
2470
|
children,
|
|
2385
2471
|
h("div", { className: "dshsc-modal-foot" }, footer)
|
|
2386
2472
|
)
|
|
2387
|
-
);
|
|
2473
|
+
), overlayHost());
|
|
2388
2474
|
}
|
|
2389
2475
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
2390
2476
|
const [form, setForm] = import_react3.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
@@ -3127,20 +3213,21 @@ var MARKDOWN = /\.(md|markdown)$/i;
|
|
|
3127
3213
|
function isMarkdownPath(relPath) {
|
|
3128
3214
|
return MARKDOWN.test(String(relPath || ""));
|
|
3129
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
|
+
}
|
|
3130
3221
|
function nearestScroller(node) {
|
|
3131
3222
|
let current = node?.parentElement || null;
|
|
3132
3223
|
while (current) {
|
|
3133
|
-
if (
|
|
3134
|
-
const style = getComputedStyle(current);
|
|
3135
|
-
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight + 4) {
|
|
3136
|
-
return current;
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3224
|
+
if (isScroller(current)) return current;
|
|
3139
3225
|
current = current.parentElement || null;
|
|
3140
3226
|
}
|
|
3141
3227
|
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3142
3228
|
}
|
|
3143
3229
|
function scrollHost(node) {
|
|
3230
|
+
if (isScroller(node)) return node;
|
|
3144
3231
|
return node && nearestScroller(node) || node;
|
|
3145
3232
|
}
|
|
3146
3233
|
var STATUS_KEY = {
|
|
@@ -3332,6 +3419,7 @@ function SkillReader({
|
|
|
3332
3419
|
const bodyRef = import_react4.default.useRef(null);
|
|
3333
3420
|
const dialogRef = import_react4.default.useRef(null);
|
|
3334
3421
|
const scrollRef = import_react4.default.useRef(0);
|
|
3422
|
+
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
3335
3423
|
const name = row?.name || "";
|
|
3336
3424
|
const tree = state.tree;
|
|
3337
3425
|
const files = tree?.files || [];
|
|
@@ -3358,11 +3446,6 @@ function SkillReader({
|
|
|
3358
3446
|
const node = dialogRef.current;
|
|
3359
3447
|
node?.focus?.();
|
|
3360
3448
|
const onKeyDown = (event) => {
|
|
3361
|
-
if (event?.key === "Escape") {
|
|
3362
|
-
event.stopPropagation?.();
|
|
3363
|
-
onClose?.();
|
|
3364
|
-
return;
|
|
3365
|
-
}
|
|
3366
3449
|
if (event?.key !== "Tab" || !node?.querySelectorAll) return;
|
|
3367
3450
|
const focusables = Array.from(
|
|
3368
3451
|
node.querySelectorAll('a[href],button:not([disabled]),[tabindex]:not([tabindex="-1"])')
|
|
@@ -3378,9 +3461,9 @@ function SkillReader({
|
|
|
3378
3461
|
first.focus?.();
|
|
3379
3462
|
}
|
|
3380
3463
|
};
|
|
3381
|
-
document.addEventListener("keydown", onKeyDown);
|
|
3464
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
3382
3465
|
return () => {
|
|
3383
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
3466
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
3384
3467
|
previous?.focus?.();
|
|
3385
3468
|
};
|
|
3386
3469
|
}, [variant, onClose]);
|
|
@@ -3805,7 +3888,7 @@ function SkillReader({
|
|
|
3805
3888
|
ref: dialogRef,
|
|
3806
3889
|
tabIndex: -1
|
|
3807
3890
|
}, header, tabsBar, body, actionsFooter)
|
|
3808
|
-
));
|
|
3891
|
+
), overlayHost());
|
|
3809
3892
|
}
|
|
3810
3893
|
|
|
3811
3894
|
// client/section.js
|
|
@@ -3822,6 +3905,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3822
3905
|
const [viewer, setViewer] = import_react5.default.useState(null);
|
|
3823
3906
|
const rootRef = import_react5.default.useRef(null);
|
|
3824
3907
|
const scrollPos = import_react5.default.useRef(null);
|
|
3908
|
+
const restoreScroll = import_react5.default.useRef(null);
|
|
3825
3909
|
const [filter, setFilter] = import_react5.default.useState("all");
|
|
3826
3910
|
const [builtinOpen, setBuiltinOpen] = import_react5.default.useState(false);
|
|
3827
3911
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
@@ -3869,30 +3953,36 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3869
3953
|
}
|
|
3870
3954
|
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3871
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
|
+
}
|
|
3872
3961
|
function openViewer(row, mode = "drawer") {
|
|
3873
3962
|
if (!row) return;
|
|
3874
3963
|
if (mode === "page") {
|
|
3875
|
-
scrollPos.current =
|
|
3964
|
+
scrollPos.current = listScroller()?.scrollTop ?? 0;
|
|
3876
3965
|
}
|
|
3877
3966
|
setViewer({ name: row.name, mode });
|
|
3878
3967
|
}
|
|
3879
3968
|
function openPage() {
|
|
3880
3969
|
setViewer((prev) => {
|
|
3881
3970
|
if (!prev) return prev;
|
|
3882
|
-
scrollPos.current =
|
|
3971
|
+
scrollPos.current = listScroller()?.scrollTop ?? 0;
|
|
3883
3972
|
return { ...prev, mode: "page" };
|
|
3884
3973
|
});
|
|
3885
3974
|
}
|
|
3886
3975
|
function closeViewer() {
|
|
3887
|
-
|
|
3976
|
+
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
3888
3977
|
setViewer(null);
|
|
3889
|
-
if (restore === null || typeof window === "undefined") return;
|
|
3890
|
-
const apply2 = () => {
|
|
3891
|
-
const scroller = getScrollParent(rootRef.current);
|
|
3892
|
-
if (scroller) scroller.scrollTop = restore;
|
|
3893
|
-
};
|
|
3894
|
-
if (typeof window.requestAnimationFrame === "function") window.requestAnimationFrame(apply2);
|
|
3895
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]);
|
|
3896
3986
|
async function changePlacement(next) {
|
|
3897
3987
|
if (next === placement || placementBusy) return;
|
|
3898
3988
|
setPlacementBusy(next);
|
|
@@ -4021,7 +4111,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4021
4111
|
h("style", null, CSS),
|
|
4022
4112
|
viewer?.mode === "page" ? null : listNode,
|
|
4023
4113
|
readerNode,
|
|
4024
|
-
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,
|
|
4025
4115
|
modal ? h(Modals, {
|
|
4026
4116
|
t,
|
|
4027
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>",
|