@1e0zj/dsh-plugin-mall 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.js CHANGED
@@ -1,380 +1,408 @@
1
- // Browser half of dsh-plugin-mall: the Settings → Plugins → 插件市场 tab.
2
- //
3
- // Client plugin contract (see @deepseek-ai/dsh-client-modules): the bundle
4
- // registers itself on window.__ModuleLoader__, exports a cordis-style
5
- // { apply, inject }, and is served at /plugins/@1e0zj/dsh-plugin-mall/client.js.
6
- // This file is hand-written ES5-ish JS on purpose — no build step — using the
7
- // React instance the shell shares through the client module loader.
8
- window.__ModuleLoader__.load({
9
- id: "@1e0zj/dsh-plugin-mall",
10
- factory: (require) => {
11
- var module = { exports: {} };
12
- var exports = module.exports;
13
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
- var React = require("react");
15
-
16
- // ── styles ──────────────────────────────────────────────────────────────
17
- var css = [
18
- ".mkt_root{display:flex;flex-direction:column;gap:12px;max-width:960px}",
19
- ".mkt_head{display:flex;flex-direction:column;gap:2px}",
20
- ".mkt_title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary)}",
21
- ".mkt_sub{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
22
- ".mkt_row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
23
- ".mkt_input{flex:1;min-width:200px;background:var(--dsw-alias-bg-secondary,#fff);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 10px;font-size:13px;color:var(--dsw-alias-label-primary)}",
24
- ".mkt_select{background:var(--dsw-alias-bg-secondary,#fff);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 8px;font-size:13px;color:var(--dsw-alias-label-primary)}",
25
- ".mkt_btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-secondary,#fff);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer}",
26
- ".mkt_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}",
27
- ".mkt_btn:disabled{opacity:.55;cursor:default}",
28
- ".mkt_btnPrimary{background:var(--dsw-alias-state-business-primary,#2b6cb0);border-color:transparent;color:#fff}",
29
- ".mkt_btnPrimary:hover:not(:disabled){color:#fff;opacity:.92}",
30
- ".mkt_btnDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
31
- ".mkt_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
32
- ".mkt_columns{display:grid;grid-template-columns:1fr 320px;gap:14px;align-items:start}",
33
- "@media (max-width:860px){.mkt_columns{grid-template-columns:1fr}}",
34
- ".mkt_list{display:flex;flex-direction:column;gap:10px;min-width:0}",
35
- ".mkt_card{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-primary,#fff);display:flex;flex-direction:column;gap:6px;min-width:0}",
36
- ".mkt_cardHead{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}",
37
- ".mkt_name{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}",
38
- ".mkt_meta{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
39
- ".mkt_desc{font-size:12.5px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
40
- ".mkt_cardActions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
41
- ".mkt_rail{display:flex;flex-direction:column;gap:10px;min-width:0}",
42
- ".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
43
- ".mkt_pre{font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-secondary,#f6f7f8);border-radius:6px;padding:8px;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all}",
44
- ".mkt_ok{color:var(--dsw-alias-state-success-primary,#2f855a)}",
45
- ".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
46
- ".mkt_link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;cursor:pointer}",
47
- ].join("\n");
48
- var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
49
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
50
- var styleTag = document.createElement("style");
51
- styleTag.dataset.plugin = "@1e0zj/dsh-plugin-mall";
52
- styleTag.dataset.pluginCss = tagId;
53
- styleTag.textContent = css;
54
- document.head.appendChild(styleTag);
55
- }
56
-
57
- // ── tiny helpers ────────────────────────────────────────────────────────
58
- var h = React.createElement;
59
- var useState = React.useState;
60
- var useEffect = React.useEffect;
61
- var useRef = React.useRef;
62
- var useCallback = React.useCallback;
63
-
64
- function clip(text, max) {
65
- var t = String(text == null ? "" : text).replace(/\s+/g, " ").trim();
66
- return t.length > max ? t.slice(0, max - 1) + "…" : t;
67
- }
68
-
69
- function errorText(e) {
70
- return String(e && e.message ? e.message : e);
71
- }
72
-
73
- function kindLabel(kind) {
74
- if (kind === "bundle") return "宿主插件层";
75
- if (kind === "client") return "浏览器UI插件";
76
- if (kind === "missing") return "未解析";
77
- return "普通依赖";
78
- }
79
-
80
- // ── job polling ─────────────────────────────────────────────────────────
81
- function useJobPolling(call) {
82
- var jobsRef = useRef({});
83
- var _jobs = useState({});
84
- var jobs = _jobs[0];
85
- var setJobs = _jobs[1];
86
- useEffect(function () {
87
- var timer = setInterval(function () {
88
- var current = jobsRef.current;
89
- var running = Object.keys(current).filter(function (id) {
90
- var job = current[id];
91
- return job && job.status !== "completed" && job.status !== "failed" && job.status !== "killed";
92
- });
93
- if (running.length === 0) return;
94
- running.forEach(function (id) {
95
- call("job", { jobId: id }).then(function (value) {
96
- var snapshot = value.snapshot || {};
97
- setJobs(function (prev) {
98
- var old = prev[id] || {};
99
- var output = (old.output || "") + (value.output || "");
100
- return Object.assign({}, prev, { [id]: Object.assign({}, old, {
101
- status: snapshot.status,
102
- detail: snapshot.detail,
103
- output: output,
104
- }) });
105
- });
106
- }).catch(function () { /* keep polling */ });
107
- });
108
- }, 1200);
109
- return function () { clearInterval(timer); };
110
- }, [call]);
111
- var track = useCallback(function (id, spec) {
112
- jobsRef.current = Object.assign({}, jobsRef.current, { [id]: { status: "running", spec: spec, output: "" } });
113
- setJobs(Object.assign({}, jobsRef.current));
114
- }, []);
115
- return { jobs: jobs, track: track };
116
- }
117
-
118
- // ── repo card ───────────────────────────────────────────────────────────
119
- function RepoCard(props) {
120
- var item = props.item;
121
- var installing = props.installing === true;
122
- return h("div", { className: "mkt_card" },
123
- h("div", { className: "mkt_cardHead" },
124
- h("span", { className: "mkt_name" }, item.fullName),
125
- h("span", { className: "mkt_meta" }, "★" + item.stars),
126
- item.language ? h("span", { className: "mkt_meta" }, item.language) : null,
127
- item.license ? h("span", { className: "mkt_meta" }, item.license) : null,
128
- item.archived ? h("span", { className: "mkt_badge" }, "archived") : null
129
- ),
130
- item.description ? h("div", { className: "mkt_desc" }, clip(item.description, 180)) : null,
131
- h("div", { className: "mkt_meta" }, "更新 " + (item.updatedAt || "").slice(0, 10)),
132
- h("div", { className: "mkt_cardActions" },
133
- h("button", { className: "mkt_btn mkt_btnPrimary", disabled: installing, onClick: function () { props.onInstall(item.fullName); } },
134
- installing ? "安装中…" : "安装"),
135
- h("button", { className: "mkt_btn", onClick: function () { props.onInfo(item.fullName); } }, "详情")
136
- )
137
- );
138
- }
139
-
140
- // ── info panel ──────────────────────────────────────────────────────────
141
- function InfoPanel(props) {
142
- var info = props.info;
143
- if (!info) return null;
144
- var data = info.data;
145
- return h("div", { className: "mkt_card" },
146
- h("p", { className: "mkt_panelTitle" }, "仓库详情"),
147
- info.loading ? h("div", { className: "mkt_meta" }, "加载中…")
148
- : info.error ? h("div", { className: "mkt_error" }, info.error)
149
- : !data ? null
150
- : h(React.Fragment, null,
151
- h("div", { className: "mkt_name" }, data.meta.fullName),
152
- h("div", { className: "mkt_desc" }, clip(data.meta.description, 240)),
153
- h("div", { className: "mkt_meta" },
154
- "★" + data.meta.stars + " · " + (data.meta.language || "-") + " · " + (data.meta.license || "-") + " · 分支 " + data.meta.defaultBranch),
155
- data.packageJson == null
156
- ? h("div", { className: "mkt_meta" }, "package.json: 未找到(可能不是 npm 插件)")
157
- : h(React.Fragment, null,
158
- h("div", { className: "mkt_meta" }, "package.json: " + (data.packageJson.name || "?") + "@" + (data.packageJson.version || "?")),
159
- data.packageJson.dshBundlePatch != null
160
- ? h("div", { className: "mkt_ok" }, "✓ dsh.bundle 宿主插件")
161
- : data.packageJson.dshClientPlatform != null
162
- ? h("div", { className: "mkt_ok" }, " dsh.client 浏览器UI插件")
163
- : h("div", { className: "mkt_meta" }, "⚠ 无 bundle/client 声明,装上是普通依赖")),
164
- h("button", { className: "mkt_btn mkt_btnPrimary", onClick: function () { props.onInstall(data.meta.fullName); } }, "安装 github:" + data.meta.fullName),
165
- h("a", { className: "mkt_link", href: data.meta.htmlUrl, target: "_blank", rel: "noreferrer" }, "打开仓库 ↗")
166
- )
167
- );
168
- }
169
-
170
- // ── jobs panel ──────────────────────────────────────────────────────────
171
- function JobsPanel(props) {
172
- var ids = Object.keys(props.jobs);
173
- if (ids.length === 0) return null;
174
- return h("div", { className: "mkt_card" },
175
- h("p", { className: "mkt_panelTitle" }, "安装任务"),
176
- ids.map(function (id) {
177
- var job = props.jobs[id];
178
- var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
179
- return h("div", { key: id, style: { display: "flex", flexDirection: "column", gap: "4px" } },
180
- h("div", { className: "mkt_meta" },
181
- id + " · " + clip(job.spec || "", 40) + " · " + (job.status || "running")),
182
- job.detail ? h("div", { className: "mkt_desc" }, job.detail) : null,
183
- done && job.status === "completed"
184
- ? h("div", { className: "mkt_ok" }, "✓ 安装完成 — 重启 dsh 后生效")
185
- : job.status === "failed"
186
- ? h("div", { className: "mkt_error" }, "安装失败,见下方输出")
187
- : null,
188
- job.output ? h("pre", { className: "mkt_pre" }, job.output.slice(-4000)) : null
189
- );
190
- })
191
- );
192
- }
193
-
194
- // ── installed panel ─────────────────────────────────────────────────────
195
- function InstalledPanel(props) {
196
- var installed = props.installed;
197
- if (!installed) return null;
198
- return h("div", { className: "mkt_card" },
199
- h("p", { className: "mkt_panelTitle" }, "已装插件"),
200
- installed.error
201
- ? h("div", { className: "mkt_error" }, installed.error)
202
- : installed.deps && installed.deps.length === 0
203
- ? h("div", { className: "mkt_meta" }, "还没有装过插件")
204
- : (installed.deps || []).map(function (dep) {
205
- return h("div", { key: dep.name, style: { display: "flex", justifyContent: "space-between", gap: "8px" } },
206
- h("span", { className: "mkt_desc" }, dep.name + "@" + dep.version),
207
- h("span", { className: "mkt_badge" }, kindLabel(dep.kind)));
208
- })
209
- );
210
- }
211
-
212
- // ── the marketplace tab ─────────────────────────────────────────────────
213
- function MarketplaceTab(props) {
214
- var rpc = props.rpc;
215
- var _query = useState("");
216
- var query = _query[0];
217
- var setQuery = _query[1];
218
- var _sort = useState("stars");
219
- var sort = _sort[0];
220
- var setSort = _sort[1];
221
- var _results = useState(null);
222
- var results = _results[0];
223
- var setResults = _results[1];
224
- var _loading = useState(false);
225
- var loading = _loading[0];
226
- var setLoading = _loading[1];
227
- var _error = useState(null);
228
- var error = _error[0];
229
- var setError = _error[1];
230
- var _info = useState(null);
231
- var info = _info[0];
232
- var setInfo = _info[1];
233
- var _installed = useState(null);
234
- var installed = _installed[0];
235
- var setInstalled = _installed[1];
236
- var _installing = useState({});
237
- var installing = _installing[0];
238
- var setInstalling = _installing[1];
239
-
240
- var call = useCallback(function (endpoint, payload) {
241
- return rpc.call("/market", endpoint, payload || {}).then(function (res) {
242
- if (!res || res.ok !== true) throw new Error((res && res.error) || "market request failed");
243
- return res.value;
244
- });
245
- }, [rpc]);
246
-
247
- var doSearch = useCallback(function () {
248
- setLoading(true);
249
- setError(null);
250
- call("search", { query: query, sort: sort, perPage: 20 }).then(function (value) {
251
- setResults(value);
252
- }).catch(function (e) {
253
- setError(errorText(e));
254
- }).finally(function () {
255
- setLoading(false);
256
- });
257
- }, [call, query, sort]);
258
-
259
- var doInfo = useCallback(function (repo) {
260
- setInfo({ repo: repo, loading: true });
261
- call("info", { repo: repo }).then(function (value) {
262
- setInfo({ repo: repo, data: value });
263
- }).catch(function (e) {
264
- setInfo({ repo: repo, error: errorText(e) });
265
- });
266
- }, [call]);
267
-
268
- var polling = useJobPolling(call);
269
- var track = polling.track;
270
- var jobs = polling.jobs;
271
-
272
- var doInstall = useCallback(function (repo) {
273
- var spec = "github:" + repo;
274
- setInstalling(function (prev) {
275
- var next = Object.assign({}, prev, { [repo]: true });
276
- return next;
277
- });
278
- setError(null);
279
- call("install", { spec: spec }).then(function (value) {
280
- track(value.jobId, spec);
281
- }).catch(function (e) {
282
- setError(errorText(e));
283
- }).finally(function () {
284
- setInstalling(function (prev) {
285
- var next = Object.assign({}, prev);
286
- delete next[repo];
287
- return next;
288
- });
289
- });
290
- }, [call, track]);
291
-
292
- var refreshInstalled = useCallback(function () {
293
- call("installed", {}).then(function (value) {
294
- setInstalled(value);
295
- }).catch(function (e) {
296
- setInstalled({ error: errorText(e) });
297
- });
298
- }, [call]);
299
-
300
- useEffect(function () {
301
- refreshInstalled();
302
- }, [refreshInstalled]);
303
-
304
- return h("div", { className: "mkt_root" },
305
- h("div", { className: "mkt_head" },
306
- h("div", { className: "mkt_title" }, "插件市场"),
307
- h("div", { className: "mkt_sub" }, "搜索 GitHub dsh-plugin 话题插件 · 安装后需重启 dsh 生效")
308
- ),
309
- h("div", { className: "mkt_row" },
310
- h("input", {
311
- className: "mkt_input",
312
- placeholder: "搜索插件(留空列出最热)",
313
- value: query,
314
- onChange: function (e) { setQuery(e.target.value); },
315
- onKeyDown: function (e) { if (e.key === "Enter") doSearch(); },
316
- }),
317
- h("select", {
318
- className: "mkt_select",
319
- value: sort,
320
- onChange: function (e) { setSort(e.target.value); },
321
- },
322
- h("option", { value: "stars" }, "按 star"),
323
- h("option", { value: "updated" }, "最近更新"),
324
- h("option", { value: "forks" }, "按 fork")
325
- ),
326
- h("button", { className: "mkt_btn mkt_btnPrimary", disabled: loading, onClick: doSearch }, loading ? "搜索中…" : "搜索"),
327
- h("button", { className: "mkt_btn", onClick: refreshInstalled }, "刷新已装")
328
- ),
329
- error ? h("div", { className: "mkt_error" }, error) : null,
330
- h("div", { className: "mkt_columns" },
331
- h("div", { className: "mkt_list" },
332
- results == null
333
- ? h("div", { className: "mkt_meta" }, "输入关键词点搜索,或直接搜索浏览最热插件。")
334
- : results.items.length === 0
335
- ? h("div", { className: "mkt_meta" }, "没有匹配的仓库。")
336
- : h(React.Fragment, null,
337
- h("div", { className: "mkt_meta" }, "共 " + results.total + " 个仓库(显示 " + results.items.length + " 个)"),
338
- results.items.map(function (item) {
339
- return h(RepoCard, {
340
- key: item.fullName,
341
- item: item,
342
- installing: installing[item.fullName] === true,
343
- onInstall: doInstall,
344
- onInfo: doInfo,
345
- });
346
- })
347
- )
348
- ),
349
- h("div", { className: "mkt_rail" },
350
- h(InfoPanel, { info: info, onInstall: doInstall }),
351
- h(JobsPanel, { jobs: jobs }),
352
- h(InstalledPanel, { installed: installed })
353
- )
354
- )
355
- );
356
- }
357
-
358
- // ── plugin body ─────────────────────────────────────────────────────────
359
- var inject = ["slots", "connection"];
360
- function apply(ctx) {
361
- var rpc = ctx.get("connection").rpc;
362
- ctx.effect(function () {
363
- return ctx.slots.inject("settings.plugins.tab", function () {
364
- return ctx.slots.register({
365
- name: "settings.plugins.tab",
366
- id: "market",
367
- order: 30,
368
- label: "插件市场",
369
- inject: function () {
370
- return { rpc: rpc };
371
- },
372
- }, MarketplaceTab);
373
- });
374
- }, "@1e0zj/dsh-plugin-mall: marketplace tab");
375
- }
376
- exports.apply = apply;
377
- exports.inject = inject;
378
- return module.exports;
379
- },
380
- });
1
+ // Browser half of dsh-plugin-mall: the Settings → Plugins → 插件市场 tab.
2
+ //
3
+ // Client plugin contract (see @deepseek-ai/dsh-client-modules): the bundle
4
+ // registers itself on window.__ModuleLoader__, exports a cordis-style
5
+ // { apply, inject }, and is served at /plugins/@1e0zj/dsh-plugin-mall/client.js.
6
+ // This file is hand-written ES5-ish JS on purpose — no build step — using the
7
+ // React instance the shell shares through the client module loader.
8
+ window.__ModuleLoader__.load({
9
+ id: "@1e0zj/dsh-plugin-mall",
10
+ factory: (require) => {
11
+ var module = { exports: {} };
12
+ var exports = module.exports;
13
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
+ var React = require("react");
15
+
16
+ // ── styles ──────────────────────────────────────────────────────────────
17
+ var css = [
18
+ ".mkt_root{display:flex;flex-direction:column;gap:12px;max-width:960px}",
19
+ ".mkt_head{display:flex;flex-direction:column;gap:2px}",
20
+ ".mkt_title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary)}",
21
+ ".mkt_sub{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
22
+ ".mkt_row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
23
+ ".mkt_input{flex:1;min-width:200px;background:var(--dsw-alias-bg-secondary,#fff);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 10px;font-size:13px;color:var(--dsw-alias-label-primary)}",
24
+ ".mkt_select{background:var(--dsw-alias-bg-secondary,#fff);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 8px;font-size:13px;color:var(--dsw-alias-label-primary)}",
25
+ ".mkt_btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-secondary,#fff);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer}",
26
+ ".mkt_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}",
27
+ ".mkt_btn:disabled{opacity:.55;cursor:default}",
28
+ ".mkt_btnPrimary{background:var(--dsw-alias-state-business-primary,#2b6cb0);border-color:transparent;color:#fff}",
29
+ ".mkt_btnPrimary:hover:not(:disabled){color:#fff;opacity:.92}",
30
+ ".mkt_btnDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
31
+ ".mkt_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
32
+ ".mkt_columns{display:grid;grid-template-columns:1fr 320px;gap:14px;align-items:start}",
33
+ "@media (max-width:860px){.mkt_columns{grid-template-columns:1fr}}",
34
+ ".mkt_list{display:flex;flex-direction:column;gap:10px;min-width:0}",
35
+ ".mkt_card{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-primary,#fff);display:flex;flex-direction:column;gap:6px;min-width:0}",
36
+ ".mkt_cardHead{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}",
37
+ ".mkt_name{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}",
38
+ ".mkt_meta{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
39
+ ".mkt_desc{font-size:12.5px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
40
+ ".mkt_cardActions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
41
+ ".mkt_rail{display:flex;flex-direction:column;gap:10px;min-width:0}",
42
+ ".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
43
+ ".mkt_pre{font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-secondary,#f6f7f8);border-radius:6px;padding:8px;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all}",
44
+ ".mkt_ok{color:var(--dsw-alias-state-success-primary,#2f855a)}",
45
+ ".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
46
+ ".mkt_link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;cursor:pointer}",
47
+ ".mkt_installedHead{display:flex;align-items:center;gap:8px;width:100%;background:none;border:0;padding:0;margin:0;cursor:pointer;font:inherit;text-align:left}",
48
+ ".mkt_installedHead:hover .mkt_panelTitle{color:var(--dsw-alias-state-business-primary)}",
49
+ ".mkt_depList{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
50
+ ".mkt_depRow{display:flex;justify-content:space-between;align-items:center;gap:8px}",
51
+ ].join("\n");
52
+ var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
53
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
54
+ var styleTag = document.createElement("style");
55
+ styleTag.dataset.plugin = "@1e0zj/dsh-plugin-mall";
56
+ styleTag.dataset.pluginCss = tagId;
57
+ styleTag.textContent = css;
58
+ document.head.appendChild(styleTag);
59
+ }
60
+
61
+ // ── tiny helpers ────────────────────────────────────────────────────────
62
+ var h = React.createElement;
63
+ var useState = React.useState;
64
+ var useEffect = React.useEffect;
65
+ var useRef = React.useRef;
66
+ var useCallback = React.useCallback;
67
+
68
+ function clip(text, max) {
69
+ var t = String(text == null ? "" : text).replace(/\s+/g, " ").trim();
70
+ return t.length > max ? t.slice(0, max - 1) + "…" : t;
71
+ }
72
+
73
+ function errorText(e) {
74
+ return String(e && e.message ? e.message : e);
75
+ }
76
+
77
+ function kindLabel(kind) {
78
+ if (kind === "bundle") return "宿主插件层";
79
+ if (kind === "client") return "浏览器UI插件";
80
+ if (kind === "missing") return "未解析";
81
+ return "普通依赖";
82
+ }
83
+
84
+ // ── job polling ─────────────────────────────────────────────────────────
85
+ function useJobPolling(call, onSettled) {
86
+ var jobsRef = useRef({});
87
+ var _jobs = useState({});
88
+ var jobs = _jobs[0];
89
+ var setJobs = _jobs[1];
90
+ useEffect(function () {
91
+ var timer = setInterval(function () {
92
+ var current = jobsRef.current;
93
+ var running = Object.keys(current).filter(function (id) {
94
+ var job = current[id];
95
+ return job && job.status !== "completed" && job.status !== "failed" && job.status !== "killed";
96
+ });
97
+ if (running.length === 0) return;
98
+ running.forEach(function (id) {
99
+ call("job", { jobId: id }).then(function (value) {
100
+ var snapshot = value.snapshot || {};
101
+ var old = jobsRef.current[id] || {};
102
+ var wasTerminal = old.status === "completed" || old.status === "failed" || old.status === "killed";
103
+ var nowTerminal = snapshot.status === "completed" || snapshot.status === "failed" || snapshot.status === "killed";
104
+ if (!wasTerminal && nowTerminal && onSettled) {
105
+ try { onSettled(id); } catch (e) { /* best effort */ }
106
+ }
107
+ var output = (old.output || "") + (value.output || "");
108
+ jobsRef.current = Object.assign({}, jobsRef.current, { [id]: Object.assign({}, old, {
109
+ status: snapshot.status,
110
+ detail: snapshot.detail,
111
+ output: output,
112
+ }) });
113
+ setJobs(Object.assign({}, jobsRef.current));
114
+ }).catch(function () { /* keep polling */ });
115
+ });
116
+ }, 1200);
117
+ return function () { clearInterval(timer); };
118
+ }, [call, onSettled]);
119
+ var track = useCallback(function (id, spec) {
120
+ jobsRef.current = Object.assign({}, jobsRef.current, { [id]: { status: "running", spec: spec, output: "" } });
121
+ setJobs(Object.assign({}, jobsRef.current));
122
+ }, []);
123
+ return { jobs: jobs, track: track };
124
+ }
125
+
126
+ // ── repo card ───────────────────────────────────────────────────────────
127
+ function RepoCard(props) {
128
+ var item = props.item;
129
+ var installing = props.installing === true;
130
+ return h("div", { className: "mkt_card" },
131
+ h("div", { className: "mkt_cardHead" },
132
+ h("span", { className: "mkt_name" }, item.fullName),
133
+ h("span", { className: "mkt_meta" }, "★" + item.stars),
134
+ item.language ? h("span", { className: "mkt_meta" }, item.language) : null,
135
+ item.license ? h("span", { className: "mkt_meta" }, item.license) : null,
136
+ item.archived ? h("span", { className: "mkt_badge" }, "archived") : null
137
+ ),
138
+ item.description ? h("div", { className: "mkt_desc" }, clip(item.description, 180)) : null,
139
+ h("div", { className: "mkt_meta" }, "更新 " + (item.updatedAt || "").slice(0, 10)),
140
+ h("div", { className: "mkt_cardActions" },
141
+ h("button", { className: "mkt_btn mkt_btnPrimary", disabled: installing, onClick: function () { props.onInstall(item.fullName); } },
142
+ installing ? "安装中…" : "安装"),
143
+ h("button", { className: "mkt_btn", onClick: function () { props.onInfo(item.fullName); } }, "详情")
144
+ )
145
+ );
146
+ }
147
+
148
+ // ── info panel ──────────────────────────────────────────────────────────
149
+ function InfoPanel(props) {
150
+ var info = props.info;
151
+ if (!info) return null;
152
+ var data = info.data;
153
+ return h("div", { className: "mkt_card" },
154
+ h("p", { className: "mkt_panelTitle" }, "仓库详情"),
155
+ info.loading ? h("div", { className: "mkt_meta" }, "加载中…")
156
+ : info.error ? h("div", { className: "mkt_error" }, info.error)
157
+ : !data ? null
158
+ : h(React.Fragment, null,
159
+ h("div", { className: "mkt_name" }, data.meta.fullName),
160
+ h("div", { className: "mkt_desc" }, clip(data.meta.description, 240)),
161
+ h("div", { className: "mkt_meta" },
162
+ "★" + data.meta.stars + " · " + (data.meta.language || "-") + " · " + (data.meta.license || "-") + " · 分支 " + data.meta.defaultBranch),
163
+ data.packageJson == null
164
+ ? h("div", { className: "mkt_meta" }, "package.json: 未找到(可能不是 npm 插件)")
165
+ : h(React.Fragment, null,
166
+ h("div", { className: "mkt_meta" }, "package.json: " + (data.packageJson.name || "?") + "@" + (data.packageJson.version || "?")),
167
+ data.packageJson.dshBundlePatch != null
168
+ ? h("div", { className: "mkt_ok" }, "✓ dsh.bundle 宿主插件")
169
+ : data.packageJson.dshClientPlatform != null
170
+ ? h("div", { className: "mkt_ok" }, "✓ dsh.client 浏览器UI插件")
171
+ : h("div", { className: "mkt_meta" }, "⚠ 无 bundle/client 声明,装上是普通依赖")),
172
+ h("button", { className: "mkt_btn mkt_btnPrimary", onClick: function () { props.onInstall(data.meta.fullName); } }, "安装 github:" + data.meta.fullName),
173
+ h("a", { className: "mkt_link", href: data.meta.htmlUrl, target: "_blank", rel: "noreferrer" }, "打开仓库 ↗")
174
+ )
175
+ );
176
+ }
177
+
178
+ // ── jobs panel ──────────────────────────────────────────────────────────
179
+ function JobsPanel(props) {
180
+ var ids = Object.keys(props.jobs);
181
+ if (ids.length === 0) return null;
182
+ return h("div", { className: "mkt_card" },
183
+ h("p", { className: "mkt_panelTitle" }, "安装任务"),
184
+ ids.map(function (id) {
185
+ var job = props.jobs[id];
186
+ var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
187
+ return h("div", { key: id, style: { display: "flex", flexDirection: "column", gap: "4px" } },
188
+ h("div", { className: "mkt_meta" },
189
+ id + " · " + clip(job.spec || "", 40) + " · " + (job.status || "running")),
190
+ job.detail ? h("div", { className: "mkt_desc" }, job.detail) : null,
191
+ done && job.status === "completed"
192
+ ? h("div", { className: "mkt_ok" }, "✓ 安装完成 — 重启 dsh 后生效")
193
+ : job.status === "failed"
194
+ ? h("div", { className: "mkt_error" }, "安装失败,见下方输出")
195
+ : null,
196
+ job.output ? h("pre", { className: "mkt_pre" }, job.output.slice(-4000)) : null
197
+ );
198
+ })
199
+ );
200
+ }
201
+
202
+ // ── installed panel (collapsible summary) ───────────────────────────────
203
+ function InstalledPanel(props) {
204
+ var installed = props.installed;
205
+ var _open = useState(false);
206
+ var open = _open[0];
207
+ var setOpen = _open[1];
208
+ if (!installed) return null;
209
+ var count = installed.error ? 0 : (installed.deps || []).length;
210
+ return h("div", { className: "mkt_card" },
211
+ h("button", {
212
+ type: "button",
213
+ className: "mkt_installedHead",
214
+ onClick: function () { setOpen(!open); },
215
+ "aria-expanded": open ? "true" : "false",
216
+ },
217
+ h("span", { className: "mkt_panelTitle" }, "已装插件"),
218
+ h("span", { className: "mkt_badge" }, count + " 个"),
219
+ h("span", { className: "mkt_meta" }, open ? "收起 ▲" : "展开 ▼")
220
+ ),
221
+ installed.error
222
+ ? h("div", { className: "mkt_error" }, installed.error)
223
+ : !open ? null
224
+ : (installed.deps || []).length === 0
225
+ ? h("div", { className: "mkt_meta" }, "还没有装过插件")
226
+ : h("div", { className: "mkt_depList" }, (installed.deps || []).map(function (dep) {
227
+ return h("div", { key: dep.name, className: "mkt_depRow" },
228
+ h("span", { className: "mkt_desc" }, dep.name + "@" + dep.version),
229
+ h("span", { className: "mkt_badge" }, kindLabel(dep.kind)));
230
+ }))
231
+ );
232
+ }
233
+
234
+ // ── the marketplace tab ─────────────────────────────────────────────────
235
+ function MarketplaceTab(props) {
236
+ var rpc = props.rpc;
237
+ var _query = useState("");
238
+ var query = _query[0];
239
+ var setQuery = _query[1];
240
+ var _sort = useState("stars");
241
+ var sort = _sort[0];
242
+ var setSort = _sort[1];
243
+ var _results = useState(null);
244
+ var results = _results[0];
245
+ var setResults = _results[1];
246
+ var _loading = useState(false);
247
+ var loading = _loading[0];
248
+ var setLoading = _loading[1];
249
+ var _error = useState(null);
250
+ var error = _error[0];
251
+ var setError = _error[1];
252
+ var _info = useState(null);
253
+ var info = _info[0];
254
+ var setInfo = _info[1];
255
+ var _installed = useState(null);
256
+ var installed = _installed[0];
257
+ var setInstalled = _installed[1];
258
+ var _installing = useState({});
259
+ var installing = _installing[0];
260
+ var setInstalling = _installing[1];
261
+
262
+ var call = useCallback(function (endpoint, payload) {
263
+ return rpc.call("/market", endpoint, payload || {}).then(function (res) {
264
+ if (!res || res.ok !== true) throw new Error((res && res.error) || "market request failed");
265
+ return res.value;
266
+ });
267
+ }, [rpc]);
268
+
269
+ var doSearch = useCallback(function () {
270
+ setLoading(true);
271
+ setError(null);
272
+ call("search", { query: query, sort: sort, perPage: 20 }).then(function (value) {
273
+ setResults(value);
274
+ }).catch(function (e) {
275
+ setError(errorText(e));
276
+ }).finally(function () {
277
+ setLoading(false);
278
+ });
279
+ }, [call, query, sort]);
280
+
281
+ var doInfo = useCallback(function (repo) {
282
+ setInfo({ repo: repo, loading: true });
283
+ call("info", { repo: repo }).then(function (value) {
284
+ setInfo({ repo: repo, data: value });
285
+ }).catch(function (e) {
286
+ setInfo({ repo: repo, error: errorText(e) });
287
+ });
288
+ }, [call]);
289
+
290
+ var refreshInstalled = useCallback(function () {
291
+ call("installed", {}).then(function (value) {
292
+ setInstalled(value);
293
+ }).catch(function (e) {
294
+ setInstalled({ error: errorText(e) });
295
+ });
296
+ }, [call]);
297
+
298
+ // 点开即自动搜索(空关键词 = 最热插件),安装任务结束自动刷新已装列表。
299
+ var polling = useJobPolling(call, refreshInstalled);
300
+ var track = polling.track;
301
+ var jobs = polling.jobs;
302
+
303
+ useEffect(function () {
304
+ doSearch();
305
+ // eslint-disable-next-line react-hooks/exhaustive-deps
306
+ }, []);
307
+
308
+ var doInstall = useCallback(function (repo) {
309
+ var spec = "github:" + repo;
310
+ setInstalling(function (prev) {
311
+ var next = Object.assign({}, prev, { [repo]: true });
312
+ return next;
313
+ });
314
+ setError(null);
315
+ call("install", { spec: spec }).then(function (value) {
316
+ track(value.jobId, spec);
317
+ }).catch(function (e) {
318
+ setError(errorText(e));
319
+ }).finally(function () {
320
+ setInstalling(function (prev) {
321
+ var next = Object.assign({}, prev);
322
+ delete next[repo];
323
+ return next;
324
+ });
325
+ });
326
+ }, [call, track]);
327
+
328
+ useEffect(function () {
329
+ refreshInstalled();
330
+ }, [refreshInstalled]);
331
+
332
+ return h("div", { className: "mkt_root" },
333
+ h("div", { className: "mkt_head" },
334
+ h("div", { className: "mkt_title" }, "插件市场"),
335
+ h("div", { className: "mkt_sub" }, "搜索 GitHub dsh-plugin 话题插件 · 安装后需重启 dsh 生效")
336
+ ),
337
+ h("div", { className: "mkt_row" },
338
+ h("input", {
339
+ className: "mkt_input",
340
+ placeholder: "搜索插件(留空列出最热)",
341
+ value: query,
342
+ onChange: function (e) { setQuery(e.target.value); },
343
+ onKeyDown: function (e) { if (e.key === "Enter") doSearch(); },
344
+ }),
345
+ h("select", {
346
+ className: "mkt_select",
347
+ value: sort,
348
+ onChange: function (e) { setSort(e.target.value); },
349
+ },
350
+ h("option", { value: "stars" }, "按 star"),
351
+ h("option", { value: "updated" }, "最近更新"),
352
+ h("option", { value: "forks" }, "按 fork")
353
+ ),
354
+ h("button", { className: "mkt_btn mkt_btnPrimary", disabled: loading, onClick: doSearch }, loading ? "搜索中…" : "搜索"),
355
+ h("button", { className: "mkt_btn", onClick: refreshInstalled }, "刷新已装")
356
+ ),
357
+ error ? h("div", { className: "mkt_error" }, error) : null,
358
+ h("div", { className: "mkt_columns" },
359
+ h("div", { className: "mkt_list" },
360
+ results == null
361
+ ? h("div", { className: "mkt_meta" }, loading ? "正在加载最热插件…" : "—")
362
+ : results.items.length === 0
363
+ ? h("div", { className: "mkt_meta" }, "没有匹配的仓库。")
364
+ : h(React.Fragment, null,
365
+ h("div", { className: "mkt_meta" }, "共 " + results.total + " 个仓库(显示 " + results.items.length + " 个)"),
366
+ results.items.map(function (item) {
367
+ return h(RepoCard, {
368
+ key: item.fullName,
369
+ item: item,
370
+ installing: installing[item.fullName] === true,
371
+ onInstall: doInstall,
372
+ onInfo: doInfo,
373
+ });
374
+ })
375
+ )
376
+ ),
377
+ h("div", { className: "mkt_rail" },
378
+ h(InstalledPanel, { installed: installed }),
379
+ h(InfoPanel, { info: info, onInstall: doInstall }),
380
+ h(JobsPanel, { jobs: jobs })
381
+ )
382
+ )
383
+ );
384
+ }
385
+
386
+ // ── plugin body ─────────────────────────────────────────────────────────
387
+ var inject = ["slots", "connection"];
388
+ function apply(ctx) {
389
+ var rpc = ctx.get("connection").rpc;
390
+ ctx.effect(function () {
391
+ return ctx.slots.inject("settings.plugins.tab", function () {
392
+ return ctx.slots.register({
393
+ name: "settings.plugins.tab",
394
+ id: "market",
395
+ order: 30,
396
+ label: "插件市场",
397
+ inject: function () {
398
+ return { rpc: rpc };
399
+ },
400
+ }, MarketplaceTab);
401
+ });
402
+ }, "@1e0zj/dsh-plugin-mall: marketplace tab");
403
+ }
404
+ exports.apply = apply;
405
+ exports.inject = inject;
406
+ return module.exports;
407
+ },
408
+ });