@huanlin/dsh-plugin-sidebar-brand-text 0.6.1 → 0.6.2
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 +1 -1
- package/lib/client.js +1067 -1066
- package/lib/client.js.map +1 -1
- package/lib/index.js +199 -233
- package/lib/types/client/BrandTextCard.d.ts +4 -4
- package/lib/types/client/controller.d.ts +66 -66
- package/lib/types/client/index.d.ts +39 -38
- package/lib/types/client/titleWriter.d.ts +34 -29
- package/lib/types/config.d.ts +12 -2
- package/lib/types/index.d.ts +3 -3
- package/lib/types/settings.d.ts +37 -31
- package/package.json +14 -15
package/lib/index.js
CHANGED
|
@@ -1,234 +1,200 @@
|
|
|
1
|
-
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
|
|
3
|
-
//#region src/config.ts
|
|
4
|
-
/** Schemastery schema for the composition entry
|
|
5
|
-
const Config = z.object({
|
|
6
|
-
name: z.string().default("DSH Local Build").description("Brand name text shown in the sidebar next to the logo."),
|
|
7
|
-
revision: z.string().default("").description("Revision badge text shown beside the brand name. Empty string hides the badge.")
|
|
8
|
-
});
|
|
9
|
-
/**
|
|
10
|
-
* Resolve a raw config object into a complete {@link BrandTextConfig}.
|
|
11
|
-
*
|
|
12
|
-
* Unknown keys are dropped; missing or wrong-typed keys fall back to
|
|
13
|
-
* the defaults. This runs on every gateway read so the client always
|
|
14
|
-
* sees a well-formed value.
|
|
15
|
-
* @param config - raw config (entry source or settings layer).
|
|
16
|
-
* @returns the resolved config with defaults applied.
|
|
17
|
-
*/
|
|
18
|
-
function resolveConfig(config = {}) {
|
|
19
|
-
return {
|
|
20
|
-
name: typeof config.name === "string" ? config.name : DEFAULT.name,
|
|
21
|
-
revision: typeof config.revision === "string" ? config.revision : DEFAULT.revision
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
/** Defaults used when no config arrives (defensive only). */
|
|
25
|
-
const DEFAULT = {
|
|
26
|
-
name: "DSH Local Build",
|
|
27
|
-
revision: ""
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/settings.ts
|
|
32
|
-
/**
|
|
33
|
-
const SETTINGS_NAMESPACE = "sidebar-brand-text";
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
writeJson(res,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/** Build a success envelope. */
|
|
201
|
-
function envelopeOk(value) {
|
|
202
|
-
return {
|
|
203
|
-
ok: true,
|
|
204
|
-
value
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
/** Build an error envelope. */
|
|
208
|
-
function envelopeError(code, message) {
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
error: {
|
|
212
|
-
code,
|
|
213
|
-
message
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
//#endregion
|
|
219
|
-
//#region src/index.ts
|
|
220
|
-
const name = "sidebar-brand-text";
|
|
221
|
-
/** `webServer` is required for the HTTP gateway that backs the settings card. */
|
|
222
|
-
const inject = ["webServer"];
|
|
223
|
-
/**
|
|
224
|
-
* Plugin body: install the settings bridge and register the HTTP gateway.
|
|
225
|
-
*
|
|
226
|
-
* @param ctx - host context carrying `webServer`.
|
|
227
|
-
* @param config - resolved config (seed values).
|
|
228
|
-
*/
|
|
229
|
-
function apply(ctx, config) {
|
|
230
|
-
registerBrandTextGateway(ctx, installBrandTextSettings(ctx, resolveConfig(config)));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
//#endregion
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
|
|
3
|
+
//#region src/config.ts
|
|
4
|
+
/** Schemastery schema for the composition entry (live-editable via `.volatile()`). */
|
|
5
|
+
const Config = z.object({
|
|
6
|
+
name: z.string().default("DSH Local Build").description("Brand name text shown in the sidebar next to the logo.").volatile(),
|
|
7
|
+
revision: z.string().default("").description("Revision badge text shown beside the brand name. Empty string hides the badge.").volatile()
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a raw config object into a complete {@link BrandTextConfig}.
|
|
11
|
+
*
|
|
12
|
+
* Unknown keys are dropped; missing or wrong-typed keys fall back to
|
|
13
|
+
* the defaults. This runs on every gateway read so the client always
|
|
14
|
+
* sees a well-formed value.
|
|
15
|
+
* @param config - raw config (entry source or settings layer).
|
|
16
|
+
* @returns the resolved config with defaults applied.
|
|
17
|
+
*/
|
|
18
|
+
function resolveConfig(config = {}) {
|
|
19
|
+
return {
|
|
20
|
+
name: typeof config.name === "string" ? config.name : DEFAULT.name,
|
|
21
|
+
revision: typeof config.revision === "string" ? config.revision : DEFAULT.revision
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Defaults used when no config arrives (defensive only). */
|
|
25
|
+
const DEFAULT = {
|
|
26
|
+
name: "DSH Local Build",
|
|
27
|
+
revision: ""
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/settings.ts
|
|
32
|
+
/** The composition row id: the settings namespace / profile entry id. */
|
|
33
|
+
const SETTINGS_NAMESPACE = "sidebar-brand-text";
|
|
34
|
+
/**
|
|
35
|
+
* Declare the plugin's settings presentation policy and return the bridge.
|
|
36
|
+
*
|
|
37
|
+
* `auto: false` suppresses a schema-generated page: this plugin ships its own
|
|
38
|
+
* form through the `plugins.row.config` slot on the Plugins page.
|
|
39
|
+
*
|
|
40
|
+
* @param ctx - host context.
|
|
41
|
+
* @param config - the entry's volatile Cordis config.
|
|
42
|
+
* @returns the settings bridge.
|
|
43
|
+
*/
|
|
44
|
+
function installBrandTextSettings(ctx, config) {
|
|
45
|
+
ctx.inject(["settings"], (sctx) => {
|
|
46
|
+
sctx.effect(() => sctx.settings.configure({ auto: false }, ctx.fiber));
|
|
47
|
+
});
|
|
48
|
+
return { source: () => resolveConfig({
|
|
49
|
+
name: config.name.get(),
|
|
50
|
+
revision: config.revision.get()
|
|
51
|
+
}) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/gateway.ts
|
|
56
|
+
/** HTTP route prefix owning every sidebar-brand-text API request. */
|
|
57
|
+
const API_PREFIX = "/sbbt/api";
|
|
58
|
+
/** Config keys the `set` endpoint accepts (allow-list; unknown keys are dropped). */
|
|
59
|
+
const ALLOWED_KEYS = new Set(["name", "revision"]);
|
|
60
|
+
/**
|
|
61
|
+
* Register the `/sbbt/api` HTTP route on the host's web server.
|
|
62
|
+
*
|
|
63
|
+
* @param ctx - host context carrying `webServer`.
|
|
64
|
+
* @param bridge - the settings bridge the route reads through.
|
|
65
|
+
*/
|
|
66
|
+
function registerBrandTextGateway(ctx, bridge) {
|
|
67
|
+
let settings;
|
|
68
|
+
ctx.inject(["settings"], (sctx) => {
|
|
69
|
+
settings = sctx.settings;
|
|
70
|
+
return () => {
|
|
71
|
+
settings = void 0;
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
ctx.effect(() => {
|
|
75
|
+
const webServer = ctx.webServer;
|
|
76
|
+
if (!webServer || typeof webServer.register !== "function") return () => {};
|
|
77
|
+
return webServer.register({
|
|
78
|
+
kind: "prefix",
|
|
79
|
+
path: API_PREFIX,
|
|
80
|
+
handler: async (req, res) => {
|
|
81
|
+
if ((req.method ?? "") !== "POST") {
|
|
82
|
+
writeJson(res, 405, envelopeError("method-not-allowed", "POST only"));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const origin = req.headers.origin;
|
|
86
|
+
if (typeof origin === "string" && origin) {
|
|
87
|
+
let originHost;
|
|
88
|
+
try {
|
|
89
|
+
originHost = new URL(origin).host;
|
|
90
|
+
} catch {
|
|
91
|
+
writeJson(res, 400, envelopeError("invalid-origin", "invalid Origin header"));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const reqHost = req.headers.host;
|
|
95
|
+
if (typeof reqHost === "string" && originHost !== reqHost) {
|
|
96
|
+
writeJson(res, 403, envelopeError("origin-not-allowed", "same-origin requests only"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
|
|
101
|
+
writeJson(res, 415, envelopeError("content-type-not-supported", "application/json required"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
|
105
|
+
const method = pathname.startsWith(`${API_PREFIX}/`) ? pathname.slice(`${API_PREFIX}/`.length) : void 0;
|
|
106
|
+
if (method === void 0 || method.includes("/")) {
|
|
107
|
+
writeJson(res, 404, envelopeError("not-found", "unknown sidebar-brand-text API method"));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const body = await readJsonBody(req);
|
|
112
|
+
if (method === "get") writeJson(res, 200, envelopeOk({ config: resolveConfig(bridge.source()) }));
|
|
113
|
+
else if (method === "set") writeJson(res, 200, envelopeOk(await handleSet(body, settings, bridge)));
|
|
114
|
+
else writeJson(res, 404, envelopeError("not-found", `unknown sidebar-brand-text API method "${method}"`));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
writeJson(res, 500, envelopeError("internal", error instanceof Error ? error.message : String(error)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}, "sidebar-brand-text: /sbbt/api routes");
|
|
121
|
+
}
|
|
122
|
+
/** Handle the `set` method: validate patch, write user layer, return resolved config. */
|
|
123
|
+
async function handleSet(body, settings, bridge) {
|
|
124
|
+
const patch = extractPatch(body);
|
|
125
|
+
if (Object.keys(patch).length === 0) return { config: resolveConfig(bridge.source()) };
|
|
126
|
+
if (settings === void 0) throw new Error("sidebar-brand-text: settings service is unavailable — configuration cannot be written");
|
|
127
|
+
await settings.update(SETTINGS_NAMESPACE, patch);
|
|
128
|
+
return { config: resolveConfig(bridge.source()) };
|
|
129
|
+
}
|
|
130
|
+
/** Extract and validate the patch from the request body. */
|
|
131
|
+
function extractPatch(body) {
|
|
132
|
+
if (typeof body !== "object" || body === null) return {};
|
|
133
|
+
const raw = Reflect.get(body, "patch");
|
|
134
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
135
|
+
const normalized = {};
|
|
136
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
137
|
+
if (!ALLOWED_KEYS.has(key)) continue;
|
|
138
|
+
if (value === null || value === void 0) continue;
|
|
139
|
+
if (typeof value === "string") {
|
|
140
|
+
if (key === "name") normalized.name = value;
|
|
141
|
+
else if (key === "revision") normalized.revision = value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return normalized;
|
|
145
|
+
}
|
|
146
|
+
/** Read and parse a JSON body from a node:http request. */
|
|
147
|
+
async function readJsonBody(req, maxBytes = 8192) {
|
|
148
|
+
const chunks = [];
|
|
149
|
+
let bytes = 0;
|
|
150
|
+
for await (const chunk of req) {
|
|
151
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
152
|
+
bytes += buffer.length;
|
|
153
|
+
if (bytes > maxBytes) throw new Error("request body too large");
|
|
154
|
+
chunks.push(buffer);
|
|
155
|
+
}
|
|
156
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
157
|
+
if (text === "") return {};
|
|
158
|
+
return JSON.parse(text);
|
|
159
|
+
}
|
|
160
|
+
/** Write a JSON response envelope. */
|
|
161
|
+
function writeJson(res, status, body) {
|
|
162
|
+
const json = JSON.stringify(body);
|
|
163
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
164
|
+
res.end(json);
|
|
165
|
+
}
|
|
166
|
+
/** Build a success envelope. */
|
|
167
|
+
function envelopeOk(value) {
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
value
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Build an error envelope. */
|
|
174
|
+
function envelopeError(code, message) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
error: {
|
|
178
|
+
code,
|
|
179
|
+
message
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/index.ts
|
|
186
|
+
const name = "sidebar-brand-text";
|
|
187
|
+
/** `webServer` is required for the HTTP gateway that backs the settings card. */
|
|
188
|
+
const inject = ["webServer"];
|
|
189
|
+
/**
|
|
190
|
+
* Plugin body: install the settings bridge and register the HTTP gateway.
|
|
191
|
+
*
|
|
192
|
+
* @param ctx - host context carrying `webServer`.
|
|
193
|
+
* @param config - the entry's volatile Cordis config.
|
|
194
|
+
*/
|
|
195
|
+
function apply(ctx, config) {
|
|
196
|
+
registerBrandTextGateway(ctx, installBrandTextSettings(ctx, config));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
234
200
|
export { Config, apply, inject, name };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
1
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { BrandTextState } from './controller.ts';
|
|
4
4
|
/** Inject face: controller + selector hook. */
|
|
@@ -12,11 +12,11 @@ export interface BrandTextCardInjected {
|
|
|
12
12
|
};
|
|
13
13
|
readonly useSnapshot: SnapshotSelectorHook<BrandTextState>;
|
|
14
14
|
}
|
|
15
|
-
/** Full props: locale seat
|
|
16
|
-
export type BrandTextCardProps = PropsLocale<'dsh-plugin-sidebar-brand-text'> & BrandTextCardInjected;
|
|
15
|
+
/** Full props: the `plugins.row.config` owner share (view + form), locale seat, and inject. */
|
|
16
|
+
export type BrandTextCardProps = PropsRuntime<'plugins.row.config'> & PropsLocale<'dsh-plugin-sidebar-brand-text'> & BrandTextCardInjected;
|
|
17
17
|
/**
|
|
18
18
|
* Render the sidebar-brand-text settings card.
|
|
19
19
|
* @param props - locale + controller/useSnapshot inject.
|
|
20
20
|
* @returns a `<li>` card element.
|
|
21
21
|
*/
|
|
22
|
-
export declare function BrandTextCard({ t, controller, useSnapshot }: BrandTextCardProps): import("react").JSX.Element;
|
|
22
|
+
export declare function BrandTextCard({ view, t, controller, useSnapshot }: BrandTextCardProps): string | import("react").JSX.Element;
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `BrandTextSettingsController` — client-side state store for the
|
|
3
|
-
* sidebar-brand-text config.
|
|
4
|
-
*
|
|
5
|
-
* Loads the config from the host's `/sbbt/api/get` route, stages edits,
|
|
6
|
-
* and saves via `/sbbt/api/set`. The `sidebar.brand.name` slot occupant
|
|
7
|
-
* and the `
|
|
8
|
-
* `bindSnapshotSelector`, so a save is instantly reflected in the sidebar
|
|
9
|
-
* without a DOM event or page reload.
|
|
10
|
-
*
|
|
11
|
-
* Mirrors the ego-browser `EgoBrowserSettingsController` pattern.
|
|
12
|
-
*
|
|
13
|
-
* @module @huanlin/dsh-plugin-sidebar-brand-text/client/controller
|
|
14
|
-
*/
|
|
15
|
-
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
16
|
-
import { type BrandTextConfig } from '../types.ts';
|
|
17
|
-
/** The controller's snapshot state. */
|
|
18
|
-
export interface BrandTextState {
|
|
19
|
-
/** 'idle' | 'loading' | 'ready' */
|
|
20
|
-
status: 'idle' | 'loading' | 'ready';
|
|
21
|
-
/** True after a successful `/sbbt/api/get`; false when the route is unreachable. */
|
|
22
|
-
available: boolean;
|
|
23
|
-
/** False when the settings service is absent (read-only). */
|
|
24
|
-
writable: boolean;
|
|
25
|
-
/** Current draft values (edited by the card, read by the brand slot). */
|
|
26
|
-
draft: BrandTextConfig;
|
|
27
|
-
/** True when the draft differs from the last-saved config. */
|
|
28
|
-
dirty: boolean;
|
|
29
|
-
/** Apply lifecycle: 'idle' | 'saving' | 'saved' | 'error'. */
|
|
30
|
-
applyState: {
|
|
31
|
-
kind: 'idle';
|
|
32
|
-
} | {
|
|
33
|
-
kind: 'saving';
|
|
34
|
-
} | {
|
|
35
|
-
kind: 'saved';
|
|
36
|
-
} | {
|
|
37
|
-
kind: 'error';
|
|
38
|
-
message: string;
|
|
39
|
-
};
|
|
40
|
-
/** Card expand state (toggled by the card header button). */
|
|
41
|
-
_open: boolean;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Controller managing the brand-text config lifecycle.
|
|
45
|
-
*
|
|
46
|
-
* Constructed once in the client `apply()` and shared between the
|
|
47
|
-
* `sidebar.brand.name` slot and the `
|
|
48
|
-
*/
|
|
49
|
-
export declare class BrandTextSettingsController {
|
|
50
|
-
readonly store: SnapshotStore<BrandTextState>;
|
|
51
|
-
loaded: boolean;
|
|
52
|
-
private generation;
|
|
53
|
-
constructor();
|
|
54
|
-
/** Fetch the config from `/sbbt/api/get` and update the store. */
|
|
55
|
-
load(): Promise<void>;
|
|
56
|
-
/** Stage an edit to a field (does not save). */
|
|
57
|
-
edit(field: 'name' | 'revision', value: string): void;
|
|
58
|
-
/** Discard staged edits and reload from the host. */
|
|
59
|
-
discard(): void;
|
|
60
|
-
/** Save the staged draft via `/sbbt/api/set`. */
|
|
61
|
-
save(): Promise<void>;
|
|
62
|
-
/** Toggle the card's expand state (mirrors ego-browser `controller.toggle()`). */
|
|
63
|
-
toggle(): void;
|
|
64
|
-
/** Mark the store as unavailable (route unreachable or settings service absent). */
|
|
65
|
-
private markUnavailable;
|
|
66
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `BrandTextSettingsController` — client-side state store for the
|
|
3
|
+
* sidebar-brand-text config.
|
|
4
|
+
*
|
|
5
|
+
* Loads the config from the host's `/sbbt/api/get` route, stages edits,
|
|
6
|
+
* and saves via `/sbbt/api/set`. The `sidebar.brand.name` slot occupant
|
|
7
|
+
* and the `plugins.row.config` card both read from the same store via
|
|
8
|
+
* `bindSnapshotSelector`, so a save is instantly reflected in the sidebar
|
|
9
|
+
* without a DOM event or page reload.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the ego-browser `EgoBrowserSettingsController` pattern.
|
|
12
|
+
*
|
|
13
|
+
* @module @huanlin/dsh-plugin-sidebar-brand-text/client/controller
|
|
14
|
+
*/
|
|
15
|
+
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
16
|
+
import { type BrandTextConfig } from '../types.ts';
|
|
17
|
+
/** The controller's snapshot state. */
|
|
18
|
+
export interface BrandTextState {
|
|
19
|
+
/** 'idle' | 'loading' | 'ready' */
|
|
20
|
+
status: 'idle' | 'loading' | 'ready';
|
|
21
|
+
/** True after a successful `/sbbt/api/get`; false when the route is unreachable. */
|
|
22
|
+
available: boolean;
|
|
23
|
+
/** False when the settings service is absent (read-only). */
|
|
24
|
+
writable: boolean;
|
|
25
|
+
/** Current draft values (edited by the card, read by the brand slot). */
|
|
26
|
+
draft: BrandTextConfig;
|
|
27
|
+
/** True when the draft differs from the last-saved config. */
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
/** Apply lifecycle: 'idle' | 'saving' | 'saved' | 'error'. */
|
|
30
|
+
applyState: {
|
|
31
|
+
kind: 'idle';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'saving';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'saved';
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'error';
|
|
38
|
+
message: string;
|
|
39
|
+
};
|
|
40
|
+
/** Card expand state (toggled by the card header button). */
|
|
41
|
+
_open: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Controller managing the brand-text config lifecycle.
|
|
45
|
+
*
|
|
46
|
+
* Constructed once in the client `apply()` and shared between the
|
|
47
|
+
* `sidebar.brand.name` slot and the `plugins.row.config` card.
|
|
48
|
+
*/
|
|
49
|
+
export declare class BrandTextSettingsController {
|
|
50
|
+
readonly store: SnapshotStore<BrandTextState>;
|
|
51
|
+
loaded: boolean;
|
|
52
|
+
private generation;
|
|
53
|
+
constructor();
|
|
54
|
+
/** Fetch the config from `/sbbt/api/get` and update the store. */
|
|
55
|
+
load(): Promise<void>;
|
|
56
|
+
/** Stage an edit to a field (does not save). */
|
|
57
|
+
edit(field: 'name' | 'revision', value: string): void;
|
|
58
|
+
/** Discard staged edits and reload from the host. */
|
|
59
|
+
discard(): void;
|
|
60
|
+
/** Save the staged draft via `/sbbt/api/set`. */
|
|
61
|
+
save(): Promise<void>;
|
|
62
|
+
/** Toggle the card's expand state (mirrors ego-browser `controller.toggle()`). */
|
|
63
|
+
toggle(): void;
|
|
64
|
+
/** Mark the store as unavailable (route unreachable or settings service absent). */
|
|
65
|
+
private markUnavailable;
|
|
66
|
+
}
|