@max-null/dsh-plugin-center 0.1.5 → 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/client.js CHANGED
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  inject: () => inject
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
+ var import_react_dom = require("react-dom");
33
34
  var import_react = require("react");
34
35
  var import_jsx_runtime = require("react/jsx-runtime");
35
36
  var CSS = `
@@ -60,11 +61,27 @@ var CSS = `
60
61
  .pc-badge.local, .pc-badge.builtin { background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-tertiary); }
61
62
  .pc-tag { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: 6px; font-size: 12px; line-height: 1; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); }
62
63
  .pc-tag.danger { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
63
- .pc-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); }
64
- .pc-dot.failed { background: var(--dsw-alias-state-error-primary); }
64
+ .pc-switch { position: relative; flex: none; width: 36px; height: 20px; border-radius: 10px; border: none; background: rgba(0,0,0,.22); cursor: pointer; transition: background .2s ease; padding: 0; }
65
+ .pc-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: left .2s ease; }
66
+ .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
67
+ .pc-switch.on::after { left: 18px; }
68
+ .pc-switch:disabled { opacity: .6; cursor: default; }
69
+ body[data-ds-dark-theme] .pc-switch { background: rgba(255,255,255,.28); }
70
+ body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
65
71
 
66
72
  .pc-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0; }
67
- .pc-chip { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; }
73
+ /* \u7B5B\u9009\u533A\u7EB5\u5411\u5BB9\u5668\uFF1AAI \u63A8\u8350\u884C / \u4E3B\u7B5B\u9009\u884C / \u5206\u7C7B\u884C\u4E09\u6BB5\uFF0C\u95F4\u8DDD\u7EDF\u4E00\u3002 */
74
+ .pc-filter { display: flex; flex-direction: column; gap: 6px; flex: none; margin-top: 2px; }
75
+ /* \u4E3B\u7B5B\u9009\u884C\uFF1A\u4E00\u884C\u653E\u641C\u7D22 + \u6765\u6E90 + \u5DE5\u4F5C\u533A\u6309\u94AE\uFF0C\u4E0D\u6362\u884C\u3002 */
76
+ .pc-toolbar-main { flex-wrap: nowrap; }
77
+ /* \u5206\u7C7B\u884C\uFF1A\u72EC\u7ACB\u4E00\u884C\u3001\u6A2A\u5411\u6EDA\u52A8\uFF08\u4E0D wrap \u6210\u6298\u884C\u4E71\u6392\uFF09\uFF0Cchip \u4E0D\u538B\u7F29\u3002 */
78
+ .pc-catbar { display: flex; align-items: center; gap: 6px; overflow-x: auto; padding: 2px 0 4px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l3, rgba(0,0,0,.2)) transparent; }
79
+ .pc-catbar .pc-chip { flex: none; white-space: nowrap; }
80
+ /* \u5E02\u573A\u8D85\u91CF\u63D0\u793A\uFF1A\u5C0F\u53F7\u5F31\u5316\u6587\u5B57\uFF0C\u4E0E\u7B5B\u9009\u533A\u89C6\u89C9\u5206\u79BB\u3002 */
81
+ .pc-limit { margin: 2px 0 8px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-caption); }
82
+ .pc-ai-row { display: flex; gap: 8px; align-items: center; }
83
+ .pc-ai-row .pc-btn { height: 28px; }
84
+ .pc-chip { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; transition: background .15s ease, color .15s ease, border-color .15s ease; }
68
85
  .pc-chip:hover { background: var(--dsw-alias-interactive-bg-hover); }
69
86
  .pc-chip.active { background: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); color: var(--dsw-alias-bg-base); }
70
87
 
@@ -77,7 +94,12 @@ var CSS = `
77
94
  .pc-search { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; width: 200px; font-family: inherit; }
78
95
  .pc-search:focus { border-color: var(--dsw-alias-state-business-primary); }
79
96
  .pc-search::placeholder { color: var(--dsw-alias-label-caption); }
80
- .pc-select { height: 28px; padding: 0 8px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; }
97
+ /* \u5B9A\u5236 select\uFF1A\u5265\u79BB\u6D4F\u89C8\u5668\u539F\u751F\u5916\u89C2\uFF08\u9AD8\u5EA6/\u5185\u8FB9\u8DDD/padding \u5DEE\u5F02\u662F\u8DE8\u63A7\u4EF6\u4E0D\u9F50\u7684
98
+ \u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E chip/\u641C\u7D22\u6846\u540C\u9AD8\u540C\u5706\u89D2\u3002 */
99
+ .pc-select { height: 28px; padding: 0 26px 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background-color: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23777777' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 10px; }
100
+ .pc-select:hover { background-color: var(--dsw-alias-interactive-bg-hover); }
101
+ .pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
102
+ body[data-ds-dark-theme] .pc-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23b0b0b0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); }
81
103
  .pc-iconbtn { width: 28px; height: 28px; padding: 0; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
82
104
  .pc-iconbtn:hover { background: var(--dsw-alias-interactive-bg-hover); }
83
105
 
@@ -97,9 +119,24 @@ var CSS = `
97
119
  .pc-wn-item { border-bottom: 1px solid var(--dsw-alias-border-l1); padding: 14px 0; }
98
120
  .pc-wn-item:last-child { border-bottom: none; }
99
121
  .pc-wn-list { margin-top: 8px; padding-left: 20px; color: var(--dsw-alias-label-secondary); font-size: 13px; }
100
- .pc-toast { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%); padding: 10px 18px; border-radius: 10px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-primary); font-size: 13px; box-shadow: 0 8px 32px rgba(0,0,0,.18); z-index: 200; max-width: 80vw; }
122
+ .pc-toast { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%); padding: 10px 18px; border-radius: 10px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-primary); font-size: 13px; box-shadow: 0 8px 32px rgba(0,0,0,.18); z-index: 1500; max-width: 80vw; }
101
123
  .pc-toast.ok { border-color: var(--dsw-alias-state-success-primary); }
102
124
  .pc-toast.error { border-color: var(--dsw-alias-state-error-primary); }
