@bison-lab/payload-core 3.24.0 → 3.26.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/dist/index.mjs CHANGED
@@ -6,6 +6,7 @@ import { catalog } from "@bison-lab/fonts";
6
6
  import { DESTRUCTIVE_SCALE_HEX, SHADE_STEPS, presetHints } from "@bison-lab/tokens";
7
7
  import { APIError, Forbidden, ValidationError, definePlugin, validations } from "payload";
8
8
  import { select, text } from "payload/shared";
9
+ import { redirectsPlugin } from "@payloadcms/plugin-redirects";
9
10
  //#region src/seo/fields.ts
10
11
  /**
11
12
  * The index switch, last in the SEO tab. Off by default: a page is public
@@ -1753,13 +1754,98 @@ async function seedFeatures(payload, extras = []) {
1753
1754
  }
1754
1755
  //#endregion
1755
1756
  //#region src/fields/slug.ts
1757
+ const SLUG_FIELD = "@bison-lab/payload-core/admin#SlugField";
1758
+ const PAGE_TITLE_CELL = "@bison-lab/payload-core/admin#PageTitleCell";
1759
+ /** Sentinel the Parent control writes while the editor is naming a new folder. */
1760
+ const NEW_FOLDER_PARENT = "__new__";
1761
+ const EXISTING_PARENT_MESSAGE = "That folder is already in the list. Pick it from the list.";
1762
+ const SLUG_UNLOCK_WARNING = "This page is live. Changing the address will send visitors from the old URL to the new one.";
1763
+ async function allSlugs({ collection, id, req }) {
1764
+ if (typeof req?.payload?.find !== "function") return [];
1765
+ const where = {};
1766
+ if (id !== void 0) where.id = { not_equals: id };
1767
+ const query = {
1768
+ collection,
1769
+ depth: 0,
1770
+ limit: 500,
1771
+ overrideAccess: true,
1772
+ pagination: false,
1773
+ req,
1774
+ where
1775
+ };
1776
+ const [live, drafts] = await Promise.all([req.payload.find(query), req.payload.find({
1777
+ ...query,
1778
+ draft: true
1779
+ })]);
1780
+ return [...live.docs, ...drafts.docs].flatMap((doc) => typeof doc.slug === "string" && doc.slug ? [doc.slug] : []);
1781
+ }
1782
+ async function existingParents(prefix, args) {
1783
+ if (new Set((args.folderRoots ?? []).map(normalizeSlug).filter(Boolean)).has(prefix)) return true;
1784
+ const slugs = await allSlugs(args);
1785
+ if (slugs.includes(prefix)) return true;
1786
+ return inferredFolders(slugs).includes(prefix);
1787
+ }
1788
+ function slugifySegment(segment) {
1789
+ return segment.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1790
+ }
1756
1791
  /**
1757
- * A stored slug from whatever was typed: lowercased, with any run of
1758
- * whitespace and slashes stripped from either end in one pass, so `/ about-us /`
1759
- * does not keep the inner spaces a trim-then-strip would leave.
1792
+ * A stored slug from whatever was typed: NFKD, hyphenated, slash segments
1793
+ * kept, so `Café / About Us` becomes `cafe/about-us`.
1760
1794
  */
