dsh-fonttune 0.2.2 → 0.2.4
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/CHANGELOG.en.md +44 -0
- package/CHANGELOG.md +44 -0
- package/README.en.md +20 -5
- package/README.md +20 -5
- package/lib/client.js +913 -312
- package/lib/index.js +95 -11
- package/lib/shared.cjs +285 -145
- package/package.json +11 -6
package/lib/index.js
CHANGED
|
@@ -71,6 +71,30 @@ const MAX_STACK = 200;
|
|
|
71
71
|
*/
|
|
72
72
|
const SAFE_FEATURES = /^[a-zA-Z0-9"' ,]*$/;
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Mark a schema editable without reloading, where the running schemastery
|
|
76
|
+
* knows the modifier.
|
|
77
|
+
*
|
|
78
|
+
* DSH 0.1.7-alpha.x derives a settings page from one form per composed entry
|
|
79
|
+
* and only projects the fields under a node marked `.volatile()` — an entry
|
|
80
|
+
* whose schema has no such node is skipped entirely, so the plugin would have
|
|
81
|
+
* no configuration page there. The modifier arrived in schemastery 3.18.x for
|
|
82
|
+
* the alpha line; the 0.1.5-rc.x line ships 3.18.2, where calling it while
|
|
83
|
+
* this module is imported would throw. Feature-detecting it is therefore the
|
|
84
|
+
* whole compatibility shim: the rc line keeps the plain schema it has always
|
|
85
|
+
* used, and the alpha gets the marking it needs.
|
|
86
|
+
*
|
|
87
|
+
* Marking the ROOT object makes every field below it live-editable; schemastery
|
|
88
|
+
* refuses a volatile node nested inside another volatile one, so nothing below
|
|
89
|
+
* may carry the modifier as well.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} schema - the object schema.
|
|
92
|
+
* @returns {object} the schema, marked when the modifier exists.
|
|
93
|
+
*/
|
|
94
|
+
function volatileWhenSupported(schema) {
|
|
95
|
+
return typeof schema.volatile === "function" ? schema.volatile() : schema;
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
/**
|
|
75
99
|
* The durable settings section.
|
|
76
100
|
*
|
|
@@ -82,7 +106,7 @@ const SAFE_FEATURES = /^[a-zA-Z0-9"' ,]*$/;
|
|
|
82
106
|
* value. Installing the plugin therefore changes nothing until the user asks
|
|
83
107
|
* for something.
|
|
84
108
|
*/
|
|
85
|
-
export const Config = z.object({
|
|
109
|
+
export const Config = volatileWhenSupported(z.object({
|
|
86
110
|
[SANS_FIELD]: z
|
|
87
111
|
.string()
|
|
88
112
|
.max(MAX_STACK)
|
|
@@ -220,13 +244,46 @@ export const Config = z.object({
|
|
|
220
244
|
.max(shared.MAX_PRESET_NAME)
|
|
221
245
|
.default("")
|
|
222
246
|
.description("Name of the preset the card currently edits (edits auto-save into it)"),
|
|
223
|
-
});
|
|
247
|
+
}));
|
|
224
248
|
|
|
225
249
|
/**
|
|
226
250
|
* One row of the structured index injection table.
|
|
227
251
|
* @typedef {{kind: "style", text: string} | {kind: "html", placement: "head", html: string}} StyleRow
|
|
228
252
|
*/
|
|
229
253
|
|
|
254
|
+
/**
|
|
255
|
+
* The cosmokit volatile-reference protocol: a parsed config field may be a
|
|
256
|
+
* frozen `{ get(), [write](value) }` reference rather than a plain value.
|
|
257
|
+
*
|
|
258
|
+
* DSH 0.1.7-alpha.x marks the plugin's whole schema `.volatile()` so a settings
|
|
259
|
+
* write can land without remounting the entry, and the loader then hands this
|
|
260
|
+
* plugin a config whose fields ARE those references. Reading them as plain data
|
|
261
|
+
* (which `normalizeConfig` expects) would see objects where strings belong and
|
|
262
|
+
* emit nothing, so the references are unwrapped first. On the 0.1.5-rc.x line
|
|
263
|
+
* no field is a reference and this is a plain deep copy.
|
|
264
|
+
*/
|
|
265
|
+
const VOLATILE_WRITE = Symbol.for("cosmokit.volatile.write");
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Replace every volatile reference in a parsed config with its current value.
|
|
269
|
+
*
|
|
270
|
+
* Exported because it is half of this plugin's host-line contract: a caller that
|
|
271
|
+
* wants to read a resolved config — a test, or a later composition — has to go
|
|
272
|
+
* through it, since on the alpha a parsed field is a reference rather than data.
|
|
273
|
+
* @param {unknown} value - a parsed config value, or a reference to one.
|
|
274
|
+
* @returns {unknown} the same shape, references unwrapped.
|
|
275
|
+
*/
|
|
276
|
+
export function plainConfigValue(value) {
|
|
277
|
+
if (value === null || typeof value !== "object") return value;
|
|
278
|
+
if (VOLATILE_WRITE in value && typeof value.get === "function") {
|
|
279
|
+
return plainConfigValue(value.get());
|
|
280
|
+
}
|
|
281
|
+
if (Array.isArray(value)) return value.map(plainConfigValue);
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const [key, child] of Object.entries(value)) out[key] = plainConfigValue(child);
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
|
|
230
287
|
/**
|
|
231
288
|
* The style row carrying one configuration.
|
|
232
289
|
*
|
|
@@ -240,7 +297,7 @@ export const Config = z.object({
|
|
|
240
297
|
* @returns {StyleRow} the row the web server renders into `<head>`.
|
|
241
298
|
*/
|
|
242
299
|
function styleRow(resolved) {
|
|
243
|
-
const cfg = normalizeConfig(resolved);
|
|
300
|
+
const cfg = normalizeConfig(plainConfigValue(resolved));
|
|
244
301
|
// 0.2.0 ships the per-theme editor out; light and dark stay one value set
|
|
245
302
|
// (the dark fields remain in the schema, dormant, for a future release).
|
|
246
303
|
cfg[PER_THEME_FIELD] = false;
|
|
@@ -258,20 +315,47 @@ function styleRow(resolved) {
|
|
|
258
315
|
|
|
259
316
|
/**
|
|
260
317
|
* Register the settings section and keep the index in sync with it.
|
|
318
|
+
*
|
|
319
|
+
* TWO HOST DIALECTS. Up to DSH 0.1.5-rc.x the settings service registers a
|
|
320
|
+
* named section with a schema and reads it back by that name
|
|
321
|
+
* (`settings.installSection`), and the section name is what the browser half
|
|
322
|
+
* binds. From 0.1.7-alpha.x the service derives one form per composed entry and
|
|
323
|
+
* addresses it by the PROFILE ENTRY ID — there is no `installSection` at all,
|
|
324
|
+
* and an instance announces its page policy with `settings.configure` instead.
|
|
325
|
+
* Both are pure registrations that the browser half then reads through its own
|
|
326
|
+
* service, so which one ran does not change anything else here.
|
|
327
|
+
*
|
|
261
328
|
* @param {object} ctx - host cordis context.
|
|
262
329
|
* @param {unknown} config - the plugin entry's composition config (base layer).
|
|
263
330
|
*/
|
|
264
331
|
export function apply(ctx, config) {
|
|
265
332
|
let current = () => config;
|
|
266
333
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
267
|
-
settingsCtx.settings
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
334
|
+
const settings = settingsCtx.settings;
|
|
335
|
+
if (settings && typeof settings.installSection === "function") {
|
|
336
|
+
settings.installSection(ctx, NAMESPACE, Config, config ?? {}, {
|
|
337
|
+
setSource: (source) => {
|
|
338
|
+
current = source;
|
|
339
|
+
},
|
|
340
|
+
// The row table is rebuilt on every index render and every worker boot
|
|
341
|
+
// payload, so each read is already fresh; nothing to invalidate here.
|
|
342
|
+
onChange: () => {},
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (settings && typeof settings.configure === "function") {
|
|
347
|
+
// `auto: true` keeps the host's generated page as the fallback wherever
|
|
348
|
+
// no custom page of ours is reachable.
|
|
349
|
+
settingsCtx.effect(() => settings.configure({ auto: true }, ctx.fiber));
|
|
350
|
+
// This dialect has no registered reader: the entry's live, merged value
|
|
351
|
+
// is the fiber's own resolved config, which is what the settings service
|
|
352
|
+
// shows a form as well. The browser half adopts and rewrites the served
|
|
353
|
+
// element on activation either way, so this only decides the first frame.
|
|
354
|
+
current = () => {
|
|
355
|
+
const live = ctx.fiber && ctx.fiber.config;
|
|
356
|
+
return live === undefined || live === null ? config : live;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
275
359
|
});
|
|
276
360
|
ctx.on("webserver/index-inject", (table) => {
|
|
277
361
|
table.push(styleRow(current()));
|