125
+ /* DSH 0.1.x \u8BBE\u7F6E\u5BFC\u822A\u65E0 icon \u5951\u7EA6\uFF08external section \u4E00\u5F8B\u9ED8\u8BA4\u9F7F\u8F6E\uFF09\u3002settings-nav-icon
126
+ \u6807\u8BB0\u672C\u63D2\u4EF6\u884C\u540E\uFF1A\u9690\u85CF\u58F3\u6E32\u67D3\u7684\u9F7F\u8F6E SVG\uFF0C\u7528\u4E0E\u53F3\u4E0A\u89D2 HeaderButton \u76F8\u540C\u7684 2\xD72 \u7F51\u683C
127
+ \u56FE\u6807\uFF08currentColor mask\uFF09\u66FF\u6362\uFF0C\u8DDF\u968F\u539F\u751F\u5BFC\u822A hover/active \u989C\u8272\u4E14\u4E0D\u6539\u53D8\u58F3\u7684
128
+ 16px \u56FE\u6807\u8282\u594F\u3002\u9009\u62E9\u5668\u517C\u5BB9\u56FE\u6807\u76F4\u63A5\u4E3A button \u9996\u5B50\u5143\u7D20\u4E0E\u5305\u4E00\u5C42 wrapper \u4E24\u79CD\u60C5\u51B5\u3002 */
129
+ [data-dsh-plugin-center-settings-nav] > svg:first-child,
130
+ [data-dsh-plugin-center-settings-nav] > *:first-child > svg:first-child { display: none; }
131
+ [data-dsh-plugin-center-settings-nav]::before {
132
+ content: '';
133
+ flex: none;
134
+ width: 16px;
135
+ height: 16px;
136
+ background: currentColor;
137
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='2' y='2' width='5' height='5' rx='1'/%3E%3Crect x='9' y='2' width='5' height='5' rx='1'/%3E%3Crect x='2' y='9' width='5' height='5' rx='1'/%3E%3Crect x='9' y='9' width='5' height='5' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
138
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='2' y='2' width='5' height='5' rx='1'/%3E%3Crect x='9' y='2' width='5' height='5' rx='1'/%3E%3Crect x='2' y='9' width='5' height='5' rx='1'/%3E%3Crect x='9' y='9' width='5' height='5' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
139
+ }
103
140
  `;
104
141
  var cssInjected = false;
105
142
  function injectCss() {
@@ -110,6 +147,30 @@ function injectCss() {
110
147
  style.textContent = CSS;
111
148
  document.head.append(style);
112
149
  }
150
+ var SETTINGS_NAV_MARKER = "data-dsh-plugin-center-settings-nav";
151
+ function registerSettingsNavIcon(label) {
152
+ let disposed = false;
153
+ const sync = () => {
154
+ if (disposed) return;
155
+ const currentLabel = label().trim();
156
+ const buttons = document.querySelectorAll('[role="dialog"] nav button');
157
+ for (const button of buttons) {
158
+ const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
159
+ if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
160
+ else button.removeAttribute(SETTINGS_NAV_MARKER);
161
+ }
162
+ };
163
+ sync();
164
+ const observer = new MutationObserver(sync);
165
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
166
+ return () => {
167
+ disposed = true;
168
+ observer.disconnect();
169
+ document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`).forEach((element) => {
170
+ element.removeAttribute(SETTINGS_NAV_MARKER);
171
+ });
172
+ };
173
+ }
113
174
  var rpc = async () => {
114
175
  throw new Error("plugin-center: rpc not wired");
115
176
  };
@@ -126,6 +187,9 @@ function closeOverlay() {
126
187
  overlayOpen = false;
127
188
  overlayListeners.forEach((l) => l());
128
189
  }
