@tetherto/dev-websites-core 0.3.5 → 0.3.7

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.
Files changed (65) hide show
  1. package/dist/cache/handler.d.mts.map +1 -1
  2. package/dist/cms/index.d.mts +2 -2
  3. package/dist/cms/index.mjs +2 -2
  4. package/dist/cms.mjs +545 -10
  5. package/dist/cms.mjs.map +1 -1
  6. package/dist/i18n/index.mjs +1 -2
  7. package/dist/index.d.mts +118 -3
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +2 -2
  10. package/dist/index5.d.mts +2 -2
  11. package/dist/ui/carousel/index.mjs +1 -2
  12. package/dist/ui/components/Accordion/index.mjs +1 -2
  13. package/dist/ui/components/Avatar/index.mjs +1 -2
  14. package/dist/ui/components/Badge/index.mjs +1 -2
  15. package/dist/ui/components/BadgeVersion/index.mjs +1 -2
  16. package/dist/ui/components/Button/index.mjs +1 -2
  17. package/dist/ui/components/Card/index.mjs +1 -2
  18. package/dist/ui/components/Carousel/index.mjs +1 -2
  19. package/dist/ui/components/Checkbox/index.mjs +1 -2
  20. package/dist/ui/components/CodeBlock/index.mjs +1 -2
  21. package/dist/ui/components/CodeBlockTabs/index.mjs +1 -2
  22. package/dist/ui/components/CollapsibleSection/index.mjs +1 -2
  23. package/dist/ui/components/CopyButton/index.mjs +1 -2
  24. package/dist/ui/components/CustomHtml/CustomHtml.d.mts +8 -0
  25. package/dist/ui/components/CustomHtml/CustomHtml.d.mts.map +1 -0
  26. package/dist/ui/components/CustomHtml/CustomHtml.mjs +31 -0
  27. package/dist/ui/components/CustomHtml/CustomHtml.mjs.map +1 -0
  28. package/dist/ui/components/CustomHtml/converters.d.mts +14 -0
  29. package/dist/ui/components/CustomHtml/converters.d.mts.map +1 -0
  30. package/dist/ui/components/CustomHtml/converters.mjs +9 -0
  31. package/dist/ui/components/CustomHtml/converters.mjs.map +1 -0
  32. package/dist/ui/components/CustomHtml/index.d.mts +3 -0
  33. package/dist/ui/components/CustomHtml/index.mjs +3 -0
  34. package/dist/ui/components/Dialog/index.mjs +1 -2
  35. package/dist/ui/components/EmptyState/index.mjs +1 -2
  36. package/dist/ui/components/FieldError/index.mjs +1 -2
  37. package/dist/ui/components/FormattedDate/index.mjs +1 -2
  38. package/dist/ui/components/GoogleOAuthButton/index.mjs +1 -2
  39. package/dist/ui/components/IconButton/index.mjs +1 -2
  40. package/dist/ui/components/Input/index.mjs +1 -2
  41. package/dist/ui/components/Label/index.mjs +1 -2
  42. package/dist/ui/components/LazyVideo/index.mjs +1 -2
  43. package/dist/ui/components/Pagination/index.mjs +1 -2
  44. package/dist/ui/components/PaginationItem/index.mjs +1 -2
  45. package/dist/ui/components/PasswordInput/index.mjs +1 -2
  46. package/dist/ui/components/ProgressStepper/index.mjs +1 -2
  47. package/dist/ui/components/Prose/index.mjs +1 -2
  48. package/dist/ui/components/Radio/index.mjs +1 -2
  49. package/dist/ui/components/Reveal/index.mjs +1 -2
  50. package/dist/ui/components/SearchInput/index.mjs +1 -2
  51. package/dist/ui/components/Select/index.mjs +1 -2
  52. package/dist/ui/components/Sheet/index.mjs +1 -2
  53. package/dist/ui/components/StaggerReveal/index.mjs +1 -2
  54. package/dist/ui/components/Stepper/index.mjs +1 -2
  55. package/dist/ui/components/Switch/index.mjs +1 -2
  56. package/dist/ui/components/TextButton/index.mjs +1 -2
  57. package/dist/ui/components/Textarea/index.mjs +1 -2
  58. package/dist/ui/components/Toast/index.mjs +1 -2
  59. package/dist/ui/components/Tooltip/index.mjs +1 -2
  60. package/dist/ui/components/VisuallyHidden/index.mjs +1 -2
  61. package/dist/ui/hooks/index.mjs +1 -2
  62. package/dist/ui/index.d.mts +4 -1
  63. package/dist/ui/index.mjs +1 -0
  64. package/dist/ui/utils/index.mjs +1 -2
  65. package/package.json +18 -10
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.mts","names":[],"sources":["../../src/cache/types.ts","../../src/cache/createMongoCacheHandler.ts","../../src/cache/handler.ts"],"mappings":";;;;;;KAgCY;EACV;EACA;EACA;EACA,UAAU;;EAEV,cAAc,0BAA0B;GACvC;;KAWS;EACV;EACA,OAAO;;;KAIG;EACV;;;;KCtBU;EACV,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,OAAO,oBAAoB,MAAM,kBAAkB;EACpE,OAAO,cAAc;EACrB;EACA,cAAc,yBAAyB,sBAAsB;;;;;;KAOnD,oCAAoC,kBAAkB"}
1
+ {"version":3,"file":"handler.d.mts","names":["_$__createMongoCacheHandler_js0.MongoCacheHandlerConstructor"],"sources":["../../src/cache/types.ts","../../src/cache/createMongoCacheHandler.ts","../../src/cache/handler.ts"],"mappings":";;;;;;KAgCY;EACV;EACA;EACA;EACA,UAAU;;EAEV,cAAc,0BAA0B;GACvC;;KAWS;EACV;EACA,OAAO;;;KAIG;EACV;;;;KCtBU;EACV,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,OAAO,oBAAoB,MAAM,kBAAkB;EACpE,OAAO,cAAc;EACrB;EACA,cAAc,yBAAyB,sBAAsB;;;;;;KAOnD,oCAAoC,kBAAkB"}
@@ -1,3 +1,3 @@
1
- import { $ as createAccessHelpers, A as downloadCtaBlock, B as seoFields, C as codeBlockLanguages, D as sectionHeadingBlock, E as leafBlocks, F as richTextBlock, G as STOCK_COLLECTION_SLUGS, H as BuildCmsConfigOptions, I as coerceBlogPostCategoriesOnRead, J as GlobalOverride, K as StockCollectionSlug, L as coerceBlogPostCategoriesOnWrite, M as imageBlock, N as codeTabsBlock, O as accordionBlock, P as codeBlock, Q as AccessOptions, R as setPublishedAt, S as showcaseContentBlocks, T as languageOptions, U as buildCmsConfig, V as draftsWithAutosave, W as Overrides, X as applyGlobalOverride, Y as applyCollectionOverride, Z as AccessHelpers, _ as requireEnvAtRuntime, a as createChangelog, b as blogContentBlocks, c as createPages, d as createAuthors, f as createMedia, g as createS3MediaPlugin, h as CreateS3MediaPluginOptions, i as createEventTags, j as videoBlock, k as stepsBlock, l as createBlogPosts, m as createUsers, n as createSiteSettings, o as createChangelogTags, p as createApiTokens, q as CollectionOverride, r as createEvents, s as createLegalPages, t as createAnnouncementBanner, u as createCategories, v as getRichTextEditorConfig, w as defaultCodeLanguage, x as pageContentBlocks, y as richTextEditor, z as slugify } from "../index.mjs";
1
+ import { $ as AccessOptions, A as downloadCtaBlock, B as slugify, C as codeBlockLanguages, D as sectionHeadingBlock, E as leafBlocks, F as codeBlock, G as Overrides, H as draftsWithAutosave, I as richTextBlock, J as CollectionOverride, K as STOCK_COLLECTION_SLUGS, L as coerceBlogPostCategoriesOnRead, M as videoBlock, N as imageBlock, O as accordionBlock, P as codeTabsBlock, Q as AccessHelpers, R as coerceBlogPostCategoriesOnWrite, S as showcaseContentBlocks, T as languageOptions, U as BuildCmsConfigOptions, V as seoFields, W as buildCmsConfig, X as applyCollectionOverride, Y as GlobalOverride, Z as applyGlobalOverride, _ as requireEnvAtRuntime, _t as withRoles, a as createChangelog, at as RoleOption, b as blogContentBlocks, c as createPages, ct as adminRoleAccess, d as createAuthors, dt as globalRoleRule, et as createAccessHelpers, f as createMedia, ft as hiddenUnless, g as createS3MediaPlugin, gt as userHasAnyRole, h as CreateS3MediaPluginOptions, ht as resolveRoleOptions, i as createEventTags, it as DEFAULT_ROLES, j as customHtmlBlock, k as stepsBlock, l as createBlogPosts, lt as collectionRoleRule, m as createUsers, mt as publishedOrRoleRead, n as createSiteSettings, nt as DEFAULT_COLLECTION_ROLES, o as createChangelogTags, ot as RoleRule, p as createApiTokens, pt as isCmsAdmin, q as StockCollectionSlug, r as createEvents, rt as DEFAULT_GLOBAL_ROLES, s as createLegalPages, st as RolesOptions, t as createAnnouncementBanner, tt as ADMIN_ROLE, u as createCategories, ut as effectiveRoles, v as getRichTextEditorConfig, vt as writeAccess, w as defaultCodeLanguage, x as pageContentBlocks, y as richTextEditor, z as setPublishedAt } from "../index.mjs";
2
2
  import { n as readPayloadMediaAsDataUrl, r as readPayloadMediaBuffer, t as PayloadMedia } from "../readMediaBuffer.mjs";
