dshmarket 1.36.0 → 1.38.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/client.js CHANGED
@@ -135,6 +135,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
135
135
  notesNone: "该插件未提供更新说明。",
136
136
  notesLoadFail: "暂时读不到更新说明,稍后再试。",
137
137
  restore: "恢复",
138
+ restoreOnline: "换用线上版本",
138
139
  restoreHint: "会卸载本地版本,重新安装线上版本,并保持更新检测。无法回退,请二次确认。",
139
140
  restoreContinue: "继续更新",
140
141
  restoreNoCatalog: "精选目录里没有这个插件,无法恢复到线上版本。可以卸载本地版,或继续用本地开发。",
@@ -603,6 +604,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
603
604
  notesNone: "This plugin does not publish update notes.",
604
605
  notesLoadFail: "Update notes are unavailable right now — try again later.",
605
606
  restore: "Restore",
607
+ restoreOnline: "Use online version",
606
608
  restoreHint: "This will uninstall the local version, reinstall the catalog version, and keep update checks. This cannot be undone — please confirm again.",
607
609
  restoreContinue: "Continue update",
608
610
  restoreNoCatalog: "This plugin is not in the curated catalog, so it cannot be restored. Uninstall the local copy, or keep developing it locally.",
@@ -4320,6 +4322,71 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
4320
4322
  });
4321
4323
  }
4322
4324
  //#endregion
4325
+ //#region src/client/self-check.ts
4326
+ /**
4327
+ * What the browser can see about the market that the server cannot.
4328
+ *
4329
+ * This exists because of a pattern, not a hypothesis. #293 ("市场显示空白")
4330
+ * and #384 ("lightbox cannot be closed") are both reproducible for their
4331
+ * reporters and for nobody else, on clean profiles, across host versions.
4332
+ * Both investigations then stalled in the same place: I asked the reporter to
4333
+ * open a console and paste the result of an expression. That costs a day per
4334
+ * question, asks a non-developer to run code from a stranger, and is the one
4335
+ * step where a report goes quiet.
4336
+ *
4337
+ * Meanwhile the exported log — which the issue template already asks for, and
4338
+ * which reporters do send — describes only the server: dependencies, bundle
4339
+ * resolution, events. Everything it says was already true of a machine where
4340
+ * the bug does not happen. The interesting half was in a page nobody looked
4341
+ * at.
4342
+ *
4343
+ * So the browser answers the questions I have actually had to ask, in the
4344
+ * artefact people already know how to produce. Nothing here diagnoses
4345
+ * anything on its own; it is evidence, collected before it is needed.
4346
+ */
4347
+ /**
4348
+ * How many times this module has been evaluated in this page.
4349
+ *
4350
+ * Module scope runs once per module instance, so >1 means the market's client
4351
+ * bundle was loaded twice — two React copies, two of every module singleton,
4352
+ * two portal containers. That is a specific hypothesis for #384: two React
4353
+ * roots each attach delegated listeners to their own portal container, and a
4354
+ * click lands on whichever container is last in `body` while the handlers
4355
+ * live on the other one, so the buttons look present and do nothing.
4356
+ *
4357
+ * Counted on `window` rather than in a module variable for the obvious
4358
+ * reason: a module variable would be duplicated along with everything else
4359
+ * and each copy would confidently report 1.
4360
+ */
4361
+ const globals = globalThis;
4362
+ globals.__dshmarketClientLoads = (globals.__dshmarketClientLoads ?? 0) + 1;
4363
+ /** Whether `value` looks like a browser environment worth inspecting. */
4364
+ const hasDom = () => typeof document !== "undefined" && document.body !== null;
4365
+ /**
4366
+ * The browser-side section of the exported log.
4367
+ *
4368
+ * Deliberately facts, not verdicts. "portal containers: 2" is something a
4369
+ * reporter can paste without judging it, and something I can act on; "your
4370
+ * bundle is double-loaded" would be a guess printed in the user's face, and
4371
+ * wrong the first time a host legitimately renders two markets.
4372
+ * @returns the lines to append, or an empty array outside a browser.
4373
+ */
4374
+ function clientDiagnostics() {
4375
+ if (!hasDom()) return [];
4376
+ const portals = document.querySelectorAll("[data-dsh-market-portal]");
4377
+ const roots = document.querySelectorAll("[data-dsh-market-root]");
4378
+ const last = portals.length > 0 ? portals[portals.length - 1] : null;
4379
+ return [
4380
+ `client bundle evaluations: ${String(globals.__dshmarketClientLoads ?? 0)}`,
4381
+ `market roots in the document: ${String(roots.length)}`,
4382
+ `portal containers: ${String(portals.length)}` + (last === null ? "" : ` (last one is body's last child: ${String(last === document.body.lastElementChild)})`),
4383
+ `plugin cards rendered: ${String(document.querySelectorAll("[data-dsh-market-root] [class*=\"_card\"]").length)}`,
4384
+ `document baseURI: ${document.baseURI}`,
4385
+ `page URL: ${location.origin}${location.pathname}`,
4386
+ `user agent: ${navigator.userAgent}`
4387
+ ];
4388
+ }
4389
+ //#endregion
4323
4390
  //#region src/client/MarketSection.tsx
