@tetherto/dev-websites-core 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/cms/google-oauth.d.mts +20 -0
- package/dist/cms/google-oauth.d.mts.map +1 -0
- package/dist/cms/google-oauth.mjs +130 -0
- package/dist/cms/google-oauth.mjs.map +1 -0
- package/dist/cms/index.d.mts +2 -2
- package/dist/cms/index.mjs +2 -2
- package/dist/cms.mjs +436 -7
- package/dist/cms.mjs.map +1 -1
- package/dist/index.d.mts +24 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index5.d.mts +2 -2
- package/dist/seo/index.mjs +2 -1
- package/dist/seo.mjs +2 -13
- package/dist/seo.mjs.map +1 -1
- package/dist/site-url.mjs +16 -0
- package/dist/site-url.mjs.map +1 -0
- package/dist/ui/components/Badge/Badge.d.mts +1 -1
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.d.mts +5 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.d.mts.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.mjs +72 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.mjs.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.d.mts +6 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.d.mts.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.mjs +7 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.mjs.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/index.d.mts +2 -0
- package/dist/ui/components/GoogleOAuthButton/index.mjs +2 -0
- package/dist/ui/index.d.mts +3 -1
- package/dist/ui/index.mjs +1 -0
- package/package.json +18 -2
- package/src/ui/components/GoogleOAuthButton/GoogleOAuthButton.tsx +69 -0
- package/src/ui/components/GoogleOAuthButton/googleOAuthStartPath.ts +2 -0
- package/src/ui/components/GoogleOAuthButton/index.ts +1 -0
package/dist/cms.mjs
CHANGED
|
@@ -708,6 +708,31 @@ const showcaseContentBlocks = [
|
|
|
708
708
|
imageBlock
|
|
709
709
|
];
|
|
710
710
|
//#endregion
|
|
711
|
+
//#region src/cms/hooks/coerce-blog-post-categories.ts
|
|
712
|
+
function toCategoryIdList(value) {
|
|
713
|
+
if (value == null || value === "") return [];
|
|
714
|
+
return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
715
|
+
if (typeof item === "string" || typeof item === "number") return String(item);
|
|
716
|
+
if (item && typeof item === "object" && "id" in item && item.id != null) return String(item.id);
|
|
717
|
+
return null;
|
|
718
|
+
}).filter((id) => Boolean(id));
|
|
719
|
+
}
|
|
720
|
+
/** Posts saved before `category` became `hasMany` still store a single value. */
|
|
721
|
+
const coerceBlogPostCategoriesOnWrite = ({ data }) => {
|
|
722
|
+
if (!data || !("category" in data)) return data;
|
|
723
|
+
data.category = toCategoryIdList(data.category);
|
|
724
|
+
return data;
|
|
725
|
+
};
|
|
726
|
+
const coerceBlogPostCategoriesOnRead = ({ doc }) => {
|
|
727
|
+
if (!doc) return doc;
|
|
728
|
+
if (doc.category == null || doc.category === "") {
|
|
729
|
+
doc.category = [];
|
|
730
|
+
return doc;
|
|
731
|
+
}
|
|
732
|
+
if (!Array.isArray(doc.category)) doc.category = [doc.category];
|
|
733
|
+
return doc;
|
|
734
|
+
};
|
|
735
|
+
//#endregion
|
|
711
736
|
//#region src/cms/hooks/set-published-at.ts
|
|
712
737
|
const setPublishedAt = ({ data }) => {
|
|
713
738
|
if (data && !data.publishedAt && data._status === "published") data.publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -746,7 +771,10 @@ function createBlogPosts(access) {
|
|
|
746
771
|
delete: access.adminOnly
|
|
747
772
|
},
|
|
748
773
|
versions: draftsWithAutosave,
|
|
749
|
-
hooks: {
|
|
774
|
+
hooks: {
|
|
775
|
+
beforeChange: [setPublishedAt, coerceBlogPostCategoriesOnWrite],
|
|
776
|
+
afterRead: [coerceBlogPostCategoriesOnRead]
|
|
777
|
+
},
|
|
750
778
|
fields: [
|
|
751
779
|
{
|
|
752
780
|
name: "title",
|
|
@@ -798,6 +826,8 @@ function createBlogPosts(access) {
|
|
|
798
826
|
name: "category",
|
|
799
827
|
type: "relationship",
|
|
800
828
|
relationTo: "categories",
|
|
829
|
+
hasMany: true,
|
|
830
|
+
label: "Categories",
|
|
801
831
|
admin: { position: "sidebar" }
|
|
802
832
|
},
|
|
803
833
|
{
|
|
@@ -1048,6 +1078,300 @@ function createChangelogTags(access) {
|
|
|
1048
1078
|
};
|
|
1049
1079
|
}
|
|
1050
1080
|
//#endregion
|
|
1081
|
+
//#region src/cms/collections/createEventTags.ts
|
|
1082
|
+
/** Stock event-tags (no site revalidation — add via overrides.hooks). */
|
|
1083
|
+
function createEventTags(access) {
|
|
1084
|
+
return {
|
|
1085
|
+
slug: "event-tags",
|
|
1086
|
+
labels: {
|
|
1087
|
+
singular: "Event Tag",
|
|
1088
|
+
plural: "Event Tags"
|
|
1089
|
+
},
|
|
1090
|
+
defaultSort: "order",
|
|
1091
|
+
admin: {
|
|
1092
|
+
useAsTitle: "name",
|
|
1093
|
+
defaultColumns: [
|
|
1094
|
+
"name",
|
|
1095
|
+
"slug",
|
|
1096
|
+
"order"
|
|
1097
|
+
],
|
|
1098
|
+
description: "Reusable event categories (Meetup, Workshop, Hackathon, …). Editors can add types without code changes. Related from Events."
|
|
1099
|
+
},
|
|
1100
|
+
access: {
|
|
1101
|
+
read: access.publicRead,
|
|
1102
|
+
create: access.adminOnly,
|
|
1103
|
+
update: access.adminOnly,
|
|
1104
|
+
delete: access.adminOnly
|
|
1105
|
+
},
|
|
1106
|
+
fields: [
|
|
1107
|
+
{
|
|
1108
|
+
name: "name",
|
|
1109
|
+
type: "text",
|
|
1110
|
+
required: true,
|
|
1111
|
+
localized: true
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: "slug",
|
|
1115
|
+
type: "text",
|
|
1116
|
+
required: true,
|
|
1117
|
+
unique: true,
|
|
1118
|
+
admin: {
|
|
1119
|
+
position: "sidebar",
|
|
1120
|
+
description: "URL-friendly identifier used by filters and relationships."
|
|
1121
|
+
},
|
|
1122
|
+
hooks: { beforeValidate: [slugify] }
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
name: "order",
|
|
1126
|
+
type: "number",
|
|
1127
|
+
defaultValue: 0,
|
|
1128
|
+
admin: {
|
|
1129
|
+
position: "sidebar",
|
|
1130
|
+
description: "Display order in admin lists (ascending). Ties broken by name."
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "description",
|
|
1135
|
+
type: "textarea",
|
|
1136
|
+
localized: true,
|
|
1137
|
+
admin: { description: "Optional context shown to editors." }
|
|
1138
|
+
}
|
|
1139
|
+
]
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region src/cms/collections/createEvents.ts
|
|
1144
|
+
/**
|
|
1145
|
+
* Display abbreviations accepted in addition to IANA names (e.g. Europe/Paris).
|
|
1146
|
+
* Sites map these to IANA when they need a timezone database identifier.
|
|
1147
|
+
*/
|
|
1148
|
+
const TIMEZONE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
1149
|
+
"UTC",
|
|
1150
|
+
"GMT",
|
|
1151
|
+
"CET",
|
|
1152
|
+
"CEST",
|
|
1153
|
+
"WET",
|
|
1154
|
+
"WEST",
|
|
1155
|
+
"EET",
|
|
1156
|
+
"EEST",
|
|
1157
|
+
"BST",
|
|
1158
|
+
"EST",
|
|
1159
|
+
"EDT",
|
|
1160
|
+
"CST",
|
|
1161
|
+
"CDT",
|
|
1162
|
+
"MST",
|
|
1163
|
+
"MDT",
|
|
1164
|
+
"PST",
|
|
1165
|
+
"PDT"
|
|
1166
|
+
]);
|
|
1167
|
+
function parseDate(value) {
|
|
1168
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
1169
|
+
if (typeof value === "string" && value.trim()) {
|
|
1170
|
+
const date = new Date(value);
|
|
1171
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
1172
|
+
}
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
function isValidIanaTimeZone(timeZone) {
|
|
1176
|
+
try {
|
|
1177
|
+
Intl.DateTimeFormat("en-US", { timeZone }).format(/* @__PURE__ */ new Date());
|
|
1178
|
+
return true;
|
|
1179
|
+
} catch {
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const validateTimezone = (value) => {
|
|
1184
|
+
if (!value || !value.trim()) return "Timezone is required.";
|
|
1185
|
+
const timeZone = value.trim();
|
|
1186
|
+
if (TIMEZONE_ABBREVIATIONS.has(timeZone.toUpperCase())) return true;
|
|
1187
|
+
if (isValidIanaTimeZone(timeZone)) return true;
|
|
1188
|
+
return "Must be a timezone abbreviation (CET, UTC, …) or a valid IANA timezone (e.g. Europe/Paris).";
|
|
1189
|
+
};
|
|
1190
|
+
const validateExternalUrl = (value) => {
|
|
1191
|
+
if (!value || !value.trim()) return "External URL is required.";
|
|
1192
|
+
try {
|
|
1193
|
+
new URL(value);
|
|
1194
|
+
return true;
|
|
1195
|
+
} catch {
|
|
1196
|
+
return "Must be a valid URL (e.g. https://example.com/event).";
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
const validateEndsAt = (value, { siblingData }) => {
|
|
1200
|
+
const end = parseDate(value);
|
|
1201
|
+
if (!end) return "End date and time is required.";
|
|
1202
|
+
const start = parseDate(siblingData && typeof siblingData === "object" && "startsAt" in siblingData ? siblingData.startsAt : void 0);
|
|
1203
|
+
if (start && end.getTime() <= start.getTime()) return "End date and time must be after the start.";
|
|
1204
|
+
return true;
|
|
1205
|
+
};
|
|
1206
|
+
/**
|
|
1207
|
+
* Stock events collection (shared fields only).
|
|
1208
|
+
* SEO / site-specific fields — add via `overrides.fields`.
|
|
1209
|
+
* Revalidation — add via `overrides.hooks`.
|
|
1210
|
+
*/
|
|
1211
|
+
function createEvents(access) {
|
|
1212
|
+
return {
|
|
1213
|
+
slug: "events",
|
|
1214
|
+
labels: {
|
|
1215
|
+
singular: "Event",
|
|
1216
|
+
plural: "Events"
|
|
1217
|
+
},
|
|
1218
|
+
defaultSort: "startsAt",
|
|
1219
|
+
admin: {
|
|
1220
|
+
useAsTitle: "title",
|
|
1221
|
+
defaultColumns: [
|
|
1222
|
+
"title",
|
|
1223
|
+
"slug",
|
|
1224
|
+
"tag",
|
|
1225
|
+
"startsAt",
|
|
1226
|
+
"endsAt",
|
|
1227
|
+
"_status"
|
|
1228
|
+
],
|
|
1229
|
+
description: "Reusable events consumed by marketing / community surfaces across sites."
|
|
1230
|
+
},
|
|
1231
|
+
access: {
|
|
1232
|
+
read: access.adminOrPublished,
|
|
1233
|
+
create: access.adminOnly,
|
|
1234
|
+
update: access.adminOnly,
|
|
1235
|
+
delete: access.adminOnly
|
|
1236
|
+
},
|
|
1237
|
+
versions: draftsWithAutosave,
|
|
1238
|
+
hooks: { beforeChange: [setPublishedAt] },
|
|
1239
|
+
fields: [
|
|
1240
|
+
{
|
|
1241
|
+
name: "title",
|
|
1242
|
+
type: "text",
|
|
1243
|
+
required: true,
|
|
1244
|
+
localized: true
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
name: "slug",
|
|
1248
|
+
type: "text",
|
|
1249
|
+
required: true,
|
|
1250
|
+
unique: true,
|
|
1251
|
+
admin: {
|
|
1252
|
+
position: "sidebar",
|
|
1253
|
+
description: "URL-friendly identifier. Not localized — one slug per event."
|
|
1254
|
+
},
|
|
1255
|
+
hooks: { beforeValidate: [slugify] }
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
name: "summary",
|
|
1259
|
+
type: "textarea",
|
|
1260
|
+
required: true,
|
|
1261
|
+
localized: true,
|
|
1262
|
+
admin: { description: "Short description for listings, previews, and calendar bodies." }
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
name: "coverImage",
|
|
1266
|
+
type: "upload",
|
|
1267
|
+
relationTo: "media",
|
|
1268
|
+
admin: { description: "Optional cover/hero image for detail or promotional surfaces." }
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
name: "startsAt",
|
|
1272
|
+
type: "date",
|
|
1273
|
+
required: true,
|
|
1274
|
+
admin: {
|
|
1275
|
+
position: "sidebar",
|
|
1276
|
+
date: {
|
|
1277
|
+
pickerAppearance: "dayAndTime",
|
|
1278
|
+
displayFormat: "MMM d, yyyy h:mm a"
|
|
1279
|
+
},
|
|
1280
|
+
description: "Event start date and time. Sites derive upcoming vs past from this value."
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "endsAt",
|
|
1285
|
+
type: "date",
|
|
1286
|
+
required: true,
|
|
1287
|
+
validate: validateEndsAt,
|
|
1288
|
+
admin: {
|
|
1289
|
+
position: "sidebar",
|
|
1290
|
+
date: {
|
|
1291
|
+
pickerAppearance: "dayAndTime",
|
|
1292
|
+
displayFormat: "MMM d, yyyy h:mm a"
|
|
1293
|
+
},
|
|
1294
|
+
description: "Event end date and time. Must be after the start."
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
name: "timezone",
|
|
1299
|
+
type: "text",
|
|
1300
|
+
required: true,
|
|
1301
|
+
validate: validateTimezone,
|
|
1302
|
+
admin: {
|
|
1303
|
+
position: "sidebar",
|
|
1304
|
+
description: "Display timezone: abbreviation (CET, WET, UTC) or IANA name (Europe/Paris)."
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
name: "location",
|
|
1309
|
+
type: "text",
|
|
1310
|
+
required: true,
|
|
1311
|
+
localized: true,
|
|
1312
|
+
admin: { description: "Venue or platform (e.g. Discord, Online, Lisbon)." }
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: "tag",
|
|
1316
|
+
type: "relationship",
|
|
1317
|
+
relationTo: "event-tags",
|
|
1318
|
+
required: true,
|
|
1319
|
+
admin: {
|
|
1320
|
+
position: "sidebar",
|
|
1321
|
+
description: "Primary event category from Event Tags."
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "ctaKind",
|
|
1326
|
+
type: "select",
|
|
1327
|
+
required: true,
|
|
1328
|
+
defaultValue: "link",
|
|
1329
|
+
options: [{
|
|
1330
|
+
label: "External link",
|
|
1331
|
+
value: "link"
|
|
1332
|
+
}, {
|
|
1333
|
+
label: "Add to calendar",
|
|
1334
|
+
value: "calendar"
|
|
1335
|
+
}],
|
|
1336
|
+
admin: {
|
|
1337
|
+
position: "sidebar",
|
|
1338
|
+
description: "Primary listing action. Link opens the external URL; calendar lets the site offer an add-to-calendar flow."
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: "ctaLabel",
|
|
1343
|
+
type: "text",
|
|
1344
|
+
required: true,
|
|
1345
|
+
localized: true,
|
|
1346
|
+
admin: { description: "Label for the primary listing action (e.g. Register, Watch recording)." }
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
name: "ctaUrl",
|
|
1350
|
+
type: "text",
|
|
1351
|
+
required: true,
|
|
1352
|
+
validate: validateExternalUrl,
|
|
1353
|
+
admin: { description: "External event URL. Opened for link actions; included in calendar details for calendar actions." }
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
name: "content",
|
|
1357
|
+
type: "richText",
|
|
1358
|
+
localized: true,
|
|
1359
|
+
editor: richTextEditor(),
|
|
1360
|
+
admin: { description: "Optional long-form body for event detail pages." }
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
name: "publishedAt",
|
|
1364
|
+
type: "date",
|
|
1365
|
+
admin: {
|
|
1366
|
+
position: "sidebar",
|
|
1367
|
+
date: { pickerAppearance: "dayAndTime" },
|
|
1368
|
+
description: "Auto-filled on first publish if left empty."
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
]
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
//#endregion
|
|
1051
1375
|
//#region src/cms/collections/createMedia.ts
|
|
1052
1376
|
/** Stock media upload collection (always publicly readable once uploaded). */
|
|
1053
1377
|
function createMedia(access) {
|
|
@@ -1174,7 +1498,7 @@ function createUsers(access) {
|
|
|
1174
1498
|
return {
|
|
1175
1499
|
slug: "users",
|
|
1176
1500
|
admin: { useAsTitle: "email" },
|
|
1177
|
-
auth:
|
|
1501
|
+
auth: { verify: false },
|
|
1178
1502
|
access: {
|
|
1179
1503
|
read: access.adminOnly,
|
|
1180
1504
|
create: access.adminOnly,
|
|
@@ -1182,7 +1506,29 @@ function createUsers(access) {
|
|
|
1182
1506
|
delete: access.adminOnly,
|
|
1183
1507
|
admin: access.adminOnly
|
|
1184
1508
|
},
|
|
1185
|
-
fields: [
|
|
1509
|
+
fields: [
|
|
1510
|
+
{
|
|
1511
|
+
name: "googleId",
|
|
1512
|
+
type: "text",
|
|
1513
|
+
unique: true,
|
|
1514
|
+
index: true,
|
|
1515
|
+
admin: {
|
|
1516
|
+
position: "sidebar",
|
|
1517
|
+
readOnly: true,
|
|
1518
|
+
description: "Google OAuth user ID"
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
name: "name",
|
|
1523
|
+
type: "text",
|
|
1524
|
+
admin: { description: "Display name from Google" }
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
name: "picture",
|
|
1528
|
+
type: "text",
|
|
1529
|
+
admin: { description: "Profile picture URL from Google" }
|
|
1530
|
+
}
|
|
1531
|
+
]
|
|
1186
1532
|
};
|
|
1187
1533
|
}
|
|
1188
1534
|
//#endregion
|
|
@@ -1313,7 +1659,9 @@ const STOCK_COLLECTION_SLUGS = [
|
|
|
1313
1659
|
"pages",
|
|
1314
1660
|
"legal-pages",
|
|
1315
1661
|
"changelog-tags",
|
|
1316
|
-
"changelog"
|
|
1662
|
+
"changelog",
|
|
1663
|
+
"event-tags",
|
|
1664
|
+
"events"
|
|
1317
1665
|
];
|
|
1318
1666
|
//#endregion
|
|
1319
1667
|
//#region src/cms/buildCmsConfig.ts
|
|
@@ -1341,13 +1689,20 @@ function buildCmsConfig(options) {
|
|
|
1341
1689
|
"pages": createPages(access),
|
|
1342
1690
|
"legal-pages": createLegalPages(access),
|
|
1343
1691
|
"changelog-tags": createChangelogTags(access),
|
|
1344
|
-
"changelog": createChangelog(access)
|
|
1692
|
+
"changelog": createChangelog(access),
|
|
1693
|
+
"event-tags": createEventTags(access),
|
|
1694
|
+
"events": createEvents(access)
|
|
1345
1695
|
};
|
|
1346
1696
|
const collections = [...STOCK_COLLECTION_SLUGS.map((slug) => applyCollectionOverride(stock[slug], overrides[slug])).filter((c) => c !== null), ...options.extraCollections ?? []];
|
|
1347
1697
|
const siteSettings = applyGlobalOverride(createSiteSettings(access), overrides["site-settings"]);
|
|
1348
1698
|
const globals = [...siteSettings ? [siteSettings] : [], ...options.extraGlobals ?? []];
|
|
1699
|
+
const { jobs: jobsOverride, ...payloadRest } = options.payload ?? {};
|
|
1349
1700
|
return buildConfig({
|
|
1350
|
-
...
|
|
1701
|
+
...payloadRest,
|
|
1702
|
+
jobs: {
|
|
1703
|
+
autoRun: [{ cron: "* * * * *" }],
|
|
1704
|
+
...jobsOverride
|
|
1705
|
+
},
|
|
1351
1706
|
...defined({
|
|
1352
1707
|
sharp: options.sharp,
|
|
1353
1708
|
cors: options.cors,
|
|
@@ -1414,6 +1769,80 @@ function createS3MediaPlugin(options = {}) {
|
|
|
1414
1769
|
});
|
|
1415
1770
|
}
|
|
1416
1771
|
//#endregion
|
|
1417
|
-
|
|
1772
|
+
//#region src/cms/globals/createAnnouncementBanner.ts
|
|
1773
|
+
const validateBannerId = (value, { siblingData }) => {
|
|
1774
|
+
if (siblingData?.enabled && !String(value ?? "").trim()) return "Banner ID is required when the banner is enabled.";
|
|
1775
|
+
return true;
|
|
1776
|
+
};
|
|
1777
|
+
/** Announcement banner global (no site revalidation — add via applyGlobalOverride hooks). */
|
|
1778
|
+
function createAnnouncementBanner(access) {
|
|
1779
|
+
return {
|
|
1780
|
+
slug: "announcement-banner",
|
|
1781
|
+
label: "Announcement Banner",
|
|
1782
|
+
admin: { description: "Sticky site-wide banner shown above the main navigation. Bump Banner ID when replacing content so users who dismissed an older message in the current session see the new one." },
|
|
1783
|
+
access: {
|
|
1784
|
+
read: access.publicRead,
|
|
1785
|
+
update: access.adminOnly
|
|
1786
|
+
},
|
|
1787
|
+
fields: [
|
|
1788
|
+
{
|
|
1789
|
+
name: "enabled",
|
|
1790
|
+
type: "checkbox",
|
|
1791
|
+
defaultValue: false,
|
|
1792
|
+
admin: { description: "When off, the banner is hidden on the public site." }
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
name: "bannerId",
|
|
1796
|
+
type: "text",
|
|
1797
|
+
admin: { description: "Stable identifier for this banner version. Change it when publishing new copy so users who dismissed an older banner in this session see the update." },
|
|
1798
|
+
validate: validateBannerId
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
type: "row",
|
|
1802
|
+
fields: [{
|
|
1803
|
+
name: "prefix",
|
|
1804
|
+
type: "text",
|
|
1805
|
+
localized: true,
|
|
1806
|
+
maxLength: 8,
|
|
1807
|
+
admin: {
|
|
1808
|
+
width: "25%",
|
|
1809
|
+
description: "Accent label (max 8 characters), e.g. \"New:\""
|
|
1810
|
+
}
|
|
1811
|
+
}, {
|
|
1812
|
+
name: "description",
|
|
1813
|
+
type: "text",
|
|
1814
|
+
localized: true,
|
|
1815
|
+
maxLength: 1e3,
|
|
1816
|
+
admin: {
|
|
1817
|
+
width: "75%",
|
|
1818
|
+
description: "Banner message (max 1000 characters)."
|
|
1819
|
+
}
|
|
1820
|
+
}]
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
type: "row",
|
|
1824
|
+
fields: [{
|
|
1825
|
+
name: "ctaLabel",
|
|
1826
|
+
type: "text",
|
|
1827
|
+
localized: true,
|
|
1828
|
+
maxLength: 18,
|
|
1829
|
+
admin: {
|
|
1830
|
+
width: "50%",
|
|
1831
|
+
description: "Optional button label (max 18 characters)."
|
|
1832
|
+
}
|
|
1833
|
+
}, {
|
|
1834
|
+
name: "ctaHref",
|
|
1835
|
+
type: "text",
|
|
1836
|
+
admin: {
|
|
1837
|
+
width: "50%",
|
|
1838
|
+
description: "Optional button URL. Relative paths stay in-site."
|
|
1839
|
+
}
|
|
1840
|
+
}]
|
|
1841
|
+
}
|
|
1842
|
+
]
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
//#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 };
|
|
1418
1847
|
|
|
1419
1848
|
//# sourceMappingURL=cms.mjs.map
|