dsh-m 0.1.1 → 0.2.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/lib/client.js CHANGED
@@ -2,14 +2,124 @@ window.__ModuleLoader__.load({
2
2
  id: "dsh-m",
3
3
  factory: (require) => {
4
4
  "use strict";
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // src/client/market-state.js
27
+ var market_state_exports = {};
28
+ __export(market_state_exports, {
29
+ MARKET_PAGE_SIZE: () => MARKET_PAGE_SIZE,
30
+ normalizeMarketQuery: () => normalizeMarketQuery,
31
+ normalizeMarketResponse: () => normalizeMarketResponse,
32
+ registryNotice: () => registryNotice,
33
+ resetPageOnFilterChange: () => resetPageOnFilterChange
34
+ });
35
+ function toSafeInt(value, fallback, min, max) {
36
+ const n = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
37
+ if (min !== void 0 && n < min) return min;
38
+ if (max !== void 0 && n > max) return max;
39
+ return n;
40
+ }
41
+ function normalizeMarketQuery(input) {
42
+ const raw = input && typeof input === "object" ? input : {};
43
+ const query = typeof raw.query === "string" ? raw.query.trim() : "";
44
+ const category = typeof raw.category === "string" && CATEGORIES.includes(raw.category) ? raw.category : null;
45
+ const offset = toSafeInt(raw.offset, 0, 0);
46
+ const limit = toSafeInt(raw.limit, MARKET_PAGE_SIZE, 1, MARKET_PAGE_SIZE);
47
+ return { query, category, offset, limit };
48
+ }
49
+ function resetPageOnFilterChange(previous, next) {
50
+ const prev = previous && typeof previous === "object" ? previous : {};
51
+ const merged = { ...next };
52
+ if (prev.query !== next.query || prev.category !== next.category) {
53
+ merged.offset = 0;
54
+ }
55
+ return merged;
56
+ }
57
+ function normalizeMarketResponse(raw) {
58
+ const body = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
59
+ const items = Array.isArray(body.items) ? body.items.filter((it) => it && typeof it === "object") : [];
60
+ const total = toSafeInt(body.total, items.length, 0);
61
+ const offset = toSafeInt(body.offset, 0, 0);
62
+ const limit = toSafeInt(body.limit, MARKET_PAGE_SIZE, 1);
63
+ let categoryCounts = {};
64
+ if (body.categoryCounts && typeof body.categoryCounts === "object" && !Array.isArray(body.categoryCounts)) {
65
+ for (const [key, value] of Object.entries(body.categoryCounts)) {
66
+ if (typeof value === "number" && Number.isFinite(value)) categoryCounts[key] = value;
67
+ }
68
+ }
69
+ const rs = body.registryState && typeof body.registryState === "object" && !Array.isArray(body.registryState) ? body.registryState : {};
70
+ const registryState = {
71
+ ...FALLBACK_REGISTRY_STATE,
72
+ ...rs,
73
+ errors: Array.isArray(rs.errors) ? rs.errors.map(String) : []
74
+ };
75
+ return {
76
+ items,
77
+ total,
78
+ offset,
79
+ limit,
80
+ categoryCounts,
81
+ registryState,
82
+ installedComplete: body.installedComplete === true,
83
+ latestComplete: body.latestComplete === true,
84
+ latestTimedOut: body.latestTimedOut === true
85
+ };
86
+ }
87
+ function registryNotice(summary, total) {
88
+ const s = summary && typeof summary === "object" ? summary : {};
89
+ let key = "notice.default";
90
+ if (s.status === "unavailable") key = "notice.unavailable";
91
+ else if (s.stale || s.status === "stale") key = "notice.stale";
92
+ else if (!s.isDefault) key = "notice.custom";
93
+ return { key, count: typeof total === "number" && Number.isFinite(total) ? total : 0 };
94
+ }
95
+ var MARKET_PAGE_SIZE, CATEGORIES, FALLBACK_REGISTRY_STATE;
96
+ var init_market_state = __esm({
97
+ "src/client/market-state.js"() {
98
+ "use strict";
99
+ MARKET_PAGE_SIZE = 50;
100
+ CATEGORIES = ["market", "tools", "ui", "search", "media", "other"];
101
+ FALLBACK_REGISTRY_STATE = {
102
+ configuredAddress: "",
103
+ activeAddress: null,
104
+ source: "bundled",
105
+ status: "unavailable",
106
+ isDefault: true,
107
+ stale: false,
108
+ fetchedAt: null,
109
+ errors: [],
110
+ count: 0
111
+ };
112
+ }
113
+ });
5
114
 
6
115
  // src/client/main.jsx
7
116
  var React = require("react");
8
117
  var rd = require("react-dom");
9
118
  var h = React.createElement;
10
- var { useState, useEffect, useCallback, useMemo } = React;
119
+ var { useState, useEffect, useCallback, useMemo, useRef } = React;
11
120
  var PLUGIN_ID = "dsh-m";
12
121
  var API = "/dshm";
122
+ var { MARKET_PAGE_SIZE: MARKET_PAGE_SIZE2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2 } = (init_market_state(), __toCommonJS(market_state_exports));
13
123
  var ZH = {
14
124
  "market.title": "\u63D2\u4EF6\u5E02\u573A",
15
125
  "tab.market": "\u5E02\u573A",
@@ -38,6 +148,7 @@ var ZH = {
38
148
  "badge.update": "\u53EF\u5347\u7EA7",
39
149
  "badge.market": "\u5E02\u573A\u5B89\u88C5",
40
150
  "badge.nonmarket": "\u975E\u5E02\u573A\u5B89\u88C5",
151
+ "badge.custom": "\u81EA\u5B9A\u4E49",
41
152
  "action.install": "\u5B89\u88C5",
42
153
  "action.upgrade": "\u5347\u7EA7",
43
154
  "action.uninstall": "\u5378\u8F7D",
@@ -55,6 +166,8 @@ var ZH = {
55
166
  "detail.listed.no": "\u4E0D\u5728\u6536\u5F55\u6E05\u5355\u4E2D",
56
167
  "detail.path": "\u8DEF\u5F84",
57
168
  "detail.note": "\u6CE8\u610F",
169
+ "detail.links": "\u8BE6\u60C5",
170
+ "link.home": "\u5B98\u7F51",
58
171
  "manage.hint": "\u5DF2\u5B89\u88C5\uFF0C\u53EF\u5728\u300C\u5DF2\u88C5\u300D\u9875\u7BA1\u7406",
59
172
  "version.failed": "\u7248\u672C\u67E5\u8BE2\u5931\u8D25",
60
173
  "src.npm": "npm",
@@ -81,12 +194,59 @@ var ZH = {
81
194
  "settings.upgradeself": "\u5347\u7EA7 dsh-m",
82
195
  "settings.upgradehint": "\u5347\u7EA7\u540E\u540C\u6837\u9700\u8981\u91CD\u542F\u751F\u6548",
83
196
  "settings.about": "\u5173\u4E8E",
84
- "settings.about.text": "DSH Marketplace\uFF08dsh-m\uFF09\u2014 \u4E2A\u4EBA\u81EA\u7528\u7684 DeepSeek Harness \u63D2\u4EF6\u5E02\u573A\u3002\u6536\u5F55\u3001\u5B89\u88C5\u3001\u5378\u8F7D\u3001\u5347\u7EA7\uFF0C\u5168\u90E8\u672C\u673A\u5B8C\u6210\u3002",
197
+ "settings.about.text": "\u4E2A\u4EBA DSH \u63D2\u4EF6\u5E02\u573A\uFF1A\u6536\u5F55\u3001\u5B89\u88C5\u3001\u5378\u8F7D\u3001\u5347\u7EA7\u5168\u90E8\u672C\u673A\u5B8C\u6210\uFF1B\u6536\u5F55\u6E05\u5355\u652F\u6301\u81EA\u5B9A\u4E49\u8986\u76D6\uFF0C\u5E94\u7528\u5373\u65F6\u751F\u6548\u3002",
85
198
  "src.override": "\u81EA\u5B9A\u4E49\u6E90",
86
199
  "src.jsdelivr": "jsDelivr\uFF08@main\uFF09",
87
200
  "src.raw": "raw.githubusercontent\uFF08@main\uFF09",
88
201
  "src.cache": "\u672C\u5730\u7F13\u5B58",
89
202
  "src.bundled": "\u5305\u5185\u5FEB\u7167\uFF08\u515C\u5E95\uFF09",
203
+ "src.default.raw": "raw.githubusercontent\uFF08@main\uFF09",
204
+ "src.default.jsdelivr": "jsDelivr\uFF08@main\uFF09",
205
+ "src.default.cache": "\u9ED8\u8BA4\u6E05\u5355\u7F13\u5B58",
206
+ "src.custom.url": "\u81EA\u5B9A\u4E49 URL \u6E90",
207
+ "src.custom.file": "\u672C\u5730\u6587\u4EF6\u6E90",
208
+ "src.custom.cache": "\u81EA\u5B9A\u4E49\u6E90\uFF08\u7F13\u5B58\uFF09",
209
+ "src.custom.unavailable": "\u81EA\u5B9A\u4E49\u6E90\uFF08\u4E0D\u53EF\u7528\uFF09",
210
+ "settings.address": "Registry \u5730\u5740",
211
+ "settings.address.hint": "\u7A7A = \u5B98\u65B9\u9ED8\u8BA4\u6E05\u5355\uFF1B\u652F\u6301 HTTPS URL \u6216\u672C\u673A\u7EDD\u5BF9\u8DEF\u5F84 / file://\u3002\u6574\u4F53\u8986\u76D6\u9ED8\u8BA4\u6E05\u5355\uFF0C\u4E0D\u505A\u5408\u5E76\u3002",
212
+ "settings.address.ph": "https://example.com/registry.json \u6216 /path/to/registry.json",
213
+ "settings.configured": "\u914D\u7F6E\u5730\u5740",
214
+ "settings.activecfg": "\u5F53\u524D\u751F\u6548\u914D\u7F6E",
215
+ "settings.effective": "\u751F\u6548\u6765\u6E90",
216
+ "settings.status.label": "\u914D\u7F6E\u72B6\u6001",
217
+ "settings.status.loading": "\u52A0\u8F7D\u4E2D",
218
+ "settings.status.ready": "\u5DF2\u751F\u6548",
219
+ "settings.status.pending": "\u5F85\u5199\u5165\uFF08\u6821\u9A8C\u5DF2\u901A\u8FC7\uFF09",
220
+ "settings.status.rejected": "\u5DF2\u62D2\u7EDD\uFF08\u4FDD\u6301\u65E7\u914D\u7F6E\uFF09",
221
+ "settings.status.unavailable": "\u4E0D\u53EF\u7528",
222
+ "settings.apply": "\u6821\u9A8C\u5E76\u5E94\u7528",
223
+ "settings.apply.applying": "\u6821\u9A8C\u4E2D\u2026",
224
+ "settings.apply.ok": "Registry \u5730\u5740\u5DF2\u751F\u6548\uFF08\u65E0\u9700\u91CD\u542F\uFF09",
225
+ "settings.apply.failed": "\u5E94\u7528\u5931\u8D25\uFF1A{err}",
226
+ "settings.reset": "\u6062\u590D\u9ED8\u8BA4",
227
+ "settings.reset.ok": "\u5DF2\u6062\u590D\u9ED8\u8BA4\u6536\u5F55\u6E05\u5355",
228
+ "settings.download": "\u4E0B\u8F7D\u9ED8\u8BA4 registry.json",
229
+ "settings.download.downloading": "\u4E0B\u8F7D\u4E2D\u2026",
230
+ "settings.download.ok": "\u9ED8\u8BA4\u6E05\u5355\u5DF2\u4E0B\u8F7D\uFF08\u5F53\u524D\u914D\u7F6E\u4E0D\u53D8\uFF09",
231
+ "settings.download.failed": "\u4E0B\u8F7D\u5931\u8D25\uFF1A{err}",
232
+ "settings.diagnose": "\u68C0\u67E5\u6761\u76EE\u53EF\u8FBE\u6027",
233
+ "settings.diagnose.running": "\u8BCA\u65AD\u4E2D\u2026",
234
+ "settings.diagnose.failed": "\u8BCA\u65AD\u5931\u8D25\uFF1A{err}",
235
+ "settings.diagnose.result": "\u63A2\u6D4B {checked} \u9879 \xB7 \u901A\u8FC7 {passed} \xB7 \u5931\u8D25 {failed}{trunc}",
236
+ "settings.diagnose.truncated": "\uFF08\u4EC5\u663E\u793A\u524D 100 \u6761\u95EE\u9898\uFF09",
237
+ "settings.diagnose.none": "\u672A\u53D1\u73B0\u95EE\u9898",
238
+ "settings.trust.hint": "\u26A0\uFE0F \u81EA\u5B9A\u4E49\u6536\u5F55\u6E05\u5355\u672A\u7ECF\u5B98\u65B9 CI \u6821\u9A8C\uFF0C\u6761\u76EE\u6765\u6E90\u8BF7\u786E\u8BA4\u53EF\u4FE1\u540E\u518D\u5B89\u88C5\u3002",
239
+ "settings.cache.hint": "\u5207\u6362\u540E\u65E7\u81EA\u5B9A\u4E49\u6E90\u7F13\u5B58\u5C06\u88AB\u6E05\u7406\uFF08\u9ED8\u8BA4\u7F13\u5B58\u4FDD\u7559\uFF09\uFF1B\u81EA\u5B9A\u4E49\u6E90\u5931\u8D25\u65F6\u4FDD\u7559\u5176\u6700\u8FD1\u4E00\u6B21\u6210\u529F\u7F13\u5B58\u3002",
240
+ "settings.warnings": "\u7EF4\u62A4\u63D0\u793A",
241
+ "notice.default": "\u5B98\u65B9\u9ED8\u8BA4\u6536\u5F55\u6E05\u5355 \xB7 \u5171 {count} \u6761",
242
+ "notice.custom": "\u81EA\u5B9A\u4E49\u6536\u5F55\u6E05\u5355 \xB7 \u5171 {count} \u6761",
243
+ "notice.stale": "\u6765\u6E90\u4E3A\u672C\u5730\u7F13\u5B58\uFF08\u5171 {count} \u6761\uFF09\uFF0C\u53EF\u7528\u300C\u5F3A\u5236\u5237\u65B0\u300D\u66F4\u65B0",
244
+ "notice.unavailable": "\u6536\u5F55\u6E05\u5355\u4E0D\u53EF\u7528 \xB7 \u8BF7\u5230\u8BBE\u7F6E\u9875\u68C0\u67E5\u5730\u5740",
245
+ "market.page.prev": "\u4E0A\u4E00\u9875",
246
+ "market.page.next": "\u4E0B\u4E00\u9875",
247
+ "market.page.info": "\u7B2C {page} / {pages} \u9875 \xB7 \u5171 {total} \u6761",
248
+ "market.perf": "\u6536\u5F55\u8D85\u8FC7 200 \u6761\uFF1A\u4EC5\u67E5\u8BE2\u5F53\u524D\u9875\u7684\u6700\u65B0\u7248\u672C\uFF08\u6BCF\u9875 50 \u6761\uFF09\uFF0C\u5982\u9700\u5168\u90E8\u8BF7\u7528\u641C\u7D22/\u5206\u7C7B\u8FC7\u6EE4",
249
+ "notice.toolview.err": "\u6536\u5F55\u6E05\u5355\u6682\u4E0D\u53EF\u7528",
90
250
  "self.upgraded": "dsh-m \u5DF2\u66F4\u65B0\u5230 v{v}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
91
251
  "self.failed": "\u81EA\u66F4\u65B0\u5931\u8D25\uFF1A{err}",
92
252
  "registry.refreshed": "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0",
@@ -120,7 +280,7 @@ var ZH = {
120
280
  "readme.hide": "\u6536\u8D77 README",
121
281
  "readme.loading": "\u52A0\u8F7D README\u2026 ",
122
282
  "readme.none": "\uFF08\u8BE5\u63D2\u4EF6\u6CA1\u6709 README\uFF09",
123
- "readme.truncated": "\n\n\u2026\uFF08\u8D85\u8FC7 64KB \u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1\u63D2\u4EF6\u76EE\u5F55\uFF09",
283
+ "readme.truncated": "\u2026\uFF08\u8D85\u8FC7 64KB \u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1\u63D2\u4EF6\u76EE\u5F55\uFF09",
124
284
  "warn.unlink": "\u5378\u8F7D\u53EA\u79FB\u9664 profile \u5BF9\u672C\u5730\u76EE\u5F55\u7684\u5F15\u7528\uFF08{path}\uFF09\uFF0C\u4E0D\u4F1A\u5220\u9664\u76EE\u5F55\u672C\u8EAB\u3002",
125
285
  "warn.core": "\u8FD9\u662F file: \u5B89\u88C5\u7684\u6838\u5FC3/\u5F52\u6863\u5305\uFF0C\u5378\u8F7D\u53EF\u80FD\u5F71\u54CD DSH \u529F\u80FD\uFF0C\u4E14\u9700\u8981\u624B\u52A8\u6062\u590D\u3002",
126
286
  "profile.hint": "web profile\uFF1A{path}",
@@ -157,6 +317,7 @@ var EN = {
157
317
  "badge.update": "Update",
158
318
  "badge.market": "Via market",
159
319
  "badge.nonmarket": "Non-market",
320
+ "badge.custom": "Custom",
160
321
  "action.install": "Install",
161
322
  "action.upgrade": "Upgrade",
162
323
  "action.uninstall": "Uninstall",
@@ -174,6 +335,8 @@ var EN = {
174
335
  "detail.listed.no": "Not in the registry",
175
336
  "detail.path": "Path",
176
337
  "detail.note": "Note",
338
+ "detail.links": "Details",
339
+ "link.home": "Homepage",
177
340
  "manage.hint": "Installed \u2014 manage it on the Installed tab",
178
341
  "version.failed": "version lookup failed",
179
342
  "src.npm": "npm",
@@ -200,12 +363,59 @@ var EN = {
200
363
  "settings.upgradeself": "Upgrade dsh-m",
201
364
  "settings.upgradehint": "A restart is required after upgrading",
202
365
  "settings.about": "About",
203
- "settings.about.text": "DSH Marketplace (dsh-m) \u2014 your personal DeepSeek Harness plugin marketplace. Browse, install, uninstall and upgrade, all local.",
366
+ "settings.about.text": "A personal DSH plugin marketplace \u2014 browse, install, uninstall and upgrade, all local; registry overrides apply live.",
204
367
  "src.override": "Custom source",
205
368
  "src.jsdelivr": "jsDelivr (@main)",
206
369
  "src.raw": "raw.githubusercontent (@main)",
207
370
  "src.cache": "Local cache",
208
371
  "src.bundled": "Bundled snapshot (fallback)",
372
+ "src.default.raw": "raw.githubusercontent (@main)",
373
+ "src.default.jsdelivr": "jsDelivr (@main)",
374
+ "src.default.cache": "Default registry cache",
375
+ "src.custom.url": "Custom URL source",
376
+ "src.custom.file": "Local file source",
377
+ "src.custom.cache": "Custom source (cache)",
378
+ "src.custom.unavailable": "Custom source (unavailable)",
379
+ "settings.address": "Registry address",
380
+ "settings.address.hint": "Empty = official default registry; accepts an HTTPS URL or a local absolute path / file://. Replaces (not merges) the default registry. Live effect.",
381
+ "settings.address.ph": "https://example.com/registry.json or /path/to/registry.json",
382
+ "settings.configured": "Configured address",
383
+ "settings.activecfg": "Active config",
384
+ "settings.effective": "Effective source",
385
+ "settings.status.label": "Config status",
386
+ "settings.status.loading": "Loading",
387
+ "settings.status.ready": "Applied",
388
+ "settings.status.pending": "Pending write (validated)",
389
+ "settings.status.rejected": "Rejected (previous config kept)",
390
+ "settings.status.unavailable": "Unavailable",
391
+ "settings.apply": "Validate & apply",
392
+ "settings.apply.applying": "Validating\u2026",
393
+ "settings.apply.ok": "Registry address applied (no restart needed)",
394
+ "settings.apply.failed": "Apply failed: {err}",
395
+ "settings.reset": "Restore default",
396
+ "settings.reset.ok": "Restored to the default registry",
397
+ "settings.download": "Download default registry.json",
398
+ "settings.download.downloading": "Downloading\u2026",
399
+ "settings.download.ok": "Default registry downloaded (current config unchanged)",
400
+ "settings.download.failed": "Download failed: {err}",
401
+ "settings.diagnose": "Check entries reachability",
402
+ "settings.diagnose.running": "Checking\u2026",
403
+ "settings.diagnose.failed": "Diagnose failed: {err}",
404
+ "settings.diagnose.result": "Probes {checked} \xB7 passed {passed} \xB7 failed {failed}{trunc}",
405
+ "settings.diagnose.truncated": " (showing first 100 issues)",
406
+ "settings.diagnose.none": "No issues found",
407
+ "settings.trust.hint": "\u26A0\uFE0F Custom registries are not validated by official CI. Only install entries from sources you trust.",
408
+ "settings.cache.hint": "Old custom-source caches are cleaned after switching (the default cache is kept); a failed custom source keeps its last good cache.",
409
+ "settings.warnings": "Maintenance notice",
410
+ "notice.default": "Official default registry \xB7 {count} listings",
411
+ "notice.custom": "Custom registry \xB7 {count} listings",
412
+ "notice.stale": "Served from local cache ({count} listings) \u2014 force refresh to update",
413
+ "notice.unavailable": "Registry unavailable \xB7 check the address in Settings",
414
+ "market.page.prev": "Previous",
415
+ "market.page.next": "Next",
416
+ "market.page.info": "Page {page} / {pages} \xB7 {total} listings",
417
+ "market.perf": "200+ listings: latest versions are queried for the current page only (50 per page); use search/category filters",
418
+ "notice.toolview.err": "Registry temporarily unavailable",
209
419
  "self.upgraded": "dsh-m updated to v{v} \u2014 restart to take effect",
210
420
  "self.failed": "Self-update failed: {err}",
211
421
  "registry.refreshed": "Registry force-refreshed",
@@ -239,7 +449,7 @@ var EN = {
239
449
  "readme.hide": "Hide README",
240
450
  "readme.loading": "Loading README\u2026 ",
241
451
  "readme.none": "(No README)",
242
- "readme.truncated": "\n\n\u2026(truncated at 64KB \u2014 see the plugin directory for full content)",
452
+ "readme.truncated": "\u2026(truncated at 64KB \u2014 see the plugin directory for full content)",
243
453
  "warn.unlink": "Uninstalling only removes the profile's reference to the local directory ({path}); the directory itself is kept.",
244
454
  "warn.core": "This is a core/archive package installed via file:. Uninstalling may affect DSH features and requires manual restore.",
245
455
  "profile.hint": "web profile: {path}",
@@ -258,7 +468,7 @@ function lookup(key, params) {
258
468
  const dict = browserLang() === "en" ? EN : ZH;
259
469
  return interpolate(dict[key] ?? ZH[key] ?? key, params);
260
470
  }
261
- var CATEGORIES = ["market", "tools", "ui", "search", "media", "other"];
471
+ var CATEGORIES2 = ["market", "tools", "ui", "search", "media", "other"];
262
472
  var CSS = `
263
473
  .dshm-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-mask-3,rgba(15,23,42,.48));display:flex;align-items:center;justify-content:center;padding:24px 16px;box-sizing:border-box}
264
474
  .dshm-panel{width:min(920px,100%);height:min(680px,86vh);display:flex;flex-direction:column;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-layer-3,#fff));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(2,6,23,.25);overflow:hidden;font-family:inherit;color:var(--dsw-alias-label-primary,inherit)}
@@ -281,7 +491,7 @@ var CSS = `
281
491
  .dshm-btn.primary:hover{filter:brightness(1.08)}
282
492
  .dshm-btn.danger{color:var(--dsw-alias-state-error-primary,#b91c1c);border-color:var(--dsw-alias-state-error-primary,#b91c1c)}
283
493
  .dshm-btn.sm{padding:3px 9px;font-size:11px}
284
- .dshm-input{flex:1;min-width:120px;border:1px solid var(--dsw-alias-border-l2,#c7d2fe);background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);border-radius:8px;padding:6px 10px;font:inherit;font-size:13px;outline:none}
494
+ .dshm-input{flex:1;min-width:120px;border:1px solid var(--dsw-alias-border-l2,#c7d2fe);background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);border-radius:8px;padding:5px 10px;font:inherit;font-size:12px;outline:none}
285
495
  .dshm-input:focus{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
286
496
  .dshm-chips{display:flex;flex-wrap:wrap;gap:6px}
287
497
  .dshm-chip{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);border-radius:999px;padding:2px 10px;font:inherit;font-size:11px;cursor:pointer}
@@ -306,12 +516,43 @@ var CSS = `
306
516
  .dshm-banner{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-state-warn-tertiary,#fffbeb);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:12px}
307
517
  .dshm-banner .dshm-banner-text{flex:1}
308
518
  .dshm-row{display:flex;align-items:center;gap:8px}
309
- .dshm-kv{display:grid;grid-template-columns:110px 1fr;gap:4px 10px;font-size:12px}
310
- .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280)}
519
+ .dshm-kv{display:grid;grid-template-columns:96px 1fr;gap:6px 10px;font-size:12px;align-items:baseline}
520
+ .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280);font-size:11px}
521
+ .dshm-section{background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.04));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;margin:0 0 10px}
522
+ .dshm-section:last-child{margin-bottom:2px}
523
+ .dshm-section-title{display:flex;align-items:center;gap:7px;font-weight:700;font-size:12px;letter-spacing:.2px;color:var(--dsw-alias-label-primary,inherit)}
524
+ .dshm-section-title::before{content:"";width:3px;height:11px;border-radius:2px;background:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
525
+ .dshm-section-sub{margin-left:auto;font-weight:400;font-size:11px;color:var(--dsw-alias-label-caption,#9ca3af)}
526
+ .dshm-note{border-left:3px solid var(--dsw-alias-border-l2,#cbd5e1);padding:3px 10px;color:var(--dsw-alias-label-caption,#9ca3af);font-size:11px;line-height:17px;word-break:break-word}
527
+ .dshm-note.warn{border-left-color:var(--dsw-alias-state-warn-primary,#b45309)}
528
+ .dshm-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12));padding:1px 7px;border-radius:5px;word-break:break-all}
311
529
  .dshm-spin{display:inline-block;width:12px;height:12px;border:2px solid var(--dsw-alias-border-l2,#c7d2fe);border-top-color:var(--dsw-alias-interactive-bg-selected,#4f46e5);border-radius:50%;animation:dshm-rot .8s linear infinite;vertical-align:-2px}
312
530
  @keyframes dshm-rot{to{transform:rotate(360deg)}}
313
531
  .dshm-others{display:flex;align-items:center;gap:8px;padding:10px 12px;border:1px dashed var(--dsw-alias-border-l2,#e2e4e8);border-radius:10px;color:var(--dsw-alias-label-caption,#9ca3af);font-size:12px;line-height:18px}
314
- .dshm-readme{max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-word;background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.04));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;padding:10px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#4b5563);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
532
+ .dshm-readme{max-height:280px;overflow:auto;background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.04));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;padding:10px 12px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#4b5563);margin:0}
533
+ .dshm-readme.md{font-family:inherit;white-space:normal;word-break:break-word}
534
+ .dshm-readme.md h1,.dshm-readme.md h2,.dshm-readme.md h3,.dshm-readme.md h4,.dshm-readme.md h5,.dshm-readme.md h6{margin:8px 0 4px;font-weight:700;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}
535
+ .dshm-readme.md h1{font-size:15px}.dshm-readme.md h2{font-size:14px}.dshm-readme.md h3{font-size:13px}.dshm-readme.md h4,.dshm-readme.md h5,.dshm-readme.md h6{font-size:12px}
536
+ .dshm-readme.md>:first-child{margin-top:0}
537
+ .dshm-readme.md p{margin:4px 0}
538
+ .dshm-readme.md a{color:var(--dsw-alias-state-business-primary,#4d6bfe);text-decoration:none}
539
+ .dshm-readme.md a:hover{text-decoration:underline}
540
+ .dshm-readme.md code{background:rgba(127,127,127,.16);border-radius:4px;padding:1px 4px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
541
+ .dshm-readme.md pre{background:rgba(127,127,127,.12);border:1px solid var(--dsw-alias-border-l2,transparent);border-radius:8px;padding:8px 10px;overflow:auto;margin:6px 0}
542
+ .dshm-readme.md pre code{background:transparent;padding:0;font-size:11px;line-height:16px}
543
+ .dshm-readme.md img{max-height:20px;max-width:100%;vertical-align:middle}
544
+ .dshm-readme.md ul,.dshm-readme.md ol{margin:4px 0;padding-left:20px}
545
+ .dshm-readme.md li{margin:2px 0}
546
+ .dshm-readme.md blockquote{margin:6px 0;padding:2px 10px;border-left:3px solid var(--dsw-alias-border-l2,#cbd5e1);color:var(--dsw-alias-label-caption,#6b7280)}
547
+ .dshm-readme.md table{border-collapse:collapse;margin:6px 0;font-size:11px}
548
+ .dshm-readme.md th,.dshm-readme.md td{border:1px solid var(--dsw-alias-border-l2,#cbd5e1);padding:3px 8px;text-align:left}
549
+ .dshm-readme.md th{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1))}
550
+ .dshm-readme.md hr{border:0;border-top:1px solid var(--dsw-alias-border-l2,#cbd5e1);margin:8px 0}
551
+ .dshm-readme.md .dshm-md-note{margin-top:8px;padding-top:6px;border-top:1px dashed var(--dsw-alias-border-l2,#cbd5e1);color:var(--dsw-alias-label-caption,#9ca3af);font-size:11px}
552
+ .dshm-links{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:7px;padding-top:6px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);font-size:11px;color:var(--dsw-alias-label-caption,#6b7280)}
553
+ .dshm-links-k,.dshm-links-sep{color:var(--dsw-alias-label-caption,#9ca3af)}
554
+ .dshm-links a{color:var(--dsw-alias-state-business-primary,#4d6bfe);text-decoration:none;font-weight:500}
555
+ .dshm-links a:hover{text-decoration:underline}
315
556
  .dshm-prog{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
316
557
  .dshm-prog .bar{flex:1;height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.08));overflow:hidden;min-width:80px}
317
558
  .dshm-prog .bar i{display:block;height:100%;background:var(--dsw-alias-interactive-bg-selected,#4f46e5);transition:width .3s}
@@ -330,11 +571,12 @@ function ensureCss() {
330
571
  el.textContent = CSS;
331
572
  document.head.appendChild(el);
332
573
  }
333
- async function api(method, params) {
574
+ async function api(method, params, signal) {
334
575
  const res = await fetch(API, {
335
576
  method: "POST",
336
577
  headers: { "content-type": "application/json" },
337
- body: JSON.stringify({ method, ...params || {} })
578
+ body: JSON.stringify({ method, ...params || {} }),
579
+ ...signal ? { signal } : {}
338
580
  });
339
581
  const data = await res.json().catch(() => ({}));
340
582
  if (!res.ok || data.ok === false) throw new Error(data.error || `API ${res.status}`);
@@ -356,6 +598,51 @@ function useAsync(fn, deps) {
356
598
  }, [run]);
357
599
  return { ...state, reload: run };
358
600
  }
601
+ function useMarketData() {
602
+ const [query, setQuery] = useState(() => normalizeMarketQuery2({}));
603
+ const [data, setData] = useState(null);
604
+ const [loading, setLoading] = useState(true);
605
+ const [error, setError] = useState(null);
606
+ const genRef = useRef(0);
607
+ const abortRef = useRef(null);
608
+ const queryRef = useRef(query);
609
+ const fetchPage = useCallback((nextQuery, force) => {
610
+ const gen = ++genRef.current;
611
+ abortRef.current?.abort();
612
+ const ac = new AbortController();
613
+ abortRef.current = ac;
614
+ setLoading(true);
615
+ setError(null);
616
+ const params = {
617
+ query: nextQuery.query || void 0,
618
+ category: nextQuery.category || void 0,
619
+ offset: nextQuery.offset,
620
+ limit: nextQuery.limit,
621
+ ...force ? { force: true } : {}
622
+ };
623
+ return api("market", params, ac.signal).then((raw) => {
624
+ if (genRef.current !== gen || ac.signal.aborted) return;
625
+ setData(normalizeMarketResponse2(raw));
626
+ setLoading(false);
627
+ }).catch((e) => {
628
+ if (genRef.current !== gen || ac.signal.aborted) return;
629
+ setError(String(e && e.message || e));
630
+ setLoading(false);
631
+ });
632
+ }, []);
633
+ const updateQuery = useCallback((patch, opts = {}) => {
634
+ const next = resetPageOnFilterChange2(queryRef.current, normalizeMarketQuery2({ ...queryRef.current, ...patch }));
635
+ queryRef.current = next;
636
+ setQuery(next);
637
+ if (opts.fetch !== false) fetchPage(next, opts.force);
638
+ }, [fetchPage]);
639
+ const reload = useCallback((force) => fetchPage(queryRef.current, force), [fetchPage]);
640
+ useEffect(() => {
641
+ fetchPage(queryRef.current, false);
642
+ return () => abortRef.current?.abort();
643
+ }, [fetchPage]);
644
+ return { query, data, loading, error, reload, updateQuery };
645
+ }
359
646
  function Icon({ entry }) {
360
647
  const [broken, setBroken] = useState(false);
361
648
  const letter = String(entry.name || entry.id || "?").charAt(0).toUpperCase();
@@ -374,6 +661,181 @@ function Icon({ entry }) {
374
661
  function Spin() {
375
662
  return h("span", { className: "dshm-spin" });
376
663
  }
664
+ function safeUrl(u) {
665
+ const t = String(u || "").trim();
666
+ if (/^(https?:\/\/|mailto:)/i.test(t)) return t;
667
+ if (/^[/#]/.test(t)) return t;
668
+ return "#";
669
+ }
670
+ function ExtLink({ href, className, children }) {
671
+ return h(
672
+ "a",
673
+ {
674
+ className: className || "dshm-md-a",
675
+ href: safeUrl(href),
676
+ target: "_blank",
677
+ rel: "noopener noreferrer",
678
+ onClick: (e) => e.stopPropagation()
679
+ },
680
+ children
681
+ );
682
+ }
683
+ function MdImg({ src, alt }) {
684
+ return h("img", {
685
+ className: "dshm-md-img",
686
+ src: safeUrl(src),
687
+ alt: alt || "",
688
+ referrerPolicy: "no-referrer",
689
+ onError: (e) => {
690
+ e.currentTarget.style.display = "none";
691
+ }
692
+ });
693
+ }
694
+ function mdInline(text, kb) {
695
+ const re = /(\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\))|(!\[[^\]]*\]\([^)]*\))|(\[[^\]]*\]\([^)]*\))|(`[^`]+`)|(\*\*[^*]+\*\*)|(~~[^~]+~~)|(\*[^*\s][^*]*\*)|(<https?:\/\/[^>\s]+>)|(https?:\/\/[^\s<>()\[\]{}"'「」【】]+[^\s<>()\[\]{}"'「」【】.,;:!?…,。;:!?)】」"')])/g;
696
+ const src = String(text);
697
+ const nodes = [];
698
+ let last = 0;
699
+ let m;
700
+ let i = 0;
701
+ while (m = re.exec(src)) {
702
+ if (m.index > last) nodes.push(src.slice(last, m.index));
703
+ const tok = m[0];
704
+ const k = `${kb}-${i++}`;
705
+ if (tok.startsWith("[![")) {
706
+ const im = /^!\[([^\]]*)\]\(([^)]*)\)/.exec(tok.slice(1));
707
+ const lm = /\]\(([^)]*)\)\s*$/.exec(tok);
708
+ const img = h(MdImg, { src: im && im[2], alt: im && im[1] });
709
+ const href = lm && lm[1];
710
+ nodes.push(href && safeUrl(href) !== "#" ? h(ExtLink, { key: k, href }, img) : h("span", { key: k }, img));
711
+ } else if (tok.startsWith("![") || tok.startsWith("<![")) {
712
+ const im = /^!\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
713
+ nodes.push(h(MdImg, { key: k, src: im && im[2], alt: im && im[1] }));
714
+ } else if (tok.startsWith("[")) {
715
+ const lm = /^\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
716
+ nodes.push(h(ExtLink, { key: k, href: lm && lm[2] }, mdInline(lm ? lm[1] : tok, k)));
717
+ } else if (tok.startsWith("`")) {
718
+ nodes.push(h("code", { key: k }, tok.slice(1, -1)));
719
+ } else if (tok.startsWith("**")) {
720
+ nodes.push(h("strong", { key: k }, mdInline(tok.slice(2, -2), k)));
721
+ } else if (tok.startsWith("~~")) {
722
+ nodes.push(h("del", { key: k }, mdInline(tok.slice(2, -2), k)));
723
+ } else if (tok.startsWith("*")) {
724
+ nodes.push(h("em", { key: k }, mdInline(tok.slice(1, -1), k)));
725
+ } else if (tok.startsWith("<")) {
726
+ const u = tok.slice(1, -1);
727
+ nodes.push(h(ExtLink, { key: k, href: u }, u));
728
+ } else {
729
+ nodes.push(h(ExtLink, { key: k, href: tok }, tok.length > 72 ? `${tok.slice(0, 69)}\u2026` : tok));
730
+ }
731
+ last = m.index + tok.length;
732
+ }
733
+ if (last < src.length) nodes.push(src.slice(last));
734
+ return nodes;
735
+ }
736
+ function mdBlocks(lines, kb) {
737
+ const out = [];
738
+ let i = 0;
739
+ let n = 0;
740
+ const isFence = (s) => /^\s*```/.test(s);
741
+ const isHeading = (s) => /^#{1,6}\s+/.test(s);
742
+ const isHr = (s) => /^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(s);
743
+ const isQuote = (s) => /^\s*>/.test(s);
744
+ const isUl = (s) => /^\s*[-*+]\s+/.test(s);
745
+ const isOl = (s) => /^\s*\d+[.)]\s+/.test(s);
746
+ const isTableRow = (s) => s.includes("|") && /^\s*\|/.test(s);
747
+ while (i < lines.length) {
748
+ const line = lines[i];
749
+ if (!line.trim()) {
750
+ i++;
751
+ continue;
752
+ }
753
+ const k = `${kb}-b${n++}`;
754
+ if (isFence(line)) {
755
+ const buf2 = [];
756
+ i++;
757
+ while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) buf2.push(lines[i++]);
758
+ i++;
759
+ out.push(h("pre", { key: k }, h("code", null, buf2.join("\n"))));
760
+ continue;
761
+ }
762
+ if (isHeading(line)) {
763
+ const hm = /^(#{1,6})\s+(.*)$/.exec(line);
764
+ out.push(h(`h${hm[1].length}`, { key: k }, mdInline(hm[2], k)));
765
+ i++;
766
+ continue;
767
+ }
768
+ if (isHr(line)) {
769
+ out.push(h("hr", { key: k }));
770
+ i++;
771
+ continue;
772
+ }
773
+ if (isQuote(line)) {
774
+ const buf2 = [];
775
+ while (i < lines.length && isQuote(lines[i])) buf2.push(lines[i++].replace(/^\s*>\s?/, ""));
776
+ out.push(h("blockquote", { key: k }, mdBlocks(buf2, k)));
777
+ continue;
778
+ }
779
+ if (isUl(line) || isOl(line)) {
780
+ const ordered = isOl(line);
781
+ const re = ordered ? /^\s*\d+[.)]\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/;
782
+ const items = [];
783
+ while (i < lines.length && (ordered ? isOl(lines[i]) : isUl(lines[i]))) {
784
+ items.push(h("li", { key: `li${items.length}` }, mdInline(re.exec(lines[i])[1], `${k}-${items.length}`)));
785
+ i++;
786
+ }
787
+ out.push(h(ordered ? "ol" : "ul", { key: k }, items));
788
+ continue;
789
+ }
790
+ if (isTableRow(line) && i + 1 < lines.length && lines[i + 1].includes("-") && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1])) {
791
+ const cells = (s) => s.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
792
+ const head = cells(lines[i]);
793
+ i += 2;
794
+ const rows = [];
795
+ while (i < lines.length && isTableRow(lines[i])) {
796
+ rows.push(cells(lines[i]));
797
+ i++;
798
+ }
799
+ out.push(
800
+ h(
801
+ "table",
802
+ { key: k },
803
+ h("thead", null, h("tr", null, head.map((c, x) => h("th", { key: x }, mdInline(c, `${k}h${x}`))))),
804
+ h("tbody", null, rows.map((r, y) => h("tr", { key: y }, r.map((c, x) => h("td", { key: x }, mdInline(c, `${k}${y}x${x}`))))))
805
+ )
806
+ );
807
+ continue;
808
+ }
809
+ const buf = [line];
810
+ i++;
811
+ while (i < lines.length && lines[i].trim() && !isFence(lines[i]) && !isHeading(lines[i]) && !isHr(lines[i]) && !isQuote(lines[i]) && !isUl(lines[i]) && !isOl(lines[i])) {
812
+ buf.push(lines[i]);
813
+ i++;
814
+ }
815
+ out.push(h("p", { key: k }, mdInline(buf.join(" "), k)));
816
+ }
817
+ return out;
818
+ }
819
+ function renderMarkdown(src) {
820
+ return mdBlocks(String(src || "").replace(/\r\n?/g, "\n").split("\n"), "md");
821
+ }
822
+ function officialLinks({ npm, github, homepage }) {
823
+ const links = [];
824
+ if (github) links.push(["GitHub", `https://github.com/${github}`]);
825
+ if (npm) links.push(["npm", `https://www.npmjs.com/package/${npm}`]);
826
+ if (!links.length && homepage) links.push([lookup("link.home"), homepage]);
827
+ return links;
828
+ }
829
+ function LinksRow(props) {
830
+ const links = officialLinks(props);
831
+ if (!links.length) return null;
832
+ const kids = [];
833
+ links.forEach(([label, href], idx) => {
834
+ if (idx) kids.push(h("span", { key: `sep${idx}`, className: "dshm-links-sep" }, "\xB7"));
835
+ kids.push(h(ExtLink, { key: label, href }, label));
836
+ });
837
+ return h("div", { className: "dshm-links" }, h("span", { className: "dshm-links-k" }, `${lookup("detail.links")}\uFF1A`), ...kids);
838
+ }
377
839
  function TwoStepButton({ label, confirmLabel, className, onConfirm, disabled }) {
378
840
  const [arm, setArm] = useState(false);
379
841
  useEffect(() => {
@@ -440,28 +902,32 @@ function RestartBanner({ note, onDone }) {
440
902
  phase === "idle" && !err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, lookup("common.later")) : null
441
903
  );
442
904
  }
443
- function MarketTab({ notify, onCount }) {
444
- const { loading, data, error, reload } = useAsync((force) => api("market", force ? { force: true } : {}), []);
445
- const [q, setQ] = useState("");
446
- const [cat, setCat] = useState(null);
905
+ function MarketTab({ notify, market }) {
906
+ const { data, loading, error, reload, query, updateQuery } = market;
447
907
  const [openId, setOpenId] = useState(null);
448
908
  const [busyId, setBusyId] = useState(null);
449
- const items = useMemo(() => {
450
- const list = data && data.items || [];
451
- const kw = q.trim().toLowerCase();
452
- return list.filter((it) => {
453
- if (cat && it.category !== cat) return false;
454
- if (!kw) return true;
455
- const hay = `${it.id} ${it.name} ${it.description} ${(it.tags || []).join(" ")}`.toLowerCase();
456
- return hay.includes(kw);
457
- });
458
- }, [data, q, cat]);
909
+ const [qInput, setQInput] = useState(query.query);
910
+ const debounceRef = useRef(null);
911
+ const items = data && data.items || [];
912
+ const total = data && data.total || 0;
913
+ const limit = data && data.limit || MARKET_PAGE_SIZE2;
914
+ const offset = data && data.offset || 0;
915
+ const page = total > 0 ? Math.floor(offset / limit) + 1 : 1;
916
+ const pages = total > 0 ? Math.max(1, Math.ceil(total / limit)) : 1;
917
+ const counts = data && data.categoryCounts || {};
918
+ const notice = data ? registryNotice2(data.registryState, total) : null;
919
+ const onSearchInput = (value) => {
920
+ setQInput(value);
921
+ if (debounceRef.current) clearTimeout(debounceRef.current);
922
+ debounceRef.current = setTimeout(() => updateQuery({ query: value }), 300);
923
+ };
459
924
  const doInstall = async (it, version) => {
460
925
  setBusyId(it.id);
461
926
  try {
462
927
  const res = await api("install", { id: it.id, ...version ? { version } : {} });
463
928
  notify({
464
929
  kind: "ok",
930
+ needsRestart: true,
465
931
  text: lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + (res.usedAllowAllBuilds ? lookup("notify.allowbuilds") : "")
466
932
  });
467
933
  await reload(false);
@@ -474,75 +940,101 @@ function MarketTab({ notify, onCount }) {
474
940
  return h(
475
941
  React.Fragment,
476
942
  null,
943
+ notice ? h(
944
+ "div",
945
+ { className: notice.key === "notice.unavailable" ? "dshm-err" : "dshm-hint" },
946
+ lookup(notice.key, { count: notice.count })
947
+ ) : null,
948
+ total > 200 ? h("div", { className: "dshm-hint" }, lookup("market.perf")) : null,
477
949
  h(
478
950
  "div",
479
951
  { className: "dshm-row" },
480
952
  h("input", {
481
953
  className: "dshm-input",
482
954
  placeholder: lookup("search.ph"),
483
- value: q,
484
- onChange: (e) => setQ(e.target.value)
955
+ value: qInput,
956
+ onChange: (e) => onSearchInput(e.target.value)
485
957
  }),
486
958
  h("button", { className: "dshm-btn", onClick: () => reload(true), title: lookup("settings.policy.v") }, loading ? Spin() : `\u21BB ${lookup("common.refresh")}`)
487
959
  ),
488
960
  h(
489
961
  "div",
490
962
  { className: "dshm-chips" },
491
- h("button", { className: `dshm-chip${cat === null ? " on" : ""}`, onClick: () => setCat(null) }, lookup("cat.all")),
492
- CATEGORIES.map((key) => {
493
- const n = (data && data.items || []).filter((it) => it.category === key).length;
963
+ h("button", { className: `dshm-chip${query.category === null ? " on" : ""}`, onClick: () => updateQuery({ category: null, offset: 0 }) }, lookup("cat.all")),
964
+ CATEGORIES2.map((key) => {
965
+ const n = typeof counts[key] === "number" ? counts[key] : 0;
494
966
  return h(
495
967
  "button",
496
- { key, className: `dshm-chip${cat === key ? " on" : ""}`, onClick: () => setCat(cat === key ? null : key) },
968
+ { key, className: `dshm-chip${query.category === key ? " on" : ""}`, onClick: () => updateQuery({ category: query.category === key ? null : key, offset: 0 }) },
497
969
  `${lookup("cat." + key)}${n ? ` ${n}` : ""}`
498
970
  );
499
971
  })
500
972
  ),
501
973
  busyId ? h(ProgressLine, { key: "prog" }) : null,
502
974
  loading && !data ? h("div", { className: "dshm-empty" }, lookup("market.loading"), Spin()) : error ? h("div", { className: "dshm-err" }, lookup("failed.load", { err: error })) : items.length === 0 ? h("div", { className: "dshm-empty" }, lookup("market.empty")) : h(
503
- "div",
504
- { className: "dshm-cards" },
505
- items.map((it) => Card({
506
- key: it.id,
507
- icon: h(Icon, { entry: it }),
508
- name: it.name,
509
- badges: [
510
- it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, lookup("badge.update")) : null,
511
- it.installed ? h("span", { className: "dshm-badge", key: "i" }, lookup("badge.installed")) : null,
512
- h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
513
- ],
514
- desc: it.description,
515
- sub: [
516
- it.latestVersion ? lookup("sub.latest", { v: it.latestVersion }) : it.latestTag ? it.latestTag : it.latestSha ? lookup("sub.head", { sha: it.latestSha.slice(0, 7) }) : null,
517
- it.installedVersion ? lookup("sub.installed", { v: it.installedVersion }) : null,
518
- it.latestError ? lookup("version.failed") : null
519
- ].filter(Boolean).join(" \xB7 "),
520
- open: openId === it.id,
521
- onToggle: () => setOpenId(openId === it.id ? null : it.id),
522
- detail: DetailRows([
523
- [lookup("detail.id"), it.id],
524
- [lookup("detail.source"), it.source === "npm" ? `npm \xB7 ${it.npm}` : `GitHub \xB7 ${it.github}`],
525
- [lookup("detail.latest"), it.latestVersion ? `v${it.latestVersion}` : it.latestTag ? it.latestTag : it.latestSha ? it.latestSha : it.latestError || "\u2014"],
526
- [lookup("detail.installed"), it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : lookup("installed.none")],
527
- [lookup("detail.tags"), (it.tags || []).join(", ") || "\u2014"],
528
- it.latestError ? [lookup("version.failed"), it.latestError] : null
529
- ]),
530
- actions: [
531
- it.installed ? h("span", { className: "dshm-hint", key: "hint" }, lookup("manage.hint")) : h(
532
- "button",
533
- {
534
- key: "install",
535
- className: "dshm-btn primary sm",
536
- disabled: busyId === it.id,
537
- onClick: (e) => {
538
- e.stopPropagation();
539
- doInstall(it);
540
- }
541
- },
542
- busyId === it.id ? h(Spin) : lookup("action.install")
543
- )
544
- ]
545
- }))
975
+ React.Fragment,
976
+ null,
977
+ h(
978
+ "div",
979
+ { className: "dshm-cards" },
980
+ items.map((it) => Card({
981
+ key: it.id,
982
+ icon: h(Icon, { entry: it }),
983
+ name: it.name,
984
+ badges: [
985
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, lookup("badge.update")) : null,
986
+ it.installed ? h("span", { className: "dshm-badge", key: "i" }, lookup("badge.installed")) : null,
987
+ h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
988
+ ],
989
+ desc: it.description,
990
+ sub: [
991
+ it.latestVersion ? lookup("sub.latest", { v: it.latestVersion }) : it.latestTag ? it.latestTag : it.latestSha ? lookup("sub.head", { sha: it.latestSha.slice(0, 7) }) : null,
992
+ it.installedVersion ? lookup("sub.installed", { v: it.installedVersion }) : null,
993
+ it.latestError ? lookup("version.failed") : null
994
+ ].filter(Boolean).join(" \xB7 "),
995
+ links: h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
996
+ open: openId === it.id,
997
+ onToggle: () => setOpenId(openId === it.id ? null : it.id),
998
+ detail: DetailRows([
999
+ [lookup("detail.id"), it.id],
1000
+ [lookup("detail.source"), it.source === "npm" ? h(ExtLink, { href: `https://www.npmjs.com/package/${it.npm}` }, `npm \xB7 ${it.npm}`) : h(ExtLink, { href: `https://github.com/${it.github}` }, `GitHub \xB7 ${it.github}`)],
1001
+ [lookup("detail.latest"), it.latestVersion ? `v${it.latestVersion}` : it.latestTag ? it.latestTag : it.latestSha ? it.latestSha : it.latestError || "\u2014"],
1002
+ [lookup("detail.installed"), it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : lookup("installed.none")],
1003
+ [lookup("detail.tags"), (it.tags || []).join(", ") || "\u2014"],
1004
+ it.latestError ? [lookup("version.failed"), it.latestError] : null
1005
+ ]),
1006
+ actions: [
1007
+ it.installed ? h("span", { className: "dshm-hint", key: "hint" }, lookup("manage.hint")) : h(
1008
+ "button",
1009
+ {
1010
+ key: "install",
1011
+ className: "dshm-btn primary sm",
1012
+ disabled: busyId === it.id,
1013
+ onClick: (e) => {
1014
+ e.stopPropagation();
1015
+ doInstall(it);
1016
+ }
1017
+ },
1018
+ busyId === it.id ? h(Spin) : lookup("action.install")
1019
+ )
1020
+ ]
1021
+ }))
1022
+ ),
1023
+ pages > 1 ? h(
1024
+ "div",
1025
+ { className: "dshm-row", style: { justifyContent: "center", marginTop: "4px" } },
1026
+ h("button", {
1027
+ className: "dshm-btn sm",
1028
+ disabled: page <= 1 || loading,
1029
+ onClick: () => updateQuery({ offset: Math.max(0, offset - limit) })
1030
+ }, lookup("market.page.prev")),
1031
+ h("span", { className: "dshm-hint" }, lookup("market.page.info", { page, pages, total })),
1032
+ h("button", {
1033
+ className: "dshm-btn sm",
1034
+ disabled: page >= pages || loading,
1035
+ onClick: () => updateQuery({ offset: Math.min(total - 1, offset + limit) })
1036
+ }, lookup("market.page.next"))
1037
+ ) : null
546
1038
  )
547
1039
  );
548
1040
  }
@@ -586,10 +1078,15 @@ function ReadmeBlock({ pkg }) {
586
1078
  live = false;
587
1079
  };
588
1080
  }, [pkg]);
589
- if (state.loading) return h("div", { className: "dshm-hint" }, "\u52A0\u8F7D README\u2026 ", Spin());
1081
+ if (state.loading) return h("div", { className: "dshm-hint" }, lookup("readme.loading"), Spin());
590
1082
  if (state.err) return h("div", { className: "dshm-err" }, state.err);
591
- if (!state.text) return h("div", { className: "dshm-hint" }, "\uFF08\u8BE5\u63D2\u4EF6\u6CA1\u6709 README\uFF09");
592
- return h("pre", { className: "dshm-readme" }, state.text + (state.truncated ? "\n\n\u2026\uFF08\u8D85\u8FC7 64KB \u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1\u63D2\u4EF6\u76EE\u5F55\uFF09" : ""));
1083
+ if (!state.text) return h("div", { className: "dshm-hint" }, lookup("readme.none"));
1084
+ return h(
1085
+ "div",
1086
+ { className: "dshm-readme md", onClick: (e) => e.stopPropagation() },
1087
+ renderMarkdown(state.text),
1088
+ state.truncated ? h("div", { className: "dshm-md-note" }, lookup("readme.truncated")) : null
1089
+ );
593
1090
  }
594
1091
  function uninstallGuard(it) {
595
1092
  if (it.source === "link") {
@@ -614,6 +1111,7 @@ function InstalledTab({ notify, installed }) {
614
1111
  const res = await api("uninstall", { pkg: it.pkg });
615
1112
  notify({
616
1113
  kind: "ok",
1114
+ needsRestart: true,
617
1115
  text: lookup("notify.uninstalled", { pkg: res.pkg }) + (res.liveDisabled ? lookup("notify.livedisabled") : "") + (res.leftovers && res.leftovers.length ? lookup("notify.leftovers", { paths: res.leftovers.join(", ") }) : "")
618
1116
  });
619
1117
  await reload();
@@ -629,6 +1127,7 @@ function InstalledTab({ notify, installed }) {
629
1127
  const res = await api("upgrade", { pkg: it.pkg });
630
1128
  notify({
631
1129
  kind: "ok",
1130
+ needsRestart: true,
632
1131
  text: lookup("notify.upgraded", {
633
1132
  pkg: res.pkg,
634
1133
  from: res.fromVersion ? `v${res.fromVersion}` : "\u2014",
@@ -670,6 +1169,10 @@ function InstalledTab({ notify, installed }) {
670
1169
  `v${it.version || "?"}`,
671
1170
  { npm: lookup("src.npm"), github: lookup("src.github"), link: lookup("src.link"), file: lookup("src.file"), unknown: lookup("src.unknown") }[it.source] || it.source
672
1171
  ].join(" \xB7 "),
1172
+ links: h(LinksRow, {
1173
+ npm: it.source === "npm" ? it.pkg : null,
1174
+ github: it.registryGithub || it.githubRepo || (it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null)
1175
+ }),
673
1176
  open: openPkg === it.pkg,
674
1177
  onToggle: () => setOpenPkg(openPkg === it.pkg ? null : it.pkg),
675
1178
  detail: readmePkg === it.pkg ? h(ReadmeBlock, { pkg: it.pkg }) : DetailRows([
@@ -716,32 +1219,114 @@ function InstalledTab({ notify, installed }) {
716
1219
  )
717
1220
  );
718
1221
  }
719
- function SettingsTab({ notify }) {
1222
+ function regSourceLabel(data) {
1223
+ if (!data) return "\u2014";
1224
+ const map = {
1225
+ "default-raw": "src.default.raw",
1226
+ "default-jsdelivr": "src.default.jsdelivr",
1227
+ "default-cache": "src.default.cache",
1228
+ bundled: "src.bundled",
1229
+ "custom-url": "src.custom.url",
1230
+ "custom-file": "src.custom.file",
1231
+ "custom-cache": "src.custom.cache",
1232
+ "custom-unavailable": "src.custom.unavailable",
1233
+ // 旧字段兼容
1234
+ override: "src.override",
1235
+ jsdelivr: "src.jsdelivr",
1236
+ raw: "src.raw",
1237
+ cache: "src.cache"
1238
+ };
1239
+ return lookup(map[data.source] || data.source);
1240
+ }
1241
+ function configStatusLabel(status) {
1242
+ return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
1243
+ }
1244
+ var STATUS_BADGE = { ready: "", pending: "info", rejected: "warn", unavailable: "err", loading: "info" };
1245
+ function SettingsTab({ notify, onRegistryChanged }) {
720
1246
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
1247
+ const cfgState = useAsync(() => api("registry-config"), []);
721
1248
  const self = useAsync(() => api("self-check"), []);
722
1249
  const [busy, setBusy] = useState(false);
723
1250
  const [upgrading, setUpgrading] = useState(false);
1251
+ const [draftAddress, setDraftAddress] = useState(null);
1252
+ const [applying, setApplying] = useState(false);
1253
+ const [downloading, setDownloading] = useState(false);
1254
+ const [diagnosing, setDiagnosing] = useState(false);
1255
+ const [applyError, setApplyError] = useState(null);
1256
+ const [diagnosticResult, setDiagnosticResult] = useState(null);
1257
+ const diagnoseAbort = useRef(null);
1258
+ const cfgData = cfgState.data;
1259
+ useEffect(() => {
1260
+ if (draftAddress === null && cfgData) setDraftAddress(cfgData.registryUrl ?? "");
1261
+ }, [cfgData, draftAddress]);
1262
+ useEffect(() => () => diagnoseAbort.current?.abort(), []);
724
1263
  const refresh = async () => {
725
1264
  setBusy(true);
726
1265
  try {
727
1266
  await reg.reload(true);
728
- notify({ kind: "ok", text: lookup("registry.refreshed") });
1267
+ notify({ kind: "ok", text: lookup("registry.refreshed"), needsRestart: false });
729
1268
  } finally {
730
1269
  setBusy(false);
731
1270
  }
732
1271
  };
733
- const upgradeSelf = async () => {
734
- setUpgrading(true);
1272
+ const reloadRegistryState = async () => {
1273
+ await cfgState.reload().catch(() => void 0);
1274
+ };
1275
+ const applyAddress = async (raw) => {
1276
+ setApplying(true);
1277
+ setApplyError(null);
735
1278
  try {
736
- const res = await api("self-upgrade");
737
- notify({ kind: "ok", text: lookup("self.upgraded", { v: res.version }) });
738
- await self.reload();
1279
+ await api("registry-config-apply", { registryUrl: raw });
1280
+ setDraftAddress(typeof raw === "string" ? raw.trim() : "");
1281
+ notify({ kind: "ok", text: raw.trim() === "" ? lookup("settings.reset.ok") : lookup("settings.apply.ok"), needsRestart: false });
1282
+ await reloadRegistryState();
1283
+ onRegistryChanged?.();
739
1284
  } catch (e) {
740
- notify({ kind: "err", text: lookup("failed.selfupdate", { err: e && e.message || e }) });
1285
+ const message = String(e && e.message || e);
1286
+ setApplyError(message);
1287
+ await reloadRegistryState().catch(() => void 0);
741
1288
  } finally {
742
- setUpgrading(false);
1289
+ setApplying(false);
743
1290
  }
744
1291
  };
1292
+ const downloadDefault = async () => {
1293
+ setDownloading(true);
1294
+ try {
1295
+ const res = await api("registry-default-download");
1296
+ const text = JSON.stringify(res.registry, null, 2);
1297
+ const blob = new Blob([text], { type: "application/json;charset=utf-8" });
1298
+ const url = URL.createObjectURL(blob);
1299
+ const a = document.createElement("a");
1300
+ a.href = url;
1301
+ a.download = "registry.json";
1302
+ document.body.appendChild(a);
1303
+ a.click();
1304
+ a.remove();
1305
+ URL.revokeObjectURL(url);
1306
+ notify({ kind: "ok", text: lookup("settings.download.ok"), needsRestart: false });
1307
+ } catch (e) {
1308
+ notify({ kind: "err", text: lookup("settings.download.failed", { err: e && e.message || e }) });
1309
+ } finally {
1310
+ setDownloading(false);
1311
+ }
1312
+ };
1313
+ const runDiagnose = async () => {
1314
+ diagnoseAbort.current?.abort();
1315
+ const ac = new AbortController();
1316
+ diagnoseAbort.current = ac;
1317
+ setDiagnosing(true);
1318
+ setDiagnosticResult(null);
1319
+ try {
1320
+ const res = await api("registry-diagnose", {}, ac.signal);
1321
+ if (!ac.signal.aborted) setDiagnosticResult(res.check);
1322
+ } catch (e) {
1323
+ if (!ac.signal.aborted) notify({ kind: "err", text: lookup("settings.diagnose.failed", { err: e && e.message || e }) });
1324
+ } finally {
1325
+ if (!ac.signal.aborted) setDiagnosing(false);
1326
+ }
1327
+ };
1328
+ const snap = cfgData || {};
1329
+ const state = snap.registryState || (reg.data ? reg.data.registryState : null) || null;
745
1330
  return h(
746
1331
  React.Fragment,
747
1332
  null,
@@ -749,22 +1334,79 @@ function SettingsTab({ notify }) {
749
1334
  lookup("settings.registry"),
750
1335
  h(
751
1336
  "div",
752
- { className: "dshm-kv" },
753
- h("span", { className: "k" }, lookup("settings.source")),
754
- h("span", null, regSourceLabel(reg.data)),
1337
+ { className: "dshm-row", style: { flexDirection: "column", alignItems: "stretch", gap: "4px" } },
1338
+ h("input", {
1339
+ className: "dshm-input",
1340
+ placeholder: lookup("settings.address.ph"),
1341
+ value: draftAddress ?? "",
1342
+ onChange: (e) => setDraftAddress(e.target.value),
1343
+ spellcheck: "false"
1344
+ }),
1345
+ h("div", { className: "dshm-note" }, lookup("settings.address.hint"))
1346
+ ),
1347
+ h(
1348
+ "div",
1349
+ { className: "dshm-actions" },
1350
+ h("button", {
1351
+ className: "dshm-btn primary sm",
1352
+ disabled: applying || diagnosing || draftAddress === null,
1353
+ onClick: () => applyAddress(draftAddress ?? "")
1354
+ }, applying ? h("span", null, lookup("settings.apply.applying"), " ", h(Spin)) : lookup("settings.apply")),
1355
+ h("button", {
1356
+ className: "dshm-btn sm",
1357
+ disabled: applying || draftAddress === null || (draftAddress ?? "").trim() === "",
1358
+ onClick: () => applyAddress("")
1359
+ }, lookup("settings.reset")),
1360
+ h("button", {
1361
+ className: "dshm-btn sm",
1362
+ disabled: downloading,
1363
+ onClick: downloadDefault
1364
+ }, downloading ? h("span", null, lookup("settings.download.downloading"), " ", h(Spin)) : lookup("settings.download")),
1365
+ h("button", {
1366
+ className: "dshm-btn sm",
1367
+ disabled: diagnosing || applying,
1368
+ onClick: runDiagnose
1369
+ }, diagnosing ? h("span", null, lookup("settings.diagnose.running"), " ", h(Spin)) : lookup("settings.diagnose")),
1370
+ h("button", { className: "dshm-btn sm", disabled: busy || reg.loading, onClick: refresh }, busy || reg.loading ? h(Spin) : lookup("settings.force"))
1371
+ ),
1372
+ applyError ? h("div", { className: "dshm-err" }, lookup("settings.apply.failed", { err: applyError })) : null,
1373
+ h(
1374
+ "div",
1375
+ { className: "dshm-kv", style: { marginTop: "4px" } },
1376
+ h("span", { className: "k" }, lookup("settings.configured")),
1377
+ h("span", null, snap.registryUrl ? h("span", { className: "dshm-code" }, snap.registryUrl) : h("span", { className: "dshm-hint" }, "\uFF08\u9ED8\u8BA4\uFF09")),
1378
+ h("span", { className: "k" }, lookup("settings.activecfg")),
1379
+ h("span", null, snap.activeConfigAddress ? h("span", { className: "dshm-code" }, snap.activeConfigAddress) : h("span", { className: "dshm-hint" }, "\uFF08\u9ED8\u8BA4\uFF09")),
1380
+ h("span", { className: "k" }, lookup("settings.status.label")),
1381
+ h("span", null, h("span", { className: `dshm-badge ${STATUS_BADGE[snap.configStatus] || ""}` }, configStatusLabel(snap.configStatus))),
1382
+ h("span", { className: "k" }, lookup("settings.effective")),
1383
+ h("span", null, regSourceLabel(state), state && !state.isDefault ? h("span", { className: "dshm-badge info", style: { marginLeft: "6px" } }, lookup("badge.custom")) : null),
755
1384
  h("span", { className: "k" }, lookup("settings.updated")),
756
- h("span", null, fmtDate(reg.data && reg.data.fetchedAt)),
1385
+ h("span", null, fmtDate(state && state.fetchedAt)),
757
1386
  h("span", { className: "k" }, lookup("settings.count")),
758
- h("span", null, reg.data ? lookup("settings.count.v", { n: reg.data.plugins.length }) : "\u2014"),
1387
+ h("span", null, state ? lookup("settings.count.v", { n: state.count ?? 0 }) : "\u2014"),
759
1388
  h("span", { className: "k" }, lookup("settings.policy")),
760
1389
  h("span", null, lookup("settings.policy.v"))
761
1390
  ),
762
- reg.data && reg.data.errors && reg.data.errors.length ? h("div", { className: "dshm-err" }, `${lookup("settings.remotehint")}\uFF1A${reg.data.errors.join("\uFF1B")}`) : null,
763
- h(
1391
+ state && state.stale && state.status !== "unavailable" ? h("div", { className: "dshm-note" }, lookup("settings.cache.hint")) : null,
1392
+ state && !state.isDefault ? h("div", { className: "dshm-note warn" }, lookup("settings.trust.hint")) : null,
1393
+ snap.warnings && snap.warnings.length ? h("div", { className: "dshm-note warn" }, `${lookup("settings.warnings")}\uFF1A${snap.warnings.join("\uFF1B")}`) : null,
1394
+ state && state.errors && state.errors.length ? h("div", { className: "dshm-err" }, `${lookup("settings.remotehint")}\uFF1A${state.errors.slice(0, 5).join("\uFF1B")}`) : null,
1395
+ diagnosticResult ? h(
764
1396
  "div",
765
- { className: "dshm-actions" },
766
- h("button", { className: "dshm-btn sm", disabled: busy || reg.loading, onClick: refresh }, busy || reg.loading ? h(Spin) : lookup("settings.force"))
767
- )
1397
+ { className: "dshm-hint", style: { wordBreak: "break-all" } },
1398
+ lookup("settings.diagnose.result", {
1399
+ checked: diagnosticResult.checked ?? 0,
1400
+ passed: diagnosticResult.passed ?? 0,
1401
+ failed: diagnosticResult.failed ?? 0,
1402
+ trunc: diagnosticResult.truncated ? lookup("settings.diagnose.truncated") : ""
1403
+ }),
1404
+ diagnosticResult.issues && diagnosticResult.issues.length ? h(
1405
+ "div",
1406
+ { style: { marginTop: "4px" } },
1407
+ diagnosticResult.issues.slice(0, 100).map((iss, i) => h("div", { key: i, className: "dshm-err" }, `\xB7 [${iss.id}] ${iss.field}: ${iss.message}`))
1408
+ ) : h("div", { className: "dshm-note" }, lookup("settings.diagnose.none"))
1409
+ ) : null
768
1410
  ),
769
1411
  Section(
770
1412
  lookup("settings.self"),
@@ -788,17 +1430,33 @@ function SettingsTab({ notify }) {
788
1430
  h("div", { className: "dshm-hint" }, lookup("settings.about.text"))
789
1431
  )
790
1432
  );
1433
+ async function upgradeSelf() {
1434
+ setUpgrading(true);
1435
+ try {
1436
+ const res = await api("self-upgrade");
1437
+ notify({ kind: "ok", text: lookup("self.upgraded", { v: res.version }), needsRestart: true });
1438
+ await self.reload();
1439
+ } catch (e) {
1440
+ notify({ kind: "err", text: lookup("failed.selfupdate", { err: e && e.message || e }) });
1441
+ } finally {
1442
+ setUpgrading(false);
1443
+ }
1444
+ }
791
1445
  }
792
- function regSourceLabel(data) {
793
- if (!data) return "\u2014";
794
- const map = { override: "src.override", jsdelivr: "src.jsdelivr", raw: "src.raw", cache: "src.cache", bundled: "src.bundled" };
795
- return lookup(map[data.source] || data.source);
796
- }
797
- function Section(title, ...children) {
1446
+ function Section(title, sub, ...children) {
1447
+ if (sub != null && typeof sub !== "string") {
1448
+ children = [sub, ...children];
1449
+ sub = null;
1450
+ }
798
1451
  return h(
799
1452
  "div",
800
- { style: { display: "flex", flexDirection: "column", gap: "8px" } },
801
- h("div", { style: { fontWeight: 600, fontSize: "13px" } }, title),
1453
+ { className: "dshm-section" },
1454
+ h(
1455
+ "div",
1456
+ { className: "dshm-section-title" },
1457
+ title,
1458
+ sub ? h("span", { className: "dshm-section-sub" }, sub) : null
1459
+ ),
802
1460
  ...children
803
1461
  );
804
1462
  }
@@ -809,11 +1467,11 @@ function DetailRows(rows) {
809
1467
  { className: "dshm-kv" },
810
1468
  list.flatMap(([k, v]) => [
811
1469
  h("span", { className: "k", key: `${k}-k` }, k),
812
- h("span", { key: `${k}-v`, style: { wordBreak: "break-all" } }, String(v))
1470
+ h("span", { key: `${k}-v`, style: { wordBreak: "break-all" } }, v)
813
1471
  ])
814
1472
  );
815
1473
  }
816
- function Card({ icon, name, badges, desc, sub, open, onToggle, detail, actions }) {
1474
+ function Card({ icon, name, badges, desc, sub, links, open, onToggle, detail, actions }) {
817
1475
  return h(
818
1476
  "div",
819
1477
  {
@@ -832,6 +1490,7 @@ function Card({ icon, name, badges, desc, sub, open, onToggle, detail, actions }
832
1490
  h("div", { className: "dshm-top" }, h("span", { className: "dshm-name" }, name), ...badges.filter(Boolean)),
833
1491
  h("div", { className: "dshm-desc", style: open ? { WebkitLineClamp: "unset" } : null }, desc),
834
1492
  sub ? h("div", { className: "dshm-sub" }, sub) : null,
1493
+ links || null,
835
1494
  open ? h("div", { className: "dshm-detail" }, detail) : null,
836
1495
  open && actions && actions.length ? h("div", { className: "dshm-actions" }, ...actions) : null
837
1496
  )
@@ -844,10 +1503,14 @@ var TABS = [
844
1503
  ];
845
1504
  function MarketPanel({ onClose }) {
846
1505
  const [tab, setTab] = useState("market");
847
- const market = useAsync((force) => api("market", force ? { force: true } : {}), []);
1506
+ const market = useMarketData();
848
1507
  const installed = useAsync(() => api("installed"), []);
1508
+ const onRegistryChanged = useCallback(async () => {
1509
+ await market.reload(false);
1510
+ await installed.reload().catch(() => void 0);
1511
+ }, [market, installed]);
849
1512
  const counts = {
850
- market: market.data ? market.data.items.length : null,
1513
+ market: market.data ? market.data.total : null,
851
1514
  installed: installed.data ? installed.data.items.length : null
852
1515
  };
853
1516
  const [banner, setBanner] = useState(null);
@@ -864,9 +1527,9 @@ function MarketPanel({ onClose }) {
864
1527
  const t = setTimeout(() => setToast(null), 6e3);
865
1528
  return () => clearTimeout(t);
866
1529
  }, [toast]);
867
- const notify = useCallback(({ kind, text }) => {
1530
+ const notify = useCallback(({ kind, text, needsRestart }) => {
868
1531
  setToast({ kind, text });
869
- if (kind === "ok") setBanner({ text: lookup("banner.done") });
1532
+ if (kind === "ok" && needsRestart) setBanner({ text: lookup("banner.done") });
870
1533
  }, []);
871
1534
  return h(
872
1535
  "div",
@@ -905,7 +1568,7 @@ function MarketPanel({ onClose }) {
905
1568
  { className: "dshm-body" },
906
1569
  tab === "market" ? h(MarketTab, { notify, market }) : null,
907
1570
  tab === "installed" ? h(InstalledTab, { notify, installed }) : null,
908
- tab === "settings" ? h(SettingsTab, { notify }) : null
1571
+ tab === "settings" ? h(SettingsTab, { notify, onRegistryChanged }) : null
909
1572
  ),
910
1573
  toast ? h(
911
1574
  "div",
@@ -1045,6 +1708,7 @@ function ToolCardRow({ it, onInstalled }) {
1045
1708
  ),
1046
1709
  h("div", { className: "dshm-desc" }, it.description),
1047
1710
  h("div", { className: "dshm-sub" }, `${it.id} \xB7 ${(it.tags || []).join("\u3001") || it.category}`),
1711
+ h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
1048
1712
  h(
1049
1713
  "div",
1050
1714
  { className: "dshm-actions" },
@@ -1070,10 +1734,10 @@ function SearchToolView(props) {
1070
1734
  let live = true;
1071
1735
  api("search", { query, category: args.category, limit: args.limit }).then((d) => {
1072
1736
  if (live) setItems(d.items || []);
1073
- }).catch((e) => {
1737
+ }).catch(() => {
1074
1738
  if (live) {
1075
1739
  setItems([]);
1076
- setErr(e && e.message || String(e));
1740
+ setErr(lookup("notice.toolview.err"));
1077
1741
  }
1078
1742
  });
1079
1743
  return () => {