4324
4391
  /**
4325
4392
  * The Market settings section: Discover / Themes / Installed tabs over the
@@ -5404,6 +5471,21 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
5404
5471
  const [qThemes, setQThemes] = (0, react.useState)("");
5405
5472
  const [qInstalled, setQInstalled] = (0, react.useState)("");
5406
5473
  const [cat, setCat] = (0, react.useState)("all");
5474
+ (0, react.useEffect)(() => {
5475
+ const target = props.preferredSubsectionId;
5476
+ if (target === void 0) return;
5477
+ const separator = target.indexOf(":");
5478
+ const kind = separator === -1 ? target : target.slice(0, separator);
5479
+ const value = separator === -1 ? "" : target.slice(separator + 1);
5480
+ if (kind === "installed") {
5481
+ setTab("installed");
5482
+ setQInstalled(value);
5483
+ } else if (kind === "discover") {
5484
+ setTab("discover");
5485
+ setCat("all");
5486
+ setQ(value);
5487
+ }
5488
+ }, [props.preferredSubsectionId]);
5407
5489
  const [confirming, setConfirming] = (0, react.useState)(null);
5408
5490
  /** The plugin whose comment thread is open, or null. */
5409
5491
  const [commentsFor, setCommentsFor] = (0, react.useState)(null);
@@ -5475,7 +5557,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
5475
5557
  setExportState("busy");
5476
5558
  fetch(api("/dsh-market/logs")).then(async (res) => {
5477
5559
  if (!res.ok) throw new Error(`HTTP ${String(res.status)}`);
5478
- const blob = await res.blob();
5560
+ const serverText = await res.text();
5561
+ const browser = clientDiagnostics();
5562
+ const blob = new Blob([serverText, ...browser.length > 0 ? [
5563
+ "## browser\n",
5564
+ browser.join("\n"),
5565
+ "\n"
5566
+ ] : []], { type: "text/plain;charset=utf-8" });
5479
5567
  const url = URL.createObjectURL(blob);
5480
5568
  const anchor = document.createElement("a");
5481
5569
  anchor.href = url;
@@ -6392,7 +6480,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6392
6480
  });
6393
6481
  setUpdateNotes(null);
6394
6482
  setNotesState("loading");