1761
1795
  function normalizeSlug(value) {
1762
- return value.replace(/^[\s/]+|[\s/]+$/g, "").toLowerCase();
1796
+ return value.split("/").map(slugifySegment).filter(Boolean).join("/");
1797
+ }
1798
+ /** The folder prefix Parent stores. `__new__` resolves through `newFolder`. */
1799
+ function parentPrefix(parent, newFolder) {
1800
+ if (parent === "__new__") return normalizeSlug(newFolder ?? "");
1801
+ return typeof parent === "string" ? normalizeSlug(parent) : "";
1802
+ }
1803
+ /** Last segment of a stored slug — the title leaf once a folder is set. */
1804
+ function slugLeaf(slug) {
1805
+ const parts = normalizeSlug(slug).split("/");
1806
+ return parts[parts.length - 1] ?? "";
1807
+ }
1808
+ /**
1809
+ * Address from a parent prefix and a title (or an existing leaf). Title-follow
1810
+ * uses this so a folder is not wiped when the title changes.
1811
+ */
1812
+ function composeSlug(parent, title, newFolder) {
1813
+ const prefix = parentPrefix(parent, newFolder);
1814
+ const leaf = slugifySegment(title);
1815
+ return prefix ? `${prefix}/${leaf}` : leaf;
1816
+ }
1817
+ /** Prefixes that exist only because a child slug lives under them. */
1818
+ function inferredFolders(slugs) {
1819
+ const folders = /* @__PURE__ */ new Set();
1820
+ for (const slug of slugs) {
1821
+ const parts = normalizeSlug(slug).split("/");
1822
+ for (let i = 1; i < parts.length; i += 1) folders.add(parts.slice(0, i).join("/"));
1823
+ }
1824
+ return [...folders].sort();
1825
+ }
1826
+ /** Slash count after the first segment — how far to indent a list row. */
1827
+ function slugDepth(slug) {
1828
+ const normalized = normalizeSlug(slug);
1829
+ if (!normalized) return 0;
1830
+ return normalized.split("/").length - 1;
1831
+ }
1832
+ function resolvePrefix(...sources) {
1833
+ let parent;
1834
+ let newFolder;
1835
+ for (const source of sources) {
1836
+ if (parent === void 0 && typeof source?.parent === "string") parent = source.parent;
1837
+ if (newFolder === void 0 && typeof source?.newFolder === "string") newFolder = source.newFolder;
1838
+ }
1839
+ return {
1840
+ parent,
1841
+ newFolder
1842
+ };
1843
+ }
1844
+ function isLockedDoc(doc) {
1845
+ return Boolean(doc?.slugLocked || doc?.publishedSlug);
1846
+ }
1847
+ function isUnlocking(data) {
1848
+ return Boolean(data?.slugUnlock);
1763
1849
  }
1764
1850
  async function slugProblem(slug, { collection, id, isReserved, req }) {
1765
1851
  if (!slug) return void 0;
@@ -1781,13 +1867,52 @@ async function slugProblem(slug, { collection, id, isReserved, req }) {
1781
1867
  draft: true
1782
1868
  })).docs.length > 0 ? `Another page is already using "/${slug}". Please choose a different address.` : void 0;
1783
1869
  }
1870
+ const slugUpdateAccess = (collection) => async ({ doc, id, req, siblingData }) => {
1871
+ if (isUnlocking(siblingData)) return true;
1872
+ if (isLockedDoc(doc)) return false;
1873
+ if (id === void 0 || typeof req?.payload?.findByID !== "function") return true;
1874
+ try {
1875
+ return (await req.payload.findByID({
1876
+ collection,
1877
+ id,
1878
+ depth: 0,
1879
+ draft: false,
1880
+ overrideAccess: true,
1881
+ req
1882
+ }))?._status !== "published";
1883
+ } catch {
1884
+ return true;
1885
+ }
1886
+ };
1887
+ /**
1888
+ * Locks the slug on first publish and remembers the live path so a later
1889
+ * change can write a redirect. Unlock is a one-save flag and is cleared here.
1890
+ */
1891
+ const lockSlugOnPublish = ({ data }) => {
1892
+ if (!data) return data;
1893
+ data.slugUnlock = false;
1894
+ if (data._status === "published") {
1895
+ data.slugLocked = true;
1896
+ if (typeof data.slug === "string") data.publishedSlug = data.slug;
1897
+ }
1898
+ return data;
1899
+ };
1900
+ function hiddenCheckbox(name, label) {
1901
+ return {
1902
+ name,
1903
+ type: "checkbox",
1904
+ label,
1905
+ defaultValue: false,
1906
+ admin: { hidden: true }
1907
+ };
1908
+ }
1784
1909
  /**
1785
1910
  * The path a document is published at, normalised on the way in. Unique
1786
- * across the collection. `validate` is the message under the field;
1787
- * `beforeChange` is the enforcement on a draft save, where Payload skips
1788
- * field validation.
1911
+ * across the collection. Follows `title` until first publish, then locks.
1912
+ * `validate` is the message under the field; `beforeChange` is the enforcement
1913
+ * on a draft save, where Payload skips field validation.
1789
1914
  */