3
- export { type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, type GlobalOverride, type Overrides, type PayloadMedia, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
3
+ export { ADMIN_ROLE, type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, DEFAULT_COLLECTION_ROLES, DEFAULT_GLOBAL_ROLES, DEFAULT_ROLES, type GlobalOverride, type Overrides, type PayloadMedia, type RoleOption, type RoleRule, type RolesOptions, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, adminRoleAccess, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, collectionRoleRule, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, customHtmlBlock, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, effectiveRoles, getRichTextEditorConfig, globalRoleRule, hiddenUnless, imageBlock, isCmsAdmin, languageOptions, leafBlocks, pageContentBlocks, publishedOrRoleRead, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, resolveRoleOptions, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, userHasAnyRole, videoBlock, withRoles, writeAccess };
@@ -1,3 +1,3 @@
1
- import { A as richTextBlock, B as languageOptions, C as blogContentBlocks, D as accordionBlock, E as sectionHeadingBlock, F as downloadCtaBlock, G as createAccessHelpers, H as seoFields, I as codeTabsBlock, L as codeBlock, M as richTextEditor, N as videoBlock, O as leafBlocks, P as imageBlock, R as codeBlockLanguages, S as coerceBlogPostCategoriesOnWrite, T as showcaseContentBlocks, U as slugify, V as createAuthors, W as createApiTokens, _ as createCategories, a as STOCK_COLLECTION_SLUGS, b as setPublishedAt, c as createSiteSettings, d as createPages, f as createMedia, g as createChangelog, h as createChangelogTags, i as buildCmsConfig, j as getRichTextEditorConfig, k as stepsBlock, l as createUsers, m as createEventTags, n as createS3MediaPlugin, o as applyCollectionOverride, p as createEvents, r as requireEnvAtRuntime, s as applyGlobalOverride, t as createAnnouncementBanner, u as createLegalPages, v as createBlogPosts, w as pageContentBlocks, x as coerceBlogPostCategoriesOnRead, y as draftsWithAutosave, z as defaultCodeLanguage } from "../cms.mjs";
1
+ import { $ as globalRoleRule, A as richTextBlock, B as defaultCodeLanguage, C as blogContentBlocks, D as accordionBlock, E as sectionHeadingBlock, F as downloadCtaBlock, G as createApiTokens, H as createAuthors, I as customHtmlBlock, J as DEFAULT_GLOBAL_ROLES, K as ADMIN_ROLE, L as codeTabsBlock, M as richTextEditor, N as videoBlock, O as leafBlocks, P as imageBlock, Q as effectiveRoles, R as codeBlock, S as coerceBlogPostCategoriesOnWrite, T as showcaseContentBlocks, U as seoFields, V as languageOptions, W as slugify, X as adminRoleAccess, Y as DEFAULT_ROLES, Z as collectionRoleRule, _ as createCategories, a as STOCK_COLLECTION_SLUGS, at as withRoles, b as setPublishedAt, c as createSiteSettings, d as createPages, et as hiddenUnless, f as createMedia, g as createChangelog, h as createChangelogTags, i as buildCmsConfig, it as userHasAnyRole, j as getRichTextEditorConfig, k as stepsBlock, l as createUsers, m as createEventTags, n as createS3MediaPlugin, nt as publishedOrRoleRead, o as applyCollectionOverride, ot as writeAccess, p as createEvents, q as DEFAULT_COLLECTION_ROLES, r as requireEnvAtRuntime, rt as resolveRoleOptions, s as applyGlobalOverride, st as createAccessHelpers, t as createAnnouncementBanner, tt as isCmsAdmin, u as createLegalPages, v as createBlogPosts, w as pageContentBlocks, x as coerceBlogPostCategoriesOnRead, y as draftsWithAutosave, z as codeBlockLanguages } from "../cms.mjs";
2
2
  import { n as readPayloadMediaBuffer, t as readPayloadMediaAsDataUrl } from "../readMediaBuffer.mjs";
3
- export { STOCK_COLLECTION_SLUGS, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
3
+ export { ADMIN_ROLE, DEFAULT_COLLECTION_ROLES, DEFAULT_GLOBAL_ROLES, DEFAULT_ROLES, STOCK_COLLECTION_SLUGS, accordionBlock, adminRoleAccess, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, collectionRoleRule, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, customHtmlBlock, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, effectiveRoles, getRichTextEditorConfig, globalRoleRule, hiddenUnless, imageBlock, isCmsAdmin, languageOptions, leafBlocks, pageContentBlocks, publishedOrRoleRead, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, resolveRoleOptions, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, userHasAnyRole, videoBlock, withRoles, writeAccess };
package/dist/cms.mjs CHANGED
@@ -30,6 +30,370 @@ function createAccessHelpers(options = {}) {
30
30
  };
31
31
  }
32
32
  //#endregion
33
+ //#region src/cms/shared/roles.ts
34
+ const ADMIN_ROLE = "admin";
35
+ const CMS_ROLES_SLUG = "cms-roles";
36
+ const moderator = ["moderator"];
37
+ const developer = ["developer"];
38
+ const moderatorAndDeveloper = ["moderator", "developer"];
39
+ const DEFAULT_ROLES = [
40
+ {
41
+ label: "Admin",
42
+ value: ADMIN_ROLE
43
+ },
44
+ {
45
+ label: "Moderator",
46
+ value: "moderator"
47
+ },
48
+ {
49
+ label: "Developer",
50
+ value: "developer"
51
+ }
52
+ ];
53
+ const DEFAULT_COLLECTION_ROLES = {
54
+ "api-tokens": false,
55
+ "media": { roles: moderatorAndDeveloper },
56
+ "authors": { roles: moderator },
57
+ "categories": { roles: moderator },
58
+ "blog-posts": {
59
+ roles: moderator,
60
+ drafts: true
61
+ },
62
+ "pages": {
63
+ roles: moderator,
64
+ drafts: true
65
+ },
66
+ "legal-pages": {
67
+ roles: moderator,
68
+ drafts: true
69
+ },
70
+ "changelog-tags": { roles: developer },
71
+ "changelog": {
72
+ roles: developer,
73
+ drafts: true
74
+ },
75
+ "event-tags": { roles: moderator },
76
+ "events": {
77
+ roles: moderator,
78
+ drafts: true
79
+ }
80
+ };
81
+ const DEFAULT_GLOBAL_ROLES = {
82
+ "site-settings": false,
83
+ "announcement-banner": { roles: moderator }
84
+ };
85
+ let roleCache = null;
86
+ function asCmsUser(user) {
87
+ if (!user || typeof user !== "object") return null;
88
+ const record = user;
89
+ if (record.collection !== "users") return null;
90
+ return record;
91
+ }
92
+ function roleRefs(roles) {
93
+ if (!Array.isArray(roles)) return {
94
+ ids: [],
95
+ slugs: []
96
+ };
97
+ const ids = [];
98
+ const slugs = [];
99
+ roles.forEach((role) => {
100
+ if (typeof role === "string" || typeof role === "number") {
101
+ const value = String(role);
102
+ if (/^[a-f0-9]{24}$/i.test(value)) ids.push(value);
103
+ else if (value) slugs.push(value);
104
+ return;
105
+ }
106
+ if (role && typeof role === "object" && "id" in role && role.id != null) ids.push(String(role.id));
107
+ });
108
+ return {
109
+ ids,
110
+ slugs
111
+ };
112
+ }
113
+ function readPermissions(value) {
114
+ if (!Array.isArray(value)) return [];
115
+ return value.flatMap((entry) => {
116
+ if (!entry || typeof entry !== "object" || !("target" in entry)) return [];
117
+ const row = entry;
118
+ if (!row.target) return [];
119
+ return [{
120
+ target: row.target,
121
+ create: Boolean(row.create),
122
+ update: Boolean(row.update),
123
+ delete: Boolean(row.delete),
124
+ drafts: Boolean(row.drafts)
125
+ }];
126
+ });
127
+ }
128
+ async function refreshRoleCache(payload) {
129
+ const found = await payload.find({
130
+ collection: CMS_ROLES_SLUG,
131
+ depth: 0,
132
+ limit: 500,
133
+ pagination: false,
134
+ overrideAccess: true
135
+ });
136
+ const byId = /* @__PURE__ */ new Map();
137
+ const bySlug = /* @__PURE__ */ new Map();
138
+ found.docs.forEach((doc) => {
139
+ const record = doc;
140
+ if (!record.slug) return;
141
+ const role = {
142
+ id: String(record.id),
143
+ name: record.name ?? record.slug,
144
+ slug: record.slug,
145
+ fullAccess: Boolean(record.fullAccess) || record.slug === "admin",
146
+ permissions: readPermissions(record.permissions)
147
+ };
148
+ byId.set(role.id, role);
149
+ bySlug.set(role.slug, role);
150
+ });
151
+ roleCache = {
152
+ byId,
153
+ bySlug
154
+ };
155
+ }
156
+ async function ensureRoleCache(payload) {
157
+ if (!roleCache) await refreshRoleCache(payload);
158
+ return roleCache ?? {
159
+ byId: /* @__PURE__ */ new Map(),
160
+ bySlug: /* @__PURE__ */ new Map()
161
+ };
162
+ }
163
+ function lookupRoles(refs, cache) {
164
+ return [...refs.ids.map((id) => cache.byId.get(id)), ...refs.slugs.map((slug) => cache.bySlug.get(slug))].filter((role) => Boolean(role));
165
+ }
166
+ function syncAssignment(user) {
167
+ const record = asCmsUser(user);
168
+ if (!record) return "guest";
169
+ const refs = roleRefs(record.roles);
170
+ if (refs.ids.length === 0 && refs.slugs.length === 0) return "admin";
171
+ if (!roleCache) return "unknown";
172
+ return lookupRoles(refs, roleCache);
173
+ }
174
+ async function loadAssignment(req) {
175
+ const record = asCmsUser(req.user);
176
+ if (!record) return "guest";
177
+ const refs = roleRefs(record.roles);
178
+ if (refs.ids.length === 0 && refs.slugs.length === 0) return "admin";
179
+ return lookupRoles(refs, await ensureRoleCache(req.payload));
180
+ }
181
+ function hasPermission(roles, target, action) {
182
+ if (roles.some((role) => role.fullAccess)) return true;
183
+ return roles.some((role) => role.permissions.some((permission) => permission.target === target && permission[action]));
184
+ }
185
+ /**
186
+ * `null` means this user has no roles yet and is treated as admin.
187
+ * `true` / `false` is the cached decision. A cold cache returns `null` so the
188
+ * nav stays visible until startup finishes loading roles.
189
+ */
190
+ function cachedPermission(user, target, action) {
191
+ const assigned = syncAssignment(user);
192
+ if (assigned === "guest") return false;
193
+ if (assigned === "admin") return true;
194
+ if (assigned === "unknown") return null;
195
+ return hasPermission(assigned, target, action);
196
+ }
197
+ async function userIsFullAdmin(req) {
198
+ const assigned = await loadAssignment(req);
199
+ if (assigned === "guest") return false;
200
+ if (assigned === "admin") return true;
201
+ return assigned.some((role) => role.fullAccess);
202
+ }
203
+ async function userCan(req, target, action) {
204
+ const assigned = await loadAssignment(req);
205
+ if (assigned === "guest") return false;
206
+ if (assigned === "admin") return true;
207
+ return hasPermission(assigned, target, action);
208
+ }
209
+ /**
210
+ * Users created before the roles field existed have none stored.
211
+ * Treat that as admin so turning roles on does not lock the panel.
212
+ */
213
+ function effectiveRoles(user) {
214
+ const assigned = syncAssignment(user);
215
+ if (assigned === "admin") return [ADMIN_ROLE];
216
+ if (assigned === "guest" || assigned === "unknown") return [];
217
+ return assigned.map((role) => role.slug);
218
+ }
219
+ function userHasAnyRole(user, roles) {
220
+ const assigned = effectiveRoles(user);
221
+ if (assigned.includes("admin")) return true;
222
+ return roles.some((role) => assigned.includes(role));
223
+ }
224
+ function isCmsAdmin(user) {
225
+ const assigned = syncAssignment(user);
226
+ if (assigned === "admin") return true;
227
+ if (assigned === "guest" || assigned === "unknown") return false;
228
+ return assigned.some((role) => role.fullAccess);
229
+ }
230
+ const adminRoleAccess = ({ req }) => userIsFullAdmin(req);
231
+ function hiddenUnless(roles) {
232
+ return ({ user }) => !userHasAnyRole(user, roles);
233
+ }
234
+ function writeAccess(roles) {
235
+ return ({ req }) => userHasAnyRole(req.user, roles);
236
+ }
237
+ function publishedOrRoleRead(roles) {
238
+ return ({ req }) => {
239
+ if (userHasAnyRole(req.user, roles)) return true;
240
+ return { _status: { equals: "published" } };
241
+ };
242
+ }
243
+ const selfOrAdminAccess = async ({ req }) => {
244
+ if (await userIsFullAdmin(req)) return true;
245
+ const record = asCmsUser(req.user);
246
+ if (record?.id == null) return false;
247
+ return { id: { equals: record.id } };
248
+ };
249
+ const manageRolesAccess = ({ req }) => userIsFullAdmin(req);
250
+ function hiddenFor(target) {
251
+ return ({ user }) => {
252
+ const create = cachedPermission(user, target, "create");
253
+ const update = cachedPermission(user, target, "update");
254
+ const drafts = cachedPermission(user, target, "drafts");
255
+ if (create == null || update == null || drafts == null) return false;
256
+ return !(create || update || drafts);
257
+ };
258
+ }
259
+ function hiddenUnlessFullAdmin() {
260
+ return ({ user }) => {
261
+ const assigned = syncAssignment(user);
262
+ if (assigned === "unknown" || assigned === "admin") return false;
263
+ if (assigned === "guest") return true;
264
+ return !assigned.some((role) => role.fullAccess);
265
+ };
266
+ }
267
+ function resolveRoleOptions(options = {}) {
268
+ const custom = options.roles?.filter((role) => role.value !== "admin") ?? DEFAULT_ROLES.filter((role) => role.value !== "admin");
269
+ const merged = /* @__PURE__ */ new Map([[ADMIN_ROLE, {
270
+ label: "Admin",
271
+ value: ADMIN_ROLE
272
+ }]]);
273
+ [...custom, ...options.extraRoles ?? []].forEach((role) => {
274
+ if (!role.value || role.value === "admin") return;
275
+ merged.set(role.value, {
276
+ label: role.label,
277
+ value: role.value
278
+ });
279
+ });
280
+ return [...merged.values()];
281
+ }
282
+ function collectionRoleRule(options, slug, fallback) {
283
+ return roleRule(options, "collections", slug, fallback);
284
+ }
285
+ function globalRoleRule(options, slug, fallback) {
286
+ return roleRule(options, "globals", slug, fallback);
287
+ }
288
+ function roleRule(options, kind, slug, fallback) {
289
+ const overrides = (options === true || options == null ? {} : options)[kind];
290
+ if (overrides && Object.prototype.hasOwnProperty.call(overrides, slug)) return overrides[slug];
291
+ const defaults = kind === "collections" ? DEFAULT_COLLECTION_ROLES : DEFAULT_GLOBAL_ROLES;
292
+ if (Object.prototype.hasOwnProperty.call(defaults, slug)) return defaults[slug];
293
+ return fallback;
294
+ }
295
+ function rolesField() {
296
+ return {
297
+ name: "roles",
298
+ type: "relationship",
299
+ relationTo: CMS_ROLES_SLUG,
300
+ hasMany: true,
301
+ saveToJWT: true,
302
+ access: {
303
+ create: manageRolesAccess,
304
+ update: manageRolesAccess
305
+ },
306
+ admin: {
307
+ position: "sidebar",
308
+ description: "Only an admin can change roles. Several roles can be combined. Empty means Admin until a role is saved. The user must sign in again before a change applies."
309
+ }
310
+ };
311
+ }
312
+ function permissionFor(target, rule, role, collection) {
313
+ if (rule == null || rule === false || !rule.roles.includes(role)) return null;
314
+ return {
315
+ target,
316
+ create: collection,
317
+ update: true,
318
+ delete: false,
319
+ drafts: collection && rule.drafts === true
320
+ };
321
+ }
322
+ /** Targets shown in the Roles editor: stock defaults plus anything the app configures. */
323
+ function roleTargetSlugs(options = {}) {
324
+ const targets = /* @__PURE__ */ new Set([
325
+ ...Object.keys(DEFAULT_COLLECTION_ROLES),
326
+ ...Object.keys(DEFAULT_GLOBAL_ROLES),
327
+ ...Object.keys(options.collections ?? {}),
328
+ ...Object.keys(options.globals ?? {})
329
+ ]);
330
+ targets.delete("users");
331
+ targets.delete(CMS_ROLES_SLUG);
332
+ return [...targets].sort();
333
+ }
334
+ /**
335
+ * Default role documents. Existing documents with the same slug are left alone,
336
+ * so edits made in the admin survive a restart.
337
+ */
338
+ function buildRoleSeeds(options = {}) {
339
+ const roles = resolveRoleOptions(options);
340
+ const targets = roleTargetSlugs(options);
341
+ return roles.map((role) => {
342
+ if (role.value === "admin") return {
343
+ name: role.label,
344
+ slug: role.value,
345
+ fullAccess: true,
346
+ permissions: []
347
+ };
348
+ const permissions = targets.flatMap((target) => {
349
+ const collection = collectionRoleRule(options, target);
350
+ const global = globalRoleRule(options, target);
351
+ const row = permissionFor(target, collection !== void 0 ? collection : global, role.value, collection !== void 0);
352
+ return row ? [row] : [];
353
+ });
354
+ return {
355
+ name: role.label,
356
+ slug: role.value,
357
+ fullAccess: false,
358
+ permissions
359
+ };
360
+ });
361
+ }
362
+ function isCollectionConfig(config) {
363
+ if ("auth" in config || "upload" in config || "labels" in config || "defaultSort" in config) return true;
364
+ return Boolean(config.access && ("create" in config.access || "delete" in config.access));
365
+ }
366
+ /** Limits create, update, and delete from the Roles collection. Admin is always allowed. */
367
+ function withRoles(config) {
368
+ const collection = isCollectionConfig(config);
369
+ const users = collection && Boolean(config.auth);
370
+ const slug = config.slug;
371
+ const versioned = "versions" in config && Boolean(config.versions);
372
+ return {
373
+ ...config,
374
+ admin: {
375
+ ...config.admin,
376
+ ...users ? { hidden: hiddenUnlessFullAdmin() } : { hidden: hiddenFor(slug) }
377
+ },
378
+ access: {
379
+ ...config.access,
380
+ ...collection ? {
381
+ create: users ? adminRoleAccess : ({ req }) => userCan(req, slug, "create"),
382
+ delete: users ? adminRoleAccess : ({ req }) => userCan(req, slug, "delete")
383
+ } : {},
384
+ ...users ? {
385
+ read: selfOrAdminAccess,
386
+ update: selfOrAdminAccess
387
+ } : { update: ({ req }) => userCan(req, slug, "update") },
388
+ ...versioned && !users ? { read: async ({ req }) => {
389
+ if (await userCan(req, slug, "update") || await userCan(req, slug, "drafts")) return true;
390
+ return { _status: { equals: "published" } };
391
+ } } : {}
392
+ },
393
+ ...users && !(config.fields ?? []).some((field) => "name" in field && field.name === "roles") ? { fields: [...config.fields ?? [], rolesField()] } : {}
394
+ };
395
+ }
396
+ //#endregion
33
397
  //#region src/cms/collections/createApiTokens.ts
34
398
  const adminOrSelf = ({ req }) => {
35
399
  if (req.user?.collection === "users") return true;
@@ -96,6 +460,133 @@ function createApiTokens(access) {
96
460
  //#region src/cms/hooks/slugify.ts
97
461
  const slugify = ({ value }) => typeof value === "string" ? value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : value;
98
462
  //#endregion
463
+ //#region src/cms/collections/createCmsRoles.ts
464
+ const refresh = ({ req }) => refreshRoleCache(req.payload);
465
+ const refreshAfterDelete = ({ req }) => refreshRoleCache(req.payload);
466
+ const keepAdminRole = ({ data, originalDoc }) => {
467
+ if (originalDoc?.slug === "admin" || data?.slug === "admin") return {
468
+ ...data,
469
+ slug: ADMIN_ROLE,
470
+ fullAccess: true
471
+ };
472
+ return data;
473
+ };
474
+ const blockAdminDelete = async ({ id, req }) => {
475
+ if ((await req.payload.findByID({
476
+ collection: "cms-roles",
477
+ id,
478
+ depth: 0,
479
+ overrideAccess: true
480
+ })).slug === "admin") throw new Error("The Admin role cannot be deleted.");
481
+ };
482
+ const fullAdmin = ({ req }) => userIsFullAdmin(req);
483
+ function createCmsRoles(targets) {
484
+ return {
485
+ slug: CMS_ROLES_SLUG,
486
+ labels: {
487
+ singular: "Role",
488
+ plural: "Roles"
489
+ },
490
+ admin: {
491
+ useAsTitle: "name",
492
+ hidden: hiddenUnlessFullAdmin(),
493
+ description: "Who can edit each collection and global. Only an admin can change this."
494
+ },
495
+ access: {
496
+ read: fullAdmin,
497
+ create: fullAdmin,
498
+ update: fullAdmin,
499
+ delete: fullAdmin
500
+ },
501
+ hooks: {
502
+ beforeChange: [keepAdminRole],
503
+ beforeDelete: [blockAdminDelete],
504
+ afterChange: [refresh],
505
+ afterDelete: [refreshAfterDelete]
506
+ },
507
+ fields: [
508
+ {
509
+ name: "name",
510
+ type: "text",
511
+ required: true
512
+ },
513
+ {
514
+ name: "slug",
515
+ type: "text",
516
+ required: true,
517
+ unique: true,
518
+ index: true,
519
+ hooks: { beforeValidate: [slugify] },
520
+ admin: { description: "Stable id. Do not change it after users are assigned this role." }
521
+ },
522
+ {
523
+ name: "fullAccess",
524
+ type: "checkbox",
525
+ defaultValue: false,
526
+ admin: { description: "Can edit every collection, including Roles and Users." }
527
+ },
528
+ {
529
+ name: "permissions",
530
+ type: "array",
531
+ admin: {
532
+ condition: (data) => !data?.fullAccess,
533
+ description: "One row per collection or global. No row means no access."
534
+ },
535
+ fields: [
536
+ {
537
+ name: "target",
538
+ type: "select",
539
+ required: true,
540
+ options: targets.map((slug) => ({
541
+ label: slug,
542
+ value: slug
543
+ }))
544
+ },
545
+ {
546
+ name: "create",
547
+ type: "checkbox",
548
+ defaultValue: false
549
+ },
550
+ {
551
+ name: "update",
552
+ type: "checkbox",
553
+ defaultValue: false
554
+ },
555
+ {
556
+ name: "delete",
557
+ type: "checkbox",
558
+ defaultValue: false
559
+ },
560
+ {
561
+ name: "drafts",
562
+ type: "checkbox",
563
+ defaultValue: false,
564
+ admin: { description: "Can open unpublished documents. Update also allows this." }
565
+ }
566
+ ]
567
+ }
568
+ ]
569
+ };
570
+ }
571
+ /** Creates each default role once. Documents that already exist are left as edited. */
572
+ async function seedDefaultCmsRoles(payload, options = {}) {
573
+ const seeds = buildRoleSeeds(options);
574
+ await Promise.all(seeds.map(async (role) => {
575
+ if ((await payload.find({
576
+ collection: "cms-roles",
577
+ where: { slug: { equals: role.slug } },
578
+ limit: 1,
579
+ depth: 0,
580
+ overrideAccess: true
581
+ })).docs.length > 0) return;
582
+ await payload.create({
583
+ collection: CMS_ROLES_SLUG,
584
+ data: role,
585
+ overrideAccess: true
586
+ });
587
+ }));
588
+ }
589
+ //#endregion
99
590
  //#region src/cms/shared/seo-fields.ts
100
591
  const MAX_META_TITLE = 60;
101
592
  const MAX_META_DESCRIPTION = 160;
@@ -398,6 +889,25 @@ const codeTabsBlock = {
398
889
  }]
399
890
  };
400
891
  //#endregion
892
+ //#region src/cms/blocks/custom-html.ts
893
+ const customHtmlBlock = {
894
+ slug: "custom-html",
895
+ labels: {
896
+ singular: "Custom HTML",
897
+ plural: "Custom HTML"
898
+ },
899
+ fields: [{
900
+ name: "html",
901
+ type: "code",
902
+ required: true,
903
+ localized: true,
904
+ admin: {
905
+ language: "html",
906
+ description: "Raw HTML, CSS, and scripts. Rendered as-is. Inline scripts run in the browser, same as a WordPress Custom HTML block."
907
+ }
908
+ }]
909
+ };
910
+ //#endregion
401
911
  //#region src/cms/blocks/download-cta.ts
402
912
  const downloadCtaBlock = {
403
913
  slug: "download-cta",
@@ -552,17 +1062,21 @@ const richTextFeatures = ({ defaultFeatures }) => [
552
1062
  name: "caption",
553
1063
  type: "text"
554
1064
  }] } } }),