6395
- fetch(`/dsh-market/changelog?name=${encodeURIComponent(name)}`).then((res) => res.json()).then((body) => {
6483
+ fetch(`${api("/dsh-market/changelog")}?name=${encodeURIComponent(name)}`).then((res) => res.json()).then((body) => {
6396
6484
  setUpdateNotes(body);
6397
6485
  setNotesState("ready");
6398
6486
  }).catch(() => setNotesState("fail"));
@@ -6427,7 +6515,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6427
6515
  /** Write (or clear, when empty) this plugin's note. */
6428
6516
  const saveNote = (0, react.useCallback)((name, text) => {
6429
6517
  setNotingName(null);
6430
- fetch("/dsh-market/note", {
6518
+ fetch(api("/dsh-market/note"), {
6431
6519
  method: "POST",
6432
6520
  headers: { "content-type": "application/json" },
6433
6521
  body: JSON.stringify({
@@ -6458,7 +6546,9 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6458
6546
  }))).then(({ status, body }) => {
6459
6547
  if (status === 200 && body.ok) {
6460
6548
  if (!body.hot) setRemovedCount((n) => n + 1);
6461
- clearPendingRefresh(name);
6549
+ const neverLoadedHere = hotNames.includes(name) || refreshNames.includes(name);
6550
+ if (body.refresh === true && !neverLoadedHere) setRefreshNames((names) => names.includes(name) ? names : names.concat(name));
6551
+ else clearPendingRefresh(name);
6462
6552
  refreshInstalled();
6463
6553
  } else {
6464
6554
  if (body.cancelled === true) {
@@ -6477,7 +6567,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6477
6567
  setInstallError((text(body.error) || humanOutput(text(body.stderr)) || "error").trim().slice(-600));
6478
6568
  }
6479
6569
  }).catch((error) => setInstallError(String(error))).finally(() => setRemovingName(null));
6480
- }, [refreshInstalled]);
6570
+ }, [
6571
+ refreshInstalled,
6572
+ hotNames,
6573
+ refreshNames
6574
+ ]);
6481
6575
  /** Live enable/disable of one installed plugin (#60). `reload` opts the
6482
6576
  * card-level theme flow into a page refresh so the visual result lands
6483
6577
  * immediately (mirrors the use-skin reload on activate). */
@@ -6661,10 +6755,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6661
6755
  });
6662
6756
  }, [doGroupAction, groups]);
6663
6757
  const selfName = installed["dshmarket"] !== void 0 ? "dshmarket" : "dsh-market";
6664
- const updatableNames = Object.keys(installed).filter((name) => name !== selfName && !updatedNames.includes(name) && updates[name] && updates[name].updateAvailable);
6758
+ const batchUpdatableNames = Object.keys(installed).filter((name) => name !== selfName && !updatedNames.includes(name) && updates[name] && updates[name].updateAvailable).filter((name) => updates[name]?.restoreRequired !== true);
6665
6759
  const installedOtherCount = Object.keys(installed).filter((name) => name !== selfName).length;
6666
6760
  const doUpdateAll = (0, react.useCallback)(() => {
6667
- const names = updatableNames.slice();
6761
+ const names = batchUpdatableNames.slice();
6668
6762
  setUpdatingAll(true);
6669
6763
  const next = () => {
6670
6764
  const name = names.shift();
@@ -6675,7 +6769,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
6675
6769
  doUpdate(name).then(next, next);
6676
6770
  };
6677
6771
  next();
6678
- }, [updatableNames, doUpdate]);
6772
+ }, [batchUpdatableNames, doUpdate]);
6679
6773
  const finishRestore = (0, react.useCallback)((body) => {
6680
6774
  const errors = Array.isArray(body.errors) ? body.errors : [];
6681
6775
  const unportable = Array.isArray(body.unportable) ? body.unportable : [];
@@ -7433,18 +7527,20 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
7433
7527
  }),
