@bison-lab/payload-core 3.13.0 → 3.15.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 -29
- package/dist/admin.d.mts +25 -5
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +437 -178
- package/dist/admin.mjs.map +1 -1
- package/dist/index.d.mts +93 -50
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +849 -725
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/admin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { a as colorDocFromState, i as SYSTEM_COLOR_KEYS, n as pageEditorLooks, r as pageEditorTokens, s as stateFromColorDoc } from "./looks-BbL309kO.mjs";
|
|
3
|
-
import { ArrayField, CheckboxInput, FieldDescription, FieldError, FieldLabel, fieldBaseClass, useConfig, useField, useForm, useFormFields, useRowLabel } from "@payloadcms/ui";
|
|
3
|
+
import { ArrayField, CheckboxInput, FieldDescription, FieldError, FieldLabel, fieldBaseClass, useAuth, useConfig, useField, useForm, useFormFields, useRowLabel } from "@payloadcms/ui";
|
|
4
4
|
import { GREY_SCALES, SHADE_STEPS, contrastForeground, contrastRatio, createColorScale, deriveDarkPalette, deriveLightPalette, hexToHSL, hslToString, includedShadeSteps, isThemeHex, presetHints, setColorScaleInclude } from "@bison-lab/tokens";
|
|
5
5
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -2515,6 +2515,153 @@ const LookField = ({ field, path, readOnly }) => {
|
|
|
2515
2515
|
});
|
|
2516
2516
|
};
|
|
2517
2517
|
//#endregion
|
|
2518
|
+
//#region src/features/types.ts
|
|
2519
|
+
const FEATURES_SLUG = "features";
|
|
2520
|
+
const FEATURE_GROUPS = [
|
|
2521
|
+
"pages",
|
|
2522
|
+
"media",
|
|
2523
|
+
"theme",
|
|
2524
|
+
"users"
|
|
2525
|
+
];
|
|
2526
|
+
const FEATURE_GROUP_LABELS = {
|
|
2527
|
+
pages: "Pages",
|
|
2528
|
+
media: "Media",
|
|
2529
|
+
theme: "Theme",
|
|
2530
|
+
users: "Users"
|
|
2531
|
+
};
|
|
2532
|
+
/**
|
|
2533
|
+
* Package catalogue. Features itself is not a row — that screen is
|
|
2534
|
+
* Developer-only in code. Empty Global falls back to these defaults.
|
|
2535
|
+
*/
|
|
2536
|
+
const PACKAGE_FEATURES = [
|
|
2537
|
+
{
|
|
2538
|
+
slug: "content",
|
|
2539
|
+
label: "Content",
|
|
2540
|
+
group: "pages",
|
|
2541
|
+
defaultReleased: true
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
slug: "publish",
|
|
2545
|
+
label: "Publish",
|
|
2546
|
+
group: "pages",
|
|
2547
|
+
defaultReleased: true
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
slug: "media",
|
|
2551
|
+
label: "Media",
|
|
2552
|
+
group: "media",
|
|
2553
|
+
defaultReleased: true
|
|
2554
|
+
},
|
|
2555
|
+
{
|
|
2556
|
+
slug: "theme-colors",
|
|
2557
|
+
label: "Colors",
|
|
2558
|
+
group: "theme",
|
|
2559
|
+
defaultReleased: true
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
slug: "theme-typography",
|
|
2563
|
+
label: "Typography",
|
|
2564
|
+
group: "theme",
|
|
2565
|
+
defaultReleased: true
|
|
2566
|
+
},
|
|
2567
|
+
{
|
|
2568
|
+
slug: "theme-appearance",
|
|
2569
|
+
label: "Appearance",
|
|
2570
|
+
group: "theme",
|
|
2571
|
+
defaultReleased: true
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
slug: "theme-identity",
|
|
2575
|
+
label: "Identity",
|
|
2576
|
+
group: "theme",
|
|
2577
|
+
defaultReleased: true
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
slug: "brand-assets",
|
|
2581
|
+
label: "Brand assets",
|
|
2582
|
+
group: "theme",
|
|
2583
|
+
defaultReleased: true
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
slug: "users",
|
|
2587
|
+
label: "Users",
|
|
2588
|
+
group: "users",
|
|
2589
|
+
defaultReleased: true
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
slug: "roles",
|
|
2593
|
+
label: "Roles",
|
|
2594
|
+
group: "users",
|
|
2595
|
+
defaultReleased: true
|
|
2596
|
+
}
|
|
2597
|
+
];
|
|
2598
|
+
function isFeatureGroupId(value) {
|
|
2599
|
+
return typeof value === "string" && FEATURE_GROUPS.includes(value);
|
|
2600
|
+
}
|
|
2601
|
+
function isFeatureSlug(value) {
|
|
2602
|
+
return typeof value === "string" && /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
|
|
2603
|
+
}
|
|
2604
|
+
//#endregion
|
|
2605
|
+
//#region src/features/matrix.ts
|
|
2606
|
+
const MISSING_PACKAGE_FEATURES_MESSAGE = "Features must include Content, Publish, Media, Theme screens, Brand assets, Users, and Roles.";
|
|
2607
|
+
function featureCatalogue(extras = []) {
|
|
2608
|
+
return [...PACKAGE_FEATURES.map((feature) => ({ ...feature })), ...extras.map((extra) => ({
|
|
2609
|
+
slug: extra.slug,
|
|
2610
|
+
label: extra.label,
|
|
2611
|
+
group: extra.group ?? "users",
|
|
2612
|
+
defaultReleased: extra.defaultReleased ?? false
|
|
2613
|
+
}))];
|
|
2614
|
+
}
|
|
2615
|
+
function defaultFeaturesFieldValue(extras = []) {
|
|
2616
|
+
return featureCatalogue(extras).map((feature) => ({
|
|
2617
|
+
id: feature.slug,
|
|
2618
|
+
slug: feature.slug,
|
|
2619
|
+
label: feature.label,
|
|
2620
|
+
group: feature.group,
|
|
2621
|
+
released: feature.defaultReleased
|
|
2622
|
+
}));
|
|
2623
|
+
}
|
|
2624
|
+
function parseFeaturesMatrix(value) {
|
|
2625
|
+
if (!Array.isArray(value) || value.length === 0) return {
|
|
2626
|
+
ok: false,
|
|
2627
|
+
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
2628
|
+
};
|
|
2629
|
+
const rows = [];
|
|
2630
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2631
|
+
for (const item of value) {
|
|
2632
|
+
if (!item || typeof item !== "object") continue;
|
|
2633
|
+
const slug = "slug" in item ? item.slug : void 0;
|
|
2634
|
+
if (!isFeatureSlug(slug) || seen.has(slug)) continue;
|
|
2635
|
+
seen.add(slug);
|
|
2636
|
+
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
2637
|
+
const label = "label" in item && typeof item.label === "string" ? item.label : pack?.label ?? slug;
|
|
2638
|
+
const group = "group" in item && isFeatureGroupId(item.group) ? item.group : pack?.group ?? "users";
|
|
2639
|
+
rows.push({
|
|
2640
|
+
id: slug,
|
|
2641
|
+
slug,
|
|
2642
|
+
label,
|
|
2643
|
+
group,
|
|
2644
|
+
released: Boolean("released" in item && item.released)
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
if (PACKAGE_FEATURES.some((feature) => !seen.has(feature.slug))) return {
|
|
2648
|
+
ok: false,
|
|
2649
|
+
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
2650
|
+
};
|
|
2651
|
+
return {
|
|
2652
|
+
ok: true,
|
|
2653
|
+
rows
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
function featureGroupLabel(group) {
|
|
2657
|
+
return isFeatureGroupId(group) ? FEATURE_GROUP_LABELS[group] : group;
|
|
2658
|
+
}
|
|
2659
|
+
function packageFeatureLabel(slug, extras = []) {
|
|
2660
|
+
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
2661
|
+
if (pack) return pack.label;
|
|
2662
|
+
return extras.find((feature) => feature.slug === slug)?.label ?? slug;
|
|
2663
|
+
}
|
|
2664
|
+
//#endregion
|
|
2518
2665
|
//#region src/roles/types.ts
|
|
2519
2666
|
/**
|
|
2520
2667
|
* The Roles Global a site's generated types will describe. Optional and
|
|
@@ -2533,75 +2680,90 @@ const ROLE_LABELS = {
|
|
|
2533
2680
|
designer: "Designer",
|
|
2534
2681
|
author: "Author"
|
|
2535
2682
|
};
|
|
2536
|
-
const
|
|
2537
|
-
"
|
|
2538
|
-
"
|
|
2539
|
-
"
|
|
2540
|
-
"
|
|
2683
|
+
const THEME_FEATURE_SLUGS = [
|
|
2684
|
+
"theme-colors",
|
|
2685
|
+
"theme-typography",
|
|
2686
|
+
"theme-appearance",
|
|
2687
|
+
"theme-identity",
|
|
2688
|
+
"brand-assets"
|
|
2541
2689
|
];
|
|
2690
|
+
const DEFAULT_ROLE_GRANTS = {
|
|
2691
|
+
developer: [],
|
|
2692
|
+
admin: [
|
|
2693
|
+
"content",
|
|
2694
|
+
"publish",
|
|
2695
|
+
"media",
|
|
2696
|
+
"users",
|
|
2697
|
+
"roles"
|
|
2698
|
+
],
|
|
2699
|
+
designer: [
|
|
2700
|
+
"content",
|
|
2701
|
+
"media",
|
|
2702
|
+
...THEME_FEATURE_SLUGS,
|
|
2703
|
+
"users",
|
|
2704
|
+
"roles"
|
|
2705
|
+
],
|
|
2706
|
+
author: ["content", "media"]
|
|
2707
|
+
};
|
|
2542
2708
|
function isRole(value) {
|
|
2543
2709
|
return typeof value === "string" && ROLES.includes(value);
|
|
2544
2710
|
}
|
|
2545
|
-
/** Lowercase slug: `
|
|
2711
|
+
/** Lowercase slug from a letters-and-spaces name: `designer`, `the-designer`. */
|
|
2546
2712
|
function isRoleSlug(value) {
|
|
2547
|
-
return typeof value === "string" && /^[a-z]
|
|
2713
|
+
return typeof value === "string" && /^[a-z]+(-[a-z]+)*$/.test(value);
|
|
2714
|
+
}
|
|
2715
|
+
/** Strip digits and punctuation as the name is typed. Trailing space stays. */
|
|
2716
|
+
function sanitizeRoleNameInput(value) {
|
|
2717
|
+
return value.replace(/[^A-Za-z ]/g, "").replace(/ {2,}/g, " ");
|
|
2548
2718
|
}
|
|
2549
2719
|
function roleLabel(role, label) {
|
|
2550
2720
|
const trimmed = typeof label === "string" ? label.trim() : "";
|
|
2551
2721
|
if (trimmed) return trimmed;
|
|
2552
2722
|
return isRole(role) ? ROLE_LABELS[role] : role;
|
|
2553
2723
|
}
|
|
2724
|
+
function defaultGrantsForRole(role, extraGrants) {
|
|
2725
|
+
if (isRole(role)) return [...DEFAULT_ROLE_GRANTS[role]];
|
|
2726
|
+
return extraGrants ? [...extraGrants] : [];
|
|
2727
|
+
}
|
|
2554
2728
|
//#endregion
|
|
2555
2729
|
//#region src/roles/matrix.ts
|
|
2556
2730
|
const ROLES_SLUG = "roles";
|
|
2557
2731
|
/**
|
|
2558
|
-
* Default rank and
|
|
2559
|
-
*
|
|
2732
|
+
* Default rank and grants. Developer is implicit (empty grants). Brand
|
|
2733
|
+
* screens default to Designer.
|
|
2560
2734
|
*/
|
|
2561
|
-
const DEFAULT_ROLE_MATRIX =
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
publish: true,
|
|
2567
|
-
users: true
|
|
2568
|
-
},
|
|
2569
|
-
{
|
|
2570
|
-
role: "admin",
|
|
2571
|
-
content: true,
|
|
2572
|
-
brand: false,
|
|
2573
|
-
publish: true,
|
|
2574
|
-
users: true
|
|
2575
|
-
},
|
|
2576
|
-
{
|
|
2577
|
-
role: "designer",
|
|
2578
|
-
content: false,
|
|
2579
|
-
brand: true,
|
|
2580
|
-
publish: false,
|
|
2581
|
-
users: false
|
|
2582
|
-
},
|
|
2583
|
-
{
|
|
2584
|
-
role: "author",
|
|
2585
|
-
content: true,
|
|
2586
|
-
brand: false,
|
|
2587
|
-
publish: false,
|
|
2588
|
-
users: false
|
|
2589
|
-
}
|
|
2590
|
-
];
|
|
2591
|
-
const DEVELOPER_DESCRIPTION = "Everything Admin can do, plus the document API tab.";
|
|
2735
|
+
const DEFAULT_ROLE_MATRIX = ROLES.map((role) => ({
|
|
2736
|
+
role,
|
|
2737
|
+
grants: defaultGrantsForRole(role)
|
|
2738
|
+
}));
|
|
2739
|
+
const DEVELOPER_DESCRIPTION = "Everything, including features not released for assignment.";
|
|
2592
2740
|
const MISSING_SEED_ROLES_MESSAGE = "Roles must include Developer, Admin, Designer, and Author.";
|
|
2593
2741
|
const DUPLICATE_ROLE_SLUG_MESSAGE = "Each role needs a unique slug.";
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2742
|
+
function uniqueSlugs(values) {
|
|
2743
|
+
const slugs = [];
|
|
2744
|
+
for (const value of values) if (typeof value === "string" && isFeatureSlug(value) && !slugs.includes(value)) slugs.push(value);
|
|
2745
|
+
return slugs;
|
|
2746
|
+
}
|
|
2747
|
+
/** Read stored grants, or rebuild them from the old capability ticks. */
|
|
2748
|
+
function grantsFromStoredRole(item) {
|
|
2749
|
+
if ("grants" in item && Array.isArray(item.grants)) return uniqueSlugs(item.grants);
|
|
2750
|
+
const grants = [];
|
|
2751
|
+
if ("content" in item && item.content) grants.push("content", "media");
|
|
2752
|
+
if ("publish" in item && item.publish) grants.push("publish");
|
|
2753
|
+
if ("users" in item && item.users) grants.push("users", "roles");
|
|
2754
|
+
if ("brand" in item && item.brand) grants.push(...THEME_FEATURE_SLUGS);
|
|
2755
|
+
return uniqueSlugs(grants);
|
|
2756
|
+
}
|
|
2600
2757
|
function seedRoleRows(extras = []) {
|
|
2601
2758
|
return [...DEFAULT_ROLE_MATRIX.map((row) => ({
|
|
2602
2759
|
...row,
|
|
2760
|
+
grants: [...row.grants],
|
|
2603
2761
|
label: isRole(row.role) ? ROLE_LABELS[row.role] : row.role
|
|
2604
|
-
})), ...extras.map((extra) => ({
|
|
2762
|
+
})), ...extras.map((extra) => ({
|
|
2763
|
+
role: extra.role,
|
|
2764
|
+
label: extra.label,
|
|
2765
|
+
grants: extra.grants ? [...extra.grants] : []
|
|
2766
|
+
}))];
|
|
2605
2767
|
}
|
|
2606
2768
|
function defaultRolesFieldValue(extras = []) {
|
|
2607
2769
|
return seedRoleRows(extras).map((row) => ({
|
|
@@ -2617,10 +2779,7 @@ function readRoleRow(item) {
|
|
|
2617
2779
|
id: role,
|
|
2618
2780
|
role,
|
|
2619
2781
|
label: roleLabel(role, "label" in item && typeof item.label === "string" ? item.label : void 0),
|
|
2620
|
-
|
|
2621
|
-
brand: Boolean("brand" in item && item.brand),
|
|
2622
|
-
publish: Boolean("publish" in item && item.publish),
|
|
2623
|
-
users: Boolean("users" in item && item.users)
|
|
2782
|
+
grants: grantsFromStoredRole(item)
|
|
2624
2783
|
};
|
|
2625
2784
|
}
|
|
2626
2785
|
function parseRolesMatrix(value) {
|
|
@@ -2632,6 +2791,8 @@ function parseRolesMatrix(value) {
|
|
|
2632
2791
|
const seen = /* @__PURE__ */ new Set();
|
|
2633
2792
|
for (const item of value) {
|
|
2634
2793
|
if (!item || typeof item !== "object") continue;
|
|
2794
|
+
const role = "role" in item ? item.role : void 0;
|
|
2795
|
+
if (typeof role !== "string" || role.trim() === "") continue;
|
|
2635
2796
|
const parsed = readRoleRow(item);
|
|
2636
2797
|
if ("error" in parsed) return {
|
|
2637
2798
|
ok: false,
|
|
@@ -2659,15 +2820,15 @@ function roleSelectOptions(matrix = DEFAULT_ROLE_MATRIX) {
|
|
|
2659
2820
|
value: row.role
|
|
2660
2821
|
}));
|
|
2661
2822
|
}
|
|
2662
|
-
/**
|
|
2823
|
+
/** Grant copy only. Developer names the unreleased catalogue; nothing about MCP or seed. */
|
|
2663
2824
|
function roleDescription(role, matrix = DEFAULT_ROLE_MATRIX) {
|
|
2664
2825
|
if (role === "developer") return DEVELOPER_DESCRIPTION;
|
|
2665
2826
|
const row = matrix.find((entry) => entry.role === role);
|
|
2666
2827
|
if (!row) return "No capabilities";
|
|
2667
|
-
return
|
|
2828
|
+
return row.grants.map((slug) => packageFeatureLabel(slug)).join(", ") || "No capabilities";
|
|
2668
2829
|
}
|
|
2669
2830
|
/**
|
|
2670
|
-
* Seed
|
|
2831
|
+
* Seed grants and package labels come back; extra rows stay as they are.
|
|
2671
2832
|
*/
|
|
2672
2833
|
function resetRolesMatrix(current) {
|
|
2673
2834
|
const extras = [];
|
|
@@ -2681,57 +2842,71 @@ function resetRolesMatrix(current) {
|
|
|
2681
2842
|
}
|
|
2682
2843
|
//#endregion
|
|
2683
2844
|
//#region src/admin/roles-matrix-field.tsx
|
|
2684
|
-
const HIDE_ROW_CHROME = `.bl-roles-matrix .array-actions__add,.bl-roles-matrix .array-actions__duplicate,.bl-roles-matrix .array-actions__copy{display:none}.bl-roles-matrix__toolbar{display:flex;justify-content:flex-end;margin-block-end:.75rem}.bl-roles-matrix__reset{min-height:2.25rem;padding:.35rem .9rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;background:transparent;color:inherit;font:inherit;cursor:pointer}body.bl-roles-matrix-seed-menu .array-actions__remove{display:none}`;
|
|
2845
|
+
const HIDE_ROW_CHROME = `.bl-roles-matrix .array-actions__add,.bl-roles-matrix .array-actions__duplicate,.bl-roles-matrix .array-actions__copy{display:none}.bl-roles-matrix .array-field__add-row{display:none}.bl-roles-matrix--hide-developer .array-field__row:has([data-role-seed="developer"]),.bl-roles-matrix--hide-developer .collapsible:has([data-role-seed="developer"]){display:none}.bl-roles-matrix__toolbar{display:flex;justify-content:flex-end;gap:.5rem;margin-block-end:.75rem}.bl-roles-matrix__reset,.bl-roles-matrix__add{min-height:2.25rem;padding:.35rem .9rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;background:transparent;color:inherit;font:inherit;cursor:pointer}body.bl-roles-matrix-seed-menu .array-actions__remove{display:none}`;
|
|
2685
2846
|
function seedRoleFromTrigger(trigger) {
|
|
2686
2847
|
let node = trigger.parentElement;
|
|
2687
2848
|
while (node) {
|
|
2688
2849
|
const slugs = node.querySelectorAll("[data-role-slug]");
|
|
2689
|
-
if (slugs.length === 1) return isRole(slugs[0]?.getAttribute("data-role-slug"));
|
|
2850
|
+
if (slugs.length === 1) return isRole(slugs[0]?.getAttribute("data-role-seed") || slugs[0]?.getAttribute("data-role-slug"));
|
|
2690
2851
|
if (slugs.length > 1) return false;
|
|
2691
2852
|
node = node.parentElement;
|
|
2692
2853
|
}
|
|
2693
2854
|
return false;
|
|
2694
2855
|
}
|
|
2856
|
+
function viewerIsDeveloper(user) {
|
|
2857
|
+
if (!user || typeof user !== "object" || !("roles" in user)) return false;
|
|
2858
|
+
const roles = user.roles;
|
|
2859
|
+
return Array.isArray(roles) && roles.includes("developer");
|
|
2860
|
+
}
|
|
2695
2861
|
/**
|
|
2696
2862
|
* Payload's array field: drag rank stays, add is for custom rows, seed
|
|
2697
|
-
* rows have no remove.
|
|
2863
|
+
* rows have no remove. Developer stays in the saved array and is shown
|
|
2864
|
+
* only to a Developer. Reset restores seed ticks and package labels.
|
|
2698
2865
|
*/
|
|
2699
2866
|
const RolesMatrixField = (props) => {
|
|
2700
2867
|
const { setValue, value } = useField({ path: props.path });
|
|
2868
|
+
const { user } = useAuth();
|
|
2869
|
+
const hideDeveloper = !viewerIsDeveloper(user);
|
|
2701
2870
|
const rootRef = useRef(null);
|
|
2702
2871
|
const seedMenu = useRef(false);
|
|
2872
|
+
const onClickCapture = useCallback((event) => {
|
|
2873
|
+
const target = event.target;
|
|
2874
|
+
if (!(target instanceof Element)) return;
|
|
2875
|
+
const trigger = target.closest(".array-actions__button");
|
|
2876
|
+
if (trigger) {
|
|
2877
|
+
const owner = trigger.closest(".array-field");
|
|
2878
|
+
seedMenu.current = owner !== null && owner === rootRef.current?.querySelector(".array-field") && seedRoleFromTrigger(trigger);
|
|
2879
|
+
document.body.classList.toggle("bl-roles-matrix-seed-menu", seedMenu.current);
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (!target.closest(".array-actions__remove")) return;
|
|
2883
|
+
if (!seedMenu.current) return;
|
|
2884
|
+
event.preventDefault();
|
|
2885
|
+
event.stopPropagation();
|
|
2886
|
+
}, []);
|
|
2703
2887
|
return /* @__PURE__ */ jsxs("div", {
|
|
2704
|
-
className: "bl-roles-matrix",
|
|
2888
|
+
className: ["bl-roles-matrix", hideDeveloper && "bl-roles-matrix--hide-developer"].filter(Boolean).join(" "),
|
|
2705
2889
|
ref: rootRef,
|
|
2706
|
-
onClickCapture
|
|
2707
|
-
const target = event.target;
|
|
2708
|
-
if (!(target instanceof Element)) return;
|
|
2709
|
-
const trigger = target.closest(".array-actions__button");
|
|
2710
|
-
if (trigger) {
|
|
2711
|
-
const owner = trigger.closest(".array-field");
|
|
2712
|
-
seedMenu.current = owner !== null && owner === rootRef.current?.querySelector(".array-field") && seedRoleFromTrigger(trigger);
|
|
2713
|
-
document.body.classList.toggle("bl-roles-matrix-seed-menu", seedMenu.current);
|
|
2714
|
-
return;
|
|
2715
|
-
}
|
|
2716
|
-
if (!target.closest(".array-actions__remove")) return;
|
|
2717
|
-
if (!seedMenu.current) return;
|
|
2718
|
-
event.preventDefault();
|
|
2719
|
-
event.stopPropagation();
|
|
2720
|
-
}, []),
|
|
2890
|
+
onClickCapture,
|
|
2721
2891
|
children: [
|
|
2722
2892
|
/* @__PURE__ */ jsx("style", {
|
|
2723
2893
|
href: "bl-roles-matrix",
|
|
2724
2894
|
precedence: "default",
|
|
2725
2895
|
children: HIDE_ROW_CHROME
|
|
2726
2896
|
}),
|
|
2727
|
-
/* @__PURE__ */
|
|
2897
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2728
2898
|
className: "bl-roles-matrix__toolbar",
|
|
2729
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
2899
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
2900
|
+
type: "button",
|
|
2901
|
+
className: "bl-roles-matrix__add",
|
|
2902
|
+
onClick: () => rootRef.current?.querySelector(".array-field__add-row")?.click(),
|
|
2903
|
+
children: "Add Role"
|
|
2904
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2730
2905
|
type: "button",
|
|
2731
2906
|
className: "bl-roles-matrix__reset",
|
|
2732
2907
|
onClick: () => setValue(resetRolesMatrix(value)),
|
|
2733
2908
|
children: "Reset to defaults"
|
|
2734
|
-
})
|
|
2909
|
+
})]
|
|
2735
2910
|
}),
|
|
2736
2911
|
/* @__PURE__ */ jsx(ArrayField, { ...props })
|
|
2737
2912
|
]
|
|
@@ -2745,9 +2920,11 @@ const RolesMatrixField = (props) => {
|
|
|
2745
2920
|
function RolesRowLabel() {
|
|
2746
2921
|
const { data } = useRowLabel();
|
|
2747
2922
|
const role = typeof data.role === "string" ? data.role : "";
|
|
2923
|
+
const seed = typeof data.seed === "string" && data.seed ? data.seed : isRole(role) ? role : "";
|
|
2748
2924
|
const label = roleLabel(role, data.label) || "Role";
|
|
2749
2925
|
return /* @__PURE__ */ jsx("span", {
|
|
2750
2926
|
"data-role-slug": role,
|
|
2927
|
+
"data-role-seed": seed,
|
|
2751
2928
|
children: label
|
|
2752
2929
|
});
|
|
2753
2930
|
}
|
|
@@ -2801,8 +2978,56 @@ const RoleSlugField = ({ field, path, readOnly }) => {
|
|
|
2801
2978
|
});
|
|
2802
2979
|
};
|
|
2803
2980
|
//#endregion
|
|
2981
|
+
//#region src/admin/role-name-field.tsx
|
|
2982
|
+
/**
|
|
2983
|
+
* Role name. Letters and spaces only, so the hidden slug stays
|
|
2984
|
+
* `the-greatest-designer` from `The Greatest Designer`.
|
|
2985
|
+
*/
|
|
2986
|
+
const RoleNameField = ({ field, path, readOnly }) => {
|
|
2987
|
+
const { value, setValue, showError, errorMessage } = useField({ path });
|
|
2988
|
+
const required = Boolean(field.required);
|
|
2989
|
+
const id = `field-${path.replace(/\./g, "__")}`;
|
|
2990
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2991
|
+
className: [
|
|
2992
|
+
fieldBaseClass,
|
|
2993
|
+
"text",
|
|
2994
|
+
readOnly && "read-only"
|
|
2995
|
+
].filter(Boolean).join(" "),
|
|
2996
|
+
children: [
|
|
2997
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
2998
|
+
htmlFor: id,
|
|
2999
|
+
label: field.label,
|
|
3000
|
+
required
|
|
3001
|
+
}),
|
|
3002
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3003
|
+
className: `${fieldBaseClass}__wrap`,
|
|
3004
|
+
children: [/* @__PURE__ */ jsx(FieldError, {
|
|
3005
|
+
message: errorMessage,
|
|
3006
|
+
path,
|
|
3007
|
+
showError
|
|
3008
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
3009
|
+
id,
|
|
3010
|
+
type: "text",
|
|
3011
|
+
autoComplete: "off",
|
|
3012
|
+
spellCheck: false,
|
|
3013
|
+
readOnly: Boolean(readOnly),
|
|
3014
|
+
disabled: Boolean(readOnly),
|
|
3015
|
+
value: value ?? "",
|
|
3016
|
+
onChange: (event) => {
|
|
3017
|
+
if (!readOnly) setValue(sanitizeRoleNameInput(event.target.value));
|
|
3018
|
+
}
|
|
3019
|
+
})]
|
|
3020
|
+
}),
|
|
3021
|
+
/* @__PURE__ */ jsx(FieldDescription, {
|
|
3022
|
+
description: field.admin?.description,
|
|
3023
|
+
path
|
|
3024
|
+
})
|
|
3025
|
+
]
|
|
3026
|
+
});
|
|
3027
|
+
};
|
|
3028
|
+
//#endregion
|
|
2804
3029
|
//#region src/admin/roles-field.tsx
|
|
2805
|
-
const GRID_CSS
|
|
3030
|
+
const GRID_CSS = `.bl-roles__grid{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));margin-block:.35rem 0}.bl-roles__option{display:flex;flex-direction:column;gap:.25rem;padding:.65rem .75rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px}.bl-roles__option.is-checked{border-color:var(--theme-elevation-400,#94a3b8)}.bl-roles__option.is-included{opacity:.7}.bl-roles__what{font-size:.8rem;line-height:1.35;color:var(--theme-elevation-500,#64748b)}`;
|
|
2806
3031
|
function optionValue(option) {
|
|
2807
3032
|
return typeof option === "string" ? option : String(option.value);
|
|
2808
3033
|
}
|
|
@@ -2881,7 +3106,7 @@ const RolesField = ({ field, path: pathFromProps, readOnly, validate }) => {
|
|
|
2881
3106
|
/* @__PURE__ */ jsx("style", {
|
|
2882
3107
|
href: "bl-roles",
|
|
2883
3108
|
precedence: "default",
|
|
2884
|
-
children: GRID_CSS
|
|
3109
|
+
children: GRID_CSS
|
|
2885
3110
|
}),
|
|
2886
3111
|
/* @__PURE__ */ jsx(FieldLabel, {
|
|
2887
3112
|
as: "span",
|
|
@@ -2933,135 +3158,169 @@ const RolesField = ({ field, path: pathFromProps, readOnly, validate }) => {
|
|
|
2933
3158
|
});
|
|
2934
3159
|
};
|
|
2935
3160
|
//#endregion
|
|
2936
|
-
//#region src/
|
|
3161
|
+
//#region src/admin/roles-grants-field.tsx
|
|
3162
|
+
const LIST_CSS$1 = `.bl-role-grants{margin-block:.2rem 0;font-size:16px}.bl-role-grants__group{margin-block:.75rem 0}.bl-role-grants__group-title{font-size:13px;letter-spacing:.02em;color:var(--theme-elevation-400,#94a3b8);font-weight:650;margin-block-end:.25rem}.bl-role-grants__row{display:flex;align-items:center;gap:.65rem;padding:.35rem 0}.bl-role-grants__row.is-implicit{opacity:.85}.bl-role-grants input{inline-size:1.15rem;block-size:1.15rem}.bl-role-grants input:disabled{cursor:default}.bl-role-grants__note{margin-block:.35rem 0;font-size:13px;color:var(--theme-elevation-500,#64748b)}`;
|
|
3163
|
+
function asGrants(value) {
|
|
3164
|
+
if (!Array.isArray(value)) return [];
|
|
3165
|
+
return value.filter((entry) => typeof entry === "string");
|
|
3166
|
+
}
|
|
3167
|
+
function rolePathFromGrants(path) {
|
|
3168
|
+
return path.replace(/\.grants$/, ".role");
|
|
3169
|
+
}
|
|
2937
3170
|
/**
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
2940
|
-
*
|
|
3171
|
+
* Released catalogue rows as ticks. Unreleased features do not appear,
|
|
3172
|
+
* except on Developer: every catalogue row is shown granted and locked
|
|
3173
|
+
* so a new Feature is visibly included.
|
|
2941
3174
|
*/
|
|
2942
|
-
const
|
|
2943
|
-
{
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
slug
|
|
2969
|
-
|
|
2970
|
-
fallback: "users",
|
|
2971
|
-
locked: true
|
|
2972
|
-
},
|
|
2973
|
-
{
|
|
2974
|
-
slug: "roles",
|
|
2975
|
-
label: "Roles",
|
|
2976
|
-
fallback: "users",
|
|
2977
|
-
locked: true
|
|
2978
|
-
},
|
|
2979
|
-
{
|
|
2980
|
-
slug: "features",
|
|
2981
|
-
label: "Features",
|
|
2982
|
-
fallback: "users",
|
|
2983
|
-
locked: true
|
|
3175
|
+
const RolesGrantsField = (props) => {
|
|
3176
|
+
const { setValue, value } = useField({ path: props.path });
|
|
3177
|
+
const { value: role } = useField({ path: rolePathFromGrants(props.path) });
|
|
3178
|
+
const { config } = useConfig();
|
|
3179
|
+
const [catalogue, setCatalogue] = useState(defaultFeaturesFieldValue());
|
|
3180
|
+
const api = config?.routes?.api;
|
|
3181
|
+
const serverURL = config?.serverURL ?? "";
|
|
3182
|
+
const implicitAll = role === "developer";
|
|
3183
|
+
useEffect(() => {
|
|
3184
|
+
if (!api) return;
|
|
3185
|
+
let cancelled = false;
|
|
3186
|
+
fetch(`${serverURL}${api}/globals/${FEATURES_SLUG}?depth=0`, { credentials: "include" }).then((response) => response.ok ? response.json() : null).then((doc) => {
|
|
3187
|
+
if (cancelled || !doc) return;
|
|
3188
|
+
const parsed = parseFeaturesMatrix(doc.features);
|
|
3189
|
+
if (parsed.ok) setCatalogue(parsed.rows);
|
|
3190
|
+
}).catch(() => void 0);
|
|
3191
|
+
return () => {
|
|
3192
|
+
cancelled = true;
|
|
3193
|
+
};
|
|
3194
|
+
}, [api, serverURL]);
|
|
3195
|
+
const grants = asGrants(value);
|
|
3196
|
+
const shown = implicitAll ? catalogue : catalogue.filter((row) => row.released);
|
|
3197
|
+
function toggle(slug, checked) {
|
|
3198
|
+
if (implicitAll) return;
|
|
3199
|
+
const next = new Set(grants);
|
|
3200
|
+
if (checked) next.add(slug);
|
|
3201
|
+
else next.delete(slug);
|
|
3202
|
+
setValue([...next]);
|
|
2984
3203
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3204
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3205
|
+
className: "bl-role-grants",
|
|
3206
|
+
children: [
|
|
3207
|
+
/* @__PURE__ */ jsx("style", {
|
|
3208
|
+
href: "bl-role-grants",
|
|
3209
|
+
precedence: "default",
|
|
3210
|
+
children: LIST_CSS$1
|
|
3211
|
+
}),
|
|
3212
|
+
implicitAll ? /* @__PURE__ */ jsx("p", {
|
|
3213
|
+
className: "bl-role-grants__note",
|
|
3214
|
+
children: "Always granted, including features not yet released."
|
|
3215
|
+
}) : null,
|
|
3216
|
+
FEATURE_GROUPS.map((group) => {
|
|
3217
|
+
const members = shown.filter((row) => (row.group ?? "users") === group);
|
|
3218
|
+
if (members.length === 0) return null;
|
|
3219
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3220
|
+
className: "bl-role-grants__group",
|
|
3221
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3222
|
+
className: "bl-role-grants__group-title",
|
|
3223
|
+
children: featureGroupLabel(group)
|
|
3224
|
+
}), members.map((row) => /* @__PURE__ */ jsxs("label", {
|
|
3225
|
+
className: ["bl-role-grants__row", implicitAll && "is-implicit"].filter(Boolean).join(" "),
|
|
3226
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3227
|
+
type: "checkbox",
|
|
3228
|
+
"aria-label": `${row.label || row.slug}`,
|
|
3229
|
+
checked: implicitAll || grants.includes(row.slug),
|
|
3230
|
+
disabled: implicitAll,
|
|
3231
|
+
onChange: (event) => toggle(row.slug, event.target.checked)
|
|
3232
|
+
}), /* @__PURE__ */ jsx("span", { children: row.label || row.slug })]
|
|
3233
|
+
}, row.slug))]
|
|
3234
|
+
}, group);
|
|
3235
|
+
})
|
|
3236
|
+
]
|
|
3237
|
+
});
|
|
3238
|
+
};
|
|
2990
3239
|
//#endregion
|
|
2991
3240
|
//#region src/admin/features-matrix-field.tsx
|
|
2992
|
-
const
|
|
3241
|
+
const LIST_CSS = `.bl-features{margin-block:.35rem 0;font-size:16px}.bl-features__group{margin-block:1rem 0}.bl-features__group-title{font-size:14px;letter-spacing:.02em;color:var(--theme-elevation-400,#94a3b8);font-weight:650;margin-block-end:.35rem}.bl-features__row{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.55rem 0;border-bottom:1px solid var(--theme-elevation-150,#e2e8f0)}.bl-features__row:last-child{border-bottom:0}.bl-features input{inline-size:1.15rem;block-size:1.15rem}`;
|
|
2993
3242
|
function asRows(value) {
|
|
2994
3243
|
return Array.isArray(value) ? value : [];
|
|
2995
3244
|
}
|
|
3245
|
+
function FeatureReleaseRow({ path, index, row, rows, setRows }) {
|
|
3246
|
+
const { setValue: setReleased } = useField({ path: `${path}.${index}.released` });
|
|
3247
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3248
|
+
className: "bl-features__row",
|
|
3249
|
+
children: [/* @__PURE__ */ jsx("span", { children: row.label || row.slug }), /* @__PURE__ */ jsx("input", {
|
|
3250
|
+
type: "checkbox",
|
|
3251
|
+
"aria-label": `Release ${row.label || row.slug}`,
|
|
3252
|
+
checked: Boolean(row.released),
|
|
3253
|
+
onChange: (event) => {
|
|
3254
|
+
const released = event.target.checked;
|
|
3255
|
+
setReleased(released);
|
|
3256
|
+
setRows(rows.map((entry) => entry.slug === row.slug ? {
|
|
3257
|
+
...entry,
|
|
3258
|
+
released
|
|
3259
|
+
} : entry));
|
|
3260
|
+
}
|
|
3261
|
+
})]
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
2996
3264
|
/**
|
|
2997
|
-
*
|
|
2998
|
-
*
|
|
3265
|
+
* One release switch per catalogue row, grouped. Off hides the tick on
|
|
3266
|
+
* Roles; Developer still has the feature.
|
|
3267
|
+
*
|
|
3268
|
+
* Writes both the array value and each row's `released` checkbox path.
|
|
3269
|
+
* Payload still registers those child fields; toggling only the parent
|
|
3270
|
+
* array leaves them false and Save stores the old flags.
|
|
2999
3271
|
*/
|
|
3000
3272
|
const FeaturesMatrixField = (props) => {
|
|
3001
3273
|
const { setValue, value } = useField({ path: props.path });
|
|
3002
3274
|
const { config } = useConfig();
|
|
3003
|
-
const [columns, setColumns] = useState(roleSelectOptions(seedRoleRows()));
|
|
3004
3275
|
const api = config?.routes?.api;
|
|
3005
3276
|
const serverURL = config?.serverURL ?? "";
|
|
3006
3277
|
useEffect(() => {
|
|
3007
|
-
if (!api) return;
|
|
3278
|
+
if (!api || asRows(value).length > 0) return;
|
|
3008
3279
|
let cancelled = false;
|
|
3009
|
-
fetch(`${serverURL}${api}/globals/${
|
|
3280
|
+
fetch(`${serverURL}${api}/globals/${FEATURES_SLUG}?depth=0`, { credentials: "include" }).then((response) => response.ok ? response.json() : null).then((doc) => {
|
|
3010
3281
|
if (cancelled || !doc) return;
|
|
3011
|
-
const
|
|
3012
|
-
if (
|
|
3282
|
+
const incoming = asRows(doc.features);
|
|
3283
|
+
if (incoming.length > 0) setValue(incoming, true);
|
|
3013
3284
|
}).catch(() => void 0);
|
|
3014
3285
|
return () => {
|
|
3015
3286
|
cancelled = true;
|
|
3016
3287
|
};
|
|
3017
|
-
}, [
|
|
3288
|
+
}, [
|
|
3289
|
+
api,
|
|
3290
|
+
serverURL,
|
|
3291
|
+
setValue,
|
|
3292
|
+
value
|
|
3293
|
+
]);
|
|
3018
3294
|
const rows = asRows(value);
|
|
3019
|
-
function toggle(slug, role, checked) {
|
|
3020
|
-
if (role === "developer") return;
|
|
3021
|
-
setValue(rows.map((row) => {
|
|
3022
|
-
if (row.slug !== slug || isLockedFeature(row.slug, row.locked)) return row;
|
|
3023
|
-
const roles = new Set(row.roles ?? []);
|
|
3024
|
-
if (checked) roles.add(role);
|
|
3025
|
-
else roles.delete(role);
|
|
3026
|
-
roles.add("developer");
|
|
3027
|
-
return {
|
|
3028
|
-
...row,
|
|
3029
|
-
roles: [...roles]
|
|
3030
|
-
};
|
|
3031
|
-
}));
|
|
3032
|
-
}
|
|
3033
3295
|
return /* @__PURE__ */ jsxs("div", {
|
|
3034
3296
|
className: "bl-features",
|
|
3035
3297
|
children: [/* @__PURE__ */ jsx("style", {
|
|
3036
3298
|
href: "bl-features",
|
|
3037
3299
|
precedence: "default",
|
|
3038
|
-
children:
|
|
3039
|
-
}),
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
}) }, column.value);
|
|
3060
|
-
})] }, row.slug);
|
|
3061
|
-
}) })] })]
|
|
3300
|
+
children: LIST_CSS
|
|
3301
|
+
}), FEATURE_GROUPS.map((group) => {
|
|
3302
|
+
const members = rows.map((row, index) => ({
|
|
3303
|
+
row,
|
|
3304
|
+
index
|
|
3305
|
+
})).filter(({ row }) => (row.group ?? "users") === group);
|
|
3306
|
+
if (members.length === 0) return null;
|
|
3307
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3308
|
+
className: "bl-features__group",
|
|
3309
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3310
|
+
className: "bl-features__group-title",
|
|
3311
|
+
children: featureGroupLabel(group)
|
|
3312
|
+
}), members.map(({ row, index }) => /* @__PURE__ */ jsx(FeatureReleaseRow, {
|
|
3313
|
+
path: props.path,
|
|
3314
|
+
index,
|
|
3315
|
+
row,
|
|
3316
|
+
rows,
|
|
3317
|
+
setRows: (next) => setValue(next)
|
|
3318
|
+
}, row.slug))]
|
|
3319
|
+
}, group);
|
|
3320
|
+
})]
|
|
3062
3321
|
});
|
|
3063
3322
|
};
|
|
3064
3323
|
//#endregion
|
|
3065
|
-
export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FeaturesMatrixField, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, RoleSlugField, RolesField, RolesMatrixField, RolesRowLabel, SectionHeading, ThemeDocumentControls, ThemeSaveButton };
|
|
3324
|
+
export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FeaturesMatrixField, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, RoleNameField, RoleSlugField, RolesField, RolesGrantsField, RolesMatrixField, RolesRowLabel, SectionHeading, ThemeDocumentControls, ThemeSaveButton };
|
|
3066
3325
|
|
|
3067
3326
|
//# sourceMappingURL=admin.mjs.map
|