dsh-m 0.1.0 → 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,46 +2,501 @@ 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";
13
- var CATEGORIES = [
14
- ["market", "\u5E02\u573A"],
15
- ["tools", "\u5DE5\u5177"],
16
- ["ui", "\u754C\u9762"],
17
- ["search", "\u641C\u7D22"],
18
- ["media", "\u591A\u5A92\u4F53"],
19
- ["other", "\u5176\u4ED6"]
20
- ];
122
+ var { MARKET_PAGE_SIZE: MARKET_PAGE_SIZE2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2 } = (init_market_state(), __toCommonJS(market_state_exports));
123
+ var ZH = {
124
+ "market.title": "\u63D2\u4EF6\u5E02\u573A",
125
+ "tab.market": "\u5E02\u573A",
126
+ "tab.installed": "\u5DF2\u88C5",
127
+ "tab.settings": "\u8BBE\u7F6E",
128
+ "cat.all": "\u5168\u90E8",
129
+ "cat.market": "\u5E02\u573A",
130
+ "cat.tools": "\u5DE5\u5177",
131
+ "cat.ui": "\u754C\u9762",
132
+ "cat.search": "\u641C\u7D22",
133
+ "cat.media": "\u591A\u5A92\u4F53",
134
+ "cat.other": "\u5176\u4ED6",
135
+ "search.ph": "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\u2026",
136
+ "common.refresh": "\u5237\u65B0",
137
+ "common.close": "\u5173\u95ED",
138
+ "common.later": "\u7A0D\u540E",
139
+ "common.ok": "\u77E5\u9053\u4E86",
140
+ "common.none": "\u2014",
141
+ "market.loading": "\u52A0\u8F7D\u6536\u5F55\u6E05\u5355\u4E2D\u2026 ",
142
+ "market.empty": "\u6CA1\u6709\u5339\u914D\u7684\u6536\u5F55\u6761\u76EE",
143
+ "installed.loading": "\u8BFB\u53D6 web profile \u4E2D\u2026 ",
144
+ "installed.empty": "web profile \u5C1A\u672A\u5B89\u88C5\u4EFB\u4F55 dsh \u63D2\u4EF6",
145
+ "installed.none": "\u672A\u5B89\u88C5",
146
+ "installed.others": "\u53E6\u6709 {n} \u4E2A\u975E dsh \u4F9D\u8D56\uFF08\u672A\u8BC6\u522B\u4E3A\u63D2\u4EF6\uFF09\uFF0C\u5DF2\u9ED8\u8BA4\u6298\u53E0\u3002",
147
+ "badge.installed": "\u5DF2\u5B89\u88C5",
148
+ "badge.update": "\u53EF\u5347\u7EA7",
149
+ "badge.market": "\u5E02\u573A\u5B89\u88C5",
150
+ "badge.nonmarket": "\u975E\u5E02\u573A\u5B89\u88C5",
151
+ "badge.custom": "\u81EA\u5B9A\u4E49",
152
+ "action.install": "\u5B89\u88C5",
153
+ "action.upgrade": "\u5347\u7EA7",
154
+ "action.uninstall": "\u5378\u8F7D",
155
+ "confirm.uninstall": "\u786E\u8BA4\u5378\u8F7D\uFF1F",
156
+ "confirm.unlink": "\u786E\u8BA4\u79FB\u9664\u672C\u5730\u5F15\u7528\uFF1F",
157
+ "confirm.core": "\u26A0\uFE0F \u786E\u8BA4\u5378\u8F7D\u6838\u5FC3\u5305\uFF1F",
158
+ "detail.id": "\u6536\u5F55 id",
159
+ "detail.source": "\u6765\u6E90",
160
+ "detail.latest": "\u6700\u65B0",
161
+ "detail.installed": "\u5DF2\u88C5",
162
+ "detail.tags": "\u6807\u7B7E",
163
+ "detail.pkg": "\u5305\u540D",
164
+ "detail.spec": "\u5B89\u88C5 spec",
165
+ "detail.listed": "\u6536\u5F55",
166
+ "detail.listed.no": "\u4E0D\u5728\u6536\u5F55\u6E05\u5355\u4E2D",
167
+ "detail.path": "\u8DEF\u5F84",
168
+ "detail.note": "\u6CE8\u610F",
169
+ "detail.links": "\u8BE6\u60C5",
170
+ "link.home": "\u5B98\u7F51",
171
+ "manage.hint": "\u5DF2\u5B89\u88C5\uFF0C\u53EF\u5728\u300C\u5DF2\u88C5\u300D\u9875\u7BA1\u7406",
172
+ "version.failed": "\u7248\u672C\u67E5\u8BE2\u5931\u8D25",
173
+ "src.npm": "npm",
174
+ "src.github": "github",
175
+ "src.link": "\u672C\u5730 link",
176
+ "src.file": "\u672C\u5730 file",
177
+ "src.unknown": "\u672A\u77E5",
178
+ "sub.latest": "\u6700\u65B0 v{v}",
179
+ "sub.head": "HEAD {sha}",
180
+ "sub.installed": "\u5DF2\u88C5 v{v}",
181
+ "settings.registry": "\u6536\u5F55\u6E05\u5355\uFF08registry\uFF09",
182
+ "settings.source": "\u5F53\u524D\u6765\u6E90",
183
+ "settings.updated": "\u66F4\u65B0\u65F6\u95F4",
184
+ "settings.count": "\u6761\u76EE\u6570",
185
+ "settings.count.v": "{n} \u6761",
186
+ "settings.policy": "\u7F13\u5B58\u7B56\u7565",
187
+ "settings.policy.v": "TTL 60 \u5206\u949F\uFF1B\u8BBE\u7F6E registryUrl \u53EF\u8986\u76D6\u6E90",
188
+ "settings.remotehint": "\u8FDC\u7AEF\u63D0\u793A",
189
+ "settings.force": "\u5F3A\u5236\u5237\u65B0",
190
+ "settings.self": "dsh-m \u81EA\u8EAB",
191
+ "settings.current": "\u5F53\u524D\u7248\u672C",
192
+ "settings.npmlatest": "npm \u6700\u65B0",
193
+ "settings.lookupfailed": "\u67E5\u8BE2\u5931\u8D25\uFF1A{err}",
194
+ "settings.upgradeself": "\u5347\u7EA7 dsh-m",
195
+ "settings.upgradehint": "\u5347\u7EA7\u540E\u540C\u6837\u9700\u8981\u91CD\u542F\u751F\u6548",
196
+ "settings.about": "\u5173\u4E8E",
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",
198
+ "src.override": "\u81EA\u5B9A\u4E49\u6E90",
199
+ "src.jsdelivr": "jsDelivr\uFF08@main\uFF09",
200
+ "src.raw": "raw.githubusercontent\uFF08@main\uFF09",
201
+ "src.cache": "\u672C\u5730\u7F13\u5B58",
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",
250
+ "self.upgraded": "dsh-m \u5DF2\u66F4\u65B0\u5230 v{v}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
251
+ "self.failed": "\u81EA\u66F4\u65B0\u5931\u8D25\uFF1A{err}",
252
+ "registry.refreshed": "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0",
253
+ "notify.installed": "\u5DF2\u5B89\u88C5 {pkg}{version}",
254
+ "notify.allowbuilds": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF0C\u5DF2\u6309\u7B56\u7565\u653E\u884C\uFF09",
255
+ "notify.uninstalled": "\u5DF2\u5378\u8F7D {pkg}",
256
+ "notify.livedisabled": "\uFF08\u5DF2\u5148\u4E0B\u7EBF\u8FD0\u884C\u4E2D\u7684\u754C\u9762\uFF09",
257
+ "notify.leftovers": "\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A{paths}",
258
+ "notify.upgraded": "\u5DF2\u5347\u7EA7 {pkg}\uFF08{from} \u2192 {to}\uFF09",
259
+ "notify.upgradehint": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF09",
260
+ "failed.install": "\u5B89\u88C5\u5931\u8D25\uFF1A{err}",
261
+ "failed.uninstall": "\u5378\u8F7D\u5931\u8D25\uFF1A{err}",
262
+ "failed.upgrade": "\u5347\u7EA7\u5931\u8D25\uFF1A{err}",
263
+ "failed.selfupdate": "\u81EA\u66F4\u65B0\u5931\u8D25\uFF1A{err}",
264
+ "failed.load": "\u52A0\u8F7D\u5931\u8D25\uFF1A{err}",
265
+ "failed.read": "\u8BFB\u53D6\u5931\u8D25\uFF1A{err}",
266
+ "failed.open": "\u6253\u5F00\u5E02\u573A\u9762\u677F\u5931\u8D25:",
267
+ "banner.done": "\u53D8\u66F4\u5B8C\u6210\uFF0C\u9700\u8981\u91CD\u542F DSH Web \u540E\u751F\u6548\u3002",
268
+ "restart.doing": "\u6B63\u5728\u8BF7\u6C42\u91CD\u542F\u2026",
269
+ "restart.waiting": "\u5DF2\u8BF7\u6C42\u91CD\u542F\uFF0C\u7B49\u5F85 DSH Web \u6062\u590D\u2026",
270
+ "restart.now": "\u26A1 \u4E00\u952E\u91CD\u542F",
271
+ "restart.failed": "\u91CD\u542F\u5931\u8D25\uFF1A{err}",
272
+ "restart.timeout": "\u91CD\u542F\u8D85\u65F6\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5 dsh web \u670D\u52A1\u72B6\u6001",
273
+ "restart.hint.done": "\u5DF2\u8BF7\u6C42\u91CD\u542F DSH web\uFF08via {via}\uFF09\u3002\u670D\u52A1\u51E0\u79D2\u5185\u6062\u590D\uFF0C\u4E4B\u540E\u8BA9\u7528\u6237\u5237\u65B0\u9875\u9762\u5373\u53EF\u3002",
274
+ "phase.resolving": "\u89E3\u6790\u4F9D\u8D56",
275
+ "phase.downloading": "\u4E0B\u8F7D",
276
+ "phase.linking": "\u94FE\u63A5\u5B89\u88C5",
277
+ "phase.building": "\u6784\u5EFA\u811A\u672C",
278
+ "phase.ready": "\u51C6\u5907\u4E2D",
279
+ "readme.show": "\u{1F4D6} README",
280
+ "readme.hide": "\u6536\u8D77 README",
281
+ "readme.loading": "\u52A0\u8F7D README\u2026 ",
282
+ "readme.none": "\uFF08\u8BE5\u63D2\u4EF6\u6CA1\u6709 README\uFF09",
283
+ "readme.truncated": "\u2026\uFF08\u8D85\u8FC7 64KB \u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1\u63D2\u4EF6\u76EE\u5F55\uFF09",
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",
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",
286
+ "profile.hint": "web profile\uFF1A{path}",
287
+ "title.panel": "\u63D2\u4EF6\u5E02\u573A",
288
+ "title.full": "DeepSeek Harness \u63D2\u4EF6\u5E02\u573A"
289
+ };
290
+ var EN = {
291
+ "market.title": "Plugin Marketplace",
292
+ "title.panel": "Plugin Marketplace",
293
+ "title.full": "DeepSeek Harness Plugin Marketplace",
294
+ "tab.market": "Market",
295
+ "tab.installed": "Installed",
296
+ "tab.settings": "Settings",
297
+ "cat.all": "All",
298
+ "cat.market": "Market",
299
+ "cat.tools": "Tools",
300
+ "cat.ui": "UI",
301
+ "cat.search": "Search",
302
+ "cat.media": "Media",
303
+ "cat.other": "Other",
304
+ "search.ph": "Search name, description, tags\u2026",
305
+ "common.refresh": "Refresh",
306
+ "common.close": "Close",
307
+ "common.later": "Later",
308
+ "common.ok": "OK",
309
+ "common.none": "\u2014",
310
+ "market.loading": "Loading listings\u2026 ",
311
+ "market.empty": "No matching listings",
312
+ "installed.loading": "Reading web profile\u2026 ",
313
+ "installed.empty": "No DSH plugins installed in this web profile",
314
+ "installed.none": "Not installed",
315
+ "installed.others": "{n} non-DSH dependencies (not recognized as plugins) are collapsed.",
316
+ "badge.installed": "Installed",
317
+ "badge.update": "Update",
318
+ "badge.market": "Via market",
319
+ "badge.nonmarket": "Non-market",
320
+ "badge.custom": "Custom",
321
+ "action.install": "Install",
322
+ "action.upgrade": "Upgrade",
323
+ "action.uninstall": "Uninstall",
324
+ "confirm.uninstall": "Confirm uninstall?",
325
+ "confirm.unlink": "Confirm remove link?",
326
+ "confirm.core": "\u26A0\uFE0F Remove core package?",
327
+ "detail.id": "Listing id",
328
+ "detail.source": "Source",
329
+ "detail.latest": "Latest",
330
+ "detail.installed": "Installed",
331
+ "detail.tags": "Tags",
332
+ "detail.pkg": "Package",
333
+ "detail.spec": "Spec",
334
+ "detail.listed": "Listed",
335
+ "detail.listed.no": "Not in the registry",
336
+ "detail.path": "Path",
337
+ "detail.note": "Note",
338
+ "detail.links": "Details",
339
+ "link.home": "Homepage",
340
+ "manage.hint": "Installed \u2014 manage it on the Installed tab",
341
+ "version.failed": "version lookup failed",
342
+ "src.npm": "npm",
343
+ "src.github": "github",
344
+ "src.link": "local link",
345
+ "src.file": "local file",
346
+ "src.unknown": "unknown",
347
+ "sub.latest": "Latest v{v}",
348
+ "sub.head": "HEAD {sha}",
349
+ "sub.installed": "Installed v{v}",
350
+ "settings.registry": "Registry",
351
+ "settings.source": "Source",
352
+ "settings.updated": "Updated",
353
+ "settings.count": "Listings",
354
+ "settings.count.v": "{n} listings",
355
+ "settings.policy": "Caching",
356
+ "settings.policy.v": "60 min TTL; override via registryUrl",
357
+ "settings.remotehint": "Remote notice",
358
+ "settings.force": "Force refresh",
359
+ "settings.self": "dsh-m itself",
360
+ "settings.current": "Current version",
361
+ "settings.npmlatest": "npm latest",
362
+ "settings.lookupfailed": "lookup failed: {err}",
363
+ "settings.upgradeself": "Upgrade dsh-m",
364
+ "settings.upgradehint": "A restart is required after upgrading",
365
+ "settings.about": "About",
366
+ "settings.about.text": "A personal DSH plugin marketplace \u2014 browse, install, uninstall and upgrade, all local; registry overrides apply live.",
367
+ "src.override": "Custom source",
368
+ "src.jsdelivr": "jsDelivr (@main)",
369
+ "src.raw": "raw.githubusercontent (@main)",
370
+ "src.cache": "Local cache",
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",
419
+ "self.upgraded": "dsh-m updated to v{v} \u2014 restart to take effect",
420
+ "self.failed": "Self-update failed: {err}",
421
+ "registry.refreshed": "Registry force-refreshed",
422
+ "notify.installed": "Installed {pkg}{version}",
423
+ "notify.allowbuilds": " (note: this plugin ran build scripts, allowed by policy)",
424
+ "notify.uninstalled": "Uninstalled {pkg}",
425
+ "notify.livedisabled": " (live UI disabled first)",
426
+ "notify.leftovers": "; possible leftover data: {paths}",
427
+ "notify.upgraded": "Upgraded {pkg} ({from} \u2192 {to})",
428
+ "notify.upgradehint": " (note: this plugin ran build scripts)",
429
+ "failed.install": "Install failed: {err}",
430
+ "failed.uninstall": "Uninstall failed: {err}",
431
+ "failed.upgrade": "Upgrade failed: {err}",
432
+ "failed.selfupdate": "Self-update failed: {err}",
433
+ "failed.load": "Load failed: {err}",
434
+ "failed.read": "Read failed: {err}",
435
+ "failed.open": "Failed to open the marketplace panel:",
436
+ "banner.done": "Changes applied. Restart DSH Web to take effect.",
437
+ "restart.doing": "Requesting restart\u2026",
438
+ "restart.waiting": "Restart requested, waiting for DSH Web\u2026",
439
+ "restart.now": "\u26A1 Restart",
440
+ "restart.failed": "Restart failed: {err}",
441
+ "restart.timeout": "Restart timed out \u2014 check the dsh web service manually",
442
+ "restart.hint.done": "Restart requested (via {via}). The service will be back in seconds; ask the user to refresh afterwards.",
443
+ "phase.resolving": "Resolving",
444
+ "phase.downloading": "Downloading",
445
+ "phase.linking": "Linking",
446
+ "phase.building": "Building",
447
+ "phase.ready": "Preparing",
448
+ "readme.show": "\u{1F4D6} README",
449
+ "readme.hide": "Hide README",
450
+ "readme.loading": "Loading README\u2026 ",
451
+ "readme.none": "(No README)",
452
+ "readme.truncated": "\u2026(truncated at 64KB \u2014 see the plugin directory for full content)",
453
+ "warn.unlink": "Uninstalling only removes the profile's reference to the local directory ({path}); the directory itself is kept.",
454
+ "warn.core": "This is a core/archive package installed via file:. Uninstalling may affect DSH features and requires manual restore.",
455
+ "profile.hint": "web profile: {path}",
456
+ "title.panel": "Plugin Marketplace",
457
+ "title.full": "DeepSeek Harness Plugin Marketplace"
458
+ };
459
+ function browserLang() {
460
+ const lang = typeof document !== "undefined" && document.documentElement.lang || typeof navigator !== "undefined" && navigator.language || "zh";
461
+ return /^en\b/i.test(String(lang)) ? "en" : "zh";
462
+ }
463
+ function interpolate(tpl, params) {
464
+ if (!params) return String(tpl);
465
+ return String(tpl).replace(/\{(\w+)\}/g, (_, k) => params[k] != null ? String(params[k]) : `{${k}}`);
466
+ }
467
+ function lookup(key, params) {
468
+ const dict = browserLang() === "en" ? EN : ZH;
469
+ return interpolate(dict[key] ?? ZH[key] ?? key, params);
470
+ }
471
+ var CATEGORIES2 = ["market", "tools", "ui", "search", "media", "other"];
21
472
  var CSS = `
22
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}
23
- .dshm-panel{width:min(920px,100%);height:min(680px,86vh);display:flex;flex-direction:column;background: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)}
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)}
24
475
  .dshm-head{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb)}
25
476
  .dshm-title{font-weight:700;font-size:15px;margin-right:6px}
26
- .dshm-tab{border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);border-radius:8px;padding:5px 12px;font:inherit;font-size:13px;cursor:pointer}
27
- .dshm-tab:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
28
- .dshm-tab.on{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l2,#e5e7eb);font-weight:600}
477
+ .dshm-seg{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--dsw-alias-border-l2,#e2e4e8);border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f5f6f8)}
478
+ .dshm-seg button{appearance:none;border:0;background:transparent;height:28px;padding:0 14px;border-radius:7px;font:inherit;font-size:12px;color:var(--dsw-alias-label-tertiary,#7b8088);cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background .15s,color .15s,box-shadow .15s}
479
+ .dshm-seg button:hover{color:var(--dsw-alias-label-secondary,#4b5058)}
480
+ .dshm-seg button.on{background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,#17191c);font-weight:600;box-shadow:var(--dsw-shadow-lv1,0 2px 8px rgb(20 24 32 / 8%))}
481
+ .dshm-seg .dshm-count{font-size:11px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption,#9ca3af);margin:0}
482
+ .dshm-seg button.on .dshm-count{color:var(--dsw-alias-state-business-primary,#4d6bfe)}
29
483
  .dshm-spacer{flex:1}
30
484
  .dshm-body{flex:1;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:12px}
31
485
  .dshm-hint{color:var(--dsw-alias-label-caption,#6b7280);font-size:12px;line-height:18px;margin:0}
32
- .dshm-err{color:var(--dsw-alias-state-danger-primary,#dc2626);font-size:12px;line-height:18px}
486
+ .dshm-err{color:var(--dsw-alias-state-error-primary,#b91c1c);font-size:12px;line-height:18px}
33
487
  .dshm-btn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,inherit);border-radius:8px;padding:5px 12px;font:inherit;font-size:12px;cursor:pointer;white-space:nowrap}
34
488
  .dshm-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
35
489
  .dshm-btn:disabled{opacity:.5;cursor:default}
36
490
  .dshm-btn.primary{background:var(--dsw-alias-interactive-bg-selected,#4f46e5);border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5);color:#fff}
37
491
  .dshm-btn.primary:hover{filter:brightness(1.08)}
38
- .dshm-btn.danger{color:var(--dsw-alias-state-danger-primary,#dc2626);border-color:var(--dsw-alias-state-danger-primary,#dc2626)}
492
+ .dshm-btn.danger{color:var(--dsw-alias-state-error-primary,#b91c1c);border-color:var(--dsw-alias-state-error-primary,#b91c1c)}
39
493
  .dshm-btn.sm{padding:3px 9px;font-size:11px}
40
- .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}
41
495
  .dshm-input:focus{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
42
496
  .dshm-chips{display:flex;flex-wrap:wrap;gap:6px}
43
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}
44
- .dshm-chip.on{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
498
+ .dshm-chip:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
499
+ .dshm-chip.on{background:var(--dsw-specific-sidebar-nav-item-active,rgba(38,49,72,.08));border-color:transparent;color:var(--dsw-alias-label-primary,inherit);font-weight:500}
45
500
  .dshm-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
46
501
  @media (max-width:680px){.dshm-cards{grid-template-columns:1fr}}
47
502
  .dshm-card{display:flex;gap:12px;align-items:flex-start;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;cursor:pointer;text-align:left;width:100%;box-sizing:border-box;min-width:0;font:inherit;color:var(--dsw-alias-label-primary,inherit);transition:border-color .16s,background .16s}
@@ -51,20 +506,62 @@ var CSS = `
51
506
  .dshm-top{display:flex;align-items:center;gap:8px;min-width:0}