7434
7528
  (() => {
7435
7529
  const self = installed["dshmarket"] !== void 0 ? "dshmarket" : "dsh-market";
7436
- return updates[self] && updates[self].updateAvailable && !updatedNames.includes(self) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7530
+ const status = updates[self];
7531
+ return status && status.updateAvailable && !updatedNames.includes(self) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7437
7532
  variant: "primary",
7438
7533
  size: "sm",
7439
7534
  disabled: updatingName !== null || busyUrl !== null,
7440
7535
  onClick: () => {
7441
7536
  setTab("installed");
7442
- doUpdate(self);
7537
+ if (status.restoreRequired === true) askRestore(self);
7538
+ else doUpdate(self);
7443
7539
  },
7444
- children: updatingName === self ? t("updating") : t("marketUpdate")
7540
+ children: updatingName === self ? t("updating") : status.restoreRequired === true ? t("restoreOnline") : t("marketUpdate")
7445
7541
  });
7446
7542
  })(),
7447
- updatableNames.length >= 2 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7543
+ batchUpdatableNames.length >= 2 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7448
7544
  variant: "primary",
7449
7545
  size: "sm",
7450
7546
  disabled: updatingAll || updatingName !== null || busyUrl !== null || removingName !== null,
@@ -7452,7 +7548,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
7452
7548
  setTab("installed");
7453
7549
  doUpdateAll();
7454
7550
  },
7455
- children: updatingAll ? t("updating") : t("updateAll") + " (" + updatableNames.length + ")"
7551
+ children: updatingAll ? t("updating") : t("updateAll") + " (" + batchUpdatableNames.length + ")"
7456
7552
  })
7457
7553
  ]
7458
7554
  }),
@@ -8804,8 +8900,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
8804
8900
  size: "sm",
8805
8901
  className: Market_module_css_default.warnBtn,
8806
8902
  disabled: updatingName !== null,
8807
- onClick: () => doUpdate(name),
8808
- children: t("update")
8903
+ onClick: () => {
8904
+ if (status.restoreRequired === true) askRestore(name);
8905
+ else doUpdate(name);
8906
+ },
8907
+ children: status.restoreRequired === true ? t("restoreOnline") : t("update")
8809
8908
  }) : localDev ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8810
8909
  className: Market_module_css_default.metaTag,
8811
8910
  title: t("linkedDev"),
@@ -8820,7 +8919,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
8820
8919
  className: Market_module_css_default.dangerBtn,
8821
8920
  disabled: true,
8822
8921
  children: t("uninstalling")