190
+ function toggleOverlay() {
191
+ overlayOpen ? closeOverlay() : openOverlay();
192
+ }
129
193
  function closeWhatsNew() {
130
194
  whatsNewOpen = false;
131
195
  for (const d of whatsNewDigests) readCache[d.name] = d.toVersion;
@@ -161,6 +225,82 @@ function useWhatsNewOpen() {
161
225
  var readCache = {};
162
226
  var installedCache = null;
163
227
  var marketCache = {};
228
+ var countsState = { installed: 0, market: 0, dshMarket: 0, failed: 0 };
229
+ var countListeners = /* @__PURE__ */ new Set();
230
+ function setCounts(partial) {
231
+ const next = { ...countsState, ...partial };
232
+ if (next.installed === countsState.installed && next.market === countsState.market && next.dshMarket === countsState.dshMarket && next.failed === countsState.failed) return;
233
+ countsState = next;
234
+ countListeners.forEach((l) => l());
235
+ }
236
+ var updatingPlugins = /* @__PURE__ */ new Set();
237
+ var updatingListeners = /* @__PURE__ */ new Set();
238
+ function setUpdating(name, on) {
239
+ if (on) updatingPlugins.add(name);
240
+ else updatingPlugins.delete(name);
241
+ updatingListeners.forEach((l) => l());
242
+ }
243
+ var pendingToggles = /* @__PURE__ */ new Map();
244
+ var pendingListeners = /* @__PURE__ */ new Set();
245
+ function setPendingToggle(id, action) {
246
+ if (action === "disable") pendingToggles.set(id, true);
247
+ else if (action === "enable") pendingToggles.set(id, false);
248
+ else pendingToggles.delete(id);
249
+ pendingListeners.forEach((l) => l());
250
+ }
251
+ function usePendingVersion() {
252
+ const [v, setV] = (0, import_react.useState)(0);
253
+ (0, import_react.useEffect)(() => {
254
+ const l = () => {
255
+ setV((x) => x + 1);
256
+ };
257
+ pendingListeners.add(l);
258
+ return () => {
259
+ pendingListeners.delete(l);
260
+ };
261
+ }, []);
262
+ return v;
263
+ }
264
+ var installedListeners = /* @__PURE__ */ new Set();
265
+ function useInstalledVersion() {
266
+ const [v, setV] = (0, import_react.useState)(0);
267
+ (0, import_react.useEffect)(() => {
268
+ const l = () => {
269
+ setV((x) => x + 1);
270
+ };
271
+ installedListeners.add(l);
272
+ return () => {
273
+ installedListeners.delete(l);
274
+ };
275
+ }, []);
276
+ return v;
277
+ }
278
+ function useUpdatingVersion() {
279
+ const [v, setV] = (0, import_react.useState)(0);
280
+ (0, import_react.useEffect)(() => {
281
+ const l = () => {
282
+ setV((x) => x + 1);
283
+ };
284
+ updatingListeners.add(l);
285
+ return () => {
286
+ updatingListeners.delete(l);
287
+ };
288
+ }, []);
289
+ return v;
290
+ }
291
+ function useCounts() {
292
+ const [v, setV] = (0, import_react.useState)(0);
293
+ (0, import_react.useEffect)(() => {
294
+ const l = () => {
295
+ setV((x) => x + 1);
296
+ };
297
+ countListeners.add(l);
298
+ return () => {
299
+ countListeners.delete(l);
300
+ };
301
+ }, []);
302
+ return countsState;
303
+ }
164
304
  var toastState = null;
165
305
  var toastListeners = /* @__PURE__ */ new Set();
166
306
  function showToast(message, kind = "ok", duration = 3200) {
@@ -257,7 +397,32 @@ var STRINGS = {
257
397
  close: "\u5173\u95ED",
258
398
  checkFail: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
259
399
  foundUpdates: "\u53D1\u73B0 {n} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6",
260
- allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0"
400
+ allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0",
401
+ disable: "\u7981\u7528",
402
+ enable: "\u542F\u7528",
403
+ toggling: "\u5904\u7406\u4E2D\u2026",
404
+ disabledOk: "\u5DF2\u7981\u7528 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
405
+ enabledOk: "\u5DF2\u542F\u7528 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
406
+ toggleFailed: "\u64CD\u4F5C\u5931\u8D25\uFF1A{e}",
407
+ revertedDisable: "\u5DF2\u64A4\u9500\u7981\u7528 {n}",
408
+ revertedEnable: "\u5DF2\u64A4\u9500\u542F\u7528 {n}",
409
+ tabDiagnose: "\u8BCA\u65AD",
410
+ diagExport: "\u5BFC\u51FA\u8BCA\u65AD\u65E5\u5FD7",
411
+ diagCopied: "\u8BCA\u65AD\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
412
+ diagTitle: "\u73AF\u5883\u4E0E\u63D2\u4EF6\u8BCA\u65AD",
413
+ diagInstalled: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF08{n}\uFF09",
414
+ diagDisabled: "\u7981\u7528\u72B6\u6001",
415
+ diagPnpmLog: "pnpm \u65E5\u5FD7\uFF08\u5C3E\u90E8\uFF09",
416
+ aiTitle: "AI \u63A8\u8350",
417
+ aiPlaceholder: "\u63CF\u8FF0\u4F60\u7684\u9700\u6C42\uFF0C\u8BA9 AI \u63A8\u8350\u63D2\u4EF6\u2026\uFF08\u4F8B\u5982\uFF1A\u80FD\u9884\u89C8 Markdown \u7684\u63D2\u4EF6\uFF09",
418
+ aiAsk: "\u63A8\u8350",
419
+ aiAsking: "AI \u601D\u8003\u4E2D\u2026",
420
+ aiFail: "AI \u63A8\u8350\u5931\u8D25\uFF1A{e}",
421
+ scoreLabel: "\u8BC4\u5206",
422
+ screenshot: "\u622A\u56FE",
423
+ noScreenshot: "\u65E0\u622A\u56FE",
424
+ screenshotFail: "\u622A\u56FE\u83B7\u53D6\u5931\u8D25",
425
+ marketTooMany: "\u4EC5\u663E\u793A\u524D {n} \u4E2A\uFF08\u5171 {m}\uFF09\uFF0C\u641C\u7D22\u53EF\u7F29\u5C0F\u8303\u56F4"
261
426
  },
262
427
  en: {
263
428
  title: "Plugin Center",
@@ -311,7 +476,32 @@ var STRINGS = {
311
476
  close: "Close",
312
477
  checkFail: "Failed to check updates, please retry later",
313
478
  foundUpdates: "{n} updates found",
314
- allUpToDate: "All plugins are up to date"
479
+ allUpToDate: "All plugins are up to date",
480
+ disable: "Disable",
481
+ enable: "Enable",
482
+ toggling: "Working\u2026",
483
+ disabledOk: "Disabled {n}; restart to take effect",
484
+ enabledOk: "Enabled {n}; restart to take effect",
485
+ toggleFailed: "Failed: {e}",
486
+ revertedDisable: "Disable of {n} reverted",
487
+ revertedEnable: "Enable of {n} reverted",
488
+ tabDiagnose: "Diagnostics",
489
+ diagExport: "Export diagnostics",
490
+ diagCopied: "Diagnostics copied to clipboard",
491
+ diagTitle: "Environment & plugin diagnostics",
492
+ diagInstalled: "Installed plugins ({n})",
493
+ diagDisabled: "Disabled state",
494
+ diagPnpmLog: "pnpm log (tail)",
495
+ aiTitle: "AI recommendation",
496
+ aiPlaceholder: "Describe what you need \u2014 e.g. a Markdown preview plugin\u2026",
497
+ aiAsk: "Recommend",
498
+ aiAsking: "AI is thinking\u2026",
499
+ aiFail: "AI recommendation failed: {e}",
500
+ scoreLabel: "Score",
501
+ screenshot: "Screenshot",
502
+ noScreenshot: "No screenshot",
503
+ screenshotFail: "Screenshot fetch failed",
504
+ marketTooMany: "Showing first {n} of {m}; search to narrow down"
315
505
  }
316
506
  };
317
507
  var localeId = "zh";
@@ -336,15 +526,22 @@ function useT() {
336
526
  localeListeners.delete(l);
337
527
  };
338
528
  }, []);
339
- return (key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars);
529
+ return (0, import_react.useCallback)((key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars), [id]);
340
530
  }
