@bison-lab/payload-core 3.14.0 → 3.16.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 +57 -37
- package/dist/admin.mjs +2 -8
- package/dist/admin.mjs.map +1 -1
- package/dist/{identity-Dwyq8519.mjs → identity-LAzSeorC.mjs} +1 -1
- package/dist/identity-LAzSeorC.mjs.map +1 -0
- package/dist/index.d.mts +32 -69
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +874 -221
- package/dist/index.mjs.map +1 -1
- package/dist/looks-BbL309kO.mjs.map +1 -1
- package/dist/{looks-Xs6v5vYU.d.mts → looks-Dk0l0mif.d.mts} +79 -11
- package/dist/looks-Dk0l0mif.d.mts.map +1 -0
- package/dist/metadata.d.mts +1 -1
- package/dist/metadata.mjs +1 -1
- package/dist/{share-image-BlFAJLnd.mjs → share-image-C4ILz4p2.mjs} +1 -1
- package/dist/{share-image-BlFAJLnd.mjs.map → share-image-C4ILz4p2.mjs.map} +1 -1
- package/dist/theme.d.mts +1 -1
- package/dist/theme.mjs +1 -1
- package/dist/{title-sWwcCqHT.d.mts → title-B2-gWQZd.d.mts} +1 -1
- package/dist/{title-sWwcCqHT.d.mts.map → title-B2-gWQZd.d.mts.map} +1 -1
- package/package.json +3 -3
- package/dist/access-B3LglN2J.mjs +0 -662
- package/dist/access-B3LglN2J.mjs.map +0 -1
- package/dist/identity-Dwyq8519.mjs.map +0 -1
- package/dist/looks-Xs6v5vYU.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
3
|
-
import { A as THEME_FONT_FIELD, C as validateThemeHex, D as THEME_COLOR_SCALE_FIELD, E as THEME_COLOR_FIELD, F as THEME_PUBLISH_FIELD, I as THEME_SAVE_BUTTON, L as THEME_SECTION_HEADING, M as THEME_IDENTITY_FALLBACK, N as THEME_LIBRARY_FIELD, O as THEME_CONTRAST_REPORT, P as THEME_PAIRING_FIELD, R as headingSelectValue, S as validateSourceIncluded, T as THEME_APPEARANCE_FIELD, _ as THEME_SLUG, a as pageEditorLooks, c as findColorTokens, d as rewriteColorToken, f as themeColorKeys, g as THEME_IDENTITY_SLUG, h as THEME_COLORS_SLUG, i as PAGE_EDITOR_SYSTEM_KEYS, j as THEME_GREY_SCALE_FIELD, k as THEME_DOCUMENT_CONTROLS, l as rewriteColorTokens, m as THEME_APPEARANCE_SLUG, n as colorTokenField, o as pageEditorTokens, p as SYSTEM_COLOR_KEYS, r as lookField, s as themeLibraryFromDoc, t as resolveThemeIdentity, u as deleteLibraryColor, v as THEME_TYPOGRAPHY_SLUG, w as LOOK_FIELD, x as themeConfigFromDoc, y as docFromConfig } from "./identity-Dwyq8519.mjs";
|
|
1
|
+
import { n as documentTitle, r as titleTemplate, t as SHARE_IMAGE_SIZE } from "./share-image-C4ILz4p2.mjs";
|
|
2
|
+
import { A as THEME_FONT_FIELD, C as validateThemeHex, D as THEME_COLOR_SCALE_FIELD, E as THEME_COLOR_FIELD, F as THEME_PUBLISH_FIELD, I as THEME_SAVE_BUTTON, L as THEME_SECTION_HEADING, M as THEME_IDENTITY_FALLBACK, N as THEME_LIBRARY_FIELD, O as THEME_CONTRAST_REPORT, P as THEME_PAIRING_FIELD, R as headingSelectValue, S as validateSourceIncluded, T as THEME_APPEARANCE_FIELD, _ as THEME_SLUG, a as pageEditorLooks, c as findColorTokens, d as rewriteColorToken, f as themeColorKeys, g as THEME_IDENTITY_SLUG, h as THEME_COLORS_SLUG, i as PAGE_EDITOR_SYSTEM_KEYS, j as THEME_GREY_SCALE_FIELD, k as THEME_DOCUMENT_CONTROLS, l as rewriteColorTokens, m as THEME_APPEARANCE_SLUG, n as colorTokenField, o as pageEditorTokens, p as SYSTEM_COLOR_KEYS, r as lookField, s as themeLibraryFromDoc, t as resolveThemeIdentity, u as deleteLibraryColor, v as THEME_TYPOGRAPHY_SLUG, w as LOOK_FIELD, x as themeConfigFromDoc, y as docFromConfig } from "./identity-LAzSeorC.mjs";
|
|
4
3
|
import { seoPlugin as seoPlugin$1 } from "@payloadcms/plugin-seo";
|
|
5
4
|
import { catalog } from "@bison-lab/fonts";
|
|
6
5
|
import { DESTRUCTIVE_SCALE_HEX, SHADE_STEPS, presetHints } from "@bison-lab/tokens";
|
|
@@ -169,24 +168,20 @@ const APPEARANCE_CHOICES = {
|
|
|
169
168
|
/** Theme-store REST path. LibraryField calls `/api/globals/theme` + this. */
|
|
170
169
|
const COLOR_USAGES_PATH = "/color-usages";
|
|
171
170
|
/**
|
|
172
|
-
* Walk every current document in the
|
|
171
|
+
* Walk every current document in the resolved collections and globals
|
|
173
172
|
* (drafts included). A color only on an unpublished page is still in use.
|
|
174
173
|
*/
|
|
175
174
|
async function findColorUsages(payload, scopes, key) {
|
|
175
|
+
const { collections, globals } = resolveScopes(payload, scopes);
|
|
176
176
|
const usages = [];
|
|
177
|
-
for (const collection of
|
|
177
|
+
for (const collection of collections) for await (const doc of eachCollectionDoc(payload, collection)) for (const hit of findColorTokens(doc, key)) usages.push({
|
|
178
178
|
collection,
|
|
179
179
|
id: idOf(doc),
|
|
180
180
|
path: hit.path,
|
|
181
181
|
token: hit.token
|
|
182
182
|
});
|
|
183
|
-
for (const slug of
|
|
184
|
-
const doc = await payload
|
|
185
|
-
slug,
|
|
186
|
-
draft: true,
|
|
187
|
-
depth: 0,
|
|
188
|
-
overrideAccess: true
|
|
189
|
-
});
|
|
183
|
+
for (const slug of globals) {
|
|
184
|
+
const doc = await readGlobal(payload, slug);
|
|
190
185
|
if (!doc) continue;
|
|
191
186
|
for (const hit of findColorTokens(doc, key)) usages.push({
|
|
192
187
|
global: slug,
|
|
@@ -197,7 +192,8 @@ async function findColorUsages(payload, scopes, key) {
|
|
|
197
192
|
return usages;
|
|
198
193
|
}
|
|
199
194
|
async function rewriteColorUsages(payload, scopes, fromKey, toKey) {
|
|
200
|
-
|
|
195
|
+
const { collections, globals } = resolveScopes(payload, scopes);
|
|
196
|
+
for (const collection of collections) for await (const doc of eachCollectionDoc(payload, collection)) {
|
|
201
197
|
if (findColorTokens(doc, fromKey).length === 0) continue;
|
|
202
198
|
const { id: _id, ...data } = rewriteColorTokens(doc, fromKey, toKey);
|
|
203
199
|
const id = idOf(doc);
|
|
@@ -210,13 +206,8 @@ async function rewriteColorUsages(payload, scopes, fromKey, toKey) {
|
|
|
210
206
|
overrideAccess: true
|
|
211
207
|
});
|
|
212
208
|
}
|
|
213
|
-
for (const slug of
|
|
214
|
-
const doc = await payload
|
|
215
|
-
slug,
|
|
216
|
-
draft: true,
|
|
217
|
-
depth: 0,
|
|
218
|
-
overrideAccess: true
|
|
219
|
-
});
|
|
209
|
+
for (const slug of globals) {
|
|
210
|
+
const doc = await readGlobal(payload, slug);
|
|
220
211
|
if (!doc || findColorTokens(doc, fromKey).length === 0) continue;
|
|
221
212
|
await payload.updateGlobal({
|
|
222
213
|
slug,
|
|
@@ -233,17 +224,52 @@ async function replacementKeys(payload, except) {
|
|
|
233
224
|
overrideAccess: true
|
|
234
225
|
})).map((look) => look.value).filter((key) => key !== except);
|
|
235
226
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
227
|
+
function resolveScopes(payload, scopes) {
|
|
228
|
+
if (scopes === true) return registeredScopes(payload);
|
|
229
|
+
return {
|
|
230
|
+
collections: scopes?.collections ?? [],
|
|
231
|
+
globals: scopes?.globals ?? []
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function registeredScopes(payload) {
|
|
235
|
+
return {
|
|
236
|
+
collections: slugsOf(payload.config?.collections ?? payload.collections).filter((slug) => !slug.startsWith("payload-")),
|
|
237
|
+
globals: slugsOf(payload.config?.globals ?? payload.globals?.config).filter((slug) => slug !== THEME_SLUG)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function slugsOf(value) {
|
|
241
|
+
if (Array.isArray(value)) return value.map((item) => item?.slug).filter((slug) => Boolean(slug));
|
|
242
|
+
if (value && typeof value === "object") return Object.keys(value);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
async function readGlobal(payload, slug) {
|
|
246
|
+
try {
|
|
247
|
+
return await payload.findGlobal({
|
|
248
|
+
slug,
|
|
241
249
|
draft: true,
|
|
242
250
|
depth: 0,
|
|
243
|
-
limit: 100,
|
|
244
|
-
page,
|
|
245
251
|
overrideAccess: true
|
|
246
252
|
});
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function* eachCollectionDoc(payload, collection) {
|
|
258
|
+
let page = 1;
|
|
259
|
+
for (;;) {
|
|
260
|
+
let result;
|
|
261
|
+
try {
|
|
262
|
+
result = await payload.find({
|
|
263
|
+
collection,
|
|
264
|
+
draft: true,
|
|
265
|
+
depth: 0,
|
|
266
|
+
limit: 100,
|
|
267
|
+
page,
|
|
268
|
+
overrideAccess: true
|
|
269
|
+
});
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
247
273
|
for (const doc of result.docs) yield doc;
|
|
248
274
|
const totalPages = result.totalPages ?? (result.docs.length < 100 ? page : page + 1);
|
|
249
275
|
if (page >= totalPages || result.docs.length === 0) break;
|
|
@@ -304,75 +330,664 @@ async function readJson(req) {
|
|
|
304
330
|
return null;
|
|
305
331
|
}
|
|
306
332
|
//#endregion
|
|
307
|
-
//#region src/
|
|
333
|
+
//#region src/features/types.ts
|
|
334
|
+
const FEATURES_SLUG = "features";
|
|
335
|
+
const FEATURE_GROUPS = [
|
|
336
|
+
"pages",
|
|
337
|
+
"media",
|
|
338
|
+
"theme",
|
|
339
|
+
"users"
|
|
340
|
+
];
|
|
341
|
+
const FEATURE_GROUP_LABELS = {
|
|
342
|
+
pages: "Pages",
|
|
343
|
+
media: "Media",
|
|
344
|
+
theme: "Theme",
|
|
345
|
+
users: "Users"
|
|
346
|
+
};
|
|
347
|
+
const PACKAGE_FEATURE_SLUGS = [
|
|
348
|
+
"content",
|
|
349
|
+
"publish",
|
|
350
|
+
"media",
|
|
351
|
+
"theme-colors",
|
|
352
|
+
"theme-typography",
|
|
353
|
+
"theme-appearance",
|
|
354
|
+
"theme-identity",
|
|
355
|
+
"brand-assets",
|
|
356
|
+
"users",
|
|
357
|
+
"roles"
|
|
358
|
+
];
|
|
308
359
|
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* different child never goes live. A first save (no stored row) still
|
|
312
|
-
* writes only the named child; `getPublishedTheme` fills the rest from
|
|
313
|
-
* the seed.
|
|
360
|
+
* Package catalogue. Features itself is not a row — that screen is
|
|
361
|
+
* Developer-only in code. Empty Global falls back to these defaults.
|
|
314
362
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
363
|
+
const PACKAGE_FEATURES = [
|
|
364
|
+
{
|
|
365
|
+
slug: "content",
|
|
366
|
+
label: "Content",
|
|
367
|
+
group: "pages",
|
|
368
|
+
defaultReleased: true
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
slug: "publish",
|
|
372
|
+
label: "Publish",
|
|
373
|
+
group: "pages",
|
|
374
|
+
defaultReleased: true
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
slug: "media",
|
|
378
|
+
label: "Media",
|
|
379
|
+
group: "media",
|
|
380
|
+
defaultReleased: true
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
slug: "theme-colors",
|
|
384
|
+
label: "Colors",
|
|
385
|
+
group: "theme",
|
|
386
|
+
defaultReleased: true
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
slug: "theme-typography",
|
|
390
|
+
label: "Typography",
|
|
391
|
+
group: "theme",
|
|
392
|
+
defaultReleased: true
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
slug: "theme-appearance",
|
|
396
|
+
label: "Appearance",
|
|
397
|
+
group: "theme",
|
|
398
|
+
defaultReleased: true
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
slug: "theme-identity",
|
|
402
|
+
label: "Identity",
|
|
403
|
+
group: "theme",
|
|
404
|
+
defaultReleased: true
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
slug: "brand-assets",
|
|
408
|
+
label: "Brand assets",
|
|
409
|
+
group: "theme",
|
|
410
|
+
defaultReleased: true
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
slug: "users",
|
|
414
|
+
label: "Users",
|
|
415
|
+
group: "users",
|
|
416
|
+
defaultReleased: true
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
slug: "roles",
|
|
420
|
+
label: "Roles",
|
|
421
|
+
group: "users",
|
|
422
|
+
defaultReleased: true
|
|
423
|
+
}
|
|
424
|
+
];
|
|
425
|
+
function isFeatureGroupId(value) {
|
|
426
|
+
return typeof value === "string" && FEATURE_GROUPS.includes(value);
|
|
427
|
+
}
|
|
428
|
+
function isPackageFeatureSlug(value) {
|
|
429
|
+
return typeof value === "string" && PACKAGE_FEATURE_SLUGS.includes(value);
|
|
430
|
+
}
|
|
431
|
+
function isFeatureSlug(value) {
|
|
432
|
+
return typeof value === "string" && /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
|
|
433
|
+
}
|
|
434
|
+
/** @deprecated Locks are gone; the release switch is the valve. */
|
|
435
|
+
function isLockedFeature(_slug, _locked) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/features/matrix.ts
|
|
440
|
+
const MISSING_PACKAGE_FEATURES_MESSAGE = "Features must include Content, Publish, Media, Theme screens, Brand assets, Users, and Roles.";
|
|
441
|
+
function featureCatalogue(extras = []) {
|
|
442
|
+
return [...PACKAGE_FEATURES.map((feature) => ({ ...feature })), ...extras.map((extra) => ({
|
|
443
|
+
slug: extra.slug,
|
|
444
|
+
label: extra.label,
|
|
445
|
+
group: extra.group ?? "users",
|
|
446
|
+
defaultReleased: extra.defaultReleased ?? false
|
|
447
|
+
}))];
|
|
448
|
+
}
|
|
449
|
+
function defaultFeaturesFieldValue(extras = []) {
|
|
450
|
+
return featureCatalogue(extras).map((feature) => ({
|
|
451
|
+
id: feature.slug,
|
|
452
|
+
slug: feature.slug,
|
|
453
|
+
label: feature.label,
|
|
454
|
+
group: feature.group,
|
|
455
|
+
released: feature.defaultReleased
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
function stampFeatureCatalogue(value, extras = []) {
|
|
459
|
+
const bySlug = new Map(featureCatalogue(extras).map((feature) => [feature.slug, feature]));
|
|
460
|
+
if (!Array.isArray(value)) return defaultFeaturesFieldValue(extras);
|
|
461
|
+
return value.flatMap((item) => {
|
|
462
|
+
if (!item || typeof item !== "object") return [];
|
|
463
|
+
const slug = "slug" in item && typeof item.slug === "string" ? item.slug : "";
|
|
464
|
+
const feature = bySlug.get(slug);
|
|
465
|
+
if (!feature) return [];
|
|
466
|
+
return [{
|
|
467
|
+
id: feature.slug,
|
|
468
|
+
slug: feature.slug,
|
|
469
|
+
label: feature.label,
|
|
470
|
+
group: feature.group,
|
|
471
|
+
released: Boolean("released" in item && item.released)
|
|
472
|
+
}];
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function parseFeaturesMatrix(value) {
|
|
476
|
+
if (!Array.isArray(value) || value.length === 0) return {
|
|
477
|
+
ok: false,
|
|
478
|
+
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
319
479
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
480
|
+
const rows = [];
|
|
481
|
+
const seen = /* @__PURE__ */ new Set();
|
|
482
|
+
for (const item of value) {
|
|
483
|
+
if (!item || typeof item !== "object") continue;
|
|
484
|
+
const slug = "slug" in item ? item.slug : void 0;
|
|
485
|
+
if (!isFeatureSlug(slug) || seen.has(slug)) continue;
|
|
486
|
+
seen.add(slug);
|
|
487
|
+
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
488
|
+
const label = "label" in item && typeof item.label === "string" ? item.label : pack?.label ?? slug;
|
|
489
|
+
const group = "group" in item && isFeatureGroupId(item.group) ? item.group : pack?.group ?? "users";
|
|
490
|
+
rows.push({
|
|
491
|
+
id: slug,
|
|
492
|
+
slug,
|
|
493
|
+
label,
|
|
494
|
+
group,
|
|
495
|
+
released: Boolean("released" in item && item.released)
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
if (PACKAGE_FEATURES.some((feature) => !seen.has(feature.slug))) return {
|
|
499
|
+
ok: false,
|
|
500
|
+
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
323
501
|
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
502
|
+
return {
|
|
503
|
+
ok: true,
|
|
504
|
+
rows
|
|
327
505
|
};
|
|
506
|
+
}
|
|
507
|
+
function validateFeaturesMatrix(value, extras = []) {
|
|
508
|
+
const parsed = parseFeaturesMatrix(value);
|
|
509
|
+
if (!parsed.ok) return parsed.message;
|
|
510
|
+
const allowed = new Set(featureCatalogue(extras).map((feature) => feature.slug));
|
|
511
|
+
if (parsed.rows.some((row) => !allowed.has(row.slug))) return MISSING_PACKAGE_FEATURES_MESSAGE;
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
function featureGroupLabel(group) {
|
|
515
|
+
return isFeatureGroupId(group) ? FEATURE_GROUP_LABELS[group] : group;
|
|
516
|
+
}
|
|
517
|
+
function packageFeatureLabel(slug, extras = []) {
|
|
518
|
+
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
519
|
+
if (pack) return pack.label;
|
|
520
|
+
return extras.find((feature) => feature.slug === slug)?.label ?? slug;
|
|
521
|
+
}
|
|
522
|
+
function isGroupOnlySlug(slug) {
|
|
523
|
+
return isFeatureGroupId(slug) && !PACKAGE_FEATURES.some((feature) => feature.slug === slug);
|
|
524
|
+
}
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/roles/types.ts
|
|
527
|
+
/**
|
|
528
|
+
* The Roles Global a site's generated types will describe. Optional and
|
|
529
|
+
* nullable, no index signature: a generated Global is assignable to this,
|
|
530
|
+
* never the reverse.
|
|
531
|
+
*/
|
|
532
|
+
const ROLES = [
|
|
533
|
+
"developer",
|
|
534
|
+
"admin",
|
|
535
|
+
"designer",
|
|
536
|
+
"author"
|
|
537
|
+
];
|
|
538
|
+
const ROLE_LABELS = {
|
|
539
|
+
developer: "Developer",
|
|
540
|
+
admin: "Admin",
|
|
541
|
+
designer: "Designer",
|
|
542
|
+
author: "Author"
|
|
543
|
+
};
|
|
544
|
+
/** Capability aliases over feature grants. Brand is any Theme-group leaf. */
|
|
545
|
+
const CAPABILITIES = [
|
|
546
|
+
"content",
|
|
547
|
+
"brand",
|
|
548
|
+
"publish",
|
|
549
|
+
"users"
|
|
550
|
+
];
|
|
551
|
+
const THEME_FEATURE_SLUGS = [
|
|
552
|
+
"theme-colors",
|
|
553
|
+
"theme-typography",
|
|
554
|
+
"theme-appearance",
|
|
555
|
+
"theme-identity",
|
|
556
|
+
"brand-assets"
|
|
557
|
+
];
|
|
558
|
+
const CAPABILITY_FEATURES = {
|
|
559
|
+
content: ["content"],
|
|
560
|
+
publish: ["publish"],
|
|
561
|
+
users: ["users"],
|
|
562
|
+
brand: THEME_FEATURE_SLUGS
|
|
563
|
+
};
|
|
564
|
+
const DEFAULT_ROLE_GRANTS = {
|
|
565
|
+
developer: [],
|
|
566
|
+
admin: [
|
|
567
|
+
"content",
|
|
568
|
+
"publish",
|
|
569
|
+
"media",
|
|
570
|
+
"users",
|
|
571
|
+
"roles"
|
|
572
|
+
],
|
|
573
|
+
designer: [
|
|
574
|
+
"content",
|
|
575
|
+
"media",
|
|
576
|
+
...THEME_FEATURE_SLUGS,
|
|
577
|
+
"users",
|
|
578
|
+
"roles"
|
|
579
|
+
],
|
|
580
|
+
author: ["content", "media"]
|
|
581
|
+
};
|
|
582
|
+
function isRole(value) {
|
|
583
|
+
return typeof value === "string" && ROLES.includes(value);
|
|
584
|
+
}
|
|
585
|
+
/** Lowercase slug from a letters-and-spaces name: `designer`, `the-designer`. */
|
|
586
|
+
function isRoleSlug(value) {
|
|
587
|
+
return typeof value === "string" && /^[a-z]+(-[a-z]+)*$/.test(value);
|
|
588
|
+
}
|
|
589
|
+
/** Display name: letters and single spaces only. `The Greatest Designer`. */
|
|
590
|
+
function isRoleName(value) {
|
|
591
|
+
return typeof value === "string" && /^[A-Za-z]+(?: [A-Za-z]+)*$/.test(value.trim());
|
|
592
|
+
}
|
|
593
|
+
/** Strip digits and punctuation as the name is typed. Trailing space stays. */
|
|
594
|
+
function sanitizeRoleNameInput(value) {
|
|
595
|
+
return value.replace(/[^A-Za-z ]/g, "").replace(/ {2,}/g, " ");
|
|
596
|
+
}
|
|
597
|
+
/** Name → stored key. `The Greatest Designer` → `the-greatest-designer`. */
|
|
598
|
+
function slugifyRoleName(value) {
|
|
599
|
+
if (typeof value !== "string") return "";
|
|
600
|
+
return value.trim().toLowerCase().replace(/[^a-z]+/g, "-").replace(/^-|-$/g, "");
|
|
601
|
+
}
|
|
602
|
+
function roleLabel(role, label) {
|
|
603
|
+
const trimmed = typeof label === "string" ? label.trim() : "";
|
|
604
|
+
if (trimmed) return trimmed;
|
|
605
|
+
return isRole(role) ? ROLE_LABELS[role] : role;
|
|
606
|
+
}
|
|
607
|
+
function defaultGrantsForRole(role, extraGrants) {
|
|
608
|
+
if (isRole(role)) return [...DEFAULT_ROLE_GRANTS[role]];
|
|
609
|
+
return extraGrants ? [...extraGrants] : [];
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/roles/matrix.ts
|
|
613
|
+
const ROLES_SLUG = "roles";
|
|
614
|
+
const ROLES_GLOBAL_DESCRIPTION = "Who may do what. Drag to change rank.";
|
|
615
|
+
const ROLES_FIELD_DESCRIPTION = "Drag to change rank. Ticks are features released on Features. Developer has the whole catalogue.";
|
|
616
|
+
/**
|
|
617
|
+
* Default rank and grants. Developer is implicit (empty grants). Brand
|
|
618
|
+
* screens default to Designer.
|
|
619
|
+
*/
|
|
620
|
+
const DEFAULT_ROLE_MATRIX = ROLES.map((role) => ({
|
|
621
|
+
role,
|
|
622
|
+
grants: defaultGrantsForRole(role)
|
|
623
|
+
}));
|
|
624
|
+
const DEVELOPER_DESCRIPTION = "Everything, including features not released for assignment.";
|
|
625
|
+
const LAST_USERS_TICK_MESSAGE = "Keep Users granted on at least one of Admin or Developer.";
|
|
626
|
+
const MISSING_SEED_ROLES_MESSAGE = "Roles must include Developer, Admin, Designer, and Author.";
|
|
627
|
+
const DUPLICATE_ROLE_SLUG_MESSAGE = "Each role needs a unique slug.";
|
|
628
|
+
const DUPLICATE_ROLE_NAME_MESSAGE = "Each role needs a unique name.";
|
|
629
|
+
const INVALID_ROLE_NAME_MESSAGE = "Use letters and spaces only.";
|
|
630
|
+
function uniqueSlugs(values) {
|
|
631
|
+
const slugs = [];
|
|
632
|
+
for (const value of values) if (typeof value === "string" && isFeatureSlug(value) && !slugs.includes(value)) slugs.push(value);
|
|
633
|
+
return slugs;
|
|
634
|
+
}
|
|
635
|
+
/** Read stored grants, or rebuild them from the old capability ticks. */
|
|
636
|
+
function grantsFromStoredRole(item) {
|
|
637
|
+
if ("grants" in item && Array.isArray(item.grants)) return uniqueSlugs(item.grants);
|
|
638
|
+
const grants = [];
|
|
639
|
+
if ("content" in item && item.content) grants.push("content", "media");
|
|
640
|
+
if ("publish" in item && item.publish) grants.push("publish");
|
|
641
|
+
if ("users" in item && item.users) grants.push("users", "roles");
|
|
642
|
+
if ("brand" in item && item.brand) grants.push(...THEME_FEATURE_SLUGS);
|
|
643
|
+
return uniqueSlugs(grants);
|
|
644
|
+
}
|
|
645
|
+
function seedRoleRows(extras = []) {
|
|
646
|
+
return [...DEFAULT_ROLE_MATRIX.map((row) => ({
|
|
647
|
+
...row,
|
|
648
|
+
grants: [...row.grants],
|
|
649
|
+
label: isRole(row.role) ? ROLE_LABELS[row.role] : row.role
|
|
650
|
+
})), ...extras.map((extra) => ({
|
|
651
|
+
role: extra.role,
|
|
652
|
+
label: extra.label,
|
|
653
|
+
grants: extra.grants ? [...extra.grants] : []
|
|
654
|
+
}))];
|
|
655
|
+
}
|
|
656
|
+
function defaultRolesFieldValue(extras = []) {
|
|
657
|
+
return seedRoleRows(extras).map((row) => ({
|
|
658
|
+
id: row.role,
|
|
659
|
+
...row
|
|
660
|
+
}));
|
|
661
|
+
}
|
|
662
|
+
function readRoleRow(item) {
|
|
663
|
+
const role = "role" in item ? item.role : void 0;
|
|
664
|
+
if (typeof role !== "string" || role.trim() === "") return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
|
|
665
|
+
if (!isRoleSlug(role)) return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
|
|
328
666
|
return {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
667
|
+
id: role,
|
|
668
|
+
role,
|
|
669
|
+
label: roleLabel(role, "label" in item && typeof item.label === "string" ? item.label : void 0),
|
|
670
|
+
grants: grantsFromStoredRole(item)
|
|
333
671
|
};
|
|
334
672
|
}
|
|
335
|
-
function
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
673
|
+
function parseRolesMatrix(value) {
|
|
674
|
+
if (!Array.isArray(value)) return {
|
|
675
|
+
ok: false,
|
|
676
|
+
message: MISSING_SEED_ROLES_MESSAGE
|
|
677
|
+
};
|
|
678
|
+
const rows = [];
|
|
679
|
+
const seen = /* @__PURE__ */ new Set();
|
|
680
|
+
for (const item of value) {
|
|
681
|
+
if (!item || typeof item !== "object") continue;
|
|
682
|
+
const role = "role" in item ? item.role : void 0;
|
|
683
|
+
if (typeof role !== "string" || role.trim() === "") continue;
|
|
684
|
+
const parsed = readRoleRow(item);
|
|
685
|
+
if ("error" in parsed) return {
|
|
686
|
+
ok: false,
|
|
687
|
+
message: parsed.error
|
|
688
|
+
};
|
|
689
|
+
if (seen.has(parsed.role)) return {
|
|
690
|
+
ok: false,
|
|
691
|
+
message: DUPLICATE_ROLE_SLUG_MESSAGE
|
|
692
|
+
};
|
|
693
|
+
seen.add(parsed.role);
|
|
694
|
+
rows.push(parsed);
|
|
695
|
+
}
|
|
696
|
+
if (ROLES.some((role) => !seen.has(role))) return {
|
|
697
|
+
ok: false,
|
|
698
|
+
message: MISSING_SEED_ROLES_MESSAGE
|
|
699
|
+
};
|
|
339
700
|
return {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
logoMark: theme.logoMark
|
|
701
|
+
ok: true,
|
|
702
|
+
rows
|
|
343
703
|
};
|
|
344
704
|
}
|
|
345
|
-
function
|
|
346
|
-
if (
|
|
347
|
-
return
|
|
705
|
+
function roleHasGrant(row, slug) {
|
|
706
|
+
if (row.role === "developer") return true;
|
|
707
|
+
return row.grants.includes(slug);
|
|
708
|
+
}
|
|
709
|
+
function roleHasCapability(row, capability) {
|
|
710
|
+
if (row.role === "developer") return true;
|
|
711
|
+
return CAPABILITY_FEATURES[capability].some((slug) => row.grants.includes(slug));
|
|
348
712
|
}
|
|
349
713
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
714
|
+
* Mint a slug from the name only when the row has none yet. An existing
|
|
715
|
+
* key — seed or custom — stays put so a rename cannot orphan users.
|
|
352
716
|
*/
|
|
353
|
-
function
|
|
354
|
-
if (!
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
717
|
+
function applyRoleSlugsFromNames(value) {
|
|
718
|
+
if (!Array.isArray(value)) return value;
|
|
719
|
+
return value.map((item) => {
|
|
720
|
+
if (!item || typeof item !== "object") return item;
|
|
721
|
+
const row = item;
|
|
722
|
+
if (isRoleSlug(row.role)) return item;
|
|
723
|
+
const slug = slugifyRoleName(row.label);
|
|
724
|
+
return isRoleSlug(slug) ? {
|
|
725
|
+
...row,
|
|
726
|
+
role: slug
|
|
727
|
+
} : item;
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
function hasDuplicateRoleNames(value) {
|
|
731
|
+
if (!Array.isArray(value)) return false;
|
|
732
|
+
const seen = /* @__PURE__ */ new Set();
|
|
733
|
+
for (const item of value) {
|
|
734
|
+
if (!item || typeof item !== "object") continue;
|
|
735
|
+
const role = "role" in item && typeof item.role === "string" ? item.role : "";
|
|
736
|
+
const name = ("label" in item && typeof item.label === "string" ? item.label : "").trim() || (isRole(role) ? ROLE_LABELS[role] : "");
|
|
737
|
+
if (!name) continue;
|
|
738
|
+
const key = name.toLowerCase();
|
|
739
|
+
if (seen.has(key)) return true;
|
|
740
|
+
seen.add(key);
|
|
741
|
+
}
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
function hasInvalidRoleName(value) {
|
|
745
|
+
if (!Array.isArray(value)) return false;
|
|
746
|
+
for (const item of value) {
|
|
747
|
+
if (!item || typeof item !== "object") continue;
|
|
748
|
+
const label = "label" in item && typeof item.label === "string" ? item.label.trim() : "";
|
|
749
|
+
if (!label) continue;
|
|
750
|
+
if (!isRoleName(label)) return true;
|
|
751
|
+
}
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
function validateRolesMatrix(value) {
|
|
755
|
+
if (hasDuplicateRoleNames(value)) return DUPLICATE_ROLE_NAME_MESSAGE;
|
|
756
|
+
if (hasInvalidRoleName(value)) return INVALID_ROLE_NAME_MESSAGE;
|
|
757
|
+
const parsed = parseRolesMatrix(applyRoleSlugsFromNames(value));
|
|
758
|
+
if (!parsed.ok) return parsed.message;
|
|
759
|
+
return true;
|
|
359
760
|
}
|
|
360
761
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
762
|
+
* Developer is exclusive. Admin does not swallow Designer: both store.
|
|
763
|
+
* Unknown slugs (dropped catalogue keys such as `approver`) fall away unless
|
|
764
|
+
* they appear on the matrix.
|
|
363
765
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
766
|
+
function normalizeStoredRoles(value, matrix = DEFAULT_ROLE_MATRIX) {
|
|
767
|
+
if (!Array.isArray(value)) return [];
|
|
768
|
+
const allowed = new Set(matrix.map((row) => row.role));
|
|
769
|
+
const roles = [...new Set(value.filter((entry) => typeof entry === "string" && allowed.has(entry)))];
|
|
770
|
+
if (roles.includes("developer")) return ["developer"];
|
|
771
|
+
return roles;
|
|
772
|
+
}
|
|
773
|
+
function roleSelectOptions(matrix = DEFAULT_ROLE_MATRIX) {
|
|
774
|
+
return matrix.map((row) => ({
|
|
775
|
+
label: roleLabel(row.role, row.label),
|
|
776
|
+
value: row.role
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
/** Grant copy only. Developer names the unreleased catalogue; nothing about MCP or seed. */
|
|
780
|
+
function roleDescription(role, matrix = DEFAULT_ROLE_MATRIX) {
|
|
781
|
+
if (role === "developer") return DEVELOPER_DESCRIPTION;
|
|
782
|
+
const row = matrix.find((entry) => entry.role === role);
|
|
783
|
+
if (!row) return "No capabilities";
|
|
784
|
+
return row.grants.map((slug) => packageFeatureLabel(slug)).join(", ") || "No capabilities";
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Seed grants and package labels come back; extra rows stay as they are.
|
|
788
|
+
*/
|
|
789
|
+
function resetRolesMatrix(current) {
|
|
790
|
+
const extras = [];
|
|
791
|
+
if (Array.isArray(current)) for (const item of current) {
|
|
792
|
+
if (!item || typeof item !== "object") continue;
|
|
793
|
+
const parsed = readRoleRow(item);
|
|
794
|
+
if ("error" in parsed || isRole(parsed.role)) continue;
|
|
795
|
+
extras.push(parsed);
|
|
796
|
+
}
|
|
797
|
+
return [...defaultRolesFieldValue(), ...extras];
|
|
798
|
+
}
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region src/roles/access.ts
|
|
801
|
+
function isAccessArgs$1(value) {
|
|
802
|
+
return typeof value === "object" && value !== null && "req" in value;
|
|
803
|
+
}
|
|
804
|
+
function storedRoles(user) {
|
|
805
|
+
return Array.isArray(user?.roles) ? user.roles : [];
|
|
806
|
+
}
|
|
807
|
+
function hasRole(user, role) {
|
|
808
|
+
return storedRoles(user).includes(role);
|
|
809
|
+
}
|
|
810
|
+
/** Not a tick. True only when `developer` is stored on the row. */
|
|
811
|
+
function isDeveloper(user) {
|
|
812
|
+
return hasRole(user, "developer");
|
|
813
|
+
}
|
|
814
|
+
function hasCapability(user, capability, matrix = DEFAULT_ROLE_MATRIX) {
|
|
815
|
+
if (isDeveloper(user)) return true;
|
|
816
|
+
return storedRoles(user).some((role) => {
|
|
817
|
+
const row = matrix.find((entry) => entry.role === role);
|
|
818
|
+
return row ? roleHasCapability(row, capability) : false;
|
|
374
819
|
});
|
|
375
|
-
|
|
820
|
+
}
|
|
821
|
+
function hasGrant(user, slug, matrix = DEFAULT_ROLE_MATRIX) {
|
|
822
|
+
if (isDeveloper(user)) return true;
|
|
823
|
+
return storedRoles(user).some((role) => {
|
|
824
|
+
const row = matrix.find((entry) => entry.role === role);
|
|
825
|
+
return row ? roleHasGrant(row, slug) : false;
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Reads the saved Roles Global, falling back to the seed when the row is
|
|
830
|
+
* empty, missing, or unreadable. Always override-access so a Designer
|
|
831
|
+
* evaluating Theme does not have to read Settings → Roles.
|
|
832
|
+
*/
|
|
833
|
+
async function getRolesMatrix(req = {}, extras = []) {
|
|
834
|
+
const fallback = seedRoleRows(extras);
|
|
835
|
+
const findGlobal = req.payload?.findGlobal;
|
|
836
|
+
if (typeof findGlobal !== "function") return fallback;
|
|
837
|
+
try {
|
|
838
|
+
const doc = await findGlobal({
|
|
839
|
+
slug: ROLES_SLUG,
|
|
840
|
+
overrideAccess: true,
|
|
841
|
+
req
|
|
842
|
+
});
|
|
843
|
+
const parsed = parseRolesMatrix(doc && typeof doc === "object" && "roles" in doc ? doc.roles : void 0);
|
|
844
|
+
return parsed.ok ? parsed.rows : fallback;
|
|
845
|
+
} catch {
|
|
846
|
+
return fallback;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function capabilityPredicate(capability) {
|
|
850
|
+
function predicate(userOrArgs, matrix) {
|
|
851
|
+
if (isAccessArgs$1(userOrArgs)) {
|
|
852
|
+
const user = userOrArgs.req.user;
|
|
853
|
+
if (matrix) return hasCapability(user, capability, matrix);
|
|
854
|
+
return getRolesMatrix(userOrArgs.req).then((rows) => hasCapability(user, capability, rows));
|
|
855
|
+
}
|
|
856
|
+
return hasCapability(userOrArgs, capability, matrix ?? DEFAULT_ROLE_MATRIX);
|
|
857
|
+
}
|
|
858
|
+
return predicate;
|
|
859
|
+
}
|
|
860
|
+
const canManageContent = capabilityPredicate("content");
|
|
861
|
+
const canManageBrand = capabilityPredicate("brand");
|
|
862
|
+
const canPublish = capabilityPredicate("publish");
|
|
863
|
+
/** Users grant on any held role (Developer is implicit). */
|
|
864
|
+
const isAdmin = capabilityPredicate("users");
|
|
865
|
+
/** This role id currently has the Users grant, or is Developer. */
|
|
866
|
+
function isPrivilegedRole(role, matrix = DEFAULT_ROLE_MATRIX) {
|
|
867
|
+
if (typeof role !== "string") return false;
|
|
868
|
+
if (role === "developer") return true;
|
|
869
|
+
const row = matrix.find((entry) => entry.role === role);
|
|
870
|
+
return row ? roleHasGrant(row, "users") : false;
|
|
871
|
+
}
|
|
872
|
+
function isAuthenticated(userOrArgs) {
|
|
873
|
+
if (isAccessArgs$1(userOrArgs)) return Boolean(userOrArgs.req.user);
|
|
874
|
+
return Boolean(userOrArgs);
|
|
875
|
+
}
|
|
876
|
+
const isAdminOrSelf = async ({ req }) => {
|
|
877
|
+
if (!req.user) return false;
|
|
878
|
+
if (await isAdmin({ req })) return true;
|
|
879
|
+
return { id: { equals: req.user.id } };
|
|
880
|
+
};
|
|
881
|
+
const authenticatedOrPublished = ({ req: { user } }) => {
|
|
882
|
+
if (isAuthenticated(user)) return true;
|
|
883
|
+
return { _status: { equals: "published" } };
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* API tab condition. Code-locked to the `developer` slug — not a Features
|
|
887
|
+
* row and not a Role tick a client can grant.
|
|
888
|
+
*/
|
|
889
|
+
function isDeveloperTab({ req }) {
|
|
890
|
+
return isDeveloper(req.user);
|
|
891
|
+
}
|
|
892
|
+
/** Access for chrome globals (Features, Better Editor settings). */
|
|
893
|
+
function developerOnlyAccess() {
|
|
894
|
+
return {
|
|
895
|
+
read: ({ req }) => isDeveloper(req.user),
|
|
896
|
+
update: ({ req }) => isDeveloper(req.user)
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
/** `admin.hidden`: hide unless the login is Developer. */
|
|
900
|
+
function hideUnlessDeveloper({ user }) {
|
|
901
|
+
return !isDeveloper(user);
|
|
902
|
+
}
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region src/features/access.ts
|
|
905
|
+
function isAccessArgs(value) {
|
|
906
|
+
return typeof value === "object" && value !== null && "req" in value;
|
|
907
|
+
}
|
|
908
|
+
function grantedOnReleased(user, slug, row, matrix) {
|
|
909
|
+
if (!row.released) return false;
|
|
910
|
+
return hasGrant(user, slug, matrix);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* True when the feature is released and a held role is granted it.
|
|
914
|
+
* Developer is always allowed. An empty Global falls back to catalogue
|
|
915
|
+
* defaults. A group slug (`pages`, `theme`) is true when any child is.
|
|
916
|
+
*/
|
|
917
|
+
function hasFeature(user, slug, features = null, matrix = DEFAULT_ROLE_MATRIX) {
|
|
918
|
+
if (isDeveloper(user)) return true;
|
|
919
|
+
if (!user) return false;
|
|
920
|
+
const rows = features && features.length > 0 ? features : defaultFeaturesFieldValue();
|
|
921
|
+
const leaf = rows.find((entry) => entry.slug === slug);
|
|
922
|
+
if (leaf) return grantedOnReleased(user, slug, leaf, matrix);
|
|
923
|
+
if (!isGroupOnlySlug(slug)) return false;
|
|
924
|
+
return rows.some((entry) => entry.group === slug && grantedOnReleased(user, entry.slug, entry, matrix));
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Reads the saved Features Global. `null` means empty or unreadable — callers
|
|
928
|
+
* fall back to catalogue defaults. Always override-access.
|
|
929
|
+
*/
|
|
930
|
+
async function getFeaturesMatrix(req = {}) {
|
|
931
|
+
const findGlobal = req.payload?.findGlobal;
|
|
932
|
+
if (typeof findGlobal !== "function") return null;
|
|
933
|
+
try {
|
|
934
|
+
const doc = await findGlobal({
|
|
935
|
+
slug: FEATURES_SLUG,
|
|
936
|
+
overrideAccess: true,
|
|
937
|
+
req
|
|
938
|
+
});
|
|
939
|
+
const parsed = parseFeaturesMatrix(doc && typeof doc === "object" && "features" in doc ? doc.features : void 0);
|
|
940
|
+
return parsed.ok ? parsed.rows : null;
|
|
941
|
+
} catch {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Access / nav helper for one catalogue slug. Sync against a passed grid;
|
|
947
|
+
* async when given `req` so it can read both Globals.
|
|
948
|
+
*/
|
|
949
|
+
function canUseFeature(slug) {
|
|
950
|
+
function predicate(userOrArgs, features, roles) {
|
|
951
|
+
if (isAccessArgs(userOrArgs)) {
|
|
952
|
+
const user = userOrArgs.req.user;
|
|
953
|
+
if (features !== void 0) return hasFeature(user, slug, features, roles ?? DEFAULT_ROLE_MATRIX);
|
|
954
|
+
return Promise.all([getFeaturesMatrix(userOrArgs.req), getRolesMatrix(userOrArgs.req)]).then(([grid, matrix]) => hasFeature(user, slug, grid, matrix));
|
|
955
|
+
}
|
|
956
|
+
return hasFeature(userOrArgs, slug, features ?? null, roles ?? DEFAULT_ROLE_MATRIX);
|
|
957
|
+
}
|
|
958
|
+
return predicate;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Feature slugs (and group slugs) this login may use. Written onto the
|
|
962
|
+
* user at afterRead so `admin.hidden({ user })` can follow the saved
|
|
963
|
+
* Features and Roles Globals without a `req`.
|
|
964
|
+
*/
|
|
965
|
+
async function allowedFeaturesForUser(user, req = {}) {
|
|
966
|
+
const [features, roles] = await Promise.all([getFeaturesMatrix(req), getRolesMatrix(req)]);
|
|
967
|
+
return [...(features && features.length > 0 ? features : defaultFeaturesFieldValue()).map((row) => row.slug), ...FEATURE_GROUPS].filter((slug) => hasFeature(user, slug, features, roles));
|
|
968
|
+
}
|
|
969
|
+
function userHasAllowedFeature(user, slug) {
|
|
970
|
+
if (isDeveloper(user)) return true;
|
|
971
|
+
const allowed = user?.allowedFeatures;
|
|
972
|
+
if (!Array.isArray(allowed)) return false;
|
|
973
|
+
return allowed.includes(slug);
|
|
974
|
+
}
|
|
975
|
+
/** `admin.hidden`: hide when the login cannot use the feature. */
|
|
976
|
+
function hideUnlessFeature(slug) {
|
|
977
|
+
return (args) => {
|
|
978
|
+
const user = args.user ?? null;
|
|
979
|
+
if (isDeveloper(user)) return false;
|
|
980
|
+
if (user && Array.isArray(user.allowedFeatures)) return !userHasAllowedFeature(user, slug);
|
|
981
|
+
if (args.req) {
|
|
982
|
+
const result = canUseFeature(slug)({ req: {
|
|
983
|
+
...args.req,
|
|
984
|
+
user
|
|
985
|
+
} });
|
|
986
|
+
if (result instanceof Promise) return result.then((ok) => !ok);
|
|
987
|
+
return !result;
|
|
988
|
+
}
|
|
989
|
+
return !canUseFeature(slug)(user);
|
|
990
|
+
};
|
|
376
991
|
}
|
|
377
992
|
//#endregion
|
|
378
993
|
//#region src/theme/global.ts
|
|
@@ -677,35 +1292,11 @@ function identityFields(collection) {
|
|
|
677
1292
|
}
|
|
678
1293
|
];
|
|
679
1294
|
}
|
|
680
|
-
function pageHooks(child) {
|
|
681
|
-
return {
|
|
682
|
-
beforeChange: [async ({ data, req }) => {
|
|
683
|
-
if (req?.payload) await persistThemeChild(req.payload, data, child);
|
|
684
|
-
return data;
|
|
685
|
-
}],
|
|
686
|
-
afterRead: [async ({ doc, req }) => {
|
|
687
|
-
if (hasThemeSlice(doc, child)) return doc;
|
|
688
|
-
const theme = await req?.payload?.findGlobal({
|
|
689
|
-
slug: THEME_SLUG,
|
|
690
|
-
draft: false,
|
|
691
|
-
depth: child === "identity" ? 1 : 0,
|
|
692
|
-
overrideAccess: true
|
|
693
|
-
});
|
|
694
|
-
if (!theme) return doc;
|
|
695
|
-
return {
|
|
696
|
-
...doc,
|
|
697
|
-
...sliceFromTheme(theme, child)
|
|
698
|
-
};
|
|
699
|
-
}]
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
1295
|
/**
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
* settings forms, not collaborative documents. No draft mode, no preview
|
|
708
|
-
* pane.
|
|
1296
|
+
* One Theme Global. Colors, Typography, Appearance, and Identity are
|
|
1297
|
+
* tabs on that document. Save writes the whole form — Payload hydrates
|
|
1298
|
+
* every tab from the live row, so an untouched tab does not revert.
|
|
1299
|
+
* Locking is off. No draft mode, no preview pane.
|
|
709
1300
|
*/
|
|
710
1301
|
function createTheme(options) {
|
|
711
1302
|
const access = requireAccess$1(options);
|
|
@@ -720,59 +1311,49 @@ function createTheme(options) {
|
|
|
720
1311
|
const typeFields = typographyFields(seed, fonts, fontsBaseUrl);
|
|
721
1312
|
const lookFields = appearanceFields(seed);
|
|
722
1313
|
const markFields = logo ? identityFields(logo.collection) : [];
|
|
723
|
-
|
|
1314
|
+
return [{
|
|
724
1315
|
slug: THEME_SLUG,
|
|
725
1316
|
label: "Theme",
|
|
726
1317
|
lockDocuments: false,
|
|
727
1318
|
admin: {
|
|
728
|
-
|
|
1319
|
+
group: "Theme",
|
|
1320
|
+
hidden: hideUnlessFeature("theme"),
|
|
729
1321
|
hideAPIURL: true,
|
|
730
|
-
custom: adminCustom
|
|
1322
|
+
custom: adminCustom,
|
|
1323
|
+
components: { elements: { beforeDocumentControls: [THEME_DOCUMENT_CONTROLS] } }
|
|
731
1324
|
},
|
|
732
1325
|
access: {
|
|
733
1326
|
read: access.read,
|
|
734
1327
|
update: access.update
|
|
735
1328
|
},
|
|
736
|
-
fields: [
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1329
|
+
fields: [{
|
|
1330
|
+
type: "tabs",
|
|
1331
|
+
tabs: [
|
|
1332
|
+
{
|
|
1333
|
+
label: "Colors",
|
|
1334
|
+
fields: colorFields
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
label: "Typography",
|
|
1338
|
+
fields: typeFields
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
label: "Appearance",
|
|
1342
|
+
fields: lookFields
|
|
1343
|
+
},
|
|
1344
|
+
...logo ? [{
|
|
1345
|
+
label: "Identity",
|
|
1346
|
+
fields: markFields
|
|
1347
|
+
}] : []
|
|
1348
|
+
]
|
|
1349
|
+
}],
|
|
742
1350
|
endpoints: colorUsageEndpoints(access, colorUsages),
|
|
743
1351
|
hooks: { afterChange: [async ({ doc }) => {
|
|
744
1352
|
if (!onPublish) return;
|
|
745
1353
|
const themeDoc = doc;
|
|
746
1354
|
await onPublish(themeConfigFromDoc(themeDoc, seed), themeDoc);
|
|
747
1355
|
}] }
|
|
748
|
-
};
|
|
749
|
-
function page(slug, label, child, fields, preview) {
|
|
750
|
-
return {
|
|
751
|
-
slug,
|
|
752
|
-
label,
|
|
753
|
-
lockDocuments: false,
|
|
754
|
-
admin: {
|
|
755
|
-
group: "Theme",
|
|
756
|
-
hidden: hideUnlessFeature("theme"),
|
|
757
|
-
hideAPIURL: true,
|
|
758
|
-
custom: adminCustom,
|
|
759
|
-
components: preview ? { elements: { beforeDocumentControls: [THEME_DOCUMENT_CONTROLS] } } : void 0
|
|
760
|
-
},
|
|
761
|
-
access: {
|
|
762
|
-
read: access.read,
|
|
763
|
-
update: access.update
|
|
764
|
-
},
|
|
765
|
-
fields,
|
|
766
|
-
hooks: pageHooks(child)
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
return [
|
|
770
|
-
store,
|
|
771
|
-
page(THEME_COLORS_SLUG, "Colors", "colors", colorFields, true),
|
|
772
|
-
page(THEME_TYPOGRAPHY_SLUG, "Typography", "typography", typeFields, true),
|
|
773
|
-
page(THEME_APPEARANCE_SLUG, "Appearance", "appearance", lookFields, true),
|
|
774
|
-
...logo ? [page(THEME_IDENTITY_SLUG, "Identity", "identity", markFields, false)] : []
|
|
775
|
-
];
|
|
1356
|
+
}];
|
|
776
1357
|
}
|
|
777
1358
|
//#endregion
|
|
778
1359
|
//#region src/brand-assets/sanitize.ts
|
|
@@ -840,6 +1421,7 @@ function createBrandAssets(options) {
|
|
|
840
1421
|
admin: {
|
|
841
1422
|
group: "Settings",
|
|
842
1423
|
useAsTitle: "label",
|
|
1424
|
+
hidden: hideUnlessFeature("brand-assets"),
|
|
843
1425
|
description: "Logo, favicon, and mobile-menu mark. SVG preferred; PNG and ICO allowed."
|
|
844
1426
|
},
|
|
845
1427
|
upload: {
|
|
@@ -921,6 +1503,52 @@ const THEME_PREVIEW_BREAKPOINTS = [
|
|
|
921
1503
|
}
|
|
922
1504
|
];
|
|
923
1505
|
//#endregion
|
|
1506
|
+
//#region src/theme/publish.ts
|
|
1507
|
+
/**
|
|
1508
|
+
* Publishing a child writes that slice only. Other responsibilities stay
|
|
1509
|
+
* as they already are on the stored document. Unsaved form state on a
|
|
1510
|
+
* different child never goes live. A first save (no stored row) still
|
|
1511
|
+
* writes only the named child; `getPublishedTheme` fills the rest from
|
|
1512
|
+
* the seed.
|
|
1513
|
+
*/
|
|
1514
|
+
function publishThemeChild(stored, incoming, child) {
|
|
1515
|
+
if (child === "colors") return {
|
|
1516
|
+
...stored,
|
|
1517
|
+
colors: incoming.colors
|
|
1518
|
+
};
|
|
1519
|
+
if (child === "typography") return {
|
|
1520
|
+
...stored,
|
|
1521
|
+
typography: incoming.typography
|
|
1522
|
+
};
|
|
1523
|
+
if (child === "appearance") return {
|
|
1524
|
+
...stored,
|
|
1525
|
+
appearance: incoming.appearance
|
|
1526
|
+
};
|
|
1527
|
+
return {
|
|
1528
|
+
...stored,
|
|
1529
|
+
logo: incoming.logo,
|
|
1530
|
+
favicon: incoming.favicon,
|
|
1531
|
+
logoMark: incoming.logoMark
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Save on a Theme page writes that slice onto the hidden `theme` row.
|
|
1536
|
+
* Other children stay as stored.
|
|
1537
|
+
*/
|
|
1538
|
+
async function persistThemeChild(payload, incoming, child) {
|
|
1539
|
+
const next = publishThemeChild(await payload.findGlobal({
|
|
1540
|
+
slug: "theme",
|
|
1541
|
+
draft: false,
|
|
1542
|
+
overrideAccess: true
|
|
1543
|
+
}) ?? {}, incoming, child);
|
|
1544
|
+
await payload.updateGlobal({
|
|
1545
|
+
slug: THEME_SLUG,
|
|
1546
|
+
data: next,
|
|
1547
|
+
draft: false
|
|
1548
|
+
});
|
|
1549
|
+
return next;
|
|
1550
|
+
}
|
|
1551
|
+
//#endregion
|
|
924
1552
|
//#region src/roles/fields.ts
|
|
925
1553
|
const ROLES_MATRIX_FIELD = "@bison-lab/payload-core/admin#RolesMatrixField";
|
|
926
1554
|
const ROLES_ROW_LABEL = "@bison-lab/payload-core/admin#RolesRowLabel";
|
|
@@ -1030,69 +1658,67 @@ const FEATURES_MATRIX_FIELD = "@bison-lab/payload-core/admin#FeaturesMatrixField
|
|
|
1030
1658
|
*/
|
|
1031
1659
|
function createFeatures({ extras = [] } = {}) {
|
|
1032
1660
|
const catalogue = featureCatalogue(extras);
|
|
1661
|
+
const featuresField = {
|
|
1662
|
+
name: "features",
|
|
1663
|
+
type: "array",
|
|
1664
|
+
label: "Features",
|
|
1665
|
+
labels: {
|
|
1666
|
+
singular: "Feature",
|
|
1667
|
+
plural: "Features"
|
|
1668
|
+
},
|
|
1669
|
+
minRows: catalogue.length,
|
|
1670
|
+
maxRows: catalogue.length,
|
|
1671
|
+
required: true,
|
|
1672
|
+
defaultValue: defaultFeaturesFieldValue(extras),
|
|
1673
|
+
validate: (value) => validateFeaturesMatrix(value, extras),
|
|
1674
|
+
hooks: { beforeChange: [({ value }) => {
|
|
1675
|
+
if (!Array.isArray(value)) return value;
|
|
1676
|
+
return stampFeatureCatalogue(value, extras);
|
|
1677
|
+
}] },
|
|
1678
|
+
admin: {
|
|
1679
|
+
components: { Field: FEATURES_MATRIX_FIELD },
|
|
1680
|
+
description: "Release a feature so it can be assigned on Roles. Off keeps it Developer-only.",
|
|
1681
|
+
initCollapsed: false
|
|
1682
|
+
},
|
|
1683
|
+
fields: [
|
|
1684
|
+
{
|
|
1685
|
+
name: "slug",
|
|
1686
|
+
type: "text",
|
|
1687
|
+
label: "Slug",
|
|
1688
|
+
required: true,
|
|
1689
|
+
admin: { readOnly: true }
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
name: "label",
|
|
1693
|
+
type: "text",
|
|
1694
|
+
label: "Name",
|
|
1695
|
+
required: true,
|
|
1696
|
+
admin: { readOnly: true }
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
name: "group",
|
|
1700
|
+
type: "text",
|
|
1701
|
+
label: "Group",
|
|
1702
|
+
admin: { hidden: true }
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
name: "released",
|
|
1706
|
+
type: "checkbox",
|
|
1707
|
+
label: "Released",
|
|
1708
|
+
admin: { hidden: true }
|
|
1709
|
+
}
|
|
1710
|
+
]
|
|
1711
|
+
};
|
|
1033
1712
|
return {
|
|
1034
1713
|
slug: FEATURES_SLUG,
|
|
1035
1714
|
label: "Features",
|
|
1036
1715
|
admin: {
|
|
1037
1716
|
group: "Settings",
|
|
1038
|
-
hidden:
|
|
1717
|
+
hidden: hideUnlessDeveloper,
|
|
1039
1718
|
description: "Which features other people may be granted on Roles."
|
|
1040
1719
|
},
|
|
1041
|
-
access:
|
|
1042
|
-
|
|
1043
|
-
update: ({ req }) => isDeveloper(req.user)
|
|
1044
|
-
},
|
|
1045
|
-
fields: [{
|
|
1046
|
-
name: "features",
|
|
1047
|
-
type: "array",
|
|
1048
|
-
label: "Features",
|
|
1049
|
-
labels: {
|
|
1050
|
-
singular: "Feature",
|
|
1051
|
-
plural: "Features"
|
|
1052
|
-
},
|
|
1053
|
-
minRows: catalogue.length,
|
|
1054
|
-
maxRows: catalogue.length,
|
|
1055
|
-
required: true,
|
|
1056
|
-
defaultValue: defaultFeaturesFieldValue(extras),
|
|
1057
|
-
validate: (value) => validateFeaturesMatrix(value, extras),
|
|
1058
|
-
hooks: { beforeChange: [({ value }) => {
|
|
1059
|
-
if (!Array.isArray(value)) return value;
|
|
1060
|
-
return stampFeatureCatalogue(value, extras);
|
|
1061
|
-
}] },
|
|
1062
|
-
admin: {
|
|
1063
|
-
components: { Field: FEATURES_MATRIX_FIELD },
|
|
1064
|
-
description: "Release a feature so it can be assigned on Roles. Off keeps it Developer-only.",
|
|
1065
|
-
initCollapsed: false
|
|
1066
|
-
},
|
|
1067
|
-
fields: [
|
|
1068
|
-
{
|
|
1069
|
-
name: "slug",
|
|
1070
|
-
type: "text",
|
|
1071
|
-
label: "Slug",
|
|
1072
|
-
required: true,
|
|
1073
|
-
admin: { readOnly: true }
|
|
1074
|
-
},
|
|
1075
|
-
{
|
|
1076
|
-
name: "label",
|
|
1077
|
-
type: "text",
|
|
1078
|
-
label: "Name",
|
|
1079
|
-
required: true,
|
|
1080
|
-
admin: { readOnly: true }
|
|
1081
|
-
},
|
|
1082
|
-
{
|
|
1083
|
-
name: "group",
|
|
1084
|
-
type: "text",
|
|
1085
|
-
label: "Group",
|
|
1086
|
-
admin: { hidden: true }
|
|
1087
|
-
},
|
|
1088
|
-
{
|
|
1089
|
-
name: "released",
|
|
1090
|
-
type: "checkbox",
|
|
1091
|
-
label: "Released",
|
|
1092
|
-
admin: { hidden: true }
|
|
1093
|
-
}
|
|
1094
|
-
]
|
|
1095
|
-
}]
|
|
1720
|
+
access: developerOnlyAccess(),
|
|
1721
|
+
fields: [featuresField]
|
|
1096
1722
|
};
|
|
1097
1723
|
}
|
|
1098
1724
|
//#endregion
|
|
@@ -1189,6 +1815,15 @@ function slugField({ collection, isReserved }) {
|
|
|
1189
1815
|
//#endregion
|
|
1190
1816
|
//#region src/collections/pages.ts
|
|
1191
1817
|
/**
|
|
1818
|
+
* Content to create or save a draft; Publish to publish or edit a live
|
|
1819
|
+
* page. Without Publish, update is constrained to `_status: draft`.
|
|
1820
|
+
*/
|
|
1821
|
+
const pagesUpdate = async (args) => {
|
|
1822
|
+
if (!await Promise.resolve(canUseFeature("content")(args))) return false;
|
|
1823
|
+
if (await Promise.resolve(canPublish(args))) return true;
|
|
1824
|
+
return { _status: { equals: "draft" } };
|
|
1825
|
+
};
|
|
1826
|
+
/**
|
|
1192
1827
|
* CMS-managed pages: one required hero, then a reorderable body of sections.
|
|
1193
1828
|
* The CMS owns copy and the order of sections; what a section looks like is
|
|
1194
1829
|
* code-owned, which is why the blocks are an argument.
|
|
@@ -1215,7 +1850,7 @@ function createPages({ heroBlocks, isReservedSlug, layoutBlocks, previewPath, pr
|
|
|
1215
1850
|
read: authenticatedOrPublished,
|
|
1216
1851
|
readVersions: isAuthenticated,
|
|
1217
1852
|
create: canUseFeature("content"),
|
|
1218
|
-
update:
|
|
1853
|
+
update: pagesUpdate,
|
|
1219
1854
|
delete: isAdmin
|
|
1220
1855
|
},
|
|
1221
1856
|
versions: {
|
|
@@ -1374,27 +2009,45 @@ function createUsers({ secureCookies, rolesField, extras = [] }) {
|
|
|
1374
2009
|
return arg.result;
|
|
1375
2010
|
}]
|
|
1376
2011
|
},
|
|
1377
|
-
fields: [
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
name: "roles",
|
|
1382
|
-
type: "select",
|
|
1383
|
-
hasMany: true,
|
|
1384
|
-
required: true,
|
|
1385
|
-
defaultValue: ["author"],
|
|
1386
|
-
options: roleSelectOptions(fallback),
|
|
1387
|
-
admin: {
|
|
1388
|
-
components: { Field: rolesField ?? "@bison-lab/payload-core/admin#RolesField" },
|
|
1389
|
-
description: "One person can hold several, e.g. Author + Designer."
|
|
2012
|
+
fields: [
|
|
2013
|
+
{
|
|
2014
|
+
type: "row",
|
|
2015
|
+
fields: [nameField("firstName"), nameField("lastName")]
|
|
1390
2016
|
},
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
2017
|
+
{
|
|
2018
|
+
name: "roles",
|
|
2019
|
+
type: "select",
|
|
2020
|
+
hasMany: true,
|
|
2021
|
+
required: true,
|
|
2022
|
+
defaultValue: ["author"],
|
|
2023
|
+
saveToJWT: true,
|
|
2024
|
+
options: roleSelectOptions(fallback),
|
|
2025
|
+
admin: {
|
|
2026
|
+
components: { Field: rolesField ?? "@bison-lab/payload-core/admin#RolesField" },
|
|
2027
|
+
description: "One person can hold several, e.g. Author + Designer."
|
|
2028
|
+
},
|
|
2029
|
+
access: { update: isAdmin },
|
|
2030
|
+
validate: rolesValidate(extras),
|
|
2031
|
+
hooks: { beforeValidate: [async ({ value, req }) => {
|
|
2032
|
+
if (!Array.isArray(value)) return value;
|
|
2033
|
+
return normalizeStoredRoles(value, await getRolesMatrix(req, extras));
|
|
2034
|
+
}] }
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
name: "allowedFeatures",
|
|
2038
|
+
type: "json",
|
|
2039
|
+
admin: {
|
|
2040
|
+
hidden: true,
|
|
2041
|
+
readOnly: true
|
|
2042
|
+
},
|
|
2043
|
+
access: { update: () => false },
|
|
2044
|
+
saveToJWT: true,
|
|
2045
|
+
hooks: { afterRead: [async ({ siblingData, data, req }) => allowedFeaturesForUser({
|
|
2046
|
+
id: siblingData?.id ?? data?.id,
|
|
2047
|
+
roles: siblingData?.roles ?? data?.roles
|
|
2048
|
+
}, req ?? {})] }
|
|
2049
|
+
}
|
|
2050
|
+
]
|
|
1398
2051
|
};
|
|
1399
2052
|
}
|
|
1400
2053
|
//#endregion
|
|
@@ -1470,6 +2123,6 @@ const adminOnlyApiTab = definePlugin({
|
|
|
1470
2123
|
})
|
|
1471
2124
|
});
|
|
1472
2125
|
//#endregion
|
|
1473
|
-
export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, CAPABILITY_FEATURES, DEFAULT_ROLE_GRANTS, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DUPLICATE_ROLE_NAME_MESSAGE, DUPLICATE_ROLE_SLUG_MESSAGE, FEATURES_MATRIX_FIELD, FEATURES_SLUG, FEATURE_GROUPS, FEATURE_GROUP_LABELS, INVALID_ROLE_NAME_MESSAGE, LAST_ADMIN_DELETE_MESSAGE, LAST_ADMIN_DEMOTE_MESSAGE, LAST_USERS_TICK_MESSAGE, LOOK_FIELD, MISSING_PACKAGE_FEATURES_MESSAGE, MISSING_SEED_ROLES_MESSAGE, PACKAGE_FEATURES, PACKAGE_FEATURE_SLUGS, PAGE_EDITOR_SYSTEM_KEYS, ROLES, ROLES_FIELD, ROLES_FIELD_DESCRIPTION, ROLES_GLOBAL_DESCRIPTION, ROLES_GRANTS_FIELD, ROLES_MATRIX_FIELD, ROLES_ROW_LABEL, ROLES_SLUG, ROLE_LABELS, ROLE_NAME_FIELD, ROLE_SLUG_FIELD, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FEATURE_SLUGS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, adminOnlyApiTab, applyRoleSlugsFromNames, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, createBrandAssets, createFeatures, createMedia, createPages, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, documentTitle, featureCatalogue, featureGroupLabel, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getRolesMatrix, hasCapability, hasFeature, hasGrant, hasRole, hideUnlessFeature, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isFeatureGroupId, isFeatureSlug, isLockedFeature, isPackageFeatureSlug, isPrivilegedRole, isRole, isRoleName, isRoleSlug, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parseFeaturesMatrix, parseRolesMatrix, persistThemeChild, publishThemeChild, resetRolesMatrix, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeRoleNameInput, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugField, slugifyRoleName, stampFeatureCatalogue, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateRolesMatrix };
|
|
2126
|
+
export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, CAPABILITY_FEATURES, DEFAULT_ROLE_GRANTS, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DUPLICATE_ROLE_NAME_MESSAGE, DUPLICATE_ROLE_SLUG_MESSAGE, FEATURES_MATRIX_FIELD, FEATURES_SLUG, FEATURE_GROUPS, FEATURE_GROUP_LABELS, INVALID_ROLE_NAME_MESSAGE, LAST_ADMIN_DELETE_MESSAGE, LAST_ADMIN_DEMOTE_MESSAGE, LAST_USERS_TICK_MESSAGE, LOOK_FIELD, MISSING_PACKAGE_FEATURES_MESSAGE, MISSING_SEED_ROLES_MESSAGE, PACKAGE_FEATURES, PACKAGE_FEATURE_SLUGS, PAGE_EDITOR_SYSTEM_KEYS, ROLES, ROLES_FIELD, ROLES_FIELD_DESCRIPTION, ROLES_GLOBAL_DESCRIPTION, ROLES_GRANTS_FIELD, ROLES_MATRIX_FIELD, ROLES_ROW_LABEL, ROLES_SLUG, ROLE_LABELS, ROLE_NAME_FIELD, ROLE_SLUG_FIELD, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FEATURE_SLUGS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, adminOnlyApiTab, allowedFeaturesForUser, applyRoleSlugsFromNames, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, createBrandAssets, createFeatures, createMedia, createPages, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, developerOnlyAccess, documentTitle, featureCatalogue, featureGroupLabel, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getRolesMatrix, hasCapability, hasFeature, hasGrant, hasRole, hideUnlessDeveloper, hideUnlessFeature, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isFeatureGroupId, isFeatureSlug, isLockedFeature, isPackageFeatureSlug, isPrivilegedRole, isRole, isRoleName, isRoleSlug, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parseFeaturesMatrix, parseRolesMatrix, persistThemeChild, publishThemeChild, resetRolesMatrix, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeRoleNameInput, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugField, slugifyRoleName, stampFeatureCatalogue, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateRolesMatrix };
|
|
1474
2127
|
|
|
1475
2128
|
//# sourceMappingURL=index.mjs.map
|