1790
- function slugField({ collection, isReserved }) {
1915
+ function slugField({ collection, isReserved, folderRoots = [] }) {
1791
1916
  const validate = async (value, options) => {
1792
1917
  try {
1793
1918
  const builtIn = await validations.text(value, options);
@@ -1800,14 +1925,25 @@ function slugField({ collection, isReserved }) {
1800
1925
  req: options.req
1801
1926
  }) ?? true;
1802
1927
  };
1803
- return {
1928
+ const slug = {
1804
1929
  name: "slug",
1805
1930
  type: "text",
1806
1931
  required: true,
1807
1932
  unique: true,
1808
- admin: { description: "Path under the site root, no leading slash: \"about-us\" or \"patients/stories\"." },
1933
+ access: { update: slugUpdateAccess(collection) },
1934
+ admin: {
1935
+ description: "Path under the site root, no leading slash: \"about-us\" or \"patients/stories\".",
1936
+ components: { Field: SLUG_FIELD },
1937
+ custom: { folderRoots }
1938
+ },
1809
1939
  hooks: {
1810
- beforeValidate: [({ value }) => typeof value === "string" ? normalizeSlug(value) : value],
1940
+ beforeValidate: [({ data, originalDoc, siblingData, value }) => {
1941
+ const title = typeof data?.title === "string" ? data.title : "";
1942
+ const { parent, newFolder } = resolvePrefix(siblingData, data, originalDoc);
1943
+ if (!isLockedDoc(originalDoc) && !isUnlocking(data) && !isUnlocking(siblingData) && title) return composeSlug(parent, title, newFolder);
1944
+ if ((isUnlocking(data) || isUnlocking(siblingData)) && (parent !== void 0 || newFolder)) return composeSlug(parent, typeof value === "string" && value ? slugLeaf(value) : title ? slugifySegment(title) : "", newFolder);
1945
+ return typeof value === "string" ? normalizeSlug(value) : value;
1946
+ }],
1811
1947
  beforeChange: [async ({ data, originalDoc, req, value }) => {
1812
1948
  if (typeof value !== "string") return value;
1813
1949
  const problem = await slugProblem(value, {
@@ -1829,9 +1965,81 @@ function slugField({ collection, isReserved }) {
1829
1965
  },
1830
1966
  validate
1831
1967
  };
1968
+ return [
1969
+ {
1970
+ name: "parent",
1971
+ type: "text",
1972
+ defaultValue: "",
1973
+ access: { update: slugUpdateAccess(collection) },
1974
+ admin: { hidden: true },
1975
+ hooks: { beforeValidate: [({ data, originalDoc, siblingData, value }) => {
1976
+ const { newFolder } = resolvePrefix(siblingData, data, originalDoc);
1977
+ if (value === "__new__") return parentPrefix(NEW_FOLDER_PARENT, newFolder);
1978
+ return typeof value === "string" ? normalizeSlug(value) : "";
1979
+ }] },
1980
+ validate: async (value, options) => {
1981
+ const { newFolder } = resolvePrefix(options.siblingData, options.data);
1982
+ if (value !== "__new__" && !newFolder) return true;
1983
+ const prefix = parentPrefix(NEW_FOLDER_PARENT, newFolder);
1984
+ if (!prefix) return true;
1985
+ return await existingParents(prefix, {
1986
+ collection,
1987
+ folderRoots,
1988
+ id: options.id,
1989
+ isReserved,
1990
+ req: options.req
1991
+ }) ? EXISTING_PARENT_MESSAGE : true;
1992
+ }
1993
+ },
1994
+ {
1995
+ name: "newFolder",
1996
+ type: "text",
1997
+ admin: { hidden: true },
1998
+ access: { update: slugUpdateAccess(collection) }
1999
+ },
2000
+ slug,
2001
+ hiddenCheckbox("slugLocked", "Slug locked"),
2002
+ hiddenCheckbox("slugUnlock", "Unlock slug"),
2003
+ {
2004
+ name: "publishedSlug",
2005
+ type: "text",
2006
+ label: "Published slug",
2007
+ admin: { hidden: true }
2008
+ }
2009
+ ];
1832
2010
  }
1833
2011
  //#endregion
1834
2012
  //#region src/collections/pages.ts
2013
+ /** Standing copy on the Layout field — same job as the hero description. */
2014
+ const LAYOUT_DESCRIPTION = "Every page needs at least one section below the hero. A page cannot be published without one.";
2015
+ /** Field error when Publish is clicked with an empty body. */
2016
+ const LAYOUT_REQUIRED_MESSAGE = "Add at least one section. A page cannot be published without one.";
2017
+ /**
2018
+ * Toast label for that error. Payload's envelope is always
2019
+ * "The following field is invalid: {label}", so the label has to name the fix.
2020
+ */
2021
+ const LAYOUT_REQUIRED_LABEL = "Layout — add a section";
2022
+ function layoutRows(data, originalDoc) {
2023
+ if (data && "layout" in data && data.layout !== void 0) return Array.isArray(data.layout) ? data.layout : [];
2024
+ return Array.isArray(originalDoc?.layout) ? originalDoc.layout : [];
2025
+ }
2026
+ /**
2027
+ * Drafts may be empty. Publish with no section is refused here so the toast
2028
+ * names the fix — Payload's built-in minRows toast only says "Content → Layout".
2029
+ */
2030
+ const requireLayoutOnPublish = ({ collection, data, originalDoc, req }) => {
2031
+ if (!data || data._status !== "published") return data;
2032
+ if (layoutRows(data, originalDoc).length >= 1) return data;
2033
+ throw new ValidationError({
2034
+ collection: typeof collection?.slug === "string" ? collection.slug : "pages",
2035
+ errors: [{
2036
+ label: LAYOUT_REQUIRED_LABEL,
2037
+ message: LAYOUT_REQUIRED_MESSAGE,
2038
+ path: "layout"
2039
+ }],
2040
+ req
2041
+ }, req?.t);
2042
+ };
1835
2043
  /**
1836
2044
  * Content to create or save a draft; Publish to publish or edit a live
1837
2045
  * page. Without Publish, update is constrained to `_status: draft`.
@@ -1846,11 +2054,12 @@ const pagesUpdate = async (args) => {
1846
2054
  * The CMS owns copy and the order of sections; what a section looks like is
1847
2055
  * code-owned, which is why the blocks are an argument.
1848
2056
  */
1849
- function createPages({ heroBlocks, isReservedSlug, layoutBlocks, previewPath, previewButton }) {
2057
+ function createPages({ folderRoots, heroBlocks, isReservedSlug, layoutBlocks, previewPath, previewButton }) {
1850
2058
  const [defaultHero] = heroBlocks;
1851
2059
  if (!defaultHero) throw new Error("createPages needs at least one hero block");
1852
2060
  return {
1853
2061
  slug: "pages",
2062
+ defaultSort: "slug",
1854
2063
  admin: {
1855
2064
  useAsTitle: "title",
1856
2065
  group: "Content",
@@ -1875,14 +2084,20 @@ function createPages({ heroBlocks, isReservedSlug, layoutBlocks, previewPath, pr
1875
2084
  drafts: { autosave: { interval: 375 } },
1876
2085
  maxPerDoc: 50
1877
2086
  },
2087
+ hooks: {
2088
+ beforeValidate: [requireLayoutOnPublish],
2089
+ beforeChange: [lockSlugOnPublish]
2090
+ },
1878
2091
  fields: [
1879
2092
  {
1880
2093
  name: "title",
1881
2094
  type: "text",
1882
- required: true
2095
+ required: true,
2096
+ admin: { components: { Cell: PAGE_TITLE_CELL } }
1883
2097
  },
1884
- slugField({
2098
+ ...slugField({
1885
2099
  collection: "pages",
2100
+ folderRoots,
1886
2101
  isReserved: isReservedSlug
1887
2102
  }),
1888
2103
  {
@@ -1904,7 +2119,8 @@ function createPages({ heroBlocks, isReservedSlug, layoutBlocks, previewPath, pr
1904
2119
  labels: {
1905
2120
  singular: "Section",
1906
2121
  plural: "Sections"
1907
- }
2122
+ },
2123
+ admin: { description: LAYOUT_DESCRIPTION }
1908
2124
  }
1909
2125
  ]
1910
2126
  };
@@ -2097,6 +2313,119 @@ function createMedia({ staticDir = "media", mimeTypes = ["image/*"], imageSizes
2097
2313
  };
2098
2314
  }
2099
2315
  //#endregion
2316
+ //#region src/plugins/redirects.ts
2317
+ const REDIRECTS_SLUG = "redirects";
2318
+ function redirectPath(slug) {
2319
+ const normalized = normalizeSlug(slug);
2320
+ return normalized ? `/${normalized}` : "";
2321
+ }
2322
+ async function recordPublishedSlugRedirect({ fromSlug, payload, req, toSlug }) {
2323
+ const from = redirectPath(fromSlug);
2324
+ const to = redirectPath(toSlug);
2325
+ if (!from || !to || from === to) return;
2326
+ const found = async (where) => payload.find({
2327
+ collection: REDIRECTS_SLUG,
2328
+ depth: 0,
2329
+ limit: 100,
2330
+ overrideAccess: true,
2331
+ pagination: false,
2332
+ req,
2333
+ where
2334
+ });
2335
+ for (const doc of (await found({ from: { equals: to } })).docs) await payload.delete({
2336
+ collection: REDIRECTS_SLUG,
2337
+ id: doc.id,
2338
+ overrideAccess: true,
2339
+ req
2340
+ });
2341
+ for (const doc of (await found({ "to.url": { equals: from } })).docs) await payload.update({
2342
+ collection: REDIRECTS_SLUG,
2343
+ data: { to: {
2344
+ type: "custom",
2345
+ url: to
2346
+ } },
2347
+ id: doc.id,
2348
+ overrideAccess: true,
2349
+ req
2350
+ });
2351
+ const data = {
2352
+ from,
2353
+ to: {
2354
+ type: "custom",
2355
+ url: to
2356
+ },
2357
+ type: "308"
2358
+ };
2359
+ const existing = (await found({ from: { equals: from } })).docs[0];
2360
+ if (existing) {
2361
+ await payload.update({
2362
+ collection: REDIRECTS_SLUG,
2363
+ data,
2364
+ id: existing.id,
2365
+ overrideAccess: true,
2366
+ req
2367
+ });
2368
+ return;
2369
+ }
2370
+ await payload.create({
2371
+ collection: REDIRECTS_SLUG,
2372
+ data,
2373
+ overrideAccess: true,
2374
+ req
2375
+ });
2376
+ }
2377
+ const writePublishedSlugRedirect = async ({ doc, previousDoc, req }) => {
2378
+ if (doc._status !== "published") return doc;
2379
+ const fromSlug = typeof previousDoc?.publishedSlug === "string" ? previousDoc.publishedSlug : "";
2380
+ const toSlug = typeof doc.slug === "string" ? doc.slug : "";
2381
+ await recordPublishedSlugRedirect({
2382
+ fromSlug,
2383
+ payload: req.payload,
2384
+ req,
2385
+ toSlug
2386
+ });
2387
+ return doc;
2388
+ };
2389
+ function withPublishedSlugRedirect(collection) {
2390
+ return {
2391
+ ...collection,
2392
+ hooks: {
2393
+ ...collection.hooks,
2394
+ afterChange: [...collection.hooks?.afterChange ?? [], writePublishedSlugRedirect]
2395
+ }
2396
+ };
2397
+ }
2398
+ /**
2399
+ * `@payloadcms/plugin-redirects` scoped to `pages`, 308 only. A published
2400
+ * slug change writes the redirect and collapses chains. The plugin stores
2401
+ * rows; the site still has to serve them.
2402
+ */
2403
+ function createRedirects() {
2404
+ const plugin = redirectsPlugin({
2405
+ collections: ["pages"],
2406
+ redirectTypeFieldOverride: {
2407
+ admin: { hidden: true },
2408
+ defaultValue: "308"
2409
+ },
2410
+ redirectTypes: ["308"],
2411
+ overrides: {
2412
+ access: {
2413
+ create: canUseFeature("content"),
2414
+ delete: isAdmin,
2415
+ update: canUseFeature("content")
2416
+ },
2417
+ admin: { group: "Content" }
2418
+ }
2419
+ });
2420
+ return (config) => {
2421
+ const next = plugin(config);
2422
+ return {
2423
+ ...next,
2424
+ collections: next.collections?.map((collection) => collection.slug === "pages" ? withPublishedSlugRedirect(collection) : collection)
2425
+ };
2426
+ };
2427
+ }
2428
+ //#endregion
2100
2429
  //#region src/admin-nav/fields.ts
2101
2430
  const ADMIN_NAV = "@bison-lab/payload-core/admin#AdminNav";
2102
2431
  const ADMIN_NAV_ROW_LABEL = "@bison-lab/payload-core/admin#AdminNavRowLabel";
@@ -2684,6 +3013,6 @@ const documentTitleActions = definePlugin({
2684
3013
  plugin: ({ config }) => withTitleActions(config)
2685
3014
  });
2686
3015
  //#endregion
2687
- export { ADMIN_NAV, ADMIN_NAV_ENTITY_FIELD, ADMIN_NAV_ITEM_ROW_LABEL, ADMIN_NAV_ROW_LABEL, ADMIN_NAV_SLUG, BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, CAPABILITY_FEATURES, DEFAULT_ROLE_GRANTS, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DOCUMENT_CREATE_NEW, DOCUMENT_TITLE_ACTIONS, 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, NAVIGATION_DB_NAME, NAVIGATION_FEATURE, NAVIGATION_FOOTER_DB_NAME, NAVIGATION_FOOTER_SLUG, NAVIGATION_SLUG, 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, adminNav, adminOnlyApiTab, allowedFeaturesForUser, applyRoleSlugsFromNames, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, createAdminNav, createBrandAssets, createFeatures, createMedia, createNavigation, createPages, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, developerOnlyAccess, documentCreateNew, documentTitle, documentTitleActions, featureCatalogue, featureGroupLabel, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getNavigation, getRolesMatrix, hasCapability, hasFeature, hasGrant, hasRole, headerCtaEnabled, 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, resolveAdminNav, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeRoleNameInput, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugField, slugifyRoleName, stampFeatureCatalogue, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateHeaderCta, validateRolesMatrix };
3016
+ export { ADMIN_NAV, ADMIN_NAV_ENTITY_FIELD, ADMIN_NAV_ITEM_ROW_LABEL, ADMIN_NAV_ROW_LABEL, ADMIN_NAV_SLUG, BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, CAPABILITY_FEATURES, DEFAULT_ROLE_GRANTS, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DOCUMENT_CREATE_NEW, DOCUMENT_TITLE_ACTIONS, DUPLICATE_ROLE_NAME_MESSAGE, DUPLICATE_ROLE_SLUG_MESSAGE, EXISTING_PARENT_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, LAYOUT_DESCRIPTION, LAYOUT_REQUIRED_LABEL, LAYOUT_REQUIRED_MESSAGE, LOOK_FIELD, MISSING_PACKAGE_FEATURES_MESSAGE, MISSING_SEED_ROLES_MESSAGE, NAVIGATION_DB_NAME, NAVIGATION_FEATURE, NAVIGATION_FOOTER_DB_NAME, NAVIGATION_FOOTER_SLUG, NAVIGATION_SLUG, NEW_FOLDER_PARENT, PACKAGE_FEATURES, PACKAGE_FEATURE_SLUGS, PAGE_EDITOR_SYSTEM_KEYS, PAGE_TITLE_CELL, REDIRECTS_SLUG, 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, SLUG_FIELD, SLUG_UNLOCK_WARNING, 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, adminNav, adminOnlyApiTab, allowedFeaturesForUser, applyRoleSlugsFromNames, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, composeSlug, createAdminNav, createBrandAssets, createFeatures, createMedia, createNavigation, createPages, createRedirects, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, developerOnlyAccess, documentCreateNew, documentTitle, documentTitleActions, featureCatalogue, featureGroupLabel, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getNavigation, getRolesMatrix, hasCapability, hasFeature, hasGrant, hasRole, headerCtaEnabled, hideUnlessDeveloper, hideUnlessFeature, inferredFolders, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isFeatureGroupId, isFeatureSlug, isLockedFeature, isPackageFeatureSlug, isPrivilegedRole, isRole, isRoleName, isRoleSlug, lockSlugOnPublish, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parentPrefix, parseFeaturesMatrix, parseRolesMatrix, persistThemeChild, publishThemeChild, recordPublishedSlugRedirect, redirectPath, requireLayoutOnPublish, resetRolesMatrix, resolveAdminNav, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeRoleNameInput, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugDepth, slugField, slugLeaf, slugifyRoleName, stampFeatureCatalogue, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateHeaderCta, validateRolesMatrix, writePublishedSlugRedirect };
2688
3017
 
2689
3018
  //# sourceMappingURL=index.mjs.map