@xrxs-ai/dsh-xrxs-plugins-store 0.2.5 → 0.2.7

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
@@ -30,10 +30,10 @@ __export(index_exports, {
30
30
  inject: () => inject
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
- var import_react12 = require("react");
33
+ var import_react13 = require("react");
34
34
 
35
35
  // src/client/MarketplaceApp.tsx
36
- var import_react9 = require("react");
36
+ var import_react10 = require("react");
37
37
 
38
38
  // src/client/pages/DeveloperPage.tsx
39
39
  var import_react4 = require("react");
@@ -462,6 +462,13 @@ function formatDate(text) {
462
462
  if (d === null) return text;
463
463
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
464
464
  }
465
+ function formatShortDate(text) {
466
+ const d = parseTime(text);
467
+ if (d === null) return text;
468
+ const now = /* @__PURE__ */ new Date();
469
+ const yearPrefix = d.getFullYear() === now.getFullYear() ? "" : `${String(d.getFullYear() % 100).padStart(2, "0")}-`;
470
+ return `${yearPrefix}${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
471
+ }
465
472
  function formatRelative(text) {
466
473
  const parsed = parseTime(text);
467
474
  if (parsed === null) return text;
@@ -926,14 +933,27 @@ var MARKET_CSS = `
926
933
  font-size: 13px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8f959e);
927
934
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
928
935
  }
936
+ /* \u884C\u5C3E\u5143\u4FE1\u606F\u6309\u56FA\u5B9A\u56DB\u5217\u6392(\u7248\u672C / \u4E0B\u8F7D\u91CF / \u517C\u5BB9\u6027 / \u64CD\u4F5C),\u9010\u5217\u4E0E\u4E0A\u4E0B\u884C\u5BF9\u9F50:
937
+ \u82E5\u7528\u53F3\u5BF9\u9F50 flex,\u672B\u5217(\u6309\u94AE\u6216\u300C\u5DF2\u5B89\u88C5\u300D\u5FBD\u6807)\u5BBD\u5EA6\u4E0D\u540C\u4F1A\u628A\u5DE6\u8FB9\u6240\u6709\u5217\u4E00\u8D77\u63A8\u7740\u5DE6\u53F3\u6D6E\u52A8 */
929
938
  .xps-mk-row-meta {
930
- flex: none; display: flex; align-items: center; gap: 10px;
939
+ flex: none;
940
+ display: grid;
941
+ grid-template-columns: 140px 56px 124px 74px;
942
+ align-items: center;
943
+ justify-items: end;
944
+ column-gap: 8px;
931
945
  font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8f959e);
932
946
  }
947
+ .xps-mk-row-meta > * { min-width: 0; }
933
948
  .xps-mk-row-version {
934
949
  font-variant-numeric: tabular-nums;
935
950
  font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
951
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
936
952
  }
953
+ .xps-mk-row-downloads { font-variant-numeric: tabular-nums; white-space: nowrap; }
954
+ /* \u517C\u5BB9\u6027\u5FBD\u6807\u5217\u5BBD\u56FA\u5B9A:\u8D85\u957F compatRange \u7684\u4E0D\u6362\u884C\u5FBD\u6807\u5217\u5185\u53F3\u5BF9\u9F50,\u6EA2\u51FA\u65F6\u5411\u5DE6\u8BA9\u4F4D(\u4E0D\u628A\u5217\u6491\u5BBD) */
955
+ .xps-mk-row-compat { display: inline-flex; justify-content: flex-end; }
956
+ .xps-mk-row-action { display: inline-flex; justify-content: flex-end; }
937
957
  /* \u56FE\u6807:r11;\u5F69\u8272\u6E10\u53D8\u7531 PluginIcon \u5185\u8054 gradient,\u65E0 hairline */
938
958
  /* \u56FE\u6807:r10(\u4E0E\u5DF2\u88C5\u9875 .xps-row-icon \u7684 40px \u5706\u89D2\u4E00\u81F4) */
939
959
  .xps-mk-icon {
@@ -1042,30 +1062,8 @@ var MARKET_CSS = `
1042
1062
  font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary, #1f2329);
1043
1063
  text-align: right; word-break: break-all;
1044
1064
  }
1045
- .xps-mk-version {
1046
- display: flex; justify-content: space-between; align-items: center; gap: 8px;
1047
- width: 100%; box-sizing: border-box; padding: 4px 8px;
1048
- border: none; border-radius: 8px; background: transparent; cursor: pointer;
1049
- color: var(--dsw-alias-label-primary, #1f2329);
1050
- font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1051
- font-size: 12px; line-height: 18px; text-align: left;
1052
- transition: background-color 0.15s ease;
1053
- }
1054
- .xps-mk-version:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(31, 35, 41, 0.06)); }
1055
- .xps-mk-version:active { background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1)); }
1056
- .xps-mk-version:focus-visible {
1057
- outline: 2px solid var(--dsw-alias-brand-primary, #3370ff);
1058
- outline-offset: -2px;
1059
- }
1060
- .xps-mk-version-active, .xps-mk-version-active:hover {
1061
- background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1));
1062
- color: var(--dsw-alias-label-primary, #1f2329);
1063
- }
1064
- .xps-mk-version-date {
1065
- flex: none; font-size: 11px; line-height: 17px;
1066
- font-family: inherit;
1067
- color: var(--dsw-alias-label-tertiary, #8f959e);
1068
- }
1065
+ /* \u8BE6\u60C5\u9875\u7248\u672C\u5386\u53F2\u4E0B\u62C9:\u5360\u6EE1\u5361\u7247\u5BBD(.xps-mk-select \u9ED8\u8BA4 flex:none \u53EA\u9002\u914D\u5DE5\u5177\u680F) */
1066
+ .xps-mk-version-select { display: block; width: 100%; margin-top: 8px; }
1069
1067
 
1070
1068
  /* README markdown \u767D\u540D\u5355\u6E32\u67D3 */
1071
1069
  .xps-md {
@@ -1094,6 +1092,17 @@ var MARKET_CSS = `
1094
1092
  cursor: pointer; text-decoration: none;
1095
1093
  }