341
- function InstalledView({ search, category, source }) {
531
+ function InstalledView({ search, category, source, onToggle, togglingId }) {
342
532
  const t = useT();
533
+ const installedVersion = useInstalledVersion();
534
+ usePendingVersion();
343
535
  const [items, setItems] = (0, import_react.useState)(installedCache);
344
536
  const [error, setError] = (0, import_react.useState)(null);
345
537
  (0, import_react.useEffect)(() => {
346
- if (installedCache !== null) return;
347
538
  let alive = true;
539
+ if (installedCache !== null) {
540
+ setItems(installedCache);
541
+ return () => {
542
+ alive = false;
543
+ };
544
+ }
348
545
  void rpc("listInstalled").then(
349
546
  (v) => {
350
547
  installedCache = v;
@@ -357,7 +554,11 @@ function InstalledView({ search, category, source }) {
357
554
  return () => {
358
555
  alive = false;
359
556
  };
360
- }, []);
557
+ }, [installedVersion]);
558
+ const handleToggleLocal = (p) => {
559
+ setItems((prev) => prev === null ? prev : prev.map((item) => item.entryId === p.entryId ? { ...item, enabled: !item.enabled } : item));
560
+ onToggle(p);
561
+ };
361
562
  if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
362
563
  if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
363
564
  const srcLabel = {
@@ -382,22 +583,42 @@ function InstalledView({ search, category, source }) {
382
583
  ] }),
383
584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: srcLabel[p.source] }),
384
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
385
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-dot${p.fiberPhase === "failed" ? " failed" : ""}` })
586
+ p.fiberPhase === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-dot failed", title: "failed" }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
588
+ "button",
589
+ {
590
+ type: "button",
591
+ role: "switch",
592
+ "aria-checked": p.enabled,
593
+ "aria-label": p.enabled ? t("disable") : t("enable"),
594
+ title: p.enabled ? t("disable") : t("enable"),
595
+ className: `pc-switch${p.enabled ? " on" : ""}`,
596
+ disabled: togglingId !== null,
597
+ onClick: () => {
598
+ handleToggleLocal(p);
599
+ }
600
+ }
601
+ )
386
602
  ] }),
387
603
  p.description !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: p.description }),
388
604
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
389
605
  p.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
390
606
  p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("requiresDsh", { r: p.compatRange }) }),
391
- !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
607
+ !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") }),
608
+ pendingToggles.has(p.entryId) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("pendingRestart") })
392
609
  ] })
393
610
  ] }, p.entryId)) });
394
611
  }
395
612
  function MarketView({ category, single, source, search, onCount }) {
396
613
  const t = useT();
397
- const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? []);
398
- const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? false);
614
+ const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? marketCache.all?.plugins ?? []);
615
+ const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? marketCache.all?.done ?? false);
399
616
  const [error, setError] = (0, import_react.useState)(null);
400
617
  const [busy, setBusy] = (0, import_react.useState)(null);
618
+ const [shotBusy, setShotBusy] = (0, import_react.useState)(null);
619
+ const [shotUrl, setShotUrl] = (0, import_react.useState)(null);
620
+ const [shotName, setShotName] = (0, import_react.useState)(null);
621
+ const [shotZoom, setShotZoom] = (0, import_react.useState)(false);
401
622
  (0, import_react.useEffect)(() => {
402
623
  let alive = true;
403
624
  let timer;
@@ -408,9 +629,10 @@ function MarketView({ category, single, source, search, onCount }) {
408
629
  onCount(cached.plugins.length);
409
630
  if (cached.done) return;
410
631
  } else {
411
- setItems([]);
412
- setDone(false);
413
- onCount(0);
632
+ const fallback = marketCache.all;
633
+ setItems(fallback?.plugins ?? []);
634
+ setDone(fallback?.done ?? false);
635
+ if (fallback !== void 0) onCount(fallback.plugins.length);
414
636
  }
415
637
  const poll = async () => {
416
638
  try {
@@ -438,12 +660,14 @@ function MarketView({ category, single, source, search, onCount }) {
438
660
  const matchSearch = q === "" || m.name.toLowerCase().includes(q) || (m.description.zh || m.description.en).toLowerCase().includes(q);
439
661
  return matchSearch && (category === null || m.categories.includes(category));
440
662
  });
663
+ const shown = filtered.slice(0, 200);
441
664
  const descOf = (m) => localeId === "en" ? m.description.en || m.description.zh : m.description.zh || m.description.en;
442
665
  const install = (m) => {
443
666
  setBusy(m.name);
444
667
  void rpc("install", { spec: m.spec }).then(
445
668
  (v) => {
446
- if (v !== true) throw new Error(t("installNotApplied"));
669
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
670
+ if (v !== true && durationMs === void 0) throw new Error(t("installNotApplied"));
447
671
  setBusy(null);
448
672
  pendingInstall.add(m.spec);
449
673
  for (const key of Object.keys(marketCache)) {
@@ -451,18 +675,44 @@ function MarketView({ category, single, source, search, onCount }) {
451
675
  if (c !== void 0) c.plugins = c.plugins.map((p) => p.name === m.name ? { ...p, installed: true } : p);
452
676
  }
453
677
  setItems((prev) => prev.map((p) => p.name === m.name ? { ...p, installed: true } : p));
454
- showToast(t("installQueued", { n: m.name }), "ok", 5e3);
678
+ showToast(t("installQueued", { n: m.name }) + (durationMs !== void 0 ? `\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
455
679
  },
456
680
  (e) => {
457
681
  setBusy(null);
458
- showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 8e3);
682
+ showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
683
+ }
684
+ );
685
+ };
686
+ const showScreenshot = (m) => {
687
+ if (shotBusy !== null) return;
688
+ setShotBusy(m.name);
689
+ void rpc("screenshot", { name: m.name }).then(
690
+ (url) => {
691
+ setShotBusy(null);
692
+ if (typeof url !== "string" || url === "") {
693
+ showToast(t("noScreenshot"), "error", 6e3);
694
+ return;
695
+ }
696
+ setShotName(m.name);
697
+ setShotUrl(url);
698
+ setShotZoom(false);
699
+ },
700
+ () => {
701
+ setShotBusy(null);
702
+ showToast(t("screenshotFail"), "error", 6e3);
459
703
  }
460
704
  );
461
705
  };
