dshmarket 1.51.0 → 1.52.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 +310 -86
- package/lib/http.js +34 -2
- package/lib/index.js +33 -1
- package/lib/profile.js +33 -0
- package/lib/routes.js +28 -5
- package/lib/types/http.d.ts +31 -1
- package/lib/types/profile.d.ts +14 -0
- package/lib/types/verify.d.ts +6 -0
- package/package.json +1 -1
- package/src/client/MarketSection.tsx +32 -21
- package/src/client/SearchInput.tsx +69 -0
- package/src/client/index.ts +8 -0
- package/src/client/locales.ts +2 -0
- package/src/client/market-data.ts +2 -0
- package/src/client/market-mark.ts +47 -0
- package/src/client/settings-nav-icon.ts +174 -0
- package/src/http.ts +33 -2
- package/src/index.ts +42 -1
- package/src/profile.ts +35 -0
- package/src/routes.ts +29 -4
- package/src/verify.ts +6 -0
package/client/client.js
CHANGED
|
@@ -374,6 +374,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
374
374
|
stateLive: "已生效",
|
|
375
375
|
stateRestart: "已安装,重启后生效",
|
|
376
376
|
stateInert: "已安装,未生效",
|
|
377
|
+
stateDependencyLibrary: "{0} 的依赖库",
|
|
377
378
|
stateBroken: "已安装,校验未通过",
|
|
378
379
|
stateDisabled: "已停用",
|
|
379
380
|
phaseResolving: "解析依赖",
|
|
@@ -909,6 +910,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
909
910
|
stateLive: "Active",
|
|
910
911
|
stateRestart: "Installed — restart to apply",
|
|
911
912
|
stateInert: "Installed, not active",
|
|
913
|
+
stateDependencyLibrary: "Library of {0}",
|
|
912
914
|
stateBroken: "Installed, verification failed",
|
|
913
915
|
stateDisabled: "Disabled",
|
|
914
916
|
phaseResolving: "Resolving dependencies",
|
|
@@ -2690,6 +2692,78 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
2690
2692
|
"warnLine": "nUhMVa_warnLine"
|
|
2691
2693
|
};
|
|
2692
2694
|
//#endregion
|
|
2695
|
+
//#region src/client/market-mark.ts
|
|
2696
|
+
/**
|
|
2697
|
+
* The market's block mark as geometry rather than as a component.
|
|
2698
|
+
*
|
|
2699
|
+
* Two consumers draw this mark and they must not drift:
|
|
2700
|
+
*
|
|
2701
|
+
* - `MarketLogo` (MarketSection.tsx) renders it inside the section as an
|
|
2702
|
+
* ordinary React SVG in `currentColor`, including the animated variant;
|
|
2703
|
+
* - `settings-nav-icon.ts` serialises it into a CSS mask for the settings
|
|
2704
|
+
* navigation glyph, which cannot use `currentColor` (a mask is an
|
|
2705
|
+
* independent image) and so needs it as standalone markup.
|
|
2706
|
+
*
|
|
2707
|
+
* Keeping the numbers here means a change to the mark is one edit, and the
|
|
2708
|
+
* suite can hold both renderings to the same source.
|
|
2709
|
+
*
|
|
2710
|
+
* The mark is the brand asset in `assets/logo.svg`: an 8-cell grid plus the
|
|
2711
|
+
* block being plugged into its empty corner, offset and tilted 9°.
|
|
2712
|
+
*/
|
|
2713
|
+
/** Side of one block, and its corner radius. */
|
|
2714
|
+
const MARK_BLOCK_SIZE = 3.3;
|
|
2715
|
+
const MARK_BLOCK_RADIUS = .53;
|
|
2716
|
+
/** The eight grid cells, row-major. The ninth slot stays empty on purpose. */
|
|
2717
|
+
const MARK_GRID_BLOCKS = [
|
|
2718
|
+
{
|
|
2719
|
+
x: 1.96,
|
|
2720
|
+
y: 3.36
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
x: 5.71,
|
|
2724
|
+
y: 3.36
|
|
2725
|
+
},
|
|
2726
|
+
{
|
|
2727
|
+
x: 1.96,
|
|
2728
|
+
y: 7.11
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
x: 5.71,
|
|
2732
|
+
y: 7.11
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
x: 9.46,
|
|
2736
|
+
y: 7.11
|
|
2737
|
+
},
|
|
2738
|
+
{
|
|
2739
|
+
x: 1.96,
|
|
2740
|
+
y: 10.86
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
x: 5.71,
|
|
2744
|
+
y: 10.86
|
|
2745
|
+
},
|
|
2746
|
+
{
|
|
2747
|
+
x: 9.46,
|
|
2748
|
+
y: 10.86
|
|
2749
|
+
}
|
|
2750
|
+
];
|
|
2751
|
+
/**
|
|
2752
|
+
* The block being plugged in: OUTSIDE the grid's empty corner, offset
|
|
2753
|
+
* (+1.28, -1.27) and tilted 9deg, exactly as in assets/logo.svg. The earlier
|
|
2754
|
+
* icon sat it neatly in the empty slot, which reads as one crooked tile
|
|
2755
|
+
* rather than a block arriving — the whole idea of the mark, and the reason
|
|
2756
|
+
* it no longer matched the GitHub logo.
|
|
2757
|
+
*/
|
|
2758
|
+
const MARK_PLUG_BLOCK = {
|
|
2759
|
+
x: 10.74,
|
|
2760
|
+
y: 2.09,
|
|
2761
|
+
degrees: 9,
|
|
2762
|
+
/** Rotation origin: the plug block's own centre. */
|
|
2763
|
+
originX: 12.39,
|
|
2764
|
+
originY: 3.74
|
|
2765
|
+
};
|
|
2766
|
+
//#endregion
|
|
2693
2767
|
//#region src/client/comments.ts
|
|
2694
2768
|
/**
|
|
2695
2769
|
* giscus wiring for the in-market comment thread.
|
|
@@ -2863,6 +2937,64 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
2863
2937
|
]
|
|
2864
2938
|
});
|
|
2865
2939
|
}
|
|
2940
|
+
/** Keep keystrokes out of the market's large render tree, not just its filter.
|
|
2941
|
+
* Only settled queries reach the parent. Explicit navigation remains immediate.
|
|
2942
|
+
*/
|
|
2943
|
+
function SearchInput({ value, onCommit, className, placeholder, resetToken }) {
|
|
2944
|
+
const [draft, setDraft] = (0, react.useState)(value);
|
|
2945
|
+
const timer = (0, react.useRef)(null);
|
|
2946
|
+
const composing = (0, react.useRef)(false);
|
|
2947
|
+
const cancel = (0, react.useCallback)(() => {
|
|
2948
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
2949
|
+
timer.current = null;
|
|
2950
|
+
}, []);
|
|
2951
|
+
const commit = (next) => {
|
|
2952
|
+
cancel();
|
|
2953
|
+
onCommit(next);
|
|
2954
|
+
};
|
|
2955
|
+
const schedule = (next) => {
|
|
2956
|
+
cancel();
|
|
2957
|
+
if (composing.current) return;
|
|
2958
|
+
if (next === "") commit(next);
|
|
2959
|
+
else timer.current = setTimeout(() => commit(next), 250);
|
|
2960
|
+
};
|
|
2961
|
+
(0, react.useLayoutEffect)(() => {
|
|
2962
|
+
cancel();
|
|
2963
|
+
setDraft(value);
|
|
2964
|
+
}, [
|
|
2965
|
+
value,
|
|
2966
|
+
resetToken,
|
|
2967
|
+
cancel
|
|
2968
|
+
]);
|
|
2969
|
+
(0, react.useEffect)(() => cancel, [cancel]);
|
|
2970
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
2971
|
+
className,
|
|
2972
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
2973
|
+
placeholder,
|
|
2974
|
+
value: draft,
|
|
2975
|
+
onChange: (event) => {
|
|
2976
|
+
const next = event.target.value;
|
|
2977
|
+
setDraft(next);
|
|
2978
|
+
schedule(next);
|
|
2979
|
+
},
|
|
2980
|
+
onCompositionStart: () => {
|
|
2981
|
+
composing.current = true;
|
|
2982
|
+
cancel();
|
|
2983
|
+
},
|
|
2984
|
+
onCompositionEnd: (event) => {
|
|
2985
|
+
composing.current = false;
|
|
2986
|
+
const next = event.currentTarget.value;
|
|
2987
|
+
setDraft(next);
|
|
2988
|
+
schedule(next);
|
|
2989
|
+
},
|
|
2990
|
+
onKeyDown: (event) => {
|
|
2991
|
+
if (event.key === "Enter" && !composing.current && !event.nativeEvent.isComposing && event.keyCode !== 229) commit(event.currentTarget.value);
|
|
2992
|
+
},
|
|
2993
|
+
onBlur: (event) => {
|
|
2994
|
+
if (!composing.current) commit(event.currentTarget.value);
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2866
2998
|
//#endregion
|
|
2867
2999
|
//#region src/client/operations.ts
|
|
2868
3000
|
const BUCKETS = {
|
|
@@ -5191,7 +5323,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
5191
5323
|
});
|
|
5192
5324
|
}
|
|
5193
5325
|
/** The state label + dot for one activation result (P0-2). */
|
|
5194
|
-
function activationMeta(state, t) {
|
|
5326
|
+
function activationMeta(state, t, dependencyOf) {
|
|
5327
|
+
if (state === "inert" && dependencyOf !== void 0) return {
|
|
5328
|
+
label: t("stateDependencyLibrary").replace("{0}", dependencyOf),
|
|
5329
|
+
dot: "done"
|
|
5330
|
+
};
|
|
5195
5331
|
if (state === "live") return {
|
|
5196
5332
|
label: t("stateLive"),
|
|
5197
5333
|
dot: "done"
|
|
@@ -6073,86 +6209,36 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
6073
6209
|
* Official-style market glyph: the shared block-grid brand mark converted to
|
|
6074
6210
|
* the official monochrome icon form (16×16, fill="currentColor") so it
|
|
6075
6211
|
* follows the active theme. Mirrors the settings-nav glyph used for the
|
|
6076
|
-
* "market" section id.
|
|
6212
|
+
* "market" section id — both now draw the geometry in market-mark.ts, so the
|
|
6213
|
+
* nav entry and the section it opens cannot drift apart.
|
|
6077
6214
|
*/
|
|
6078
6215
|
function MarketLogo({ size = 16, style, animated = false }) {
|
|
6079
6216
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
6080
6217
|
width: size,
|
|
6081
6218
|
height: size,
|
|
6082
|
-
viewBox:
|
|
6219
|
+
viewBox: `0 0 16 16`,
|
|
6083
6220
|
fill: "none",
|
|
6084
6221
|
xmlns: "http://www.w3.org/2000/svg",
|
|
6085
6222
|
"aria-hidden": "true",
|
|
6086
6223
|
style,
|
|
6087
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
6224
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
6088
6225
|
fill: "currentColor",
|
|
6089
|
-
children:
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
}),
|
|
6097
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6098
|
-
x: "5.71",
|
|
6099
|
-
y: "3.36",
|
|
6100
|
-
width: "3.3",
|
|
6101
|
-
height: "3.3",
|
|
6102
|
-
rx: "0.53"
|
|
6103
|
-
}),
|
|
6104
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6105
|
-
x: "1.96",
|
|
6106
|
-
y: "7.11",
|
|
6107
|
-
width: "3.3",
|
|
6108
|
-
height: "3.3",
|
|
6109
|
-
rx: "0.53"
|
|
6110
|
-
}),
|
|
6111
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6112
|
-
x: "5.71",
|
|
6113
|
-
y: "7.11",
|
|
6114
|
-
width: "3.3",
|
|
6115
|
-
height: "3.3",
|
|
6116
|
-
rx: "0.53"
|
|
6117
|
-
}),
|
|
6118
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6119
|
-
x: "9.46",
|
|
6120
|
-
y: "7.11",
|
|
6121
|
-
width: "3.3",
|
|
6122
|
-
height: "3.3",
|
|
6123
|
-
rx: "0.53"
|
|
6124
|
-
}),
|
|
6125
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6126
|
-
x: "1.96",
|
|
6127
|
-
y: "10.86",
|
|
6128
|
-
width: "3.3",
|
|
6129
|
-
height: "3.3",
|
|
6130
|
-
rx: "0.53"
|
|
6131
|
-
}),
|
|
6132
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6133
|
-
x: "5.71",
|
|
6134
|
-
y: "10.86",
|
|
6135
|
-
width: "3.3",
|
|
6136
|
-
height: "3.3",
|
|
6137
|
-
rx: "0.53"
|
|
6138
|
-
}),
|
|
6139
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6140
|
-
x: "9.46",
|
|
6141
|
-
y: "10.86",
|
|
6142
|
-
width: "3.3",
|
|
6143
|
-
height: "3.3",
|
|
6144
|
-
rx: "0.53"
|
|
6145
|
-
})
|
|
6146
|
-
]
|
|
6226
|
+
children: MARK_GRID_BLOCKS.map((block) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6227
|
+
x: block.x,
|
|
6228
|
+
y: block.y,
|
|
6229
|
+
width: MARK_BLOCK_SIZE,
|
|
6230
|
+
height: MARK_BLOCK_SIZE,
|
|
6231
|
+
rx: MARK_BLOCK_RADIUS
|
|
6232
|
+
}, `${block.x},${block.y}`))
|
|
6147
6233
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
6148
6234
|
className: animated ? Market_module_css_default.logoPlug : void 0,
|
|
6149
|
-
x:
|
|
6150
|
-
y:
|
|
6151
|
-
width:
|
|
6152
|
-
height:
|
|
6153
|
-
rx:
|
|
6235
|
+
x: MARK_PLUG_BLOCK.x,
|
|
6236
|
+
y: MARK_PLUG_BLOCK.y,
|
|
6237
|
+
width: MARK_BLOCK_SIZE,
|
|
6238
|
+
height: MARK_BLOCK_SIZE,
|
|
6239
|
+
rx: MARK_BLOCK_RADIUS,
|
|
6154
6240
|
fill: "currentColor",
|
|
6155
|
-
transform: animated ? void 0 :
|
|
6241
|
+
transform: animated ? void 0 : `rotate(${MARK_PLUG_BLOCK.degrees} ${MARK_PLUG_BLOCK.originX} ${MARK_PLUG_BLOCK.originY})`
|
|
6156
6242
|
})]
|
|
6157
6243
|
});
|
|
6158
6244
|
}
|
|
@@ -6363,6 +6449,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
6363
6449
|
return saved || "discover";
|
|
6364
6450
|
});
|
|
6365
6451
|
const [q, setQ] = (0, react.useState)("");
|
|
6452
|
+
const [discoverSearchReset, resetDiscoverSearch] = (0, react.useState)(0);
|
|
6453
|
+
const [installedSearchReset, resetInstalledSearch] = (0, react.useState)(0);
|
|
6366
6454
|
/** Per-tab searches stay independent: discover / themes / installed. */
|
|
6367
6455
|
const [qThemes, setQThemes] = (0, react.useState)("");
|
|
6368
6456
|
const [qFavorites, setQFavorites] = (0, react.useState)("");
|
|
@@ -6377,10 +6465,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
6377
6465
|
if (kind === "installed") {
|
|
6378
6466
|
setTab("installed");
|
|
6379
6467
|
setQInstalled(value);
|
|
6468
|
+
resetInstalledSearch((n) => n + 1);
|
|
6380
6469
|
} else if (kind === "discover") {
|
|
6381
6470
|
setTab("discover");
|
|
6382
6471
|
setCat("all");
|
|
6383
6472
|
setQ(value);
|
|
6473
|
+
resetDiscoverSearch((n) => n + 1);
|
|
6384
6474
|
}
|
|
6385
6475
|
}, [props.preferredSubsectionId]);
|
|
6386
6476
|
const [confirming, setConfirming] = (0, react.useState)(null);
|
|
@@ -9097,7 +9187,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9097
9187
|
children: activationWarnings.map(({ name, info }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
9098
9188
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: name }),
|
|
9099
9189
|
" — ",
|
|
9100
|
-
activationMeta(info.state, t).label,
|
|
9190
|
+
activationMeta(info.state, t, info.dependencyOf).label,
|
|
9101
9191
|
info.reasons.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
9102
9192
|
className: Market_module_css_default.spec,
|
|
9103
9193
|
children: [
|
|
@@ -9476,13 +9566,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9476
9566
|
className: Market_module_css_default.stickyHead,
|
|
9477
9567
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9478
9568
|
className: Market_module_css_default.tabSearchRow,
|
|
9479
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
9569
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchInput, {
|
|
9570
|
+
resetToken: discoverSearchReset,
|
|
9480
9571
|
className: Market_module_css_default.tabSearch,
|
|
9481
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
9482
9572
|
placeholder: t("searchPh"),
|
|
9483
9573
|
value: q,
|
|
9484
|
-
|
|
9485
|
-
})
|
|
9574
|
+
onCommit: setQ
|
|
9575
|
+
}, "discover")
|
|
9486
9576
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9487
9577
|
className: Market_module_css_default.cats,
|
|
9488
9578
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -9567,13 +9657,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9567
9657
|
children: t("favoritesEmpty")
|
|
9568
9658
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9569
9659
|
className: Market_module_css_default.themeToolbar,
|
|
9570
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
9660
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchInput, {
|
|
9571
9661
|
className: Market_module_css_default.themeSearch,
|
|
9572
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
9573
9662
|
placeholder: t("searchFavoritesPh"),
|
|
9574
9663
|
value: qFavorites,
|
|
9575
|
-
|
|
9576
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9664
|
+
onCommit: setQFavorites
|
|
9665
|
+
}, "favorites"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9577
9666
|
className: Market_module_css_default.themeToolbarActions,
|
|
9578
9667
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterMenu, {
|
|
9579
9668
|
sortField: favSortField,
|
|
@@ -9654,13 +9743,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9654
9743
|
] })] }) : tab === "themes" && themeSnap !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9655
9744
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9656
9745
|
className: Market_module_css_default.themeToolbar,
|
|
9657
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
9746
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchInput, {
|
|
9658
9747
|
className: Market_module_css_default.themeSearch,
|
|
9659
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
9660
9748
|
placeholder: t("searchPh"),
|
|
9661
9749
|
value: qThemes,
|
|
9662
|
-
|
|
9663
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9750
|
+
onCommit: setQThemes
|
|
9751
|
+
}, "themes"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9664
9752
|
className: Market_module_css_default.themeToolbarActions,
|
|
9665
9753
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterMenu, {
|
|
9666
9754
|
sortField: themeSortField,
|
|
@@ -9742,13 +9830,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9742
9830
|
}),
|
|
9743
9831
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9744
9832
|
className: Market_module_css_default.tabSearchRow,
|
|
9745
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
9833
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchInput, {
|
|
9834
|
+
resetToken: installedSearchReset,
|
|
9746
9835
|
className: Market_module_css_default.tabSearch,
|
|
9747
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
9748
9836
|
placeholder: t("searchPh"),
|
|
9749
9837
|
value: qInstalled,
|
|
9750
|
-
|
|
9751
|
-
})
|
|
9838
|
+
onCommit: setQInstalled
|
|
9839
|
+
}, "installed")
|
|
9752
9840
|
}),
|
|
9753
9841
|
installedView === "groups" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9754
9842
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -10067,7 +10155,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10067
10155
|
const generation = status?.kind === "generation" || isGenerationSpec(String(spec));
|
|
10068
10156
|
const localDev = !generation && (/^(?:link|file):/i.test(String(spec)) || status?.kind === "linked");
|
|
10069
10157
|
const act = activations[name];
|
|
10070
|
-
const meta = act !== void 0 ? activationMeta(act.state, t) : null;
|
|
10158
|
+
const meta = act !== void 0 ? activationMeta(act.state, t, act.dependencyOf) : null;
|
|
10071
10159
|
const version = status && status.version ? "v" + status.version : "";
|
|
10072
10160
|
const specText = String(spec);
|
|
10073
10161
|
const specRedundant = version !== "" && /^[\^~]?\d/.test(specText);
|
|
@@ -11417,6 +11505,141 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
11417
11505
|
}, (0, react.createElement)("div", { className: Market_module_css_default.setHeadText }, (0, react.createElement)("div", { className: Market_module_css_default.setName }, t("nav"), version !== null ? (0, react.createElement)("span", { className: Market_module_css_default.version }, ` v${version}`) : null, prerelease ? (0, react.createElement)("span", { className: Market_module_css_default.setBetaTag }, t("setChannelBeta")) : null), (0, react.createElement)("div", { className: Market_module_css_default.setDesc }, t("setCardDesc"))), (0, react.createElement)("span", { className: open ? `${Market_module_css_default.setChevron} ${Market_module_css_default.setChevronOpen}` : Market_module_css_default.setChevron }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 }))), open ? (0, react.createElement)("div", { className: Market_module_css_default.setBody }, body) : null);
|
|
11418
11506
|
}
|
|
11419
11507
|
//#endregion
|
|
11508
|
+
//#region src/client/settings-nav-icon.ts
|
|
11509
|
+
/**
|
|
11510
|
+
* The market's block mark in the settings navigation.
|
|
11511
|
+
*
|
|
11512
|
+
* The settings shell picks nav glyphs from a closed list of section ids
|
|
11513
|
+
* (`models`, `agent-presets`, `plugins`) and falls back to its own gear for
|
|
11514
|
+
* every other id; `settings.section` projects only `id` / `order` / `label`,
|
|
11515
|
+
* so a registrant has no icon to pass — the slot contract in
|
|
11516
|
+
* `@deepseek-ai/dsh-client-ui-settings` and the runtime slot inventory both
|
|
11517
|
+
* list exactly those three options. Every third-party section therefore wears
|
|
11518
|
+
* the gear, the market included.
|
|
11519
|
+
*
|
|
11520
|
+
* So the market claims its own row once the dialog is mounted and swaps the
|
|
11521
|
+
* fallback gear for the block mark — the same mark `MarketLogo` draws inside
|
|
11522
|
+
* the section (see market-mark.ts), which is what makes the nav entry read as
|
|
11523
|
+
* the same thing as the page it opens. `dsh-better-sidebar` and
|
|
11524
|
+
* `dsh-skill-mcp-panel` solve it the same way.
|
|
11525
|
+
*
|
|
11526
|
+
* Scope, deliberately narrow:
|
|
11527
|
+
*
|
|
11528
|
+
* - only the row whose visible text equals this plugin's own localized
|
|
11529
|
+
* section label is marked; no shell structure is touched;
|
|
11530
|
+
* - the marker and the injected stylesheet belong to a `ctx.effect`, so they
|
|
11531
|
+
* are removed with the fiber;
|
|
11532
|
+
* - a locale switch re-claims the row through the MutationObserver, so the
|
|
11533
|
+
* label and the glyph never disagree.
|
|
11534
|
+
*
|
|
11535
|
+
* Delete this module (and its call in index.ts) the day `settings.section`
|
|
11536
|
+
* grows an `icon` field.
|
|
11537
|
+
*/
|
|
11538
|
+
/** Marks the one nav row this plugin owns. */
|
|
11539
|
+
const NAV_ICON_MARKER = "data-dsh-market-nav-icon";
|
|
11540
|
+
/**
|
|
11541
|
+
* The nav rows of the settings dialog. The shell renders each
|
|
11542
|
+
* `settings.section` entry as a `<button>` inside the panel's `<nav>`
|
|
11543
|
+
* (SettingsPanel in dsh-client-ui-settings-general).
|
|
11544
|
+
*/
|
|
11545
|
+
const NAV_ROW_SELECTOR = "[role=\"dialog\"] nav button";
|
|
11546
|
+
/**
|
|
11547
|
+
* The mark as standalone SVG for a CSS `mask-image`.
|
|
11548
|
+
*
|
|
11549
|
+
* Painted pure black on purpose: a mask reads alpha only, and the visible
|
|
11550
|
+
* colour comes from the element's `background-color: currentColor`. The
|
|
11551
|
+
* plug block carries its tilt without the animated variant's transform
|
|
11552
|
+
* classes — a mask cannot animate through CSS-module classes.
|
|
11553
|
+
*/
|
|
11554
|
+
function marketMaskSvg() {
|
|
11555
|
+
const blocks = MARK_GRID_BLOCKS.map((block) => `<rect x="${block.x}" y="${block.y}" width="${MARK_BLOCK_SIZE}" height="${MARK_BLOCK_SIZE}" rx="${MARK_BLOCK_RADIUS}"/>`).join("");
|
|
11556
|
+
const plug = MARK_PLUG_BLOCK;
|
|
11557
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#000"><g>${blocks}</g><rect x="${plug.x}" y="${plug.y}" width="${MARK_BLOCK_SIZE}" height="${MARK_BLOCK_SIZE}" rx="${MARK_BLOCK_RADIUS}" transform="rotate(${plug.degrees} ${plug.originX} ${plug.originY})"/></svg>`;
|
|
11558
|
+
}
|
|
11559
|
+
/** The mask URL for the mark (encoded at runtime, never hand-escaped). */
|
|
11560
|
+
function marketMaskUrl(svg = marketMaskSvg()) {
|
|
11561
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
11562
|
+
}
|
|
11563
|
+
/**
|
|
11564
|
+
* Whether a nav row is this plugin's own.
|
|
11565
|
+
*
|
|
11566
|
+
* Pure, and the only decision this feature makes: the row whose visible text
|
|
11567
|
+
* is the section label the shell is currently projecting. An empty label
|
|
11568
|
+
* matches nothing — a locale that has not resolved yet must not mark the
|
|
11569
|
+
* whole nav.
|
|
11570
|
+
*/
|
|
11571
|
+
function isOwnNavRow(rowText, wantedLabel) {
|
|
11572
|
+
const wanted = String(wantedLabel ?? "").trim();
|
|
11573
|
+
if (wanted.length === 0) return false;
|
|
11574
|
+
return String(rowText ?? "").trim() === wanted;
|
|
11575
|
+
}
|
|
11576
|
+
/** Stylesheet for the marked row: hide the shell's gear, draw the mark. */
|
|
11577
|
+
function navIconCss(maskUrl) {
|
|
11578
|
+
return [
|
|
11579
|
+
`[${NAV_ICON_MARKER}] > svg { display: none; }`,
|
|
11580
|
+
`[${NAV_ICON_MARKER}]::before {`,
|
|
11581
|
+
` content: '';`,
|
|
11582
|
+
` flex: none;`,
|
|
11583
|
+
` width: 16px;`,
|
|
11584
|
+
` height: 16px;`,
|
|
11585
|
+
` background-color: currentColor;`,
|
|
11586
|
+
` -webkit-mask-image: url("${maskUrl}");`,
|
|
11587
|
+
` mask-image: url("${maskUrl}");`,
|
|
11588
|
+
` -webkit-mask-repeat: no-repeat;`,
|
|
11589
|
+
` mask-repeat: no-repeat;`,
|
|
11590
|
+
` -webkit-mask-position: center;`,
|
|
11591
|
+
` mask-position: center;`,
|
|
11592
|
+
` -webkit-mask-size: 16px 16px;`,
|
|
11593
|
+
` mask-size: 16px 16px;`,
|
|
11594
|
+
`}`
|
|
11595
|
+
].join("\n");
|
|
11596
|
+
}
|
|
11597
|
+
/**
|
|
11598
|
+
* Install the nav glyph.
|
|
11599
|
+
*
|
|
11600
|
+
* @param ctx - client context, for effect ownership.
|
|
11601
|
+
* @param resolveLabel - this plugin's current section label (the same thunk
|
|
11602
|
+
* the `settings.section` registration passes), re-read on every sync so a
|
|
11603
|
+
* locale switch is picked up without re-registering.
|
|
11604
|
+
*/
|
|
11605
|
+
function installSettingsNavIcon(ctx, resolveLabel) {
|
|
11606
|
+
if (typeof document === "undefined") return;
|
|
11607
|
+
ctx.effect(() => {
|
|
11608
|
+
const tag = document.createElement("style");
|
|
11609
|
+
tag.dataset.plugin = "dshmarket";
|
|
11610
|
+
tag.dataset.pluginCss = "dshmarket/settings-nav-icon";
|
|
11611
|
+
tag.textContent = navIconCss(marketMaskUrl());
|
|
11612
|
+
document.head.appendChild(tag);
|
|
11613
|
+
let disposed = false;
|
|
11614
|
+
let scheduled = false;
|
|
11615
|
+
const sync = () => {
|
|
11616
|
+
scheduled = false;
|
|
11617
|
+
if (disposed) return;
|
|
11618
|
+
const wanted = resolveLabel();
|
|
11619
|
+
for (const row of document.querySelectorAll(NAV_ROW_SELECTOR)) if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, "");
|
|
11620
|
+
else row.removeAttribute(NAV_ICON_MARKER);
|
|
11621
|
+
};
|
|
11622
|
+
const schedule = () => {
|
|
11623
|
+
if (scheduled || disposed) return;
|
|
11624
|
+
scheduled = true;
|
|
11625
|
+
queueMicrotask(sync);
|
|
11626
|
+
};
|
|
11627
|
+
sync();
|
|
11628
|
+
const observer = new MutationObserver(schedule);
|
|
11629
|
+
observer.observe(document.body, {
|
|
11630
|
+
childList: true,
|
|
11631
|
+
subtree: true,
|
|
11632
|
+
characterData: true
|
|
11633
|
+
});
|
|
11634
|
+
return () => {
|
|
11635
|
+
disposed = true;
|
|
11636
|
+
observer.disconnect();
|
|
11637
|
+
for (const row of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) row.removeAttribute(NAV_ICON_MARKER);
|
|
11638
|
+
tag.remove();
|
|
11639
|
+
};
|
|
11640
|
+
}, "dsh-market: settings nav icon");
|
|
11641
|
+
}
|
|
11642
|
+
//#endregion
|
|
11420
11643
|
//#region src/client/index.ts
|
|
11421
11644
|
/**
|
|
11422
11645
|
* dsh-market client: registers a "Market" settings section rendering the
|
|
@@ -11459,6 +11682,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
11459
11682
|
en
|
|
11460
11683
|
}), "dsh-market: dictionaries");
|
|
11461
11684
|
const t = ctx.locale.bind(NS);
|
|
11685
|
+
installSettingsNavIcon(ctx, () => t("nav"));
|
|
11462
11686
|
/**
|
|
11463
11687
|
* The market's own panel, as an element — one builder for the settings
|
|
11464
11688
|
* section this package registers and for `market.render()` (#602). Built
|
package/lib/http.js
CHANGED
|
@@ -11,11 +11,43 @@ export function sendJson(response, status, payload) {
|
|
|
11
11
|
});
|
|
12
12
|
response.end(JSON.stringify(payload));
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* True when the request's Origin, IF IT HAS ONE, matches its Host — required
|
|
16
|
+
* on every POST route.
|
|
17
|
+
*
|
|
18
|
+
* An absent Origin is allowed (#648). The check exists to stop another web
|
|
19
|
+
* page from driving this loopback API, and the Fetch spec has browsers send
|
|
20
|
+
* `Origin` on every POST — including same-origin and cross-origin form
|
|
21
|
+
* submissions. A request that arrives WITHOUT it therefore did not come from
|
|
22
|
+
* a page at all, and a non-browser client can set any Origin it likes, so
|
|
23
|
+
* refusing the header's absence bought nothing while breaking a whole
|
|
24
|
+
* supported host: the Desktop app's own proxy strips `origin`, `host`,
|
|
25
|
+
* `cookie` and `sec-fetch-site` before forwarding to the in-process host and
|
|
26
|
+
* injects a cookie only that host can sign. Every mutating request from the
|
|
27
|
+
* Desktop build was answered 403 `untrusted origin`.
|
|
28
|
+
*
|
|
29
|
+
* What still holds after the change, and is the part worth keeping:
|
|
30
|
+
*
|
|
31
|
+
* - a cross-site page is refused — its Origin is present and different;
|
|
32
|
+
* - `Origin: null` (a sandboxed iframe, a `data:` document) is refused, because
|
|
33
|
+
* `null` is a present-but-unparseable origin, not an absent one;
|
|
34
|
+
* - a same-host-but-different-port request is refused;
|
|
35
|
+
* - an EMPTY Origin is refused too: the header being present and unparseable
|
|
36
|
+
* is not the same statement as its being absent, and only absence is what
|
|
37
|
+
* a stripping proxy produces.
|
|
38
|
+
*
|
|
39
|
+
* The header's absence is a statement about the client, not about intent, and
|
|
40
|
+
* the intent is the only thing this can judge.
|
|
41
|
+
*
|
|
42
|
+
* @param request - the incoming request.
|
|
43
|
+
* @returns whether the request may mutate market state.
|
|
44
|
+
*/
|
|
15
45
|
export function sameOrigin(request) {
|
|
16
46
|
const origin = request.headers.origin;
|
|
47
|
+
if (origin === undefined)
|
|
48
|
+
return true;
|
|
17
49
|
const host = request.headers.host;
|
|
18
|
-
if (
|
|
50
|
+
if (host === undefined)
|
|
19
51
|
return false;
|
|
20
52
|
try {
|
|
21
53
|
return new URL(origin).host === host;
|
package/lib/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* composes the webServer and shell services.
|
|
4
4
|
*/
|
|
5
5
|
import { createDesktopPluginRuntime } from './dsh-cli.js';
|
|
6
|
+
import { isDshProfileName } from './profile.js';
|
|
6
7
|
import { mountMarketRoutes } from './routes.js';
|
|
7
8
|
import { installDesktopMarketSettings, installMarketSettings } from './settings.js';
|
|
8
9
|
export const name = 'dsh-market';
|
|
@@ -23,6 +24,30 @@ function argvProfile() {
|
|
|
23
24
|
return argv[flag + 1];
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The profile this process was launched into, as the launcher itself reports
|
|
29
|
+
* it. `argvProfile` only sees a `--profile` flag on this process's argv, and
|
|
30
|
+
* the official desktop host starts a profile through the launcher's node
|
|
31
|
+
* entry rather than the CLI — no flag, no `desktopProfiles` service either,
|
|
32
|
+
* so the market fell back to `web` and every install, update and uninstall
|
|
33
|
+
* landed in the web profile instead of the one the user was looking at
|
|
34
|
+
* (#639).
|
|
35
|
+
*
|
|
36
|
+
* The launcher's `dir` is taken with the name rather than derived from it:
|
|
37
|
+
* the launcher owns where a profile lives, and deriving the path would
|
|
38
|
+
* disagree with it for any profile that does not sit in the default place.
|
|
39
|
+
* A name the market could not use as a directory segment is refused rather
|
|
40
|
+
* than joined into a path.
|
|
41
|
+
*/
|
|
42
|
+
function launchedProfile(context) {
|
|
43
|
+
if (context === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const name = typeof context.name === 'string' ? context.name.trim() : '';
|
|
46
|
+
const dir = typeof context.dir === 'string' ? context.dir.trim() : '';
|
|
47
|
+
if (!isDshProfileName(name) || dir === '')
|
|
48
|
+
return undefined;
|
|
49
|
+
return { name, dir };
|
|
50
|
+
}
|
|
26
51
|
/**
|
|
27
52
|
* Resolve the host's `agents` inventory lazily — at request time, not at
|
|
28
53
|
* market startup, so the guard sees whichever agents exist by the time an
|
|
@@ -37,8 +62,15 @@ export function apply(ctx, config) {
|
|
|
37
62
|
const host = hostCtx;
|
|
38
63
|
const desktopProfiles = ctx.get('desktopProfiles');
|
|
39
64
|
if (desktopProfiles === undefined) {
|
|
65
|
+
// An explicit `profile:` in cordis.yml is the operator speaking and
|
|
66
|
+
// still wins; the launcher's own answer comes next, and only then the
|
|
67
|
+
// flag-and-default guesswork. The launcher's directory rides along with
|
|
68
|
+
// its name, and never with somebody else's.
|
|
69
|
+
const launched = launchedProfile(ctx.get('profileContext'));
|
|
70
|
+
const useLaunchedDir = config?.profile === undefined && launched !== undefined;
|
|
40
71
|
const resolved = {
|
|
41
|
-
profile: config?.profile ?? argvProfile() ?? 'web',
|
|
72
|
+
profile: config?.profile ?? launched?.name ?? argvProfile() ?? 'web',
|
|
73
|
+
...(useLaunchedDir ? { profileDirectory: launched.dir } : {}),
|
|
42
74
|
// Left UNDEFINED when unconfigured, deliberately: `?? true` here
|
|
43
75
|
// would turn "the operator said nothing" into "the operator said
|
|
44
76
|
// yes", and restartAllowed() could no longer tell them apart — which
|
package/lib/profile.js
CHANGED
|
@@ -78,6 +78,39 @@ export function readManifestDeps(profile, explicitDir) {
|
|
|
78
78
|
return {};
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* For each installed package, the OTHER installed package that declares it —
|
|
83
|
+
* as a dependency or a peer dependency — in its own manifest.
|
|
84
|
+
*
|
|
85
|
+
* pnpm's auto-install-peers writes a plugin's peers into the profile manifest
|
|
86
|
+
* as direct dependencies, so a native binding a plugin needs shows up in the
|
|
87
|
+
* installed list looking exactly like a plugin the user chose (#634). What
|
|
88
|
+
* separates them is that somebody else asked for it.
|
|
89
|
+
*
|
|
90
|
+
* Ownership is decided by the first owner in sorted order, so the answer does
|
|
91
|
+
* not depend on the manifest's key order. A package that declares itself is
|
|
92
|
+
* ignored, and so is a cycle's other half once one owner is chosen.
|
|
93
|
+
*/
|
|
94
|
+
export function readDependencyOwners(profile, names, explicitDir) {
|
|
95
|
+
const installed = new Set(names);
|
|
96
|
+
const owners = {};
|
|
97
|
+
for (const owner of [...names].sort()) {
|
|
98
|
+
const manifest = readInstalledManifest(profile, owner, explicitDir);
|
|
99
|
+
if (typeof manifest !== 'object' || manifest === null)
|
|
100
|
+
continue;
|
|
101
|
+
const declared = manifest;
|
|
102
|
+
for (const field of [declared.dependencies, declared.peerDependencies]) {
|
|
103
|
+
if (typeof field !== 'object' || field === null)
|
|
104
|
+
continue;
|
|
105
|
+
for (const dependency of Object.keys(field)) {
|
|
106
|
+
if (dependency === owner || !installed.has(dependency))
|
|
107
|
+
continue;
|
|
108
|
+
owners[dependency] ??= owner;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return owners;
|
|
113
|
+
}
|
|
81
114
|
function objectRecord(value) {
|
|
82
115
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
83
116
|
? value
|