555
- BlocksFeature({ blocks: [CodeBlock({
556
- languages: codeBlockLanguages,
557
- defaultLanguage: defaultCodeLanguage
558
- }), videoBlock] }),
1065
+ BlocksFeature({ blocks: [
1066
+ CodeBlock({
1067
+ languages: codeBlockLanguages,
1068
+ defaultLanguage: defaultCodeLanguage
1069
+ }),
1070
+ videoBlock,
1071
+ customHtmlBlock
1072
+ ] }),
559
1073
  EXPERIMENTAL_TableFeature()
560
1074
  ];
561
1075
  /**
562
1076
  * Shared Lexical editor for every `richText` field.
563
1077
  *
564
- * Adds `CodeBlock`, inline `video` block, media upload captions, and
565
- * `EXPERIMENTAL_TableFeature` on top of Payload defaults.
1078
+ * Adds `CodeBlock`, inline `video` and `custom-html` blocks, media upload
1079
+ * captions, and `EXPERIMENTAL_TableFeature` on top of Payload defaults.
566
1080
  */
567
1081
  const richTextEditor = () => lexicalEditor({ features: richTextFeatures });
568
1082
  /** Resolve Lexical editor config for seed scripts / markdown conversion. */
@@ -630,6 +1144,7 @@ const leafBlocks = [
630
1144
  imageBlock,
631
1145
  videoBlock,
632
1146
  downloadCtaBlock,
1147
+ customHtmlBlock,
633
1148
  stepsBlock
634
1149
  ];
