@tetherto/dev-websites-core 0.2.0 → 0.2.1
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/cms/index.d.mts +3 -0
- package/dist/cms/index.mjs +3 -0
- package/dist/cms-5bS91IRZ.mjs +1419 -0
- package/dist/cms-5bS91IRZ.mjs.map +1 -0
- package/dist/i18n/index.d.mts +2 -0
- package/dist/i18n/index.mjs +2 -0
- package/dist/i18n-Dy3VrbBw.mjs +10 -0
- package/dist/i18n-Dy3VrbBw.mjs.map +1 -0
- package/dist/index-9B8SfwdR.d.mts +214 -0
- package/dist/index-9B8SfwdR.d.mts.map +1 -0
- package/dist/index-D36IEwUg.d.mts +8 -0
- package/dist/index-D36IEwUg.d.mts.map +1 -0
- package/dist/index-Dyi7DDoO.d.mts +23 -0
- package/dist/index-Dyi7DDoO.d.mts.map +1 -0
- package/dist/index-tTs9h9BS.d.mts +63 -0
- package/dist/index-tTs9h9BS.d.mts.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +7 -0
- package/dist/logger-BPURnavq.mjs +44 -0
- package/dist/logger-BPURnavq.mjs.map +1 -0
- package/dist/readMediaBuffer-1C3QTpkO.d.mts +15 -0
- package/dist/readMediaBuffer-1C3QTpkO.d.mts.map +1 -0
- package/dist/readMediaBuffer-DWkTbYVn.mjs +62 -0
- package/dist/readMediaBuffer-DWkTbYVn.mjs.map +1 -0
- package/dist/seo/index.d.mts +2 -0
- package/dist/seo/index.mjs +2 -0
- package/dist/seo-e5XjgQ7l.mjs +124 -0
- package/dist/seo-e5XjgQ7l.mjs.map +1 -0
- package/dist/utils/index.d.mts +2 -0
- package/dist/utils/index.mjs +3 -0
- package/dist/utils-CVVQQltg.mjs +18 -0
- package/dist/utils-CVVQQltg.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1419 @@
|
|
|
1
|
+
import { t as createLogger } from "./logger-BPURnavq.mjs";
|
|
2
|
+
import { buildConfig } from "payload";
|
|
3
|
+
import { BlocksFeature, CodeBlock, EXPERIMENTAL_TableFeature, UploadFeature, editorConfigFactory, lexicalEditor } from "@payloadcms/richtext-lexical";
|
|
4
|
+
import { s3Storage } from "@payloadcms/storage-s3";
|
|
5
|
+
//#region src/cms/shared/access.ts
|
|
6
|
+
const isAdminUser = (req) => req.user?.collection === "users";
|
|
7
|
+
const isDisabledApiToken = (req) => req.user?.collection === "api-tokens" && req.user?.enabled === false;
|
|
8
|
+
/**
|
|
9
|
+
* Build access helpers. Pass `{ apiTokens: true }` when the `api-tokens`
|
|
10
|
+
* collection is registered (stock by default via `buildCmsConfig`).
|
|
11
|
+
*/
|
|
12
|
+
function createAccessHelpers(options = {}) {
|
|
13
|
+
const apiTokens = options.apiTokens === true;
|
|
14
|
+
const adminOnly = ({ req }) => isAdminUser(req);
|
|
15
|
+
const adminOrPublished = ({ req }) => {
|
|
16
|
+
if (isAdminUser(req)) return true;
|
|
17
|
+
if (apiTokens && isDisabledApiToken(req)) return false;
|
|
18
|
+
return { _status: { equals: "published" } };
|
|
19
|
+
};
|
|
20
|
+
const publicRead = ({ req }) => {
|
|
21
|
+
if (apiTokens && isDisabledApiToken(req)) return false;
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
const adminOnlyField = ({ req }) => isAdminUser(req);
|
|
25
|
+
return {
|
|
26
|
+
adminOnly,
|
|
27
|
+
adminOrPublished,
|
|
28
|
+
publicRead,
|
|
29
|
+
adminOnlyField
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/cms/collections/createApiTokens.ts
|
|
34
|
+
const adminOrSelf = ({ req }) => {
|
|
35
|
+
if (req.user?.collection === "users") return true;
|
|
36
|
+
if (req.user?.collection === "api-tokens") return { id: { equals: req.user.id } };
|
|
37
|
+
return false;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Stock API-key auth collection for server-to-server Payload REST/GraphQL clients.
|
|
41
|
+
* Disable per site with `overrides['api-tokens'] = false`.
|
|
42
|
+
*/
|
|
43
|
+
function createApiTokens(access) {
|
|
44
|
+
return {
|
|
45
|
+
slug: "api-tokens",
|
|
46
|
+
labels: {
|
|
47
|
+
singular: "API Token",
|
|
48
|
+
plural: "API Tokens"
|
|
49
|
+
},
|
|
50
|
+
admin: {
|
|
51
|
+
useAsTitle: "name",
|
|
52
|
+
defaultColumns: [
|
|
53
|
+
"name",
|
|
54
|
+
"description",
|
|
55
|
+
"enabled",
|
|
56
|
+
"createdAt"
|
|
57
|
+
],
|
|
58
|
+
description: "Server-to-server tokens for non-admin clients calling the Payload REST/GraphQL API. Each token authenticates as an api-tokens user and is restricted to published, public-readable content."
|
|
59
|
+
},
|
|
60
|
+
auth: {
|
|
61
|
+
useAPIKey: true,
|
|
62
|
+
disableLocalStrategy: true
|
|
63
|
+
},
|
|
64
|
+
access: {
|
|
65
|
+
read: adminOrSelf,
|
|
66
|
+
create: access.adminOnly,
|
|
67
|
+
update: access.adminOnly,
|
|
68
|
+
delete: access.adminOnly,
|
|
69
|
+
admin: access.adminOnly
|
|
70
|
+
},
|
|
71
|
+
fields: [
|
|
72
|
+
{
|
|
73
|
+
name: "name",
|
|
74
|
+
type: "text",
|
|
75
|
+
required: true,
|
|
76
|
+
admin: { description: "Human-readable label, e.g. \"mobile-app-prod\" or \"partner-foo\"." }
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "description",
|
|
80
|
+
type: "textarea",
|
|
81
|
+
admin: { description: "Who is this token for and what is it used for? Free-form notes." }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "enabled",
|
|
85
|
+
type: "checkbox",
|
|
86
|
+
defaultValue: true,
|
|
87
|
+
admin: {
|
|
88
|
+
position: "sidebar",
|
|
89
|
+
description: "Disable to soft-revoke. To hard-revoke, clear the API Key field or delete the document."
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/cms/hooks/slugify.ts
|
|
97
|
+
const slugify = ({ value }) => typeof value === "string" ? value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : value;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/cms/shared/seo-fields.ts
|
|
100
|
+
const MAX_META_TITLE = 60;
|
|
101
|
+
const MAX_META_DESCRIPTION = 160;
|
|
102
|
+
const validateMetaTitle = (value) => {
|
|
103
|
+
if (!value || value.trim().length === 0) return "Meta title is required.";
|
|
104
|
+
if (value.length > MAX_META_TITLE) return `Meta title must be ${MAX_META_TITLE} characters or fewer (currently ${value.length}).`;
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
const validateMetaDescription = (value) => {
|
|
108
|
+
if (!value || value.trim().length === 0) return "Meta description is required.";
|
|
109
|
+
if (value.length > MAX_META_DESCRIPTION) return `Meta description must be ${MAX_META_DESCRIPTION} characters or fewer (currently ${value.length}).`;
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
const validateUrl = (value) => {
|
|
113
|
+
if (!value) return true;
|
|
114
|
+
try {
|
|
115
|
+
new URL(value);
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
return "Must be a valid URL (e.g. https://example.com/page).";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const seoFields = {
|
|
122
|
+
name: "seo",
|
|
123
|
+
type: "group",
|
|
124
|
+
label: "SEO",
|
|
125
|
+
fields: [
|
|
126
|
+
{
|
|
127
|
+
name: "metaTitle",
|
|
128
|
+
type: "text",
|
|
129
|
+
required: true,
|
|
130
|
+
localized: true,
|
|
131
|
+
maxLength: MAX_META_TITLE,
|
|
132
|
+
validate: validateMetaTitle,
|
|
133
|
+
admin: { description: `Required. ${MAX_META_TITLE} characters max — shown in browser tabs and search results.` }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "metaDescription",
|
|
137
|
+
type: "textarea",
|
|
138
|
+
required: true,
|
|
139
|
+
localized: true,
|
|
140
|
+
validate: validateMetaDescription,
|
|
141
|
+
admin: { description: `Required. ${MAX_META_DESCRIPTION} characters max — shown in search result snippets.` }
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "ogImage",
|
|
145
|
+
type: "upload",
|
|
146
|
+
relationTo: "media",
|
|
147
|
+
admin: { description: "Used when the page is shared on social media (1200×630 recommended). Falls back to cover image if empty." }
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "canonicalUrl",
|
|
151
|
+
type: "text",
|
|
152
|
+
validate: validateUrl,
|
|
153
|
+
admin: { description: "Override for syndicated content. Leave empty to auto-derive from slug." }
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "noIndex",
|
|
157
|
+
type: "checkbox",
|
|
158
|
+
defaultValue: false,
|
|
159
|
+
admin: { description: "When checked, search engines will not index this page." }
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "noFollow",
|
|
163
|
+
type: "checkbox",
|
|
164
|
+
defaultValue: false,
|
|
165
|
+
admin: { description: "When checked, search engines will not follow links on this page (paired with noIndex if needed)." }
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
};
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/cms/collections/createAuthors.ts
|
|
171
|
+
function createAuthors(access) {
|
|
172
|
+
return {
|
|
173
|
+
slug: "authors",
|
|
174
|
+
admin: {
|
|
175
|
+
useAsTitle: "name",
|
|
176
|
+
defaultColumns: [
|
|
177
|
+
"name",
|
|
178
|
+
"slug",
|
|
179
|
+
"role"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
access: {
|
|
183
|
+
read: access.publicRead,
|
|
184
|
+
create: access.adminOnly,
|
|
185
|
+
update: access.adminOnly,
|
|
186
|
+
delete: access.adminOnly
|
|
187
|
+
},
|
|
188
|
+
fields: [
|
|
189
|
+
{
|
|
190
|
+
name: "name",
|
|
191
|
+
type: "text",
|
|
192
|
+
required: true,
|
|
193
|
+
localized: true
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "slug",
|
|
197
|
+
type: "text",
|
|
198
|
+
required: true,
|
|
199
|
+
unique: true,
|
|
200
|
+
admin: { position: "sidebar" },
|
|
201
|
+
hooks: { beforeValidate: [slugify] }
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "avatar",
|
|
205
|
+
type: "upload",
|
|
206
|
+
relationTo: "media"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "role",
|
|
210
|
+
type: "text",
|
|
211
|
+
localized: true,
|
|
212
|
+
admin: { description: "Job title or role (e.g. \"Lead Engineer\")" }
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "bio",
|
|
216
|
+
type: "textarea",
|
|
217
|
+
localized: true
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "social",
|
|
221
|
+
type: "group",
|
|
222
|
+
label: "Social Profiles",
|
|
223
|
+
admin: { position: "sidebar" },
|
|
224
|
+
fields: [
|
|
225
|
+
{
|
|
226
|
+
name: "website",
|
|
227
|
+
type: "text"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "twitter",
|
|
231
|
+
type: "text",
|
|
232
|
+
admin: { description: "@handle or full URL" }
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "linkedin",
|
|
236
|
+
type: "text"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "github",
|
|
240
|
+
type: "text"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
...seoFields,
|
|
246
|
+
fields: [...seoFields.fields, {
|
|
247
|
+
name: "jsonLd",
|
|
248
|
+
type: "group",
|
|
249
|
+
label: "Structured Data Overrides",
|
|
250
|
+
admin: { description: "Used in JSON-LD Person schema on author pages and article bylines." },
|
|
251
|
+
fields: [
|
|
252
|
+
{
|
|
253
|
+
name: "jobTitle",
|
|
254
|
+
type: "text",
|
|
255
|
+
admin: { description: "schema.org jobTitle (defaults to role if empty)" }
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "worksFor",
|
|
259
|
+
type: "text",
|
|
260
|
+
admin: { description: "Organization name for schema.org worksFor" }
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "sameAs",
|
|
264
|
+
type: "array",
|
|
265
|
+
admin: { description: "Extra canonical URLs for this person (Wikipedia, Crunchbase, etc.)" },
|
|
266
|
+
fields: [{
|
|
267
|
+
name: "url",
|
|
268
|
+
type: "text",
|
|
269
|
+
required: true
|
|
270
|
+
}]
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}]
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/cms/blocks/language-options.ts
|
|
280
|
+
/** Shared language options for CMS code blocks and Lexical CodeBlock feature. */
|
|
281
|
+
const languageOptions = [
|
|
282
|
+
{
|
|
283
|
+
label: "TypeScript",
|
|
284
|
+
value: "typescript"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
label: "JavaScript",
|
|
288
|
+
value: "javascript"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
label: "Python",
|
|
292
|
+
value: "python"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
label: "Bash / Shell",
|
|
296
|
+
value: "bash"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
label: "Shell",
|
|
300
|
+
value: "shell"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
label: "JSON",
|
|
304
|
+
value: "json"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
label: "CSS",
|
|
308
|
+
value: "css"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
label: "HTML",
|
|
312
|
+
value: "html"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
label: "Rust",
|
|
316
|
+
value: "rust"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
label: "Go",
|
|
320
|
+
value: "go"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
label: "YAML",
|
|
324
|
+
value: "yaml"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
label: "Markdown",
|
|
328
|
+
value: "markdown"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
label: "Plain Text",
|
|
332
|
+
value: "plaintext"
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
const codeBlockLanguages = Object.fromEntries(languageOptions.map((opt) => [opt.value, opt.label]));
|
|
336
|
+
const defaultCodeLanguage = "plaintext";
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/cms/blocks/code.ts
|
|
339
|
+
const codeBlock = {
|
|
340
|
+
slug: "code",
|
|
341
|
+
labels: {
|
|
342
|
+
singular: "Code Block",
|
|
343
|
+
plural: "Code Blocks"
|
|
344
|
+
},
|
|
345
|
+
fields: [
|
|
346
|
+
{
|
|
347
|
+
name: "language",
|
|
348
|
+
type: "select",
|
|
349
|
+
defaultValue: "typescript",
|
|
350
|
+
options: languageOptions
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "filename",
|
|
354
|
+
type: "text",
|
|
355
|
+
admin: { description: "Optional filename label shown above the block" }
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "code",
|
|
359
|
+
type: "code",
|
|
360
|
+
required: true,
|
|
361
|
+
admin: { language: "typescript" }
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/cms/blocks/code-tabs.ts
|
|
367
|
+
const codeTabsBlock = {
|
|
368
|
+
slug: "code-tabs",
|
|
369
|
+
labels: {
|
|
370
|
+
singular: "Code Tabs",
|
|
371
|
+
plural: "Code Tabs"
|
|
372
|
+
},
|
|
373
|
+
fields: [{
|
|
374
|
+
name: "tabs",
|
|
375
|
+
type: "array",
|
|
376
|
+
required: true,
|
|
377
|
+
minRows: 1,
|
|
378
|
+
fields: [
|
|
379
|
+
{
|
|
380
|
+
name: "label",
|
|
381
|
+
type: "text",
|
|
382
|
+
required: true,
|
|
383
|
+
admin: { description: "Tab label (e.g. Windows, Linux, Android)" }
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: "language",
|
|
387
|
+
type: "select",
|
|
388
|
+
defaultValue: "bash",
|
|
389
|
+
options: languageOptions
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "code",
|
|
393
|
+
type: "code",
|
|
394
|
+
required: true,
|
|
395
|
+
admin: { language: "bash" }
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
}]
|
|
399
|
+
};
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/cms/blocks/download-cta.ts
|
|
402
|
+
const downloadCtaBlock = {
|
|
403
|
+
slug: "download-cta",
|
|
404
|
+
labels: {
|
|
405
|
+
singular: "Download CTA",
|
|
406
|
+
plural: "Download CTAs"
|
|
407
|
+
},
|
|
408
|
+
fields: [
|
|
409
|
+
{
|
|
410
|
+
name: "eyebrow",
|
|
411
|
+
type: "text",
|
|
412
|
+
localized: true
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "heading",
|
|
416
|
+
type: "text",
|
|
417
|
+
required: true,
|
|
418
|
+
localized: true
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "headingAccent",
|
|
422
|
+
type: "text",
|
|
423
|
+
localized: true
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "description",
|
|
427
|
+
type: "textarea",
|
|
428
|
+
localized: true
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: "submitLabel",
|
|
432
|
+
type: "text",
|
|
433
|
+
required: true,
|
|
434
|
+
localized: true
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "downloadUrl",
|
|
438
|
+
type: "text",
|
|
439
|
+
required: true
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "downloadLabel",
|
|
443
|
+
type: "text",
|
|
444
|
+
required: true,
|
|
445
|
+
localized: true
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "successMessage",
|
|
449
|
+
type: "textarea",
|
|
450
|
+
localized: true
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: "consent",
|
|
454
|
+
type: "textarea",
|
|
455
|
+
localized: true
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "footnote",
|
|
459
|
+
type: "textarea",
|
|
460
|
+
localized: true
|
|
461
|
+
}
|
|
462
|
+
]
|
|
463
|
+
};
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/cms/blocks/image.ts
|
|
466
|
+
const imageBlock = {
|
|
467
|
+
slug: "image",
|
|
468
|
+
labels: {
|
|
469
|
+
singular: "Image",
|
|
470
|
+
plural: "Images"
|
|
471
|
+
},
|
|
472
|
+
fields: [
|
|
473
|
+
{
|
|
474
|
+
name: "image",
|
|
475
|
+
type: "upload",
|
|
476
|
+
relationTo: "media",
|
|
477
|
+
required: true
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "caption",
|
|
481
|
+
type: "text",
|
|
482
|
+
localized: true
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "size",
|
|
486
|
+
type: "select",
|
|
487
|
+
defaultValue: "full",
|
|
488
|
+
options: [
|
|
489
|
+
{
|
|
490
|
+
label: "Full Width",
|
|
491
|
+
value: "full"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
label: "Wide",
|
|
495
|
+
value: "wide"
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
label: "Medium",
|
|
499
|
+
value: "medium"
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/cms/blocks/video.ts
|
|
507
|
+
const videoBlock = {
|
|
508
|
+
slug: "video",
|
|
509
|
+
labels: {
|
|
510
|
+
singular: "Video",
|
|
511
|
+
plural: "Videos"
|
|
512
|
+
},
|
|
513
|
+
fields: [
|
|
514
|
+
{
|
|
515
|
+
name: "video",
|
|
516
|
+
type: "upload",
|
|
517
|
+
relationTo: "media",
|
|
518
|
+
required: true,
|
|
519
|
+
filterOptions: { mimeType: { contains: "video" } }
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "caption",
|
|
523
|
+
type: "text",
|
|
524
|
+
localized: true
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: "size",
|
|
528
|
+
type: "select",
|
|
529
|
+
defaultValue: "full",
|
|
530
|
+
options: [
|
|
531
|
+
{
|
|
532
|
+
label: "Full Width",
|
|
533
|
+
value: "full"
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
label: "Wide",
|
|
537
|
+
value: "wide"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
label: "Medium",
|
|
541
|
+
value: "medium"
|
|
542
|
+
}
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
};
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/cms/lexical/editor.ts
|
|
549
|
+
const richTextFeatures = ({ defaultFeatures }) => [
|
|
550
|
+
...defaultFeatures.filter((feature) => feature.key !== "upload"),
|
|
551
|
+
UploadFeature({ collections: { media: { fields: [{
|
|
552
|
+
name: "caption",
|
|
553
|
+
type: "text"
|
|
554
|
+
}] } } }),
|
|
555
|
+
BlocksFeature({ blocks: [CodeBlock({
|
|
556
|
+
languages: codeBlockLanguages,
|
|
557
|
+
defaultLanguage: defaultCodeLanguage
|
|
558
|
+
}), videoBlock] }),
|
|
559
|
+
EXPERIMENTAL_TableFeature()
|
|
560
|
+
];
|
|
561
|
+
/**
|
|
562
|
+
* Shared Lexical editor for every `richText` field.
|
|
563
|
+
*
|
|
564
|
+
* Adds `CodeBlock`, inline `video` block, media upload captions, and
|
|
565
|
+
* `EXPERIMENTAL_TableFeature` on top of Payload defaults.
|
|
566
|
+
*/
|
|
567
|
+
const richTextEditor = () => lexicalEditor({ features: richTextFeatures });
|
|
568
|
+
/** Resolve Lexical editor config for seed scripts / markdown conversion. */
|
|
569
|
+
async function getRichTextEditorConfig(config) {
|
|
570
|
+
return editorConfigFactory.fromFeatures({
|
|
571
|
+
config,
|
|
572
|
+
features: richTextFeatures
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/cms/blocks/rich-text.ts
|
|
577
|
+
const richTextBlock = {
|
|
578
|
+
slug: "rich-text",
|
|
579
|
+
labels: {
|
|
580
|
+
singular: "Rich Text",
|
|
581
|
+
plural: "Rich Text Blocks"
|
|
582
|
+
},
|
|
583
|
+
fields: [{
|
|
584
|
+
name: "content",
|
|
585
|
+
type: "richText",
|
|
586
|
+
localized: true,
|
|
587
|
+
editor: richTextEditor()
|
|
588
|
+
}]
|
|
589
|
+
};
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/cms/blocks/steps.ts
|
|
592
|
+
const stepsBlock = {
|
|
593
|
+
slug: "steps",
|
|
594
|
+
labels: {
|
|
595
|
+
singular: "Steps List",
|
|
596
|
+
plural: "Steps Lists"
|
|
597
|
+
},
|
|
598
|
+
fields: [{
|
|
599
|
+
name: "items",
|
|
600
|
+
type: "array",
|
|
601
|
+
required: true,
|
|
602
|
+
minRows: 1,
|
|
603
|
+
fields: [
|
|
604
|
+
{
|
|
605
|
+
name: "title",
|
|
606
|
+
type: "text",
|
|
607
|
+
required: true,
|
|
608
|
+
localized: true
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: "description",
|
|
612
|
+
type: "textarea",
|
|
613
|
+
localized: true
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: "avatar",
|
|
617
|
+
type: "upload",
|
|
618
|
+
relationTo: "media",
|
|
619
|
+
admin: { description: "Optional avatar or icon shown next to the step" }
|
|
620
|
+
}
|
|
621
|
+
]
|
|
622
|
+
}]
|
|
623
|
+
};
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/cms/blocks/leaf-blocks.ts
|
|
626
|
+
const leafBlocks = [
|
|
627
|
+
richTextBlock,
|
|
628
|
+
codeBlock,
|
|
629
|
+
codeTabsBlock,
|
|
630
|
+
imageBlock,
|
|
631
|
+
videoBlock,
|
|
632
|
+
downloadCtaBlock,
|
|
633
|
+
stepsBlock
|
|
634
|
+
];
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/cms/blocks/accordion.ts
|
|
637
|
+
const accordionBlock = {
|
|
638
|
+
slug: "accordion",
|
|
639
|
+
labels: {
|
|
640
|
+
singular: "Accordion",
|
|
641
|
+
plural: "Accordions"
|
|
642
|
+
},
|
|
643
|
+
fields: [{
|
|
644
|
+
name: "items",
|
|
645
|
+
type: "array",
|
|
646
|
+
required: true,
|
|
647
|
+
minRows: 1,
|
|
648
|
+
fields: [
|
|
649
|
+
{
|
|
650
|
+
name: "title",
|
|
651
|
+
type: "text",
|
|
652
|
+
required: true,
|
|
653
|
+
localized: true
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: "defaultOpen",
|
|
657
|
+
type: "checkbox",
|
|
658
|
+
defaultValue: false
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: "content",
|
|
662
|
+
type: "blocks",
|
|
663
|
+
blocks: leafBlocks,
|
|
664
|
+
localized: true
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
}]
|
|
668
|
+
};
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/cms/blocks/section-heading.ts
|
|
671
|
+
const sectionHeadingBlock = {
|
|
672
|
+
slug: "section-heading",
|
|
673
|
+
labels: {
|
|
674
|
+
singular: "Section Heading",
|
|
675
|
+
plural: "Section Headings"
|
|
676
|
+
},
|
|
677
|
+
fields: [{
|
|
678
|
+
name: "text",
|
|
679
|
+
type: "text",
|
|
680
|
+
required: true,
|
|
681
|
+
localized: true
|
|
682
|
+
}]
|
|
683
|
+
};
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/cms/blocks/index.ts
|
|
686
|
+
const blogContentBlocks = [
|
|
687
|
+
richTextBlock,
|
|
688
|
+
codeBlock,
|
|
689
|
+
codeTabsBlock,
|
|
690
|
+
imageBlock,
|
|
691
|
+
videoBlock,
|
|
692
|
+
downloadCtaBlock,
|
|
693
|
+
stepsBlock,
|
|
694
|
+
accordionBlock
|
|
695
|
+
];
|
|
696
|
+
const pageContentBlocks = [
|
|
697
|
+
richTextBlock,
|
|
698
|
+
codeBlock,
|
|
699
|
+
codeTabsBlock,
|
|
700
|
+
imageBlock,
|
|
701
|
+
stepsBlock,
|
|
702
|
+
accordionBlock
|
|
703
|
+
];
|
|
704
|
+
const showcaseContentBlocks = [
|
|
705
|
+
sectionHeadingBlock,
|
|
706
|
+
richTextBlock,
|
|
707
|
+
codeBlock,
|
|
708
|
+
imageBlock
|
|
709
|
+
];
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/cms/hooks/set-published-at.ts
|
|
712
|
+
const setPublishedAt = ({ data }) => {
|
|
713
|
+
if (data && !data.publishedAt && data._status === "published") data.publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
714
|
+
return data;
|
|
715
|
+
};
|
|
716
|
+
//#endregion
|
|
717
|
+
//#region src/cms/shared/drafts-with-autosave.ts
|
|
718
|
+
const draftsWithAutosave = {
|
|
719
|
+
maxPerDoc: 25,
|
|
720
|
+
drafts: {
|
|
721
|
+
autosave: { interval: 800 },
|
|
722
|
+
schedulePublish: true
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/cms/collections/createBlogPosts.ts
|
|
727
|
+
/** Stock blog-posts collection (no site revalidation — add via overrides.hooks). */
|
|
728
|
+
function createBlogPosts(access) {
|
|
729
|
+
return {
|
|
730
|
+
slug: "blog-posts",
|
|
731
|
+
defaultSort: "-publishedAt",
|
|
732
|
+
admin: {
|
|
733
|
+
useAsTitle: "title",
|
|
734
|
+
defaultColumns: [
|
|
735
|
+
"title",
|
|
736
|
+
"slug",
|
|
737
|
+
"author",
|
|
738
|
+
"publishedAt",
|
|
739
|
+
"_status"
|
|
740
|
+
]
|
|
741
|
+
},
|
|
742
|
+
access: {
|
|
743
|
+
read: access.adminOrPublished,
|
|
744
|
+
create: access.adminOnly,
|
|
745
|
+
update: access.adminOnly,
|
|
746
|
+
delete: access.adminOnly
|
|
747
|
+
},
|
|
748
|
+
versions: draftsWithAutosave,
|
|
749
|
+
hooks: { beforeChange: [setPublishedAt] },
|
|
750
|
+
fields: [
|
|
751
|
+
{
|
|
752
|
+
name: "title",
|
|
753
|
+
type: "text",
|
|
754
|
+
required: true,
|
|
755
|
+
localized: true
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "slug",
|
|
759
|
+
type: "text",
|
|
760
|
+
required: true,
|
|
761
|
+
unique: true,
|
|
762
|
+
admin: {
|
|
763
|
+
position: "sidebar",
|
|
764
|
+
description: "URL-friendly identifier. Not localized — one slug per post."
|
|
765
|
+
},
|
|
766
|
+
hooks: { beforeValidate: [slugify] }
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
name: "coverImage",
|
|
770
|
+
type: "upload",
|
|
771
|
+
relationTo: "media"
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
name: "imageAlt",
|
|
775
|
+
type: "text",
|
|
776
|
+
localized: true,
|
|
777
|
+
admin: { description: "Alt text used for the auto-generated social/OG image (`og:image:alt`). Should describe the post for screen readers." }
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: "excerpt",
|
|
781
|
+
type: "textarea",
|
|
782
|
+
localized: true,
|
|
783
|
+
admin: { description: "Short summary shown in listing cards and meta description fallback." }
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
name: "content",
|
|
787
|
+
type: "blocks",
|
|
788
|
+
localized: true,
|
|
789
|
+
blocks: blogContentBlocks
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: "author",
|
|
793
|
+
type: "relationship",
|
|
794
|
+
relationTo: "authors",
|
|
795
|
+
admin: { position: "sidebar" }
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
name: "category",
|
|
799
|
+
type: "relationship",
|
|
800
|
+
relationTo: "categories",
|
|
801
|
+
admin: { position: "sidebar" }
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: "readingTime",
|
|
805
|
+
type: "number",
|
|
806
|
+
min: 1,
|
|
807
|
+
admin: {
|
|
808
|
+
position: "sidebar",
|
|
809
|
+
description: "Estimated reading time in minutes."
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
name: "publishedAt",
|
|
814
|
+
type: "date",
|
|
815
|
+
admin: {
|
|
816
|
+
position: "sidebar",
|
|
817
|
+
date: { pickerAppearance: "dayAndTime" },
|
|
818
|
+
description: "Auto-filled on first publish if left empty."
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
name: "showLatestNews",
|
|
823
|
+
type: "checkbox",
|
|
824
|
+
defaultValue: true,
|
|
825
|
+
admin: {
|
|
826
|
+
position: "sidebar",
|
|
827
|
+
description: "Show a \"Latest News\" section at the bottom of this post."
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
seoFields
|
|
831
|
+
]
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/cms/collections/createCategories.ts
|
|
836
|
+
function createCategories(access) {
|
|
837
|
+
return {
|
|
838
|
+
slug: "categories",
|
|
839
|
+
admin: {
|
|
840
|
+
useAsTitle: "name",
|
|
841
|
+
defaultColumns: ["name", "slug"]
|
|
842
|
+
},
|
|
843
|
+
access: {
|
|
844
|
+
read: access.publicRead,
|
|
845
|
+
create: access.adminOnly,
|
|
846
|
+
update: access.adminOnly,
|
|
847
|
+
delete: access.adminOnly
|
|
848
|
+
},
|
|
849
|
+
fields: [
|
|
850
|
+
{
|
|
851
|
+
name: "name",
|
|
852
|
+
type: "text",
|
|
853
|
+
required: true,
|
|
854
|
+
localized: true
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
name: "slug",
|
|
858
|
+
type: "text",
|
|
859
|
+
required: true,
|
|
860
|
+
unique: true,
|
|
861
|
+
admin: { position: "sidebar" },
|
|
862
|
+
hooks: { beforeValidate: [slugify] }
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: "description",
|
|
866
|
+
type: "textarea",
|
|
867
|
+
localized: true
|
|
868
|
+
}
|
|
869
|
+
]
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/cms/collections/createChangelog.ts
|
|
874
|
+
/**
|
|
875
|
+
* Stock changelog collection (shared fields only).
|
|
876
|
+
* Site-specific body / GitHub / related-block fields — add via `overrides.fields`.
|
|
877
|
+
* Revalidation — add via `overrides.hooks`.
|
|
878
|
+
*/
|
|
879
|
+
function createChangelog(access) {
|
|
880
|
+
return {
|
|
881
|
+
slug: "changelog",
|
|
882
|
+
labels: {
|
|
883
|
+
singular: "Changelog",
|
|
884
|
+
plural: "Changelog"
|
|
885
|
+
},
|
|
886
|
+
defaultSort: "-releaseDate",
|
|
887
|
+
admin: {
|
|
888
|
+
useAsTitle: "title",
|
|
889
|
+
defaultColumns: [
|
|
890
|
+
"title",
|
|
891
|
+
"version",
|
|
892
|
+
"versionType",
|
|
893
|
+
"tags",
|
|
894
|
+
"releaseDate",
|
|
895
|
+
"_status"
|
|
896
|
+
]
|
|
897
|
+
},
|
|
898
|
+
access: {
|
|
899
|
+
read: access.adminOrPublished,
|
|
900
|
+
create: access.adminOnly,
|
|
901
|
+
update: access.adminOnly,
|
|
902
|
+
delete: access.adminOnly
|
|
903
|
+
},
|
|
904
|
+
versions: draftsWithAutosave,
|
|
905
|
+
fields: [
|
|
906
|
+
{
|
|
907
|
+
name: "title",
|
|
908
|
+
type: "text",
|
|
909
|
+
required: true,
|
|
910
|
+
localized: true,
|
|
911
|
+
admin: { description: "Release headline shown on the changelog detail page (e.g. \"Added Solana Support\")." }
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
name: "slug",
|
|
915
|
+
type: "text",
|
|
916
|
+
required: true,
|
|
917
|
+
unique: true,
|
|
918
|
+
admin: {
|
|
919
|
+
position: "sidebar",
|
|
920
|
+
description: "URL-friendly identifier. Not localized — one slug per entry."
|
|
921
|
+
},
|
|
922
|
+
hooks: { beforeValidate: [slugify] }
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: "version",
|
|
926
|
+
type: "text",
|
|
927
|
+
admin: {
|
|
928
|
+
position: "sidebar",
|
|
929
|
+
description: "Optional semantic version label shown in the badge, e.g. v1.1.0 or 2.3.0. Omit for non-versioned announcements."
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: "versionType",
|
|
934
|
+
type: "select",
|
|
935
|
+
options: [
|
|
936
|
+
{
|
|
937
|
+
label: "Major",
|
|
938
|
+
value: "major"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
label: "Minor",
|
|
942
|
+
value: "minor"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
label: "Patch",
|
|
946
|
+
value: "patch"
|
|
947
|
+
}
|
|
948
|
+
],
|
|
949
|
+
admin: {
|
|
950
|
+
position: "sidebar",
|
|
951
|
+
description: "Release line for badge styling (major / minor / patch). Editors set this explicitly."
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "releaseDate",
|
|
956
|
+
type: "date",
|
|
957
|
+
required: true,
|
|
958
|
+
admin: {
|
|
959
|
+
position: "sidebar",
|
|
960
|
+
date: {
|
|
961
|
+
pickerAppearance: "dayOnly",
|
|
962
|
+
displayFormat: "MMM d, yyyy"
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
name: "tags",
|
|
968
|
+
type: "relationship",
|
|
969
|
+
relationTo: "changelog-tags",
|
|
970
|
+
hasMany: true,
|
|
971
|
+
label: "Tags",
|
|
972
|
+
admin: {
|
|
973
|
+
position: "sidebar",
|
|
974
|
+
description: "Editor-managed tags from the Changelog Tags collection."
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
name: "heroImage",
|
|
979
|
+
type: "upload",
|
|
980
|
+
relationTo: "media",
|
|
981
|
+
admin: { description: "Large visual above the article body on the changelog detail page." }
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "summary",
|
|
985
|
+
type: "textarea",
|
|
986
|
+
localized: true,
|
|
987
|
+
admin: { description: "Short excerpt for listing cards and previews." }
|
|
988
|
+
}
|
|
989
|
+
]
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
//#endregion
|
|
993
|
+
//#region src/cms/collections/createChangelogTags.ts
|
|
994
|
+
/** Stock changelog-tags (no site revalidation — add via overrides.hooks). */
|
|
995
|
+
function createChangelogTags(access) {
|
|
996
|
+
return {
|
|
997
|
+
slug: "changelog-tags",
|
|
998
|
+
defaultSort: "order",
|
|
999
|
+
admin: {
|
|
1000
|
+
useAsTitle: "name",
|
|
1001
|
+
defaultColumns: [
|
|
1002
|
+
"name",
|
|
1003
|
+
"slug",
|
|
1004
|
+
"order"
|
|
1005
|
+
],
|
|
1006
|
+
description: "Tags shown on changelog entries."
|
|
1007
|
+
},
|
|
1008
|
+
access: {
|
|
1009
|
+
read: access.publicRead,
|
|
1010
|
+
create: access.adminOnly,
|
|
1011
|
+
update: access.adminOnly,
|
|
1012
|
+
delete: access.adminOnly
|
|
1013
|
+
},
|
|
1014
|
+
fields: [
|
|
1015
|
+
{
|
|
1016
|
+
name: "name",
|
|
1017
|
+
type: "text",
|
|
1018
|
+
required: true,
|
|
1019
|
+
localized: true
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
name: "slug",
|
|
1023
|
+
type: "text",
|
|
1024
|
+
required: true,
|
|
1025
|
+
unique: true,
|
|
1026
|
+
admin: {
|
|
1027
|
+
position: "sidebar",
|
|
1028
|
+
description: "URL-friendly identifier used by listing filters."
|
|
1029
|
+
},
|
|
1030
|
+
hooks: { beforeValidate: [slugify] }
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
name: "order",
|
|
1034
|
+
type: "number",
|
|
1035
|
+
defaultValue: 0,
|
|
1036
|
+
admin: {
|
|
1037
|
+
position: "sidebar",
|
|
1038
|
+
description: "Display order in admin lists (ascending). Ties broken by name."
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
name: "description",
|
|
1043
|
+
type: "textarea",
|
|
1044
|
+
localized: true,
|
|
1045
|
+
admin: { description: "Optional context shown to editors. Not rendered publicly yet." }
|
|
1046
|
+
}
|
|
1047
|
+
]
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region src/cms/collections/createMedia.ts
|
|
1052
|
+
/** Stock media upload collection (always publicly readable once uploaded). */
|
|
1053
|
+
function createMedia(access) {
|
|
1054
|
+
return {
|
|
1055
|
+
slug: "media",
|
|
1056
|
+
admin: {
|
|
1057
|
+
useAsTitle: "filename",
|
|
1058
|
+
defaultColumns: [
|
|
1059
|
+
"filename",
|
|
1060
|
+
"alt",
|
|
1061
|
+
"updatedAt"
|
|
1062
|
+
]
|
|
1063
|
+
},
|
|
1064
|
+
access: {
|
|
1065
|
+
read: access.publicRead,
|
|
1066
|
+
create: access.adminOnly,
|
|
1067
|
+
update: access.adminOnly,
|
|
1068
|
+
delete: access.adminOnly
|
|
1069
|
+
},
|
|
1070
|
+
upload: {
|
|
1071
|
+
imageSizes: [
|
|
1072
|
+
{
|
|
1073
|
+
name: "thumbnail",
|
|
1074
|
+
width: 400,
|
|
1075
|
+
height: 300,
|
|
1076
|
+
position: "centre"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "card",
|
|
1080
|
+
width: 768,
|
|
1081
|
+
height: 512,
|
|
1082
|
+
position: "centre"
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
name: "tablet",
|
|
1086
|
+
width: 1024,
|
|
1087
|
+
height: void 0,
|
|
1088
|
+
position: "centre"
|
|
1089
|
+
}
|
|
1090
|
+
],
|
|
1091
|
+
adminThumbnail: "thumbnail",
|
|
1092
|
+
mimeTypes: [
|
|
1093
|
+
"image/*",
|
|
1094
|
+
"video/*",
|
|
1095
|
+
"application/pdf"
|
|
1096
|
+
]
|
|
1097
|
+
},
|
|
1098
|
+
fields: [{
|
|
1099
|
+
name: "alt",
|
|
1100
|
+
type: "text",
|
|
1101
|
+
required: true,
|
|
1102
|
+
localized: true
|
|
1103
|
+
}]
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/cms/collections/createPages.ts
|
|
1108
|
+
function createContentPageCollection(access, slug) {
|
|
1109
|
+
return {
|
|
1110
|
+
slug,
|
|
1111
|
+
admin: {
|
|
1112
|
+
useAsTitle: "title",
|
|
1113
|
+
defaultColumns: [
|
|
1114
|
+
"title",
|
|
1115
|
+
"slug",
|
|
1116
|
+
"publishedAt",
|
|
1117
|
+
"_status"
|
|
1118
|
+
]
|
|
1119
|
+
},
|
|
1120
|
+
access: {
|
|
1121
|
+
read: access.adminOrPublished,
|
|
1122
|
+
create: access.adminOnly,
|
|
1123
|
+
update: access.adminOnly,
|
|
1124
|
+
delete: access.adminOnly
|
|
1125
|
+
},
|
|
1126
|
+
versions: draftsWithAutosave,
|
|
1127
|
+
hooks: { beforeChange: [setPublishedAt] },
|
|
1128
|
+
fields: [
|
|
1129
|
+
{
|
|
1130
|
+
name: "title",
|
|
1131
|
+
type: "text",
|
|
1132
|
+
required: true,
|
|
1133
|
+
localized: true
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
name: "slug",
|
|
1137
|
+
type: "text",
|
|
1138
|
+
required: true,
|
|
1139
|
+
unique: true,
|
|
1140
|
+
admin: {
|
|
1141
|
+
position: "sidebar",
|
|
1142
|
+
description: "URL-friendly identifier. Not localized — one slug per page."
|
|
1143
|
+
},
|
|
1144
|
+
hooks: { beforeValidate: [slugify] }
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "content",
|
|
1148
|
+
type: "blocks",
|
|
1149
|
+
localized: true,
|
|
1150
|
+
blocks: pageContentBlocks
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
name: "publishedAt",
|
|
1154
|
+
type: "date",
|
|
1155
|
+
admin: {
|
|
1156
|
+
position: "sidebar",
|
|
1157
|
+
date: { pickerAppearance: "dayAndTime" },
|
|
1158
|
+
description: "Auto-filled on first publish if left empty."
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
seoFields
|
|
1162
|
+
]
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function createPages(access) {
|
|
1166
|
+
return createContentPageCollection(access, "pages");
|
|
1167
|
+
}
|
|
1168
|
+
function createLegalPages(access) {
|
|
1169
|
+
return createContentPageCollection(access, "legal-pages");
|
|
1170
|
+
}
|
|
1171
|
+
//#endregion
|
|
1172
|
+
//#region src/cms/collections/createUsers.ts
|
|
1173
|
+
function createUsers(access) {
|
|
1174
|
+
return {
|
|
1175
|
+
slug: "users",
|
|
1176
|
+
admin: { useAsTitle: "email" },
|
|
1177
|
+
auth: true,
|
|
1178
|
+
access: {
|
|
1179
|
+
read: access.adminOnly,
|
|
1180
|
+
create: access.adminOnly,
|
|
1181
|
+
update: access.adminOnly,
|
|
1182
|
+
delete: access.adminOnly,
|
|
1183
|
+
admin: access.adminOnly
|
|
1184
|
+
},
|
|
1185
|
+
fields: []
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
//#endregion
|
|
1189
|
+
//#region src/cms/globals/createSiteSettings.ts
|
|
1190
|
+
function createSiteSettings(access) {
|
|
1191
|
+
return {
|
|
1192
|
+
slug: "site-settings",
|
|
1193
|
+
label: "Site Settings",
|
|
1194
|
+
access: {
|
|
1195
|
+
read: access.publicRead,
|
|
1196
|
+
update: access.adminOnly
|
|
1197
|
+
},
|
|
1198
|
+
fields: [
|
|
1199
|
+
{
|
|
1200
|
+
name: "name",
|
|
1201
|
+
type: "text",
|
|
1202
|
+
required: true,
|
|
1203
|
+
localized: true,
|
|
1204
|
+
admin: { description: "Public site name used in titles and structured data." }
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: "defaultDescription",
|
|
1208
|
+
type: "textarea",
|
|
1209
|
+
required: true,
|
|
1210
|
+
localized: true,
|
|
1211
|
+
admin: { description: "Fallback meta description when a page does not define its own." }
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "defaultOgImage",
|
|
1215
|
+
type: "upload",
|
|
1216
|
+
relationTo: "media",
|
|
1217
|
+
admin: { description: "Default Open Graph image (1200×630 recommended)." }
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
name: "organization",
|
|
1221
|
+
type: "group",
|
|
1222
|
+
label: "Organization",
|
|
1223
|
+
fields: [
|
|
1224
|
+
{
|
|
1225
|
+
name: "name",
|
|
1226
|
+
type: "text",
|
|
1227
|
+
required: true
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
name: "legalName",
|
|
1231
|
+
type: "text"
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
name: "description",
|
|
1235
|
+
type: "textarea",
|
|
1236
|
+
localized: true
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "logo",
|
|
1240
|
+
type: "upload",
|
|
1241
|
+
relationTo: "media"
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "foundingDate",
|
|
1245
|
+
type: "text",
|
|
1246
|
+
admin: { description: "Year or ISO date for schema.org structured data." }
|
|
1247
|
+
}
|
|
1248
|
+
]
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
name: "social",
|
|
1252
|
+
type: "group",
|
|
1253
|
+
label: "Social Profiles",
|
|
1254
|
+
fields: [
|
|
1255
|
+
{
|
|
1256
|
+
name: "twitter",
|
|
1257
|
+
type: "text"
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
name: "github",
|
|
1261
|
+
type: "text"
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
name: "linkedin",
|
|
1265
|
+
type: "text"
|
|
1266
|
+
}
|
|
1267
|
+
]
|
|
1268
|
+
}
|
|
1269
|
+
]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region src/cms/applyOverride.ts
|
|
1274
|
+
function mergeHooks(base, extra) {
|
|
1275
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(extra ?? {})]);
|
|
1276
|
+
const merged = {};
|
|
1277
|
+
keys.forEach((key) => {
|
|
1278
|
+
const combined = [...base?.[key] ?? [], ...extra?.[key] ?? []];
|
|
1279
|
+
if (combined.length) merged[key] = combined;
|
|
1280
|
+
});
|
|
1281
|
+
return merged;
|
|
1282
|
+
}
|
|
1283
|
+
function applyOverride(base, override) {
|
|
1284
|
+
if (override === false) return null;
|
|
1285
|
+
if (!override) return base;
|
|
1286
|
+
const next = {
|
|
1287
|
+
...base,
|
|
1288
|
+
...override.admin && { admin: {
|
|
1289
|
+
...base.admin,
|
|
1290
|
+
...override.admin
|
|
1291
|
+
} },
|
|
1292
|
+
...override.access && { access: {
|
|
1293
|
+
...base.access,
|
|
1294
|
+
...override.access
|
|
1295
|
+
} },
|
|
1296
|
+
...override.fields && { fields: override.fields(base.fields ?? []) },
|
|
1297
|
+
...override.hooks && { hooks: mergeHooks(base.hooks, override.hooks) }
|
|
1298
|
+
};
|
|
1299
|
+
return override.config ? override.config(next) : next;
|
|
1300
|
+
}
|
|
1301
|
+
const applyCollectionOverride = (base, override) => applyOverride(base, override);
|
|
1302
|
+
const applyGlobalOverride = (base, override) => applyOverride(base, override);
|
|
1303
|
+
//#endregion
|
|
1304
|
+
//#region src/cms/overrides.ts
|
|
1305
|
+
/** Slugs of the stock collections shipped by `buildCmsConfig`, in registration order. */
|
|
1306
|
+
const STOCK_COLLECTION_SLUGS = [
|
|
1307
|
+
"users",
|
|
1308
|
+
"api-tokens",
|
|
1309
|
+
"media",
|
|
1310
|
+
"authors",
|
|
1311
|
+
"categories",
|
|
1312
|
+
"blog-posts",
|
|
1313
|
+
"pages",
|
|
1314
|
+
"legal-pages",
|
|
1315
|
+
"changelog-tags",
|
|
1316
|
+
"changelog"
|
|
1317
|
+
];
|
|
1318
|
+
//#endregion
|
|
1319
|
+
//#region src/cms/buildCmsConfig.ts
|
|
1320
|
+
/** Drop keys whose value is `undefined` so they don't clobber Payload defaults. */
|
|
1321
|
+
function defined(obj) {
|
|
1322
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Build a Payload config from shared stock collections/globals plus site
|
|
1326
|
+
* overrides and extras. Types are still generated in the consuming app.
|
|
1327
|
+
*/
|
|
1328
|
+
function buildCmsConfig(options) {
|
|
1329
|
+
const overrides = options.overrides ?? {};
|
|
1330
|
+
const access = createAccessHelpers({
|
|
1331
|
+
...options.access,
|
|
1332
|
+
apiTokens: options.access?.apiTokens ?? overrides["api-tokens"] !== false
|
|
1333
|
+
});
|
|
1334
|
+
const stock = {
|
|
1335
|
+
"users": createUsers(access),
|
|
1336
|
+
"api-tokens": createApiTokens(access),
|
|
1337
|
+
"media": createMedia(access),
|
|
1338
|
+
"authors": createAuthors(access),
|
|
1339
|
+
"categories": createCategories(access),
|
|
1340
|
+
"blog-posts": createBlogPosts(access),
|
|
1341
|
+
"pages": createPages(access),
|
|
1342
|
+
"legal-pages": createLegalPages(access),
|
|
1343
|
+
"changelog-tags": createChangelogTags(access),
|
|
1344
|
+
"changelog": createChangelog(access)
|
|
1345
|
+
};
|
|
1346
|
+
const collections = [...STOCK_COLLECTION_SLUGS.map((slug) => applyCollectionOverride(stock[slug], overrides[slug])).filter((c) => c !== null), ...options.extraCollections ?? []];
|
|
1347
|
+
const siteSettings = applyGlobalOverride(createSiteSettings(access), overrides["site-settings"]);
|
|
1348
|
+
const globals = [...siteSettings ? [siteSettings] : [], ...options.extraGlobals ?? []];
|
|
1349
|
+
return buildConfig({
|
|
1350
|
+
...options.payload,
|
|
1351
|
+
...defined({
|
|
1352
|
+
sharp: options.sharp,
|
|
1353
|
+
cors: options.cors,
|
|
1354
|
+
csrf: options.csrf,
|
|
1355
|
+
plugins: options.plugins
|
|
1356
|
+
}),
|
|
1357
|
+
secret: options.secret,
|
|
1358
|
+
db: options.db,
|
|
1359
|
+
admin: {
|
|
1360
|
+
user: "users",
|
|
1361
|
+
...options.admin
|
|
1362
|
+
},
|
|
1363
|
+
editor: options.editor ?? richTextEditor(),
|
|
1364
|
+
collections,
|
|
1365
|
+
...globals.length ? { globals } : {},
|
|
1366
|
+
localization: {
|
|
1367
|
+
locales: options.locales.map((code) => ({
|
|
1368
|
+
code,
|
|
1369
|
+
label: code.toUpperCase()
|
|
1370
|
+
})),
|
|
1371
|
+
defaultLocale: options.defaultLocale,
|
|
1372
|
+
fallback: true
|
|
1373
|
+
},
|
|
1374
|
+
typescript: options.typescript ?? { outputFile: "src/payload-types.ts" }
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
//#endregion
|
|
1378
|
+
//#region src/cms/requireEnv.ts
|
|
1379
|
+
const log = createLogger("env");
|
|
1380
|
+
/**
|
|
1381
|
+
* Read a required env var. During Next.js `phase-production-build` and
|
|
1382
|
+
* `generate:importmap` (when NEXT_PHASE is unset) returns "" instead of throwing,
|
|
1383
|
+
* because those phases do not connect to the DB.
|
|
1384
|
+
*/
|
|
1385
|
+
function requireEnvAtRuntime(name) {
|
|
1386
|
+
const value = process.env[name];
|
|
1387
|
+
const isBuildTime = !process.env.NEXT_PHASE || process.env.NEXT_PHASE === "phase-production-build";
|
|
1388
|
+
if (!value && !isBuildTime) {
|
|
1389
|
+
log.error(`Missing required environment variable: ${name}`);
|
|
1390
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
1391
|
+
}
|
|
1392
|
+
return value ?? "";
|
|
1393
|
+
}
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region src/cms/createS3MediaPlugin.ts
|
|
1396
|
+
/** Payload S3 storage plugin wired for the stock `media` collection. */
|
|
1397
|
+
function createS3MediaPlugin(options = {}) {
|
|
1398
|
+
const bucket = options.bucket ?? process.env.AWS_S3_BUCKET ?? "";
|
|
1399
|
+
const endpoint = options.endpoint ?? process.env.AWS_S3_ENDPOINT ?? "";
|
|
1400
|
+
const forcePathStyle = options.forcePathStyle ?? process.env.AWS_S3_FORCE_PATH_STYLE === "true";
|
|
1401
|
+
return s3Storage({
|
|
1402
|
+
enabled: Boolean(bucket),
|
|
1403
|
+
collections: { [options.collection ?? "media"]: true },
|
|
1404
|
+
bucket,
|
|
1405
|
+
config: {
|
|
1406
|
+
credentials: {
|
|
1407
|
+
accessKeyId: options.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
|
|
1408
|
+
secretAccessKey: options.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? ""
|
|
1409
|
+
},
|
|
1410
|
+
region: options.region ?? process.env.AWS_S3_REGION ?? "us-east-1",
|
|
1411
|
+
...endpoint ? { endpoint } : {},
|
|
1412
|
+
...forcePathStyle ? { forcePathStyle: true } : {}
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
//#endregion
|
|
1417
|
+
export { downloadCtaBlock as A, createAccessHelpers as B, leafBlocks as C, richTextEditor as D, getRichTextEditorConfig as E, languageOptions as F, createAuthors as I, seoFields as L, codeBlock as M, codeBlockLanguages as N, videoBlock as O, defaultCodeLanguage as P, slugify as R, accordionBlock as S, richTextBlock as T, setPublishedAt as _, applyCollectionOverride as a, showcaseContentBlocks as b, createUsers as c, createMedia as d, createChangelogTags as f, draftsWithAutosave as g, createBlogPosts as h, STOCK_COLLECTION_SLUGS as i, codeTabsBlock as j, imageBlock as k, createLegalPages as l, createCategories as m, requireEnvAtRuntime as n, applyGlobalOverride as o, createChangelog as p, buildCmsConfig as r, createSiteSettings as s, createS3MediaPlugin as t, createPages as u, blogContentBlocks as v, stepsBlock as w, sectionHeadingBlock as x, pageContentBlocks as y, createApiTokens as z };
|
|
1418
|
+
|
|
1419
|
+
//# sourceMappingURL=cms-5bS91IRZ.mjs.map
|