8823
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [localDev && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8922
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [localDev && status?.restoreRequired !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
8824
8923
  variant: "outline",
8825
8924
  size: "sm",
8826
8925
  disabled: removingName !== null || busyUrl !== null || updatingName !== null,
@@ -9334,7 +9433,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9334
9433
  return {
9335
9434
  updateAvailable: own.updateAvailable === true,
9336
9435
  latest: own.latest ?? null,
9337
- channelSwitch: own.channelSwitch ?? null
9436
+ channelSwitch: own.channelSwitch ?? null,
9437
+ restoreRequired: own.restoreRequired === true
9338
9438
  };
9339
9439
  }
9340
9440
  /**
@@ -9356,6 +9456,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9356
9456
  const [status, setStatus] = (0, react.useState)(null);
9357
9457
  const [update, setUpdate] = (0, react.useState)(null);
9358
9458
  const [phase, setPhase] = (0, react.useState)("idle");
9459
+ const [restoreConfirming, setRestoreConfirming] = (0, react.useState)(false);
9359
9460
  const [purge, setPurge] = (0, react.useState)(false);
9360
9461
  const [error, setError] = (0, react.useState)(null);
9361
9462
  /**
@@ -9405,6 +9506,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9405
9506
  })).json();
9406
9507
  }, []);
9407
9508
  const onUpdate = (0, react.useCallback)((force = false) => {
9509
+ setRestoreConfirming(false);
9408
9510
  setPhase("working");
9409
9511
  setError(null);
9410
9512
  setStale(false);
@@ -9412,6 +9514,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9412
9514
  try {
9413
9515
  const body = await post(api("/dsh-market/update"), {
9414
9516
  name: "dshmarket",
9517
+ ...update?.restoreRequired === true ? { restore: true } : {},
9415
9518
  ...force ? { force: true } : {}
9416
9519
  });
9417
9520
  if (body.ok === true) setPhase("updated");
@@ -9425,7 +9528,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9425
9528
  setPhase("failed");
9426
9529
  }
9427
9530
  })();
9428
- }, [post, t]);
9531
+ }, [
9532
+ post,
9533
+ t,
9534
+ update?.restoreRequired
9535
+ ]);
9429
9536
  const onRemove = (0, react.useCallback)(() => {
9430
9537
  setPhase("working");
9431
9538
  setError(null);
@@ -9531,13 +9638,26 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9531
9638
  variant: "primary",
9532
9639
  size: "sm",
9533
9640
  disabled: busy,
9534
- onClick: () => onUpdate()
9535
- }, t("setSelfUpdate")) : update?.channelSwitch != null ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
9641
+ onClick: () => {
9642
+ if (update.restoreRequired) setRestoreConfirming(true);
9643
+ else onUpdate();
9644
+ }
9645
+ }, update.restoreRequired ? t("restoreOnline") : t("setSelfUpdate")) : update?.channelSwitch != null ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
9536
9646
  variant: "outline",
9537
9647
  size: "sm",
9538
9648
  disabled: busy,
9539
9649
  onClick: () => onUpdate()
9540
- }, t("setChannelSwitch")) : null) : null, status?.selfManaged === true ? row(t("setChannel"), t(CHANNEL_HINT[status.channel]), (0, react.createElement)("div", { className: Market_module_css_default.setSeg }, (status?.channels ?? ["stable", "beta"]).map((id) => (0, react.createElement)("button", {
9650
+ }, t("setChannelSwitch")) : null) : null, status?.selfManaged === true && restoreConfirming ? (0, react.createElement)("div", { className: Market_module_css_default.setConfirm }, (0, react.createElement)("div", { className: Market_module_css_default.setHint }, t("restoreHint")), (0, react.createElement)("div", { className: Market_module_css_default.setActions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
9651
+ variant: "ghost",
9652
+ size: "sm",
9653
+ onClick: () => {
9654
+ setRestoreConfirming(false);
9655
+ }
9656
+ }, t("setSelfCancel")), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
9657
+ variant: "primary",
9658
+ size: "sm",
9659
+ onClick: () => onUpdate()
9660
+ }, t("restoreContinue")))) : null, status?.selfManaged === true ? row(t("setChannel"), t(CHANNEL_HINT[status.channel]), (0, react.createElement)("div", { className: Market_module_css_default.setSeg }, (status?.channels ?? ["stable", "beta"]).map((id) => (0, react.createElement)("button", {
9541
9661
  key: id,
9542
9662
  type: "button",
9543
9663
  className: status?.channel === id ? `${Market_module_css_default.setSegBtn} ${Market_module_css_default.setSegOn}` : Market_module_css_default.setSegBtn,
@@ -9648,14 +9768,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
9648
9768
  label: () => t("nav"),
9649
9769
  locale: NS,
9650
9770
  inject: () => ({ t })
9651
- }, () => (0, react.createElement)(MarketSection, {
9771
+ }, (ownerProps = {}) => (0, react.createElement)(MarketSection, {
9652
9772
  t,
9653
9773
  locale: ctx.locale,
9654
9774
  theme: ctx.theme,
9655
9775
  themeStore: {
9656
9776
  subscribe: (cb) => ctx.on("theme/change", cb),
9657
9777
  getSnapshot: () => ctx.theme.getTheme()
9658
- }
9778
+ },
9779
+ preferredSubsectionId: ownerProps.preferredSubsectionId
9659
9780
  }));
9660
9781
  if (typeof off === "function") retireSection = off;
9661
9782
  return off;