462
706
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
463
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-grid ${single ? "single" : "double"}`, children: filtered.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
707
+ filtered.length > 200 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-limit", children: t("marketTooMany", { n: shown.length, m: filtered.length }) }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-grid ${single ? "single" : "double"}`, children: shown.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
464
709
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
465
710
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: m.name }),
711
+ m.score !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-tag", title: `${m.score.total}${m.score.explanation !== "" ? `\uFF1A${m.score.explanation}` : ""}`, children: [
712
+ t("scoreLabel"),
713
+ " ",
714
+ m.score.total
715
+ ] }),
466
716
  m.stars !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
467
717
  "\u2605 ",
468
718
  m.stars
@@ -474,18 +724,49 @@ function MarketView({ category, single, source, search, onCount }) {
474
724
  ] }),
475
725
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: descOf(m) }),
476
726
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
477
- m.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
727
+ m.categories.slice(0, 4).map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
478
728
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-iconbtn", title: t("screenshot"), "aria-label": t("screenshot"), disabled: shotBusy !== null, onClick: () => {
730
+ showScreenshot(m);
731
+ }, children: shotBusy === m.name ? "\u2026" : "\u{1F4F7}" }),
479
732
  m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? t("pendingRestart") : t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
480
733
  install(m);
481
734
  }, children: busy === m.name ? t("installing") : t("install") })
482
735
  ] })
483
736
  ] }, m.name)) }),
484
- !done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) })
737
+ !done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) }),
738
+ shotUrl !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", onClick: () => {
739
+ setShotUrl(null);
740
+ setShotName(null);
741
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", onClick: (e) => {
742
+ e.stopPropagation();
743
+ }, children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: shotName ?? "" }),
746
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: () => {
748
+ setShotUrl(null);
749
+ setShotName(null);
750
+ }, "aria-label": t("close"), children: "\u2715" })
751
+ ] }),
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", style: { alignItems: "center", overflow: shotZoom ? "auto" : "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
753
+ "img",
754
+ {
755
+ src: shotUrl,
756
+ alt: shotName ?? "",
757
+ onClick: () => {
758
+ setShotZoom((v) => !v);
759
+ },
760
+ title: shotZoom ? "\u7F29\u5C0F" : "\u70B9\u51FB\u653E\u5927",
761
+ style: shotZoom ? { maxWidth: "none", maxHeight: "none", width: "auto", borderRadius: 8 } : { maxWidth: "100%", maxHeight: "70vh", borderRadius: 8, objectFit: "contain" }
762
+ }
763
+ ) })
764
+ ] }) })
485
765
  ] });
486
766
  }
487
767
  function UpdatesView({ updates, refresh, updateOne, busy }) {
488
768
  const t = useT();
769
+ useUpdatingVersion();
489
770
  if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
490
771
  if (updates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
491
772
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
@@ -501,11 +782,101 @@ function UpdatesView({ updates, refresh, updateOne, busy }) {
501
782
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
502
783
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
503
784
  updateOne(u.name, u.toVersion);
504
- }, children: busy === u.name || busy === "__all__" ? t("updating") : t("update") })
785
+ }, children: busy === u.name || busy === "__all__" || updatingPlugins.has(u.name) ? t("updating") : t("update") })
505
786
  ] }),
506
787
  u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
507
788
  ] }, u.name)) });
508
789
  }