635
1150
  //#endregion
@@ -690,6 +1205,7 @@ const blogContentBlocks = [
690
1205
  imageBlock,
691
1206
  videoBlock,
692
1207
  downloadCtaBlock,
1208
+ customHtmlBlock,
693
1209
  stepsBlock,
694
1210
  accordionBlock
695
1211
  ];
@@ -1693,12 +2209,31 @@ function buildCmsConfig(options) {
1693
2209
  "event-tags": createEventTags(access),
1694
2210
  "events": createEvents(access)
1695
2211
  };
1696
- const collections = [...STOCK_COLLECTION_SLUGS.map((slug) => applyCollectionOverride(stock[slug], overrides[slug])).filter((c) => c !== null), ...options.extraCollections ?? []];
1697
- const siteSettings = applyGlobalOverride(createSiteSettings(access), overrides["site-settings"]);
2212
+ const rolesSetting = options.access?.roles;
2213
+ const rolesEnabled = rolesSetting != null;
2214
+ const rolesConfig = rolesSetting === true || rolesSetting == null ? {} : rolesSetting;
2215
+ if (rolesEnabled) Object.keys(stock).forEach((slug) => {
2216
+ stock[slug] = withRoles(stock[slug]);
2217
+ });
2218
+ const collections = [
2219
+ ...rolesEnabled ? [createCmsRoles(roleTargetSlugs(rolesConfig))] : [],
2220
+ ...STOCK_COLLECTION_SLUGS.map((slug) => applyCollectionOverride(stock[slug], overrides[slug])).filter((c) => c !== null),
2221
+ ...options.extraCollections ?? []
2222
+ ];
2223
+ const siteSettingsBase = createSiteSettings(access);
2224
+ const siteSettingsWithRoles = rolesEnabled ? withRoles(siteSettingsBase) : siteSettingsBase;
2225
+ const siteSettings = applyGlobalOverride(siteSettingsWithRoles, overrides["site-settings"]);
1698
2226
  const globals = [...siteSettings ? [siteSettings] : [], ...options.extraGlobals ?? []];
