@huanlin/dsh-plugin-sidebar-brand-text 0.3.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/README.md +118 -0
- package/cordis.patch.yml +18 -0
- package/lib/client.js +661 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +235 -0
- package/lib/invariant.js +21 -0
- package/lib/types/client/BrandText.d.ts +37 -0
- package/lib/types/client/BrandTextCard.d.ts +22 -0
- package/lib/types/client/bindSnapshotSelector.d.ts +7 -0
- package/lib/types/client/controller.d.ts +66 -0
- package/lib/types/client/index.d.ts +38 -0
- package/lib/types/client/locales.d.ts +13 -0
- package/lib/types/client/styles.d.ts +21 -0
- package/lib/types/config.d.ts +24 -0
- package/lib/types/gateway.d.ts +31 -0
- package/lib/types/index.d.ts +32 -0
- package/lib/types/invariant.d.ts +10 -0
- package/lib/types/settings.d.ts +31 -0
- package/lib/types/types.d.ts +21 -0
- package/package.json +123 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "@huanlin/dsh-plugin-sidebar-brand-text", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
27
|
+
react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
28
|
+
let __deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
29
|
+
__deepseek_ai_dsh_client_runtime_client = __toESM(__deepseek_ai_dsh_client_runtime_client);
|
|
30
|
+
let react = require("react");
|
|
31
|
+
react = __toESM(react);
|
|
32
|
+
|
|
33
|
+
//#region src/types.ts
|
|
34
|
+
/** Runtime defaults applied when no config arrives (defensive only). */
|
|
35
|
+
const DEFAULT_BRAND_TEXT_CONFIG = {
|
|
36
|
+
name: "DSH Local Build",
|
|
37
|
+
revision: ""
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/client/BrandText.tsx
|
|
42
|
+
/**
|
|
43
|
+
* Render the configured brand name and optional revision badge.
|
|
44
|
+
*
|
|
45
|
+
* While loading or on error, falls back to the shell defaults.
|
|
46
|
+
* @param props - the `useSnapshot` inject face (plus the slot's runtime share, unused).
|
|
47
|
+
* @returns the brand-name span and optional revision-badge span.
|
|
48
|
+
*/
|
|
49
|
+
function BrandText({ useSnapshot }) {
|
|
50
|
+
const state = useSnapshot((s) => s);
|
|
51
|
+
const cfg = state.available ? state.draft : DEFAULT_BRAND_TEXT_CONFIG;
|
|
52
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53
|
+
className: "sbbt-brand-name",
|
|
54
|
+
children: cfg.name
|
|
55
|
+
}), cfg.revision !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56
|
+
className: "sbbt-build-revision",
|
|
57
|
+
children: cfg.revision
|
|
58
|
+
}) : null] });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/client/BrandTextCard.tsx
|
|
63
|
+
const cardStyle = {
|
|
64
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.22))",
|
|
65
|
+
background: "var(--dsw-alias-bg-layer-3, transparent)",
|
|
66
|
+
borderRadius: 12,
|
|
67
|
+
listStyle: "none",
|
|
68
|
+
transition: "border-color .16s, background .16s"
|
|
69
|
+
};
|
|
70
|
+
const headerStyle = {
|
|
71
|
+
appearance: "none",
|
|
72
|
+
width: "100%",
|
|
73
|
+
font: "inherit",
|
|
74
|
+
color: "inherit",
|
|
75
|
+
textAlign: "left",
|
|
76
|
+
cursor: "pointer",
|
|
77
|
+
background: "transparent",
|
|
78
|
+
border: 0,
|
|
79
|
+
borderRadius: 12,
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
gap: 12,
|
|
82
|
+
padding: "14px 16px",
|
|
83
|
+
display: "flex"
|
|
84
|
+
};
|
|
85
|
+
const headTextStyle = {
|
|
86
|
+
flexDirection: "column",
|
|
87
|
+
flex: 1,
|
|
88
|
+
gap: 4,
|
|
89
|
+
minWidth: 0,
|
|
90
|
+
display: "flex"
|
|
91
|
+
};
|
|
92
|
+
const nameStyle = {
|
|
93
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
94
|
+
fontSize: 15,
|
|
95
|
+
fontWeight: 600,
|
|
96
|
+
lineHeight: 1.4
|
|
97
|
+
};
|
|
98
|
+
const descStyle = {
|
|
99
|
+
color: "var(--dsw-alias-label-tertiary, rgba(128,128,128,0.7))",
|
|
100
|
+
fontSize: 13,
|
|
101
|
+
lineHeight: 1.5
|
|
102
|
+
};
|
|
103
|
+
const pendingStyle = {
|
|
104
|
+
whiteSpace: "nowrap",
|
|
105
|
+
background: "var(--dsw-alias-bg-module-platform, rgba(128,128,128,0.12))",
|
|
106
|
+
color: "var(--dsw-alias-label-secondary, inherit)",
|
|
107
|
+
borderRadius: 999,
|
|
108
|
+
flex: "none",
|
|
109
|
+
padding: "1px 8px",
|
|
110
|
+
fontSize: 11,
|
|
111
|
+
fontWeight: 500,
|
|
112
|
+
lineHeight: "17px"
|
|
113
|
+
};
|
|
114
|
+
const chevronStyle = (open) => ({
|
|
115
|
+
color: "var(--dsw-alias-label-tertiary, inherit)",
|
|
116
|
+
flex: "none",
|
|
117
|
+
transition: "transform .16s",
|
|
118
|
+
display: "inline-flex",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
transform: open ? "rotate(180deg)" : "none"
|
|
121
|
+
});
|
|
122
|
+
const bodyStyle = {
|
|
123
|
+
borderTop: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.22))",
|
|
124
|
+
margin: "0 16px",
|
|
125
|
+
padding: "12px 0 4px"
|
|
126
|
+
};
|
|
127
|
+
const formStyle = {
|
|
128
|
+
display: "flex",
|
|
129
|
+
flexDirection: "column",
|
|
130
|
+
gap: 12
|
|
131
|
+
};
|
|
132
|
+
const fieldStyle = {
|
|
133
|
+
display: "flex",
|
|
134
|
+
flexDirection: "column",
|
|
135
|
+
gap: 4
|
|
136
|
+
};
|
|
137
|
+
const labelStyle = {
|
|
138
|
+
display: "block",
|
|
139
|
+
fontSize: 13,
|
|
140
|
+
fontWeight: 500,
|
|
141
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
142
|
+
};
|
|
143
|
+
const inputStyle = {
|
|
144
|
+
width: "100%",
|
|
145
|
+
padding: "6px 10px",
|
|
146
|
+
fontSize: 13,
|
|
147
|
+
borderRadius: 8,
|
|
148
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
|
|
149
|
+
background: "var(--dsw-alias-bg-layer-3, transparent)",
|
|
150
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
151
|
+
boxSizing: "border-box",
|
|
152
|
+
fontFamily: "inherit"
|
|
153
|
+
};
|
|
154
|
+
const hintStyle = {
|
|
155
|
+
fontSize: 12,
|
|
156
|
+
color: "var(--dsw-alias-label-tertiary, rgba(128,128,128,0.6))",
|
|
157
|
+
margin: 0,
|
|
158
|
+
lineHeight: 1.5
|
|
159
|
+
};
|
|
160
|
+
const footerStyle = {
|
|
161
|
+
borderTop: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.22))",
|
|
162
|
+
justifyContent: "flex-end",
|
|
163
|
+
alignItems: "center",
|
|
164
|
+
gap: 8,
|
|
165
|
+
padding: "12px 0 4px",
|
|
166
|
+
display: "flex"
|
|
167
|
+
};
|
|
168
|
+
const btnBase = {
|
|
169
|
+
appearance: "none",
|
|
170
|
+
font: "inherit",
|
|
171
|
+
cursor: "pointer",
|
|
172
|
+
border: "1px solid transparent",
|
|
173
|
+
borderRadius: 8,
|
|
174
|
+
padding: "5px 14px",
|
|
175
|
+
fontSize: 13,
|
|
176
|
+
fontWeight: 500,
|
|
177
|
+
lineHeight: "20px",
|
|
178
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
179
|
+
background: "var(--dsw-alias-bg-module-platform, rgba(128,128,128,0.12))",
|
|
180
|
+
transition: "background .16s, opacity .16s"
|
|
181
|
+
};
|
|
182
|
+
const noticeStyle = {
|
|
183
|
+
color: "var(--dsw-alias-label-tertiary, rgba(128,128,128,0.7))",
|
|
184
|
+
margin: "0 0 8px",
|
|
185
|
+
fontSize: 12,
|
|
186
|
+
lineHeight: 1.5
|
|
187
|
+
};
|
|
188
|
+
const savedStyle = {
|
|
189
|
+
color: "var(--dsw-alias-state-success-primary, #30d158)",
|
|
190
|
+
margin: "0 0 12px",
|
|
191
|
+
fontSize: 12,
|
|
192
|
+
lineHeight: 1.5
|
|
193
|
+
};
|
|
194
|
+
const errorStyle = {
|
|
195
|
+
color: "var(--dsw-alias-label-error, #ff453a)",
|
|
196
|
+
margin: "0 0 12px",
|
|
197
|
+
fontSize: 12,
|
|
198
|
+
lineHeight: 1.5,
|
|
199
|
+
minWidth: 0
|
|
200
|
+
};
|
|
201
|
+
const CHEVRON_SVG = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9l6 6 6-6\"/></svg>";
|
|
202
|
+
/**
|
|
203
|
+
* Render the sidebar-brand-text settings card.
|
|
204
|
+
* @param props - locale + controller/useSnapshot inject.
|
|
205
|
+
* @returns a `<li>` card element.
|
|
206
|
+
*/
|
|
207
|
+
function BrandTextCard({ t, controller, useSnapshot }) {
|
|
208
|
+
const state = useSnapshot((s) => s);
|
|
209
|
+
if (state.status === "idle") controller.load();
|
|
210
|
+
const degraded = state.status === "ready" && !state.available;
|
|
211
|
+
const open = state._open || degraded;
|
|
212
|
+
const applyState = state.applyState ?? { kind: "idle" };
|
|
213
|
+
const saving = applyState.kind === "saving";
|
|
214
|
+
const saved = applyState.kind === "saved";
|
|
215
|
+
const errorText = applyState.kind === "error" ? applyState.message : void 0;
|
|
216
|
+
const busy = !state.writable || saving;
|
|
217
|
+
const header = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
218
|
+
type: "button",
|
|
219
|
+
style: headerStyle,
|
|
220
|
+
"aria-expanded": open,
|
|
221
|
+
"aria-label": t("card.title"),
|
|
222
|
+
onClick: () => {
|
|
223
|
+
if (!degraded) controller.toggle();
|
|
224
|
+
},
|
|
225
|
+
children: [
|
|
226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
227
|
+
style: headTextStyle,
|
|
228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
229
|
+
style: nameStyle,
|
|
230
|
+
children: t("card.title")
|
|
231
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
232
|
+
style: descStyle,
|
|
233
|
+
children: t("card.intro")
|
|
234
|
+
})]
|
|
235
|
+
}),
|
|
236
|
+
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
237
|
+
style: pendingStyle,
|
|
238
|
+
children: t("card.unsaved")
|
|
239
|
+
}) : null,
|
|
240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
241
|
+
style: chevronStyle(open),
|
|
242
|
+
dangerouslySetInnerHTML: { __html: CHEVRON_SVG }
|
|
243
|
+
})
|
|
244
|
+
]
|
|
245
|
+
});
|
|
246
|
+
let body = null;
|
|
247
|
+
if (open) if (!state.available) body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
248
|
+
style: bodyStyle,
|
|
249
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
250
|
+
style: noticeStyle,
|
|
251
|
+
role: "status",
|
|
252
|
+
children: t("card.unavailable")
|
|
253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
254
|
+
style: footerStyle,
|
|
255
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
256
|
+
type: "button",
|
|
257
|
+
style: btnBase,
|
|
258
|
+
onClick: () => {
|
|
259
|
+
controller.load();
|
|
260
|
+
},
|
|
261
|
+
children: t("card.retry")
|
|
262
|
+
})
|
|
263
|
+
})]
|
|
264
|
+
});
|
|
265
|
+
else body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
266
|
+
style: bodyStyle,
|
|
267
|
+
children: [
|
|
268
|
+
saved ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
269
|
+
style: savedStyle,
|
|
270
|
+
role: "status",
|
|
271
|
+
children: t("card.saved")
|
|
272
|
+
}) : null,
|
|
273
|
+
errorText !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
274
|
+
style: errorStyle,
|
|
275
|
+
role: "status",
|
|
276
|
+
children: errorText
|
|
277
|
+
}) : null,
|
|
278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
279
|
+
style: formStyle,
|
|
280
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
281
|
+
style: fieldStyle,
|
|
282
|
+
children: [
|
|
283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
284
|
+
style: labelStyle,
|
|
285
|
+
htmlFor: "sbbt-name",
|
|
286
|
+
children: t("field.name.label")
|
|
287
|
+
}),
|
|
288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
289
|
+
id: "sbbt-name",
|
|
290
|
+
type: "text",
|
|
291
|
+
style: inputStyle,
|
|
292
|
+
value: state.draft.name,
|
|
293
|
+
placeholder: t("field.name.placeholder"),
|
|
294
|
+
disabled: busy,
|
|
295
|
+
onChange: (e) => controller.edit("name", e.target.value)
|
|
296
|
+
}),
|
|
297
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
298
|
+
style: hintStyle,
|
|
299
|
+
children: t("field.name.hint")
|
|
300
|
+
})
|
|
301
|
+
]
|
|
302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
303
|
+
style: fieldStyle,
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
306
|
+
style: labelStyle,
|
|
307
|
+
htmlFor: "sbbt-revision",
|
|
308
|
+
children: t("field.revision.label")
|
|
309
|
+
}),
|
|
310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
311
|
+
id: "sbbt-revision",
|
|
312
|
+
type: "text",
|
|
313
|
+
style: inputStyle,
|
|
314
|
+
value: state.draft.revision,
|
|
315
|
+
placeholder: t("field.revision.placeholder"),
|
|
316
|
+
disabled: busy,
|
|
317
|
+
onChange: (e) => controller.edit("revision", e.target.value)
|
|
318
|
+
}),
|
|
319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
320
|
+
style: hintStyle,
|
|
321
|
+
children: t("field.revision.hint")
|
|
322
|
+
})
|
|
323
|
+
]
|
|
324
|
+
})]
|
|
325
|
+
}),
|
|
326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
327
|
+
style: footerStyle,
|
|
328
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
329
|
+
type: "button",
|
|
330
|
+
style: {
|
|
331
|
+
...btnBase,
|
|
332
|
+
opacity: !state.dirty || saving ? .5 : 1
|
|
333
|
+
},
|
|
334
|
+
disabled: !state.dirty || saving,
|
|
335
|
+
onClick: () => controller.discard(),
|
|
336
|
+
children: t("card.discard")
|
|
337
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
338
|
+
type: "button",
|
|
339
|
+
style: {
|
|
340
|
+
...btnBase,
|
|
341
|
+
background: "var(--dsw-alias-brand-primary, #0a84ff)",
|
|
342
|
+
color: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
343
|
+
opacity: !state.dirty || saving ? .5 : 1
|
|
344
|
+
},
|
|
345
|
+
disabled: !state.dirty || saving,
|
|
346
|
+
onClick: () => {
|
|
347
|
+
controller.save();
|
|
348
|
+
},
|
|
349
|
+
children: saving ? t("card.saving") : t("card.save")
|
|
350
|
+
})]
|
|
351
|
+
})
|
|
352
|
+
]
|
|
353
|
+
});
|
|
354
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
355
|
+
style: cardStyle,
|
|
356
|
+
children: [header, open ? body : null]
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/client/controller.ts
|
|
362
|
+
/** Initial state before the first load. */
|
|
363
|
+
function initialState() {
|
|
364
|
+
return {
|
|
365
|
+
status: "idle",
|
|
366
|
+
available: false,
|
|
367
|
+
writable: false,
|
|
368
|
+
draft: { ...DEFAULT_BRAND_TEXT_CONFIG },
|
|
369
|
+
dirty: false,
|
|
370
|
+
applyState: { kind: "idle" },
|
|
371
|
+
_open: false
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Controller managing the brand-text config lifecycle.
|
|
376
|
+
*
|
|
377
|
+
* Constructed once in the client `apply()` and shared between the
|
|
378
|
+
* `sidebar.brand.name` slot and the `settings.plugin.item` card.
|
|
379
|
+
*/
|
|
380
|
+
var BrandTextSettingsController = class {
|
|
381
|
+
store;
|
|
382
|
+
loaded = false;
|
|
383
|
+
generation = 0;
|
|
384
|
+
constructor() {
|
|
385
|
+
this.store = (0, __deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(initialState());
|
|
386
|
+
}
|
|
387
|
+
/** Fetch the config from `/sbbt/api/get` and update the store. */
|
|
388
|
+
async load() {
|
|
389
|
+
const gen = ++this.generation;
|
|
390
|
+
this.store.update((s) => {
|
|
391
|
+
s.status = "loading";
|
|
392
|
+
});
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch("/sbbt/api/get", {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: { "content-type": "application/json" },
|
|
397
|
+
body: "{}"
|
|
398
|
+
});
|
|
399
|
+
if (!res.ok) {
|
|
400
|
+
this.markUnavailable(gen);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const parsed = await res.json().catch(() => null);
|
|
404
|
+
if (gen !== this.generation) return;
|
|
405
|
+
if (!parsed || parsed.ok !== true || !parsed.value) {
|
|
406
|
+
this.markUnavailable(gen);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const config = parsed.value.config;
|
|
410
|
+
this.loaded = true;
|
|
411
|
+
this.store.update((s) => {
|
|
412
|
+
s.status = "ready";
|
|
413
|
+
s.available = true;
|
|
414
|
+
s.writable = true;
|
|
415
|
+
if (config) s.draft = {
|
|
416
|
+
name: config.name,
|
|
417
|
+
revision: config.revision
|
|
418
|
+
};
|
|
419
|
+
s.dirty = false;
|
|
420
|
+
s.applyState = { kind: "idle" };
|
|
421
|
+
});
|
|
422
|
+
} catch {
|
|
423
|
+
this.markUnavailable(gen);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Stage an edit to a field (does not save). */
|
|
427
|
+
edit(field, value) {
|
|
428
|
+
this.store.update((s) => {
|
|
429
|
+
if (field === "name") s.draft.name = value;
|
|
430
|
+
else s.draft.revision = value;
|
|
431
|
+
s.dirty = true;
|
|
432
|
+
s.applyState = { kind: "idle" };
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/** Discard staged edits and reload from the host. */
|
|
436
|
+
discard() {
|
|
437
|
+
this.load();
|
|
438
|
+
}
|
|
439
|
+
/** Save the staged draft via `/sbbt/api/set`. */
|
|
440
|
+
async save() {
|
|
441
|
+
const gen = ++this.generation;
|
|
442
|
+
const snapshot = this.store.getSnapshot();
|
|
443
|
+
if (!snapshot.dirty) return;
|
|
444
|
+
this.store.update((s) => {
|
|
445
|
+
s.applyState = { kind: "saving" };
|
|
446
|
+
});
|
|
447
|
+
try {
|
|
448
|
+
const parsed = await (await fetch("/sbbt/api/set", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
headers: { "content-type": "application/json" },
|
|
451
|
+
body: JSON.stringify({ patch: snapshot.draft })
|
|
452
|
+
})).json().catch(() => null);
|
|
453
|
+
if (gen !== this.generation) return;
|
|
454
|
+
if (!parsed || parsed.ok !== true || !parsed.value) {
|
|
455
|
+
const message = parsed?.error?.message ?? "Save failed";
|
|
456
|
+
this.store.update((s) => {
|
|
457
|
+
s.applyState = {
|
|
458
|
+
kind: "error",
|
|
459
|
+
message
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const config = parsed.value.config;
|
|
465
|
+
this.store.update((s) => {
|
|
466
|
+
s.applyState = { kind: "saved" };
|
|
467
|
+
if (config) s.draft = {
|
|
468
|
+
name: config.name,
|
|
469
|
+
revision: config.revision
|
|
470
|
+
};
|
|
471
|
+
s.dirty = false;
|
|
472
|
+
});
|
|
473
|
+
} catch (error) {
|
|
474
|
+
if (gen !== this.generation) return;
|
|
475
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
476
|
+
this.store.update((s) => {
|
|
477
|
+
s.applyState = {
|
|
478
|
+
kind: "error",
|
|
479
|
+
message
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/** Toggle the card's expand state (mirrors ego-browser `controller.toggle()`). */
|
|
485
|
+
toggle() {
|
|
486
|
+
this.store.update((s) => {
|
|
487
|
+
s._open = !s._open;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
/** Mark the store as unavailable (route unreachable or settings service absent). */
|
|
491
|
+
markUnavailable(gen) {
|
|
492
|
+
if (gen !== this.generation) return;
|
|
493
|
+
this.store.update((s) => {
|
|
494
|
+
s.status = "ready";
|
|
495
|
+
s.available = false;
|
|
496
|
+
s.writable = false;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/client/bindSnapshotSelector.ts
|
|
503
|
+
/**
|
|
504
|
+
* Bind a React selector hook to a {@link HostObservable} snapshot source.
|
|
505
|
+
* @param source - the observable snapshot store.
|
|
506
|
+
* @returns a `useSelector(sel, eq?)` hook.
|
|
507
|
+
*/
|
|
508
|
+
function bindSnapshotSelector(source) {
|
|
509
|
+
const subscribe = (fn) => source.subscribe(fn);
|
|
510
|
+
const getSnapshot = () => source.getSnapshot();
|
|
511
|
+
return function useSelector(sel) {
|
|
512
|
+
const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
513
|
+
const prevSnapshotRef = (0, react.useRef)(void 0);
|
|
514
|
+
const prevSelectedRef = (0, react.useRef)(void 0);
|
|
515
|
+
if (prevSnapshotRef.current !== snapshot) {
|
|
516
|
+
prevSnapshotRef.current = snapshot;
|
|
517
|
+
prevSelectedRef.current = sel(snapshot);
|
|
518
|
+
}
|
|
519
|
+
return prevSelectedRef.current;
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/client/locales.ts
|
|
525
|
+
/** The locale namespace name; matches the `locale: NS` passed at slot register. */
|
|
526
|
+
const NS = "dsh-plugin-sidebar-brand-text";
|
|
527
|
+
/** English dictionary. */
|
|
528
|
+
const en = {
|
|
529
|
+
"card.title": "Sidebar Brand Text",
|
|
530
|
+
"card.intro": "Replace the sidebar brand name and revision badge with custom text.",
|
|
531
|
+
"card.unsaved": "Unsaved",
|
|
532
|
+
"card.saved": "Saved",
|
|
533
|
+
"card.saving": "Saving…",
|
|
534
|
+
"card.discard": "Discard",
|
|
535
|
+
"card.save": "Save",
|
|
536
|
+
"card.unavailable": "The sidebar-brand-text configuration channel is unavailable. Please retry later.",
|
|
537
|
+
"card.retry": "Retry",
|
|
538
|
+
"field.name.label": "Brand name",
|
|
539
|
+
"field.name.placeholder": "DSH Local Build",
|
|
540
|
+
"field.name.hint": "Text shown in the sidebar next to the logo. Replaces the default \"DSH Local Build\".",
|
|
541
|
+
"field.revision.label": "Revision badge",
|
|
542
|
+
"field.revision.placeholder": "e.g. v1.0.0 or abc1234",
|
|
543
|
+
"field.revision.hint": "Small badge text beside the brand name. Leave empty to hide the badge."
|
|
544
|
+
};
|
|
545
|
+
/** Chinese dictionary. */
|
|
546
|
+
const zh = {
|
|
547
|
+
"card.title": "侧边栏品牌文案",
|
|
548
|
+
"card.intro": "替换侧边栏左上角的品牌名与构建徽标文案。",
|
|
549
|
+
"card.unsaved": "未保存",
|
|
550
|
+
"card.saved": "已保存",
|
|
551
|
+
"card.saving": "保存中…",
|
|
552
|
+
"card.discard": "放弃",
|
|
553
|
+
"card.save": "保存",
|
|
554
|
+
"card.unavailable": "侧边栏品牌文案配置通道不可用,请稍后重试。",
|
|
555
|
+
"card.retry": "重试",
|
|
556
|
+
"field.name.label": "品牌名称",
|
|
557
|
+
"field.name.placeholder": "DSH Local Build",
|
|
558
|
+
"field.name.hint": "侧边栏 logo 右侧显示的文案。替换默认的「DSH Local Build」。",
|
|
559
|
+
"field.revision.label": "版本徽标",
|
|
560
|
+
"field.revision.placeholder": "如 v1.0.0 或 abc1234",
|
|
561
|
+
"field.revision.hint": "品牌名右侧的小徽标文案。留空则不显示徽标。"
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/client/styles.ts
|
|
566
|
+
/**
|
|
567
|
+
* One scoped stylesheet injected for the lifetime of the client activation.
|
|
568
|
+
*
|
|
569
|
+
* The shell's `sidebar.brand.name` fallback renders two CSS-Module-hashed
|
|
570
|
+
* spans (`.fallbackBrandName` + `.buildRevision`); those class names are
|
|
571
|
+
* not stable across builds and not addressable from outside the sidebar
|
|
572
|
+
* package. This plugin ships its own class names with the same visual
|
|
573
|
+
* intent, all colors and typography drawn from the shared `--dsw-*`
|
|
574
|
+
* tokens (never literals) so the badge tracks the active theme.
|
|
575
|
+
*
|
|
576
|
+
* The parent `.brandName` span (inline-flex, gap: 6px, font-size: 18px,
|
|
577
|
+
* font-weight: 600) is owned by the sidebar shell and wraps whatever the
|
|
578
|
+
* slot occupant returns, so this stylesheet only needs to style the two
|
|
579
|
+
* child spans.
|
|
580
|
+
*/
|
|
581
|
+
const CSS = `
|
|
582
|
+
.sbbt-brand-name {
|
|
583
|
+
font-size: 17px;
|
|
584
|
+
letter-spacing: 0px;
|
|
585
|
+
white-space: nowrap;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.sbbt-build-revision {
|
|
589
|
+
display: inline-flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
height: 16px;
|
|
592
|
+
padding: 0 4px;
|
|
593
|
+
border-radius: 3px;
|
|
594
|
+
color: var(--dsw-alias-label-primary-inverted);
|
|
595
|
+
background: var(--dsw-alias-label-primary);
|
|
596
|
+
font-family: var(--ds-font-family-code);
|
|
597
|
+
font-size: 8px;
|
|
598
|
+
font-weight: 500;
|
|
599
|
+
line-height: 16px;
|
|
600
|
+
}
|
|
601
|
+
`;
|
|
602
|
+
/**
|
|
603
|
+
* Install the stylesheet and return its disposer.
|
|
604
|
+
* @returns a cleanup function that removes the injected `<style>` tag.
|
|
605
|
+
*/
|
|
606
|
+
function installStyles() {
|
|
607
|
+
if (typeof document === "undefined") return () => {};
|
|
608
|
+
const style = document.createElement("style");
|
|
609
|
+
style.setAttribute("data-sidebar-brand-text-style", "");
|
|
610
|
+
style.textContent = CSS;
|
|
611
|
+
document.head.appendChild(style);
|
|
612
|
+
return () => {
|
|
613
|
+
style.remove();
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/client/index.ts
|
|
619
|
+
/** Required services: slots + locale. */
|
|
620
|
+
const inject = ["slots", "locale"];
|
|
621
|
+
/**
|
|
622
|
+
* Client plugin body: register the brand-name slot occupant, the settings
|
|
623
|
+
* card, the locale dictionary, and the stylesheet.
|
|
624
|
+
*
|
|
625
|
+
* A single `BrandTextSettingsController` is shared between the card and
|
|
626
|
+
* the brand text so a save is instantly reflected in the sidebar.
|
|
627
|
+
* @param ctx - client root context.
|
|
628
|
+
*/
|
|
629
|
+
function apply(ctx) {
|
|
630
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
631
|
+
zh,
|
|
632
|
+
en
|
|
633
|
+
}), "sidebar-brand-text: dictionaries");
|
|
634
|
+
ctx.effect(installStyles, "sidebar-brand-text: styles");
|
|
635
|
+
const controller = new BrandTextSettingsController();
|
|
636
|
+
const useSnapshot = bindSnapshotSelector(controller.store);
|
|
637
|
+
controller.load();
|
|
638
|
+
const brandInjected = () => ({ useSnapshot });
|
|
639
|
+
ctx.slots.inject("sidebar.brand.name", () => ctx.slots.register({
|
|
640
|
+
name: "sidebar.brand.name",
|
|
641
|
+
inject: brandInjected
|
|
642
|
+
}, BrandText));
|
|
643
|
+
const cardInjected = () => ({
|
|
644
|
+
controller,
|
|
645
|
+
useSnapshot
|
|
646
|
+
});
|
|
647
|
+
ctx.slots.inject("settings.plugin.item", function* () {
|
|
648
|
+
yield ctx.slots.register({
|
|
649
|
+
name: "settings.plugin.item",
|
|
650
|
+
key: "sidebar-brand-text",
|
|
651
|
+
locale: NS,
|
|
652
|
+
inject: cardInjected
|
|
653
|
+
}, BrandTextCard);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
//#endregion
|
|
658
|
+
exports.apply = apply;
|
|
659
|
+
exports.inject = inject;
|
|
660
|
+
return module.exports; } });
|
|
661
|
+
//# sourceMappingURL=client.js.map
|