790
+ function DiagnoseView() {
791
+ const t = useT();
792
+ const [report, setReport] = (0, import_react.useState)(null);
793
+ const [error, setError] = (0, import_react.useState)(null);
794
+ (0, import_react.useEffect)(() => {
795
+ let alive = true;
796
+ void rpc("diagnostics").then(
797
+ (v) => {
798
+ if (alive) setReport(v);
799
+ },
800
+ (e) => {
801
+ if (alive) setError(e instanceof Error ? e.message : String(e));
802
+ }
803
+ );
804
+ return () => {
805
+ alive = false;
806
+ };
807
+ }, []);
808
+ const textOf = () => {
809
+ if (report === null) return "no diagnostics";
810
+ const lines = [
811
+ "=== dsh-plugin-center diagnostics ===",
812
+ `dsh: ${report.dshVersion}`,
813
+ `node: ${report.node}`,
814
+ `profile: ${report.baseUrl}`,
815
+ `installed (${report.installed.length}):`,
816
+ ...report.installed.map((p) => ` ${p.enabled ? "" : "[disabled] "}${p.name}@${p.version ?? "?"} (${p.source}, ${p.fiberPhase ?? "?"})`),
817
+ "patch disabled:",
818
+ ...Object.entries(report.disabled).map(([id, v]) => ` ${id}: ${v ? "disabled" : "force-enabled"}`),
819
+ "pnpm log tail:",
820
+ report.pnpmLogTail
821
+ ];
822
+ return lines.join("\n");
823
+ };
824
+ const exportLog = () => {
825
+ const blob = new Blob([textOf()], { type: "text/plain;charset=utf-8" });
826
+ const url = URL.createObjectURL(blob);
827
+ const a = document.createElement("a");
828
+ a.href = url;
829
+ a.download = `plugin-center-diagnostics-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/gu, "-")}.txt`;
830
+ a.click();
831
+ URL.revokeObjectURL(url);
832
+ };
833
+ const copy = () => {
834
+ void navigator.clipboard?.writeText(textOf()).then(() => {
835
+ showToast(t("diagCopied"), "ok", 3e3);
836
+ }).catch(() => {
837
+ });
838
+ };
839
+ if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
840
+ if (report === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
841
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: exportLog, children: t("diagExport") }),
844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: copy, children: t("diagCopied") })
845
+ ] }),
846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagTitle") }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "dsh" }),
850
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: report.dshVersion })
851
+ ] }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "node" }),
854
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: report.node })
855
+ ] }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
857
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "profile" }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", style: { wordBreak: "break-all" }, children: report.baseUrl })
859
+ ] })
860
+ ] }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagInstalled", { n: report.installed.length }) }),
862
+ report.installed.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: p.name }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
865
+ "v",
866
+ p.version ?? "?"
867
+ ] }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: p.source }),
869
+ !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
870
+ ] }) }, p.name)),
871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagDisabled") }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: Object.keys(report.disabled).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2014" }) : Object.entries(report.disabled).map(([id, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: id }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: v ? "disabled" : "enabled" })
875
+ ] }, id)) }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagPnpmLog") }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "pc-desc", style: { whiteSpace: "pre-wrap", fontSize: 11 }, children: report.pnpmLogTail === "" ? "\u2014" : report.pnpmLogTail })
878
+ ] });
879
+ }
509
880
  function CenterPanel({ variant = "section" }) {
510
881
  const t = useT();
511
882
  const [view, setView] = (0, import_react.useState)("installed");
@@ -516,12 +887,65 @@ function CenterPanel({ variant = "section" }) {
516
887
  const [installedCategory, setInstalledCategory] = (0, import_react.useState)(null);
517
888
  const [marketSearch, setMarketSearch] = (0, import_react.useState)("");
518
889
  const [installedSource, setInstalledSource] = (0, import_react.useState)(null);
519
- const [installedCount, setInstalledCount] = (0, import_react.useState)(0);
520
- const [failedCount, setFailedCount] = (0, import_react.useState)(0);
521
- const [marketCount, setMarketCount] = (0, import_react.useState)(0);
890
+ const counts = useCounts();
522
891
  const [updates, setUpdates] = (0, import_react.useState)(null);
523
892
  const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
524
893
  const [checking, setChecking] = (0, import_react.useState)(false);
894
+ const [togglingId, setTogglingId] = (0, import_react.useState)(null);
895
+ const [aiQuery, setAiQuery] = (0, import_react.useState)("");
896
+ const [aiBusy, setAiBusy] = (0, import_react.useState)(false);
897
+ const [aiResult, setAiResult] = (0, import_react.useState)(null);
898
+ const [aiError, setAiError] = (0, import_react.useState)(null);
899
+ const handleToggle = (p) => {
900
+ if (togglingId !== null) return;
901
+ const id = p.entryId;
902
+ const nextEnabled = !p.enabled;
903
+ const wasPending = pendingToggles.has(id);
904
+ console.log("[plugin-center] toggle", { id, fromEnabled: p.enabled, toEnabled: nextEnabled, wasPending });
905
+ setTogglingId(p.name);
906
+ void rpc("toggle", { id, disabled: !nextEnabled }).then(
907
+ (v) => {
908
+ setTogglingId(null);
909
+ const nowDisabled = typeof v === "object" && v !== null ? v.nowDisabled : null;
910
+ console.log("[plugin-center] toggle result", { id, nowDisabled });
911
+ if (nowDisabled === true && !wasPending) {
912
+ setPendingToggle(id, "disable");
913
+ showToast(t("disabledOk", { n: p.name }), "ok", 5e3);
914
+ } else if (nowDisabled === false && !wasPending) {
915
+ setPendingToggle(id, "enable");
916
+ showToast(t("enabledOk", { n: p.name }), "ok", 5e3);
917
+ } else if (wasPending) {
918
+ setPendingToggle(id, null);
919
+ showToast(nowDisabled === true ? t("revertedDisable", { n: p.name }) : t("revertedEnable", { n: p.name }), "ok", 5e3);
920
+ }
921
+ if (installedCache !== null) {
922
+ installedCache = installedCache.map((item) => item.entryId === id ? { ...item, enabled: nextEnabled } : item);
923
+ }
924
+ },
925
+ (e) => {
926
+ setTogglingId(null);
927
+ console.log("[plugin-center] toggle failed", { id, error: e instanceof Error ? e.message : String(e) });
928
+ showToast(t("toggleFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
929
+ }
930
+ );
931
+ };
932
+ const askAi = () => {
933
+ const q = aiQuery.trim();
934
+ if (q === "" || aiBusy) return;
935
+ setAiBusy(true);
936
+ setAiResult(null);
937
+ setAiError(null);
938
+ void rpc("suggest", { query: q }).then(
939
+ (v) => {
940
+ setAiBusy(false);
941
+ setAiResult(v);
942
+ },
943
+ (e) => {
944
+ setAiBusy(false);
945
+ setAiError(e instanceof Error ? e.message : String(e));
946
+ }
947
+ );
948
+ };
525
949
  const refreshUpdates = (0, import_react.useCallback)((silent = false) => {
526
950
  setChecking(true);
527
951
  void rpc("checkUpdates", { since: new Date(Date.now() - 30 * 864e5).toISOString() }).then(
@@ -547,8 +971,8 @@ function CenterPanel({ variant = "section" }) {
547
971
  void rpc("listInstalled").then(
548
972
  (v) => {
549
973
  const items = v;
550
- setInstalledCount(items.length);
551
- setFailedCount(items.filter((p) => p.fiberPhase === "failed").length);
974
+ installedCache = items;
975
+ setCounts({ installed: items.length, failed: items.filter((p) => p.fiberPhase === "failed").length });
552
976
  },
553
977
  () => {
554
978
  }
@@ -557,40 +981,51 @@ function CenterPanel({ variant = "section" }) {
557
981
  }, [refreshUpdates]);
558
982
  (0, import_react.useEffect)(() => {
559
983
  let alive = true;
560
- let timer = null;
561
- const poll = async () => {
984
+ const timers = [];
985
+ const poll = async (src) => {
562
986
  if (!alive) return;
563
987
  try {
564
- const r = await rpc("listMarket", { source: "all" });
988
+ const r = await rpc("listMarket", { source: src });
565
989
  if (!alive) return;
566
- marketCache.all = { plugins: r.plugins, done: r.done };
567
- setMarketCount(r.plugins.length);
568
- if (!r.done) timer = setTimeout(() => {
569
- void poll();
570
- }, 5e3);
990
+ marketCache[src] = { plugins: r.plugins, done: r.done };
991
+ if (src === "all") setCounts({ market: r.plugins.length });
992
+ if (src === "dsh-market") setCounts({ dshMarket: r.plugins.length });
993
+ if (!r.done) timers.push(setTimeout(() => {
994
+ void poll(src);
995
+ }, 5e3));
571
996
  } catch {
572
- if (alive) timer = setTimeout(() => {
573
- void poll();
574
- }, 15e3);
997
+ if (alive) timers.push(setTimeout(() => {
998
+ void poll(src);
999
+ }, 15e3));
575
1000
  }
576
1001
  };
577
- void poll();
1002
+ void poll("all");
1003
+ void poll("awesome");
1004
+ void poll("oh-my-dsh");
578
1005
  return () => {
579
1006
  alive = false;
580
- if (timer !== null) clearTimeout(timer);
1007
+ for (const timer of timers) clearTimeout(timer);
581
1008
  };
582
1009
  }, []);
1010
+ const handleMarketCount = (0, import_react.useCallback)((n) => {
1011
+ setCounts({ market: n });
1012
+ }, []);
583
1013
  const updateOne = (name, version) => {
584
1014
  setBusyUpdate(name);
1015
+ setUpdating(name, true);
585
1016
  void rpc("update", { name, version }).then(
586
1017
  (v) => {
587
- if (v !== true) throw new Error(t("updateNotApplied"));
1018
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
1019
+ if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
1020
+ setUpdating(name, false);
588
1021
  setBusyUpdate(null);
589
- showToast(t("updatedOne", { n: name }), "ok", 5e3);
1022
+ showToast(t("updatedOne", { n: name }) + (durationMs !== void 0 ? `\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
1023
+ refreshUpdates(true);
590
1024
  },
591
1025
  (e) => {
1026
+ setUpdating(name, false);
592
1027
  setBusyUpdate(null);
593
- showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 8e3);
1028
+ showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
594
1029
  }
595
1030
  );
596
1031
  };
@@ -601,20 +1036,24 @@ function CenterPanel({ variant = "section" }) {
601
1036
  const okNames = [];
602
1037
  const failures = [];
603
1038
  for (const u of updates) {
1039
+ setUpdating(u.name, true);
604
1040
  try {
605
1041
  const v = await rpc("update", { name: u.name, version: u.toVersion });
606
- if (v !== true) throw new Error(t("updateNotApplied"));
1042
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
1043
+ if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
607
1044
  okCount++;
608
- okNames.push(u.name);
1045
+ okNames.push(durationMs !== void 0 ? `${u.name}\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : u.name);
609
1046
  } catch (e) {
610
1047
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
1048
+ } finally {
1049
+ setUpdating(u.name, false);
611
1050
  }
612
1051
  }
613
1052
  setBusyUpdate(null);
614
1053
  if (failures.length === 0) {
615
1054
  showToast(t("updatedMany", { n: okCount }) + `\uFF08${okNames.join("\u3001")}\uFF09`, "ok", 6e3);
616
1055
  } else {
617
- showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 8e3);
1056
+ showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 15e3);
618
1057
  }
619
1058
  refreshUpdates();
620
1059
  };
@@ -625,13 +1064,14 @@ function CenterPanel({ variant = "section" }) {
625
1064
  count !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-count", children: count })
626
1065
  ] }, v);
627
1066
  const tabs = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-tabs", children: [
628
- tab("installed", t("tabInstalled"), installedCount),
629
- tab("market", t("tabMarket"), marketCount),
630
- tab("updates", t("tabUpdates"), updates?.length ?? 0)
1067
+ tab("installed", t("tabInstalled"), counts.installed),
1068
+ tab("market", t("tabMarket"), counts.market),
1069
+ tab("updates", t("tabUpdates"), updates?.length ?? 0),
1070
+ tab("diagnose", t("tabDiagnose"), null)
631
1071
  ] });
632
1072
  const head = (showTitle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-head", children: [
633
1073
  showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
634
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: installedCount, b: updates?.length ?? 0, c: failedCount }) }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: counts.installed, b: updates?.length ?? 0, c: counts.failed }) }),
635
1075
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
636
1076
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", disabled: checking, onClick: () => {
637
1077
  refreshUpdates();