1096
1094
  .xps-md-link:hover { text-decoration: underline; }
1095
+ /* README \u56FE\u7247:\u4E0D\u8D85\u51FA\u6B63\u6587\u5BBD(\u5FBD\u6807\u7C7B\u5C0F\u56FE\u4FDD\u6301\u539F\u59CB\u5C3A\u5BF8);\u52A0\u8F7D\u5931\u8D25\u964D\u7EA7\u4E3A\u7070\u8272\u6587\u672C */
1096
+ .xps-md-img {
1097
+ max-width: 100%; height: auto;
1098
+ border-radius: 6px; vertical-align: middle;
1099
+ }
1100
+ .xps-md-img-fallback {
1101
+ display: inline-block; padding: 0 4px;
1102
+ font-size: 12px; color: var(--dsw-alias-label-tertiary, #8f959e);
1103
+ background: var(--dsw-alias-bg-layer-2, #f5f6f7);
1104
+ border-radius: 4px;
1105
+ }
1097
1106
  .xps-md-table-wrap { margin: 8px 0; overflow-x: auto; }
1098
1107
  .xps-md-table {
1099
1108
  border-collapse: collapse; width: 100%; font-size: 12px; line-height: 1.6;
@@ -1125,8 +1134,7 @@ var MARKET_CSS = `
1125
1134
  .xps-mk-chip,
1126
1135
  .xps-mk-row,
1127
1136
  .xps-mk-page,
1128
- .xps-mk-select,
1129
- .xps-mk-version {
1137
+ .xps-mk-select {
1130
1138
  transition: none;
1131
1139
  }
1132
1140
  .xps-mk-page:active:not(:disabled) {
@@ -1807,7 +1815,7 @@ function UploadForm(props) {
1807
1815
  ] }),
1808
1816
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { children: [
1809
1817
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { children: "README \u8BF7\u5C3D\u91CF\u5199\u6E05\u695A:" }),
1810
- "README \u662F\u5E02\u573A\u8BE6\u60C5\u9875\u7684\u5168\u90E8\u5C55\u793A\u5185\u5BB9,\u652F\u6301\u6807\u51C6 markdown(\u6807\u9898\u3001\u5217\u8868\u3001\u8868\u683C\u3001\u4EE3\u7801\u5757\u3001\u94FE\u63A5)\u3002\u5EFA\u8BAE\u5305\u542B:\u63D2\u4EF6\u89E3\u51B3\u4EC0\u4E48\u95EE\u9898\u3001\u5982\u4F55\u5B89\u88C5\u542F\u7528\u3001\u5404\u529F\u80FD\u600E\u4E48\u7528\u3001\u6709\u54EA\u4E9B\u914D\u7F6E\u9879\u4E0E\u6CE8\u610F\u4E8B\u9879 \u2014\u2014 \u8BF4\u660E\u8D8A\u5B8C\u6574,\u8D8A\u5BB9\u6613\u901A\u8FC7\u5BA1\u6838,\u4E5F\u8D8A\u5BB9\u6613\u88AB\u4F7F\u7528\u3002"
1818
+ "README \u662F\u5E02\u573A\u8BE6\u60C5\u9875\u7684\u5168\u90E8\u5C55\u793A\u5185\u5BB9,\u652F\u6301\u6807\u51C6 markdown(\u6807\u9898\u3001\u5217\u8868\u3001\u8868\u683C\u3001\u4EE3\u7801\u5757\u3001\u94FE\u63A5\u3001\u56FE\u7247 \u2014\u2014 \u56FE\u7247\u4EC5\u652F\u6301 http/https \u5916\u94FE,\u4E0D\u652F\u6301\u76F8\u5BF9\u8DEF\u5F84)\u3002\u5EFA\u8BAE\u5305\u542B:\u63D2\u4EF6\u89E3\u51B3\u4EC0\u4E48\u95EE\u9898\u3001\u5982\u4F55\u5B89\u88C5\u542F\u7528\u3001\u5404\u529F\u80FD\u600E\u4E48\u7528\u3001\u6709\u54EA\u4E9B\u914D\u7F6E\u9879\u4E0E\u6CE8\u610F\u4E8B\u9879 \u2014\u2014 \u8BF4\u660E\u8D8A\u5B8C\u6574,\u8D8A\u5BB9\u6613\u901A\u8FC7\u5BA1\u6838,\u4E5F\u8D8A\u5BB9\u6613\u88AB\u4F7F\u7528\u3002"
1811
1819
  ] }),
1812
1820
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { children: [
1813
1821
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { children: "\u4EA7\u7269\u5305(*.tgz):" }),
@@ -2267,6 +2275,12 @@ function ConfirmDialog3(props) {
2267
2275
  ] })
2268
2276
  ] }) });
2269
2277
  }
2278
+ function byInstalledAtDesc(a, b) {
2279
+ const ta = a.installedAt ?? "";
2280
+ const tb = b.installedAt ?? "";
2281
+ if (ta !== tb) return ta < tb ? 1 : -1;
2282
+ return a.name.localeCompare(b.name);
2283
+ }
2270
2284
  function InstalledPage() {
2271
2285
  ensureMarketStyles();
2272
2286
  const { items, push, dismiss } = useToasts();
@@ -2280,7 +2294,7 @@ function InstalledPage() {
2280
2294
  const load = (0, import_react5.useCallback)(() => {
2281
2295
  setList(null);
2282
2296
  setError(false);
2283
- void getInstalled().then((installed) => setList(installed)).catch(() => {
2297
+ void getInstalled().then((installed) => setList([...installed].sort(byInstalledAtDesc))).catch(() => {
2284
2298
  setList([]);
2285
2299
  setError(true);
2286
2300
  });
@@ -2549,6 +2563,24 @@ function satisfies(version, range) {
2549
2563
 
2550
2564
  // src/client/pages/market/InstallButton.tsx
2551
2565
  var import_react6 = require("react");
2566
+
2567
+ // src/client/pages/market/version.ts
2568
+ function compareVersions(a, b) {
2569
+ const parse = (text) => text.trim().replace(/^v/, "").split(".").map((part) => {
2570
+ const numeric = /^\d+/.exec(part);
2571
+ return numeric === null ? 0 : Number(numeric[0]);
2572
+ });
2573
+ const left = parse(a);
2574
+ const right = parse(b);
2575
+ const length = Math.max(left.length, right.length);
2576
+ for (let index = 0; index < length; index += 1) {
2577
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
2578
+ if (diff !== 0) return diff > 0 ? 1 : -1;
2579
+ }
2580
+ return 0;
2581
+ }
2582
+
2583
+ // src/client/pages/market/InstallButton.tsx
2552
2584
  var import_jsx_runtime15 = require("react/jsx-runtime");
2553
2585
  function InstallButton(props) {
2554
2586
  const [phase, setPhase] = (0, import_react6.useState)("idle");
@@ -2556,10 +2588,11 @@ function InstallButton(props) {
2556
2588
  const [buildConfirm, setBuildConfirm] = (0, import_react6.useState)(null);
2557
2589
  const [approving, setApproving] = (0, import_react6.useState)(false);
2558
2590
  const compat = checkCompat(props.compatRange, props.dshVersion);
2559
- const verb = props.installed?.updateAvailable === true ? "\u66F4\u65B0" : "\u5B89\u88C5";
2560
- const variant = verb === "\u66F4\u65B0" ? "update" : "primary";
2561
- if (props.installed !== void 0 && !props.installed.updateAvailable) {
2562
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: props.size ?? "medium", variant: "primary", disabled: true, title: "\u5DF2\u5B89\u88C5\u6700\u65B0\u7248\u672C,\u91CD\u88C5\u540C\u7248\u672C\u4E0D\u4F1A\u5237\u65B0", children: "\u5DF2\u5B89\u88C5" }) });
2591
+ const sameAsInstalled = props.installed !== void 0 && props.installed.version === props.version;
2592
+ const verb = props.installed === void 0 || sameAsInstalled ? "\u5B89\u88C5" : compareVersions(props.version, props.installed.version) >= 0 ? "\u66F4\u65B0" : "\u964D\u7EA7";
2593
+ const variant = verb === "\u5B89\u88C5" ? "primary" : "update";
2594
+ if (sameAsInstalled) {
2595
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: props.size ?? "medium", variant: "primary", disabled: true, title: "\u5DF2\u5B89\u88C5\u6B64\u7248\u672C,\u91CD\u88C5\u540C\u7248\u672C\u4E0D\u4F1A\u5237\u65B0", children: "\u5DF2\u5B89\u88C5" }) });
2563
2596
  }
2564
2597
  if (compat.ok === false) {
2565
2598
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -2692,6 +2725,8 @@ function PluginRow(props) {
2692
2725
  const compat = checkCompat(p.compatRange, props.dshVersion);
2693
2726
  const installed = props.installed;
2694
2727
  const upToDate = installed !== void 0 && !installed.updateAvailable;
2728
+ const versionLabel = installed !== void 0 && installed.updateAvailable ? `v${installed.version} \u2192 v${p.version}` : `v${p.version}`;
2729
+ const versionTitle = installed !== void 0 && installed.updateAvailable ? `\u672C\u673A\u5DF2\u5B89\u88C5 v${installed.version},\u53EF\u66F4\u65B0\u5230\u5E02\u573A\u6700\u65B0 v${p.version}` : `\u5E02\u573A\u6700\u65B0\u7248\u672C v${p.version}`;
2695
2730
  const open = () => props.onOpen?.(p.id);
2696
2731
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2697
2732
  "div",
@@ -2715,23 +2750,16 @@ function PluginRow(props) {
2715
2750
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "xps-mk-row-summary", children: p.summary })
2716
2751
  ] }),
2717
2752
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "xps-mk-row-meta", children: [
2718
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "xps-mk-row-version", children: [
2719
- "v",
2720
- p.version
2721
- ] }),
2722
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { title: `\u4E0B\u8F7D\u91CF ${p.downloads}`, children: [
2753
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "xps-mk-row-version", title: versionTitle, children: versionLabel }),
2754
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "xps-mk-row-downloads", title: `\u4E0B\u8F7D\u91CF ${p.downloads}`, children: [
2723
2755
  "\u2B07 ",
2724
2756
  formatDownloads(p.downloads)
2725
2757
  ] }),
2726
- compat.ok === null ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { children: "? \u517C\u5BB9\u6027\u672A\u77E5" }) : compat.ok ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { tone: "success", children: "\u2714 \u517C\u5BB9\u672C\u673A" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Badge, { tone: "warning", children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "xps-mk-row-compat", children: compat.ok === null ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { children: "? \u517C\u5BB9\u6027\u672A\u77E5" }) : compat.ok ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { tone: "success", children: "\u2714 \u517C\u5BB9\u672C\u673A" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Badge, { tone: "warning", children: [
2727
2759
  "\u26A0 \u9700 dsh ",
2728
2760
  p.compatRange
2729
- ] }),
2730
- installed !== void 0 && (installed.updateAvailable ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Badge, { tone: "warning", children: [
2731
- "\u2191 \u53EF\u66F4\u65B0 v",
2732
- installed.version
2733
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { tone: "success", children: "\u2713 \u5DF2\u5B89\u88C5" })),
2734
- !upToDate && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2761
+ ] }) }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "xps-mk-row-action", children: upToDate ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Badge, { tone: "success", children: "\u2713 \u5DF2\u5B89\u88C5" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2735
2763
  InstallButton,
2736
2764
  {
2737
2765
  pluginName: p.name,
@@ -2744,7 +2772,7 @@ function PluginRow(props) {
2744
2772
  size: "small",
2745
2773
  onToast: props.onToast
2746
2774
  }
2747
- )
2775
+ ) })
2748
2776
  ] })
2749
2777
  ]
2750
2778
  }
@@ -3002,9 +3030,10 @@ function MarketPage(props) {
3002
3030
  }
3003
3031
 
3004
3032
  // src/client/pages/PluginDetailPage.tsx
3005
- var import_react8 = require("react");
3033
+ var import_react9 = require("react");
3006
3034
 
3007
3035
  // src/client/pages/market/Markdown.tsx
3036
+ var import_react8 = require("react");
3008
3037
  var import_jsx_runtime18 = require("react/jsx-runtime");
3009
3038
  function Markdown(props) {
3010
3039
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "xps-md", children: renderBlocks(props.text) });
@@ -3142,16 +3171,80 @@ function renderBlocks(text, depth = 0) {
3142
3171
  }
3143
3172
  return blocks;
3144
3173
  }
3174
+ function MarkdownImage(props) {
3175
+ const [failed, setFailed] = (0, import_react8.useState)(false);
3176
+ (0, import_react8.useEffect)(() => setFailed(false), [props.src]);
3177
+ if (failed) {
3178
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "xps-md-img-fallback", title: props.alt, children: props.alt === "" ? "[\u56FE\u7247\u52A0\u8F7D\u5931\u8D25]" : `[\u56FE\u7247\u52A0\u8F7D\u5931\u8D25:${props.alt}]` });
3179
+ }
3180
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3181
+ "img",
3182
+ {
3183
+ className: "xps-md-img",
3184
+ src: props.src,
3185
+ alt: props.alt,
3186
+ loading: "lazy",
3187
+ referrerPolicy: "no-referrer",
3188
+ onError: () => setFailed(true)
3189
+ }
3190
+ );
3191
+ }
3192
+ function renderLink(url, content, key) {
3193
+ if (!/^https?:\/\//.test(url)) return null;
3194
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3195
+ "span",
3196
+ {
3197
+ className: "xps-md-link",
3198
+ role: "link",
3199
+ tabIndex: 0,
3200
+ onClick: () => void window.open(url, "_blank", "noopener,noreferrer"),
3201
+ onKeyDown: (event) => {
3202
+ if (event.key === "Enter" || event.key === " ") {
3203
+ event.preventDefault();
3204
+ void window.open(url, "_blank", "noopener,noreferrer");
3205
+ }
3206
+ },
3207
+ children: content
3208
+ },
3209
+ key
3210
+ );
3211
+ }
3145
3212
  function renderInline(text, keyPrefix) {
3146
3213
  const nodes = [];
3147
- const pattern = /(\*\*[^*]+\*\*|~~[^~]+~~|`[^`]+`|\[[^\]]*\]\([^)\s]*(?:\([^)\s]*\)[^)\s]*)?\))/g;
3214
+ const pattern = /(\[!\[[^\]]*\]\(\s*[^()\s]*(?:\([^()\s]*\)[^()\s]*)?\s*\)\]\(\s*[^()\s]*(?:\([^()\s]*\)[^()\s]*)?\s*\)|!\[[^\]]*\]\(\s*[^()\s]*(?:\([^()\s]*\)[^()\s]*)?\s*\)|\*\*[^*]+\*\*|~~[^~]+~~|`[^`]+`|\[[^\]]*\]\(\s*[^()\s]*(?:\([^()\s]*\)[^()\s]*)?\s*\))/g;
3148
3215
  let last = 0;
3149
3216
  let seq = 0;
3150
3217
  let m;
3151
3218
  while ((m = pattern.exec(text)) !== null) {
3152
3219
  if (m.index > last) nodes.push(text.slice(last, m.index));
3153
3220
  const token = m[0];
3154
- if (token.startsWith("**")) {
3221
+ if (token.startsWith("[![")) {
3222
+ const split = token.lastIndexOf("](");
3223
+ const inner = token.slice(1, split);
3224
+ const href = token.slice(split + 2, -1).trim();
3225
+ const image = /^!\[([^\]]*)\]\(([\s\S]*)\)$/.exec(inner);
3226
+ if (image === null) {
3227
+ nodes.push(token);
3228
+ } else {
3229
+ const [, alt = "", rawSrc = ""] = image;
3230
+ const src = rawSrc.trim();
3231
+ const picture = /^https?:\/\//.test(src) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkdownImage, { src, alt }, `${keyPrefix}-nimg${seq}`) : alt === "" ? token : alt;
3232
+ nodes.push(renderLink(href, picture, `${keyPrefix}-na${seq}`) ?? picture);
3233
+ }
3234
+ } else if (token.startsWith("![")) {
3235
+ const image = /^!\[([^\]]*)\]\((.+)\)$/.exec(token);
3236
+ if (image === null) {
3237
+ nodes.push(token);
3238
+ } else {
3239
+ const [, alt = "", rawSrc = ""] = image;
3240
+ const src = rawSrc.trim();
3241
+ if (/^https?:\/\//.test(src)) {
3242
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkdownImage, { src, alt }, `${keyPrefix}-img${seq}`));
3243
+ } else {
3244
+ nodes.push(token);
3245
+ }
3246
+ }
3247
+ } else if (token.startsWith("**")) {
3155
3248
  nodes.push(
3156
3249
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: renderInline(token.slice(2, -2), `${keyPrefix}-b${seq}`) }, `${keyPrefix}-b${seq}`)
3157
3250
  );
@@ -3169,32 +3262,8 @@ function renderInline(text, keyPrefix) {
3169
3262
  nodes.push(token);
3170
3263
  } else {
3171
3264
  const [, label = "", rawUrl = ""] = link;
3172
- const isImage = label.startsWith("!");
3173
- const text2 = isImage ? label.slice(1) : label;
3174
3265
  const url = rawUrl.trim();
3175
- if (/^https?:\/\//.test(url)) {
3176
- nodes.push(
3177
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3178
- "span",
3179
- {
3180
- className: "xps-md-link",
3181
- role: "link",
3182
- tabIndex: 0,
3183
- onClick: () => void window.open(url, "_blank", "noopener,noreferrer"),
3184
- onKeyDown: (event) => {
3185
- if (event.key === "Enter" || event.key === " ") {
3186
- event.preventDefault();
3187
- void window.open(url, "_blank", "noopener,noreferrer");
3188
- }
3189
- },
3190
- children: text2 === "" ? url : text2
3191
- },
3192
- `${keyPrefix}-a${seq}`
3193
- )
3194
- );
3195
- } else {
3196
- nodes.push(isImage ? label : token);
3197
- }
3266
+ nodes.push(renderLink(url, label === "" ? url : label, `${keyPrefix}-a${seq}`) ?? token);
3198
3267
  }
3199
3268
  }
3200
3269
  last = m.index + token.length;
@@ -3226,20 +3295,40 @@ var import_jsx_runtime19 = require("react/jsx-runtime");
3226
3295
  function PluginDetailPage(props) {
3227
3296
  ensureMarketStyles();
3228
3297
  const pluginId = props.pluginId ?? "";
3229
- const [detail, setDetail] = (0, import_react8.useState)(null);
3230
- const [status, setStatus] = (0, import_react8.useState)("loading");
3231
- const [selectedVersion, setSelectedVersion] = (0, import_react8.useState)(null);
3232
- const [reloadFlag, setReloadFlag] = (0, import_react8.useState)(0);
3233
- const [dshVersion, setDshVersion] = (0, import_react8.useState)(null);
3234
- const [installed, setInstalled] = (0, import_react8.useState)(null);
3298
+ const [detail, setDetail] = (0, import_react9.useState)(null);
3299
+ const [status, setStatus] = (0, import_react9.useState)("loading");
3300
+ const [selectedVersion, setSelectedVersion] = (0, import_react9.useState)(null);
3301
+ const [reloadFlag, setReloadFlag] = (0, import_react9.useState)(0);
3302
+ const [dshVersion, setDshVersion] = (0, import_react9.useState)(null);
3303
+ const [installed, setInstalled] = (0, import_react9.useState)(null);
3235
3304
  const toasts = useToasts();
3236
- const loadInstalled = (0, import_react8.useCallback)(
3237
- (name) => {
3238
- getInstalled().then((list) => setInstalled(list.find((item) => item.name === name) ?? null)).catch(() => setInstalled(null));
3305
+ const installedSeq = (0, import_react9.useRef)(0);
3306
+ const loadInstalled = (0, import_react9.useCallback)(
3307
+ (...names) => {
3308
+ const seq = ++installedSeq.current;
3309
+ getInstalled().then((list) => {
3310
+ if (seq === installedSeq.current) {
3311
+ setInstalled(list.find((item) => names.includes(item.name)) ?? null);
3312
+ }
3313
+ }).catch(() => {
3314
+ if (seq === installedSeq.current) setInstalled(null);
3315
+ });
3239
3316
  },
3240
3317
  []
3241
3318
  );
3242
- (0, import_react8.useEffect)(() => {
3319
+ const selectVersion = (version) => {
3320
+ setSelectedVersion(version);
3321
+ if (detail !== null) loadInstalled(pluginId, detail.name);
3322
+ };
3323
+ (0, import_react9.useEffect)(() => {
3324
+ if (detail === null) return;
3325
+ const onVisible = () => {
3326
+ if (document.visibilityState === "visible") loadInstalled(pluginId, detail.name);
3327
+ };
3328
+ document.addEventListener("visibilitychange", onVisible);
3329
+ return () => document.removeEventListener("visibilitychange", onVisible);
3330
+ }, [detail, pluginId, loadInstalled]);
3331
+ (0, import_react9.useEffect)(() => {
3243
3332
  let alive = true;
3244
3333
  getHostInfo().then((info) => {
3245
3334
  if (alive) setDshVersion(info.dshVersion);
@@ -3249,13 +3338,12 @@ function PluginDetailPage(props) {
3249
3338
  alive = false;
3250
3339
  };
3251
3340
  }, []);
3252
- (0, import_react8.useEffect)(() => {
3341
+ (0, import_react9.useEffect)(() => {
3253
3342
  let alive = true;
3254
3343
  setStatus("loading");
3255
3344
  setDetail(null);
3256
3345
  setSelectedVersion(null);
3257
3346
  setInstalled(null);
3258
- if (pluginId !== "") loadInstalled(pluginId);
3259
3347
  getPluginDetail(pluginId).then((d) => {
3260
3348
  if (!alive) return;
3261
3349
  if (d === null) {
@@ -3264,6 +3352,7 @@ function PluginDetailPage(props) {
3264
3352
  setDetail(d);
3265
3353
  setSelectedVersion(d.version);
3266
3354
  setStatus("ready");
3355
+ loadInstalled(pluginId, d.name);
3267
3356
  }
3268
3357
  }).catch(() => {
3269
3358
  if (alive) setStatus("error");
@@ -3351,7 +3440,7 @@ function PluginDetailPage(props) {
3351
3440
  sourceType: detail.sourceType,
3352
3441
  dshVersion,
3353
3442
  installed: installed ?? void 0,
3354
- onInstalled: () => loadInstalled(detail.name),
3443
+ onInstalled: () => loadInstalled(pluginId, detail.name),
3355
3444
  onToast: toasts.push
3356
3445
  }
3357
3446
  )
@@ -3380,24 +3469,22 @@ function PluginDetailPage(props) {
3380
3469
  ] }),
3381
3470
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "xps-mk-card", children: [
3382
3471
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "xps-mk-card-title", children: "\u7248\u672C\u5386\u53F2" }),
3383
- detail.versions.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3384
- "button",
3472
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3473
+ "select",
3385
3474
  {
3386
- type: "button",
3387
- className: `xps-mk-version${v.version === displayVersion ? " xps-mk-version-active" : ""}`,
3388
- title: `\u5207\u6362\u5230 v${v.version}`,
3389
- onClick: () => setSelectedVersion(v.version),
3390
- children: [
3391
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { children: [
3392
- "v",
3393
- v.version,
3394
- v.version === detail.version ? " \xB7 \u6700\u65B0" : ""
3395
- ] }),
3396
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "xps-mk-version-date", children: formatDate(v.publishedAt) })
3397
- ]
3398
- },
3399
- v.version
3400
- ))
3475
+ className: "xps-mk-select xps-mk-version-select",
3476
+ value: displayVersion,
3477
+ title: "\u5207\u6362\u7248\u672C",
3478
+ onChange: (e) => selectVersion(e.target.value),
3479
+ children: [...detail.versions].sort((a, b) => compareVersions(b.version, a.version)).map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("option", { value: v.version, children: [
3480
+ "v",
3481
+ v.version,
3482
+ " \xB7 ",
3483
+ formatShortDate(v.publishedAt),
3484
+ v.version === detail.version ? " \xB7 \u6700\u65B0" : ""
3485
+ ] }, v.version))
3486
+ }
3487
+ )
3401
3488
  ] })
3402
3489
  ] })
3403
3490
  ] })
@@ -3443,8 +3530,8 @@ var NAV_ITEMS = [
3443
3530
  { key: "developer", label: "\u6211\u7684" }
3444
3531
  ];
3445
3532
  function MarketplaceApp() {
3446
- const [nav, setNav] = (0, import_react9.useState)("market");
3447
- const [detailId, setDetailId] = (0, import_react9.useState)(null);
3533
+ const [nav, setNav] = (0, import_react10.useState)("market");
3534
+ const [detailId, setDetailId] = (0, import_react10.useState)(null);
3448
3535
  ensureThemeStyles();
3449
3536
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "xps-app", children: [
3450
3537
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("nav", { className: "xps-nav", children: NAV_ITEMS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
@@ -3472,7 +3559,7 @@ function MarketplaceApp() {
3472
3559
  }
3473
3560
 
3474
3561
  // src/client/settings/GeneralTokenRow.tsx
3475
- var import_react10 = require("react");
3562
+ var import_react11 = require("react");
3476
3563
  var import_jsx_runtime21 = require("react/jsx-runtime");
3477
3564
  var rowStyle = {
3478
3565
  display: "flex",
@@ -3529,12 +3616,12 @@ function messageOf2(error, fallback) {
3529
3616
  return error instanceof Error && error.message !== "" ? error.message : fallback;
3530
3617
  }
3531
3618
  function GeneralTokenRow() {
3532
- const [loading, setLoading] = (0, import_react10.useState)(true);
3533
- const [auth, setAuth] = (0, import_react10.useState)(null);
3534
- const [token, setToken] = (0, import_react10.useState)("");
3535
- const [pending, setPending] = (0, import_react10.useState)(false);
3536
- const [error, setError] = (0, import_react10.useState)("");
3537
- (0, import_react10.useEffect)(() => {
3619
+ const [loading, setLoading] = (0, import_react11.useState)(true);
3620
+ const [auth, setAuth] = (0, import_react11.useState)(null);
3621
+ const [token, setToken] = (0, import_react11.useState)("");
3622
+ const [pending, setPending] = (0, import_react11.useState)(false);
3623
+ const [error, setError] = (0, import_react11.useState)("");
3624
+ (0, import_react11.useEffect)(() => {
3538
3625
  let alive = true;
3539
3626
  void getAuthState().then((state) => {
3540
3627
  if (alive) setAuth(state);
@@ -3608,7 +3695,7 @@ function GeneralTokenRow() {
3608
3695
  }
3609
3696
 
3610
3697
  // src/client/settings/GeneralUpdateRow.tsx
3611
- var import_react11 = require("react");
3698
+ var import_react12 = require("react");
3612
3699
  var import_jsx_runtime22 = require("react/jsx-runtime");
3613
3700
  var rowStyle2 = {
3614
3701
  display: "flex",
@@ -3693,11 +3780,11 @@ function versionAtLeast(current, target) {
3693
3780
  return true;
3694
3781
  }
3695
3782
  function GeneralUpdateRow() {
3696
- const [phase, setPhase] = (0, import_react11.useState)(() => {
3783
+ const [phase, setPhase] = (0, import_react12.useState)(() => {
3697
3784
  const pending = readPendingUpdate();
3698
3785
  return pending !== null ? { kind: "updated", version: pending } : { kind: "checking" };
3699
3786
  });
3700
- const [error, setError] = (0, import_react11.useState)("");
3787
+ const [error, setError] = (0, import_react12.useState)("");
3701
3788
  const check = () => {
3702
3789
  setPhase({ kind: "checking" });
3703
3790
  setError("");
@@ -3714,7 +3801,7 @@ function GeneralUpdateRow() {
3714
3801
  setError(messageOf3(err, "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25"));
3715
3802
  });
3716
3803
  };
3717
- (0, import_react11.useEffect)(() => {
3804
+ (0, import_react12.useEffect)(() => {
3718
3805
  if (readPendingUpdate() !== null) {
3719
3806
  void checkSelfUpdate().then((result) => {
3720
3807
  const pending = readPendingUpdate();
@@ -3801,7 +3888,7 @@ function injectTheme() {
3801
3888
  function StoreIcon(props) {
3802
3889
  const active = props.active === true;
3803
3890
  const size = typeof props.size === "number" ? props.size : 20;
3804
- return (0, import_react12.createElement)(
3891
+ return (0, import_react13.createElement)(
3805
3892
  "svg",
3806
3893
  {
3807
3894
  width: size,
@@ -3812,13 +3899,13 @@ function StoreIcon(props) {
3812
3899
  color: active ? "var(--dsw-alias-brand-primary, #3861fb)" : "var(--dsw-alias-label-secondary, #646a73)"
3813
3900
  }
3814
3901
  },
3815
- (0, import_react12.createElement)("path", {
3902
+ (0, import_react13.createElement)("path", {
3816
3903
  d: "M4 7h16l-1.2-3.2A1 1 0 0 0 17.85 3H6.15a1 1 0 0 0-.95.8L4 7Zm0 0v12a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V7",
3817
3904
  stroke: "currentColor",
3818
3905
  strokeWidth: 1.6,
3819
3906
  strokeLinejoin: "round"
3820
3907
  }),
3821
- (0, import_react12.createElement)("path", {
3908
+ (0, import_react13.createElement)("path", {
3822
3909
  d: "M9 11a3 3 0 0 0 6 0",
3823
3910
  stroke: "currentColor",
3824
3911
  strokeWidth: 1.6,
@@ -3828,13 +3915,13 @@ function StoreIcon(props) {
3828
3915
  }
3829
3916
  function BundleConfigSeat(props) {
3830
3917
  if (props.view === "page") {
3831
- return (0, import_react12.createElement)(
3918
+ return (0, import_react13.createElement)(
3832
3919
  "div",
3833
3920
  { className: "xps-page-placeholder" },
3834
3921
  "\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A\u914D\u7F6E(\u5360\u4F4D):serviceBaseUrl / cacheDir / requestTimeout / cachePolicy"
3835
3922
  );
3836
3923
  }
3837
- return (0, import_react12.createElement)("div", null, "\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A:\u79C1\u6709\u63D2\u4EF6\u6D4F\u89C8\u3001\u5B89\u88C5\u4E0E\u5F00\u53D1\u8005\u4E0A\u4F20");
3924
+ return (0, import_react13.createElement)("div", null, "\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A:\u79C1\u6709\u63D2\u4EF6\u6D4F\u89C8\u3001\u5B89\u88C5\u4E0E\u5F00\u53D1\u8005\u4E0A\u4F20");
3838
3925
  }
3839
3926
  function apply(ctx) {
3840
3927
  const slots = ctx.get("slots");
@@ -3867,7 +3954,7 @@ function apply(ctx) {
3867
3954
  "main",
3868
3955
  () => safe(
3869
3956
  "main",
3870
- () => slots.register({ name: "main", key: "xrxs-plugins-store" }, () => (0, import_react12.createElement)(MarketplaceApp))
3957
+ () => slots.register({ name: "main", key: "xrxs-plugins-store" }, () => (0, import_react13.createElement)(MarketplaceApp))
3871
3958
  )
3872
3959
  ),
3873
3960
  "xrxs-plugins-store.main"
@@ -3879,7 +3966,7 @@ function apply(ctx) {
3879
3966
  "settings.general.item",
3880
3967
  () => slots.register(
3881
3968
  { name: "settings.general.item", id: "xrxs-plugins-store-token", order: 50 },
3882
- () => (0, import_react12.createElement)(GeneralTokenRow)
3969
+ () => (0, import_react13.createElement)(GeneralTokenRow)
3883
3970
  )
3884
3971
  )
3885
3972
  ),
@@ -3892,7 +3979,7 @@ function apply(ctx) {
3892
3979
  "settings.general.item",
3893
3980
  () => slots.register(
3894
3981
  { name: "settings.general.item", id: "xrxs-plugins-store-update", order: 51 },
3895
- () => (0, import_react12.createElement)(GeneralUpdateRow)
3982
+ () => (0, import_react13.createElement)(GeneralUpdateRow)
3896
3983
  )
3897
3984
  )
3898
3985
  ),
@@ -3905,7 +3992,7 @@ function apply(ctx) {
3905
3992
  "plugins.bundle.config",
3906
3993
  () => slots.register(
3907
3994
  { name: "plugins.bundle.config", key: "@xrxs-ai/dsh-xrxs-plugins-store" },
3908
- (props) => (0, import_react12.createElement)(BundleConfigSeat, props)
3995
+ (props) => (0, import_react13.createElement)(BundleConfigSeat, props)
3909
3996
  )
3910
3997
  )
3911
3998
  ),
@@ -88,6 +88,9 @@ export interface InstalledEntry {
88
88
  managed: boolean;
89
89
  /** 已装包自身 package.json 的 description(file: 本地装的包不在市场里,简介只能从这里来);读不到为 null。 */
90
90
  description: string | null;
91
+ /** 安装时间(ISO 8601);取 node_modules/<name> 的创建时间(pnpm 装包时建立,更新重装会刷新)。
92
+ * 目录缺失/文件系统不支持 birthtime 时为 null,页面把它排在最后。 */
93
+ installedAt: string | null;
91
94
  }
92
95
  /** 已装列表:读 <DSH_HOME>/profiles/<profile>/package.json 依赖 + dsh.profile.bundles 层栈
93
96
  * (desktop profile 被 CLI 独占时读文件不受影响;设计文档 §3.3)。
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { spawn } from 'node:child_process';
3
3
  import { existsSync, readdirSync, statSync } from 'node:fs';
4
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
5
5
  import { homedir } from 'node:os';
6
6
  import { basename, delimiter, dirname, join } from 'node:path';
7
7
  import { ApiError } from "./api-client.js";
@@ -437,7 +437,18 @@ export async function listInstalledFromProfile(host) {
437
437
  }
438
438
  if (version === '')
439
439
  version = versionFromFileSpec(spec) ?? '';
440
- return { name, version, managed: bundles.has(name), description };
440
+ // 安装时间:lstat 链接本身(pnpm 的 node_modules 项是指向 .pnpm 的符号链接,
441
+ // 链接创建时间 = 装包时间);birthtime 不可用(≤0/非法)退 mtime,再不行置 null
442
+ let installedAt = null;
443
+ try {
444
+ const link = await lstat(join(profileDir, 'node_modules', ...name.split('/')));
445
+ const time = link.birthtimeMs > 0 ? link.birthtime : link.mtime;
446
+ installedAt = Number.isNaN(time.getTime()) || time.getTime() <= 0 ? null : time.toISOString();
447
+ }
448
+ catch {
449
+ // 路径不存在:installedAt 保持 null
450
+ }
451
+ return { name, version, managed: bundles.has(name), description, installedAt };
441
452
  }));
442
453
  }
443
454
  const OUTPUT_CAP = 512 * 1024;
@@ -301,7 +301,11 @@ async function fetchLatestVersionMap(deps) {
301
301
  const version = optionalString(item.version);
302
302
  if (name === undefined || version === undefined)
303
303
  continue;
304
- map.set(name, { version, summary: optionalString(item.summary) ?? null });
304
+ map.set(name, {
305
+ version,
306
+ displayName: optionalString(item.displayName) ?? null,
307
+ summary: optionalString(item.summary) ?? null,
308
+ });
305
309
  }
306
310
  }
307
311
  catch (error) {
@@ -321,6 +325,9 @@ async function handleInstalled(rc) {
321
325
  latestVersion,
322
326
  updateAvailable: latestVersion !== null && latestVersion !== entry.version,
323
327
  managed: entry.managed,
328
+ installedAt: entry.installedAt,
329
+ // 展示名来自市场;本机手装/离线的包没有,client 半自行回退包名
330
+ displayName: meta?.displayName ?? null,
324
331
  // 市场简介优先;file: 本地装的包不在市场里,回退该包自己 package.json 的 description
325
332
  summary: meta?.summary ?? entry.description,
326
333
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrxs-ai/dsh-xrxs-plugins-store",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "DSH 私有插件市场插件(静态持久 bundle,双 face)",
5
5
  "type": "module",
6
6
  "license": "MIT",