@aglyn/aglyn 1.0.0-beta.215 → 1.0.0-beta.217
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/package.json +11 -11
- package/src/lib/app-utils/deferred-image.d.ts +3 -3
- package/src/lib/app-utils/deferred-image.js +3 -3
- package/src/lib/app-utils/deferred-image.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-help.generated.js +2 -1
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +35 -21
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/marketplace-theme.d.ts +9 -4
- package/src/lib/app-utils/marketplace-theme.js +11 -1
- package/src/lib/app-utils/marketplace-theme.js.map +1 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +36 -1
- package/src/lib/app-utils/publisher-agreement.js +48 -1
- package/src/lib/app-utils/publisher-agreement.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/theme-library.d.ts +206 -0
- package/src/lib/app-utils/theme-library.js +566 -0
- package/src/lib/app-utils/theme-library.js.map +1 -0
- package/src/lib/foundation/definitions/platform.types.js +2 -1
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +44 -0
- package/src/lib/plugin-manager/feature-plugins.js +25 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +11 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-services.js +20 -2
- package/src/lib/plugin-manager/plugin-services.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.js +12 -3
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { diffOverride, isEmptyOverride, overrideWriteValue, readArtifactOverride, resolveOverride } from "./marketplace-overrides.js";
|
|
18
|
+
import { resolveSiteTheme, themeArtifactContent } from "./marketplace-theme.js";
|
|
19
|
+
/**
|
|
20
|
+
* A site's theme library (AGL-3404): the themes a site can switch between,
|
|
21
|
+
* and the rule that switching, editing and saving never destroy one.
|
|
22
|
+
*
|
|
23
|
+
* ## Three layers, one of them picked
|
|
24
|
+
*
|
|
25
|
+
* The site renders `theme ⊕ themeOverride` (`resolveSiteTheme`), exactly as
|
|
26
|
+
* it did before there was a library. What the library adds is WHERE `theme`
|
|
27
|
+
* came from, named by `themeSelection`:
|
|
28
|
+
*
|
|
29
|
+
* - `default` — the platform default. `theme` is empty.
|
|
30
|
+
* - `preset` — a built-in theme a plugin contributes. `theme` is a copy.
|
|
31
|
+
* - `custom` — one of the site's own saved themes, kept in the library.
|
|
32
|
+
* - `installed` — a marketplace theme, kept in the library with the
|
|
33
|
+
* provenance an update needs.
|
|
34
|
+
*
|
|
35
|
+
* Every edit is an override patch over the picked theme and never a write to
|
|
36
|
+
* it, so "Restore" is deleting the patch and the theme it restores to is
|
|
37
|
+
* still exactly the one that was picked. "Save as custom theme" copies the
|
|
38
|
+
* resolved result into a new library entry; the theme it started from stays
|
|
39
|
+
* as it was.
|
|
40
|
+
*
|
|
41
|
+
* ## Switching keeps each theme's own edits
|
|
42
|
+
*
|
|
43
|
+
* Leaving a theme stashes its override on its library entry, and coming back
|
|
44
|
+
* restores it — so trying another theme is not a way to lose work. A preset
|
|
45
|
+
* or the default has no content to keep, so its entry holds only the stash.
|
|
46
|
+
*
|
|
47
|
+
* ## The site's own settings travel with the site
|
|
48
|
+
*
|
|
49
|
+
* `darkScheme` is a decision about the site's content, not about a design
|
|
50
|
+
* (`THEME_ARTIFACT_FIELDS` says the same for publishing), so it survives every
|
|
51
|
+
* switch, save and restore: whichever theme is picked, a site that switched
|
|
52
|
+
* dark off stays light.
|
|
53
|
+
*
|
|
54
|
+
* Pure: the console route and the marketplace install route both plan here
|
|
55
|
+
* and write the plan in their own transaction, so neither can file a switch
|
|
56
|
+
* differently from the other.
|
|
57
|
+
*/ /** The host subcollection the library lives in. */ export const THEME_LIBRARY_COLLECTION = 'themes';
|
|
58
|
+
/**
|
|
59
|
+
* The most custom themes one site keeps. A client can read the library, so an
|
|
60
|
+
* unbounded one is an unbounded read on every visit to the theme page.
|
|
61
|
+
*/ export const THEME_LIBRARY_MAX_CUSTOM = 25;
|
|
62
|
+
/** The longest name a saved theme takes. */ export const THEME_NAME_MAX = 60;
|
|
63
|
+
const KINDS = [
|
|
64
|
+
'default',
|
|
65
|
+
'preset',
|
|
66
|
+
'custom',
|
|
67
|
+
'installed'
|
|
68
|
+
];
|
|
69
|
+
/** The entry id the platform default's stash lives under. */ export const DEFAULT_THEME_ENTRY_ID = 'default';
|
|
70
|
+
/**
|
|
71
|
+
* The entry id a pre-library site's own theme is filed under when it first
|
|
72
|
+
* leaves it. Before the library a site had exactly one theme of its own, so
|
|
73
|
+
* a fixed id cannot collide with another.
|
|
74
|
+
*/ export const SITE_THEME_ENTRY_ID = 'site-theme';
|
|
75
|
+
/** The default's name when nothing else names it. */ export const DEFAULT_THEME_NAME = 'Material UI';
|
|
76
|
+
/** A preset's entry id, which holds only its stash. */ export function presetEntryId(presetId) {
|
|
77
|
+
return `preset--${presetId.replace(/[/]/g, '_')}`;
|
|
78
|
+
}
|
|
79
|
+
/** A marketplace theme's entry id: one per listing, so reinstalling updates it. */ export function installedEntryId(listingId) {
|
|
80
|
+
return `installed--${listingId.replace(/[/]/g, '_')}`;
|
|
81
|
+
}
|
|
82
|
+
function isRecord(value) {
|
|
83
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
function hasContent(theme) {
|
|
86
|
+
return Boolean(theme && Object.keys(theme).length);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The theme a site has picked.
|
|
90
|
+
*
|
|
91
|
+
* A site from before the library has no `themeSelection`, and reads as what
|
|
92
|
+
* it was: a marketplace theme, its own theme, or the default. That is what
|
|
93
|
+
* lets the library ship without touching a single existing host document.
|
|
94
|
+
*/ export function readThemeSelection(host) {
|
|
95
|
+
var _host_themeInstalledFrom;
|
|
96
|
+
const stored = host == null ? void 0 : host.themeSelection;
|
|
97
|
+
if (isRecord(stored) && KINDS.includes(stored['kind']) && typeof stored['id'] === 'string' && stored['id']) {
|
|
98
|
+
return {
|
|
99
|
+
kind: stored['kind'],
|
|
100
|
+
id: stored['id'],
|
|
101
|
+
name: typeof stored['name'] === 'string' && stored['name'] ? stored['name'] : stored['kind'] === 'default' ? DEFAULT_THEME_NAME : 'Theme'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const listingId = host == null ? void 0 : (_host_themeInstalledFrom = host.themeInstalledFrom) == null ? void 0 : _host_themeInstalledFrom.listingId;
|
|
105
|
+
if (listingId) {
|
|
106
|
+
var _host_themeInstalledFrom1;
|
|
107
|
+
const name = host == null ? void 0 : (_host_themeInstalledFrom1 = host.themeInstalledFrom) == null ? void 0 : _host_themeInstalledFrom1.name;
|
|
108
|
+
return {
|
|
109
|
+
kind: 'installed',
|
|
110
|
+
id: installedEntryId(listingId),
|
|
111
|
+
name: typeof name === 'string' && name ? name : 'Marketplace theme'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (hasContent(host == null ? void 0 : host.theme)) {
|
|
115
|
+
return {
|
|
116
|
+
kind: 'custom',
|
|
117
|
+
id: SITE_THEME_ENTRY_ID,
|
|
118
|
+
name: 'Site theme'
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
kind: 'default',
|
|
123
|
+
id: DEFAULT_THEME_ENTRY_ID,
|
|
124
|
+
name: DEFAULT_THEME_NAME
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** A name as stored: trimmed, bounded, and never empty. */ export function normalizeThemeName(name) {
|
|
128
|
+
if (typeof name !== 'string') return null;
|
|
129
|
+
const trimmed = name.replace(/\s+/g, ' ').trim();
|
|
130
|
+
if (!trimmed) return null;
|
|
131
|
+
return trimmed.slice(0, THEME_NAME_MAX);
|
|
132
|
+
}
|
|
133
|
+
/** Marks a field for deletion; the route writing the plan maps it to its store's delete. */ export const THEME_FIELD_DELETE = '__aglynThemeFieldDelete';
|
|
134
|
+
/**
|
|
135
|
+
* The override to store for a theme showing `edited` over `base`, or the
|
|
136
|
+
* delete marker when they are the same.
|
|
137
|
+
*/ function overrideValue(base, edited, baseSha256) {
|
|
138
|
+
const patch = diffOverride(base, edited);
|
|
139
|
+
const value = overrideWriteValue(patch, baseSha256);
|
|
140
|
+
return value && !isEmptyOverride(value.patch) ? value : THEME_FIELD_DELETE;
|
|
141
|
+
}
|
|
142
|
+
/** The site's own setting carried onto another theme. */ function withSiteSettings(theme, from) {
|
|
143
|
+
const next = _extends({}, theme);
|
|
144
|
+
if ((from == null ? void 0 : from.darkScheme) === 'off') next.darkScheme = 'off';
|
|
145
|
+
else delete next.darkScheme;
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
/** A stash as it resolves over the theme it belongs to. */ function resolveStash(base, stash) {
|
|
149
|
+
const read = readArtifactOverride({
|
|
150
|
+
overrides: stash
|
|
151
|
+
});
|
|
152
|
+
return read ? resolveOverride(base, read.patch) : base;
|
|
153
|
+
}
|
|
154
|
+
function sha(installedFrom) {
|
|
155
|
+
return isRecord(installedFrom) && typeof installedFrom['sha256'] === 'string' ? installedFrom['sha256'] : null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Files the theme being left: its override goes onto its entry, and a theme
|
|
159
|
+
* that exists nowhere but on the host (a pre-library site's own theme, or a
|
|
160
|
+
* marketplace install from before the library) gets an entry of its own
|
|
161
|
+
* first, so it is still in the list to come back to.
|
|
162
|
+
*
|
|
163
|
+
* `override` is what to stash — the live one when switching, none when the
|
|
164
|
+
* edits have just moved into a new theme.
|
|
165
|
+
*/ function fileCurrent(host, current, entries, override) {
|
|
166
|
+
const stash = override === undefined ? THEME_FIELD_DELETE : override;
|
|
167
|
+
if (entries[current.id]) {
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
id: current.id,
|
|
171
|
+
op: 'update',
|
|
172
|
+
data: {
|
|
173
|
+
override: stash
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
// The default and a preset have no content to keep, so with nothing to
|
|
179
|
+
// stash there is nothing to file.
|
|
180
|
+
if (stash === THEME_FIELD_DELETE && (current.kind === 'default' || current.kind === 'preset')) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
const entry = {
|
|
184
|
+
kind: current.kind,
|
|
185
|
+
name: current.name
|
|
186
|
+
};
|
|
187
|
+
if (current.kind === 'custom' || current.kind === 'installed') {
|
|
188
|
+
entry.theme = themeArtifactContent(host.theme);
|
|
189
|
+
}
|
|
190
|
+
if (current.kind === 'installed' && host.themeInstalledFrom) {
|
|
191
|
+
entry.installedFrom = host.themeInstalledFrom;
|
|
192
|
+
}
|
|
193
|
+
if (stash !== THEME_FIELD_DELETE) entry.override = stash;
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
id: current.id,
|
|
197
|
+
op: 'create',
|
|
198
|
+
data: entry
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
/** Host fields that point the site at `selection` over `base`. */ function pointAt(selection, base, override, installedFrom) {
|
|
203
|
+
return {
|
|
204
|
+
theme: hasContent(base) ? base : THEME_FIELD_DELETE,
|
|
205
|
+
themeOverride: override,
|
|
206
|
+
themeSelection: selection,
|
|
207
|
+
themeInstalledFrom: selection.kind === 'installed' && installedFrom ? installedFrom : THEME_FIELD_DELETE,
|
|
208
|
+
// The library is the way back now: every theme a site has left is in it,
|
|
209
|
+
// edits included. A `themeReplaced` left behind would offer a second,
|
|
210
|
+
// older "previous theme" that restores around the library.
|
|
211
|
+
themeReplaced: THEME_FIELD_DELETE
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Plans one library action against the host document and its library as
|
|
216
|
+
* read in the same transaction.
|
|
217
|
+
*
|
|
218
|
+
* `newId` mints a library entry id for a new custom theme.
|
|
219
|
+
*/ export function planThemeLibraryAction(input) {
|
|
220
|
+
var _input_host, _resolveSiteTheme;
|
|
221
|
+
const host = (_input_host = input.host) != null ? _input_host : {};
|
|
222
|
+
const { entries, action } = input;
|
|
223
|
+
const current = readThemeSelection(host);
|
|
224
|
+
const resolved = (_resolveSiteTheme = resolveSiteTheme(host)) != null ? _resolveSiteTheme : {};
|
|
225
|
+
switch(action.action){
|
|
226
|
+
case 'select':
|
|
227
|
+
return planSelect(host, current, resolved, entries, action.target);
|
|
228
|
+
case 'install':
|
|
229
|
+
{
|
|
230
|
+
var _normalizeThemeName;
|
|
231
|
+
var _entries_id;
|
|
232
|
+
const id = installedEntryId(action.listingId);
|
|
233
|
+
const name = (_normalizeThemeName = normalizeThemeName(action.name)) != null ? _normalizeThemeName : 'Marketplace theme';
|
|
234
|
+
const theme = themeArtifactContent(action.theme);
|
|
235
|
+
const selection = {
|
|
236
|
+
kind: 'installed',
|
|
237
|
+
id,
|
|
238
|
+
name
|
|
239
|
+
};
|
|
240
|
+
if (current.kind === 'installed' && current.id === id) {
|
|
241
|
+
// An update of the theme already picked. The override stays exactly
|
|
242
|
+
// as stored — including the hash it was written against, which is
|
|
243
|
+
// how the editor knows to say an override predates this version.
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
host: {
|
|
247
|
+
theme,
|
|
248
|
+
themeInstalledFrom: action.installedFrom,
|
|
249
|
+
themeSelection: selection
|
|
250
|
+
},
|
|
251
|
+
entries: [
|
|
252
|
+
entries[id] ? {
|
|
253
|
+
id,
|
|
254
|
+
op: 'update',
|
|
255
|
+
data: {
|
|
256
|
+
theme,
|
|
257
|
+
installedFrom: action.installedFrom,
|
|
258
|
+
name
|
|
259
|
+
}
|
|
260
|
+
} : {
|
|
261
|
+
id,
|
|
262
|
+
op: 'create',
|
|
263
|
+
data: {
|
|
264
|
+
kind: 'installed',
|
|
265
|
+
name,
|
|
266
|
+
theme,
|
|
267
|
+
installedFrom: action.installedFrom
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
selection
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const stash = (_entries_id = entries[id]) == null ? void 0 : _entries_id.override;
|
|
275
|
+
const edited = withSiteSettings(resolveStash(theme, stash), resolved);
|
|
276
|
+
return {
|
|
277
|
+
ok: true,
|
|
278
|
+
host: pointAt(selection, theme, overrideValue(theme, edited, sha(action.installedFrom)), action.installedFrom),
|
|
279
|
+
entries: [
|
|
280
|
+
...fileCurrent(host, current, entries, host.themeOverride),
|
|
281
|
+
entries[id] ? {
|
|
282
|
+
id,
|
|
283
|
+
op: 'update',
|
|
284
|
+
data: {
|
|
285
|
+
theme,
|
|
286
|
+
installedFrom: action.installedFrom,
|
|
287
|
+
name,
|
|
288
|
+
override: THEME_FIELD_DELETE
|
|
289
|
+
}
|
|
290
|
+
} : {
|
|
291
|
+
id,
|
|
292
|
+
op: 'create',
|
|
293
|
+
data: {
|
|
294
|
+
kind: 'installed',
|
|
295
|
+
name,
|
|
296
|
+
theme,
|
|
297
|
+
installedFrom: action.installedFrom
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
selection
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
case 'save-as':
|
|
305
|
+
{
|
|
306
|
+
const name = normalizeThemeName(action.name);
|
|
307
|
+
if (!name) {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
status: 400,
|
|
311
|
+
error: 'Give the theme a name.'
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const customCount = Object.values(entries).filter((entry)=>entry.kind === 'custom').length;
|
|
315
|
+
if (customCount >= THEME_LIBRARY_MAX_CUSTOM) {
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
status: 409,
|
|
319
|
+
error: `A site keeps up to ${THEME_LIBRARY_MAX_CUSTOM} custom themes. ` + 'Delete one you no longer use first.'
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const id = input.newId();
|
|
323
|
+
const theme = themeArtifactContent(resolved);
|
|
324
|
+
const selection = {
|
|
325
|
+
kind: 'custom',
|
|
326
|
+
id,
|
|
327
|
+
name
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
ok: true,
|
|
331
|
+
host: pointAt(selection, theme, overrideValue(theme, withSiteSettings(theme, resolved), null), null),
|
|
332
|
+
entries: [
|
|
333
|
+
// The theme this started from stays as it was picked: the edits
|
|
334
|
+
// now live in the new theme, not on top of the old one.
|
|
335
|
+
...fileCurrent(host, current, entries, undefined),
|
|
336
|
+
{
|
|
337
|
+
id,
|
|
338
|
+
op: 'create',
|
|
339
|
+
data: {
|
|
340
|
+
kind: 'custom',
|
|
341
|
+
name,
|
|
342
|
+
theme
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
selection
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
case 'update':
|
|
350
|
+
{
|
|
351
|
+
if (current.kind !== 'custom') {
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
status: 409,
|
|
355
|
+
error: 'Only your own themes can be updated. Save your changes as a ' + 'custom theme instead.'
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const theme = themeArtifactContent(resolved);
|
|
359
|
+
const override = overrideValue(theme, withSiteSettings(theme, resolved), null);
|
|
360
|
+
return {
|
|
361
|
+
ok: true,
|
|
362
|
+
host: {
|
|
363
|
+
theme: hasContent(theme) ? theme : THEME_FIELD_DELETE,
|
|
364
|
+
themeOverride: override,
|
|
365
|
+
themeSelection: current
|
|
366
|
+
},
|
|
367
|
+
entries: [
|
|
368
|
+
entries[current.id] ? {
|
|
369
|
+
id: current.id,
|
|
370
|
+
op: 'update',
|
|
371
|
+
data: {
|
|
372
|
+
theme
|
|
373
|
+
}
|
|
374
|
+
} : {
|
|
375
|
+
id: current.id,
|
|
376
|
+
op: 'create',
|
|
377
|
+
data: {
|
|
378
|
+
kind: 'custom',
|
|
379
|
+
name: current.name,
|
|
380
|
+
theme
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
selection: current
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
case 'restore':
|
|
388
|
+
{
|
|
389
|
+
var _host_theme;
|
|
390
|
+
const base = (_host_theme = host.theme) != null ? _host_theme : {};
|
|
391
|
+
return {
|
|
392
|
+
ok: true,
|
|
393
|
+
host: {
|
|
394
|
+
themeOverride: overrideValue(base, withSiteSettings(base, resolved), current.kind === 'installed' ? sha(host.themeInstalledFrom) : null)
|
|
395
|
+
},
|
|
396
|
+
entries: [],
|
|
397
|
+
selection: current
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
case 'rename':
|
|
401
|
+
{
|
|
402
|
+
const name = normalizeThemeName(action.name);
|
|
403
|
+
if (!name) {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
status: 400,
|
|
407
|
+
error: 'Give the theme a name.'
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const isLegacySiteTheme = action.id === SITE_THEME_ENTRY_ID && current.id === SITE_THEME_ENTRY_ID && !entries[SITE_THEME_ENTRY_ID];
|
|
411
|
+
const entry = entries[action.id];
|
|
412
|
+
if (!isLegacySiteTheme && (entry == null ? void 0 : entry.kind) !== 'custom') {
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
status: 404,
|
|
416
|
+
error: 'Only your own themes can be renamed.'
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const renamedSelection = current.id === action.id ? _extends({}, current, {
|
|
420
|
+
name
|
|
421
|
+
}) : null;
|
|
422
|
+
return {
|
|
423
|
+
ok: true,
|
|
424
|
+
host: renamedSelection ? {
|
|
425
|
+
themeSelection: renamedSelection
|
|
426
|
+
} : {},
|
|
427
|
+
entries: isLegacySiteTheme ? [
|
|
428
|
+
{
|
|
429
|
+
id: action.id,
|
|
430
|
+
op: 'create',
|
|
431
|
+
data: {
|
|
432
|
+
kind: 'custom',
|
|
433
|
+
name,
|
|
434
|
+
theme: themeArtifactContent(host.theme)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
] : [
|
|
438
|
+
{
|
|
439
|
+
id: action.id,
|
|
440
|
+
op: 'update',
|
|
441
|
+
data: {
|
|
442
|
+
name
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
],
|
|
446
|
+
selection: renamedSelection != null ? renamedSelection : current
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
case 'delete':
|
|
450
|
+
{
|
|
451
|
+
const entry = entries[action.id];
|
|
452
|
+
if (!entry || entry.kind !== 'custom' && entry.kind !== 'installed') {
|
|
453
|
+
return {
|
|
454
|
+
ok: false,
|
|
455
|
+
status: 404,
|
|
456
|
+
error: 'There is no such theme to delete.'
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (current.id === action.id) {
|
|
460
|
+
return {
|
|
461
|
+
ok: false,
|
|
462
|
+
status: 409,
|
|
463
|
+
error: 'This theme is in use. Pick another theme first, then delete it.'
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
ok: true,
|
|
468
|
+
host: {},
|
|
469
|
+
entries: [
|
|
470
|
+
{
|
|
471
|
+
id: action.id,
|
|
472
|
+
op: 'delete'
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
selection: current
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function planSelect(host, current, resolved, entries, target) {
|
|
481
|
+
var _entries_selection_id, _entries_selection_id1;
|
|
482
|
+
let selection;
|
|
483
|
+
let base;
|
|
484
|
+
let installedFrom = null;
|
|
485
|
+
if (target.kind === 'default') {
|
|
486
|
+
selection = {
|
|
487
|
+
kind: 'default',
|
|
488
|
+
id: DEFAULT_THEME_ENTRY_ID,
|
|
489
|
+
name: DEFAULT_THEME_NAME
|
|
490
|
+
};
|
|
491
|
+
base = {};
|
|
492
|
+
} else if (target.kind === 'preset') {
|
|
493
|
+
const name = normalizeThemeName(target.name);
|
|
494
|
+
if (!target.id || !name || !isRecord(target.theme)) {
|
|
495
|
+
return {
|
|
496
|
+
ok: false,
|
|
497
|
+
status: 400,
|
|
498
|
+
error: 'That theme could not be read.'
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
selection = {
|
|
502
|
+
kind: 'preset',
|
|
503
|
+
id: presetEntryId(target.id),
|
|
504
|
+
name
|
|
505
|
+
};
|
|
506
|
+
base = themeArtifactContent(target.theme);
|
|
507
|
+
} else {
|
|
508
|
+
var _entry_theme, _entry_installedFrom;
|
|
509
|
+
const entry = entries[target.id];
|
|
510
|
+
if (!entry || entry.kind !== target.kind) {
|
|
511
|
+
return {
|
|
512
|
+
ok: false,
|
|
513
|
+
status: 404,
|
|
514
|
+
error: 'That theme is not in this site’s library.'
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
selection = {
|
|
518
|
+
kind: entry.kind,
|
|
519
|
+
id: target.id,
|
|
520
|
+
name: entry.name
|
|
521
|
+
};
|
|
522
|
+
base = themeArtifactContent((_entry_theme = entry.theme) != null ? _entry_theme : {});
|
|
523
|
+
installedFrom = (_entry_installedFrom = entry.installedFrom) != null ? _entry_installedFrom : null;
|
|
524
|
+
}
|
|
525
|
+
if (selection.id === current.id && selection.kind === current.kind) {
|
|
526
|
+
return {
|
|
527
|
+
ok: true,
|
|
528
|
+
host: {},
|
|
529
|
+
entries: [],
|
|
530
|
+
selection: current
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const stash = (_entries_selection_id = entries[selection.id]) == null ? void 0 : _entries_selection_id.override;
|
|
534
|
+
const edited = withSiteSettings(resolveStash(base, stash), resolved);
|
|
535
|
+
const writes = fileCurrent(host, current, entries, host.themeOverride);
|
|
536
|
+
// Its stash is live again, so the entry stops holding a second copy.
|
|
537
|
+
if (((_entries_selection_id1 = entries[selection.id]) == null ? void 0 : _entries_selection_id1.override) !== undefined) {
|
|
538
|
+
writes.push({
|
|
539
|
+
id: selection.id,
|
|
540
|
+
op: 'update',
|
|
541
|
+
data: {
|
|
542
|
+
override: THEME_FIELD_DELETE
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
ok: true,
|
|
548
|
+
host: pointAt(selection, base, overrideValue(base, edited, selection.kind === 'installed' ? sha(installedFrom) : null), installedFrom),
|
|
549
|
+
entries: writes,
|
|
550
|
+
selection
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Whether the picked theme carries edits of this site's own — what "Restore"
|
|
555
|
+
* and "Update" act on. The site's dark-scheme setting is not an edit to the
|
|
556
|
+
* theme, so an override holding only that does not count.
|
|
557
|
+
*/ export function hasThemeEdits(host) {
|
|
558
|
+
const read = readArtifactOverride({
|
|
559
|
+
overrides: host == null ? void 0 : host.themeOverride
|
|
560
|
+
});
|
|
561
|
+
if (!read || isEmptyOverride(read.patch)) return false;
|
|
562
|
+
if (!isRecord(read.patch)) return true;
|
|
563
|
+
return Object.keys(read.patch).some((key)=>key !== 'darkScheme');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
//# sourceMappingURL=theme-library.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/theme-library.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostTheme } from '@aglyn/shared-data-types'\nimport {\n diffOverride,\n isEmptyOverride,\n overrideWriteValue,\n readArtifactOverride,\n resolveOverride,\n} from './marketplace-overrides'\nimport {\n resolveSiteTheme,\n themeArtifactContent,\n type ThemeHostDocument,\n} from './marketplace-theme'\n\n/**\n * A site's theme library (AGL-3404): the themes a site can switch between,\n * and the rule that switching, editing and saving never destroy one.\n *\n * ## Three layers, one of them picked\n *\n * The site renders `theme ⊕ themeOverride` (`resolveSiteTheme`), exactly as\n * it did before there was a library. What the library adds is WHERE `theme`\n * came from, named by `themeSelection`:\n *\n * - `default` — the platform default. `theme` is empty.\n * - `preset` — a built-in theme a plugin contributes. `theme` is a copy.\n * - `custom` — one of the site's own saved themes, kept in the library.\n * - `installed` — a marketplace theme, kept in the library with the\n * provenance an update needs.\n *\n * Every edit is an override patch over the picked theme and never a write to\n * it, so \"Restore\" is deleting the patch and the theme it restores to is\n * still exactly the one that was picked. \"Save as custom theme\" copies the\n * resolved result into a new library entry; the theme it started from stays\n * as it was.\n *\n * ## Switching keeps each theme's own edits\n *\n * Leaving a theme stashes its override on its library entry, and coming back\n * restores it — so trying another theme is not a way to lose work. A preset\n * or the default has no content to keep, so its entry holds only the stash.\n *\n * ## The site's own settings travel with the site\n *\n * `darkScheme` is a decision about the site's content, not about a design\n * (`THEME_ARTIFACT_FIELDS` says the same for publishing), so it survives every\n * switch, save and restore: whichever theme is picked, a site that switched\n * dark off stays light.\n *\n * Pure: the console route and the marketplace install route both plan here\n * and write the plan in their own transaction, so neither can file a switch\n * differently from the other.\n */\n\n/** The host subcollection the library lives in. */\nexport const THEME_LIBRARY_COLLECTION = 'themes'\n\n/**\n * The most custom themes one site keeps. A client can read the library, so an\n * unbounded one is an unbounded read on every visit to the theme page.\n */\nexport const THEME_LIBRARY_MAX_CUSTOM = 25\n\n/** The longest name a saved theme takes. */\nexport const THEME_NAME_MAX = 60\n\nexport type ThemeLibraryKind = 'default' | 'preset' | 'custom' | 'installed'\n\nconst KINDS: readonly ThemeLibraryKind[] = [\n 'default',\n 'preset',\n 'custom',\n 'installed',\n]\n\n/** What `themeSelection` on a host names. */\nexport interface HostThemeSelection {\n kind: ThemeLibraryKind\n /** The library entry id (or the preset's entry id). */\n id: string\n /** Shown in the picker; a copy, so a renamed entry renames the selection too. */\n name: string\n}\n\n/** A document in `hosts/{hostId}/themes`. */\nexport interface ThemeLibraryEntry {\n kind: ThemeLibraryKind\n name: string\n /** The theme itself, for `custom` and `installed`. Never edited in place by the editor. */\n theme?: HostTheme | null\n /** The override stashed while this theme is not the picked one. */\n override?: unknown\n /** Marketplace provenance, for `installed` — what `themeInstalledFrom` is restored from. */\n installedFrom?: Record<string, unknown> | null\n createdAt?: unknown\n updatedAt?: unknown\n}\n\n/** The entry id the platform default's stash lives under. */\nexport const DEFAULT_THEME_ENTRY_ID = 'default'\n\n/**\n * The entry id a pre-library site's own theme is filed under when it first\n * leaves it. Before the library a site had exactly one theme of its own, so\n * a fixed id cannot collide with another.\n */\nexport const SITE_THEME_ENTRY_ID = 'site-theme'\n\n/** The default's name when nothing else names it. */\nexport const DEFAULT_THEME_NAME = 'Material UI'\n\n/** A preset's entry id, which holds only its stash. */\nexport function presetEntryId(presetId: string): string {\n return `preset--${presetId.replace(/[/]/g, '_')}`\n}\n\n/** A marketplace theme's entry id: one per listing, so reinstalling updates it. */\nexport function installedEntryId(listingId: string): string {\n return `installed--${listingId.replace(/[/]/g, '_')}`\n}\n\n/** A host document as the library reads it. */\nexport interface ThemeLibraryHost extends ThemeHostDocument {\n themeSelection?: unknown\n themeInstalledFrom?:\n | ({ sha256?: string | null; listingId?: string; name?: string } & Record<\n string,\n unknown\n >)\n | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction hasContent(theme: HostTheme | null | undefined): theme is HostTheme {\n return Boolean(theme && Object.keys(theme).length)\n}\n\n/**\n * The theme a site has picked.\n *\n * A site from before the library has no `themeSelection`, and reads as what\n * it was: a marketplace theme, its own theme, or the default. That is what\n * lets the library ship without touching a single existing host document.\n */\nexport function readThemeSelection(\n host: ThemeLibraryHost | null | undefined,\n): HostThemeSelection {\n const stored = host?.themeSelection\n if (\n isRecord(stored) &&\n KINDS.includes(stored['kind'] as ThemeLibraryKind) &&\n typeof stored['id'] === 'string' &&\n stored['id']\n ) {\n return {\n kind: stored['kind'] as ThemeLibraryKind,\n id: stored['id'],\n name:\n typeof stored['name'] === 'string' && stored['name']\n ? stored['name']\n : stored['kind'] === 'default'\n ? DEFAULT_THEME_NAME\n : 'Theme',\n }\n }\n const listingId = host?.themeInstalledFrom?.listingId\n if (listingId) {\n const name = host?.themeInstalledFrom?.name\n return {\n kind: 'installed',\n id: installedEntryId(listingId),\n name: typeof name === 'string' && name ? name : 'Marketplace theme',\n }\n }\n if (hasContent(host?.theme)) {\n return { kind: 'custom', id: SITE_THEME_ENTRY_ID, name: 'Site theme' }\n }\n return {\n kind: 'default',\n id: DEFAULT_THEME_ENTRY_ID,\n name: DEFAULT_THEME_NAME,\n }\n}\n\n/** A name as stored: trimmed, bounded, and never empty. */\nexport function normalizeThemeName(name: unknown): string | null {\n if (typeof name !== 'string') return null\n const trimmed = name.replace(/\\s+/g, ' ').trim()\n if (!trimmed) return null\n return trimmed.slice(0, THEME_NAME_MAX)\n}\n\n/** Marks a field for deletion; the route writing the plan maps it to its store's delete. */\nexport const THEME_FIELD_DELETE = '__aglynThemeFieldDelete'\n\nexport type ThemeLibraryEntryWrite =\n | { id: string; op: 'create'; data: ThemeLibraryEntry }\n | { id: string; op: 'update'; data: Record<string, unknown> }\n | { id: string; op: 'delete' }\n\nexport type ThemeLibraryPlan =\n | {\n ok: true\n /**\n * Host fields to replace WHOLESALE (`update`, never a deep merge: a\n * merged override is the union of two patches, and a merged theme keeps\n * the tokens of the one it replaced).\n */\n host: Record<string, unknown>\n entries: ThemeLibraryEntryWrite[]\n /** The theme picked once the plan is written. */\n selection: HostThemeSelection\n }\n | { ok: false; status: number; error: string }\n\nexport type ThemeLibraryTarget =\n | { kind: 'default' }\n /** A plugin's theme, sent by the console that listed it. */\n | { kind: 'preset'; id: string; name: string; theme: HostTheme }\n | { kind: 'custom' | 'installed'; id: string }\n\nexport type ThemeLibraryAction =\n | { action: 'select'; target: ThemeLibraryTarget }\n | { action: 'save-as'; name: string }\n | { action: 'update' }\n | { action: 'restore' }\n | { action: 'rename'; id: string; name: string }\n | { action: 'delete'; id: string }\n | {\n action: 'install'\n listingId: string\n name: string\n theme: HostTheme\n installedFrom: Record<string, unknown>\n }\n\n/**\n * The override to store for a theme showing `edited` over `base`, or the\n * delete marker when they are the same.\n */\nfunction overrideValue(\n base: HostTheme,\n edited: HostTheme,\n baseSha256: string | null,\n): unknown {\n const patch = diffOverride(base, edited)\n const value = overrideWriteValue(patch, baseSha256)\n return value && !isEmptyOverride(value.patch) ? value : THEME_FIELD_DELETE\n}\n\n/** The site's own setting carried onto another theme. */\nfunction withSiteSettings(theme: HostTheme, from: HostTheme | undefined): HostTheme {\n const next = { ...theme }\n if (from?.darkScheme === 'off') next.darkScheme = 'off'\n else delete next.darkScheme\n return next\n}\n\n/** A stash as it resolves over the theme it belongs to. */\nfunction resolveStash(base: HostTheme, stash: unknown): HostTheme {\n const read = readArtifactOverride({ overrides: stash })\n return read ? resolveOverride<HostTheme>(base, read.patch) : base\n}\n\nfunction sha(installedFrom: unknown): string | null {\n return isRecord(installedFrom) && typeof installedFrom['sha256'] === 'string'\n ? installedFrom['sha256']\n : null\n}\n\n/**\n * Files the theme being left: its override goes onto its entry, and a theme\n * that exists nowhere but on the host (a pre-library site's own theme, or a\n * marketplace install from before the library) gets an entry of its own\n * first, so it is still in the list to come back to.\n *\n * `override` is what to stash — the live one when switching, none when the\n * edits have just moved into a new theme.\n */\nfunction fileCurrent(\n host: ThemeLibraryHost,\n current: HostThemeSelection,\n entries: Readonly<Record<string, ThemeLibraryEntry>>,\n override: unknown,\n): ThemeLibraryEntryWrite[] {\n const stash = override === undefined ? THEME_FIELD_DELETE : override\n if (entries[current.id]) {\n return [{ id: current.id, op: 'update', data: { override: stash } }]\n }\n // The default and a preset have no content to keep, so with nothing to\n // stash there is nothing to file.\n if (\n stash === THEME_FIELD_DELETE &&\n (current.kind === 'default' || current.kind === 'preset')\n ) {\n return []\n }\n const entry: ThemeLibraryEntry = { kind: current.kind, name: current.name }\n if (current.kind === 'custom' || current.kind === 'installed') {\n entry.theme = themeArtifactContent(host.theme)\n }\n if (current.kind === 'installed' && host.themeInstalledFrom) {\n entry.installedFrom = host.themeInstalledFrom\n }\n if (stash !== THEME_FIELD_DELETE) entry.override = stash\n return [{ id: current.id, op: 'create', data: entry }]\n}\n\n/** Host fields that point the site at `selection` over `base`. */\nfunction pointAt(\n selection: HostThemeSelection,\n base: HostTheme,\n override: unknown,\n installedFrom: unknown,\n): Record<string, unknown> {\n return {\n theme: hasContent(base) ? base : THEME_FIELD_DELETE,\n themeOverride: override,\n themeSelection: selection,\n themeInstalledFrom:\n selection.kind === 'installed' && installedFrom\n ? installedFrom\n : THEME_FIELD_DELETE,\n // The library is the way back now: every theme a site has left is in it,\n // edits included. A `themeReplaced` left behind would offer a second,\n // older \"previous theme\" that restores around the library.\n themeReplaced: THEME_FIELD_DELETE,\n }\n}\n\n/**\n * Plans one library action against the host document and its library as\n * read in the same transaction.\n *\n * `newId` mints a library entry id for a new custom theme.\n */\nexport function planThemeLibraryAction(input: {\n host: ThemeLibraryHost | null | undefined\n entries: Readonly<Record<string, ThemeLibraryEntry>>\n action: ThemeLibraryAction\n newId: () => string\n}): ThemeLibraryPlan {\n const host: ThemeLibraryHost = input.host ?? {}\n const { entries, action } = input\n const current = readThemeSelection(host)\n const resolved = resolveSiteTheme(host) ?? {}\n\n switch (action.action) {\n case 'select':\n return planSelect(host, current, resolved, entries, action.target)\n\n case 'install': {\n const id = installedEntryId(action.listingId)\n const name = normalizeThemeName(action.name) ?? 'Marketplace theme'\n const theme = themeArtifactContent(action.theme)\n const selection: HostThemeSelection = { kind: 'installed', id, name }\n if (current.kind === 'installed' && current.id === id) {\n // An update of the theme already picked. The override stays exactly\n // as stored — including the hash it was written against, which is\n // how the editor knows to say an override predates this version.\n return {\n ok: true,\n host: {\n theme,\n themeInstalledFrom: action.installedFrom,\n themeSelection: selection,\n },\n entries: [\n entries[id]\n ? {\n id,\n op: 'update',\n data: { theme, installedFrom: action.installedFrom, name },\n }\n : {\n id,\n op: 'create',\n data: {\n kind: 'installed',\n name,\n theme,\n installedFrom: action.installedFrom,\n },\n },\n ],\n selection,\n }\n }\n const stash = entries[id]?.override\n const edited = withSiteSettings(resolveStash(theme, stash), resolved)\n return {\n ok: true,\n host: pointAt(\n selection,\n theme,\n overrideValue(theme, edited, sha(action.installedFrom)),\n action.installedFrom,\n ),\n entries: [\n ...fileCurrent(host, current, entries, host.themeOverride),\n entries[id]\n ? {\n id,\n op: 'update',\n data: {\n theme,\n installedFrom: action.installedFrom,\n name,\n override: THEME_FIELD_DELETE,\n },\n }\n : {\n id,\n op: 'create',\n data: {\n kind: 'installed',\n name,\n theme,\n installedFrom: action.installedFrom,\n },\n },\n ],\n selection,\n }\n }\n\n case 'save-as': {\n const name = normalizeThemeName(action.name)\n if (!name) {\n return { ok: false, status: 400, error: 'Give the theme a name.' }\n }\n const customCount = Object.values(entries).filter(\n (entry) => entry.kind === 'custom',\n ).length\n if (customCount >= THEME_LIBRARY_MAX_CUSTOM) {\n return {\n ok: false,\n status: 409,\n error:\n `A site keeps up to ${THEME_LIBRARY_MAX_CUSTOM} custom themes. ` +\n 'Delete one you no longer use first.',\n }\n }\n const id = input.newId()\n const theme = themeArtifactContent(resolved)\n const selection: HostThemeSelection = { kind: 'custom', id, name }\n return {\n ok: true,\n host: pointAt(\n selection,\n theme,\n overrideValue(theme, withSiteSettings(theme, resolved), null),\n null,\n ),\n entries: [\n // The theme this started from stays as it was picked: the edits\n // now live in the new theme, not on top of the old one.\n ...fileCurrent(host, current, entries, undefined),\n { id, op: 'create', data: { kind: 'custom', name, theme } },\n ],\n selection,\n }\n }\n\n case 'update': {\n if (current.kind !== 'custom') {\n return {\n ok: false,\n status: 409,\n error:\n 'Only your own themes can be updated. Save your changes as a ' +\n 'custom theme instead.',\n }\n }\n const theme = themeArtifactContent(resolved)\n const override = overrideValue(\n theme,\n withSiteSettings(theme, resolved),\n null,\n )\n return {\n ok: true,\n host: {\n theme: hasContent(theme) ? theme : THEME_FIELD_DELETE,\n themeOverride: override,\n themeSelection: current,\n },\n entries: [\n entries[current.id]\n ? { id: current.id, op: 'update', data: { theme } }\n : {\n id: current.id,\n op: 'create',\n data: { kind: 'custom', name: current.name, theme },\n },\n ],\n selection: current,\n }\n }\n\n case 'restore': {\n const base = host.theme ?? {}\n return {\n ok: true,\n host: {\n themeOverride: overrideValue(\n base,\n withSiteSettings(base, resolved),\n current.kind === 'installed' ? sha(host.themeInstalledFrom) : null,\n ),\n },\n entries: [],\n selection: current,\n }\n }\n\n case 'rename': {\n const name = normalizeThemeName(action.name)\n if (!name) {\n return { ok: false, status: 400, error: 'Give the theme a name.' }\n }\n const isLegacySiteTheme =\n action.id === SITE_THEME_ENTRY_ID &&\n current.id === SITE_THEME_ENTRY_ID &&\n !entries[SITE_THEME_ENTRY_ID]\n const entry = entries[action.id]\n if (!isLegacySiteTheme && entry?.kind !== 'custom') {\n return { ok: false, status: 404, error: 'Only your own themes can be renamed.' }\n }\n const renamedSelection =\n current.id === action.id ? { ...current, name } : null\n return {\n ok: true,\n host: renamedSelection ? { themeSelection: renamedSelection } : {},\n entries: isLegacySiteTheme\n ? [\n {\n id: action.id,\n op: 'create',\n data: {\n kind: 'custom',\n name,\n theme: themeArtifactContent(host.theme),\n },\n },\n ]\n : [{ id: action.id, op: 'update', data: { name } }],\n selection: renamedSelection ?? current,\n }\n }\n\n case 'delete': {\n const entry = entries[action.id]\n if (!entry || (entry.kind !== 'custom' && entry.kind !== 'installed')) {\n return { ok: false, status: 404, error: 'There is no such theme to delete.' }\n }\n if (current.id === action.id) {\n return {\n ok: false,\n status: 409,\n error: 'This theme is in use. Pick another theme first, then delete it.',\n }\n }\n return {\n ok: true,\n host: {},\n entries: [{ id: action.id, op: 'delete' }],\n selection: current,\n }\n }\n }\n}\n\nfunction planSelect(\n host: ThemeLibraryHost,\n current: HostThemeSelection,\n resolved: HostTheme,\n entries: Readonly<Record<string, ThemeLibraryEntry>>,\n target: ThemeLibraryTarget,\n): ThemeLibraryPlan {\n let selection: HostThemeSelection\n let base: HostTheme\n let installedFrom: unknown = null\n if (target.kind === 'default') {\n selection = {\n kind: 'default',\n id: DEFAULT_THEME_ENTRY_ID,\n name: DEFAULT_THEME_NAME,\n }\n base = {}\n } else if (target.kind === 'preset') {\n const name = normalizeThemeName(target.name)\n if (!target.id || !name || !isRecord(target.theme)) {\n return { ok: false, status: 400, error: 'That theme could not be read.' }\n }\n selection = { kind: 'preset', id: presetEntryId(target.id), name }\n base = themeArtifactContent(target.theme)\n } else {\n const entry = entries[target.id]\n if (!entry || entry.kind !== target.kind) {\n return { ok: false, status: 404, error: 'That theme is not in this site’s library.' }\n }\n selection = { kind: entry.kind, id: target.id, name: entry.name }\n base = themeArtifactContent(entry.theme ?? {})\n installedFrom = entry.installedFrom ?? null\n }\n if (selection.id === current.id && selection.kind === current.kind) {\n return { ok: true, host: {}, entries: [], selection: current }\n }\n\n const stash = entries[selection.id]?.override\n const edited = withSiteSettings(resolveStash(base, stash), resolved)\n const writes: ThemeLibraryEntryWrite[] = fileCurrent(\n host,\n current,\n entries,\n host.themeOverride,\n )\n // Its stash is live again, so the entry stops holding a second copy.\n if (entries[selection.id]?.override !== undefined) {\n writes.push({\n id: selection.id,\n op: 'update',\n data: { override: THEME_FIELD_DELETE },\n })\n }\n return {\n ok: true,\n host: pointAt(\n selection,\n base,\n overrideValue(\n base,\n edited,\n selection.kind === 'installed' ? sha(installedFrom) : null,\n ),\n installedFrom,\n ),\n entries: writes,\n selection,\n }\n}\n\n/**\n * Whether the picked theme carries edits of this site's own — what \"Restore\"\n * and \"Update\" act on. The site's dark-scheme setting is not an edit to the\n * theme, so an override holding only that does not count.\n */\nexport function hasThemeEdits(host: ThemeLibraryHost | null | undefined): boolean {\n const read = readArtifactOverride({ overrides: host?.themeOverride })\n if (!read || isEmptyOverride(read.patch)) return false\n if (!isRecord(read.patch)) return true\n return Object.keys(read.patch).some((key) => key !== 'darkScheme')\n}\n"],"names":["diffOverride","isEmptyOverride","overrideWriteValue","readArtifactOverride","resolveOverride","resolveSiteTheme","themeArtifactContent","THEME_LIBRARY_COLLECTION","THEME_LIBRARY_MAX_CUSTOM","THEME_NAME_MAX","KINDS","DEFAULT_THEME_ENTRY_ID","SITE_THEME_ENTRY_ID","DEFAULT_THEME_NAME","presetEntryId","presetId","replace","installedEntryId","listingId","isRecord","value","Array","isArray","hasContent","theme","Boolean","Object","keys","length","readThemeSelection","host","stored","themeSelection","includes","kind","id","name","themeInstalledFrom","normalizeThemeName","trimmed","trim","slice","THEME_FIELD_DELETE","overrideValue","base","edited","baseSha256","patch","withSiteSettings","from","next","darkScheme","resolveStash","stash","read","overrides","sha","installedFrom","fileCurrent","current","entries","override","undefined","op","data","entry","pointAt","selection","themeOverride","themeReplaced","planThemeLibraryAction","input","action","resolved","planSelect","target","ok","status","error","customCount","values","filter","newId","isLegacySiteTheme","renamedSelection","writes","push","hasThemeEdits","some","key"],"mappings":"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"}
|
|
@@ -89,7 +89,8 @@ export var HostRedirectParams = /*#__PURE__*/ function(HostRedirectParams) {
|
|
|
89
89
|
defaultLocale: 'Which of `locales` serves an unprefixed path. Authoring.',
|
|
90
90
|
timeZone: 'Which calendar day this site attributes a published instant to ' + '(AGL-3252), overriding the workspace zone. Editorial, and it reaches ' + 'only this site\'s own rendered dates — nothing bills, gates or routes ' + 'on it, and every read validates it through `isSupportedTimeZone`, so ' + 'the worst a rewrite can do is move one publisher\'s archive by a day. ' + 'The org\'s `timeZone` is denied because /api/orgs/settings owns the ' + 'whole org document; this one rides the settings form\'s client write ' + 'beside `seo`, which is read by the same renderer for the same reason.',
|
|
91
91
|
theme: 'The persisted MUI theme for the published site. Authoring — the theme ' + 'editor writes it directly, and it renders only on this host.',
|
|
92
|
-
themeOverride: 'The editor\'s
|
|
92
|
+
themeOverride: 'The editor\'s diff on top of the picked theme (AGL-3404: every edit is ' + 'one), written wholesale by the setup page. Authoring. Its PROVENANCE ' + '(`themeInstalledFrom`) is denied instead, which is the half that has to ' + 'be true for `isOverrideForCurrentTheme` to mean anything.',
|
|
93
|
+
themeSelection: 'Which library theme the site picked (AGL-3404), written by ' + '/api/hosts/theme. A label and a pointer, never an input to a decision: ' + 'the page renders `theme` ⊕ `themeOverride`, both already authoring ' + 'fields, and the route re-reads the library itself rather than trusting ' + 'it. A forged one can only misname the theme in the picker or file the ' + 'next switch\'s stash under another entry of the same site.',
|
|
93
94
|
announcementBar: 'Site-wide announcement bar (AGL-195). `marketingOverlays`-gated, but ' + 'the gate is enforced where it counts — `site-page-enricher` re-checks ' + 'the entitlement at RENDER, so a free plan writing this bar never ships ' + 'it. The console gate is the affordance, not the boundary.',
|
|
94
95
|
popup: 'Promotional popup (AGL-196). Identical reasoning to `announcementBar`.',
|
|
95
96
|
business: 'Support email, address and social links behind the AGL-1022 host ' + 'tokens. Publisher-authored contact detail that renders into their own ' + 'pages and emails; undeclared on this interface, so the guard sees it ' + 'through the resolver sweep rather than the type.',
|