@@ -640,61 +1080,132 @@ function CenterPanel({ variant = "section" }) {
640
1080
  void updateAll();
641
1081
  }, children: t("updateAll", { n: updates?.length ?? 0 }) })
642
1082
  ] });
643
- const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
644
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
645
- setSearch(e.target.value);
646
- }, placeholder: t("searchInstalled") }),
647
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
648
- setInstalledSource(e.target.value === "" ? null : e.target.value);
649
- }, children: [
650
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
651
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
652
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
653
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
654
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
1083
+ const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-filter", children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar pc-toolbar-main", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
1086
+ setSearch(e.target.value);
1087
+ }, placeholder: t("searchInstalled") }),
1088
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
1089
+ setInstalledSource(e.target.value === "" ? null : e.target.value);
1090
+ }, children: [
1091
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
1092
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
1093
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
1094
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
1096
+ ] })
655
1097
  ] }),
656
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
657
- setInstalledCategory(null);
658
- }, children: t("all") }),
659
- CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
660
- setInstalledCategory(c);
661
- }, children: c }, c))
1098
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-catbar", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
1100
+ setInstalledCategory(null);
1101
+ }, children: t("all") }),
1102
+ CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
1103
+ setInstalledCategory(c);
1104
+ }, children: c }, c))
1105
+ ] })
662
1106
  ] }) : null;
663
- const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
664
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: marketSearch, onChange: (e) => {
665
- setMarketSearch(e.target.value);
666
- }, placeholder: t("searchMarket") }),
667
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
668
- setSource(e.target.value);
669
- setCategory(null);
670
- }, children: [
671
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
672
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
673
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
1107
+ const aiBar = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: "none" }, children: [
1108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-ai-row", children: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1110
+ "input",
1111
+ {
1112
+ className: "pc-search",
1113
+ style: { flex: 1 },
1114
+ value: aiQuery,
1115
+ onChange: (e) => {
1116
+ setAiQuery(e.target.value);
1117
+ },
1118
+ onKeyDown: (e) => {
1119
+ if (e.key === "Enter") askAi();
1120
+ },
1121
+ placeholder: t("aiPlaceholder")
1122
+ }
1123
+ ),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: aiBusy || aiQuery.trim() === "", onClick: askAi, children: aiBusy ? t("aiAsking") : t("aiAsk") })
674
1125
  ] }),