1699
- const { jobs: jobsOverride, ...payloadRest } = options.payload ?? {};
2227
+ const { jobs: jobsOverride, onInit: callerOnInit, ...payloadRest } = options.payload ?? {};
1700
2228
  return buildConfig({
1701
2229
  ...payloadRest,
2230
+ onInit: async (payload) => {
2231
+ if (rolesEnabled) {
2232
+ await seedDefaultCmsRoles(payload, rolesConfig);
2233
+ await refreshRoleCache(payload);
2234
+ }
2235
+ if (callerOnInit) await callerOnInit(payload);
2236
+ },
1702
2237
  jobs: {
1703
2238
  autoRun: [{ cron: "* * * * *" }],
1704
2239
  ...jobsOverride
@@ -1843,6 +2378,6 @@ function createAnnouncementBanner(access) {
1843
2378
  };
1844
2379
  }
1845
2380
  //#endregion
1846
- export { richTextBlock as A, languageOptions as B, blogContentBlocks as C, accordionBlock as D, sectionHeadingBlock as E, downloadCtaBlock as F, createAccessHelpers as G, seoFields as H, codeTabsBlock as I, codeBlock as L, richTextEditor as M, videoBlock as N, leafBlocks as O, imageBlock as P, codeBlockLanguages as R, coerceBlogPostCategoriesOnWrite as S, showcaseContentBlocks as T, slugify as U, createAuthors as V, createApiTokens as W, createCategories as _, STOCK_COLLECTION_SLUGS as a, setPublishedAt as b, createSiteSettings as c, createPages as d, createMedia as f, createChangelog as g, createChangelogTags as h, buildCmsConfig as i, getRichTextEditorConfig as j, stepsBlock as k, createUsers as l, createEventTags as m, createS3MediaPlugin as n, applyCollectionOverride as o, createEvents as p, requireEnvAtRuntime as r, applyGlobalOverride as s, createAnnouncementBanner as t, createLegalPages as u, createBlogPosts as v, pageContentBlocks as w, coerceBlogPostCategoriesOnRead as x, draftsWithAutosave as y, defaultCodeLanguage as z };
2381
+ export { globalRoleRule as $, richTextBlock as A, defaultCodeLanguage as B, blogContentBlocks as C, accordionBlock as D, sectionHeadingBlock as E, downloadCtaBlock as F, createApiTokens as G, createAuthors as H, customHtmlBlock as I, DEFAULT_GLOBAL_ROLES as J, ADMIN_ROLE as K, codeTabsBlock as L, richTextEditor as M, videoBlock as N, leafBlocks as O, imageBlock as P, effectiveRoles as Q, codeBlock as R, coerceBlogPostCategoriesOnWrite as S, showcaseContentBlocks as T, seoFields as U, languageOptions as V, slugify as W, adminRoleAccess as X, DEFAULT_ROLES as Y, collectionRoleRule as Z, createCategories as _, STOCK_COLLECTION_SLUGS as a, withRoles as at, setPublishedAt as b, createSiteSettings as c, createPages as d, hiddenUnless as et, createMedia as f, createChangelog as g, createChangelogTags as h, buildCmsConfig as i, userHasAnyRole as it, getRichTextEditorConfig as j, stepsBlock as k, createUsers as l, createEventTags as m, createS3MediaPlugin as n, publishedOrRoleRead as nt, applyCollectionOverride as o, writeAccess as ot, createEvents as p, DEFAULT_COLLECTION_ROLES as q, requireEnvAtRuntime as r, resolveRoleOptions as rt, applyGlobalOverride as s, createAccessHelpers as st, createAnnouncementBanner as t, isCmsAdmin as tt, createLegalPages as u, createBlogPosts as v, pageContentBlocks as w, coerceBlogPostCategoriesOnRead as x, draftsWithAutosave as y, codeBlockLanguages as z };
1847
2382
 
1848
2383
  //# sourceMappingURL=cms.mjs.map