dsh-plugin 0.1.0 → 1.0.0
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.en.md +52 -27
- package/README.md +52 -27
- package/client/client.js +2148 -229
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +396 -0
- package/lib/index.js +17 -8
- package/lib/services/install-types.js +11 -0
- package/lib/services/install.js +4 -0
- package/lib/services/installed-versions.js +51 -0
- package/lib/services/loader.js +98 -0
- package/lib/services/pending-restart.js +34 -0
- package/lib/services/preflight.js +94 -0
- package/lib/services/profile.js +88 -0
- package/lib/services/progress.js +50 -0
- package/lib/services/task-queue.js +417 -0
- package/lib/types/http/routes.d.ts +23 -0
- package/lib/types/index.d.ts +27 -0
- package/lib/types/services/install-types.d.ts +63 -0
- package/lib/types/services/install.d.ts +18 -0
- package/lib/types/services/installed-versions.d.ts +14 -0
- package/lib/types/services/loader.d.ts +43 -0
- package/lib/types/services/pending-restart.d.ts +13 -0
- package/lib/types/services/preflight.d.ts +6 -0
- package/lib/types/services/profile.d.ts +24 -0
- package/lib/types/services/progress.d.ts +17 -0
- package/lib/types/services/task-queue.d.ts +76 -0
- package/package.json +27 -6
- package/src/client/components/CatalogControls.tsx +104 -0
- package/src/client/components/CatalogHeader.tsx +78 -0
- package/src/client/components/CatalogList.tsx +75 -0
- package/src/client/components/CategoryTabs.tsx +35 -0
- package/src/client/components/Dropdown.tsx +80 -0
- package/src/client/components/NotificationsModal.tsx +230 -0
- package/src/client/components/PluginCard.tsx +104 -0
- package/src/client/components/PluginHubSection.tsx +339 -0
- package/src/client/components/ProgressView.tsx +23 -0
- package/src/client/components/icons.tsx +125 -0
- package/src/client/components/modals.tsx +365 -0
- package/src/client/hooks/useCatalog.ts +192 -0
- package/src/client/hooks/useTaskQueue.ts +405 -0
- package/src/client/index.tsx +8 -422
- package/src/client/lib/catalog.ts +204 -0
- package/src/client/lib/env.ts +16 -0
- package/src/client/lib/failures.ts +153 -0
- package/src/client/lib/format.ts +22 -0
- package/src/client/locales.ts +186 -18
- package/src/client/styles/Dropdown.module.css +123 -0
- package/src/client/styles/Header.module.css +432 -0
- package/src/client/styles/List.module.css +324 -0
- package/src/client/styles/Modal.module.css +965 -0
- package/src/client/styles/tokens.module.css +86 -0
- package/src/client/types.ts +80 -0
- package/src/server/http/routes.ts +397 -0
- package/src/server/index.ts +35 -0
- package/src/server/services/install-types.ts +63 -0
- package/src/server/services/install.ts +18 -0
- package/src/server/services/installed-versions.ts +65 -0
- package/src/server/services/loader.ts +110 -0
- package/src/server/services/pending-restart.ts +36 -0
- package/src/server/services/preflight.ts +97 -0
- package/src/server/services/profile.ts +89 -0
- package/src/server/services/progress.ts +48 -0
- package/src/server/services/task-queue.ts +450 -0
- package/src/client/Section.module.css +0 -569
package/client/client.js
CHANGED
|
@@ -5,93 +5,99 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
//#region \0dsh-css:src/client/Section.module.css.mjs
|
|
9
|
-
const css = ".XlLbKa_root{min-width:0;height:100%;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;gap:8px;display:flex}.XlLbKa_header{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:2px 2px 0;display:flex}.XlLbKa_brand{align-items:center;gap:10px;min-width:0;display:flex}.XlLbKa_brandText{flex-direction:column;gap:1px;min-width:0;display:flex}.XlLbKa_title{margin:0;font-size:14px;font-weight:600;line-height:20px}.XlLbKa_tagline{color:var(--dsw-alias-label-tertiary,#8b93a1);-webkit-line-clamp:2;-webkit-box-orient:vertical;margin:0;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.XlLbKa_controls{flex-shrink:0;align-items:center;gap:8px;display:flex}.XlLbKa_adBanner{color:#fff;cursor:pointer;background:linear-gradient(90deg,#4f46e5 0%,#7c3aed 100%);border:1px solid #8b5cf68c;border-radius:8px;align-items:center;gap:8px;padding:7px 12px;text-decoration:none;transition:filter .15s,box-shadow .15s;display:flex;box-shadow:0 2px 10px #4f46e547}.XlLbKa_adBanner:hover{filter:brightness(1.1);box-shadow:0 4px 16px #7c3aed6b}.XlLbKa_adBadge{letter-spacing:.04em;color:#fff;white-space:nowrap;background:#ffffff29;border:1px solid #ffffff8c;border-radius:4px;flex-shrink:0;padding:3px 6px;font-size:10px;font-weight:700;line-height:1}.XlLbKa_adText{text-overflow:ellipsis;white-space:nowrap;color:#fff;min-width:0;font-size:12px;line-height:18px;overflow:hidden}.XlLbKa_adArrow{color:#fff;flex-shrink:0;margin-left:auto;font-size:13px}.XlLbKa_search{width:190px;color:inherit;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:0 0;border-radius:6px;outline:none;padding:4px 9px;font-size:12px;line-height:18px;transition:border-color .12s}.XlLbKa_search::placeholder{color:var(--dsw-alias-label-tertiary,#8b93a1)}.XlLbKa_search:focus{border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_sort{color:var(--dsw-alias-label-secondary,#6b7280);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);cursor:pointer;background:0 0;border-radius:6px;outline:none;padding:4px 8px;font-size:12px;line-height:18px}.XlLbKa_sort:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_langBtn{color:var(--dsw-alias-label-secondary,#6b7280);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);cursor:pointer;background:0 0;border-radius:6px;flex-shrink:0;padding:4px 9px;font-size:12px;line-height:18px;transition:color .12s,border-color .12s}.XlLbKa_langBtn:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_openBtn{color:var(--dsw-alias-label-secondary,#6b7280);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);cursor:pointer;white-space:nowrap;background:0 0;border-radius:6px;flex-shrink:0;align-items:center;gap:4px;padding:3px 9px;font-size:12px;font-weight:500;line-height:20px;text-decoration:none;transition:color .12s,border-color .12s;display:inline-flex}.XlLbKa_openBtn:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_openArrow{font-size:12px;line-height:1}.XlLbKa_tabs{border-bottom:1px solid var(--dsw-alias-border-l1,#eceef1);flex-wrap:wrap;align-items:center;gap:6px;padding:2px 2px 8px;display:flex}.XlLbKa_tab,.XlLbKa_tabActive{cursor:pointer;border:1px solid #0000;border-radius:999px;flex-shrink:0;align-items:center;gap:6px;padding:3px 10px;font-size:12px;line-height:18px;transition:color .12s,background .12s,border-color .12s;display:inline-flex}.XlLbKa_tab{color:var(--dsw-alias-label-secondary,#6b7280);border-color:var(--dsw-alias-border-l2,#e5e7eb);background:0 0}.XlLbKa_tab:hover{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-interactive-bg-hover,#80808014);border-color:var(--dsw-alias-border-l1,#eceef1)}.XlLbKa_tabActive{color:#fff;background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_tabCount,.XlLbKa_tabCountActive{text-align:center;border-radius:999px;min-width:16px;padding:0 5px;font-size:10px;line-height:14px}.XlLbKa_tabCount{color:var(--dsw-alias-label-tertiary,#8b93a1);background:var(--dsw-alias-bg-layer-2,#8080801a)}.XlLbKa_tabActive .XlLbKa_tabCountActive{color:#fff;background:#ffffff38}.XlLbKa_body{flex-direction:column;flex:1;min-height:0;display:flex}.XlLbKa_list{flex-direction:column;flex:1;gap:6px;min-height:0;padding:2px 4px 4px 2px;display:flex;overflow-y:auto}.XlLbKa_card{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#8080800f);border-radius:8px;justify-content:space-between;align-items:stretch;gap:12px;padding:9px 12px;transition:border-color .12s,background .12s;display:flex}.XlLbKa_card:hover{border-color:var(--dsw-alias-brand-primary,#4f6ef7);background:var(--dsw-alias-bg-layer-2,#8080801a)}.XlLbKa_cardMain{flex-direction:column;gap:4px;min-width:0;display:flex}.XlLbKa_cardHead{align-items:center;gap:6px;min-width:0;display:flex}.XlLbKa_cardTitle{white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;line-height:18px;overflow:hidden}.XlLbKa_categoryBadge,.XlLbKa_verified{border:1px solid #0000;border-radius:4px;flex-shrink:0;padding:0 6px;font-size:10px;line-height:16px}.XlLbKa_categoryBadge{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-button-ghost-active-border,#4f6ef740)}.XlLbKa_verified{color:var(--dsw-alias-state-success-primary,#1a7f37);border-color:var(--dsw-alias-state-success-secondary,#1a7f374d)}.XlLbKa_desc{color:var(--dsw-alias-label-secondary,#6b7280);-webkit-line-clamp:2;-webkit-box-orient:vertical;margin:0;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.XlLbKa_topics{flex-wrap:wrap;align-items:center;gap:4px;min-width:0;display:flex}.XlLbKa_topic{color:var(--dsw-alias-label-tertiary,#8b93a1);background:var(--dsw-alias-bg-layer-2,#8080801a);white-space:nowrap;border-radius:4px;flex-shrink:0;padding:0 6px;font-size:10px;line-height:16px}.XlLbKa_cardSide{flex-direction:column;flex-shrink:0;justify-content:space-between;align-items:flex-end;gap:6px;display:flex}.XlLbKa_stats{flex-direction:column;align-items:flex-end;gap:1px;display:flex}.XlLbKa_star{color:var(--dsw-alias-state-warn-primary,#b8860b);white-space:nowrap;font-size:11px;line-height:16px}.XlLbKa_fork{color:var(--dsw-alias-label-tertiary,#8b93a1);white-space:nowrap;font-size:11px;line-height:16px}.XlLbKa_date{color:var(--dsw-alias-label-tertiary,#8b93a1);white-space:nowrap;font-size:10px;line-height:14px}.XlLbKa_installBtn,.XlLbKa_installBtnCopied,.XlLbKa_detailBtn{cursor:pointer;border-radius:6px;padding:2px 10px;font-size:11px;line-height:18px;transition:color .12s,border-color .12s,background .12s}.XlLbKa_installBtn{color:var(--dsw-alias-label-secondary,#6b7280);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:0 0}.XlLbKa_installBtn:hover{color:#4f6ef7;border-color:#4f6ef7}.XlLbKa_detailBtn{color:#4f6ef7;background:0 0;border:1px solid #4f6ef7;align-items:center;font-weight:500;text-decoration:none;display:inline-flex}.XlLbKa_detailBtn:hover{color:#fff;background:#4f6ef7}.XlLbKa_actions{align-items:center;gap:6px;display:flex}.XlLbKa_installBtnCopied{color:#fff;background:#4f6ef7;border:1px solid #4f6ef7}.XlLbKa_toast{z-index:1000;color:#f9fafb;pointer-events:none;white-space:nowrap;background:#111827;border:1px solid #ffffff1a;border-radius:8px;padding:10px 16px;font-size:12px;line-height:18px;animation:.22s ease-out XlLbKa_toastIn;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:0 6px 24px #00000038}@keyframes XlLbKa_toastIn{0%{opacity:0;transform:translate(-50%,-44%)}to{opacity:1;transform:translate(-50%,-50%)}}.XlLbKa_state{text-align:center;min-height:160px;color:var(--dsw-alias-label-tertiary,#8b93a1);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px;font-size:12px;line-height:18px;display:flex}.XlLbKa_stateTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;font-weight:600}.XlLbKa_stateDesc{max-width:420px}.XlLbKa_retryBtn{color:var(--dsw-alias-label-secondary,#6b7280);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);cursor:pointer;background:0 0;border-radius:6px;margin-top:4px;padding:4px 12px;font-size:12px;line-height:18px}.XlLbKa_retryBtn:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.XlLbKa_footer{border-top:1px solid var(--dsw-alias-border-l1,#eceef1);flex-shrink:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 4px 0;display:flex}.XlLbKa_footText{color:var(--dsw-alias-label-tertiary,#8b93a1);font-size:11px;line-height:16px}.XlLbKa_footLink{color:var(--dsw-alias-brand-primary,#4f6ef7);white-space:nowrap;font-size:11px;line-height:16px;text-decoration:none}.XlLbKa_footLink:hover{text-decoration:underline}";
|
|
10
|
-
const tagId = "dsh-plugin/Section.module.css";
|
|
11
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
12
|
-
const tag = document.createElement("style");
|
|
13
|
-
tag.dataset.plugin = "dsh-plugin";
|
|
14
|
-
tag.dataset.pluginCss = tagId;
|
|
15
|
-
tag.textContent = css;
|
|
16
|
-
document.head.appendChild(tag);
|
|
17
|
-
}
|
|
18
|
-
var Section_module_css_default = {
|
|
19
|
-
"date": "XlLbKa_date",
|
|
20
|
-
"controls": "XlLbKa_controls",
|
|
21
|
-
"sort": "XlLbKa_sort",
|
|
22
|
-
"categoryBadge": "XlLbKa_categoryBadge",
|
|
23
|
-
"detailBtn": "XlLbKa_detailBtn",
|
|
24
|
-
"card": "XlLbKa_card",
|
|
25
|
-
"desc": "XlLbKa_desc",
|
|
26
|
-
"footLink": "XlLbKa_footLink",
|
|
27
|
-
"adBadge": "XlLbKa_adBadge",
|
|
28
|
-
"adText": "XlLbKa_adText",
|
|
29
|
-
"topics": "XlLbKa_topics",
|
|
30
|
-
"actions": "XlLbKa_actions",
|
|
31
|
-
"root": "XlLbKa_root",
|
|
32
|
-
"brandText": "XlLbKa_brandText",
|
|
33
|
-
"list": "XlLbKa_list",
|
|
34
|
-
"installBtn": "XlLbKa_installBtn",
|
|
35
|
-
"retryBtn": "XlLbKa_retryBtn",
|
|
36
|
-
"cardSide": "XlLbKa_cardSide",
|
|
37
|
-
"state": "XlLbKa_state",
|
|
38
|
-
"stateDesc": "XlLbKa_stateDesc",
|
|
39
|
-
"brand": "XlLbKa_brand",
|
|
40
|
-
"tabs": "XlLbKa_tabs",
|
|
41
|
-
"header": "XlLbKa_header",
|
|
42
|
-
"cardMain": "XlLbKa_cardMain",
|
|
43
|
-
"body": "XlLbKa_body",
|
|
44
|
-
"search": "XlLbKa_search",
|
|
45
|
-
"tagline": "XlLbKa_tagline",
|
|
46
|
-
"adArrow": "XlLbKa_adArrow",
|
|
47
|
-
"cardHead": "XlLbKa_cardHead",
|
|
48
|
-
"title": "XlLbKa_title",
|
|
49
|
-
"openBtn": "XlLbKa_openBtn",
|
|
50
|
-
"openArrow": "XlLbKa_openArrow",
|
|
51
|
-
"verified": "XlLbKa_verified",
|
|
52
|
-
"tabCount": "XlLbKa_tabCount",
|
|
53
|
-
"topic": "XlLbKa_topic",
|
|
54
|
-
"stateTitle": "XlLbKa_stateTitle",
|
|
55
|
-
"footText": "XlLbKa_footText",
|
|
56
|
-
"fork": "XlLbKa_fork",
|
|
57
|
-
"langBtn": "XlLbKa_langBtn",
|
|
58
|
-
"installBtnCopied": "XlLbKa_installBtnCopied",
|
|
59
|
-
"toast": "XlLbKa_toast",
|
|
60
|
-
"toastIn": "XlLbKa_toastIn",
|
|
61
|
-
"adBanner": "XlLbKa_adBanner",
|
|
62
|
-
"cardTitle": "XlLbKa_cardTitle",
|
|
63
|
-
"stats": "XlLbKa_stats",
|
|
64
|
-
"star": "XlLbKa_star",
|
|
65
|
-
"footer": "XlLbKa_footer",
|
|
66
|
-
"tab": "XlLbKa_tab",
|
|
67
|
-
"tabCountActive": "XlLbKa_tabCountActive",
|
|
68
|
-
"tabActive": "XlLbKa_tabActive"
|
|
69
|
-
};
|
|
70
|
-
//#endregion
|
|
71
8
|
//#region src/client/locales.ts
|
|
72
9
|
/** zh/en dictionaries for the DSH-Plugin Hub settings section. */
|
|
73
10
|
const zh = {
|
|
74
11
|
nav: "插件中心",
|
|
75
|
-
title: "DSH-Plugin Hub
|
|
76
|
-
tagline: "DSH
|
|
12
|
+
title: "DSH-Plugin Hub",
|
|
13
|
+
tagline: "DSH-Plugin 插件中心:面向 DeepSeek Harness 插件收录平台,人工审验、来源可溯,免费安装",
|
|
77
14
|
adBadge: "推荐",
|
|
78
|
-
ad: "DSH-Plugin 插件中心:收录 {total}
|
|
15
|
+
ad: "DSH-Plugin 插件中心:收录 {total} 款插件,{verified} 款人工精选验证,每日更新",
|
|
79
16
|
search: "搜索插件名称、描述、标签…",
|
|
80
17
|
all: "全部",
|
|
81
18
|
sortStars: "Star 最多",
|
|
19
|
+
sortForks: "Fork 最多",
|
|
82
20
|
sortUpdated: "最近更新",
|
|
83
21
|
sortNewest: "最新收录",
|
|
84
|
-
open: "在浏览器中打开",
|
|
85
22
|
openHint: "打开 dsh-plugin.org",
|
|
86
|
-
|
|
87
|
-
toEn: "Switch to English",
|
|
23
|
+
toggleLangHint: "切换界面语言",
|
|
88
24
|
fork: "Fork",
|
|
89
25
|
loading: "正在加载插件数据…",
|
|
90
26
|
failed: "插件数据加载失败",
|
|
91
27
|
failedDesc: "请稍后重试,或点击右上角按钮在浏览器中打开插件中心。",
|
|
92
28
|
retry: "重试",
|
|
93
|
-
|
|
29
|
+
install: "安装",
|
|
94
30
|
toastCopied: "安装命令已复制,去 dsh 终端粘贴即可安装",
|
|
31
|
+
installed: "已安装",
|
|
32
|
+
notInstalled: "未安装",
|
|
33
|
+
filterAllHint: "显示全部插件",
|
|
34
|
+
filterInstalledHint: "筛选当前分类下的已安装插件",
|
|
35
|
+
filterNotInstalledHint: "筛选当前分类下的未安装插件",
|
|
36
|
+
filterInstalledNone: "当前分类下没有已安装插件",
|
|
37
|
+
filterNotInstalledNone: "当前分类下没有未安装插件",
|
|
38
|
+
uninstall: "卸载",
|
|
39
|
+
uninstalling: "卸载中…",
|
|
40
|
+
uninstallTitle: "确认卸载",
|
|
41
|
+
uninstallDesc: "将从当前环境卸载该插件。",
|
|
42
|
+
uninstallDone: "卸载成功",
|
|
43
|
+
uninstallFail: "卸载失败,请稍后重试",
|
|
44
|
+
uninstallResultTitle: "卸载完成",
|
|
45
|
+
uninstallResultDesc: "插件已从当前环境移除。",
|
|
46
|
+
confirmTitle: "确认安装",
|
|
47
|
+
confirmDesc: "安装前请前往插件仓库仔细阅读源码,确认可信后再安装",
|
|
48
|
+
confirmPlugin: "插件",
|
|
49
|
+
confirmSource: "来源",
|
|
50
|
+
confirmCommand: "安装命令",
|
|
51
|
+
copyInstallCommand: "复制安装命令",
|
|
52
|
+
copyCmdLabel: "复制",
|
|
53
|
+
copyUninstallCommand: "复制卸载命令",
|
|
54
|
+
installNow: "直接安装",
|
|
55
|
+
installing: "安装中…",
|
|
56
|
+
installDone: "安装成功",
|
|
57
|
+
installResultTitle: "安装完成",
|
|
58
|
+
installResultDesc: "插件已安装。",
|
|
59
|
+
confirmUpdateTitle: "确认更新",
|
|
60
|
+
updateNow: "直接更新",
|
|
61
|
+
updating: "更新中…",
|
|
62
|
+
queuedUpdateTitle: "已加入更新排队",
|
|
63
|
+
updateResultTitle: "更新完成",
|
|
64
|
+
updateResultDesc: "插件已更新到最新版本。",
|
|
65
|
+
installFail: "安装失败",
|
|
66
|
+
confirmCancel: "取消",
|
|
67
|
+
doneBtn: "完成",
|
|
68
|
+
restartNow: "立即重启",
|
|
69
|
+
restartLater: "稍后重启",
|
|
70
|
+
restarting: "正在重启…",
|
|
71
|
+
restartHint: "部分插件(如插件市场、顶层 bundle)需重启后才会生效。",
|
|
72
|
+
restartPendingHint: "安装成功,重启后生效",
|
|
73
|
+
restartPendingHintUninstall: "卸载成功,重启后移除",
|
|
74
|
+
sectionPendingRestart: "待重启",
|
|
75
|
+
sectionInProgress: "进行中",
|
|
76
|
+
queuedTitle: "已加入安装排队",
|
|
77
|
+
queuedUninstallTitle: "已加入卸载排队",
|
|
78
|
+
queuedHint: "已加入排队,前序任务完成后将自动开始,可关闭本窗口继续浏览。",
|
|
79
|
+
cancelTask: "取消任务",
|
|
80
|
+
cancelling: "正在取消…",
|
|
81
|
+
errorTitle: "操作失败",
|
|
82
|
+
errorTitleInstall: "安装失败",
|
|
83
|
+
errorTitleUninstall: "卸载失败",
|
|
84
|
+
errorPlugin: "插件",
|
|
85
|
+
errorClose: "知道了",
|
|
86
|
+
errorCopy: "复制错误信息",
|
|
87
|
+
errCopied: "错误信息已复制",
|
|
88
|
+
notifications: "通知",
|
|
89
|
+
notificationsBtn: "通知",
|
|
90
|
+
notificationsHint: "查看安装/卸载通知(含失败记录)",
|
|
91
|
+
notificationsDesc: "安装、卸载任务的成败都会记录在这里,即使错过提示也能随时回来查看。",
|
|
92
|
+
notificationsEmpty: "暂无通知记录",
|
|
93
|
+
notificationsClear: "清空通知",
|
|
94
|
+
failCopy: "复制完整错误信息",
|
|
95
|
+
failIssueHint: "带着错误日志去作者仓库一键提交 Issue(正文含官网收录链接)",
|
|
96
|
+
failIssueBig: "一键提交 BUG 到 GitHub Issue 为开源作贡献",
|
|
97
|
+
failPrepareHint: "该插件安装时的构建脚本执行失败(git 分发常缺失子模块或构建产物),属插件打包分发问题。请向作者仓库提交 Issue 反馈。",
|
|
98
|
+
failPackagingHint: "该插件不支持官方默认安装方式:其 git 分发缺少构建产物(package.json 声明的入口文件在仓库中不存在),说明作者未适配官方安装流程。请到作者仓库提交 Issue 反馈,请其提交构建产物或发布 npm 版。",
|
|
99
|
+
failIgnoredBuild: "该插件依赖了需要编译的原生模块(如 node-pty),pnpm 默认拦截这类构建脚本导致安装失败 —— 其他插件不受影响,属该插件的依赖/打包问题。建议向作者仓库一键反馈,请作者改用预编译版本(如 node-pty-prebuilt-multiarch)。",
|
|
100
|
+
reportIssue: "去插件仓库提交 Issue",
|
|
95
101
|
detail: "打开详情",
|
|
96
102
|
noResult: "没有找到匹配的插件",
|
|
97
103
|
noResultDesc: "换个关键词试试,或前往网站浏览全部插件。",
|
|
@@ -99,36 +105,110 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
99
105
|
daysAgo: "更新于 {days} 天前",
|
|
100
106
|
monthsAgo: "更新于 {months} 个月前",
|
|
101
107
|
yearsAgo: "更新于 {years} 年前",
|
|
102
|
-
pluginsTotal: "
|
|
103
|
-
|
|
108
|
+
pluginsTotal: "结果 {n} 条插件",
|
|
109
|
+
filterResults: "结果 {n} 条插件",
|
|
104
110
|
more: "还有 {n} 个插件,去网站查看全部",
|
|
105
111
|
browseAll: "浏览全部 {n} 个插件",
|
|
106
112
|
verified: "已验证",
|
|
113
|
+
version: "版本",
|
|
114
|
+
update: "可更新",
|
|
115
|
+
updateAvailable: "有更新",
|
|
116
|
+
updateAvailableHint: "检测到更新:有版本号的仓库比对版本号,无版本号的仓库比对仓库最近更新时间",
|
|
107
117
|
empty: "该分类暂无插件",
|
|
108
118
|
dataFrom: "数据源 dsh-plugin.org · 每日人工更新"
|
|
109
119
|
};
|
|
110
120
|
const en = {
|
|
111
121
|
nav: "Plugin Hub",
|
|
112
|
-
title: "DSH-Plugin Hub
|
|
113
|
-
tagline: "DSH
|
|
122
|
+
title: "DSH-Plugin Hub",
|
|
123
|
+
tagline: "DSH-Plugin Hub: a human-verified, free marketplace for DeepSeek Harness plugins",
|
|
114
124
|
adBadge: "Featured",
|
|
115
|
-
ad: "DSH-Plugin Hub: {total}
|
|
125
|
+
ad: "DSH-Plugin Hub: {total} plugins indexed, {verified} human-verified, updated daily",
|
|
116
126
|
search: "Search plugins by name, description, tags…",
|
|
117
127
|
all: "All",
|
|
118
128
|
sortStars: "Most stars",
|
|
129
|
+
sortForks: "Most forks",
|
|
119
130
|
sortUpdated: "Recently updated",
|
|
120
131
|
sortNewest: "Newest added",
|
|
121
|
-
open: "Open in browser",
|
|
122
132
|
openHint: "Open dsh-plugin.org",
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
toggleLangHint: "Switch language",
|
|
134
|
+
githubHint: "View source on GitHub",
|
|
125
135
|
fork: "Fork",
|
|
126
136
|
loading: "Loading plugin data…",
|
|
127
137
|
failed: "Failed to load plugin data",
|
|
128
138
|
failedDesc: "Please retry, or open the hub in your browser with the button above.",
|
|
129
139
|
retry: "Retry",
|
|
130
|
-
|
|
140
|
+
install: "Install",
|
|
131
141
|
toastCopied: "Install command copied — paste it in your dsh terminal",
|
|
142
|
+
installed: "Installed",
|
|
143
|
+
notInstalled: "Not installed",
|
|
144
|
+
filterAllHint: "Show all plugins",
|
|
145
|
+
filterInstalledHint: "Filter to installed plugins in this category",
|
|
146
|
+
filterNotInstalledHint: "Filter to uninstalled plugins in this category",
|
|
147
|
+
filterInstalledNone: "No installed plugins in this category",
|
|
148
|
+
filterNotInstalledNone: "No uninstalled plugins in this category",
|
|
149
|
+
uninstall: "Uninstall",
|
|
150
|
+
uninstalling: "Removing…",
|
|
151
|
+
uninstallTitle: "Confirm uninstall",
|
|
152
|
+
uninstallDesc: "The plugin will be removed from this environment.",
|
|
153
|
+
uninstallDone: "Uninstalled",
|
|
154
|
+
uninstallFail: "Removal failed — try again",
|
|
155
|
+
uninstallResultTitle: "Removed",
|
|
156
|
+
uninstallResultDesc: "The plugin has been removed from this environment.",
|
|
157
|
+
confirmTitle: "Confirm install",
|
|
158
|
+
confirmDesc: "Review the plugin repository source code carefully before installing.",
|
|
159
|
+
confirmPlugin: "Plugin",
|
|
160
|
+
confirmSource: "Source",
|
|
161
|
+
confirmCommand: "Command",
|
|
162
|
+
copyInstallCommand: "Copy install command",
|
|
163
|
+
copyCmdLabel: "Copy",
|
|
164
|
+
copyUninstallCommand: "Copy uninstall command",
|
|
165
|
+
installNow: "Install",
|
|
166
|
+
installing: "Installing…",
|
|
167
|
+
installDone: "Installed",
|
|
168
|
+
installResultTitle: "Installed",
|
|
169
|
+
installResultDesc: "The plugin has been installed.",
|
|
170
|
+
confirmUpdateTitle: "Confirm update",
|
|
171
|
+
updateNow: "Update",
|
|
172
|
+
updating: "Updating…",
|
|
173
|
+
queuedUpdateTitle: "Queued for update…",
|
|
174
|
+
updateResultTitle: "Updated",
|
|
175
|
+
updateResultDesc: "The plugin has been updated to the latest version.",
|
|
176
|
+
installFail: "Install failed — copy the command instead",
|
|
177
|
+
confirmCancel: "Cancel",
|
|
178
|
+
doneBtn: "Done",
|
|
179
|
+
restartNow: "Restart now",
|
|
180
|
+
restartLater: "Later",
|
|
181
|
+
restarting: "Restarting…",
|
|
182
|
+
restartHint: "Some plugins (such as plugin markets / top-level bundles) only take effect after a restart.",
|
|
183
|
+
restartPendingHint: "Installed — takes effect after restart",
|
|
184
|
+
restartPendingHintUninstall: "Removed — cleaned up after restart",
|
|
185
|
+
sectionPendingRestart: "Pending restart",
|
|
186
|
+
sectionInProgress: "In progress",
|
|
187
|
+
queuedTitle: "Queued…",
|
|
188
|
+
queuedUninstallTitle: "Queued for removal…",
|
|
189
|
+
queuedHint: "Queued — starts automatically after earlier tasks finish. You may close this window.",
|
|
190
|
+
cancelTask: "Cancel task",
|
|
191
|
+
cancelling: "Cancelling…",
|
|
192
|
+
errorTitle: "Operation failed",
|
|
193
|
+
errorTitleInstall: "Install failed",
|
|
194
|
+
errorTitleUninstall: "Removal failed",
|
|
195
|
+
errorPlugin: "Plugin",
|
|
196
|
+
errorClose: "Got it",
|
|
197
|
+
errorCopy: "Copy error",
|
|
198
|
+
errCopied: "Error copied",
|
|
199
|
+
notifications: "Notifications",
|
|
200
|
+
notificationsBtn: "Notice",
|
|
201
|
+
notificationsHint: "View install/remove notifications (including failures)",
|
|
202
|
+
notificationsDesc: "Every install and remove result is logged here — even if you missed the prompt, you can always check back.",
|
|
203
|
+
notificationsEmpty: "No notifications yet",
|
|
204
|
+
notificationsClear: "Clear notifications",
|
|
205
|
+
failCopy: "Copy full log",
|
|
206
|
+
failIssueHint: "Open a pre-filled issue on the author repo with this error log (includes catalog links)",
|
|
207
|
+
failIssueBig: "Report this bug to GitHub — contribute to open source",
|
|
208
|
+
failPrepareHint: "This plugin failed while running its build scripts during install (git tarballs often miss submodules or build output). This is a packaging issue of the plugin itself — please report it to the author repository.",
|
|
209
|
+
failPackagingHint: "This plugin does not support the official default install method: its git distribution lacks the build output (the entry file declared in package.json is not in the repository), so it has not been adapted to the official install flow. Please file an Issue on the author's repo asking for committed build output or an npm release.",
|
|
210
|
+
failIgnoredBuild: "This plugin depends on a native module (e.g. node-pty) whose build script pnpm blocks by default, so the install fails — other plugins are unaffected. This is a dependency/packaging issue of the plugin itself. Please report it to the author repository and ask the author to switch to a prebuilt variant (e.g. node-pty-prebuilt-multiarch).",
|
|
211
|
+
reportIssue: "Report an Issue to the plugin repo",
|
|
132
212
|
detail: "Open details",
|
|
133
213
|
noResult: "No plugins match your search",
|
|
134
214
|
noResultDesc: "Try another keyword, or browse all plugins on the website.",
|
|
@@ -136,27 +216,279 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
136
216
|
daysAgo: "updated {days}d ago",
|
|
137
217
|
monthsAgo: "updated {months}mo ago",
|
|
138
218
|
yearsAgo: "updated {years}y ago",
|
|
139
|
-
pluginsTotal: "{n} plugins",
|
|
140
|
-
|
|
219
|
+
pluginsTotal: "Results: {n} plugins",
|
|
220
|
+
filterResults: "Results: {n} plugins",
|
|
141
221
|
more: "{n} more on the website",
|
|
142
222
|
browseAll: "Browse all {n} plugins",
|
|
143
223
|
verified: "Verified",
|
|
224
|
+
version: "Version",
|
|
225
|
+
update: "Update",
|
|
226
|
+
updateAvailable: "Update available",
|
|
227
|
+
updateAvailableHint: "An update was detected: repos with a release are compared by tag, repos without one by last push time",
|
|
144
228
|
empty: "No plugins in this category yet",
|
|
145
229
|
dataFrom: "Data from dsh-plugin.org · curated daily"
|
|
146
230
|
};
|
|
147
231
|
//#endregion
|
|
148
|
-
//#region src/client/
|
|
232
|
+
//#region \0dsh-css:src/client/styles/Header.module.css.mjs
|
|
233
|
+
const css$4 = ".qikqja_root{min-width:0;height:100%;color:var(--hub-text-primary);flex-direction:column;gap:8px;display:flex}.qikqja_header{flex-direction:column;gap:2px;padding:2px 2px 0;display:flex}.qikqja_headerRight{flex-shrink:0;align-items:center;gap:2px;display:inline-flex}.qikqja_langBtn{height:24px;color:var(--hub-text-tertiary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;flex-shrink:0;justify-content:center;align-items:center;padding:0 8px;font-size:12px;font-weight:600;transition:color .15s,background-color .15s;display:inline-flex}.qikqja_langBtn:hover{color:var(--hub-purple-1);background-color:var(--hub-purple-tint)}.qikqja_githubLink{color:var(--hub-text-tertiary);cursor:pointer;border-radius:6px;flex-shrink:0;align-items:center;padding:3px;text-decoration:none;transition:color .15s,background-color .15s;display:inline-flex}.qikqja_githubLink:hover{color:var(--hub-purple-1);background-color:var(--hub-purple-tint)}.qikqja_githubIcon{flex-shrink:0;display:block}.qikqja_headerTitleRow{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.qikqja_brandTitle{min-width:0;color:inherit;cursor:pointer;align-items:center;gap:10px;text-decoration:none;display:flex}.qikqja_taglineLink{min-width:0;max-width:100%;color:inherit;cursor:pointer;align-self:flex-start;text-decoration:none}.qikqja_title{margin:0;font-size:14px;font-weight:600;line-height:20px}.qikqja_version{color:var(--hub-text-secondary)}.qikqja_logoIcon{flex-shrink:0;display:block}.qikqja_copyIcon{flex-shrink:0}.qikqja_tagline{color:var(--hub-text-tertiary);white-space:nowrap;text-overflow:ellipsis;margin:0;font-size:12px;line-height:18px;overflow:hidden}.qikqja_controls{flex-shrink:0;align-items:center;gap:4px;display:flex}.qikqja_filterResults{color:var(--hub-text-tertiary);white-space:nowrap;flex-shrink:0;align-items:baseline;font-size:12px;line-height:18px;display:inline-flex}.qikqja_resultCount{font-variant-numeric:tabular-nums;text-align:center;min-width:5ch}.qikqja_adBanner{border:1px solid var(--hub-purple-border);background:linear-gradient(90deg, var(--hub-purple-1) 0%, var(--hub-purple-2) 100%);color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;box-shadow:0 2px 10px var(--hub-purple-shadow);border-radius:8px;align-items:center;gap:8px;padding:7px 12px;text-decoration:none;transition:filter .15s,box-shadow .15s;display:flex}.qikqja_adBanner:hover{filter:brightness(1.1);box-shadow:0 4px 16px var(--hub-purple-shadow-strong)}.qikqja_adBadge{letter-spacing:.04em;color:#fff;white-space:nowrap;background:#ffffff29;border:1px solid #ffffff8c;border-radius:4px;flex-shrink:0;padding:3px 6px;font-size:10px;font-weight:700;line-height:1}.qikqja_adText{text-overflow:ellipsis;white-space:nowrap;color:#fff;min-width:0;font-size:12px;line-height:18px;overflow:hidden}.qikqja_adArrow{color:#fff;flex-shrink:0;margin-left:auto;font-size:13px}.qikqja_searchRow{padding:0 2px;display:flex}.qikqja_search{width:50%;color:inherit;border:1px solid var(--hub-border-2);background:0 0;border-radius:6px;outline:none;padding:4px 9px;font-size:12px;line-height:18px;transition:border-color .12s}.qikqja_search::placeholder{color:var(--hub-text-tertiary)}.qikqja_search:focus{border-color:var(--hub-brand)}.qikqja_installedBtn,.qikqja_installedBtnActive,.qikqja_installedBtnDisabled{cursor:pointer;-webkit-user-select:none;user-select:none;border:none;border-radius:6px;flex-shrink:0;align-items:center;gap:5px;height:24px;padding:0 8px;font-size:12px;line-height:22px;transition:color .12s,background .12s;display:inline-flex}.qikqja_installedBtn{color:var(--hub-text-primary);background:0 0}.qikqja_installedBtn:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}.qikqja_installedBtnDisabled,.qikqja_installedBtnDisabled:hover{color:var(--hub-text-disabled);cursor:not-allowed;background:0 0}.qikqja_installedBtnDisabled .qikqja_segCount{color:var(--hub-text-disabled);background:#8080800f}.qikqja_installedBtnActive{color:var(--hub-text-on-fill);background:var(--hub-btn-fill)}.qikqja_segCount,.qikqja_segCountActive{text-align:center;border-radius:999px;min-width:14px;padding:0 4px;font-size:10px;line-height:14px}.qikqja_segCount{color:var(--hub-text-tertiary);background:var(--hub-bg-btn)}.qikqja_installedBtnActive .qikqja_segCountActive{color:var(--hub-text-on-fill);background:var(--hub-bg-on-fill)}.qikqja_tabs{border-bottom:1px solid var(--hub-border-1);flex-wrap:wrap;align-items:center;gap:6px;padding:2px 2px 8px;display:flex}.qikqja_tab,.qikqja_tabActive{cursor:pointer;-webkit-user-select:none;user-select:none;border:none;border-radius:999px;flex-shrink:0;align-items:center;gap:6px;padding:3px 10px;font-size:12px;line-height:18px;transition:color .12s,background .12s;display:inline-flex}.qikqja_tab{color:var(--hub-text-secondary);background:0 0}.qikqja_tab:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}.qikqja_tabActive{color:var(--hub-text-on-fill);background:var(--hub-btn-fill)}.qikqja_tabCount{text-align:center;min-width:16px;color:var(--hub-text-tertiary);background:var(--hub-bg-btn);border-radius:999px;padding:0 5px;font-size:10px;line-height:14px}.qikqja_tabActive .qikqja_tabCount{color:var(--hub-text-on-fill);background:var(--hub-bg-on-fill)}.qikqja_failBtn{box-sizing:border-box;border:1px solid var(--hub-border-2);height:24px;color:var(--hub-text-primary);background:var(--hub-bg-hover);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:6px;flex-shrink:0;align-items:center;margin-left:auto;padding:0 10px;font-size:12px;line-height:22px;transition:color .12s,background .12s;display:inline-flex;position:relative}.qikqja_failBtn:hover{color:var(--hub-text-primary);background:#80808047}.qikqja_failBadge{z-index:1;text-align:center;color:#fff;background:var(--hub-danger);border-radius:999px;min-width:16px;height:16px;padding:0 5px;font-size:10px;font-weight:600;line-height:16px;position:absolute;top:-16px;right:-7px;box-shadow:0 1px 4px #d1242f66}";
|
|
234
|
+
const tagId$4 = "dsh-plugin/Header.module.css";
|
|
235
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
236
|
+
const tag = document.createElement("style");
|
|
237
|
+
tag.dataset.plugin = "dsh-plugin";
|
|
238
|
+
tag.dataset.pluginCss = tagId$4;
|
|
239
|
+
tag.textContent = css$4;
|
|
240
|
+
document.head.appendChild(tag);
|
|
241
|
+
}
|
|
242
|
+
var Header_module_css_default = {
|
|
243
|
+
"tab": "qikqja_tab",
|
|
244
|
+
"adText": "qikqja_adText",
|
|
245
|
+
"failBtn": "qikqja_failBtn",
|
|
246
|
+
"root": "qikqja_root",
|
|
247
|
+
"header": "qikqja_header",
|
|
248
|
+
"githubIcon": "qikqja_githubIcon",
|
|
249
|
+
"search": "qikqja_search",
|
|
250
|
+
"title": "qikqja_title",
|
|
251
|
+
"segCount": "qikqja_segCount",
|
|
252
|
+
"logoIcon": "qikqja_logoIcon",
|
|
253
|
+
"installedBtnDisabled": "qikqja_installedBtnDisabled",
|
|
254
|
+
"failBadge": "qikqja_failBadge",
|
|
255
|
+
"tagline": "qikqja_tagline",
|
|
256
|
+
"headerTitleRow": "qikqja_headerTitleRow",
|
|
257
|
+
"version": "qikqja_version",
|
|
258
|
+
"headerRight": "qikqja_headerRight",
|
|
259
|
+
"controls": "qikqja_controls",
|
|
260
|
+
"taglineLink": "qikqja_taglineLink",
|
|
261
|
+
"copyIcon": "qikqja_copyIcon",
|
|
262
|
+
"adArrow": "qikqja_adArrow",
|
|
263
|
+
"installedBtnActive": "qikqja_installedBtnActive",
|
|
264
|
+
"adBadge": "qikqja_adBadge",
|
|
265
|
+
"tabs": "qikqja_tabs",
|
|
266
|
+
"githubLink": "qikqja_githubLink",
|
|
267
|
+
"brandTitle": "qikqja_brandTitle",
|
|
268
|
+
"adBanner": "qikqja_adBanner",
|
|
269
|
+
"filterResults": "qikqja_filterResults",
|
|
270
|
+
"resultCount": "qikqja_resultCount",
|
|
271
|
+
"searchRow": "qikqja_searchRow",
|
|
272
|
+
"segCountActive": "qikqja_segCountActive",
|
|
273
|
+
"installedBtn": "qikqja_installedBtn",
|
|
274
|
+
"langBtn": "qikqja_langBtn",
|
|
275
|
+
"tabActive": "qikqja_tabActive",
|
|
276
|
+
"tabCount": "qikqja_tabCount"
|
|
277
|
+
};
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/client/lib/failures.ts
|
|
280
|
+
const KEY = "gro.ngilp-hsd.failure-records";
|
|
281
|
+
const MAX = 50;
|
|
149
282
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
283
|
+
* 失败归类,三态。无论底层机制如何(pnpm 白名单拦截 / 构建脚本被忽略 / prepare 失败),
|
|
284
|
+
* 对用户而言结果都一样 —— 用官方默认安装方式装不上,就是插件仓库的问题,一律引导提 Issue:
|
|
285
|
+
* - pnpmIgnoredBuild:插件依赖里的原生模块构建脚本被 pnpm 默认拦截(如 node-pty,
|
|
286
|
+
* `ERR_PNPM_IGNORED_BUILDS`)。只影响带原生模块的插件,其他插件不受影响 —— 差异在
|
|
287
|
+
* 插件的依赖选择,属插件依赖/打包问题 → 引导去仓库提 Issue(建议改用预编译版本)
|
|
288
|
+
* - pluginPrepare:插件的 prepare/构建脚本实际执行失败(git tarball 常因缺失子模块或
|
|
289
|
+
* 构建产物导致)—— 属插件打包/分发问题,应引导去仓库提 Issue
|
|
290
|
+
* - repo:其余失败(含 git prepare 被 pnpm 白名单拦截等),默认按插件仓库问题引导提 Issue
|
|
157
291
|
*/
|
|
158
|
-
|
|
292
|
+
function classifyFailure(message) {
|
|
293
|
+
if (/\[packaging\]|entry file missing/i.test(message)) return "pluginPrepare";
|
|
294
|
+
if (/ERR_PNPM_IGNORED_BUILDS|Ignored build scripts:/i.test(message)) return "pnpmIgnoredBuild";
|
|
295
|
+
if (/ERR_PNPM_PREPARE_PACKAGE|ELIFECYCLE|Command failed|prepare-guard/i.test(message)) return "pluginPrepare";
|
|
296
|
+
return "repo";
|
|
297
|
+
}
|
|
298
|
+
/** 核心行特征:错误代码 / 生命周期脚本失败 / prepare 失败 / 描述性报错(子模块缺失、找不到等)/ 退出与宿主提示信息。 */
|
|
299
|
+
const CORE_LINE_RE = /ERR_[A-Z_]+|ELIFECYCLE|Command failed|prepare-guard|Failed to prepare|exit code|\bprepare\b|pnpm failed in profile|git-hosted plugins build|submodule|not found|cannot find|no such|unable to|fatal|missing|error/i;
|
|
300
|
+
/** 提交 issue 时正文里错误摘要的上限字符数。GitHub 请求行上限 8192 字节,
|
|
301
|
+
* 固定模板与 URL 编码开销约 1~2K,核心错误(以 ASCII 日志为主)可安全带到 ~5K;
|
|
302
|
+
* 仍超长时 pluginIssueUrl 会逐档缩小核心预算,最终 URL 不会超限。 */
|
|
303
|
+
const MAX_CORE_CHARS = 5e3;
|
|
304
|
+
/** 摘要里单行上限:允许构建 key 等长行也被截短。 */
|
|
305
|
+
const MAX_LINE_CHARS = 400;
|
|
306
|
+
/**
|
|
307
|
+
* 核心错误收集器:从完整安装输出里挑出真正说明问题的行(错误代码、构建脚本失败、
|
|
308
|
+
* 退出与宿主提示),去重后拼接,单行与总量都截断 —— 只把「重点 + 原因」带进 issue 正文,
|
|
309
|
+
* 避免完整日志塞进 URL 导致请求过长。无关键行时退化为「头部 + 尾部」快照。
|
|
310
|
+
* maxChars 可由调用方按最终 URL 长度收紧(pluginIssueUrl 超限时逐档缩小)。
|
|
311
|
+
*/
|
|
312
|
+
function summarizeError(message, maxChars = MAX_CORE_CHARS) {
|
|
313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
314
|
+
const core = [];
|
|
315
|
+
for (const raw of message.split(/\r?\n/)) {
|
|
316
|
+
const line = raw.trim();
|
|
317
|
+
if (!line || !CORE_LINE_RE.test(line)) continue;
|
|
318
|
+
const short = line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS)}…` : line;
|
|
319
|
+
if (!seen.has(short)) {
|
|
320
|
+
seen.add(short);
|
|
321
|
+
core.push(short);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
let out;
|
|
325
|
+
if (core.length === 0) {
|
|
326
|
+
const tail = message.trimEnd().slice(-1e3);
|
|
327
|
+
const sep = "\n…\n";
|
|
328
|
+
const headBudget = Math.max(maxChars - tail.length - 3, 0);
|
|
329
|
+
const head = message.slice(0, 500).trim();
|
|
330
|
+
out = `${head.length > headBudget ? head.slice(0, headBudget) : head}${sep}${tail}`;
|
|
331
|
+
} else out = core.join("\n");
|
|
332
|
+
return out.length > maxChars ? `${out.slice(0, maxChars)}\n… (truncated)` : out;
|
|
333
|
+
}
|
|
334
|
+
/** 提取首个错误代码(如 ERR_PNPM_PREPARE_PACKAGE),无则 null。 */
|
|
335
|
+
function coreErrorCode(message) {
|
|
336
|
+
const m = message.match(/\[?ERR_[A-Z_]+\]?/);
|
|
337
|
+
return m ? m[0].replace(/^\[|\]$/g, "") : null;
|
|
338
|
+
}
|
|
339
|
+
/** 运行时 localStorage 访问:类型上不依赖 DOM lib(Node 测试环境也能编译),浏览器里取 window。 */
|
|
340
|
+
const storage = () => globalThis.localStorage;
|
|
341
|
+
/** 读取本地通知记录(损坏/不可用时返回空列表,不抛错)。列表最新在前,全部保留,只由「清空」按钮手动移除。 */
|
|
342
|
+
function loadNotifications() {
|
|
343
|
+
try {
|
|
344
|
+
const raw = storage()?.getItem(KEY);
|
|
345
|
+
if (!raw) return [];
|
|
346
|
+
const list = JSON.parse(raw);
|
|
347
|
+
if (!Array.isArray(list)) return [];
|
|
348
|
+
return list.filter((r) => !!r && typeof r === "object" && typeof r.message === "string").map((r) => ({
|
|
349
|
+
...r,
|
|
350
|
+
ok: r.ok === true
|
|
351
|
+
}));
|
|
352
|
+
} catch {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function save(list) {
|
|
357
|
+
try {
|
|
358
|
+
storage()?.setItem(KEY, JSON.stringify(list));
|
|
359
|
+
} catch {}
|
|
360
|
+
}
|
|
361
|
+
/** 追加一条通知记录并持久化,返回更新后的完整列表(最新在前,超上限裁剪)。
|
|
362
|
+
* 每次成功/失败都各自留痕:同一插件的安装/卸载记录都完整保留、只由「清空」按钮手动移除,
|
|
363
|
+
* 不会因后续操作被自动覆盖清除。 */
|
|
364
|
+
function addNotification(record) {
|
|
365
|
+
const prev = loadNotifications();
|
|
366
|
+
let id = Date.now();
|
|
367
|
+
while (prev.some((r) => r.id === id)) id += 1;
|
|
368
|
+
const next = [{
|
|
369
|
+
...record,
|
|
370
|
+
id,
|
|
371
|
+
at: id
|
|
372
|
+
}, ...prev].slice(0, MAX);
|
|
373
|
+
save(next);
|
|
374
|
+
return next;
|
|
375
|
+
}
|
|
376
|
+
/** 记录一次失败:携带完整错误日志,供通知中心查看/复制/提 Issue。 */
|
|
377
|
+
function addFailure(record) {
|
|
378
|
+
return addNotification({
|
|
379
|
+
...record,
|
|
380
|
+
ok: false
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/** 记录一次成功:轻量记录,不带日志。 */
|
|
384
|
+
function addSuccess(record) {
|
|
385
|
+
return addNotification({
|
|
386
|
+
...record,
|
|
387
|
+
ok: true,
|
|
388
|
+
message: ""
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/** 清空全部通知记录,返回空列表。 */
|
|
392
|
+
function clearNotifications() {
|
|
393
|
+
save([]);
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/client/lib/catalog.ts
|
|
398
|
+
/** 插件中心官网地址。 */
|
|
159
399
|
const SITE_URL = "https://dsh-plugin.org/";
|
|
400
|
+
/** 插件中心源码仓库:头部右上角 GitHub 图标的跳转地址 */
|
|
401
|
+
const GITHUB_URL = "https://github.com/dshplugin/dsh-plugin-hub";
|
|
402
|
+
/**
|
|
403
|
+
* 插件当前版本号,供头部标题展示「DSH-Plugin Hub v0.1.1」。
|
|
404
|
+
* tsdown 构建时用 define 把 __PLUGIN_VERSION__ 替换成 package.json 的版本号;
|
|
405
|
+
* node --test 直接 import 本模块时该标识符不存在,typeof 守卫兜底为空串。
|
|
406
|
+
*/
|
|
407
|
+
const PLUGIN_VERSION = "1.0.0";
|
|
408
|
+
/** dsh-plugin.org 中文页挂在 /zh/ 前缀下,英文在根路径。 */
|
|
409
|
+
function langPathOf(lang) {
|
|
410
|
+
return lang === "zh" ? "zh/" : "";
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 官网详情页两级路径:/plugins/{ownerSlug}/{slug};
|
|
414
|
+
* 旧数据缺 ownerSlug 时从 repo 推导(卡片详情按钮与弹窗来源行共用)。
|
|
415
|
+
*/
|
|
416
|
+
function pluginDetailUrl(plugin, langPath) {
|
|
417
|
+
const repo = plugin.source?.repo ?? "";
|
|
418
|
+
return `${SITE_URL}${langPath}plugins/${plugin.ownerSlug ?? repo.split("/")[0]?.toLowerCase() ?? ""}/${plugin.slug}`;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* 失败弹窗里仓库地址的跳转目标:官网详情页(含插件收录信息),
|
|
422
|
+
* 与 issue 正文的 Catalog 链接同一形式(单级 /plugins/{repo})。
|
|
423
|
+
*/
|
|
424
|
+
function pluginSiteUrl(repo) {
|
|
425
|
+
return `${SITE_URL}plugins/${repo}`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 一键反馈 GitHub Issue 的预填链接:标题带「来自 dsh-plugin.org」标识,正文只带
|
|
429
|
+
* 核心信息 —— 原因判定 + 关键错误代码 + 宿主机器环境快照 + 错误核心摘要(完整日志
|
|
430
|
+
* 太长,塞进 URL 会被 GitHub 以「request URL too long」拒绝,故只收集重点)。
|
|
431
|
+
* 错误弹窗、失败记录共用此逻辑。
|
|
432
|
+
* env 取不到时为 null,链接照常生成、只是少环境段。
|
|
433
|
+
*/
|
|
434
|
+
function pluginIssueUrl(repo, message, env) {
|
|
435
|
+
const title = `[dsh-plugin.org | dsh-plugin-hub] Install/Remove failed: ${repo}`;
|
|
436
|
+
const kind = classifyFailure(message);
|
|
437
|
+
const reason = /\[packaging\]/i.test(message) ? "plugin does NOT support the official default install method (dsh plugin add github:owner/repo — requires build output committed to the repo or a prepare script); its git distribution lacks the entry file declared in package.json" : kind === "pluginPrepare" ? "plugin prepare/build script failed during install (packaging/distribution issue)" : kind === "pnpmIgnoredBuild" ? "plugin depends on a native module whose build script pnpm blocks by default (use a prebuilt variant)" : "plugin-side install failure";
|
|
438
|
+
const code = coreErrorCode(message);
|
|
439
|
+
const build = (coreChars) => {
|
|
440
|
+
const body = [
|
|
441
|
+
"## Summary",
|
|
442
|
+
`- Cause: ${reason}`,
|
|
443
|
+
...code ? [`- Key error: \`${code}\``] : [],
|
|
444
|
+
"",
|
|
445
|
+
`## [DSH-Plugin 插件中心](${SITE_URL}) · 安装 Plugin 失败错误信息`,
|
|
446
|
+
`本错误信息由 [dsh-plugin-hub](${GITHUB_URL}) 插件中心的安装程序自动生成,随本次安装失败一并提交。`,
|
|
447
|
+
`- 使用的安装命令(官方默认安装方式):\`dsh plugin${env?.profile ? ` --profile ${env.profile}` : ""} add github:${repo}\``,
|
|
448
|
+
`- 执行结果:安装失败,未能安装该插件。`,
|
|
449
|
+
.../\[packaging\]/i.test(message) ? [
|
|
450
|
+
"",
|
|
451
|
+
"## 官方默认安装方式",
|
|
452
|
+
"DSH 生态的官方默认安装方式是 `dsh plugin add github:owner/repo`(git 直装):插件仓库需提交构建产物,或在 package.json 提供 `prepare` 脚本让 pnpm 安装时自动构建。当前插件两者皆不具备,因此无法按官方方式安装。"
|
|
453
|
+
] : [],
|
|
454
|
+
"",
|
|
455
|
+
"## Environment",
|
|
456
|
+
`- Plugin: \`${repo}\``,
|
|
457
|
+
...env ? [
|
|
458
|
+
`- DSH: ${env.dshVersion ? `v${env.dshVersion}` : "unknown"}`,
|
|
459
|
+
`- Plugin Hub: v${PLUGIN_VERSION}`,
|
|
460
|
+
`- Node: ${env.nodeVersion}`,
|
|
461
|
+
`- OS: ${env.platform} ${env.arch} (${env.release})`,
|
|
462
|
+
`- Profile: \`${env.profile}\``,
|
|
463
|
+
`- DSH Home: \`${env.dshHome}\``
|
|
464
|
+
] : [],
|
|
465
|
+
`- Catalog: [${pluginSiteUrl(repo)}](${pluginSiteUrl(repo)})`,
|
|
466
|
+
"",
|
|
467
|
+
"## Error (core)",
|
|
468
|
+
"",
|
|
469
|
+
"```",
|
|
470
|
+
summarizeError(message, coreChars),
|
|
471
|
+
"```",
|
|
472
|
+
"",
|
|
473
|
+
"---",
|
|
474
|
+
`This error was produced by the [dsh-plugin-hub](${GITHUB_URL}) installer bundled with DeepSeek Harness.`
|
|
475
|
+
].join("\n");
|
|
476
|
+
return `https://github.com/${repo}/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
|
477
|
+
};
|
|
478
|
+
const MAX_URL_CHARS = 7600;
|
|
479
|
+
let url = build(MAX_CORE_CHARS);
|
|
480
|
+
if (url.length > MAX_URL_CHARS) for (const budget of [
|
|
481
|
+
4e3,
|
|
482
|
+
2500,
|
|
483
|
+
1400,
|
|
484
|
+
800,
|
|
485
|
+
400
|
|
486
|
+
]) {
|
|
487
|
+
url = build(budget);
|
|
488
|
+
if (url.length <= MAX_URL_CHARS) break;
|
|
489
|
+
}
|
|
490
|
+
return url;
|
|
491
|
+
}
|
|
160
492
|
const CATEGORY_ORDER = [
|
|
161
493
|
"interface",
|
|
162
494
|
"session",
|
|
@@ -170,15 +502,20 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
170
502
|
"knowledge",
|
|
171
503
|
"fun"
|
|
172
504
|
];
|
|
505
|
+
/** 分类标签按界面语言取词;未知 key 原样返回。 */
|
|
506
|
+
function categoryLabel(map, key, lang) {
|
|
507
|
+
return map[key]?.[lang] ?? key;
|
|
508
|
+
}
|
|
173
509
|
/**
|
|
174
510
|
* 归一化在线 API(dsh-plugin.org/api/plugins.{lang}.json)返回的短 key 结构
|
|
175
|
-
* (s/o/n/c/t/f/d/r/v/u/a/sg/fk),统一为 HubPlugin,保证渲染逻辑只认一种结构。
|
|
511
|
+
* (s/o/n/vr/c/t/f/d/r/v/u/a/sg/fk),统一为 HubPlugin,保证渲染逻辑只认一种结构。
|
|
176
512
|
*/
|
|
177
513
|
function normalize(raw) {
|
|
178
514
|
if (typeof raw.s === "string") return {
|
|
179
515
|
slug: raw.s,
|
|
180
516
|
ownerSlug: typeof raw.o === "string" ? raw.o : void 0,
|
|
181
517
|
displayName: typeof raw.n === "string" ? raw.n : void 0,
|
|
518
|
+
version: typeof raw.vr === "string" ? raw.vr : void 0,
|
|
182
519
|
category: typeof raw.c === "string" ? raw.c : void 0,
|
|
183
520
|
topics: Array.isArray(raw.t) ? raw.t : void 0,
|
|
184
521
|
features: Array.isArray(raw.f) ? raw.f : void 0,
|
|
@@ -242,6 +579,7 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
242
579
|
en: "Entertainment"
|
|
243
580
|
}
|
|
244
581
|
};
|
|
582
|
+
/** 分类 Tab 的紧凑标签(英文模式空间受限时使用)。 */
|
|
245
583
|
const CATEGORY_SHORT_LABELS = {
|
|
246
584
|
interface: {
|
|
247
585
|
zh: "界面体验",
|
|
@@ -290,25 +628,30 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
290
628
|
};
|
|
291
629
|
const SORTS = [
|
|
292
630
|
"sortStars",
|
|
631
|
+
"sortForks",
|
|
293
632
|
"sortUpdated",
|
|
294
633
|
"sortNewest"
|
|
295
634
|
];
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
function relTime(iso, t) {
|
|
303
|
-
if (!iso) return "";
|
|
304
|
-
const days = Math.floor((Date.now() - new Date(iso).getTime()) / 864e5);
|
|
305
|
-
if (days <= 0) return t("today");
|
|
306
|
-
if (days < 30) return t("daysAgo", { days });
|
|
307
|
-
if (days < 365) return t("monthsAgo", { months: Math.floor(days / 30) });
|
|
308
|
-
return t("yearsAgo", { years: Math.floor(days / 365) });
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/client/lib/env.ts
|
|
637
|
+
let envPromise = null;
|
|
638
|
+
function getEnv() {
|
|
639
|
+
envPromise ??= fetch("/dsh-plugin-hub/env", { cache: "no-store" }).then((res) => res.ok ? res.json() : null).catch(() => null);
|
|
640
|
+
return envPromise;
|
|
309
641
|
}
|
|
310
|
-
|
|
311
|
-
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region src/client/hooks/useCatalog.ts
|
|
644
|
+
/**
|
|
645
|
+
* Catalog data + view state for the Plugin Hub section.
|
|
646
|
+
*
|
|
647
|
+
* Owns the online-data pipeline (live fetch from dsh-plugin.org), the local
|
|
648
|
+
* installed-plugin table, and the filter/search/sort/install-status view
|
|
649
|
+
* state, exposing the derived visible list and per-category counts.
|
|
650
|
+
*/
|
|
651
|
+
const PLUGINS_URL = (lang) => `https://dsh-plugin.org/api/plugins.${lang}.json`;
|
|
652
|
+
const STATS_URL = "https://dsh-plugin.org/api/stats.json";
|
|
653
|
+
function useCatalog(lang) {
|
|
654
|
+
/** 目录插件(仅保留人工验证通过的条目) */
|
|
312
655
|
const [plugins, setPlugins] = (0, react.useState)(null);
|
|
313
656
|
/** 收录/精选统计(官网 /api/stats.json 实时拉取) */
|
|
314
657
|
const [stats, setStats] = (0, react.useState)(null);
|
|
@@ -317,50 +660,113 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
317
660
|
const [category, setCategory] = (0, react.useState)("all");
|
|
318
661
|
const [query, setQuery] = (0, react.useState)("");
|
|
319
662
|
const [sort, setSort] = (0, react.useState)("sortStars");
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
(0, react.
|
|
326
|
-
listRef.current?.scrollTo({ top: 0 });
|
|
327
|
-
}, [category]);
|
|
328
|
-
(0, react.useEffect)(() => locale.subscribe(() => setLang(locale.getSnapshot().active)), [locale]);
|
|
663
|
+
/** 列表安装状态筛选:全部 / 已安装 / 未安装(分段按钮,单列表内切换,不引入第二个列表) */
|
|
664
|
+
const [installedFilter, setInstalledFilter] = (0, react.useState)("all");
|
|
665
|
+
/** 当前 profile 已安装插件:npm 包名 -> manifest spec(来自宿主本地路由) */
|
|
666
|
+
const [installed, setInstalled] = (0, react.useState)({});
|
|
667
|
+
/** 安装时记录的目录信号:repo(小写) -> { version, updatedAt }(来自宿主本地路由) */
|
|
668
|
+
const [versions, setVersions] = (0, react.useState)({});
|
|
329
669
|
(0, react.useEffect)(() => {
|
|
330
670
|
let cancelled = false;
|
|
331
671
|
setPlugins(null);
|
|
332
672
|
setStats(null);
|
|
333
673
|
setFailed(false);
|
|
334
|
-
const
|
|
674
|
+
const fetchData = (url) => fetch(url, { cache: "no-store" }).then((res) => {
|
|
675
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
676
|
+
return res.json();
|
|
677
|
+
});
|
|
678
|
+
fetchData(PLUGINS_URL(lang)).then((data) => {
|
|
335
679
|
if (cancelled) return;
|
|
336
680
|
const list = (Array.isArray(data) ? data : []).map((item) => normalize(item));
|
|
337
681
|
setPlugins(list.filter((p) => p.compatibility?.status === "verified"));
|
|
338
|
-
}
|
|
339
|
-
const fail = () => {
|
|
682
|
+
}).catch(() => {
|
|
340
683
|
if (!cancelled) setFailed(true);
|
|
341
|
-
};
|
|
342
|
-
const fetchData = (url) => fetch(url).then((res) => {
|
|
343
|
-
if (!res.ok) throw new Error(String(res.status));
|
|
344
|
-
return res.json();
|
|
345
684
|
});
|
|
346
|
-
fetchData(
|
|
347
|
-
|
|
348
|
-
if (!cancelled &&
|
|
349
|
-
|
|
350
|
-
|
|
685
|
+
fetchData(STATS_URL).then((s) => {
|
|
686
|
+
const stats = s;
|
|
687
|
+
if (!cancelled && stats && typeof stats.total === "number" && typeof stats.verified === "number") setStats({
|
|
688
|
+
total: stats.total,
|
|
689
|
+
verified: stats.verified
|
|
690
|
+
});
|
|
691
|
+
}).catch(() => {});
|
|
351
692
|
return () => {
|
|
352
693
|
cancelled = true;
|
|
353
694
|
};
|
|
354
|
-
}, [
|
|
695
|
+
}, [reloadKey, lang]);
|
|
696
|
+
/** 刷新当前 profile 已安装插件表;宿主未挂本地路由时静默降级为空表。 */
|
|
697
|
+
const refreshInstalled = async () => {
|
|
698
|
+
try {
|
|
699
|
+
const res = await fetch("/dsh-plugin-hub/installed", { cache: "no-store" });
|
|
700
|
+
if (!res.ok) return;
|
|
701
|
+
const data = await res.json();
|
|
702
|
+
setInstalled(data.installed ?? {});
|
|
703
|
+
setVersions(data.versions ?? {});
|
|
704
|
+
} catch {}
|
|
705
|
+
};
|
|
706
|
+
(0, react.useEffect)(() => {
|
|
707
|
+
refreshInstalled();
|
|
708
|
+
}, []);
|
|
709
|
+
/** 插件是否已安装:匹配 installed spec 中的 `github:<owner>/<repo>`;命中返回 npm 包名。 */
|
|
710
|
+
const installedName = (p) => {
|
|
711
|
+
const repo = p.source?.repo;
|
|
712
|
+
if (!repo) return null;
|
|
713
|
+
const needle = `github:${repo.toLowerCase()}`;
|
|
714
|
+
for (const [name, spec] of Object.entries(installed)) if (spec.toLowerCase().includes(needle)) return name;
|
|
715
|
+
return null;
|
|
716
|
+
};
|
|
717
|
+
/** 该插件安装时记录的目录版本(无记录/未安装 → null)。 */
|
|
718
|
+
const installedVersion = (p) => {
|
|
719
|
+
const repo = p.source?.repo;
|
|
720
|
+
if (!repo) return null;
|
|
721
|
+
return versions[repo.toLowerCase()]?.version ?? null;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* 是否有更新:仅对已安装插件有意义。
|
|
725
|
+
* 双信号判定——有 release 版本的比版本;无版本(repo 不打 tag)的比仓库最近更新时间
|
|
726
|
+
* (repoUpdatedAt,ISO 字符串字典序 = 时间序),更新时间变新说明有新提交。
|
|
727
|
+
*/
|
|
728
|
+
const hasUpdate = (p) => {
|
|
729
|
+
if (installedName(p) === null) return false;
|
|
730
|
+
const repo = p.source?.repo;
|
|
731
|
+
if (!repo) return false;
|
|
732
|
+
const rec = versions[repo.toLowerCase()];
|
|
733
|
+
if (!rec) return false;
|
|
734
|
+
if (p.version) return p.version !== rec.version;
|
|
735
|
+
const current = p.dates?.repoUpdatedAt;
|
|
736
|
+
return Boolean(current && rec.updatedAt && current > rec.updatedAt);
|
|
737
|
+
};
|
|
738
|
+
/** 当前分类下的插件(「全部」时为整个目录)。 */
|
|
739
|
+
const categoryPlugins = (0, react.useMemo)(() => {
|
|
740
|
+
if (!plugins) return [];
|
|
741
|
+
if (category === "all") return plugins;
|
|
742
|
+
return plugins.filter((p) => p.category === category);
|
|
743
|
+
}, [plugins, category]);
|
|
744
|
+
/** 当前分类下已安装插件数:已安装/未安装按钮上的计数跟随分类,不再用全局口径。 */
|
|
745
|
+
const installedCountInCategory = (0, react.useMemo)(() => {
|
|
746
|
+
return categoryPlugins.reduce((n, p) => n + (installedName(p) !== null ? 1 : 0), 0);
|
|
747
|
+
}, [categoryPlugins, installed]);
|
|
748
|
+
/** 当前分类下未安装插件数。 */
|
|
749
|
+
const notInstalledCountInCategory = Math.max(0, categoryPlugins.length - installedCountInCategory);
|
|
750
|
+
(0, react.useEffect)(() => {
|
|
751
|
+
if (installedFilter === "all") return;
|
|
752
|
+
if ((installedFilter === "installed" ? installedCountInCategory : notInstalledCountInCategory) === 0) setInstalledFilter("all");
|
|
753
|
+
}, [
|
|
754
|
+
installedFilter,
|
|
755
|
+
installedCountInCategory,
|
|
756
|
+
notInstalledCountInCategory
|
|
757
|
+
]);
|
|
355
758
|
const visible = (0, react.useMemo)(() => {
|
|
356
759
|
if (!plugins) return [];
|
|
357
760
|
const q = query.trim().toLowerCase();
|
|
358
761
|
return [...plugins.filter((p) => {
|
|
359
762
|
if (category !== "all" && p.category !== category) return false;
|
|
763
|
+
if (installedFilter === "installed" && installedName(p) === null) return false;
|
|
764
|
+
if (installedFilter === "notInstalled" && installedName(p) !== null) return false;
|
|
360
765
|
if (!q) return true;
|
|
361
766
|
return (p.displayName ?? "").toLowerCase().includes(q) || (p.description ?? "").toLowerCase().includes(q) || (p.topics ?? []).some((topic) => topic.toLowerCase().includes(q));
|
|
362
767
|
})].sort((a, b) => {
|
|
363
768
|
if (sort === "sortStars") return (b.stats?.stargazers_count ?? 0) - (a.stats?.stargazers_count ?? 0);
|
|
769
|
+
if (sort === "sortForks") return (b.stats?.forks_count ?? 0) - (a.stats?.forks_count ?? 0);
|
|
364
770
|
if (sort === "sortNewest") return (b.dates?.addedAt ?? "").localeCompare(a.dates?.addedAt ?? "");
|
|
365
771
|
return (b.dates?.repoUpdatedAt ?? "").localeCompare(a.dates?.repoUpdatedAt ?? "");
|
|
366
772
|
});
|
|
@@ -368,22 +774,1431 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
368
774
|
plugins,
|
|
369
775
|
category,
|
|
370
776
|
query,
|
|
371
|
-
sort
|
|
777
|
+
sort,
|
|
778
|
+
installed,
|
|
779
|
+
installedFilter
|
|
372
780
|
]);
|
|
781
|
+
/** Per-category plugin counts shown on the category chips. */
|
|
782
|
+
const categoryCounts = (0, react.useMemo)(() => {
|
|
783
|
+
const counts = {};
|
|
784
|
+
for (const p of plugins ?? []) if (p.category) counts[p.category] = (counts[p.category] ?? 0) + 1;
|
|
785
|
+
return counts;
|
|
786
|
+
}, [plugins]);
|
|
787
|
+
return {
|
|
788
|
+
plugins,
|
|
789
|
+
stats,
|
|
790
|
+
failed,
|
|
791
|
+
reload: () => setReloadKey((k) => k + 1),
|
|
792
|
+
installed,
|
|
793
|
+
installedName,
|
|
794
|
+
installedVersion,
|
|
795
|
+
hasUpdate,
|
|
796
|
+
refreshInstalled,
|
|
797
|
+
category,
|
|
798
|
+
setCategory,
|
|
799
|
+
query,
|
|
800
|
+
setQuery,
|
|
801
|
+
sort,
|
|
802
|
+
setSort,
|
|
803
|
+
installedFilter,
|
|
804
|
+
setInstalledFilter,
|
|
805
|
+
visible,
|
|
806
|
+
total: plugins?.length ?? 0,
|
|
807
|
+
installedCountInCategory,
|
|
808
|
+
notInstalledCountInCategory,
|
|
809
|
+
categoryCounts
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region src/client/hooks/useTaskQueue.ts
|
|
814
|
+
/**
|
|
815
|
+
* Background install/remove task queue for the Plugin Hub.
|
|
816
|
+
*
|
|
817
|
+
* Mirrors the server-side FIFO active queue by polling /active, settles
|
|
818
|
+
* vanished tasks against /status, and exposes the queue actions (install,
|
|
819
|
+
* uninstall, cancel) plus the modal task lookups used by the dialogs.
|
|
820
|
+
*/
|
|
821
|
+
function useTaskQueue(opts) {
|
|
822
|
+
const { t, refreshInstalled, onInstallDone, onUninstallDone, onError, installPlugin, uninstallPlugin, installedName, resolvePending } = opts;
|
|
823
|
+
const [queue, setQueue] = (0, react.useState)([]);
|
|
824
|
+
const queueRef = (0, react.useRef)([]);
|
|
825
|
+
/** 待重启列表(服务端内存态 + 本地展示信息补齐);与 queue 一样以 ref 为唯一事实来源。 */
|
|
826
|
+
const [pendingRestarts, setPendingRestarts] = (0, react.useState)([]);
|
|
827
|
+
const pendingRef = (0, react.useRef)([]);
|
|
828
|
+
/** 本次会话内安装成功插件的展示信息(目标 → 简介/版本):轮询合并待重启列表时补齐,刷新后由 resolvePending 兜底。 */
|
|
829
|
+
const pendingInfoRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
830
|
+
/** 保持最新的 resolvePending:轮询闭包捕获的是调用那一刻的目录状态,目录晚加载时也能补齐。 */
|
|
831
|
+
const resolvePendingRef = (0, react.useRef)(resolvePending);
|
|
832
|
+
resolvePendingRef.current = resolvePending;
|
|
833
|
+
/** 当前打开弹窗所对应的任务 id:该任务完成时弹窗切换为结果视图 */
|
|
834
|
+
const modalTaskRef = (0, react.useRef)(null);
|
|
835
|
+
/** 轮询定时器句柄(队列清空/组件卸载时清理) */
|
|
836
|
+
const pollRef = (0, react.useRef)(null);
|
|
837
|
+
/** 请求在途的目标集合(同步防重):双击安装/卸载时第二击直接忽略。
|
|
838
|
+
* 本地队列任务要等 fetch 返回后才入队,仅靠 queueRef 检查拦不住请求窗口内的重复点击。 */
|
|
839
|
+
const submittingRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
840
|
+
/** 请求在途标记:弹窗据此禁用确认按钮,避免等待响应期间被再次点击。 */
|
|
841
|
+
const [submitting, setSubmitting] = (0, react.useState)(false);
|
|
842
|
+
/** 停止后台任务轮询(任务结束或组件卸载时清理)。 */
|
|
843
|
+
const stopPoll = () => {
|
|
844
|
+
if (pollRef.current !== null) {
|
|
845
|
+
window.clearInterval(pollRef.current);
|
|
846
|
+
pollRef.current = null;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
/** 队列与待重启都清空才停轮询:待重启期间持续轮询,宿主一重启(内存列表清空)立刻感知并消失。 */
|
|
850
|
+
const maybeStopPoll = () => {
|
|
851
|
+
if (queueRef.current.length === 0 && pendingRef.current.length === 0) stopPoll();
|
|
852
|
+
};
|
|
853
|
+
(0, react.useEffect)(() => stopPoll, []);
|
|
854
|
+
/** 更新队列(同步镜像到 ref:queueRef 是唯一事实来源,state 只负责触发渲染,
|
|
855
|
+
* 避免依赖 React updater 的异步调度时序导致 gone/stopPoll/防重复入队读到旧值)。 */
|
|
856
|
+
const applyQueue = (updater) => {
|
|
857
|
+
const next = updater(queueRef.current);
|
|
858
|
+
queueRef.current = next;
|
|
859
|
+
setQueue(next);
|
|
860
|
+
};
|
|
861
|
+
/** 解析服务端待重启列表(`github:` 前缀已去掉,与安装任务的 target 对齐)。 */
|
|
862
|
+
const parsePendingRestarts = (value) => {
|
|
863
|
+
if (!Array.isArray(value)) return [];
|
|
864
|
+
const out = [];
|
|
865
|
+
for (const x of value) {
|
|
866
|
+
if (typeof x !== "object" || x === null) continue;
|
|
867
|
+
const target = x.target;
|
|
868
|
+
if (typeof target !== "string" || target === "") continue;
|
|
869
|
+
const at = x.at;
|
|
870
|
+
out.push({
|
|
871
|
+
target: target.replace(/^github:/, ""),
|
|
872
|
+
kind: x.kind === "uninstall" ? "uninstall" : "install",
|
|
873
|
+
at: typeof at === "number" ? at : 0
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
return out;
|
|
877
|
+
};
|
|
878
|
+
/** 合并服务端待重启列表:保留已有展示信息,缺失时用会话内记录 / 目录解析补齐。 */
|
|
879
|
+
const applyPending = (raw) => {
|
|
880
|
+
const prev = pendingRef.current;
|
|
881
|
+
const next = raw.map((p) => {
|
|
882
|
+
const old = prev.find((q) => q.target === p.target);
|
|
883
|
+
if (old?.desc !== void 0) return {
|
|
884
|
+
...p,
|
|
885
|
+
desc: old.desc,
|
|
886
|
+
version: old.version
|
|
887
|
+
};
|
|
888
|
+
const info = pendingInfoRef.current.get(p.target) ?? resolvePendingRef.current?.(p.target) ?? null;
|
|
889
|
+
return info ? {
|
|
890
|
+
...p,
|
|
891
|
+
desc: info.desc,
|
|
892
|
+
version: info.version
|
|
893
|
+
} : p;
|
|
894
|
+
});
|
|
895
|
+
pendingRef.current = next;
|
|
896
|
+
setPendingRestarts(next);
|
|
897
|
+
};
|
|
898
|
+
/** 向宿主记录某插件的安装时目录信号(version + updatedAt);version 为空时清除记录(卸载)。 */
|
|
899
|
+
const syncInstalledVersion = async (repo, version, updatedAt) => {
|
|
900
|
+
if (!repo) return;
|
|
901
|
+
try {
|
|
902
|
+
await fetch("/dsh-plugin-hub/installed-version", {
|
|
903
|
+
method: "POST",
|
|
904
|
+
headers: { "content-type": "application/json" },
|
|
905
|
+
body: JSON.stringify({
|
|
906
|
+
repo,
|
|
907
|
+
version: version ?? null,
|
|
908
|
+
updatedAt
|
|
909
|
+
})
|
|
910
|
+
});
|
|
911
|
+
} catch {}
|
|
912
|
+
};
|
|
913
|
+
/** 后台任务结束(active 中消失后查终态确认):完成 → 刷新安装表 + 结果视图/toast;失败 → 完整错误弹窗 */
|
|
914
|
+
const finishQueueTask = (ok, q, lines) => {
|
|
915
|
+
applyQueue((prev) => prev.filter((x) => x.id !== q.id));
|
|
916
|
+
if (ok) {
|
|
917
|
+
refreshInstalled();
|
|
918
|
+
if (q.kind === "uninstall") {
|
|
919
|
+
syncInstalledVersion(q.repo, void 0, void 0);
|
|
920
|
+
onUninstallDone(modalTaskRef.current === q.id, q.repo);
|
|
921
|
+
} else {
|
|
922
|
+
syncInstalledVersion(q.repo, q.version, q.updatedAt);
|
|
923
|
+
pendingInfoRef.current.set(q.target, {
|
|
924
|
+
desc: q.desc,
|
|
925
|
+
version: q.version
|
|
926
|
+
});
|
|
927
|
+
onInstallDone(modalTaskRef.current === q.id, q.repo);
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
const detail = lines.length > 0 ? [...lines].reverse().join("\n") : q.kind === "uninstall" ? t("uninstallFail") : t("installFail");
|
|
931
|
+
onError(detail, q.repo, q.kind);
|
|
932
|
+
}
|
|
933
|
+
maybeStopPoll();
|
|
934
|
+
};
|
|
935
|
+
/** 任务在 /active 中消失 = 已结束:查 /status 拿终态并收尾(cancelled 静默移除)。 */
|
|
936
|
+
const settleTask = async (q) => {
|
|
937
|
+
let status = "failed";
|
|
938
|
+
let lines = [];
|
|
939
|
+
try {
|
|
940
|
+
const res = await fetch(`/dsh-plugin-hub/status?task=${q.id}`, { cache: "no-store" });
|
|
941
|
+
if (res.ok) {
|
|
942
|
+
const data = await res.json();
|
|
943
|
+
status = data.task?.status ?? "failed";
|
|
944
|
+
lines = data.task?.lines ?? [];
|
|
945
|
+
}
|
|
946
|
+
} catch {}
|
|
947
|
+
if (status === "done") finishQueueTask(true, q, lines);
|
|
948
|
+
else if (status === "failed") finishQueueTask(false, q, lines);
|
|
949
|
+
else {
|
|
950
|
+
applyQueue((prev) => prev.filter((x) => x.id !== q.id));
|
|
951
|
+
maybeStopPoll();
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
/** 轮询整个队列:合并服务端 /active(running 在前、pending 在后),
|
|
955
|
+
* 消失的任务查终态收尾;队列清空即停止轮询。 */
|
|
956
|
+
const pollQueue = () => {
|
|
957
|
+
stopPoll();
|
|
958
|
+
pollRef.current = window.setInterval(async () => {
|
|
959
|
+
try {
|
|
960
|
+
const res = await fetch("/dsh-plugin-hub/active", { cache: "no-store" });
|
|
961
|
+
if (!res.ok) throw new Error(`active ${res.status}`);
|
|
962
|
+
const data = await res.json();
|
|
963
|
+
const active = (data.tasks ?? []).filter((a) => typeof a.id === "number");
|
|
964
|
+
const byId = new Map(active.map((a) => [a.id, a]));
|
|
965
|
+
const prevQueue = queueRef.current;
|
|
966
|
+
applyPending(parsePendingRestarts(data.pendingRestarts));
|
|
967
|
+
applyQueue((prev) => {
|
|
968
|
+
const next = [];
|
|
969
|
+
for (const a of active) {
|
|
970
|
+
const id = a.id;
|
|
971
|
+
const prevTask = prev.find((q) => q.id === id);
|
|
972
|
+
const isRemove = a.action === "remove";
|
|
973
|
+
next.push({
|
|
974
|
+
id,
|
|
975
|
+
kind: isRemove ? "uninstall" : "install",
|
|
976
|
+
target: typeof a.target === "string" ? a.target.replace(/^github:/, "") : prevTask?.target ?? "",
|
|
977
|
+
desc: prevTask?.desc,
|
|
978
|
+
repo: prevTask?.repo ?? (isRemove ? null : typeof a.target === "string" ? a.target.replace(/^github:/, "") : null),
|
|
979
|
+
version: prevTask?.version,
|
|
980
|
+
updatedAt: prevTask?.updatedAt,
|
|
981
|
+
status: a.status === "running" ? "running" : "pending",
|
|
982
|
+
progress: typeof a.progress === "number" ? a.progress : prevTask?.progress ?? 0,
|
|
983
|
+
lines: Array.isArray(a.lines) ? a.lines : prevTask?.lines ?? []
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
return next;
|
|
987
|
+
});
|
|
988
|
+
const gone = prevQueue.filter((q) => !byId.has(q.id) && q.status !== "cancelling");
|
|
989
|
+
for (const q of gone) await settleTask(q);
|
|
990
|
+
} catch {}
|
|
991
|
+
}, 600);
|
|
992
|
+
};
|
|
993
|
+
(0, react.useEffect)(() => {
|
|
994
|
+
let cancelled = false;
|
|
995
|
+
const restore = async () => {
|
|
996
|
+
try {
|
|
997
|
+
const res = await fetch("/dsh-plugin-hub/active", { cache: "no-store" });
|
|
998
|
+
if (!res.ok) return;
|
|
999
|
+
const data = await res.json();
|
|
1000
|
+
if (cancelled) return;
|
|
1001
|
+
applyPending(parsePendingRestarts(data.pendingRestarts));
|
|
1002
|
+
const items = (data.tasks ?? []).filter((a) => typeof a.id === "number").map((a) => {
|
|
1003
|
+
const isRemove = a.action === "remove";
|
|
1004
|
+
return {
|
|
1005
|
+
id: a.id,
|
|
1006
|
+
kind: isRemove ? "uninstall" : "install",
|
|
1007
|
+
target: typeof a.target === "string" ? a.target.replace(/^github:/, "") : "",
|
|
1008
|
+
repo: isRemove ? null : typeof a.target === "string" ? a.target.replace(/^github:/, "") : null,
|
|
1009
|
+
status: a.status === "running" ? "running" : "pending",
|
|
1010
|
+
progress: typeof a.progress === "number" ? a.progress : 0,
|
|
1011
|
+
lines: Array.isArray(a.lines) ? a.lines : []
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
if (items.length > 0 || pendingRef.current.length > 0) {
|
|
1015
|
+
applyQueue((prev) => [...prev, ...items.filter((n) => !prev.some((p) => p.id === n.id))]);
|
|
1016
|
+
pollQueue();
|
|
1017
|
+
}
|
|
1018
|
+
} catch {}
|
|
1019
|
+
};
|
|
1020
|
+
restore();
|
|
1021
|
+
return () => {
|
|
1022
|
+
cancelled = true;
|
|
1023
|
+
};
|
|
1024
|
+
}, []);
|
|
1025
|
+
/** 弹窗动作:直接安装。请求宿主本地路由,任务进入服务端队列(FIFO),弹窗内实时显示进度。 */
|
|
1026
|
+
const installNow = async (p, opts) => {
|
|
1027
|
+
const repo = p.source?.repo ?? "";
|
|
1028
|
+
if (!repo) return;
|
|
1029
|
+
if (queueRef.current.some((q) => q.kind === "install" && q.target === repo)) return;
|
|
1030
|
+
if (submittingRef.current.has(repo)) return;
|
|
1031
|
+
submittingRef.current.add(repo);
|
|
1032
|
+
setSubmitting(true);
|
|
1033
|
+
try {
|
|
1034
|
+
const res = await fetch("/dsh-plugin-hub/install", {
|
|
1035
|
+
method: "POST",
|
|
1036
|
+
headers: { "content-type": "application/json" },
|
|
1037
|
+
body: JSON.stringify({
|
|
1038
|
+
repo,
|
|
1039
|
+
mode: opts?.update ? "update" : void 0
|
|
1040
|
+
})
|
|
1041
|
+
});
|
|
1042
|
+
const data = await res.json();
|
|
1043
|
+
if (!data.ok || typeof data.task !== "number") {
|
|
1044
|
+
onError(data.error ?? `HTTP ${res.status}`, repo, "install");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const taskId = data.task;
|
|
1048
|
+
modalTaskRef.current = taskId;
|
|
1049
|
+
applyQueue((prev) => [...prev, {
|
|
1050
|
+
id: taskId,
|
|
1051
|
+
kind: "install",
|
|
1052
|
+
target: repo,
|
|
1053
|
+
repo,
|
|
1054
|
+
desc: p.description,
|
|
1055
|
+
version: p.version,
|
|
1056
|
+
updatedAt: p.dates?.repoUpdatedAt,
|
|
1057
|
+
status: "pending",
|
|
1058
|
+
progress: 0,
|
|
1059
|
+
lines: []
|
|
1060
|
+
}]);
|
|
1061
|
+
pendingInfoRef.current.set(repo, {
|
|
1062
|
+
desc: p.description,
|
|
1063
|
+
version: p.version
|
|
1064
|
+
});
|
|
1065
|
+
pollQueue();
|
|
1066
|
+
} catch {
|
|
1067
|
+
onError(t("installFail"), repo, "install");
|
|
1068
|
+
} finally {
|
|
1069
|
+
submittingRef.current.delete(repo);
|
|
1070
|
+
setSubmitting(false);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
/** 弹窗动作:直接卸载。与安装同一队列机制,弹窗内实时显示进度。 */
|
|
1074
|
+
const uninstallNow = async (p) => {
|
|
1075
|
+
const name = installedName(p);
|
|
1076
|
+
if (!name) return;
|
|
1077
|
+
const repo = p.source?.repo ?? null;
|
|
1078
|
+
if (queueRef.current.some((q) => q.kind === "uninstall" && q.target === name)) return;
|
|
1079
|
+
if (submittingRef.current.has(name)) return;
|
|
1080
|
+
submittingRef.current.add(name);
|
|
1081
|
+
setSubmitting(true);
|
|
1082
|
+
try {
|
|
1083
|
+
const res = await fetch("/dsh-plugin-hub/uninstall", {
|
|
1084
|
+
method: "POST",
|
|
1085
|
+
headers: { "content-type": "application/json" },
|
|
1086
|
+
body: JSON.stringify({
|
|
1087
|
+
name,
|
|
1088
|
+
repo: repo ?? void 0
|
|
1089
|
+
})
|
|
1090
|
+
});
|
|
1091
|
+
const data = await res.json();
|
|
1092
|
+
if (!data.ok || typeof data.task !== "number") {
|
|
1093
|
+
onError(data.error ?? `HTTP ${res.status}`, repo, "uninstall");
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const taskId = data.task;
|
|
1097
|
+
modalTaskRef.current = taskId;
|
|
1098
|
+
applyQueue((prev) => [...prev, {
|
|
1099
|
+
id: taskId,
|
|
1100
|
+
kind: "uninstall",
|
|
1101
|
+
target: name,
|
|
1102
|
+
desc: p.description,
|
|
1103
|
+
repo,
|
|
1104
|
+
status: "pending",
|
|
1105
|
+
progress: 0,
|
|
1106
|
+
lines: []
|
|
1107
|
+
}]);
|
|
1108
|
+
pollQueue();
|
|
1109
|
+
} catch {
|
|
1110
|
+
onError(t("uninstallFail"), repo, "uninstall");
|
|
1111
|
+
} finally {
|
|
1112
|
+
submittingRef.current.delete(name);
|
|
1113
|
+
setSubmitting(false);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
/** 取消任务:排队中立即出队,执行中终止子进程;先标记「正在取消」短暂过渡后再移除。 */
|
|
1117
|
+
const cancelTask = async (id) => {
|
|
1118
|
+
if (modalTaskRef.current === id) modalTaskRef.current = null;
|
|
1119
|
+
applyQueue((prev) => prev.map((q) => q.id === id ? {
|
|
1120
|
+
...q,
|
|
1121
|
+
status: "cancelling"
|
|
1122
|
+
} : q));
|
|
1123
|
+
try {
|
|
1124
|
+
await fetch("/dsh-plugin-hub/cancel", {
|
|
1125
|
+
method: "POST",
|
|
1126
|
+
headers: { "content-type": "application/json" },
|
|
1127
|
+
body: JSON.stringify({ id })
|
|
1128
|
+
});
|
|
1129
|
+
} catch {}
|
|
1130
|
+
window.setTimeout(() => {
|
|
1131
|
+
applyQueue((prev) => prev.filter((x) => x.id !== id));
|
|
1132
|
+
maybeStopPoll();
|
|
1133
|
+
}, 700);
|
|
1134
|
+
};
|
|
1135
|
+
/** 打开新弹窗前清空任务匹配:避免旧任务 id 误匹配到新弹窗。 */
|
|
1136
|
+
const clearModalTask = () => {
|
|
1137
|
+
modalTaskRef.current = null;
|
|
1138
|
+
};
|
|
1139
|
+
return {
|
|
1140
|
+
queue,
|
|
1141
|
+
pendingRestarts,
|
|
1142
|
+
installModalTask: installPlugin ? queue.find((q) => q.id === modalTaskRef.current && q.status !== "cancelling") ?? queue.find((q) => q.kind === "install" && q.target === (installPlugin.source?.repo ?? "") && q.status !== "cancelling") ?? null : null,
|
|
1143
|
+
uninstallModalTask: uninstallPlugin ? queue.find((q) => q.id === modalTaskRef.current && q.status !== "cancelling") ?? queue.find((q) => q.kind === "uninstall" && q.target === (installedName(uninstallPlugin) ?? "") && q.status !== "cancelling") ?? null : null,
|
|
1144
|
+
submitting,
|
|
1145
|
+
installNow,
|
|
1146
|
+
uninstallNow,
|
|
1147
|
+
cancelTask,
|
|
1148
|
+
clearModalTask
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
//#endregion
|
|
1152
|
+
//#region \0dsh-css:src/client/styles/Modal.module.css.mjs
|
|
1153
|
+
const css$3 = ".BiQ1zG_overlay{z-index:998;-webkit-user-select:none;user-select:none;background:#00000061;justify-content:center;align-items:center;animation:.16s ease-out BiQ1zG_overlayIn;display:flex;position:fixed;top:0;bottom:0;left:0;right:0}.BiQ1zG_modal,.BiQ1zG_errorModal{background:var(--hub-bg-1);border:1px solid var(--hub-border-2);border-radius:10px;flex-direction:column;gap:10px;width:420px;max-width:calc(100vw - 32px);max-height:calc(100vh - 64px);padding:14px 16px;animation:.18s ease-out BiQ1zG_modalIn;display:flex;box-shadow:0 12px 40px #0000003d}.BiQ1zG_errorModal{width:640px;max-width:calc(100vw - 48px)}.BiQ1zG_modalHead{flex-shrink:0;justify-content:space-between;align-items:center;gap:8px;display:flex}.BiQ1zG_modalTitle{color:var(--hub-text-primary);font-size:14px;font-weight:600;line-height:20px}.BiQ1zG_modalTitleBusy{color:var(--hub-brand)}.BiQ1zG_modalTitleQueued{color:var(--hub-warning)}.BiQ1zG_modalClose{width:24px;height:24px;color:var(--hub-text-tertiary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;flex-shrink:0;justify-content:center;align-items:center;font-size:16px;line-height:1;display:inline-flex}.BiQ1zG_modalClose:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}.BiQ1zG_modalCloseIcon{flex-shrink:0;width:14px;height:14px}.BiQ1zG_modalDesc{color:var(--hub-text-secondary);font-size:12px;line-height:18px}.BiQ1zG_trustHint{color:var(--hub-danger-text);background:var(--hub-danger-tint);border:1px solid var(--hub-danger-border-soft);border-radius:6px;padding:6px 10px;font-size:12px;font-weight:500;line-height:18px}.BiQ1zG_modalRow{align-items:baseline;gap:8px;min-width:0;font-size:12px;line-height:18px;display:flex}.BiQ1zG_modalLabel{min-width:64px;color:var(--hub-text-tertiary);flex-shrink:0}.BiQ1zG_modalValue{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--hub-text-primary);font-weight:500;overflow:hidden}.BiQ1zG_modalLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--hub-brand);cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;font-weight:500;text-decoration:none;transition:color .12s;display:inline-flex;overflow:hidden}.BiQ1zG_modalLink:hover{color:var(--hub-brand-hover);text-decoration:underline}.BiQ1zG_linkIcon{flex-shrink:0}.BiQ1zG_modalCmd{color:var(--hub-text-primary);background:var(--hub-bg-2);border:1px solid var(--hub-border-2);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:6px;justify-content:space-between;align-items:center;gap:8px;padding:6px 6px 6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:18px;transition:border-color .12s,background .12s;display:flex}.BiQ1zG_modalCmd:hover{border-color:var(--hub-border-ghost);background:var(--hub-bg-3)}.BiQ1zG_modalCmdText{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.BiQ1zG_modalCmdCopy{color:var(--hub-brand);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:4px;flex-shrink:0;align-items:center;gap:3px;padding:1px 7px;font-family:inherit;font-size:11px;line-height:16px;transition:color .12s,background .12s;display:inline-flex}.BiQ1zG_modalCmdCopy:hover{color:var(--hub-brand-hover);background:var(--hub-bg-2)}.BiQ1zG_modalActions{justify-content:flex-end;align-items:center;gap:8px;margin-top:2px;display:flex}.BiQ1zG_modalBody{flex-direction:column;flex:auto;gap:10px;min-height:0;display:flex;overflow-y:auto}.BiQ1zG_toast{z-index:1000;background:var(--hub-btn-fill);color:var(--hub-text-on-fill);pointer-events:none;white-space:nowrap;border:none;border-radius:8px;padding:10px 16px;font-size:12px;font-weight:500;line-height:18px;animation:.22s ease-out BiQ1zG_toastIn;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:0 6px 24px #00000047}@keyframes BiQ1zG_toastIn{0%{opacity:0;transform:translate(-50%,-44%)}to{opacity:1;transform:translate(-50%,-50%)}}.BiQ1zG_toastFail{background:var(--hub-danger);color:#fff;border-color:#ffffff3d}.BiQ1zG_queueSection{flex-direction:column;gap:6px;display:flex}.BiQ1zG_queueSectionTitle{color:var(--hub-text-secondary);padding:4px 2px 0;font-size:12px;font-weight:600;line-height:18px}.BiQ1zG_pendingRowStatus{color:var(--hub-warn);flex-shrink:0;font-weight:500}.BiQ1zG_pendingRowActions{flex-shrink:0;gap:8px;margin-left:auto;display:flex}.BiQ1zG_queueRow{border:1px solid var(--hub-border-2);background:var(--hub-bg-2);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:8px;flex-direction:column;gap:5px;padding:8px 10px;font-size:12px;line-height:18px;transition:background .12s;display:flex}.BiQ1zG_queueRow:hover{background:var(--hub-brand-tint)}.BiQ1zG_queueRowHead{align-items:center;gap:10px;display:flex}.BiQ1zG_queueRowTarget{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--hub-text-primary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.BiQ1zG_queueRowDesc{color:var(--hub-text-tertiary);-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.BiQ1zG_queueRowBody{align-items:center;gap:8px;display:flex}.BiQ1zG_queueRowStatus{color:var(--hub-brand);flex-shrink:0;font-weight:500}.BiQ1zG_queueRowTrack{flex:1;min-width:0}.BiQ1zG_queueRowPct{color:var(--hub-text-tertiary);font-variant-numeric:tabular-nums;flex-shrink:0}.BiQ1zG_stripCancel{color:var(--hub-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;flex-shrink:0;padding:2px 10px;font-size:12px;line-height:18px;transition:background .12s,color .12s}.BiQ1zG_stripCancel:hover{background:var(--hub-danger-tint);color:var(--hub-danger-text)}.BiQ1zG_stripCancel:disabled{opacity:.45;cursor:default}.BiQ1zG_queuedHint{color:var(--hub-brand);background:var(--hub-brand-tint);border:1px solid var(--hub-brand-border-soft);border-radius:6px;padding:5px 10px;font-size:12px;line-height:18px}.BiQ1zG_errorTitle{color:var(--hub-danger-text);font-size:14px;font-weight:600;line-height:20px}.BiQ1zG_errorBox{border:1px solid var(--hub-danger-border);background:var(--hub-danger-tint-weak);max-height:240px;color:var(--hub-text-primary);white-space:pre-wrap;word-break:break-word;-webkit-user-select:none;user-select:none;border-radius:6px;margin:0;padding:10px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:17px;overflow:auto}.BiQ1zG_errorCopySoft{color:var(--hub-text-tertiary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:4px;flex-shrink:0;align-items:center;padding:2px 8px;font-size:11px;line-height:16px;transition:color .12s,background .12s;display:inline-flex}.BiQ1zG_errorCopySoft:hover{color:var(--hub-text-secondary);background:var(--hub-bg-2)}.BiQ1zG_errorHint{color:var(--hub-text-tertiary);font-size:12px;line-height:18px}.BiQ1zG_modalCancel,.BiQ1zG_modalCopy{color:var(--hub-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;padding:5px 14px;font-size:12px;line-height:18px;transition:color .12s,background .12s}.BiQ1zG_modalCancel:hover,.BiQ1zG_modalCopy:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}.BiQ1zG_modalInstall{color:var(--hub-text-on-fill);background:var(--hub-btn-fill);cursor:pointer;-webkit-user-select:none;user-select:none;border:none;border-radius:6px;padding:5px 14px;font-size:12px;font-weight:600;line-height:18px;transition:background .12s}.BiQ1zG_modalInstall:hover{background:var(--hub-btn-hover)}.BiQ1zG_modalCopy:disabled,.BiQ1zG_modalInstall:disabled,.BiQ1zG_modalCancel:disabled,.BiQ1zG_modalClose:disabled,.BiQ1zG_uninstallConfirm:disabled{opacity:.55;cursor:not-allowed;pointer-events:none}.BiQ1zG_uninstallConfirm{color:#fff;background:var(--hub-danger);cursor:pointer;-webkit-user-select:none;user-select:none;border:none;border-radius:6px;padding:5px 14px;font-size:12px;font-weight:600;line-height:18px;transition:background .12s}.BiQ1zG_uninstallConfirm:hover{background:var(--hub-danger-hover)}.BiQ1zG_result{text-align:center;flex-direction:column;align-items:center;gap:5px;padding:10px 0 2px;display:flex}.BiQ1zG_resultCheck{background:var(--hub-success-tint);width:40px;height:40px;color:var(--hub-success);border-radius:50%;justify-content:center;align-items:center;margin-bottom:3px;display:inline-flex}.BiQ1zG_resultCheckIcon{flex-shrink:0;width:20px;height:20px}.BiQ1zG_resultTitle{color:var(--hub-text-primary);font-size:13px;font-weight:600;line-height:20px}.BiQ1zG_resultDesc{color:var(--hub-text-secondary);font-size:12px;line-height:18px}.BiQ1zG_resultRestarting{color:var(--hub-text-tertiary);padding:12px 0 6px;font-size:12px;line-height:18px}.BiQ1zG_restartLater{color:var(--hub-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;padding:5px 14px;font-size:12px;line-height:18px;transition:color .12s,background .12s}.BiQ1zG_restartLater:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}.BiQ1zG_restartNow{color:var(--hub-text-on-fill);background:var(--hub-btn-fill);cursor:pointer;-webkit-user-select:none;user-select:none;border:none;border-radius:6px;padding:5px 14px;font-size:12px;font-weight:600;line-height:18px;transition:background .12s}.BiQ1zG_restartNow:hover{background:var(--hub-btn-hover)}.BiQ1zG_restartNow:disabled{opacity:.55;cursor:not-allowed;pointer-events:none}.BiQ1zG_result .BiQ1zG_modalActions{justify-content:center;gap:10px;margin-top:0;padding:6px 0 2px}.BiQ1zG_result .BiQ1zG_modalActions .BiQ1zG_restartLater,.BiQ1zG_result .BiQ1zG_modalActions .BiQ1zG_restartNow{min-width:100px}.BiQ1zG_progress{margin:10px 0 2px}.BiQ1zG_progressHead{justify-content:flex-end;align-items:center;margin-bottom:3px;display:flex}.BiQ1zG_progressText{color:var(--hub-text-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:14px}.BiQ1zG_progressTrack{background:var(--hub-border-2);border-radius:2px;height:4px;overflow:hidden}.BiQ1zG_progressFill{background:var(--hub-brand);border-radius:2px;height:100%;transition:width .32s}.BiQ1zG_progressFillFail{background:var(--hub-danger)}@keyframes BiQ1zG_overlayIn{0%{opacity:0}to{opacity:1}}@keyframes BiQ1zG_modalIn{0%{opacity:0;transform:translateY(6px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}.BiQ1zG_noticeList{flex-direction:column;gap:10px;padding-right:2px;display:flex}.BiQ1zG_noticeRow{border:1px solid var(--hub-border-2);background:var(--hub-bg-2);border-radius:8px;align-items:center;gap:10px;padding:10px 12px;display:flex}.BiQ1zG_noticeBadgeOk,.BiQ1zG_noticeBadgeFail{border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;width:28px;height:28px;display:inline-flex;box-shadow:0 2px 6px #0000002e}.BiQ1zG_noticeBadgeOk{background:var(--hub-success)}.BiQ1zG_noticeBadgeFail{background:var(--hub-danger)}.BiQ1zG_noticeBadgeIcon{flex-shrink:0;width:12px;height:12px}.BiQ1zG_noticeMain{flex-direction:column;flex:1;gap:8px;min-width:0;display:flex}.BiQ1zG_noticeHead{align-items:center;gap:8px;min-width:0;display:flex}.BiQ1zG_noticeTextOk{color:var(--hub-success);flex-shrink:0;font-size:12px;font-weight:600;line-height:18px}.BiQ1zG_noticeTextFail{color:var(--hub-danger-text);flex-shrink:0;font-size:12px;font-weight:600;line-height:18px}.BiQ1zG_failList{flex-direction:column;gap:12px;padding-right:2px;display:flex}.BiQ1zG_failRow{border:1px solid var(--hub-border-2);background:var(--hub-bg-2);border-radius:8px;flex-direction:column;gap:8px;padding:10px 12px;display:flex}.BiQ1zG_failHead{align-items:center;gap:8px;min-width:0;display:flex}.BiQ1zG_failKind{border:1px solid #0000;border-radius:4px;flex-shrink:0;padding:0 6px;font-size:10px;font-weight:500;line-height:16px}.BiQ1zG_failKindInstall{color:var(--hub-danger-text);border-color:var(--hub-danger-border);background:var(--hub-danger-tint)}.BiQ1zG_failKindUninstall{color:var(--hub-warn);border-color:var(--hub-warn-border);background:var(--hub-warn-tint)}.BiQ1zG_failRepo{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--hub-brand);cursor:pointer;-webkit-user-select:none;user-select:none;flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:18px;text-decoration:none;overflow:hidden}.BiQ1zG_failRepo:hover{text-decoration:underline}.BiQ1zG_failTime{color:var(--hub-text-tertiary);font-variant-numeric:tabular-nums;flex-shrink:0;font-size:11px;line-height:16px}.BiQ1zG_failCopy{color:var(--hub-text-tertiary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:4px;flex-shrink:0;align-items:center;padding:2px 8px;font-size:11px;line-height:16px;transition:color .12s,background .12s;display:inline-flex}.BiQ1zG_failCopy:hover{color:var(--hub-text-secondary);background:var(--hub-bg-2)}.BiQ1zG_failEmpty{text-align:center;color:var(--hub-text-tertiary);padding:24px 0;font-size:12px;line-height:18px}.BiQ1zG_failClear{color:var(--hub-danger);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;padding:5px 14px;font-size:12px;line-height:18px;transition:color .12s,background .12s}.BiQ1zG_failClear:hover{color:#fff;background:var(--hub-danger)}.BiQ1zG_failBigIssue{box-sizing:border-box;text-align:center;color:#fff;background:var(--hub-danger);border:1px solid var(--hub-danger);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:8px;width:100%;margin-top:4px;padding:3px 16px;font-size:13px;font-weight:600;line-height:18px;text-decoration:none;transition:background .12s,border-color .12s,box-shadow .12s;display:block}.BiQ1zG_failBigIssue:hover{background:var(--hub-danger-hover);border-color:var(--hub-danger-hover);box-shadow:0 3px 10px #d1242f59}.BiQ1zG_failPrepareHint{color:var(--hub-warning);background:var(--hub-warning-tint);border:1px solid var(--hub-warning-border);border-radius:6px;margin-top:6px;padding:8px 12px;font-size:12px;line-height:18px}";
|
|
1154
|
+
const tagId$3 = "dsh-plugin/Modal.module.css";
|
|
1155
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
1156
|
+
const tag = document.createElement("style");
|
|
1157
|
+
tag.dataset.plugin = "dsh-plugin";
|
|
1158
|
+
tag.dataset.pluginCss = tagId$3;
|
|
1159
|
+
tag.textContent = css$3;
|
|
1160
|
+
document.head.appendChild(tag);
|
|
1161
|
+
}
|
|
1162
|
+
var Modal_module_css_default = {
|
|
1163
|
+
"queueRowTrack": "BiQ1zG_queueRowTrack",
|
|
1164
|
+
"pendingRowStatus": "BiQ1zG_pendingRowStatus",
|
|
1165
|
+
"resultRestarting": "BiQ1zG_resultRestarting",
|
|
1166
|
+
"resultTitle": "BiQ1zG_resultTitle",
|
|
1167
|
+
"linkIcon": "BiQ1zG_linkIcon",
|
|
1168
|
+
"errorHint": "BiQ1zG_errorHint",
|
|
1169
|
+
"modalCloseIcon": "BiQ1zG_modalCloseIcon",
|
|
1170
|
+
"modalCmdText": "BiQ1zG_modalCmdText",
|
|
1171
|
+
"failList": "BiQ1zG_failList",
|
|
1172
|
+
"failCopy": "BiQ1zG_failCopy",
|
|
1173
|
+
"pendingRowActions": "BiQ1zG_pendingRowActions",
|
|
1174
|
+
"modalTitle": "BiQ1zG_modalTitle",
|
|
1175
|
+
"toastIn": "BiQ1zG_toastIn",
|
|
1176
|
+
"queueRowTarget": "BiQ1zG_queueRowTarget",
|
|
1177
|
+
"restartLater": "BiQ1zG_restartLater",
|
|
1178
|
+
"progressFillFail": "BiQ1zG_progressFillFail",
|
|
1179
|
+
"toast": "BiQ1zG_toast",
|
|
1180
|
+
"modalTitleQueued": "BiQ1zG_modalTitleQueued",
|
|
1181
|
+
"queueSection": "BiQ1zG_queueSection",
|
|
1182
|
+
"resultCheck": "BiQ1zG_resultCheck",
|
|
1183
|
+
"progressFill": "BiQ1zG_progressFill",
|
|
1184
|
+
"trustHint": "BiQ1zG_trustHint",
|
|
1185
|
+
"modalCancel": "BiQ1zG_modalCancel",
|
|
1186
|
+
"modalInstall": "BiQ1zG_modalInstall",
|
|
1187
|
+
"noticeRow": "BiQ1zG_noticeRow",
|
|
1188
|
+
"failClear": "BiQ1zG_failClear",
|
|
1189
|
+
"failEmpty": "BiQ1zG_failEmpty",
|
|
1190
|
+
"overlayIn": "BiQ1zG_overlayIn",
|
|
1191
|
+
"errorCopySoft": "BiQ1zG_errorCopySoft",
|
|
1192
|
+
"resultCheckIcon": "BiQ1zG_resultCheckIcon",
|
|
1193
|
+
"failBigIssue": "BiQ1zG_failBigIssue",
|
|
1194
|
+
"modalActions": "BiQ1zG_modalActions",
|
|
1195
|
+
"progressTrack": "BiQ1zG_progressTrack",
|
|
1196
|
+
"modalCmd": "BiQ1zG_modalCmd",
|
|
1197
|
+
"failTime": "BiQ1zG_failTime",
|
|
1198
|
+
"overlay": "BiQ1zG_overlay",
|
|
1199
|
+
"failHead": "BiQ1zG_failHead",
|
|
1200
|
+
"queueRowDesc": "BiQ1zG_queueRowDesc",
|
|
1201
|
+
"failRepo": "BiQ1zG_failRepo",
|
|
1202
|
+
"modalCopy": "BiQ1zG_modalCopy",
|
|
1203
|
+
"uninstallConfirm": "BiQ1zG_uninstallConfirm",
|
|
1204
|
+
"modalHead": "BiQ1zG_modalHead",
|
|
1205
|
+
"restartNow": "BiQ1zG_restartNow",
|
|
1206
|
+
"progressHead": "BiQ1zG_progressHead",
|
|
1207
|
+
"queueRowHead": "BiQ1zG_queueRowHead",
|
|
1208
|
+
"noticeBadgeOk": "BiQ1zG_noticeBadgeOk",
|
|
1209
|
+
"queueRowPct": "BiQ1zG_queueRowPct",
|
|
1210
|
+
"queueRowBody": "BiQ1zG_queueRowBody",
|
|
1211
|
+
"modalTitleBusy": "BiQ1zG_modalTitleBusy",
|
|
1212
|
+
"failKind": "BiQ1zG_failKind",
|
|
1213
|
+
"errorBox": "BiQ1zG_errorBox",
|
|
1214
|
+
"modalLink": "BiQ1zG_modalLink",
|
|
1215
|
+
"failKindUninstall": "BiQ1zG_failKindUninstall",
|
|
1216
|
+
"modalRow": "BiQ1zG_modalRow",
|
|
1217
|
+
"toastFail": "BiQ1zG_toastFail",
|
|
1218
|
+
"progress": "BiQ1zG_progress",
|
|
1219
|
+
"modalDesc": "BiQ1zG_modalDesc",
|
|
1220
|
+
"failKindInstall": "BiQ1zG_failKindInstall",
|
|
1221
|
+
"modal": "BiQ1zG_modal",
|
|
1222
|
+
"stripCancel": "BiQ1zG_stripCancel",
|
|
1223
|
+
"progressText": "BiQ1zG_progressText",
|
|
1224
|
+
"modalClose": "BiQ1zG_modalClose",
|
|
1225
|
+
"queueSectionTitle": "BiQ1zG_queueSectionTitle",
|
|
1226
|
+
"modalBody": "BiQ1zG_modalBody",
|
|
1227
|
+
"modalLabel": "BiQ1zG_modalLabel",
|
|
1228
|
+
"noticeMain": "BiQ1zG_noticeMain",
|
|
1229
|
+
"noticeTextOk": "BiQ1zG_noticeTextOk",
|
|
1230
|
+
"queueRow": "BiQ1zG_queueRow",
|
|
1231
|
+
"queueRowStatus": "BiQ1zG_queueRowStatus",
|
|
1232
|
+
"noticeBadgeFail": "BiQ1zG_noticeBadgeFail",
|
|
1233
|
+
"noticeBadgeIcon": "BiQ1zG_noticeBadgeIcon",
|
|
1234
|
+
"modalIn": "BiQ1zG_modalIn",
|
|
1235
|
+
"noticeHead": "BiQ1zG_noticeHead",
|
|
1236
|
+
"modalCmdCopy": "BiQ1zG_modalCmdCopy",
|
|
1237
|
+
"failRow": "BiQ1zG_failRow",
|
|
1238
|
+
"result": "BiQ1zG_result",
|
|
1239
|
+
"errorModal": "BiQ1zG_errorModal",
|
|
1240
|
+
"failPrepareHint": "BiQ1zG_failPrepareHint",
|
|
1241
|
+
"errorTitle": "BiQ1zG_errorTitle",
|
|
1242
|
+
"queuedHint": "BiQ1zG_queuedHint",
|
|
1243
|
+
"modalValue": "BiQ1zG_modalValue",
|
|
1244
|
+
"resultDesc": "BiQ1zG_resultDesc",
|
|
1245
|
+
"noticeTextFail": "BiQ1zG_noticeTextFail",
|
|
1246
|
+
"noticeList": "BiQ1zG_noticeList"
|
|
1247
|
+
};
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/client/components/icons.tsx
|
|
1250
|
+
/**
|
|
1251
|
+
* Inline SVG icons used across the client. Icons inherit currentColor so
|
|
1252
|
+
* they adapt to themes and disabled states automatically.
|
|
1253
|
+
*/
|
|
1254
|
+
/** 弹窗右上角关闭按钮图标:内联 SVG 十字(stroke 继承 currentColor),随按钮禁用态一起变淡。 */
|
|
1255
|
+
function CloseIcon() {
|
|
1256
|
+
return (0, react.createElement)("svg", {
|
|
1257
|
+
className: Modal_module_css_default.modalCloseIcon,
|
|
1258
|
+
viewBox: "0 0 16 16",
|
|
1259
|
+
width: 14,
|
|
1260
|
+
height: 14,
|
|
1261
|
+
fill: "none",
|
|
1262
|
+
"aria-hidden": "true"
|
|
1263
|
+
}, (0, react.createElement)("path", {
|
|
1264
|
+
d: "M3 3l10 10M13 3L3 13",
|
|
1265
|
+
stroke: "currentColor",
|
|
1266
|
+
strokeWidth: 1.6,
|
|
1267
|
+
strokeLinecap: "round"
|
|
1268
|
+
}));
|
|
1269
|
+
}
|
|
1270
|
+
/** 来源链接图标:链节(stroke 继承 currentColor),弹窗里官网收录地址旁提示「可点击跳转」。 */
|
|
1271
|
+
function LinkIcon() {
|
|
1272
|
+
return (0, react.createElement)("svg", {
|
|
1273
|
+
className: Modal_module_css_default.linkIcon,
|
|
1274
|
+
viewBox: "0 0 24 24",
|
|
1275
|
+
width: 13,
|
|
1276
|
+
height: 13,
|
|
1277
|
+
fill: "none",
|
|
1278
|
+
"aria-hidden": "true"
|
|
1279
|
+
}, (0, react.createElement)("path", {
|
|
1280
|
+
d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
|
|
1281
|
+
stroke: "currentColor",
|
|
1282
|
+
strokeWidth: 1.8,
|
|
1283
|
+
strokeLinecap: "round",
|
|
1284
|
+
strokeLinejoin: "round"
|
|
1285
|
+
}), (0, react.createElement)("path", {
|
|
1286
|
+
d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",
|
|
1287
|
+
stroke: "currentColor",
|
|
1288
|
+
strokeWidth: 1.8,
|
|
1289
|
+
strokeLinecap: "round",
|
|
1290
|
+
strokeLinejoin: "round"
|
|
1291
|
+
}));
|
|
1292
|
+
}
|
|
1293
|
+
/** 品牌 logo 图标:蓝紫渐变圆角方块 + 白色拼图块(2x2 错落),标题左侧的品牌标识。 */
|
|
1294
|
+
function LogoIcon() {
|
|
1295
|
+
return (0, react.createElement)("svg", {
|
|
1296
|
+
className: Header_module_css_default.logoIcon,
|
|
1297
|
+
viewBox: "0 0 24 24",
|
|
1298
|
+
width: 20,
|
|
1299
|
+
height: 20,
|
|
1300
|
+
"aria-hidden": "true"
|
|
1301
|
+
}, (0, react.createElement)("defs", null, (0, react.createElement)("linearGradient", {
|
|
1302
|
+
id: "dshLogoGrad",
|
|
1303
|
+
x1: "0",
|
|
1304
|
+
y1: "0",
|
|
1305
|
+
x2: "1",
|
|
1306
|
+
y2: "1"
|
|
1307
|
+
}, (0, react.createElement)("stop", {
|
|
1308
|
+
offset: "0%",
|
|
1309
|
+
stopColor: "#4f46e5"
|
|
1310
|
+
}), (0, react.createElement)("stop", {
|
|
1311
|
+
offset: "100%",
|
|
1312
|
+
stopColor: "#7c3aed"
|
|
1313
|
+
}))), (0, react.createElement)("rect", {
|
|
1314
|
+
x: 1,
|
|
1315
|
+
y: 1,
|
|
1316
|
+
width: 22,
|
|
1317
|
+
height: 22,
|
|
1318
|
+
rx: 6,
|
|
1319
|
+
fill: "url(#dshLogoGrad)"
|
|
1320
|
+
}), (0, react.createElement)("rect", {
|
|
1321
|
+
x: 5.5,
|
|
1322
|
+
y: 5.5,
|
|
1323
|
+
width: 5.5,
|
|
1324
|
+
height: 5.5,
|
|
1325
|
+
rx: 1.5,
|
|
1326
|
+
fill: "#ffffff"
|
|
1327
|
+
}), (0, react.createElement)("rect", {
|
|
1328
|
+
x: 13,
|
|
1329
|
+
y: 5.5,
|
|
1330
|
+
width: 5.5,
|
|
1331
|
+
height: 5.5,
|
|
1332
|
+
rx: 1.5,
|
|
1333
|
+
fill: "#ffffff",
|
|
1334
|
+
opacity: .55
|
|
1335
|
+
}), (0, react.createElement)("rect", {
|
|
1336
|
+
x: 5.5,
|
|
1337
|
+
y: 13,
|
|
1338
|
+
width: 5.5,
|
|
1339
|
+
height: 5.5,
|
|
1340
|
+
rx: 1.5,
|
|
1341
|
+
fill: "#ffffff",
|
|
1342
|
+
opacity: .55
|
|
1343
|
+
}), (0, react.createElement)("rect", {
|
|
1344
|
+
x: 13,
|
|
1345
|
+
y: 13,
|
|
1346
|
+
width: 5.5,
|
|
1347
|
+
height: 5.5,
|
|
1348
|
+
rx: 1.5,
|
|
1349
|
+
fill: "#ffffff"
|
|
1350
|
+
}));
|
|
1351
|
+
}
|
|
1352
|
+
/** GitHub 图标:官方 GitHub Mark(octocat),fill 继承 currentColor,头部右上角源码链接用。 */
|
|
1353
|
+
function GitHubIcon() {
|
|
1354
|
+
return (0, react.createElement)("svg", {
|
|
1355
|
+
className: Header_module_css_default.githubIcon,
|
|
1356
|
+
viewBox: "0 0 24 24",
|
|
1357
|
+
width: 18,
|
|
1358
|
+
height: 18,
|
|
1359
|
+
fill: "currentColor",
|
|
1360
|
+
"aria-hidden": "true"
|
|
1361
|
+
}, (0, react.createElement)("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" }));
|
|
1362
|
+
}
|
|
1363
|
+
/** 复制图标:双层矩形(stroke 继承 currentColor),手动命令旁的复制按钮用。 */
|
|
1364
|
+
function CopyIcon() {
|
|
1365
|
+
return (0, react.createElement)("svg", {
|
|
1366
|
+
className: Header_module_css_default.copyIcon,
|
|
1367
|
+
viewBox: "0 0 16 16",
|
|
1368
|
+
width: 12,
|
|
1369
|
+
height: 12,
|
|
1370
|
+
fill: "none",
|
|
1371
|
+
"aria-hidden": "true"
|
|
1372
|
+
}, (0, react.createElement)("rect", {
|
|
1373
|
+
x: 5.5,
|
|
1374
|
+
y: 5.5,
|
|
1375
|
+
width: 7,
|
|
1376
|
+
height: 7,
|
|
1377
|
+
rx: 1.5,
|
|
1378
|
+
stroke: "currentColor",
|
|
1379
|
+
strokeWidth: 1.4
|
|
1380
|
+
}), (0, react.createElement)("path", {
|
|
1381
|
+
d: "M10.5 5.5v-2a1.5 1.5 0 0 0-1.5-1.5H4.5A1.5 1.5 0 0 0 3 3.5v4A1.5 1.5 0 0 0 4.5 9h1",
|
|
1382
|
+
stroke: "currentColor",
|
|
1383
|
+
strokeWidth: 1.4,
|
|
1384
|
+
strokeLinecap: "round"
|
|
1385
|
+
}));
|
|
1386
|
+
}
|
|
1387
|
+
/** 下拉箭头图标:向下 V 形(stroke 继承 currentColor),下拉框触发器右侧提示可展开。 */
|
|
1388
|
+
function ChevronDownIcon() {
|
|
1389
|
+
return (0, react.createElement)("svg", {
|
|
1390
|
+
viewBox: "0 0 16 16",
|
|
1391
|
+
width: 12,
|
|
1392
|
+
height: 12,
|
|
1393
|
+
fill: "none",
|
|
1394
|
+
"aria-hidden": "true"
|
|
1395
|
+
}, (0, react.createElement)("path", {
|
|
1396
|
+
d: "M3.5 5.5L8 10l4.5-4.5",
|
|
1397
|
+
stroke: "currentColor",
|
|
1398
|
+
strokeWidth: 1.6,
|
|
1399
|
+
strokeLinecap: "round",
|
|
1400
|
+
strokeLinejoin: "round"
|
|
1401
|
+
}));
|
|
1402
|
+
}
|
|
1403
|
+
//#endregion
|
|
1404
|
+
//#region src/client/components/ProgressView.tsx
|
|
1405
|
+
/**
|
|
1406
|
+
* Inline progress block shared by the install and uninstall dialogs:
|
|
1407
|
+
* a real percentage bar (server-estimated, client-polled). No live terminal
|
|
1408
|
+
* window — pnpm emits nothing during git clone/build, so a log pane would be
|
|
1409
|
+
* empty for most of the run; failures surface their full output in ErrorModal.
|
|
1410
|
+
*/
|
|
1411
|
+
function ProgressView({ task }) {
|
|
1412
|
+
return (0, react.createElement)("div", { className: Modal_module_css_default.progress }, (0, react.createElement)("div", { className: Modal_module_css_default.progressHead }, (0, react.createElement)("span", { className: Modal_module_css_default.progressText }, `${Math.round(task.progress)}%`)), (0, react.createElement)("div", { className: Modal_module_css_default.progressTrack }, (0, react.createElement)("div", {
|
|
1413
|
+
className: task.status === "failed" ? `${Modal_module_css_default.progressFill} ${Modal_module_css_default.progressFillFail}` : Modal_module_css_default.progressFill,
|
|
1414
|
+
style: { width: `${task.progress}%` }
|
|
1415
|
+
})));
|
|
1416
|
+
}
|
|
1417
|
+
//#endregion
|
|
1418
|
+
//#region src/client/components/modals.tsx
|
|
1419
|
+
/**
|
|
1420
|
+
* Dialog layer for the Plugin Hub: the install-confirm dialog, the uninstall
|
|
1421
|
+
* dialog and the global toast. Both dialogs lock themselves while a mutation
|
|
1422
|
+
* or restart is running, then switch to a result view offering an immediate
|
|
1423
|
+
* restart or a "later" deferral.
|
|
1424
|
+
*/
|
|
1425
|
+
/** 完成结果视图:绿色对勾 + 标题/描述 + 「稍后重启 / 立即重启」按钮对(部分插件需重启后才会挂载) */
|
|
1426
|
+
function ResultView({ title, desc, t, restarting, onRestart, onClose }) {
|
|
1427
|
+
return (0, react.createElement)("div", { className: Modal_module_css_default.result }, (0, react.createElement)("div", { className: Modal_module_css_default.resultCheck }, (0, react.createElement)("svg", {
|
|
1428
|
+
className: Modal_module_css_default.resultCheckIcon,
|
|
1429
|
+
viewBox: "0 0 16 16",
|
|
1430
|
+
width: 20,
|
|
1431
|
+
height: 20,
|
|
1432
|
+
fill: "none",
|
|
1433
|
+
"aria-hidden": "true"
|
|
1434
|
+
}, (0, react.createElement)("path", {
|
|
1435
|
+
d: "M2.5 8.5l3.5 3.5 7.5-7.5",
|
|
1436
|
+
stroke: "currentColor",
|
|
1437
|
+
strokeWidth: 2,
|
|
1438
|
+
strokeLinecap: "round",
|
|
1439
|
+
strokeLinejoin: "round"
|
|
1440
|
+
}))), (0, react.createElement)("div", { className: Modal_module_css_default.resultTitle }, title), (0, react.createElement)("div", { className: Modal_module_css_default.resultDesc }, desc), (0, react.createElement)("div", { className: Modal_module_css_default.resultRestarting }, restarting ? t("restarting") : t("restartHint")), (0, react.createElement)("div", { className: Modal_module_css_default.modalActions }, (0, react.createElement)("button", {
|
|
1441
|
+
className: Modal_module_css_default.restartLater,
|
|
1442
|
+
onClick: onClose,
|
|
1443
|
+
disabled: restarting
|
|
1444
|
+
}, t("restartLater")), (0, react.createElement)("button", {
|
|
1445
|
+
className: Modal_module_css_default.restartNow,
|
|
1446
|
+
onClick: onRestart,
|
|
1447
|
+
disabled: restarting
|
|
1448
|
+
}, restarting ? t("restarting") : t("restartNow"))));
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* 信任确认弹窗:安装进入后台队列后弹窗仍可关闭(任务继续),
|
|
1452
|
+
* 只在本任务执行中展示实时进度;完成后切换为结果视图,与卸载一致。
|
|
1453
|
+
*/
|
|
1454
|
+
function InstallModal(props) {
|
|
1455
|
+
const { plugin, done, task, t, langPath, restarting, submitting, update, onClose, onCopy, onInstall, onRestart } = props;
|
|
1456
|
+
const busy = submitting || task !== null && (task.status === "pending" || task.status === "running");
|
|
1457
|
+
const name = plugin.displayName ?? plugin.slug;
|
|
1458
|
+
const busyTitle = (label) => langPath === "zh/" ? `${name} 插件${label}` : `${label} ${name}`;
|
|
1459
|
+
const title = busy ? task && task.status === "pending" ? busyTitle(update ? t("queuedUpdateTitle") : t("queuedTitle")) : busyTitle(update ? t("updating") : t("installing")) : done ? update ? t("updateResultTitle") : t("installResultTitle") : update ? t("confirmUpdateTitle") : t("confirmTitle");
|
|
1460
|
+
return (0, react.createElement)("div", {
|
|
1461
|
+
className: Modal_module_css_default.overlay,
|
|
1462
|
+
onClick: (e) => {
|
|
1463
|
+
if (e.target === e.currentTarget) onClose();
|
|
1464
|
+
}
|
|
1465
|
+
}, (0, react.createElement)("div", {
|
|
1466
|
+
className: Modal_module_css_default.modal,
|
|
1467
|
+
role: "dialog",
|
|
1468
|
+
"aria-modal": "true"
|
|
1469
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.modalHead }, (0, react.createElement)("div", { className: busy ? `${Modal_module_css_default.modalTitle} ${task && task.status === "pending" ? Modal_module_css_default.modalTitleQueued : Modal_module_css_default.modalTitleBusy}` : Modal_module_css_default.modalTitle }, title), (0, react.createElement)("button", {
|
|
1470
|
+
className: Modal_module_css_default.modalClose,
|
|
1471
|
+
"aria-label": t("confirmCancel"),
|
|
1472
|
+
onClick: () => onClose()
|
|
1473
|
+
}, (0, react.createElement)(CloseIcon))), done ? (0, react.createElement)(ResultView, {
|
|
1474
|
+
title: update ? t("updateResultTitle") : t("installResultTitle"),
|
|
1475
|
+
desc: update ? t("updateResultDesc") : t("installResultDesc"),
|
|
1476
|
+
t,
|
|
1477
|
+
restarting,
|
|
1478
|
+
onRestart,
|
|
1479
|
+
onClose
|
|
1480
|
+
}) : (0, react.createElement)("div", { className: Modal_module_css_default.modalBody }, (0, react.createElement)("div", { className: Modal_module_css_default.trustHint }, t("confirmDesc")), (0, react.createElement)("div", { className: Modal_module_css_default.modalRow }, (0, react.createElement)("span", { className: Modal_module_css_default.modalLabel }, t("confirmPlugin")), (0, react.createElement)("span", {
|
|
1481
|
+
className: Modal_module_css_default.modalValue,
|
|
1482
|
+
title: plugin.displayName ?? plugin.slug
|
|
1483
|
+
}, plugin.displayName ?? plugin.slug)), plugin.source?.repo ? (0, react.createElement)("div", { className: Modal_module_css_default.modalRow }, (0, react.createElement)("span", { className: Modal_module_css_default.modalLabel }, t("confirmSource")), (0, react.createElement)("a", {
|
|
1484
|
+
className: Modal_module_css_default.modalLink,
|
|
1485
|
+
href: pluginDetailUrl(plugin, langPath),
|
|
1486
|
+
target: "_blank",
|
|
1487
|
+
rel: "noopener noreferrer",
|
|
1488
|
+
title: plugin.source.repo
|
|
1489
|
+
}, (0, react.createElement)(LinkIcon), plugin.source.repo)) : null, (0, react.createElement)("div", {
|
|
1490
|
+
className: Modal_module_css_default.modalCmd,
|
|
1491
|
+
onClick: onCopy,
|
|
1492
|
+
title: t("copyInstallCommand"),
|
|
1493
|
+
role: "button",
|
|
1494
|
+
tabIndex: 0
|
|
1495
|
+
}, (0, react.createElement)("span", { className: Modal_module_css_default.modalCmdText }, `dsh plugin add github:${plugin.source?.repo ?? ""}`), (0, react.createElement)("span", { className: Modal_module_css_default.modalCmdCopy }, (0, react.createElement)(CopyIcon), t("copyCmdLabel"))), task && task.status === "pending" ? (0, react.createElement)("div", { className: Modal_module_css_default.queuedHint }, t("queuedHint")) : null, task ? (0, react.createElement)(ProgressView, { task }) : null, (0, react.createElement)("div", { className: Modal_module_css_default.modalActions }, (0, react.createElement)("button", {
|
|
1496
|
+
className: Modal_module_css_default.modalCopy,
|
|
1497
|
+
disabled: busy,
|
|
1498
|
+
onClick: onCopy
|
|
1499
|
+
}, t("copyInstallCommand")), (0, react.createElement)("button", {
|
|
1500
|
+
className: Modal_module_css_default.modalInstall,
|
|
1501
|
+
disabled: busy,
|
|
1502
|
+
onClick: onInstall
|
|
1503
|
+
}, busy ? task && task.status === "pending" ? update ? t("queuedUpdateTitle") : t("queuedTitle") : update ? t("updating") : t("installing") : update ? t("updateNow") : t("installNow"))))));
|
|
1504
|
+
}
|
|
1505
|
+
/** 卸载确认弹窗:确认/进行中(后台队列,可关闭);完成后切换为结果视图(成功即生效,仅「完成」关闭)。 */
|
|
1506
|
+
function UninstallModal(props) {
|
|
1507
|
+
const { plugin, done, task, t, langPath, restarting, submitting, onClose, onCancel, onCopyCommand, onConfirm, onRestart } = props;
|
|
1508
|
+
const busy = submitting || task !== null && (task.status === "pending" || task.status === "running");
|
|
1509
|
+
const name = plugin.displayName ?? plugin.slug;
|
|
1510
|
+
const busyTitle = (label) => langPath === "zh/" ? `${name} 插件${label}` : `${label} ${name}`;
|
|
1511
|
+
const title = busy ? task && task.status === "pending" ? busyTitle(t("queuedUninstallTitle")) : busyTitle(t("uninstalling")) : done ? t("uninstallResultTitle") : t("uninstallTitle");
|
|
1512
|
+
return (0, react.createElement)("div", {
|
|
1513
|
+
className: Modal_module_css_default.overlay,
|
|
1514
|
+
onClick: (e) => {
|
|
1515
|
+
if (e.target === e.currentTarget) onClose();
|
|
1516
|
+
}
|
|
1517
|
+
}, (0, react.createElement)("div", {
|
|
1518
|
+
className: Modal_module_css_default.modal,
|
|
1519
|
+
role: "dialog",
|
|
1520
|
+
"aria-modal": "true"
|
|
1521
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.modalHead }, (0, react.createElement)("div", { className: busy ? `${Modal_module_css_default.modalTitle} ${task !== null && task.status === "pending" ? Modal_module_css_default.modalTitleQueued : Modal_module_css_default.modalTitleBusy}` : Modal_module_css_default.modalTitle }, title), (0, react.createElement)("button", {
|
|
1522
|
+
className: Modal_module_css_default.modalClose,
|
|
1523
|
+
"aria-label": t("confirmCancel"),
|
|
1524
|
+
onClick: () => onClose()
|
|
1525
|
+
}, (0, react.createElement)(CloseIcon))), done ? (0, react.createElement)(ResultView, {
|
|
1526
|
+
title: t("uninstallResultTitle"),
|
|
1527
|
+
desc: t("uninstallResultDesc"),
|
|
1528
|
+
t,
|
|
1529
|
+
restarting,
|
|
1530
|
+
onRestart,
|
|
1531
|
+
onClose
|
|
1532
|
+
}) : (0, react.createElement)("div", { className: Modal_module_css_default.modalBody }, (0, react.createElement)("div", { className: Modal_module_css_default.modalDesc }, t("uninstallDesc")), (0, react.createElement)("div", { className: Modal_module_css_default.modalRow }, (0, react.createElement)("span", { className: Modal_module_css_default.modalLabel }, t("confirmPlugin")), (0, react.createElement)("span", {
|
|
1533
|
+
className: Modal_module_css_default.modalValue,
|
|
1534
|
+
title: plugin.displayName ?? plugin.slug
|
|
1535
|
+
}, plugin.displayName ?? plugin.slug)), plugin.source?.repo ? (0, react.createElement)("div", { className: Modal_module_css_default.modalRow }, (0, react.createElement)("span", { className: Modal_module_css_default.modalLabel }, t("confirmSource")), (0, react.createElement)("a", {
|
|
1536
|
+
className: Modal_module_css_default.modalLink,
|
|
1537
|
+
href: pluginDetailUrl(plugin, langPath),
|
|
1538
|
+
target: "_blank",
|
|
1539
|
+
rel: "noopener noreferrer",
|
|
1540
|
+
title: plugin.source.repo
|
|
1541
|
+
}, (0, react.createElement)(LinkIcon), plugin.source.repo)) : null, task && task.status === "pending" ? (0, react.createElement)("div", { className: Modal_module_css_default.queuedHint }, t("queuedHint")) : null, task ? (0, react.createElement)(ProgressView, { task }) : null, (0, react.createElement)("div", { className: Modal_module_css_default.modalActions }, (0, react.createElement)("button", {
|
|
1542
|
+
className: Modal_module_css_default.modalCancel,
|
|
1543
|
+
onClick: onCancel
|
|
1544
|
+
}, t("confirmCancel")), (0, react.createElement)("button", {
|
|
1545
|
+
className: Modal_module_css_default.modalCopy,
|
|
1546
|
+
disabled: busy,
|
|
1547
|
+
onClick: onCopyCommand
|
|
1548
|
+
}, t("copyUninstallCommand")), (0, react.createElement)("button", {
|
|
1549
|
+
className: Modal_module_css_default.uninstallConfirm,
|
|
1550
|
+
disabled: busy,
|
|
1551
|
+
onClick: onConfirm
|
|
1552
|
+
}, busy ? task && task.status === "pending" ? t("queuedUninstallTitle") : t("uninstalling") : t("uninstall"))))));
|
|
1553
|
+
}
|
|
1554
|
+
/** 预填插件仓库的 GitHub Issue 链接:标题带插件名,正文附完整错误信息,方便用户一键反馈。 */
|
|
1555
|
+
/** 安装/卸载失败弹窗:布局与失败记录一致(类型徽标 + 仓库超链接 + 隐蔽复制按钮),报错完整展示,底部一键提交 Issue。 */
|
|
1556
|
+
function ErrorModal({ message, repo, kind, t, env, onCopy, onClose }) {
|
|
1557
|
+
const failureKind = classifyFailure(message);
|
|
1558
|
+
return (0, react.createElement)("div", {
|
|
1559
|
+
className: Modal_module_css_default.overlay,
|
|
1560
|
+
onClick: (e) => {
|
|
1561
|
+
if (e.target === e.currentTarget) onClose();
|
|
1562
|
+
}
|
|
1563
|
+
}, (0, react.createElement)("div", {
|
|
1564
|
+
className: Modal_module_css_default.errorModal,
|
|
1565
|
+
role: "dialog",
|
|
1566
|
+
"aria-modal": "true"
|
|
1567
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.modalHead }, (0, react.createElement)("div", { className: Modal_module_css_default.errorTitle }, kind === "install" ? t("errorTitleInstall") : t("errorTitleUninstall")), (0, react.createElement)("button", {
|
|
1568
|
+
className: Modal_module_css_default.modalClose,
|
|
1569
|
+
"aria-label": t("errorClose"),
|
|
1570
|
+
onClick: onClose
|
|
1571
|
+
}, (0, react.createElement)(CloseIcon))), (0, react.createElement)("div", { className: Modal_module_css_default.modalBody }, (0, react.createElement)("div", { className: Modal_module_css_default.failRow }, (0, react.createElement)("div", { className: Modal_module_css_default.failHead }, (0, react.createElement)("span", { className: kind === "install" ? Modal_module_css_default.failKindInstall : Modal_module_css_default.failKindUninstall }, kind === "install" ? t("install") : t("uninstall")), repo ? (0, react.createElement)("a", {
|
|
1572
|
+
className: Modal_module_css_default.failRepo,
|
|
1573
|
+
href: pluginSiteUrl(repo),
|
|
1574
|
+
target: "_blank",
|
|
1575
|
+
rel: "noopener noreferrer",
|
|
1576
|
+
title: repo
|
|
1577
|
+
}, repo) : null, (0, react.createElement)("button", {
|
|
1578
|
+
className: Modal_module_css_default.errorCopySoft,
|
|
1579
|
+
onClick: () => onCopy(message)
|
|
1580
|
+
}, t("errorCopy"))), (0, react.createElement)("pre", { className: Modal_module_css_default.errorBox }, message), failureKind === "pluginPrepare" || failureKind === "pnpmIgnoredBuild" ? (0, react.createElement)("div", null, [(0, react.createElement)("div", { className: Modal_module_css_default.failPrepareHint }, failureKind === "pnpmIgnoredBuild" ? t("failIgnoredBuild") : /\[packaging\]/i.test(message) ? t("failPackagingHint") : t("failPrepareHint")), repo ? (0, react.createElement)("a", {
|
|
1581
|
+
className: Modal_module_css_default.failBigIssue,
|
|
1582
|
+
href: pluginIssueUrl(repo, message, env),
|
|
1583
|
+
target: "_blank",
|
|
1584
|
+
rel: "noopener noreferrer",
|
|
1585
|
+
title: t("failIssueHint")
|
|
1586
|
+
}, t("failIssueBig")) : null]) : repo ? (0, react.createElement)("a", {
|
|
1587
|
+
className: Modal_module_css_default.failBigIssue,
|
|
1588
|
+
href: pluginIssueUrl(repo, message, env),
|
|
1589
|
+
target: "_blank",
|
|
1590
|
+
rel: "noopener noreferrer",
|
|
1591
|
+
title: t("failIssueHint")
|
|
1592
|
+
}, t("failIssueBig")) : null), (0, react.createElement)("div", { className: Modal_module_css_default.modalActions }, (0, react.createElement)("button", {
|
|
1593
|
+
className: Modal_module_css_default.restartLater,
|
|
1594
|
+
onClick: onClose
|
|
1595
|
+
}, t("errorClose"))))));
|
|
1596
|
+
}
|
|
1597
|
+
/** 全局反馈 Toast:复制成功(反色)/ 安装完成(反色)/ 安装失败(红色)/ 卸载结果 */
|
|
1598
|
+
function Toast({ toast, t }) {
|
|
1599
|
+
const text = toast.kind === "copied" ? t("toastCopied") : toast.kind === "errCopied" ? t("errCopied") : toast.kind === "done" ? t("installDone") : toast.kind === "fail" ? t("installFail") : toast.kind === "removed" ? t("uninstallDone") : t("uninstallFail");
|
|
1600
|
+
const fail = toast.kind === "fail" || toast.kind === "removeFail";
|
|
1601
|
+
return (0, react.createElement)("div", {
|
|
1602
|
+
key: toast.id,
|
|
1603
|
+
className: fail ? `${Modal_module_css_default.toast} ${Modal_module_css_default.toastFail}` : Modal_module_css_default.toast
|
|
1604
|
+
}, text);
|
|
1605
|
+
}
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region src/client/components/NotificationsModal.tsx
|
|
1608
|
+
/**
|
|
1609
|
+
* Notification-center dialog: a persistent log of every settled install /
|
|
1610
|
+
* remove task — successes and failures alike.
|
|
1611
|
+
*
|
|
1612
|
+
* Records are written to localStorage at settle time (see lib/failures.ts),
|
|
1613
|
+
* so a result is never lost even when the dialog was dismissed or the user
|
|
1614
|
+
* was away. Each entry carries a circular status badge (green check for
|
|
1615
|
+
* success, red cross for failure) with white glyph and message text;
|
|
1616
|
+
* failures keep their copy / fix / file-an-issue actions. Opened from the
|
|
1617
|
+
* header entry button.
|
|
1618
|
+
*/
|
|
1619
|
+
/** 记录时间紧凑展示:今年内 MM-DD HH:mm,跨年补年份前缀。 */
|
|
1620
|
+
function fmtTime(at) {
|
|
1621
|
+
const d = new Date(at);
|
|
1622
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1623
|
+
const mmdd = `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
1624
|
+
return d.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? mmdd : `${d.getFullYear()}-${mmdd}`;
|
|
1625
|
+
}
|
|
1626
|
+
/** 圆形状态徽标内的白色图形:成功为对勾、失败为叉。 */
|
|
1627
|
+
function BadgeGlyph({ ok }) {
|
|
1628
|
+
return (0, react.createElement)("svg", {
|
|
1629
|
+
className: Modal_module_css_default.noticeBadgeIcon,
|
|
1630
|
+
viewBox: "0 0 16 16",
|
|
1631
|
+
width: 12,
|
|
1632
|
+
height: 12,
|
|
1633
|
+
fill: "none",
|
|
1634
|
+
"aria-hidden": "true"
|
|
1635
|
+
}, ok ? (0, react.createElement)("path", {
|
|
1636
|
+
d: "M3 8.5l3.5 3.5 6.5-6.5",
|
|
1637
|
+
stroke: "#ffffff",
|
|
1638
|
+
strokeWidth: 2,
|
|
1639
|
+
strokeLinecap: "round",
|
|
1640
|
+
strokeLinejoin: "round"
|
|
1641
|
+
}) : (0, react.createElement)("path", {
|
|
1642
|
+
d: "M4 4l8 8M12 4L4 12",
|
|
1643
|
+
stroke: "#ffffff",
|
|
1644
|
+
strokeWidth: 2,
|
|
1645
|
+
strokeLinecap: "round"
|
|
1646
|
+
}));
|
|
1647
|
+
}
|
|
1648
|
+
function NotificationsModal({ records, tasks, pendingRestarts, t, env, onClose, onCopy, onClear, cancelTask, restarting, onRestart }) {
|
|
1649
|
+
return (0, react.createElement)("div", {
|
|
1650
|
+
className: Modal_module_css_default.overlay,
|
|
1651
|
+
onClick: (e) => {
|
|
1652
|
+
if (e.target === e.currentTarget) onClose();
|
|
1653
|
+
}
|
|
1654
|
+
}, (0, react.createElement)("div", {
|
|
1655
|
+
className: Modal_module_css_default.errorModal,
|
|
1656
|
+
role: "dialog",
|
|
1657
|
+
"aria-modal": "true"
|
|
1658
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.modalHead }, (0, react.createElement)("div", { className: Modal_module_css_default.modalTitle }, t("notifications")), (0, react.createElement)("button", {
|
|
1659
|
+
className: Modal_module_css_default.modalClose,
|
|
1660
|
+
"aria-label": t("errorClose"),
|
|
1661
|
+
onClick: onClose
|
|
1662
|
+
}, (0, react.createElement)(CloseIcon))), (0, react.createElement)("div", { className: Modal_module_css_default.modalBody }, (0, react.createElement)("div", { className: Modal_module_css_default.errorHint }, t("notificationsDesc")), tasks.length > 0 || pendingRestarts.length > 0 ? (0, react.createElement)("div", null, [pendingRestarts.length > 0 ? (0, react.createElement)("div", { className: Modal_module_css_default.queueSection }, (0, react.createElement)("div", { className: Modal_module_css_default.queueSectionTitle }, t("sectionPendingRestart")), pendingRestarts.map((p) => (0, react.createElement)("div", {
|
|
1663
|
+
key: `restart-${p.target}`,
|
|
1664
|
+
className: Modal_module_css_default.queueRow
|
|
1665
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.queueRowHead }, (0, react.createElement)("span", { className: `${Modal_module_css_default.failKind} ${p.kind === "uninstall" ? Modal_module_css_default.failKindUninstall : Modal_module_css_default.failKindInstall}` }, p.kind === "uninstall" ? t("uninstall") : t("install")), (0, react.createElement)("span", {
|
|
1666
|
+
className: Modal_module_css_default.queueRowTarget,
|
|
1667
|
+
title: p.target
|
|
1668
|
+
}, p.target)), p.desc ? (0, react.createElement)("div", {
|
|
1669
|
+
className: Modal_module_css_default.queueRowDesc,
|
|
1670
|
+
title: p.desc
|
|
1671
|
+
}, p.desc) : null, (0, react.createElement)("div", { className: Modal_module_css_default.queueRowBody }, (0, react.createElement)("span", { className: Modal_module_css_default.pendingRowStatus }, p.kind === "uninstall" ? t("restartPendingHintUninstall") : t("restartPendingHint")), (0, react.createElement)("span", { className: Modal_module_css_default.pendingRowActions }, (0, react.createElement)("button", {
|
|
1672
|
+
className: Modal_module_css_default.restartLater,
|
|
1673
|
+
disabled: restarting,
|
|
1674
|
+
onClick: onClose
|
|
1675
|
+
}, t("restartLater")), (0, react.createElement)("button", {
|
|
1676
|
+
className: Modal_module_css_default.restartNow,
|
|
1677
|
+
disabled: restarting,
|
|
1678
|
+
onClick: onRestart
|
|
1679
|
+
}, restarting ? t("restarting") : t("restartNow"))))))) : null, tasks.length > 0 ? (0, react.createElement)("div", { className: Modal_module_css_default.queueSection }, (0, react.createElement)("div", { className: Modal_module_css_default.queueSectionTitle }, t("sectionInProgress")), tasks.map((q) => (0, react.createElement)("div", {
|
|
1680
|
+
key: q.id,
|
|
1681
|
+
className: Modal_module_css_default.queueRow
|
|
1682
|
+
}, (0, react.createElement)("div", { className: Modal_module_css_default.queueRowHead }, (0, react.createElement)("span", { className: `${Modal_module_css_default.failKind} ${q.kind === "install" ? Modal_module_css_default.failKindInstall : Modal_module_css_default.failKindUninstall}` }, q.kind === "install" ? t("install") : t("uninstall")), (0, react.createElement)("span", {
|
|
1683
|
+
className: Modal_module_css_default.queueRowTarget,
|
|
1684
|
+
title: q.target
|
|
1685
|
+
}, q.target), (0, react.createElement)("button", {
|
|
1686
|
+
className: Modal_module_css_default.stripCancel,
|
|
1687
|
+
disabled: q.status === "cancelling",
|
|
1688
|
+
onClick: (e) => {
|
|
1689
|
+
e.stopPropagation();
|
|
1690
|
+
cancelTask(q.id);
|
|
1691
|
+
}
|
|
1692
|
+
}, t("cancelTask"))), q.desc ? (0, react.createElement)("div", {
|
|
1693
|
+
className: Modal_module_css_default.queueRowDesc,
|
|
1694
|
+
title: q.desc
|
|
1695
|
+
}, q.desc) : null, (0, react.createElement)("div", { className: Modal_module_css_default.queueRowBody }, (0, react.createElement)("span", { className: Modal_module_css_default.queueRowStatus }, q.status === "running" ? q.kind === "install" ? t("installing") : t("uninstalling") : q.status === "cancelling" ? t("cancelling") : q.kind === "install" ? t("queuedTitle") : t("queuedUninstallTitle")), (0, react.createElement)("div", { className: `${Modal_module_css_default.progressTrack} ${Modal_module_css_default.queueRowTrack}` }, (0, react.createElement)("div", {
|
|
1696
|
+
className: Modal_module_css_default.progressFill,
|
|
1697
|
+
style: { width: `${q.progress}%` }
|
|
1698
|
+
})), (0, react.createElement)("span", { className: Modal_module_css_default.queueRowPct }, `${q.progress}%`))))) : null]) : null, records.length === 0 ? (0, react.createElement)("div", { className: Modal_module_css_default.failEmpty }, t("notificationsEmpty")) : (0, react.createElement)("div", { className: Modal_module_css_default.noticeList }, records.map((r) => {
|
|
1699
|
+
return (0, react.createElement)("div", {
|
|
1700
|
+
key: r.id,
|
|
1701
|
+
className: Modal_module_css_default.noticeRow
|
|
1702
|
+
}, (0, react.createElement)("div", { className: r.ok ? Modal_module_css_default.noticeBadgeOk : Modal_module_css_default.noticeBadgeFail }, (0, react.createElement)(BadgeGlyph, { ok: r.ok })), (0, react.createElement)("div", { className: Modal_module_css_default.noticeMain }, (0, react.createElement)("div", { className: Modal_module_css_default.noticeHead }, (0, react.createElement)("span", { className: r.ok ? Modal_module_css_default.noticeTextOk : Modal_module_css_default.noticeTextFail }, r.ok ? r.kind === "install" ? t("installDone") : t("uninstallDone") : r.kind === "install" ? t("errorTitleInstall") : t("errorTitleUninstall")), r.repo ? (0, react.createElement)("a", {
|
|
1703
|
+
className: Modal_module_css_default.failRepo,
|
|
1704
|
+
href: pluginSiteUrl(r.repo),
|
|
1705
|
+
target: "_blank",
|
|
1706
|
+
rel: "noopener noreferrer",
|
|
1707
|
+
title: r.repo
|
|
1708
|
+
}, r.repo) : null, (0, react.createElement)("span", {
|
|
1709
|
+
className: Modal_module_css_default.failTime,
|
|
1710
|
+
title: new Date(r.at).toLocaleString()
|
|
1711
|
+
}, fmtTime(r.at)), !r.ok && (0, react.createElement)("button", {
|
|
1712
|
+
className: Modal_module_css_default.failCopy,
|
|
1713
|
+
onClick: () => onCopy(r.message)
|
|
1714
|
+
}, t("failCopy"))), !r.ok && (() => {
|
|
1715
|
+
const kind = classifyFailure(r.message);
|
|
1716
|
+
if (kind === "pluginPrepare" || kind === "pnpmIgnoredBuild") return (0, react.createElement)("div", null, [(0, react.createElement)("div", { className: Modal_module_css_default.failPrepareHint }, kind === "pnpmIgnoredBuild" ? t("failIgnoredBuild") : /\[packaging\]/i.test(r.message) ? t("failPackagingHint") : t("failPrepareHint")), r.repo ? (0, react.createElement)("a", {
|
|
1717
|
+
className: Modal_module_css_default.failBigIssue,
|
|
1718
|
+
href: pluginIssueUrl(r.repo, r.message, env),
|
|
1719
|
+
target: "_blank",
|
|
1720
|
+
rel: "noopener noreferrer",
|
|
1721
|
+
title: t("failIssueHint")
|
|
1722
|
+
}, t("failIssueBig")) : null]);
|
|
1723
|
+
return r.repo ? (0, react.createElement)("a", {
|
|
1724
|
+
className: Modal_module_css_default.failBigIssue,
|
|
1725
|
+
href: pluginIssueUrl(r.repo, r.message, env),
|
|
1726
|
+
target: "_blank",
|
|
1727
|
+
rel: "noopener noreferrer",
|
|
1728
|
+
title: t("failIssueHint")
|
|
1729
|
+
}, t("failIssueBig")) : null;
|
|
1730
|
+
})()));
|
|
1731
|
+
})), (0, react.createElement)("div", { className: Modal_module_css_default.modalActions }, records.length > 0 ? (0, react.createElement)("button", {
|
|
1732
|
+
className: Modal_module_css_default.failClear,
|
|
1733
|
+
onClick: onClear
|
|
1734
|
+
}, t("notificationsClear")) : null, (0, react.createElement)("button", {
|
|
1735
|
+
className: Modal_module_css_default.restartNow,
|
|
1736
|
+
onClick: onClose
|
|
1737
|
+
}, t("errorClose"))))));
|
|
1738
|
+
}
|
|
1739
|
+
//#endregion
|
|
1740
|
+
//#region src/client/components/CatalogHeader.tsx
|
|
1741
|
+
/**
|
|
1742
|
+
* Section header: brand title row (H1 + open-site button) + tagline, followed
|
|
1743
|
+
* by the purple ad banner that promotes the catalog stats.
|
|
1744
|
+
*/
|
|
1745
|
+
function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang }) {
|
|
1746
|
+
return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", { className: Header_module_css_default.header }, (0, react.createElement)("div", { className: Header_module_css_default.headerTitleRow }, (0, react.createElement)("a", {
|
|
1747
|
+
className: Header_module_css_default.brandTitle,
|
|
1748
|
+
href: `${SITE_URL}${langPath}`,
|
|
1749
|
+
target: "_blank",
|
|
1750
|
+
rel: "noopener noreferrer",
|
|
1751
|
+
title: t("openHint"),
|
|
1752
|
+
"aria-label": t("openHint")
|
|
1753
|
+
}, (0, react.createElement)(LogoIcon), (0, react.createElement)("h1", { className: Header_module_css_default.title }, t("title"), (0, react.createElement)("span", { className: Header_module_css_default.version }, ` v${PLUGIN_VERSION}`))), (0, react.createElement)("div", { className: Header_module_css_default.headerRight }, (0, react.createElement)("button", {
|
|
1754
|
+
className: Header_module_css_default.langBtn,
|
|
1755
|
+
type: "button",
|
|
1756
|
+
onClick: onToggleLang,
|
|
1757
|
+
title: t("toggleLangHint"),
|
|
1758
|
+
"aria-label": t("toggleLangHint")
|
|
1759
|
+
}, langPath === "zh/" ? "EN" : "中文"), (0, react.createElement)("a", {
|
|
1760
|
+
className: Header_module_css_default.githubLink,
|
|
1761
|
+
href: GITHUB_URL,
|
|
1762
|
+
target: "_blank",
|
|
1763
|
+
rel: "noopener noreferrer",
|
|
1764
|
+
title: t("githubHint"),
|
|
1765
|
+
"aria-label": t("githubHint")
|
|
1766
|
+
}, (0, react.createElement)(GitHubIcon)))), (0, react.createElement)("a", {
|
|
1767
|
+
className: Header_module_css_default.taglineLink,
|
|
1768
|
+
href: `${SITE_URL}${langPath}`,
|
|
1769
|
+
target: "_blank",
|
|
1770
|
+
rel: "noopener noreferrer",
|
|
1771
|
+
title: t("openHint")
|
|
1772
|
+
}, (0, react.createElement)("div", { className: Header_module_css_default.tagline }, t("tagline", {
|
|
1773
|
+
total: statsTotal,
|
|
1774
|
+
verified: statsVerified
|
|
1775
|
+
})))), (0, react.createElement)("a", {
|
|
1776
|
+
className: Header_module_css_default.adBanner,
|
|
1777
|
+
href: `${SITE_URL}${langPath}`,
|
|
1778
|
+
target: "_blank",
|
|
1779
|
+
rel: "noopener noreferrer"
|
|
1780
|
+
}, (0, react.createElement)("span", { className: Header_module_css_default.adBadge }, t("adBadge")), (0, react.createElement)("span", { className: Header_module_css_default.adText }, t("ad", {
|
|
1781
|
+
total: statsTotal,
|
|
1782
|
+
verified: statsVerified
|
|
1783
|
+
})), (0, react.createElement)("span", { className: Header_module_css_default.adArrow }, "↗")));
|
|
1784
|
+
}
|
|
1785
|
+
//#endregion
|
|
1786
|
+
//#region src/client/components/CategoryTabs.tsx
|
|
1787
|
+
/**
|
|
1788
|
+
* Category tabs: an "all" chip followed by one chip per catalog category.
|
|
1789
|
+
* The "all" chip carries the total plugin count; the per-category chips
|
|
1790
|
+
* carry no counts so their widths stay uniform.
|
|
1791
|
+
*/
|
|
1792
|
+
function CategoryTabs({ category, setCategory, allLabel, totalCount, langKey }) {
|
|
1793
|
+
return (0, react.createElement)("div", { className: Header_module_css_default.tabs }, (0, react.createElement)("button", {
|
|
1794
|
+
key: "all",
|
|
1795
|
+
className: category === "all" ? Header_module_css_default.tabActive : Header_module_css_default.tab,
|
|
1796
|
+
onClick: () => setCategory("all")
|
|
1797
|
+
}, allLabel, (0, react.createElement)("span", { className: Header_module_css_default.tabCount }, totalCount)), CATEGORY_ORDER.map((id) => (0, react.createElement)("button", {
|
|
1798
|
+
key: id,
|
|
1799
|
+
className: category === id ? Header_module_css_default.tabActive : Header_module_css_default.tab,
|
|
1800
|
+
onClick: () => setCategory(id)
|
|
1801
|
+
}, categoryLabel(CATEGORY_SHORT_LABELS, id, langKey))));
|
|
1802
|
+
}
|
|
1803
|
+
//#endregion
|
|
1804
|
+
//#region \0dsh-css:src/client/styles/Dropdown.module.css.mjs
|
|
1805
|
+
const css$2 = ".B_Gxsq_dropdown{flex-shrink:0;display:inline-flex;position:relative}.B_Gxsq_dropdownBtn{height:24px;color:var(--hub-text-primary);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;background:0 0;border:none;border-radius:6px;align-items:center;gap:6px;padding:0 8px;font-size:12px;line-height:22px;transition:background .12s;display:inline-flex}.B_Gxsq_dropdownBtn:hover{background:var(--hub-bg-hover)}.B_Gxsq_dropdownLabel{text-overflow:ellipsis;min-width:0;overflow:hidden}.B_Gxsq_dropdownArrow,.B_Gxsq_dropdownArrowOpen{color:var(--hub-text-tertiary);justify-content:center;align-items:center;transition:transform .12s;display:inline-flex}.B_Gxsq_dropdownArrowOpen{transform:rotate(180deg)}.B_Gxsq_dropdownPanel{z-index:50;background:var(--hub-bg-1);border:1px solid var(--hub-border-2);border-radius:8px;flex-direction:column;gap:2px;min-width:100%;padding:4px;display:flex;position:absolute;top:calc(100% + 4px);left:0;box-shadow:0 8px 24px #00000024}.B_Gxsq_dropdownItem,.B_Gxsq_dropdownItemActive{cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;text-align:left;border:none;border-radius:6px;justify-content:space-between;align-items:center;gap:10px;padding:4px 9px;font-size:12px;line-height:18px;transition:background .12s,color .12s;display:flex}.B_Gxsq_dropdownItem{color:var(--hub-text-primary);background:0 0}.B_Gxsq_dropdownItem:hover{background:var(--hub-bg-hover)}.B_Gxsq_dropdownItemActive{color:var(--hub-text-on-fill);background:var(--hub-btn-fill)}.B_Gxsq_dropdownItemLabel{text-overflow:ellipsis;min-width:0;overflow:hidden}.B_Gxsq_dropdownCount,.B_Gxsq_dropdownCountActive{text-align:center;border-radius:999px;min-width:16px;padding:0 6px;font-size:10px;line-height:14px}.B_Gxsq_dropdownCount{color:var(--hub-text-tertiary);background:var(--hub-bg-btn)}.B_Gxsq_dropdownItemActive .B_Gxsq_dropdownCountActive{color:var(--hub-text-on-fill);background:var(--hub-bg-on-fill)}";
|
|
1806
|
+
const tagId$2 = "dsh-plugin/Dropdown.module.css";
|
|
1807
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
1808
|
+
const tag = document.createElement("style");
|
|
1809
|
+
tag.dataset.plugin = "dsh-plugin";
|
|
1810
|
+
tag.dataset.pluginCss = tagId$2;
|
|
1811
|
+
tag.textContent = css$2;
|
|
1812
|
+
document.head.appendChild(tag);
|
|
1813
|
+
}
|
|
1814
|
+
var Dropdown_module_css_default = {
|
|
1815
|
+
"dropdownPanel": "B_Gxsq_dropdownPanel",
|
|
1816
|
+
"dropdownCountActive": "B_Gxsq_dropdownCountActive",
|
|
1817
|
+
"dropdownCount": "B_Gxsq_dropdownCount",
|
|
1818
|
+
"dropdown": "B_Gxsq_dropdown",
|
|
1819
|
+
"dropdownArrowOpen": "B_Gxsq_dropdownArrowOpen",
|
|
1820
|
+
"dropdownLabel": "B_Gxsq_dropdownLabel",
|
|
1821
|
+
"dropdownItemActive": "B_Gxsq_dropdownItemActive",
|
|
1822
|
+
"dropdownItem": "B_Gxsq_dropdownItem",
|
|
1823
|
+
"dropdownBtn": "B_Gxsq_dropdownBtn",
|
|
1824
|
+
"dropdownArrow": "B_Gxsq_dropdownArrow",
|
|
1825
|
+
"dropdownItemLabel": "B_Gxsq_dropdownItemLabel"
|
|
1826
|
+
};
|
|
1827
|
+
//#endregion
|
|
1828
|
+
//#region src/client/components/Dropdown.tsx
|
|
1829
|
+
/**
|
|
1830
|
+
* Custom dropdown (native <select> replacement).
|
|
1831
|
+
*
|
|
1832
|
+
* macOS/浏览器原生下拉无法跟随宿主主题,这里自绘:触发器按钮 + 绝对定位面板,
|
|
1833
|
+
* 点外部/Esc 关闭;选项 hover 加重底色、激活项深色实心高亮,全部走 --dsw-alias-* 主题变量。
|
|
1834
|
+
*/
|
|
1835
|
+
function Dropdown({ value, options, onChange, title }) {
|
|
1836
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1837
|
+
const rootRef = (0, react.useRef)(null);
|
|
1838
|
+
(0, react.useEffect)(() => {
|
|
1839
|
+
if (!open) return;
|
|
1840
|
+
const onDoc = (e) => {
|
|
1841
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
|
|
1842
|
+
};
|
|
1843
|
+
const onKey = (e) => {
|
|
1844
|
+
if (e.key === "Escape") setOpen(false);
|
|
1845
|
+
};
|
|
1846
|
+
document.addEventListener("mousedown", onDoc);
|
|
1847
|
+
document.addEventListener("keydown", onKey);
|
|
1848
|
+
return () => {
|
|
1849
|
+
document.removeEventListener("mousedown", onDoc);
|
|
1850
|
+
document.removeEventListener("keydown", onKey);
|
|
1851
|
+
};
|
|
1852
|
+
}, [open]);
|
|
1853
|
+
const current = options.find((o) => o.value === value);
|
|
1854
|
+
return (0, react.createElement)("div", {
|
|
1855
|
+
className: Dropdown_module_css_default.dropdown,
|
|
1856
|
+
ref: rootRef
|
|
1857
|
+
}, (0, react.createElement)("button", {
|
|
1858
|
+
className: Dropdown_module_css_default.dropdownBtn,
|
|
1859
|
+
type: "button",
|
|
1860
|
+
title,
|
|
1861
|
+
"aria-haspopup": "listbox",
|
|
1862
|
+
"aria-expanded": open,
|
|
1863
|
+
onClick: () => setOpen((v) => !v)
|
|
1864
|
+
}, (0, react.createElement)("span", { className: Dropdown_module_css_default.dropdownLabel }, current?.label ?? value), (0, react.createElement)("span", { className: open ? Dropdown_module_css_default.dropdownArrowOpen : Dropdown_module_css_default.dropdownArrow }, (0, react.createElement)(ChevronDownIcon))), open && (0, react.createElement)("div", {
|
|
1865
|
+
className: Dropdown_module_css_default.dropdownPanel,
|
|
1866
|
+
role: "listbox"
|
|
1867
|
+
}, options.map((o) => (0, react.createElement)("button", {
|
|
1868
|
+
key: o.value,
|
|
1869
|
+
type: "button",
|
|
1870
|
+
role: "option",
|
|
1871
|
+
"aria-selected": o.value === value,
|
|
1872
|
+
className: o.value === value ? Dropdown_module_css_default.dropdownItemActive : Dropdown_module_css_default.dropdownItem,
|
|
1873
|
+
onClick: () => {
|
|
1874
|
+
onChange(o.value);
|
|
1875
|
+
setOpen(false);
|
|
1876
|
+
}
|
|
1877
|
+
}, (0, react.createElement)("span", { className: Dropdown_module_css_default.dropdownItemLabel }, o.label), typeof o.count === "number" ? (0, react.createElement)("span", { className: o.value === value ? Dropdown_module_css_default.dropdownCountActive : Dropdown_module_css_default.dropdownCount }, o.count) : null))));
|
|
1878
|
+
}
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region src/client/components/CatalogControls.tsx
|
|
1881
|
+
/**
|
|
1882
|
+
* Toolbar above the list: search input + sort dropdown + installed / not
|
|
1883
|
+
* installed filter buttons + the notifications entry. All buttons share the
|
|
1884
|
+
* same size (24px high, like the notification entry button).
|
|
1885
|
+
*/
|
|
1886
|
+
function CatalogControls({ query, setQuery, sort, setSort, installedFilter, setInstalledFilter, installedCount, notInstalledCount, t, resultText, noticeCount, onOpenNotifications }) {
|
|
1887
|
+
return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", { className: Header_module_css_default.searchRow }, (0, react.createElement)("input", {
|
|
1888
|
+
className: Header_module_css_default.search,
|
|
1889
|
+
type: "search",
|
|
1890
|
+
placeholder: t("search"),
|
|
1891
|
+
value: query,
|
|
1892
|
+
spellCheck: false,
|
|
1893
|
+
onInput: (e) => setQuery(e.target.value)
|
|
1894
|
+
})), (0, react.createElement)("div", { className: Header_module_css_default.controls }, resultText ? (0, react.createElement)("span", { className: Header_module_css_default.filterResults }, ...resultText.split(/(\d+)/).map((part, i) => /^\d+$/.test(part) ? (0, react.createElement)("span", {
|
|
1895
|
+
key: i,
|
|
1896
|
+
className: Header_module_css_default.resultCount
|
|
1897
|
+
}, part) : part)) : null, (0, react.createElement)("button", {
|
|
1898
|
+
className: installedFilter === "all" ? Header_module_css_default.installedBtnActive : Header_module_css_default.installedBtn,
|
|
1899
|
+
onClick: () => setInstalledFilter("all"),
|
|
1900
|
+
title: t("filterAllHint"),
|
|
1901
|
+
"aria-pressed": installedFilter === "all"
|
|
1902
|
+
}, t("all")), Dropdown({
|
|
1903
|
+
value: sort,
|
|
1904
|
+
options: SORTS.map((key) => ({
|
|
1905
|
+
value: key,
|
|
1906
|
+
label: t(key)
|
|
1907
|
+
})),
|
|
1908
|
+
onChange: setSort
|
|
1909
|
+
}), (0, react.createElement)("button", {
|
|
1910
|
+
className: installedFilter === "installed" ? Header_module_css_default.installedBtnActive : installedCount === 0 ? Header_module_css_default.installedBtnDisabled : Header_module_css_default.installedBtn,
|
|
1911
|
+
onClick: () => setInstalledFilter("installed"),
|
|
1912
|
+
disabled: installedCount === 0,
|
|
1913
|
+
title: installedCount === 0 ? t("filterInstalledNone") : t("filterInstalledHint"),
|
|
1914
|
+
"aria-pressed": installedFilter === "installed"
|
|
1915
|
+
}, t("installed"), (0, react.createElement)("span", { className: installedFilter === "installed" ? Header_module_css_default.segCountActive : Header_module_css_default.segCount }, installedCount)), (0, react.createElement)("button", {
|
|
1916
|
+
className: installedFilter === "notInstalled" ? Header_module_css_default.installedBtnActive : notInstalledCount === 0 ? Header_module_css_default.installedBtnDisabled : Header_module_css_default.installedBtn,
|
|
1917
|
+
onClick: () => setInstalledFilter("notInstalled"),
|
|
1918
|
+
disabled: notInstalledCount === 0,
|
|
1919
|
+
title: notInstalledCount === 0 ? t("filterNotInstalledNone") : t("filterNotInstalledHint"),
|
|
1920
|
+
"aria-pressed": installedFilter === "notInstalled"
|
|
1921
|
+
}, t("notInstalled"), (0, react.createElement)("span", { className: installedFilter === "notInstalled" ? Header_module_css_default.segCountActive : Header_module_css_default.segCount }, notInstalledCount)), (0, react.createElement)("button", {
|
|
1922
|
+
className: Header_module_css_default.failBtn,
|
|
1923
|
+
onClick: onOpenNotifications,
|
|
1924
|
+
title: t("notificationsHint"),
|
|
1925
|
+
"aria-label": t("notificationsHint")
|
|
1926
|
+
}, t("notificationsBtn"), noticeCount > 0 ? (0, react.createElement)("span", { className: Header_module_css_default.failBadge }, noticeCount) : null)));
|
|
1927
|
+
}
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region \0dsh-css:src/client/styles/List.module.css.mjs
|
|
1930
|
+
const css$1 = "._3XaZHa_body{flex-direction:column;flex:1;min-height:0;display:flex}._3XaZHa_list{flex-direction:column;flex:1;gap:6px;min-height:0;padding:2px 4px 4px 2px;display:flex;overflow-y:auto}._3XaZHa_card{border:1px solid var(--hub-border-2);background:var(--hub-bg-1);border-radius:8px;justify-content:space-between;align-items:stretch;gap:12px;padding:9px 12px;transition:border-color .12s,background .12s;display:flex}._3XaZHa_card:hover{border-color:var(--hub-brand);background:var(--hub-bg-2)}._3XaZHa_cardMain{flex-direction:column;gap:4px;min-width:0;display:flex}._3XaZHa_cardHead{align-items:center;gap:6px;min-width:0;display:flex}._3XaZHa_cardTitle{white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;line-height:18px;overflow:hidden}._3XaZHa_categoryBadge,._3XaZHa_verified,._3XaZHa_versionBadge,._3XaZHa_updateBadge{border:1px solid #0000;border-radius:4px;flex-shrink:0;padding:0 6px;font-size:10px;line-height:16px}._3XaZHa_categoryBadge{color:var(--hub-brand);border-color:var(--hub-border-ghost)}._3XaZHa_verified{color:var(--hub-success);border-color:var(--hub-success-border)}._3XaZHa_versionBadge{color:var(--hub-text-secondary);border-color:var(--hub-border-input)}._3XaZHa_updateBadge{color:var(--hub-warn);border-color:var(--hub-warn-border);background:var(--hub-warn-tint)}._3XaZHa_desc{color:var(--hub-text-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;margin:0;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}._3XaZHa_topics{flex-wrap:wrap;align-items:center;gap:4px;min-width:0;display:flex}._3XaZHa_topic{color:var(--hub-text-tertiary);background:var(--hub-bg-3);white-space:nowrap;border-radius:4px;flex-shrink:0;padding:0 6px;font-size:10px;line-height:16px}._3XaZHa_cardSide{flex-direction:column;flex-shrink:0;justify-content:space-between;align-items:flex-end;gap:6px;display:flex}._3XaZHa_stats{flex-direction:column;align-items:flex-end;gap:1px;display:flex}._3XaZHa_star{color:var(--hub-warn);white-space:nowrap;font-size:11px;line-height:16px}._3XaZHa_fork{color:var(--hub-text-tertiary);white-space:nowrap;font-size:11px;line-height:16px}._3XaZHa_date{color:var(--hub-text-tertiary);white-space:nowrap;font-size:10px;line-height:14px}._3XaZHa_installBtn,._3XaZHa_installBtnCopied,._3XaZHa_installBtnInstalled,._3XaZHa_installBtnUpdate,._3XaZHa_uninstallBtn,._3XaZHa_detailBtn{cursor:pointer;border-radius:6px;padding:2px 10px;font-size:11px;line-height:18px;transition:color .12s,border-color .12s,background .12s}._3XaZHa_installBtnInstalled{color:var(--hub-success);background:var(--hub-success-tint);cursor:default;-webkit-user-select:none;user-select:none;border:none}._3XaZHa_installBtnUpdate{color:#fff;background:var(--hub-warn);-webkit-user-select:none;user-select:none;border:none;font-weight:500}._3XaZHa_installBtnUpdate:hover{background:var(--hub-warn-strong)}._3XaZHa_uninstallBtn{color:var(--hub-danger);-webkit-user-select:none;user-select:none;background:0 0;border:none}._3XaZHa_uninstallBtn:hover{color:#fff;background:var(--hub-danger)}._3XaZHa_installBtn{color:var(--hub-text-on-fill);background:var(--hub-btn-fill);-webkit-user-select:none;user-select:none;border:none;font-weight:500}._3XaZHa_installBtn:hover{background:var(--hub-btn-hover)}._3XaZHa_detailBtn{color:var(--hub-text-secondary);-webkit-user-select:none;user-select:none;background:0 0;border:none;align-items:center;text-decoration:none;display:inline-flex}._3XaZHa_detailBtn:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}._3XaZHa_actions{align-items:center;gap:6px;display:flex}._3XaZHa_installBtnCopied{color:var(--hub-success);background:var(--hub-success-tint);-webkit-user-select:none;user-select:none;border:none}._3XaZHa_state{text-align:center;min-height:160px;color:var(--hub-text-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px;font-size:12px;line-height:18px;display:flex}._3XaZHa_stateTitle{color:var(--hub-text-primary);font-size:13px;font-weight:600}._3XaZHa_stateDesc{max-width:420px}._3XaZHa_retryBtn{color:var(--hub-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;border-radius:6px;margin-top:4px;padding:4px 12px;font-size:12px;line-height:18px}._3XaZHa_retryBtn:hover{color:var(--hub-text-primary);background:var(--hub-bg-hover)}._3XaZHa_footer{border-top:1px solid var(--hub-border-1);flex-shrink:0;justify-content:flex-end;align-items:center;gap:8px;padding:6px 4px 0;display:flex}._3XaZHa_footLink{color:var(--hub-brand);white-space:nowrap;-webkit-user-select:none;user-select:none;font-size:11px;line-height:16px;text-decoration:none}._3XaZHa_footLink:hover{text-decoration:underline}";
|
|
1931
|
+
const tagId$1 = "dsh-plugin/List.module.css";
|
|
1932
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
1933
|
+
const tag = document.createElement("style");
|
|
1934
|
+
tag.dataset.plugin = "dsh-plugin";
|
|
1935
|
+
tag.dataset.pluginCss = tagId$1;
|
|
1936
|
+
tag.textContent = css$1;
|
|
1937
|
+
document.head.appendChild(tag);
|
|
1938
|
+
}
|
|
1939
|
+
var List_module_css_default = {
|
|
1940
|
+
"footer": "_3XaZHa_footer",
|
|
1941
|
+
"state": "_3XaZHa_state",
|
|
1942
|
+
"actions": "_3XaZHa_actions",
|
|
1943
|
+
"updateBadge": "_3XaZHa_updateBadge",
|
|
1944
|
+
"installBtnInstalled": "_3XaZHa_installBtnInstalled",
|
|
1945
|
+
"stateDesc": "_3XaZHa_stateDesc",
|
|
1946
|
+
"body": "_3XaZHa_body",
|
|
1947
|
+
"verified": "_3XaZHa_verified",
|
|
1948
|
+
"topics": "_3XaZHa_topics",
|
|
1949
|
+
"desc": "_3XaZHa_desc",
|
|
1950
|
+
"installBtn": "_3XaZHa_installBtn",
|
|
1951
|
+
"uninstallBtn": "_3XaZHa_uninstallBtn",
|
|
1952
|
+
"detailBtn": "_3XaZHa_detailBtn",
|
|
1953
|
+
"cardMain": "_3XaZHa_cardMain",
|
|
1954
|
+
"stats": "_3XaZHa_stats",
|
|
1955
|
+
"installBtnCopied": "_3XaZHa_installBtnCopied",
|
|
1956
|
+
"stateTitle": "_3XaZHa_stateTitle",
|
|
1957
|
+
"list": "_3XaZHa_list",
|
|
1958
|
+
"footLink": "_3XaZHa_footLink",
|
|
1959
|
+
"versionBadge": "_3XaZHa_versionBadge",
|
|
1960
|
+
"installBtnUpdate": "_3XaZHa_installBtnUpdate",
|
|
1961
|
+
"cardHead": "_3XaZHa_cardHead",
|
|
1962
|
+
"star": "_3XaZHa_star",
|
|
1963
|
+
"fork": "_3XaZHa_fork",
|
|
1964
|
+
"cardSide": "_3XaZHa_cardSide",
|
|
1965
|
+
"cardTitle": "_3XaZHa_cardTitle",
|
|
1966
|
+
"date": "_3XaZHa_date",
|
|
1967
|
+
"retryBtn": "_3XaZHa_retryBtn",
|
|
1968
|
+
"topic": "_3XaZHa_topic",
|
|
1969
|
+
"categoryBadge": "_3XaZHa_categoryBadge",
|
|
1970
|
+
"card": "_3XaZHa_card"
|
|
1971
|
+
};
|
|
1972
|
+
//#endregion
|
|
1973
|
+
//#region src/client/lib/format.ts
|
|
1974
|
+
/** 星数/分支数的紧凑展示:<1k 原样;千级 1.2k;十万级取整。 */
|
|
1975
|
+
function fmtStars(count) {
|
|
1976
|
+
if (!count || count <= 0) return "0";
|
|
1977
|
+
if (count < 1e3) return String(count);
|
|
1978
|
+
const k = count / 1e3;
|
|
1979
|
+
return `${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
|
|
1980
|
+
}
|
|
1981
|
+
/** 相对时间:今天 / N 天前 / N 个月前 / N 年前。 */
|
|
1982
|
+
function relTime(iso, t) {
|
|
1983
|
+
if (!iso) return "";
|
|
1984
|
+
const days = Math.floor((Date.now() - new Date(iso).getTime()) / 864e5);
|
|
1985
|
+
if (days <= 0) return t("today");
|
|
1986
|
+
if (days < 30) return t("daysAgo", { days });
|
|
1987
|
+
if (days < 365) return t("monthsAgo", { months: Math.floor(days / 30) });
|
|
1988
|
+
return t("yearsAgo", { years: Math.floor(days / 365) });
|
|
1989
|
+
}
|
|
1990
|
+
//#endregion
|
|
1991
|
+
//#region src/client/components/PluginCard.tsx
|
|
1992
|
+
/**
|
|
1993
|
+
* One plugin card in the catalog list: name/category/verified badge,
|
|
1994
|
+
* description + topics, star/fork/date stats, and the detail/install/
|
|
1995
|
+
* uninstall actions.
|
|
1996
|
+
*/
|
|
1997
|
+
function PluginCard({ plugin: p, copied, installedName, installedVersion, hasUpdate, t, langKey, langPath, onInstall, onUninstall }) {
|
|
1998
|
+
const repo = p.source?.repo ?? "";
|
|
1999
|
+
const isCopied = copied === repo;
|
|
2000
|
+
const isInstalled = installedName(p) !== null;
|
|
2001
|
+
const update = hasUpdate(p);
|
|
2002
|
+
const versionShown = isInstalled ? installedVersion(p) : p.version;
|
|
2003
|
+
return (0, react.createElement)("div", { className: List_module_css_default.card }, (0, react.createElement)("div", { className: List_module_css_default.cardMain }, (0, react.createElement)("div", { className: List_module_css_default.cardHead }, (0, react.createElement)("div", {
|
|
2004
|
+
className: List_module_css_default.cardTitle,
|
|
2005
|
+
title: p.description ?? ""
|
|
2006
|
+
}, p.displayName ?? p.slug), versionShown ? (0, react.createElement)("span", {
|
|
2007
|
+
className: List_module_css_default.versionBadge,
|
|
2008
|
+
title: t("version")
|
|
2009
|
+
}, versionShown) : null, update ? (0, react.createElement)("span", {
|
|
2010
|
+
className: List_module_css_default.updateBadge,
|
|
2011
|
+
title: t("updateAvailableHint")
|
|
2012
|
+
}, t("updateAvailable")) : null, p.category ? (0, react.createElement)("span", { className: List_module_css_default.categoryBadge }, categoryLabel(CATEGORY_LABELS, p.category, langKey)) : null, p.compatibility?.status === "verified" ? (0, react.createElement)("span", { className: List_module_css_default.verified }, t("verified")) : null), p.description ? (0, react.createElement)("p", { className: List_module_css_default.desc }, p.description) : null, (p.topics?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: List_module_css_default.topics }, p.topics.slice(0, 3).map((topic) => (0, react.createElement)("span", {
|
|
2013
|
+
key: topic,
|
|
2014
|
+
className: List_module_css_default.topic
|
|
2015
|
+
}, topic))) : null), (0, react.createElement)("div", { className: List_module_css_default.cardSide }, (0, react.createElement)("div", { className: List_module_css_default.stats }, (0, react.createElement)("span", { className: List_module_css_default.star }, "★ ", fmtStars(p.stats?.stargazers_count)), (0, react.createElement)("span", { className: List_module_css_default.fork }, t("fork"), " ", fmtStars(p.stats?.forks_count)), (0, react.createElement)("span", { className: List_module_css_default.date }, relTime(p.dates?.repoUpdatedAt, t))), repo ? (0, react.createElement)("div", { className: List_module_css_default.actions }, (0, react.createElement)("a", {
|
|
2016
|
+
className: List_module_css_default.detailBtn,
|
|
2017
|
+
href: pluginDetailUrl(p, langPath),
|
|
2018
|
+
target: "_blank",
|
|
2019
|
+
rel: "noopener noreferrer",
|
|
2020
|
+
title: p.slug
|
|
2021
|
+
}, t("detail")), isInstalled ? update ? (0, react.createElement)("button", {
|
|
2022
|
+
className: List_module_css_default.installBtnUpdate,
|
|
2023
|
+
title: t("updateAvailableHint"),
|
|
2024
|
+
onClick: () => onInstall(p, { update: true })
|
|
2025
|
+
}, t("update")) : (0, react.createElement)("button", {
|
|
2026
|
+
className: List_module_css_default.installBtnInstalled,
|
|
2027
|
+
disabled: true,
|
|
2028
|
+
title: t("installed")
|
|
2029
|
+
}, t("installed")) : (0, react.createElement)("button", {
|
|
2030
|
+
className: isCopied ? List_module_css_default.installBtnCopied : List_module_css_default.installBtn,
|
|
2031
|
+
onClick: () => onInstall(p)
|
|
2032
|
+
}, t("install")), isInstalled ? (0, react.createElement)("button", {
|
|
2033
|
+
className: List_module_css_default.uninstallBtn,
|
|
2034
|
+
onClick: () => onUninstall(p)
|
|
2035
|
+
}, t("uninstall")) : null) : null));
|
|
2036
|
+
}
|
|
2037
|
+
//#endregion
|
|
2038
|
+
//#region src/client/components/CatalogList.tsx
|
|
2039
|
+
/**
|
|
2040
|
+
* Catalog list: the scrollable container with the loading / failed / empty
|
|
2041
|
+
* states, the plugin cards, and the count footer. Resets scroll position when
|
|
2042
|
+
* the category or install-status filter changes so the replaced content is
|
|
2043
|
+
* not mistaken for a no-op update.
|
|
2044
|
+
*/
|
|
2045
|
+
function CatalogList({ plugins, failed, visible, total, t, langPath, reload, category, installedFilter, copied, installedName, installedVersion, hasUpdate, langKey, onInstall, onUninstall }) {
|
|
2046
|
+
/** 列表滚动容器:分类/搜索切换后列表内容替换但 scrollTop 保留,会让用户误以为列表没更新,需重置回顶部 */
|
|
2047
|
+
const listRef = (0, react.useRef)(null);
|
|
2048
|
+
(0, react.useEffect)(() => {
|
|
2049
|
+
listRef.current?.scrollTo({ top: 0 });
|
|
2050
|
+
}, [category, installedFilter]);
|
|
2051
|
+
return (0, react.createElement)("div", { className: List_module_css_default.body }, (0, react.createElement)("div", {
|
|
2052
|
+
ref: listRef,
|
|
2053
|
+
className: List_module_css_default.list
|
|
2054
|
+
}, plugins === null && !failed && (0, react.createElement)("div", { className: List_module_css_default.state }, t("loading")), failed && (0, react.createElement)("div", { className: List_module_css_default.state }, (0, react.createElement)("div", { className: List_module_css_default.stateTitle }, t("failed")), (0, react.createElement)("div", { className: List_module_css_default.stateDesc }, t("failedDesc")), (0, react.createElement)("button", {
|
|
2055
|
+
className: List_module_css_default.retryBtn,
|
|
2056
|
+
onClick: () => reload()
|
|
2057
|
+
}, t("retry"))), plugins !== null && !failed && visible.length === 0 && (0, react.createElement)("div", { className: List_module_css_default.state }, (0, react.createElement)("div", { className: List_module_css_default.stateTitle }, t("noResult")), (0, react.createElement)("div", { className: List_module_css_default.stateDesc }, t("noResultDesc"))), plugins !== null && !failed && visible.map((p) => (0, react.createElement)(PluginCard, {
|
|
2058
|
+
key: p.ownerSlug ? `${p.ownerSlug}/${p.slug}` : p.slug,
|
|
2059
|
+
plugin: p,
|
|
2060
|
+
copied,
|
|
2061
|
+
installedName,
|
|
2062
|
+
installedVersion,
|
|
2063
|
+
hasUpdate,
|
|
2064
|
+
t,
|
|
2065
|
+
langKey,
|
|
2066
|
+
langPath,
|
|
2067
|
+
onInstall,
|
|
2068
|
+
onUninstall
|
|
2069
|
+
}))), plugins !== null && !failed && (0, react.createElement)("div", { className: List_module_css_default.footer }, (0, react.createElement)("a", {
|
|
2070
|
+
className: List_module_css_default.footLink,
|
|
2071
|
+
href: `https://dsh-plugin.org/${langPath}`,
|
|
2072
|
+
target: "_blank",
|
|
2073
|
+
rel: "noopener noreferrer"
|
|
2074
|
+
}, t("browseAll", { n: total }))));
|
|
2075
|
+
}
|
|
2076
|
+
//#endregion
|
|
2077
|
+
//#region src/client/components/PluginHubSection.tsx
|
|
2078
|
+
/**
|
|
2079
|
+
* Plugin Hub section: wires the catalog data pipeline, the background task
|
|
2080
|
+
* queue and the feedback state together. Data/queue logic lives in hooks/,
|
|
2081
|
+
* rendering is delegated to the small presentational components in this
|
|
2082
|
+
* folder; only the dialogs/toast and the section-level copy actions remain.
|
|
2083
|
+
*/
|
|
2084
|
+
function PluginHubSection({ t: _hostT, locale }) {
|
|
2085
|
+
/** 界面语言:默认跟随宿主(系统)语言;右上角按钮可手动切换,切换后以手动选择为准 */
|
|
2086
|
+
const [manualLang, setManualLang] = (0, react.useState)(null);
|
|
2087
|
+
const lang = manualLang ?? locale.getSnapshot().active;
|
|
373
2088
|
const langKey = lang === "en" ? "en" : "zh";
|
|
374
|
-
const langPath = lang
|
|
2089
|
+
const langPath = langPathOf(lang);
|
|
375
2090
|
const dict = langKey === "en" ? en : zh;
|
|
376
2091
|
const t = (key, params) => {
|
|
377
2092
|
const raw = dict[key] ?? key;
|
|
378
2093
|
return params ? raw.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? "")) : raw;
|
|
379
2094
|
};
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
2095
|
+
const catalog = useCatalog(lang);
|
|
2096
|
+
/** 全局反馈 Toast:{id} 用于重复触发时重新走入场动画,kind 决定文案与配色 */
|
|
2097
|
+
const [toast, setToast] = (0, react.useState)(null);
|
|
2098
|
+
/** 复制反馈:记录刚复制安装命令的仓库,按钮短暂切换为「已复制」样式 */
|
|
2099
|
+
const [copied, setCopied] = (0, react.useState)(null);
|
|
2100
|
+
/** 信任确认弹窗:记录待安装的插件,确认后才执行复制 */
|
|
2101
|
+
const [confirmPlugin, setConfirmPlugin] = (0, react.useState)(null);
|
|
2102
|
+
/** 弹窗动作是否为「更新」:已安装插件点「更新」→ 走同一条 add 命令原位覆盖重装,文案区分安装/更新 */
|
|
2103
|
+
const [confirmIsUpdate, setConfirmIsUpdate] = (0, react.useState)(false);
|
|
2104
|
+
/** 卸载确认弹窗:记录待卸载的插件 */
|
|
2105
|
+
const [uninstallPlugin, setUninstallPlugin] = (0, react.useState)(null);
|
|
2106
|
+
/** 安装/卸载完成后的结果视图:停留弹窗内,点「完成」关闭 */
|
|
2107
|
+
const [installDone, setInstallDone] = (0, react.useState)(false);
|
|
2108
|
+
const [uninstallDone, setUninstallDone] = (0, react.useState)(false);
|
|
2109
|
+
/** 结果视图「立即重启」:请求宿主重启后进入等待,服务回来后整页刷新 */
|
|
2110
|
+
const [restarting, setRestarting] = (0, react.useState)(false);
|
|
2111
|
+
/** 操作失败完整信息 + 所属插件仓库 + 失败类型(决定弹窗标题「安装失败/卸载失败」) */
|
|
2112
|
+
const [errorMsg, setErrorMsg] = (0, react.useState)(null);
|
|
2113
|
+
/** 安装/卸载任务通知记录:localStorage 持久化,成败即落盘,即使错过弹窗也能回来查看 */
|
|
2114
|
+
const [notifications, setNotifications] = (0, react.useState)(() => loadNotifications());
|
|
2115
|
+
const [showNotifications, setShowNotifications] = (0, react.useState)(false);
|
|
2116
|
+
/** 宿主机器环境快照:提交 bug 的 issue 正文附带;取不到为 null(链接少环境段,不阻塞) */
|
|
2117
|
+
const [env, setEnv] = (0, react.useState)(null);
|
|
2118
|
+
(0, react.useEffect)(() => {
|
|
2119
|
+
let alive = true;
|
|
2120
|
+
getEnv().then((info) => {
|
|
2121
|
+
if (alive) setEnv(info);
|
|
2122
|
+
});
|
|
2123
|
+
return () => {
|
|
2124
|
+
alive = false;
|
|
2125
|
+
};
|
|
2126
|
+
}, []);
|
|
2127
|
+
const queue = useTaskQueue({
|
|
2128
|
+
t,
|
|
2129
|
+
refreshInstalled: catalog.refreshInstalled,
|
|
2130
|
+
onInstallDone: (viaModal, repo) => {
|
|
2131
|
+
if (viaModal) setInstallDone(true);
|
|
2132
|
+
else setToast({
|
|
2133
|
+
id: Date.now(),
|
|
2134
|
+
kind: "done"
|
|
2135
|
+
});
|
|
2136
|
+
setNotifications(addSuccess({
|
|
2137
|
+
kind: "install",
|
|
2138
|
+
repo: repo ?? ""
|
|
2139
|
+
}));
|
|
2140
|
+
},
|
|
2141
|
+
onUninstallDone: (viaModal, repo) => {
|
|
2142
|
+
if (viaModal) setUninstallDone(true);
|
|
2143
|
+
else setToast({
|
|
2144
|
+
id: Date.now(),
|
|
2145
|
+
kind: "removed"
|
|
2146
|
+
});
|
|
2147
|
+
setNotifications(addSuccess({
|
|
2148
|
+
kind: "uninstall",
|
|
2149
|
+
repo: repo ?? ""
|
|
2150
|
+
}));
|
|
2151
|
+
},
|
|
2152
|
+
onError: (message, repo, kind) => {
|
|
2153
|
+
setErrorMsg({
|
|
2154
|
+
message,
|
|
2155
|
+
repo,
|
|
2156
|
+
kind
|
|
2157
|
+
});
|
|
2158
|
+
setNotifications(addFailure({
|
|
2159
|
+
kind,
|
|
2160
|
+
repo: repo ?? "",
|
|
2161
|
+
message
|
|
2162
|
+
}));
|
|
2163
|
+
},
|
|
2164
|
+
installPlugin: confirmPlugin,
|
|
2165
|
+
uninstallPlugin,
|
|
2166
|
+
installedName: catalog.installedName,
|
|
2167
|
+
resolvePending: (repo) => {
|
|
2168
|
+
const p = catalog.plugins?.find((x) => x.source?.repo === repo);
|
|
2169
|
+
return p ? {
|
|
2170
|
+
desc: p.description,
|
|
2171
|
+
version: p.version
|
|
2172
|
+
} : null;
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
(0, react.useEffect)(() => {
|
|
2176
|
+
if (!confirmPlugin && !uninstallPlugin && !showNotifications) return;
|
|
2177
|
+
const onKey = (e) => {
|
|
2178
|
+
if (e.key === "Escape") {
|
|
2179
|
+
setConfirmPlugin(null);
|
|
2180
|
+
setUninstallPlugin(null);
|
|
2181
|
+
setUninstallDone(false);
|
|
2182
|
+
setShowNotifications(false);
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
window.addEventListener("keydown", onKey);
|
|
2186
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2187
|
+
}, [
|
|
2188
|
+
confirmPlugin,
|
|
2189
|
+
uninstallPlugin,
|
|
2190
|
+
showNotifications
|
|
2191
|
+
]);
|
|
2192
|
+
(0, react.useEffect)(() => {
|
|
2193
|
+
if (!toast) return;
|
|
2194
|
+
const timer = window.setTimeout(() => setToast(null), 2400);
|
|
2195
|
+
return () => window.clearTimeout(timer);
|
|
2196
|
+
}, [toast]);
|
|
2197
|
+
/** 复制文本到剪贴板,返回是否成功(Clipboard API + 隐藏 textarea 兜底)。 */
|
|
2198
|
+
const doCopy = async (text) => {
|
|
384
2199
|
try {
|
|
385
2200
|
await navigator.clipboard.writeText(text);
|
|
386
|
-
|
|
2201
|
+
return true;
|
|
387
2202
|
} catch {
|
|
388
2203
|
const ta = document.createElement("textarea");
|
|
389
2204
|
ta.value = text;
|
|
@@ -391,117 +2206,221 @@ window.__ModuleLoader__.load({ id: "dsh-plugin", factory: (require) => {
|
|
|
391
2206
|
ta.style.opacity = "0";
|
|
392
2207
|
document.body.appendChild(ta);
|
|
393
2208
|
ta.select();
|
|
2209
|
+
let ok = false;
|
|
394
2210
|
try {
|
|
395
2211
|
ok = document.execCommand("copy");
|
|
396
2212
|
} catch {
|
|
397
2213
|
ok = false;
|
|
398
2214
|
}
|
|
399
2215
|
document.body.removeChild(ta);
|
|
2216
|
+
return ok;
|
|
400
2217
|
}
|
|
401
|
-
|
|
2218
|
+
};
|
|
2219
|
+
/** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行。 */
|
|
2220
|
+
const copyCommand = async (p) => {
|
|
2221
|
+
const repo = p.source?.repo ?? "";
|
|
2222
|
+
if (await doCopy(`dsh plugin add github:${repo}`)) {
|
|
402
2223
|
setCopied(repo);
|
|
403
|
-
setToast({
|
|
2224
|
+
setToast({
|
|
2225
|
+
id: Date.now(),
|
|
2226
|
+
kind: "copied"
|
|
2227
|
+
});
|
|
404
2228
|
window.setTimeout(() => setCopied((cur) => cur === repo ? null : cur), 1600);
|
|
405
|
-
|
|
406
|
-
|
|
2229
|
+
}
|
|
2230
|
+
setConfirmPlugin(null);
|
|
407
2231
|
};
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
2232
|
+
/** 卸载弹窗动作:复制卸载命令,万一直接卸载失败可去终端手动执行。 */
|
|
2233
|
+
const copyUninstallCommand = async () => {
|
|
2234
|
+
const name = uninstallPlugin ? catalog.installedName(uninstallPlugin) : null;
|
|
2235
|
+
if (!name) return;
|
|
2236
|
+
if (await doCopy(`dsh plugin remove ${name}`)) setToast({
|
|
2237
|
+
id: Date.now(),
|
|
2238
|
+
kind: "copied"
|
|
2239
|
+
});
|
|
2240
|
+
};
|
|
2241
|
+
/** 「立即重启」:POST 同源 /restart,宿主进程自杀重启;随后轮询服务恢复后整页刷新。 */
|
|
2242
|
+
const requestRestart = async () => {
|
|
2243
|
+
if (restarting) return;
|
|
2244
|
+
setRestarting(true);
|
|
2245
|
+
try {
|
|
2246
|
+
await fetch("/dsh-plugin-hub/restart", { method: "POST" });
|
|
2247
|
+
} catch {}
|
|
2248
|
+
let attempts = 0;
|
|
2249
|
+
const poll = async () => {
|
|
2250
|
+
attempts += 1;
|
|
2251
|
+
try {
|
|
2252
|
+
if ((await fetch("/dsh-plugin-hub/active", {
|
|
2253
|
+
method: "GET",
|
|
2254
|
+
cache: "no-store"
|
|
2255
|
+
})).ok) {
|
|
2256
|
+
window.location.reload();
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
} catch {}
|
|
2260
|
+
if (attempts < 60) window.setTimeout(poll, 1e3);
|
|
2261
|
+
else {
|
|
2262
|
+
setRestarting(false);
|
|
2263
|
+
setToast({
|
|
2264
|
+
id: Date.now(),
|
|
2265
|
+
kind: "fail"
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
window.setTimeout(poll, 1200);
|
|
2270
|
+
};
|
|
2271
|
+
const { total, installedName, installedVersion, hasUpdate } = catalog;
|
|
2272
|
+
const statsTotal = catalog.stats?.total ?? total;
|
|
2273
|
+
const statsVerified = catalog.stats?.verified ?? 0;
|
|
2274
|
+
const count = catalog.visible.length;
|
|
2275
|
+
return (0, react.createElement)("div", { className: Header_module_css_default.root }, (0, react.createElement)(CatalogHeader, {
|
|
2276
|
+
t,
|
|
2277
|
+
langPath,
|
|
2278
|
+
statsTotal,
|
|
2279
|
+
statsVerified,
|
|
2280
|
+
onToggleLang: () => setManualLang(lang === "en" ? "zh" : "en")
|
|
2281
|
+
}), (0, react.createElement)(CategoryTabs, {
|
|
2282
|
+
category: catalog.category,
|
|
2283
|
+
setCategory: catalog.setCategory,
|
|
2284
|
+
allLabel: t("all"),
|
|
2285
|
+
totalCount: catalog.total,
|
|
2286
|
+
langKey
|
|
2287
|
+
}), (0, react.createElement)(CatalogControls, {
|
|
2288
|
+
query: catalog.query,
|
|
2289
|
+
setQuery: catalog.setQuery,
|
|
2290
|
+
sort: catalog.sort,
|
|
2291
|
+
setSort: catalog.setSort,
|
|
2292
|
+
installedFilter: catalog.installedFilter,
|
|
2293
|
+
setInstalledFilter: catalog.setInstalledFilter,
|
|
2294
|
+
installedCount: catalog.installedCountInCategory,
|
|
2295
|
+
notInstalledCount: catalog.notInstalledCountInCategory,
|
|
2296
|
+
t,
|
|
2297
|
+
resultText: catalog.plugins === null || catalog.failed ? null : catalog.category === "all" && count === total ? t("pluginsTotal", { n: count }) : t("filterResults", { n: count }),
|
|
2298
|
+
noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
|
|
2299
|
+
onOpenNotifications: () => setShowNotifications(true)
|
|
2300
|
+
}), (0, react.createElement)(CatalogList, {
|
|
2301
|
+
plugins: catalog.plugins,
|
|
2302
|
+
failed: catalog.failed,
|
|
2303
|
+
visible: catalog.visible,
|
|
2304
|
+
total,
|
|
2305
|
+
t,
|
|
2306
|
+
langPath,
|
|
2307
|
+
reload: catalog.reload,
|
|
2308
|
+
category: catalog.category,
|
|
2309
|
+
installedFilter: catalog.installedFilter,
|
|
2310
|
+
copied,
|
|
2311
|
+
installedName,
|
|
2312
|
+
installedVersion,
|
|
2313
|
+
hasUpdate,
|
|
2314
|
+
langKey,
|
|
2315
|
+
onInstall: (p, opts) => {
|
|
2316
|
+
setInstallDone(false);
|
|
2317
|
+
setConfirmIsUpdate(opts?.update ?? false);
|
|
2318
|
+
queue.clearModalTask();
|
|
2319
|
+
setConfirmPlugin(p);
|
|
2320
|
+
},
|
|
2321
|
+
onUninstall: (p) => {
|
|
2322
|
+
setUninstallDone(false);
|
|
2323
|
+
queue.clearModalTask();
|
|
2324
|
+
setUninstallPlugin(p);
|
|
2325
|
+
}
|
|
2326
|
+
}), confirmPlugin && (0, react.createElement)(InstallModal, {
|
|
2327
|
+
plugin: confirmPlugin,
|
|
2328
|
+
done: installDone,
|
|
2329
|
+
task: queue.installModalTask,
|
|
2330
|
+
t,
|
|
2331
|
+
langPath,
|
|
2332
|
+
restarting,
|
|
2333
|
+
update: confirmIsUpdate,
|
|
2334
|
+
submitting: queue.submitting,
|
|
2335
|
+
onClose: () => setConfirmPlugin(null),
|
|
2336
|
+
onCopy: () => copyCommand(confirmPlugin),
|
|
2337
|
+
onInstall: () => queue.installNow(confirmPlugin, confirmIsUpdate ? { update: true } : void 0),
|
|
2338
|
+
onRestart: () => {
|
|
2339
|
+
requestRestart();
|
|
2340
|
+
}
|
|
2341
|
+
}), uninstallPlugin && (0, react.createElement)(UninstallModal, {
|
|
2342
|
+
plugin: uninstallPlugin,
|
|
2343
|
+
done: uninstallDone,
|
|
2344
|
+
task: queue.uninstallModalTask,
|
|
2345
|
+
t,
|
|
2346
|
+
langPath,
|
|
2347
|
+
restarting,
|
|
2348
|
+
submitting: queue.submitting,
|
|
2349
|
+
onClose: () => {
|
|
2350
|
+
setUninstallDone(false);
|
|
2351
|
+
setUninstallPlugin(null);
|
|
2352
|
+
},
|
|
2353
|
+
onCancel: () => setUninstallPlugin(null),
|
|
2354
|
+
onCopyCommand: copyUninstallCommand,
|
|
2355
|
+
onConfirm: () => queue.uninstallNow(uninstallPlugin),
|
|
2356
|
+
onRestart: () => {
|
|
2357
|
+
requestRestart();
|
|
2358
|
+
}
|
|
2359
|
+
}), toast && (0, react.createElement)(Toast, {
|
|
2360
|
+
toast,
|
|
2361
|
+
t
|
|
2362
|
+
}), errorMsg && (0, react.createElement)(ErrorModal, {
|
|
2363
|
+
message: errorMsg.message,
|
|
2364
|
+
repo: errorMsg.repo,
|
|
2365
|
+
kind: errorMsg.kind,
|
|
2366
|
+
t,
|
|
2367
|
+
env,
|
|
2368
|
+
onCopy: (text) => {
|
|
2369
|
+
doCopy(text);
|
|
2370
|
+
setToast({
|
|
2371
|
+
id: Date.now(),
|
|
2372
|
+
kind: "errCopied"
|
|
2373
|
+
});
|
|
2374
|
+
},
|
|
2375
|
+
onClose: () => setErrorMsg(null)
|
|
2376
|
+
}), showNotifications && (0, react.createElement)(NotificationsModal, {
|
|
2377
|
+
records: notifications,
|
|
2378
|
+
tasks: queue.queue,
|
|
2379
|
+
pendingRestarts: queue.pendingRestarts,
|
|
2380
|
+
t,
|
|
2381
|
+
env,
|
|
2382
|
+
onClose: () => setShowNotifications(false),
|
|
2383
|
+
onCopy: (text) => {
|
|
2384
|
+
doCopy(text);
|
|
2385
|
+
setToast({
|
|
2386
|
+
id: Date.now(),
|
|
2387
|
+
kind: "errCopied"
|
|
2388
|
+
});
|
|
2389
|
+
},
|
|
2390
|
+
onClear: () => setNotifications(clearNotifications()),
|
|
2391
|
+
cancelTask: queue.cancelTask,
|
|
2392
|
+
restarting,
|
|
2393
|
+
onRestart: () => {
|
|
2394
|
+
requestRestart();
|
|
2395
|
+
}
|
|
2396
|
+
}));
|
|
2397
|
+
}
|
|
2398
|
+
//#endregion
|
|
2399
|
+
//#region \0dsh-css:src/client/styles/tokens.module.css.mjs
|
|
2400
|
+
const css = ":root,body[data-ds-dark-theme]{--hub-text-primary:var(--dsw-alias-label-primary,#1f2328);--hub-text-secondary:var(--dsw-alias-label-secondary,#6b7280);--hub-text-tertiary:var(--dsw-alias-label-tertiary,#8b93a1);--hub-text-disabled:var(--dsw-alias-label-disabled,#aab1bd);--hub-text-on-fill:var(--dsw-alias-label-primary-foreground,#fff);--hub-bg-1:var(--dsw-alias-bg-layer-1,#fff);--hub-bg-2:var(--dsw-alias-bg-layer-2,#80808014);--hub-bg-3:#8080801a;--hub-bg-btn:#8080801f;--hub-bg-hover:var(--dsw-alias-interactive-bg-hover,#80808029);--hub-bg-on-fill:#80808038;--hub-border-1:var(--dsw-alias-border-l1,#eceef1);--hub-border-2:var(--dsw-alias-border-l2,#e5e7eb);--hub-border-ghost:var(--dsw-alias-button-ghost-active-border,#4f6ef740);--hub-border-input:var(--dsw-alias-input-border,#6b728040);--hub-brand:var(--dsw-alias-state-business-primary,#4f6ef7);--hub-brand-hover:#3b5bdb;--hub-brand-tint:#4f6ef70f;--hub-brand-tint-strong:#4f6ef71f;--hub-brand-border:#4f6ef759;--hub-brand-border-soft:#4f6ef738;--hub-purple-1:#4f46e5;--hub-purple-2:#7c3aed;--hub-purple-border:#8b5cf68c;--hub-purple-tint:#4f46e514;--hub-purple-shadow:#4f46e547;--hub-purple-shadow-strong:#7c3aed6b;--hub-btn-fill:var(--dsw-alias-button-primary-fill,#1f2328);--hub-btn-hover:var(--dsw-alias-button-primary-hover,#43454a);--hub-success:var(--dsw-alias-state-success-primary,#1a7f37);--hub-success-tint:#22c55e24;--hub-success-border:var(--dsw-alias-state-success-secondary,#1a7f374d);--hub-warn:var(--dsw-alias-state-warn-primary,#b8860b);--hub-warn-tint:#b8860b14;--hub-warn-border:#b8860b59;--hub-warn-strong:#b8860bd9;--hub-warning:var(--dsw-alias-state-warning-primary,#b45309);--hub-warning-tint:#b4530914;--hub-warning-border:#b453094d;--hub-danger:var(--dsw-alias-state-danger-primary,#d1242f);--hub-danger-hover:var(--dsw-alias-state-danger-hover,#b91c1c);--hub-danger-strong:#b0202a;--hub-danger-text:#e5484d;--hub-danger-tint:#e5484d14;--hub-danger-tint-weak:#e5484d0d;--hub-danger-border:#e5484d59;--hub-danger-border-soft:#e5484d47;--hub-danger-soft:#d1242f14}body[data-ds-dark-theme]{--hub-bg-btn:#ffffff1f}";
|
|
2401
|
+
const tagId = "dsh-plugin/tokens.module.css";
|
|
2402
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2403
|
+
const tag = document.createElement("style");
|
|
2404
|
+
tag.dataset.plugin = "dsh-plugin";
|
|
2405
|
+
tag.dataset.pluginCss = tagId;
|
|
2406
|
+
tag.textContent = css;
|
|
2407
|
+
document.head.appendChild(tag);
|
|
497
2408
|
}
|
|
2409
|
+
//#endregion
|
|
2410
|
+
//#region src/client/index.tsx
|
|
2411
|
+
/**
|
|
2412
|
+
* dsh-plugin client entry: wires the Plugin Hub into the host settings via
|
|
2413
|
+
* the module-loader bundle (see tsdown.config.ts). All UI logic lives in
|
|
2414
|
+
* PluginHubSection; this file only performs the cordis apply wiring.
|
|
2415
|
+
*/
|
|
2416
|
+
const NS = "dsh-plugin";
|
|
498
2417
|
const name = NS;
|
|
499
2418
|
const inject = ["slots", "locale"];
|
|
500
2419
|
function apply(ctx) {
|
|
501
2420
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
502
2421
|
zh,
|
|
503
2422
|
en
|
|
504
|
-
}),
|
|
2423
|
+
}), `${NS}: dictionaries`);
|
|
505
2424
|
const t = ctx.locale.bind(NS);
|
|
506
2425
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
507
2426
|
name: "settings.section",
|