675
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
676
- setCategory(null);
677
- }, children: t("all") }),
678
- CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
679
- setCategory(c);
680
- }, children: c }, c)),
681
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
682
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
683
- setSingle((v) => !v);
684
- }, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
685
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
686
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
687
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
688
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "5", height: "5", rx: "1" })
689
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
690
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "12", height: "5", rx: "1" }),
691
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "12", height: "5", rx: "1" })
692
- ] }) })
1126
+ aiError !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", style: { color: "var(--dsw-alias-state-error-primary)" }, children: t("aiFail", { e: aiError }) }),
1127
+ aiResult !== null && aiResult.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-grid single", children: aiResult.map((item) => {
1128
+ const plugin = marketCache.all?.plugins.find((p) => p.spec === item.name || p.name === item.name);
1129
+ const installed = plugin?.installed === true || (installedCache ?? []).some((p) => p.name === item.name);
1130
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
1131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: item.name }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1134
+ plugin !== void 0 && plugin.stars !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
1135
+ "\u2605 ",
1136
+ plugin.stars
1137
+ ] }),
1138
+ installed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1139
+ "button",
1140
+ {
1141
+ className: "pc-btn primary",
1142
+ onClick: () => {
1143
+ if (plugin === void 0) {
1144
+ showToast(t("installFailed", { e: "\u672A\u5728\u5E02\u573A\u76EE\u5F55\u4E2D\u627E\u5230\u8BE5\u63D2\u4EF6\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5" }), "error", 8e3);
1145
+ return;
1146
+ }
1147
+ void rpc("install", { spec: plugin.spec }).then(
1148
+ () => {
1149
+ pendingInstall.add(plugin.spec);
1150
+ showToast(t("installQueued", { n: item.name }), "ok", 5e3);
1151
+ },
1152
+ (e) => showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3)
1153
+ );
1154
+ },
1155
+ children: t("install")
1156
+ }
1157
+ )
1158
+ ] }),
1159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: item.reason })
1160
+ ] }, item.name);
1161
+ }) })
1162
+ ] });
1163
+ const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-filter", children: [
1164
+ aiBar,
1165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar pc-toolbar-main", children: [
1166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", style: { flex: 1, minWidth: 120 }, value: marketSearch, onChange: (e) => {
1167
+ setMarketSearch(e.target.value);
1168
+ }, placeholder: t("searchMarket") }),
1169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
1170
+ setSource(e.target.value);
1171
+ setCategory(null);
1172
+ }, children: [
1173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
1174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: "dsh-market", children: [
1176
+ "dsh-market\uFF08",
1177
+ counts.dshMarket,
1178
+ "\uFF09"
1179
+ ] }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
1181
+ ] }),
1182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
1184
+ setSingle((v) => !v);
1185
+ }, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "5", height: "5", rx: "1" })
1190
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "12", height: "5", rx: "1" }),
1192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "12", height: "5", rx: "1" })
1193
+ ] }) })
1194
+ ] }),
1195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-catbar", children: [
1196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
1197
+ setCategory(null);
1198
+ }, children: t("all") }),
1199
+ CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
1200
+ setCategory(c);
1201
+ }, children: c }, c))
1202
+ ] })
693
1203
  ] }) : null;
694
1204
  const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
695
- view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource }),
696
- view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: setMarketCount }),
697
- view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate })
1205
+ view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource, onToggle: handleToggle, togglingId }),
1206
+ view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: handleMarketCount }),
1207
+ view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate }),
1208
+ view === "diagnose" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiagnoseView, {})
698
1209
  ] });
699
1210
  if (variant === "overlay") {
700
1211
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [
@@ -730,19 +1241,25 @@ function OverlayPanel() {
730
1241
  const t = useT();
731
1242
  const open = useOverlayOpen();
732
1243
  if (!open) return null;
733
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": t("title"), children: [
734
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
735
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
736
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
737
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": t("close"), children: "\u2715" })
738
- ] }),
739
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CenterPanel, { variant: "overlay" }) })
740
- ] }) });
1244
+ return (
1245
+ // 0.1.7:点遮罩关闭——overlay 背景点击即 closeOverlay;面板内点击
1246
+ // stopPropagation 不冒泡到遮罩(面板内部交互不受影响)。
1247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", onClick: closeOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": t("title"), onClick: (e) => {
1248
+ e.stopPropagation();
1249
+ }, children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
1252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": t("close"), children: "\u2715" })
1254
+ ] }),
1255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CenterPanel, { variant: "overlay" }) })
1256
+ ] }) })
1257
+ );
741
1258
  }
742
1259
  function Toast() {
743
1260
  const t = useToast();
744
1261
  if (t === null) return null;
745
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message });
1262
+ return (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message }), document.body);
746
1263
  }
747
1264
  function WhatsNewDialog() {
748
1265
  const t = useT();
@@ -798,8 +1315,26 @@ function WhatsNewDialog() {
798
1315
  ] }) });
799
1316
  }
800
1317
  var inject = ["slots", "connection"];
1318
+ var GLOBAL_KEYS = ["__pluginCenterOpen", "__pluginCenterToggle", "__pluginCenterClose"];
1319
+ function installGlobals() {
1320
+ const w = window;
1321
+ w.__pluginCenterOpen = openOverlay;
1322
+ w.__pluginCenterToggle = toggleOverlay;
1323
+ w.__pluginCenterClose = closeOverlay;
1324
+ w.__pluginCenterGlobalsInstalled = true;
1325
+ }
1326
+ function cleanupGlobals() {
1327
+ const w = window;
1328
+ for (const key of GLOBAL_KEYS) delete w[key];
1329
+ delete w.__pluginCenterGlobalsInstalled;
1330
+ }
801
1331
  function apply(ctx) {
802
1332
  injectCss();
1333
+ ctx.effect?.(() => registerSettingsNavIcon(() => STRINGS[localeId].title), "dsh-plugin-center: settings navigation icon");
1334
+ if (window.__pluginCenterGlobalsInstalled !== true) {
1335
+ installGlobals();
1336
+ window.addEventListener("unload", cleanupGlobals);
1337
+ }
803
1338
  rpc = async (endpoint, payload = {}) => {
804
1339
  const result = await ctx.connection.rpc.call("/plugin-center", endpoint, payload);
805
1340
  if (result.ok) return result.value;