52
507
  .dshm-name{flex:1;min-width:0;font-weight:600;font-size:14px;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
53
508
  .dshm-badge{flex:none;font-size:11px;line-height:16px;padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-success-tertiary,#ecfdf5);color:var(--dsw-alias-state-success-primary,#047857)}
54
- .dshm-badge.warn{background:var(--dsw-alias-state-warning-tertiary,#fffbeb);color:var(--dsw-alias-state-warning-primary,#b45309)}
55
- .dshm-badge.info{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-secondary,#4b5563)}
56
- .dshm-badge.err{background:var(--dsw-alias-state-danger-tertiary,#fef2f2);color:var(--dsw-alias-state-danger-primary,#dc2626)}
509
+ .dshm-badge.warn{background:var(--dsw-alias-state-warn-tertiary,#fffbeb);color:var(--dsw-alias-state-warn-primary,#b45309)}
510
+ .dshm-badge.info{background:var(--dsw-alias-state-business-tertiary,#eef2ff);color:var(--dsw-alias-state-business-primary,#4338ca)}
511
+ .dshm-badge.err{background:var(--dsw-alias-state-error-secondary,#fee2e2);color:var(--dsw-alias-state-error-primary,#b91c1c)}
57
512
  .dshm-desc{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;line-height:18px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
58
513
  .dshm-sub{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-caption,#6b7280)}
59
514
  .dshm-detail{margin-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#e5e7eb);padding-top:8px;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
60
515
  .dshm-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
61
- .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-warning-tertiary,#fffbeb);color:var(--dsw-alias-state-warning-primary,#b45309);font-size:12px}
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}
62
517
  .dshm-banner .dshm-banner-text{flex:1}
63
518
  .dshm-row{display:flex;align-items:center;gap:8px}
64
- .dshm-kv{display:grid;grid-template-columns:110px 1fr;gap:4px 10px;font-size:12px}
65
- .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}
66
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}
67
530
  @keyframes dshm-rot{to{transform:rotate(360deg)}}
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}
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}
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)}
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}
558
+ .dshm-prog .bar i{display:block;height:100%;background:var(--dsw-alias-interactive-bg-selected,#4f46e5);transition:width .3s}
559
+ .dshm-entry{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 4px);height:42px;margin:4px -2px;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:14px;line-height:22px;cursor:pointer;overflow:hidden}
560
+ .dshm-entry:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
561
+ .dshm-entry svg{flex:none;width:16px;height:16px}
562
+ .dshm-entry span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
563
+ [data-slot="sidebar.footer.action"]{display:flex!important;flex-direction:column;width:100%;min-width:0}
564
+ [data-slot="sidebar.footer.action"]>*{flex:none;min-width:0}
68
565
  .dshm-empty{text-align:center;color:var(--dsw-alias-label-caption,#6b7280);font-size:13px;padding:32px 0}
69
566
  `;
70
567
  function ensureCss() {
@@ -74,11 +571,12 @@ function ensureCss() {
74
571
  el.textContent = CSS;
75
572
  document.head.appendChild(el);
76
573
  }
77
- async function api(method, params) {
574
+ async function api(method, params, signal) {
78
575
  const res = await fetch(API, {
79
576
  method: "POST",
80
577
  headers: { "content-type": "application/json" },
81
- body: JSON.stringify({ method, ...params || {} })
578
+ body: JSON.stringify({ method, ...params || {} }),
579
+ ...signal ? { signal } : {}
82
580
  });
83
581
  const data = await res.json().catch(() => ({}));
84
582
  if (!res.ok || data.ok === false) throw new Error(data.error || `API ${res.status}`);
@@ -100,6 +598,51 @@ function useAsync(fn, deps) {
100
598
  }, [run]);
101
599
  return { ...state, reload: run };
102
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
+ }
103
646
  function Icon({ entry }) {
104
647
  const [broken, setBroken] = useState(false);
105
648
  const letter = String(entry.name || entry.id || "?").charAt(0).toUpperCase();
@@ -118,6 +661,181 @@ function Icon({ entry }) {
118
661
  function Spin() {
119
662
  return h("span", { className: "dshm-spin" });
120
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
+ }
121
839
  function TwoStepButton({ label, confirmLabel, className, onConfirm, disabled }) {
122
840
  const [arm, setArm] = useState(false);
123
841
  useEffect(() => {
@@ -156,7 +874,7 @@ function RestartBanner({ note, onDone }) {
156
874
  const deadline = Date.now() + 9e4;
157
875
  for (; ; ) {
158
876
  await new Promise((r) => setTimeout(r, 2e3));
159
- if (Date.now() > deadline) throw new Error("\u91CD\u542F\u8D85\u65F6\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5 dsh web \u670D\u52A1\u72B6\u6001");
877
+ if (Date.now() > deadline) throw new Error(lookup("restart.timeout"));
160
878
  try {
161
879
  const ping = await api("ping");
162
880
  if (ping.boot !== ping0.boot) break;
@@ -176,41 +894,45 @@ function RestartBanner({ note, onDone }) {
176
894
  h(
177
895
  "span",
178
896
  { className: "dshm-banner-text" },
179
- phase === "restarting" ? "\u6B63\u5728\u8BF7\u6C42\u91CD\u542F\u2026" : phase === "waiting" ? "\u5DF2\u8BF7\u6C42\u91CD\u542F\uFF0C\u7B49\u5F85 DSH Web \u6062\u590D\u2026" : err ? `\u91CD\u542F\u5931\u8D25\uFF1A${err}` : note || "\u672C\u6B21\u53D8\u66F4\u9700\u8981\u91CD\u542F DSH Web \u540E\u751F\u6548\u3002"
897
+ phase === "restarting" ? lookup("restart.doing") : phase === "waiting" ? lookup("restart.waiting") : err ? lookup("restart.failed", { err }) : note || lookup("banner.done")
180
898
  ),
181
- phase === "idle" && !err ? h("button", { className: "dshm-btn primary sm", onClick: restart }, "\u26A1 \u4E00\u952E\u91CD\u542F") : null,
899
+ phase === "idle" && !err ? h("button", { className: "dshm-btn primary sm", onClick: restart }, lookup("restart.now")) : null,
182
900
  phase === "restarting" || phase === "waiting" ? Spin() : null,
183
- phase === "idle" && err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, "\u77E5\u9053\u4E86") : null,
184
- phase === "idle" && !err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, "\u7A0D\u540E") : null
901
+ phase === "idle" && err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, lookup("common.ok")) : null,
902
+ phase === "idle" && !err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, lookup("common.later")) : null
185
903
  );
186
904
  }
187
- function MarketTab({ notify }) {
188
- const { loading, data, error, reload } = useAsync((force) => api("market", force ? { force: true } : {}), []);
189
- const [q, setQ] = useState("");
190
- const [cat, setCat] = useState(null);
905
+ function MarketTab({ notify, market }) {
906
+ const { data, loading, error, reload, query, updateQuery } = market;
191
907
  const [openId, setOpenId] = useState(null);
192
908
  const [busyId, setBusyId] = useState(null);
193
- const items = useMemo(() => {
194
- const list = data && data.items || [];
195
- const kw = q.trim().toLowerCase();
196
- return list.filter((it) => {
197
- if (cat && it.category !== cat) return false;
198
- if (!kw) return true;
199
- const hay = `${it.id} ${it.name} ${it.description} ${(it.tags || []).join(" ")}`.toLowerCase();
200
- return hay.includes(kw);
201
- });
202
- }, [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
+ };
203
924
  const doInstall = async (it, version) => {
204
925
  setBusyId(it.id);
205
926
  try {
206
927
  const res = await api("install", { id: it.id, ...version ? { version } : {} });
207
928
  notify({
208
929
  kind: "ok",
209
- text: `\u5DF2\u5B89\u88C5 ${res.pkg}${res.version ? ` v${res.version}` : ""}` + (res.usedAllowAllBuilds ? "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF0C\u5DF2\u6309\u7B56\u7565\u653E\u884C\uFF09" : "")
930
+ needsRestart: true,
931
+ text: lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + (res.usedAllowAllBuilds ? lookup("notify.allowbuilds") : "")
210
932
  });
211
933
  await reload(false);
212
934
  } catch (e) {
213
- notify({ kind: "err", text: `\u5B89\u88C5\u5931\u8D25\uFF1A${e && e.message || e}` });
935
+ notify({ kind: "err", text: lookup("failed.install", { err: e && e.message || e }) });
214
936
  } finally {
215
937
  setBusyId(null);
216
938
  }
@@ -218,80 +940,170 @@ function MarketTab({ notify }) {
218
940
  return h(
219
941
  React.Fragment,
220
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,
221
949
  h(
222
950
  "div",
223
951
  { className: "dshm-row" },
224
952
  h("input", {
225
953
  className: "dshm-input",
226
- placeholder: "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\u2026",
227
- value: q,
228
- onChange: (e) => setQ(e.target.value)
954
+ placeholder: lookup("search.ph"),
955
+ value: qInput,
956
+ onChange: (e) => onSearchInput(e.target.value)
229
957
  }),
230
- h("button", { className: "dshm-btn", onClick: () => reload(true), title: "\u5F3A\u5236\u5237\u65B0\uFF08\u8DF3\u8FC7\u7F13\u5B58\uFF09" }, loading ? Spin() : "\u21BB \u5237\u65B0")
958
+ h("button", { className: "dshm-btn", onClick: () => reload(true), title: lookup("settings.policy.v") }, loading ? Spin() : `\u21BB ${lookup("common.refresh")}`)
231
959
  ),
232
960
  h(
233
961
  "div",
234
962
  { className: "dshm-chips" },
235
- h("button", { className: `dshm-chip${cat === null ? " on" : ""}`, onClick: () => setCat(null) }, "\u5168\u90E8"),
236
- CATEGORIES.map(([key, label]) => {
237
- 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;
238
966
  return h(
239
967
  "button",
240
- { key, className: `dshm-chip${cat === key ? " on" : ""}`, onClick: () => setCat(cat === key ? null : key) },
241
- `${label}${n ? ` ${n}` : ""}`
968
+ { key, className: `dshm-chip${query.category === key ? " on" : ""}`, onClick: () => updateQuery({ category: query.category === key ? null : key, offset: 0 }) },
969
+ `${lookup("cat." + key)}${n ? ` ${n}` : ""}`
242
970
  );
243
971
  })
244
972
  ),
245
- loading && !data ? h("div", { className: "dshm-empty" }, "\u52A0\u8F7D\u6536\u5F55\u6E05\u5355\u4E2D\u2026 ", Spin()) : error ? h("div", { className: "dshm-err" }, `\u52A0\u8F7D\u5931\u8D25\uFF1A${error}`) : items.length === 0 ? h("div", { className: "dshm-empty" }, "\u6CA1\u6709\u5339\u914D\u7684\u6536\u5F55\u6761\u76EE") : h(
246
- "div",
247
- { className: "dshm-cards" },
248
- items.map((it) => Card({
249
- key: it.id,
250
- icon: h(Icon, { entry: it }),
251
- name: it.name,
252
- badges: [
253
- it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, "\u53EF\u5347\u7EA7") : null,
254
- it.installed ? h("span", { className: "dshm-badge", key: "i" }, "\u5DF2\u5B89\u88C5") : null,
255
- h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
256
- ],
257
- desc: it.description,
258
- sub: [
259
- it.latestVersion ? `\u6700\u65B0 v${it.latestVersion}` : it.latestSha ? `HEAD ${it.latestSha.slice(0, 7)}` : null,
260
- it.installedVersion ? `\u5DF2\u88C5 v${it.installedVersion}` : null,
261
- it.latestError ? `\u7248\u672C\u67E5\u8BE2\u5931\u8D25` : null
262
- ].filter(Boolean).join(" \xB7 "),
263
- open: openId === it.id,
264
- onToggle: () => setOpenId(openId === it.id ? null : it.id),
265
- detail: DetailRows([
266
- ["\u6536\u5F55 id", it.id],
267
- ["\u6765\u6E90", it.source === "npm" ? `npm \xB7 ${it.npm}` : `GitHub \xB7 ${it.github}`],
268
- ["\u6700\u65B0", it.latestVersion ? `v${it.latestVersion}` : it.latestSha ? it.latestSha : it.latestError || "\u2014"],
269
- ["\u5DF2\u88C5", it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : "\u672A\u5B89\u88C5"],
270
- ["\u6807\u7B7E", (it.tags || []).join("\u3001") || "\u2014"],
271
- it.latestError ? ["\u63D0\u793A", `\u7248\u672C\u67E5\u8BE2\u5931\u8D25\uFF1A${it.latestError}`] : null
272
- ]),
273
- actions: [
274
- it.installed ? h("span", { className: "dshm-hint", key: "hint" }, "\u5DF2\u5B89\u88C5\uFF0C\u53EF\u5728\u300C\u5DF2\u88C5\u300D\u9875\u7BA1\u7406") : h(
275
- "button",
276
- {
277
- key: "install",
278
- className: "dshm-btn primary sm",
279
- disabled: busyId === it.id,
280
- onClick: (e) => {
281
- e.stopPropagation();
282
- doInstall(it);
283
- }
284
- },
285
- busyId === it.id ? h(Spin) : "\u5B89\u88C5"
286
- )
287
- ]
288
- }))
973
+ busyId ? h(ProgressLine, { key: "prog" }) : null,
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(
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
289
1038
  )
290
1039
  );
291
1040
  }
292
- function InstalledTab({ notify }) {
293
- const { loading, data, error, reload } = useAsync(() => api("installed"), []);
1041
+ var PHASE_LABEL = { resolving: "phase.resolving", downloading: "phase.downloading", linking: "phase.linking", building: "phase.building" };
1042
+ function ProgressLine() {
1043
+ const [st, setSt] = useState(null);
1044
+ useEffect(() => {
1045
+ let live = true;
1046
+ const tick = async () => {
1047
+ try {
1048
+ const d = await api("status");
1049
+ if (live) setSt(d);
1050
+ } catch {
1051
+ }
1052
+ };
1053
+ tick();
1054
+ const iv = setInterval(tick, 1e3);
1055
+ return () => {
1056
+ live = false;
1057
+ clearInterval(iv);
1058
+ };
1059
+ }, []);
1060
+ if (!st) return null;
1061
+ const pct = st.total ? Math.min(100, Math.round(st.done / st.total * 100)) : null;
1062
+ const phaseLabel = lookup(PHASE_LABEL[st.phase] || "phase.ready");
1063
+ return h(
1064
+ "div",
1065
+ { className: "dshm-prog" },
1066
+ Spin(),
1067
+ h("span", null, `${st.target} \xB7 ${phaseLabel}${st.done ? ` ${st.done}${st.total ? "/" + st.total : ""}` : ""}`),
1068
+ pct !== null ? h("span", { className: "bar" }, h("i", { style: { width: pct + "%" } })) : null,
1069
+ st.currentPackage ? h("span", { className: "dshm-hint" }, String(st.currentPackage).slice(0, 44)) : null
1070
+ );
1071
+ }
1072
+ function ReadmeBlock({ pkg }) {
1073
+ const [state, setState] = useState({ loading: true, text: "", err: "", truncated: false });
1074
+ useEffect(() => {
1075
+ let live = true;
1076
+ api("readme", { pkg }).then((d) => live && setState({ loading: false, text: d.readme, err: "", truncated: d.truncated })).catch((e) => live && setState({ loading: false, text: "", err: String(e && e.message || e) }));
1077
+ return () => {
1078
+ live = false;
1079
+ };
1080
+ }, [pkg]);
1081
+ if (state.loading) return h("div", { className: "dshm-hint" }, lookup("readme.loading"), Spin());
1082
+ if (state.err) return h("div", { className: "dshm-err" }, state.err);
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
+ );
1090
+ }
1091
+ function uninstallGuard(it) {
1092
+ if (it.source === "link") {
1093
+ return {
1094
+ confirm: lookup("confirm.unlink"),
1095
+ warn: lookup("warn.unlink", { path: it.path })
1096
+ };
1097
+ }
1098
+ if (it.source === "file") {
1099
+ return { confirm: lookup("confirm.core"), warn: lookup("warn.core") };
1100
+ }
1101
+ return { confirm: lookup("confirm.uninstall"), warn: null };
1102
+ }
1103
+ function InstalledTab({ notify, installed }) {
1104
+ const { loading, data, error, reload } = installed;
294
1105
  const [openPkg, setOpenPkg] = useState(null);
1106
+ const [readmePkg, setReadmePkg] = useState(null);
295
1107
  const [busyPkg, setBusyPkg] = useState(null);
296
1108
  const doUninstall = async (it) => {
297
1109
  setBusyPkg(it.pkg);
@@ -299,11 +1111,12 @@ function InstalledTab({ notify }) {
299
1111
  const res = await api("uninstall", { pkg: it.pkg });
300
1112
  notify({
301
1113
  kind: "ok",
302
- text: `\u5DF2\u5378\u8F7D ${res.pkg}${res.liveDisabled ? "\uFF08\u5DF2\u5148\u4E0B\u7EBF\u8FD0\u884C\u4E2D\u7684\u754C\u9762\uFF09" : ""}` + (res.leftovers && res.leftovers.length ? `\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A${res.leftovers.join("\u3001")}` : "")
1114
+ needsRestart: true,
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(", ") }) : "")
303
1116
  });
304
1117
  await reload();
305
1118
  } catch (e) {
306
- notify({ kind: "err", text: `\u5378\u8F7D\u5931\u8D25\uFF1A${e && e.message || e}` });
1119
+ notify({ kind: "err", text: lookup("failed.uninstall", { err: e && e.message || e }) });
307
1120
  } finally {
308
1121
  setBusyPkg(null);
309
1122
  }
@@ -314,162 +1127,336 @@ function InstalledTab({ notify }) {
314
1127
  const res = await api("upgrade", { pkg: it.pkg });
315
1128
  notify({
316
1129
  kind: "ok",
317
- text: `\u5DF2\u5347\u7EA7 ${res.pkg}${res.fromVersion ? `\uFF08v${res.fromVersion} \u2192 ${res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "\u6700\u65B0"}\uFF09` : ""}` + (res.usedAllowAllBuilds ? "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF09" : "")
1130
+ needsRestart: true,
1131
+ text: lookup("notify.upgraded", {
1132
+ pkg: res.pkg,
1133
+ from: res.fromVersion ? `v${res.fromVersion}` : "\u2014",
1134
+ to: res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "latest"
1135
+ }) + (res.usedAllowAllBuilds ? lookup("notify.upgradehint") : "")
318
1136
  });
319
1137
  await reload();
320
1138
  } catch (e) {
321
- notify({ kind: "err", text: `\u5347\u7EA7\u5931\u8D25\uFF1A${e && e.message || e}` });
1139
+ notify({ kind: "err", text: lookup("failed.upgrade", { err: e && e.message || e }) });
322
1140
  } finally {
323
1141
  setBusyPkg(null);
324
1142
  }
325
1143
  };
326
- if (loading && !data) return h("div", { className: "dshm-empty" }, "\u8BFB\u53D6 web profile \u4E2D\u2026 ", Spin());
327
- if (error) return h("div", { className: "dshm-err" }, `\u8BFB\u53D6\u5931\u8D25\uFF1A${error}`);
1144
+ if (loading && !data) return h("div", { className: "dshm-empty" }, lookup("installed.loading"), Spin());
1145
+ if (error) return h("div", { className: "dshm-err" }, lookup("failed.read", { err: error }));
328
1146
  const items = data && data.items || [];
329
- if (!items.length) return h("div", { className: "dshm-empty" }, `web profile \u5C1A\u672A\u5B89\u88C5\u4EFB\u4F55 dsh \u63D2\u4EF6\uFF08${data.profileDir}\uFF09`);
1147
+ if (!items.length) return h("div", { className: "dshm-empty" }, `${lookup("installed.empty")} (${data.profileDir})`);
330
1148
  return h(
331
1149
  React.Fragment,
332
1150
  null,
333
- h("div", { className: "dshm-hint" }, `web profile\uFF1A${data.profileDir}${data.others ? ` \xB7 \u53E6\u6709 ${data.others} \u4E2A\u975E dsh \u4F9D\u8D56\u672A\u5217\u51FA` : ""}`),
1151
+ h("div", { className: "dshm-hint" }, lookup("profile.hint", { path: data.profileDir })),
1152
+ data.others > 0 ? h("div", { className: "dshm-others" }, lookup("installed.others", { n: data.others })) : null,
1153
+ busyPkg ? h(ProgressLine, { key: "prog" }) : null,
334
1154
  h(
335
1155
  "div",
336
1156
  { className: "dshm-cards" },
337
- items.map((it) => Card({
338
- key: it.pkg,
339
- icon: h(Icon, { entry: { name: it.name, github: it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null, icon: null } }),
340
- name: it.name,
341
- badges: [
342
- it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u53EF\u5347\u7EA7${it.latestVersion ? ` \u2192 v${it.latestVersion}` : ""}`) : null,
343
- it.registryId ? h("span", { className: "dshm-badge", key: "r" }, "\u5E02\u573A\u5B89\u88C5") : h("span", { className: "dshm-badge info", key: "r" }, "\u975E\u5E02\u573A\u5B89\u88C5")
344
- ],
345
- desc: it.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
346
- sub: [
347
- `v${it.version || "?"}`,
348
- { npm: "npm", github: "github", link: "\u672C\u5730 link", file: "\u672C\u5730 file", unknown: "\u672A\u77E5" }[it.source] || it.source
349
- ].join(" \xB7 "),
350
- open: openPkg === it.pkg,
351
- onToggle: () => setOpenPkg(openPkg === it.pkg ? null : it.pkg),
352
- detail: DetailRows([
353
- ["\u5305\u540D", it.pkg],
354
- ["\u5B89\u88C5 spec", it.spec],
355
- ["\u6700\u65B0", it.latestVersion ? `v${it.latestVersion}` : "\u2014"],
356
- ["\u6536\u5F55", it.registryId || "\u4E0D\u5728\u6536\u5F55\u6E05\u5355\u4E2D"],
357
- ["\u8DEF\u5F84", it.path]
358
- ]),
359
- actions: [
360
- it.outdated ? h(
361
- "button",
362
- {
363
- key: "up",
364
- className: "dshm-btn primary sm",
365
- disabled: busyPkg === it.pkg,
1157
+ items.map((it) => {
1158
+ const guard = uninstallGuard(it);
1159
+ return Card({
1160
+ key: it.pkg,
1161
+ icon: h(Icon, { entry: { name: it.name, github: it.registryGithub || it.githubRepo || (it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null), icon: null } }),
1162
+ name: it.name,
1163
+ badges: [
1164
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u2B06 ${it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "")}`.trim()) : null,
1165
+ it.registryId ? h("span", { className: "dshm-badge", key: "r" }, lookup("badge.market")) : h("span", { className: "dshm-badge info", key: "r" }, lookup("badge.nonmarket"))
1166
+ ],
1167
+ desc: it.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
1168
+ sub: [
1169
+ `v${it.version || "?"}`,
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
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
+ }),
1176
+ open: openPkg === it.pkg,
1177
+ onToggle: () => setOpenPkg(openPkg === it.pkg ? null : it.pkg),
1178
+ detail: readmePkg === it.pkg ? h(ReadmeBlock, { pkg: it.pkg }) : DetailRows([
1179
+ [lookup("detail.pkg"), it.pkg],
1180
+ [lookup("detail.spec"), it.spec],
1181
+ [lookup("detail.latest"), it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "\u2014")],
1182
+ [lookup("detail.listed"), it.registryId || lookup("detail.listed.no")],
1183
+ [lookup("detail.path"), it.path],
1184
+ guard.warn ? [lookup("detail.note"), guard.warn] : null
1185
+ ]),
1186
+ actions: [
1187
+ h("button", {
1188
+ key: "rd",
1189
+ className: "dshm-btn sm",
366
1190
  onClick: (e) => {
367
1191
  e.stopPropagation();
368
- doUpgrade(it);
1192
+ setReadmePkg(readmePkg === it.pkg ? null : it.pkg);
369
1193
  }
370
- },
371
- busyPkg === it.pkg ? h(Spin) : "\u5347\u7EA7"
372
- ) : null,
373
- it.registryId || it.source === "npm" ? h(TwoStepButton, {
374
- key: "un",
375
- label: "\u5378\u8F7D",
376
- confirmLabel: "\u786E\u8BA4\u5378\u8F7D\uFF1F",
377
- className: "dshm-btn sm",
378
- disabled: busyPkg === it.pkg,
379
- onConfirm: () => doUninstall(it)
380
- }) : null
381
- ]
382
- }))
1194
+ }, readmePkg === it.pkg ? lookup("readme.hide") : lookup("readme.show")),
1195
+ it.outdated ? h(
1196
+ "button",
1197
+ {
1198
+ key: "up",
1199
+ className: "dshm-btn primary sm",
1200
+ disabled: busyPkg === it.pkg,
1201
+ onClick: (e) => {
1202
+ e.stopPropagation();
1203
+ doUpgrade(it);
1204
+ }
1205
+ },
1206
+ busyPkg === it.pkg ? h(Spin) : lookup("action.upgrade")
1207
+ ) : null,
1208
+ h(TwoStepButton, {
1209
+ key: "un",
1210
+ label: lookup("action.uninstall"),
1211
+ confirmLabel: guard.confirm,
1212
+ className: "dshm-btn sm",
1213
+ disabled: busyPkg === it.pkg,
1214
+ onConfirm: () => doUninstall(it)
1215
+ })
1216
+ ]
1217
+ });
1218
+ })
383
1219
  )
384
1220
  );
385
1221
  }
386
- 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 }) {
387
1246
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
1247
+ const cfgState = useAsync(() => api("registry-config"), []);
388
1248
  const self = useAsync(() => api("self-check"), []);
389
1249
  const [busy, setBusy] = useState(false);
390
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(), []);
391
1263
  const refresh = async () => {
392
1264
  setBusy(true);
393
1265
  try {
394
1266
  await reg.reload(true);
395
- notify({ kind: "ok", text: "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0" });
1267
+ notify({ kind: "ok", text: lookup("registry.refreshed"), needsRestart: false });
396
1268
  } finally {
397
1269
  setBusy(false);
398
1270
  }
399
1271
  };
400
- const upgradeSelf = async () => {
401
- 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);
402
1278
  try {
403
- const res = await api("self-upgrade");
404
- notify({ kind: "ok", text: `dsh-m \u5DF2\u66F4\u65B0\u5230 v${res.version}\uFF0C\u91CD\u542F\u540E\u751F\u6548` });
405
- 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?.();
406
1284
  } catch (e) {
407
- notify({ kind: "err", text: `\u81EA\u66F4\u65B0\u5931\u8D25\uFF1A${e && e.message || e}` });
1285
+ const message = String(e && e.message || e);
1286
+ setApplyError(message);
1287
+ await reloadRegistryState().catch(() => void 0);
408
1288
  } finally {
409
- setUpgrading(false);
1289
+ setApplying(false);
1290
+ }
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);
410
1326
  }
411
1327
  };
1328
+ const snap = cfgData || {};
1329
+ const state = snap.registryState || (reg.data ? reg.data.registryState : null) || null;
412
1330
  return h(
413
1331
  React.Fragment,
414
1332
  null,
415
1333
  Section(
416
- "\u6536\u5F55\u6E05\u5355\uFF08registry\uFF09",
1334
+ lookup("settings.registry"),
417
1335
  h(
418
1336
  "div",
419
- { className: "dshm-kv" },
420
- h("span", { className: "k" }, "\u5F53\u524D\u6765\u6E90"),
421
- h("span", null, regSourceLabel(reg.data)),
422
- h("span", { className: "k" }, "\u66F4\u65B0\u65F6\u95F4"),
423
- h("span", null, fmtDate(reg.data && reg.data.fetchedAt)),
424
- h("span", { className: "k" }, "\u6761\u76EE\u6570"),
425
- h("span", null, reg.data ? `${reg.data.plugins.length} \u6761` : "\u2014"),
426
- h("span", { className: "k" }, "\u7F13\u5B58\u7B56\u7565"),
427
- h("span", null, "TTL 60 \u5206\u949F\uFF1B\u8BBE\u7F6E registryUrl \u53EF\u8986\u76D6\u6E90")
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"))
428
1346
  ),
429
- reg.data && reg.data.errors && reg.data.errors.length ? h("div", { className: "dshm-err" }, `\u8FDC\u7AEF\u63D0\u793A\uFF1A${reg.data.errors.join("\uFF1B")}`) : null,
430
1347
  h(
431
1348
  "div",
432
1349
  { className: "dshm-actions" },
433
- h("button", { className: "dshm-btn sm", disabled: busy || reg.loading, onClick: refresh }, busy || reg.loading ? h(Spin) : "\u5F3A\u5236\u5237\u65B0")
434
- )
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),
1384
+ h("span", { className: "k" }, lookup("settings.updated")),
1385
+ h("span", null, fmtDate(state && state.fetchedAt)),
1386
+ h("span", { className: "k" }, lookup("settings.count")),
1387
+ h("span", null, state ? lookup("settings.count.v", { n: state.count ?? 0 }) : "\u2014"),
1388
+ h("span", { className: "k" }, lookup("settings.policy")),
1389
+ h("span", null, lookup("settings.policy.v"))
1390
+ ),
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(
1396
+ "div",
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
435
1410
  ),
436
1411
  Section(
437
- "dsh-m \u81EA\u8EAB",
1412
+ lookup("settings.self"),
438
1413
  h(
439
1414
  "div",
440
1415
  { className: "dshm-kv" },
441
- h("span", { className: "k" }, "\u5F53\u524D\u7248\u672C"),
1416
+ h("span", { className: "k" }, lookup("settings.current")),
442
1417
  h("span", null, self.data ? `v${self.data.current}` : "\u2014"),
443
- h("span", { className: "k" }, "npm \u6700\u65B0"),
444
- h("span", null, self.data ? self.data.latest ? `v${self.data.latest}` : `\u67E5\u8BE2\u5931\u8D25${self.data.error ? `\uFF1A${self.data.error}` : ""}` : "\u2026")
1418
+ h("span", { className: "k" }, lookup("settings.npmlatest")),
1419
+ h("span", null, self.data ? self.data.latest ? `v${self.data.latest}` : lookup("settings.lookupfailed", { err: self.data.error || "" }) : "\u2026")
445
1420
  ),
446
1421
  self.data && self.data.outdated ? h(
447
1422
  "div",
448
1423
  { className: "dshm-actions" },
449
- h("button", { className: "dshm-btn primary sm", disabled: upgrading, onClick: upgradeSelf }, upgrading ? h(Spin) : "\u5347\u7EA7 dsh-m"),
450
- h("span", { className: "dshm-hint" }, "\u5347\u7EA7\u540E\u540C\u6837\u9700\u8981\u91CD\u542F\u751F\u6548")
1424
+ h("button", { className: "dshm-btn primary sm", disabled: upgrading, onClick: upgradeSelf }, upgrading ? h(Spin) : lookup("settings.upgradeself")),
1425
+ h("span", { className: "dshm-hint" }, lookup("settings.upgradehint"))
451
1426
  ) : null
452
1427
  ),
453
1428
  Section(
454
- "\u5173\u4E8E",
455
- h(
456
- "div",
457
- { className: "dshm-hint" },
458
- "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"
459
- )
1429
+ lookup("settings.about"),
1430
+ h("div", { className: "dshm-hint" }, lookup("settings.about.text"))
460
1431
  )
461
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
+ }
462
1445
  }
463
- function regSourceLabel(data) {
464
- if (!data) return "\u2014";
465
- const map = { override: "\u81EA\u5B9A\u4E49\u6E90", jsdelivr: "jsDelivr\uFF08@main\uFF09", raw: "raw.githubusercontent\uFF08@main\uFF09", cache: "\u672C\u5730\u7F13\u5B58", bundled: "\u5305\u5185\u5FEB\u7167\uFF08\u515C\u5E95\uFF09" };
466
- return map[data.source] || data.source;
467
- }
468
- 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
+ }
469
1451
  return h(
470
1452
  "div",
471
- { style: { display: "flex", flexDirection: "column", gap: "8px" } },
472
- 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
+ ),
473
1460
  ...children
474
1461
  );
475
1462
  }
@@ -480,11 +1467,11 @@ function DetailRows(rows) {
480
1467
  { className: "dshm-kv" },
481
1468
  list.flatMap(([k, v]) => [
482
1469
  h("span", { className: "k", key: `${k}-k` }, k),
483
- h("span", { key: `${k}-v`, style: { wordBreak: "break-all" } }, String(v))
1470
+ h("span", { key: `${k}-v`, style: { wordBreak: "break-all" } }, v)
484
1471
  ])
485
1472
  );
486
1473
  }
487
- function Card({ icon, name, badges, desc, sub, open, onToggle, detail, actions }) {
1474
+ function Card({ icon, name, badges, desc, sub, links, open, onToggle, detail, actions }) {
488
1475
  return h(
489
1476
  "div",
490
1477
  {
@@ -503,18 +1490,29 @@ function Card({ icon, name, badges, desc, sub, open, onToggle, detail, actions }
503
1490
  h("div", { className: "dshm-top" }, h("span", { className: "dshm-name" }, name), ...badges.filter(Boolean)),
504
1491
  h("div", { className: "dshm-desc", style: open ? { WebkitLineClamp: "unset" } : null }, desc),
505
1492
  sub ? h("div", { className: "dshm-sub" }, sub) : null,
1493
+ links || null,
506
1494
  open ? h("div", { className: "dshm-detail" }, detail) : null,
507
1495
  open && actions && actions.length ? h("div", { className: "dshm-actions" }, ...actions) : null
508
1496
  )
509
1497
  );
510
1498
  }
511
1499
  var TABS = [
512
- ["market", "\u5E02\u573A"],
513
- ["installed", "\u5DF2\u88C5"],
514
- ["settings", "\u8BBE\u7F6E"]
1500
+ ["market", "tab.market", "market"],
1501
+ ["installed", "tab.installed", "installed"],
1502
+ ["settings", "tab.settings", null]
515
1503
  ];
516
1504
  function MarketPanel({ onClose }) {
517
1505
  const [tab, setTab] = useState("market");
1506
+ const market = useMarketData();
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]);
1512
+ const counts = {
1513
+ market: market.data ? market.data.total : null,
1514
+ installed: installed.data ? installed.data.items.length : null
1515
+ };
518
1516
  const [banner, setBanner] = useState(null);
519
1517
  const [toast, setToast] = useState(null);
520
1518
  useEffect(() => {
@@ -529,9 +1527,9 @@ function MarketPanel({ onClose }) {
529
1527
  const t = setTimeout(() => setToast(null), 6e3);
530
1528
  return () => clearTimeout(t);
531
1529
  }, [toast]);
532
- const notify = useCallback(({ kind, text }) => {
1530
+ const notify = useCallback(({ kind, text, needsRestart }) => {
533
1531
  setToast({ kind, text });
534
- if (kind === "ok") setBanner({ text: "\u53D8\u66F4\u5B8C\u6210\uFF0C\u9700\u8981\u91CD\u542F DSH Web \u540E\u751F\u6548\u3002" });
1532
+ if (kind === "ok" && needsRestart) setBanner({ text: lookup("banner.done") });
535
1533
  }, []);
536
1534
  return h(
537
1535
  "div",
@@ -542,23 +1540,39 @@ function MarketPanel({ onClose }) {
542
1540
  h(
543
1541
  "div",
544
1542
  { className: "dshm-head" },
545
- h("span", { className: "dshm-title" }, "\u{1F6CD} DSH \u5E02\u573A"),
546
- TABS.map(
547
- ([key, label]) => h("button", { key, className: `dshm-tab${tab === key ? " on" : ""}`, onClick: () => setTab(key) }, label)
1543
+ h("span", { className: "dshm-title" }, lookup("title.full")),
1544
+ h(
1545
+ "div",
1546
+ { className: "dshm-seg", role: "tablist" },
1547
+ TABS.map(
1548
+ ([key, labelKey, countKey]) => h(
1549
+ "button",
1550
+ {
1551
+ key,
1552
+ type: "button",
1553
+ role: "tab",
1554
+ "aria-selected": tab === key,
1555
+ className: tab === key ? "on" : "",
1556
+ onClick: () => setTab(key)
1557
+ },
1558
+ lookup(labelKey),
1559
+ countKey && counts[countKey] != null ? h("span", { className: "dshm-count" }, String(counts[countKey])) : null
1560
+ )
1561
+ )
548
1562
  ),
549
1563
  h("span", { className: "dshm-spacer" }),
550
- h("button", { className: "dshm-btn", onClick: onClose }, "\u5173\u95ED")
1564
+ h("button", { className: "dshm-btn", onClick: onClose }, lookup("common.close"))
551
1565
  ),
552
1566
  h(
553
1567
  "div",
554
1568
  { className: "dshm-body" },
555
- tab === "market" ? h(MarketTab, { notify }) : null,
556
- tab === "installed" ? h(InstalledTab, { notify }) : null,
557
- tab === "settings" ? h(SettingsTab, { notify }) : null
1569
+ tab === "market" ? h(MarketTab, { notify, market }) : null,
1570
+ tab === "installed" ? h(InstalledTab, { notify, installed }) : null,
1571
+ tab === "settings" ? h(SettingsTab, { notify, onRegistryChanged }) : null
558
1572
  ),
559
1573
  toast ? h(
560
1574
  "div",
561
- { className: `dshm-banner`, style: toast.kind === "err" ? { background: "var(--dsw-alias-state-danger-tertiary,#fef2f2)", color: "var(--dsw-alias-state-danger-primary,#dc2626)" } : null },
1575
+ { className: `dshm-banner`, style: toast.kind === "err" ? { background: "var(--dsw-alias-state-error-secondary,#fee2e2)", color: "var(--dsw-alias-state-error-primary,#b91c1c)" } : null },
562
1576
  h("span", { className: "dshm-banner-text" }, toast.text)
563
1577
  ) : null,
564
1578
  banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null) }) : null
@@ -591,12 +1605,24 @@ function mountPanel() {
591
1605
  container.remove();
592
1606
  }
593
1607
  }
1608
+ function MarketIcon() {
1609
+ return h(
1610
+ "svg",
1611
+ { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true" },
1612
+ h("rect", { x: "1.75", y: "1.75", width: "5.5", height: "5.5", rx: "1.2", stroke: "currentColor", strokeWidth: "1.4" }),
1613
+ h("rect", { x: "8.75", y: "1.75", width: "5.5", height: "5.5", rx: "1.2", stroke: "currentColor", strokeWidth: "1.4" }),
1614
+ h("rect", { x: "1.75", y: "8.75", width: "5.5", height: "5.5", rx: "1.2", stroke: "currentColor", strokeWidth: "1.4" }),
1615
+ // 右下:放大镜(收一点,光学尺寸与其他宫格图标一致)
1616
+ h("circle", { cx: "10.5", cy: "10.5", r: "2.9", stroke: "currentColor", strokeWidth: "1.4" }),
1617
+ h("path", { d: "M12.6 12.6 14.3 14.3", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
1618
+ );
1619
+ }
594
1620
  function MarketEntry(props) {
595
1621
  useEffect(() => ensureCss(), []);
596
1622
  return h(
597
1623
  "button",
598
1624
  {
599
- className: "dshm-btn dshm-entry",
1625
+ className: "dshm-entry",
600
1626
  onClick: () => {
601
1627
  try {
602
1628
  mountPanel();
@@ -604,11 +1630,10 @@ function MarketEntry(props) {
604
1630
  console.error("[dsh-m] \u6253\u5F00\u5E02\u573A\u9762\u677F\u5931\u8D25:", e);
605
1631
  }
606
1632
  },
607
- title: "DSH Marketplace",
608
- style: { margin: "4px" }
1633
+ title: "\u63D2\u4EF6\u5E02\u573A"
609
1634
  },
610
- "\u{1F6CD}",
611
- props && props.wide ? " DSH \u5E02\u573A" : null
1635
+ h(MarketIcon),
1636
+ props && props.wide ? h("span", null, lookup("market.title")) : null
612
1637
  );
613
1638
  }
614
1639
  function registerSlot(slots, options, component) {
@@ -683,6 +1708,7 @@ function ToolCardRow({ it, onInstalled }) {
683
1708
  ),
684
1709
  h("div", { className: "dshm-desc" }, it.description),
685
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 }),
686
1712
  h(
687
1713
  "div",
688
1714
  { className: "dshm-actions" },
@@ -708,10 +1734,10 @@ function SearchToolView(props) {
708
1734
  let live = true;
709
1735
  api("search", { query, category: args.category, limit: args.limit }).then((d) => {
710
1736
  if (live) setItems(d.items || []);
711
- }).catch((e) => {
1737
+ }).catch(() => {
712
1738
  if (live) {
713
1739
  setItems([]);
714
- setErr(e && e.message || String(e));
1740
+ setErr(lookup("notice.toolview.err"));
715
1741
  }
716
1742
  });
717
1743
  return () => {
@@ -758,10 +1784,21 @@ function apply(ctx) {
758
1784
  const slots = ctx.slots;
759
1785
  if (!slots) return;
760
1786
  ctx.effect(() => ensureCss(), "dshm-style");
1787
+ ctx.inject(["locale"], (c) => {
1788
+ if (!c.locale || typeof c.locale.register !== "function") return;
1789
+ c.effect(() => {
1790
+ try {
1791
+ return c.locale.register("dshm", { zh: ZH, en: EN });
1792
+ } catch {
1793
+ return () => {
1794
+ };
1795
+ }
1796
+ }, "dshm-locale");
1797
+ });
761
1798
  slots.inject(
762
1799
  "sidebar.footer.action",
763
1800
  () => slots.register(
764
- { name: "sidebar.footer.action", id: "dshm-market", key: "dshm-market", order: 9, label: () => "DSH \u5E02\u573A" },
1801
+ { name: "sidebar.footer.action", id: "dshm-market", key: "dshm-market", order: 9, locale: "dshm", label: () => lookup("market.title") },
765
1802
  function DshmMarketEntry(actionProps) {
766
1803
  return h(MarketEntry, actionProps);
767
1804
  }