@riverbankcms/sdk 0.136.0 → 0.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/dist/{PreviewEditorSidebar-BU5ZlhQR.mjs → PreviewEditorSidebar-Bn6cONPa.mjs} +8 -8
- package/dist/{PreviewEditorUI-9XkHL-bC.mjs → PreviewEditorUI-LAwXZPSx.mjs} +3 -3
- package/dist/{PreviewQueryProvider-wbEpXryy.mjs → PreviewQueryProvider-ztQUdsNo.mjs} +1 -1
- package/dist/{SdkPreviewModeRuntime-CnWmj-2B.mjs → SdkPreviewModeRuntime-BbbGsI_v.mjs} +596 -164
- package/dist/{SiteChromeCustomizeContext-B6d5zkog.mjs → SiteChromeCustomizeContext-BbkNSt5a.mjs} +2888 -1404
- package/dist/_dts/ai/src/contracts/proposals.d.ts +12 -298
- package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +21 -14
- package/dist/_dts/ai/src/designer/themePatch.d.ts +5 -10
- package/dist/_dts/api/src/accessAdmin.d.ts +1 -9
- package/dist/_dts/api/src/accountApi.d.ts +49 -2
- package/dist/_dts/api/src/contentRuntime.d.ts +1 -4
- package/dist/_dts/api/src/domainConnection.d.ts +41 -0
- package/dist/_dts/api/src/endpoints.d.ts +194 -181
- package/dist/_dts/api/src/index.d.ts +4 -3
- package/dist/_dts/api/src/media.d.ts +19 -2
- package/dist/_dts/api/src/sdkContracts.d.ts +2 -15
- package/dist/_dts/api/src/sdkMediaContracts.d.ts +40 -0
- package/dist/_dts/api/src/sdkMediaEndpointDefinitions.d.ts +61 -0
- package/dist/_dts/api/src/sdkThemeWire.d.ts +15 -4
- package/dist/_dts/api/src/siteInfrastructure.d.ts +17 -23
- package/dist/_dts/api/src/siteManagementEndpoints.d.ts +7 -21
- package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +27 -3
- package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +37 -17
- package/dist/_dts/api/src/types.d.ts +1 -1
- package/dist/_dts/blocks/src/PageRenderer.d.ts +2 -2
- package/dist/_dts/blocks/src/index.d.ts +1 -1
- package/dist/_dts/blocks/src/pageRenderAdmission.d.ts +9 -0
- package/dist/_dts/blocks/src/pageRendererBlockSupport.d.ts +75 -0
- package/dist/_dts/blocks/src/pageRendererBoundaries.d.ts +2 -0
- package/dist/_dts/blocks/src/server.d.ts +1 -1
- package/dist/_dts/blocks/src/system/blocks/event-registration.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +2 -1
- package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +3 -3
- package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.state.d.ts +34 -22
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ErrorStep.d.ts +3 -5
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorPresentation.d.ts +5 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +25 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationWizardContext.d.ts +4 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +7 -3
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +5 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useWaitlistClaimRestoration.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/capacity.d.ts +164 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/cta.d.ts +29 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/dates.d.ts +47 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/index.d.ts +14 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/people.d.ts +6 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/pricing.d.ts +31 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/OfferingTitle.d.ts +11 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/index.d.ts +3 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/listingLayout.d.ts +23 -0
- package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +57 -0
- package/dist/_dts/commerce/src/purchase/index.d.ts +1 -0
- package/dist/_dts/db/src/generated/supabase/database.types.d.ts +8 -40
- package/dist/_dts/db/src/index.d.ts +1 -0
- package/dist/_dts/db/src/utils/postgrestFilter.d.ts +2 -0
- package/dist/_dts/media-core/src/bulkDeletePlanning.d.ts +4 -0
- package/dist/_dts/media-core/src/fileDomain.d.ts +3 -0
- package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +1 -0
- package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +1 -0
- package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +3 -2
- package/dist/_dts/preview-next/src/client/preview/siteChromeBrandPreferences.d.ts +1 -1
- package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlHistoryState.d.ts +2 -0
- package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlSaveRequest.d.ts +51 -0
- package/dist/_dts/preview-next/src/client/preview/siteChromeSavePlan.d.ts +24 -2
- package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +23 -1
- package/dist/_dts/preview-next/src/client/preview/usePersistedHeaderPreferences.d.ts +8 -0
- package/dist/_dts/sdk/src/public-api/contracts.d.ts +2 -1
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
- package/dist/_dts/theme-core/src/gradients/types.d.ts +5 -0
- package/dist/_dts/theme-core/src/header/interactionPalette.d.ts +25 -2
- package/dist/_dts/theme-core/src/mock-themes/all.d.ts +3 -3
- package/dist/_dts/theme-core/src/schema.d.ts +12 -11
- package/dist/_dts/theme-core/src/site-styles/compilationPlan.d.ts +43 -3
- package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +10 -0
- package/dist/_dts/theme-core/src/site-styles/headerBrandPresentation.d.ts +63 -0
- package/dist/_dts/theme-core/src/site-styles/headerBytesComparison.d.ts +30 -0
- package/dist/_dts/theme-core/src/site-styles/headerInteractionPalette.d.ts +50 -1
- package/dist/_dts/theme-core/src/site-styles/headerLayoutCatalog.d.ts +48 -9
- package/dist/_dts/theme-core/src/site-styles/headerPreferences.d.ts +162 -0
- package/dist/_dts/theme-core/src/site-styles/headerPreferencesPersistence.d.ts +73 -0
- package/dist/_dts/theme-core/src/site-styles/headerRecompile.d.ts +118 -0
- package/dist/_dts/theme-core/src/site-styles/headerStyleCompiler.d.ts +10 -1
- package/dist/_dts/theme-core/src/site-styles/headerStyleDefaults.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/headerStyleRecipe.d.ts +7 -0
- package/dist/_dts/theme-core/src/site-styles/index.d.ts +5 -0
- package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/siteChromeBackgrounds.d.ts +20 -1
- package/dist/_dts/theme-core/src/site-styles/siteChromeNavPreferences.d.ts +43 -2
- package/dist/client/{BookingConsentAgreement-DCch21a6.mjs → BookingConsentAgreement--_fUCZe-.mjs} +2 -2
- package/dist/client/{DiscountCodeDisclosure-DIj1T59K.mjs → DiscountCodeDisclosure-BMipSoDJ.mjs} +1 -1
- package/dist/client/{EventCombined.client-BWVNlXTx.mjs → EventCombined.client-CwJOxn7A.mjs} +154 -178
- package/dist/client/{EventListing.client-DVFCDe3i.mjs → EventListing.client-eVwiylPW.mjs} +2 -2
- package/dist/client/{LeafletStaticMap-BY5mIHw9.mjs → LeafletStaticMap-IKenna5u.mjs} +1 -1
- package/dist/client/{accordion-CK29E8rG.mjs → accordion-MGAO3PuE.mjs} +1 -1
- package/dist/client/{bookingForm-DFvnet-N.mjs → bookingForm-CudlQPUu.mjs} +5 -5
- package/dist/client/bookings.mjs +418 -79
- package/dist/client/capacity-BW00PO7s.mjs +271 -0
- package/dist/client/{carousel-BVU5NQlb.mjs → carousel-BrbGJ2Jl.mjs} +1 -1
- package/dist/client/{cart-BNItcZVa.mjs → cart-Bq5eU0xZ.mjs} +3 -3
- package/dist/client/{checkout-Bu6iCdkP.mjs → checkout-BAFVRdrK.mjs} +3 -3
- package/dist/client/client.mjs +2919 -1918
- package/dist/client/{context-BImarOAq.mjs → context-DCkiHVdD.mjs} +8 -5
- package/dist/client/dates-2RPXHQy4.mjs +264 -0
- package/dist/client/{eventCalendar-DS2h-UXD.mjs → eventCalendar-B0O-hPF7.mjs} +10 -17
- package/dist/client/{eventCombined-Drt4Oq9p.mjs → eventCombined-DCkDaoDI.mjs} +3 -3
- package/dist/client/{eventDetails-Bl2vWgTX.mjs → eventDetails-D8GG2dnD.mjs} +5 -3
- package/dist/client/{eventListing-CS7PHY7v.mjs → eventListing-3ssV_bxz.mjs} +3 -3
- package/dist/client/{eventRegistration-D-j8eNy2.mjs → eventRegistration-bPQo3kVw.mjs} +562 -309
- package/dist/client/{form-RMpoLTyr.mjs → form-qMv8RDkk.mjs} +2 -2
- package/dist/client/{headerSection-BYE3Lb3k.mjs → headerSection-CH0Sc5hX.mjs} +2 -2
- package/dist/client/hooks.mjs +289 -32
- package/dist/client/{locationMap-BxSr7Kkh.mjs → locationMap-0_xlBnNI.mjs} +2 -2
- package/dist/client/{newsletterForm-DerbXwFC.mjs → newsletterForm-NUW7m9RI.mjs} +2 -2
- package/dist/client/{productDetail-4SZCLhAf.mjs → productDetail-FBEOM7jW.mjs} +3 -3
- package/dist/client/{productList-D7AP4GNq.mjs → productList-R0OSGhnn.mjs} +3 -3
- package/dist/client/{propsDecoder-pzBeCh14.mjs → propsDecoder-pYvx_Srl.mjs} +88 -380
- package/dist/client/rendering/client.mjs +2616 -1637
- package/dist/client/rendering/islands.mjs +17 -17
- package/dist/client/rendering.mjs +2710 -1702
- package/dist/client/{scrollToBlock-CduAKf8T.mjs → scrollToBlock-ByQv96M2.mjs} +1 -1
- package/dist/client/{shop-Cp5p_kGe.mjs → shop-RUG9DmRE.mjs} +6 -6
- package/dist/client/{shop-commerce-DicXYaYX.mjs → shop-commerce-B76Jz23B.mjs} +5 -5
- package/dist/client/{shop-commerce-store-CGWnpY4K.mjs → shop-commerce-store-QuQNdDsD.mjs} +1 -1
- package/dist/client/{shopShared-BDf9ljkm.mjs → shopShared-vXU1omwJ.mjs} +3 -3
- package/dist/client/{useEventFilters-DeeyGYuk.mjs → useEventFilters-ByuU6HLA.mjs} +33 -18
- package/dist/preview-next/before-render.mjs +79 -51
- package/dist/preview-next/client/runtime.mjs +4 -4
- package/dist/preview-next/keepAlive.mjs +79 -51
- package/dist/preview-next/middleware.mjs +79 -51
- package/dist/{previewAuthStore-DbqBol0V.mjs → previewAuthStore-L_cKW8Sb.mjs} +79 -51
- package/dist/{sdk-runtime-19MO27F0.mjs → sdk-runtime-sUMr5d70.mjs} +2 -2
- package/dist/{separator-DaPLtJ1f.mjs → separator-BIHkie5i.mjs} +1 -1
- package/dist/server/components.mjs +2077 -1274
- package/dist/server/config-validation.mjs +289 -32
- package/dist/server/config.mjs +289 -32
- package/dist/server/data.mjs +289 -32
- package/dist/server/index.mjs +80 -52
- package/dist/server/next.mjs +1822 -1111
- package/dist/server/page-converter.mjs +167 -31
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +1988 -1286
- package/dist/server/rendering.mjs +1943 -1182
- package/dist/server/routing.mjs +351 -86
- package/dist/server/server.mjs +293 -128
- package/dist/server/theme-bridge.mjs +52 -8
- package/dist/server/theme.mjs +27 -8
- package/dist/styles/index.css +15 -0
- package/package.json +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +0 -48
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/pricing.d.ts +0 -17
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/utils.d.ts +0 -100
- /package/dist/_dts/blocks/src/system/runtime/nodes/events/{shared → domain}/destination.d.ts +0 -0
|
@@ -7664,6 +7664,11 @@ const gradientConfigSchema = z$1.object({
|
|
|
7664
7664
|
position: z$1.string().optional(),
|
|
7665
7665
|
stops: z$1.array(gradientStopSchema).min(2)
|
|
7666
7666
|
});
|
|
7667
|
+
const gradientsSchema = z$1.object({
|
|
7668
|
+
button: z$1.string().min(1).nullable().optional(),
|
|
7669
|
+
hero: z$1.string().min(1).nullable().optional(),
|
|
7670
|
+
background: z$1.string().min(1).nullable().optional()
|
|
7671
|
+
});
|
|
7667
7672
|
//#endregion
|
|
7668
7673
|
//#region ../theme-core/src/interactive/baseSchema.ts
|
|
7669
7674
|
/**
|
|
@@ -8664,11 +8669,30 @@ const headerContainerSchema = z$1.object({
|
|
|
8664
8669
|
tint: z$1.string().nullable().optional(),
|
|
8665
8670
|
opacity: z$1.number().min(0).max(1).default(.12)
|
|
8666
8671
|
}).optional();
|
|
8667
|
-
|
|
8672
|
+
//#endregion
|
|
8673
|
+
//#region ../theme-core/src/header/interactionPalette.ts
|
|
8674
|
+
/**
|
|
8675
|
+
* The palettes that fill the nav link with a colour on hover and focus. Each
|
|
8676
|
+
* one names a background/foreground token pair.
|
|
8677
|
+
*/
|
|
8678
|
+
const headerInteractionPillPaletteValues = [
|
|
8668
8679
|
"primary",
|
|
8669
8680
|
"secondary",
|
|
8670
8681
|
"accent"
|
|
8671
|
-
]
|
|
8682
|
+
];
|
|
8683
|
+
/**
|
|
8684
|
+
* "No pill": the nav link keeps whatever hover treatment its recipe compiled,
|
|
8685
|
+
* and the palette step writes nothing at all (#1318 Phase 4c decision 2).
|
|
8686
|
+
*
|
|
8687
|
+
* It exists because "a colour change on hover, no filled background" was not
|
|
8688
|
+
* expressible in the vocabulary, and it is what
|
|
8689
|
+
* `anna-raven-therapy-and-yoga` and `kirtan-lucy` have rendered since before
|
|
8690
|
+
* the palette existed. Without it those two rows can only be classified as a
|
|
8691
|
+
* conscious visual change.
|
|
8692
|
+
*/
|
|
8693
|
+
const headerInteractionQuietPalette = "quiet";
|
|
8694
|
+
const headerInteractionPaletteValues = [...headerInteractionPillPaletteValues, headerInteractionQuietPalette];
|
|
8695
|
+
const headerInteractionPaletteSchema = z$1.enum(headerInteractionPaletteValues);
|
|
8672
8696
|
//#endregion
|
|
8673
8697
|
//#region ../theme-core/src/boxBackgroundSchema.ts
|
|
8674
8698
|
const boxBackgroundOverlaySchema = z$1.preprocess(normalizeAuthoringOpacity, z$1.discriminatedUnion("type", [
|
|
@@ -9050,11 +9074,12 @@ const inputStyle = z$1.object({
|
|
|
9050
9074
|
]),
|
|
9051
9075
|
label: z$1.enum(["inside", "above"])
|
|
9052
9076
|
});
|
|
9053
|
-
const
|
|
9077
|
+
const headerPositioningValues = [
|
|
9054
9078
|
"static",
|
|
9055
9079
|
"sticky",
|
|
9056
9080
|
"fixed"
|
|
9057
|
-
]
|
|
9081
|
+
];
|
|
9082
|
+
const headerPositioning = z$1.enum(headerPositioningValues);
|
|
9058
9083
|
const headerNavStyle = z$1.enum([
|
|
9059
9084
|
"minimal",
|
|
9060
9085
|
"underline",
|
|
@@ -9189,7 +9214,7 @@ const headerSchema = z$1.object({
|
|
|
9189
9214
|
variant: headerVariantWithLegacyAliasSchema,
|
|
9190
9215
|
positioning: headerPositioning,
|
|
9191
9216
|
shrinkOnScroll: z$1.boolean(),
|
|
9192
|
-
maxWidth: headerMaxWidth,
|
|
9217
|
+
maxWidth: headerMaxWidth.default("container"),
|
|
9193
9218
|
scrollTreatment: headerScrollTreatmentSchema,
|
|
9194
9219
|
background: headerBackgroundSchema.default({
|
|
9195
9220
|
type: "color",
|
|
@@ -9388,11 +9413,6 @@ const bodyTextSchema = z$1.object({ intro: z$1.object({
|
|
|
9388
9413
|
paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
|
|
9389
9414
|
headingSize: bodyTextIntroTypographySizeSchema.optional()
|
|
9390
9415
|
}).optional() }).optional();
|
|
9391
|
-
const gradientsSchema = z$1.object({
|
|
9392
|
-
button: z$1.string().min(1).nullable().optional(),
|
|
9393
|
-
hero: z$1.string().min(1).nullable().optional(),
|
|
9394
|
-
background: z$1.string().min(1).nullable().optional()
|
|
9395
|
-
});
|
|
9396
9416
|
/**
|
|
9397
9417
|
* Testimonial quote-mark treatment, owned by the site style (analogous to the
|
|
9398
9418
|
* accordion icon). `style` is the coarse selector; the optional fields fine-tune
|
|
@@ -13359,12 +13379,12 @@ function allGroupChildPlans(plan) {
|
|
|
13359
13379
|
}
|
|
13360
13380
|
function selectedGroupChildPlans(plan, value) {
|
|
13361
13381
|
if (plan.childSelection.kind === "all") return plan.childSelection.fields;
|
|
13362
|
-
if (!isPlainObject$
|
|
13382
|
+
if (!isPlainObject$7(value)) return [];
|
|
13363
13383
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
13364
13384
|
return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
|
|
13365
13385
|
}
|
|
13366
13386
|
function canonicalizeObjectForWrite(plans, value) {
|
|
13367
|
-
if (!isPlainObject$
|
|
13387
|
+
if (!isPlainObject$7(value)) return value;
|
|
13368
13388
|
const canonical = { ...value };
|
|
13369
13389
|
for (const plan of plans) {
|
|
13370
13390
|
if (!(plan.fieldId in canonical)) continue;
|
|
@@ -13388,7 +13408,7 @@ function canonicalizeFieldValue(plan, value) {
|
|
|
13388
13408
|
}
|
|
13389
13409
|
}
|
|
13390
13410
|
function canonicalizeMedia(value) {
|
|
13391
|
-
if (!isPlainObject$
|
|
13411
|
+
if (!isPlainObject$7(value)) return value;
|
|
13392
13412
|
const canonical = { ...value };
|
|
13393
13413
|
if (mediaIdentifierSchema.safeParse(canonical.identifier).success) deleteEnvSpecificKeys(canonical);
|
|
13394
13414
|
return canonical;
|
|
@@ -13396,13 +13416,13 @@ function canonicalizeMedia(value) {
|
|
|
13396
13416
|
function canonicalizeRepeater(plan, value) {
|
|
13397
13417
|
if (!isUnknownArray$3(value)) return value;
|
|
13398
13418
|
return value.map((item) => {
|
|
13399
|
-
if (!isPlainObject$
|
|
13419
|
+
if (!isPlainObject$7(item)) return item;
|
|
13400
13420
|
const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
|
|
13401
13421
|
return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
|
|
13402
13422
|
});
|
|
13403
13423
|
}
|
|
13404
13424
|
function canonicalizeDiscriminatedGroup(plan, value) {
|
|
13405
|
-
if (!isPlainObject$
|
|
13425
|
+
if (!isPlainObject$7(value) || plan.childSelection.kind !== "discriminatedTabs") return value;
|
|
13406
13426
|
const selection = plan.childSelection;
|
|
13407
13427
|
const selectedPlans = selectedGroupChildPlans(plan, value);
|
|
13408
13428
|
const selectedIds = rootFieldIds(selectedPlans);
|
|
@@ -13417,10 +13437,10 @@ function rootFieldIds(plans) {
|
|
|
13417
13437
|
}
|
|
13418
13438
|
function omitUndefined(value) {
|
|
13419
13439
|
if (isUnknownArray$3(value)) return value.map(omitUndefined);
|
|
13420
|
-
if (!isPlainObject$
|
|
13440
|
+
if (!isPlainObject$7(value)) return value;
|
|
13421
13441
|
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0).map(([key, field]) => [key, omitUndefined(field)]));
|
|
13422
13442
|
}
|
|
13423
|
-
function isPlainObject$
|
|
13443
|
+
function isPlainObject$7(value) {
|
|
13424
13444
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13425
13445
|
}
|
|
13426
13446
|
function isUnknownArray$3(value) {
|
|
@@ -13816,7 +13836,7 @@ function validateNormalizedFieldValue(plan, value, ctx) {
|
|
|
13816
13836
|
case "select": return validateSelectPlan(plan, value);
|
|
13817
13837
|
case "richText": return isValidRichTextDoc(value) ? [] : [issue(plan, "invalidRichText")];
|
|
13818
13838
|
case "inlineText": return validateInlineTextPlan(plan, value);
|
|
13819
|
-
case "media": return isPlainObject$
|
|
13839
|
+
case "media": return isPlainObject$6(value) && mediaIdentifierSchema.safeParse(value.identifier).success && (value.type === void 0 || typeof value.type === "string" && plan.allowedMediaTypes.includes(value.type)) ? [] : [issue(plan, "invalidMedia")];
|
|
13820
13840
|
case "link": return linkSchema.safeParse(value).success ? [] : [issue(plan, "invalidLink")];
|
|
13821
13841
|
case "repeater": return validateRepeaterPlan(plan, value, ctx);
|
|
13822
13842
|
case "group": return validateGroupPlan(plan, value, ctx);
|
|
@@ -13909,7 +13929,7 @@ function validateRepeaterPlan(plan, value, ctx) {
|
|
|
13909
13929
|
}), ...value.flatMap((item, index) => validateRepeaterItem(plan, item, index, ctx))];
|
|
13910
13930
|
}
|
|
13911
13931
|
function validateRepeaterItem(plan, item, index, ctx) {
|
|
13912
|
-
if (!isPlainObject$
|
|
13932
|
+
if (!isPlainObject$6(item)) return [{
|
|
13913
13933
|
kind: "wrongType",
|
|
13914
13934
|
path: [...plan.path, index],
|
|
13915
13935
|
fieldId: plan.fieldId,
|
|
@@ -13935,7 +13955,7 @@ function validateRepeaterItem(plan, item, index, ctx) {
|
|
|
13935
13955
|
});
|
|
13936
13956
|
}
|
|
13937
13957
|
function validateGroupPlan(plan, value, ctx) {
|
|
13938
|
-
if (!isPlainObject$
|
|
13958
|
+
if (!isPlainObject$6(value)) return [issue(plan, "wrongType", { expected: "an object" })];
|
|
13939
13959
|
if (plan.childSelection.kind === "discriminatedTabs") {
|
|
13940
13960
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
13941
13961
|
if (selectedType !== void 0 && selectedType !== null && !plan.childSelection.tabs.some((tab) => tab.typeId === selectedType)) return [{
|
|
@@ -14238,7 +14258,7 @@ function isEmptyValue$1(value) {
|
|
|
14238
14258
|
if (isEmptyRichTextDoc(value)) return true;
|
|
14239
14259
|
return false;
|
|
14240
14260
|
}
|
|
14241
|
-
function isPlainObject$
|
|
14261
|
+
function isPlainObject$6(value) {
|
|
14242
14262
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
14243
14263
|
}
|
|
14244
14264
|
function isUnknownArray$2(value) {
|
|
@@ -14248,7 +14268,7 @@ function isValidRichTextDoc(value) {
|
|
|
14248
14268
|
return isRichTextDoc(value);
|
|
14249
14269
|
}
|
|
14250
14270
|
function isEmptyRichTextDoc(value) {
|
|
14251
|
-
if (!isPlainObject$
|
|
14271
|
+
if (!isPlainObject$6(value)) return false;
|
|
14252
14272
|
if (value.type !== "doc") return false;
|
|
14253
14273
|
const content = value.content;
|
|
14254
14274
|
if (!Array.isArray(content) || content.length === 0) return true;
|
|
@@ -14300,26 +14320,6 @@ function compileManifestContentContract(manifestOrFields) {
|
|
|
14300
14320
|
findNonPortableMediaPaths: (content) => findNonPortableMediaPaths(plans, content)
|
|
14301
14321
|
}) });
|
|
14302
14322
|
}
|
|
14303
|
-
/** Strictly admits already-persisted content without repairing it. */
|
|
14304
|
-
function admitStoredManifestContent(contract, content) {
|
|
14305
|
-
const state = contract[compiledManifestContentContract];
|
|
14306
|
-
const nonPortablePaths = state.findNonPortableMediaPaths(content);
|
|
14307
|
-
if (nonPortablePaths.length > 0) return {
|
|
14308
|
-
ok: false,
|
|
14309
|
-
error: {
|
|
14310
|
-
kind: "non_portable_media",
|
|
14311
|
-
paths: nonPortablePaths
|
|
14312
|
-
}
|
|
14313
|
-
};
|
|
14314
|
-
const parsed = state.strictStoredSchema.safeParse(content);
|
|
14315
|
-
return parsed.success ? {
|
|
14316
|
-
ok: true,
|
|
14317
|
-
value: content
|
|
14318
|
-
} : {
|
|
14319
|
-
ok: false,
|
|
14320
|
-
error: zodAdmissionError(parsed.error)
|
|
14321
|
-
};
|
|
14322
|
-
}
|
|
14323
14323
|
/** Validates raw editor state without granting a stored-content proof. */
|
|
14324
14324
|
function validateManifestDraft(contract, data) {
|
|
14325
14325
|
const parsed = contract[compiledManifestContentContract].draftSchema.safeParse(data);
|
|
@@ -14337,21 +14337,6 @@ function validateManifestDraft(contract, data) {
|
|
|
14337
14337
|
};
|
|
14338
14338
|
}
|
|
14339
14339
|
/**
|
|
14340
|
-
* Applies a pure runtime projection to admitted stored content and validates
|
|
14341
|
-
* the resulting render shape before granting the renderable proof.
|
|
14342
|
-
*/
|
|
14343
|
-
function deriveRenderableManifestContent(contract, stored, project) {
|
|
14344
|
-
const projected = project(stored);
|
|
14345
|
-
const parsed = contract[compiledManifestContentContract].renderSchema.safeParse(projected);
|
|
14346
|
-
return parsed.success ? {
|
|
14347
|
-
ok: true,
|
|
14348
|
-
value: projected
|
|
14349
|
-
} : {
|
|
14350
|
-
ok: false,
|
|
14351
|
-
error: zodAdmissionError(parsed.error)
|
|
14352
|
-
};
|
|
14353
|
-
}
|
|
14354
|
-
/**
|
|
14355
14340
|
* Re-admits renderable content after a transport boundary has erased its
|
|
14356
14341
|
* compile-time proof. Unlike write admission, this never canonicalizes or
|
|
14357
14342
|
* grants a stored proof, so hydrated media cannot flow back to persistence.
|
|
@@ -14507,6 +14492,23 @@ function defineLookOptionGroup(group) {
|
|
|
14507
14492
|
defaultOption(group);
|
|
14508
14493
|
return group;
|
|
14509
14494
|
}
|
|
14495
|
+
function resolveLookOption(group, requestedOptionId) {
|
|
14496
|
+
const fallback = defaultOption(group);
|
|
14497
|
+
if (requestedOptionId === null || requestedOptionId === void 0) return {
|
|
14498
|
+
kind: "defaulted",
|
|
14499
|
+
option: fallback
|
|
14500
|
+
};
|
|
14501
|
+
const selected = group.options.find((option) => option.id === requestedOptionId);
|
|
14502
|
+
return selected ? {
|
|
14503
|
+
kind: "selected",
|
|
14504
|
+
option: selected
|
|
14505
|
+
} : {
|
|
14506
|
+
kind: "repaired",
|
|
14507
|
+
requestedOptionId,
|
|
14508
|
+
option: fallback,
|
|
14509
|
+
reason: "unsupported-option"
|
|
14510
|
+
};
|
|
14511
|
+
}
|
|
14510
14512
|
function defaultOption(group) {
|
|
14511
14513
|
const option = group.options.find((candidate) => candidate.id === group.defaultOptionId);
|
|
14512
14514
|
if (option) return option;
|
|
@@ -15397,6 +15399,12 @@ function cardRoleFromLegacyVocabulary(value) {
|
|
|
15397
15399
|
*/
|
|
15398
15400
|
const cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
|
|
15399
15401
|
//#endregion
|
|
15402
|
+
//#region ../theme-core/src/site-styles/compilationPlan.ts
|
|
15403
|
+
const defaultHeaderBrandPreferences = {
|
|
15404
|
+
brandDisplay: "auto",
|
|
15405
|
+
logoSize: "auto"
|
|
15406
|
+
};
|
|
15407
|
+
//#endregion
|
|
15400
15408
|
//#region ../theme-core/src/site-styles/sectionTransitions.ts
|
|
15401
15409
|
function boundaryRuntimeMetrics(family, size, heightExpression) {
|
|
15402
15410
|
return {
|
|
@@ -17778,6 +17786,12 @@ const overlayLogoAccommodations = [
|
|
|
17778
17786
|
inlineMarkWithTitleLogoAccommodation,
|
|
17779
17787
|
unbackedOverlayLogoAccommodation
|
|
17780
17788
|
];
|
|
17789
|
+
const allCuratedLogoAccommodations = [
|
|
17790
|
+
...inlineClassicLogoAccommodations,
|
|
17791
|
+
...centeredLogoAccommodations,
|
|
17792
|
+
...overlayLogoAccommodations
|
|
17793
|
+
];
|
|
17794
|
+
new Set(allCuratedLogoAccommodations.filter((option) => option.providesBacking === true).map((option) => option.pattern));
|
|
17781
17795
|
const headerLayoutCatalog = defineExactCatalog()([
|
|
17782
17796
|
{
|
|
17783
17797
|
id: asHeaderLayoutId("classic"),
|
|
@@ -17927,9 +17941,50 @@ defineLookOptionGroup({
|
|
|
17927
17941
|
{
|
|
17928
17942
|
id: "secondary",
|
|
17929
17943
|
label: "Secondary"
|
|
17944
|
+
},
|
|
17945
|
+
{
|
|
17946
|
+
id: headerInteractionQuietPalette,
|
|
17947
|
+
label: "Text only"
|
|
17930
17948
|
}
|
|
17931
17949
|
]
|
|
17932
17950
|
});
|
|
17951
|
+
/**
|
|
17952
|
+
* Key-sorted serialisation, because one side of the comparison is built here
|
|
17953
|
+
* and the other comes back out of `jsonb` in whatever order it was stored.
|
|
17954
|
+
* `JSON.stringify` alone reported two identical effect lists as different,
|
|
17955
|
+
* which is the same key-order trap the `logoText` derivation hit in Phase 4a.
|
|
17956
|
+
*/
|
|
17957
|
+
function canonicalJson(value) {
|
|
17958
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
17959
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, member]) => member !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`).join(",")}}`;
|
|
17960
|
+
return value === void 0 ? "undefined" : JSON.stringify(value);
|
|
17961
|
+
}
|
|
17962
|
+
//#endregion
|
|
17963
|
+
//#region ../theme-core/src/site-styles/headerNavStyles.ts
|
|
17964
|
+
function applyNavTypography(style, typography) {
|
|
17965
|
+
switch (typography) {
|
|
17966
|
+
case "plain": return style;
|
|
17967
|
+
case "heading": return {
|
|
17968
|
+
...style,
|
|
17969
|
+
typography: "heading",
|
|
17970
|
+
fontWeight: "medium",
|
|
17971
|
+
letterSpacing: "wide"
|
|
17972
|
+
};
|
|
17973
|
+
case "quiet-caps": return {
|
|
17974
|
+
...style,
|
|
17975
|
+
textTransform: "uppercase",
|
|
17976
|
+
textSize: "sm",
|
|
17977
|
+
letterSpacing: "wide"
|
|
17978
|
+
};
|
|
17979
|
+
case "brand-caps": return {
|
|
17980
|
+
...style,
|
|
17981
|
+
textTransform: "uppercase",
|
|
17982
|
+
textSize: "sm",
|
|
17983
|
+
letterSpacing: "wider"
|
|
17984
|
+
};
|
|
17985
|
+
default: return assertNever$24(typography, "Unhandled header nav typography");
|
|
17986
|
+
}
|
|
17987
|
+
}
|
|
17933
17988
|
function asFooterLookId(value) {
|
|
17934
17989
|
return value;
|
|
17935
17990
|
}
|
|
@@ -18638,6 +18693,37 @@ function uniformSectionForeground(token) {
|
|
|
18638
18693
|
supportingColor: token
|
|
18639
18694
|
};
|
|
18640
18695
|
}
|
|
18696
|
+
function compileTransitionRuntime(input) {
|
|
18697
|
+
return {
|
|
18698
|
+
treatment: input.treatment,
|
|
18699
|
+
from: input.from,
|
|
18700
|
+
to: input.to,
|
|
18701
|
+
cssVars: {
|
|
18702
|
+
from: transitionEndpointCssValue(input.from),
|
|
18703
|
+
to: transitionEndpointCssValue(input.to)
|
|
18704
|
+
}
|
|
18705
|
+
};
|
|
18706
|
+
}
|
|
18707
|
+
function transitionEndpointCssValue(endpoint) {
|
|
18708
|
+
switch (endpoint.kind) {
|
|
18709
|
+
case "color-token": return themeColorTokenCssValue(endpoint.token);
|
|
18710
|
+
case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
|
|
18711
|
+
default: return assertNever$24(endpoint);
|
|
18712
|
+
}
|
|
18713
|
+
}
|
|
18714
|
+
function tokenForTransitionSurface(surface) {
|
|
18715
|
+
switch (surface) {
|
|
18716
|
+
case "base":
|
|
18717
|
+
case "hero-image": return "background";
|
|
18718
|
+
case "muted-band": return "muted";
|
|
18719
|
+
case "accent-band": return "accent";
|
|
18720
|
+
case "brand-depth-band": return "primary-800";
|
|
18721
|
+
default: return assertNever$24(surface);
|
|
18722
|
+
}
|
|
18723
|
+
}
|
|
18724
|
+
function themeColorTokenCssValue(token) {
|
|
18725
|
+
return `rgb(var(--tb-${token}))`;
|
|
18726
|
+
}
|
|
18641
18727
|
//#endregion
|
|
18642
18728
|
//#region ../theme-core/src/site-styles/curatedSiteStyles.ts
|
|
18643
18729
|
const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
|
|
@@ -20345,175 +20431,6 @@ const blockDesignFallback = {
|
|
|
20345
20431
|
};
|
|
20346
20432
|
blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
|
|
20347
20433
|
//#endregion
|
|
20348
|
-
//#region ../theme-core/src/site-styles/generatedSchemas.ts
|
|
20349
|
-
function uniqueNonEmpty(values) {
|
|
20350
|
-
const uniqueValues = [...new Set(values)];
|
|
20351
|
-
const firstValue = uniqueValues[0];
|
|
20352
|
-
if (!firstValue) throw new Error("Expected at least one generated schema option.");
|
|
20353
|
-
return [firstValue, ...uniqueValues.slice(1)];
|
|
20354
|
-
}
|
|
20355
|
-
const generatedPanelTreatments = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "panel").map((frame) => frame.treatment));
|
|
20356
|
-
const generatedFlushTreatments = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "flush-panel").map((frame) => frame.treatment));
|
|
20357
|
-
const generatedFlushBleeds = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "flush-panel").map((frame) => frame.bleed));
|
|
20358
|
-
const generatedPanelFrameSchema = z$1.object({
|
|
20359
|
-
kind: z$1.literal("panel"),
|
|
20360
|
-
treatment: z$1.enum(generatedPanelTreatments)
|
|
20361
|
-
}).strict();
|
|
20362
|
-
const generatedFlushPanelFrameSchema = z$1.object({
|
|
20363
|
-
kind: z$1.literal("flush-panel"),
|
|
20364
|
-
treatment: z$1.enum(generatedFlushTreatments),
|
|
20365
|
-
bleed: z$1.enum(generatedFlushBleeds)
|
|
20366
|
-
}).strict();
|
|
20367
|
-
const generatedContentFrameIntentSchema = z$1.discriminatedUnion("kind", [
|
|
20368
|
-
z$1.object({ kind: z$1.literal("plain") }).strict(),
|
|
20369
|
-
generatedPanelFrameSchema,
|
|
20370
|
-
generatedFlushPanelFrameSchema
|
|
20371
|
-
]);
|
|
20372
|
-
const generatedSiteStyleIdSchema = z$1.enum(curatedSiteStyleIdValues).transform(asSiteStyleId);
|
|
20373
|
-
function generatedChoiceSchema(valueSchema) {
|
|
20374
|
-
return z$1.discriminatedUnion("kind", [z$1.object({
|
|
20375
|
-
source: z$1.literal("generated"),
|
|
20376
|
-
kind: z$1.literal("auto")
|
|
20377
|
-
}).strict(), z$1.object({
|
|
20378
|
-
source: z$1.literal("generated"),
|
|
20379
|
-
kind: z$1.literal("explicit"),
|
|
20380
|
-
value: valueSchema
|
|
20381
|
-
}).strict()]);
|
|
20382
|
-
}
|
|
20383
|
-
const generatedBlockDesignIntentSchema = z$1.object({
|
|
20384
|
-
sectionSurface: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.sectionSurfaces)),
|
|
20385
|
-
contentFrame: generatedChoiceSchema(generatedContentFrameIntentSchema),
|
|
20386
|
-
cardRole: generatedChoiceSchema(cardRoleWithLegacyAliasSchema),
|
|
20387
|
-
transitionAfter: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.transitions)),
|
|
20388
|
-
emphasis: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.emphases))
|
|
20389
|
-
}).strict();
|
|
20390
|
-
const generatedMediaCompositionIntentSchema = z$1.enum([
|
|
20391
|
-
"none",
|
|
20392
|
-
"supporting",
|
|
20393
|
-
"dominant"
|
|
20394
|
-
]);
|
|
20395
|
-
const generatedDecorativeCompositionIntentSchema = z$1.enum(["none", "supporting"]);
|
|
20396
|
-
const generatedHeroLegibilityStrategySchema = z$1.enum(["none", "scrim-gradient"]);
|
|
20397
|
-
const generatedBlockMediaIntentSchema = z$1.object({
|
|
20398
|
-
media: generatedMediaCompositionIntentSchema,
|
|
20399
|
-
decorative: generatedDecorativeCompositionIntentSchema,
|
|
20400
|
-
heroLegibility: generatedChoiceSchema(generatedHeroLegibilityStrategySchema)
|
|
20401
|
-
}).strict();
|
|
20402
|
-
const generatedBoundaryIntentSchema = z$1.object({ transitionAfter: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.transitions)) }).strict();
|
|
20403
|
-
const generatedPageDesignBlockIntentSchema = z$1.object({
|
|
20404
|
-
blockId: z$1.string().min(1),
|
|
20405
|
-
blockKind: z$1.string().min(1),
|
|
20406
|
-
purpose: z$1.string().min(1),
|
|
20407
|
-
design: generatedBlockDesignIntentSchema,
|
|
20408
|
-
mediaIntent: generatedBlockMediaIntentSchema,
|
|
20409
|
-
boundaryIntent: generatedBoundaryIntentSchema
|
|
20410
|
-
}).strict();
|
|
20411
|
-
z$1.object({
|
|
20412
|
-
siteStyleId: generatedSiteStyleIdSchema,
|
|
20413
|
-
blocks: z$1.array(generatedPageDesignBlockIntentSchema)
|
|
20414
|
-
}).strict();
|
|
20415
|
-
//#endregion
|
|
20416
|
-
//#region ../theme-core/src/site-styles/pageDesignAuthority.ts
|
|
20417
|
-
const nonEmptyStringSchema = z$1.string().min(1);
|
|
20418
|
-
const blockContentVersionIdSchema = nonEmptyStringSchema.transform(asBlockContentVersionId);
|
|
20419
|
-
const designBlockIdSchema = nonEmptyStringSchema.transform(asDesignBlockId);
|
|
20420
|
-
const designPageIdSchema = nonEmptyStringSchema.transform(asDesignPageId);
|
|
20421
|
-
const designEntryIdSchema = nonEmptyStringSchema.transform(asDesignEntryId);
|
|
20422
|
-
const blockOrderIndexSchema = z$1.number().int().nonnegative().transform(asBlockOrderIndex);
|
|
20423
|
-
const publishedPageBlockSnapshotSchema = z$1.object({
|
|
20424
|
-
kind: z$1.literal("published-block-content"),
|
|
20425
|
-
blockId: designBlockIdSchema,
|
|
20426
|
-
contentVersionId: blockContentVersionIdSchema,
|
|
20427
|
-
identifier: z$1.string().nullable(),
|
|
20428
|
-
blockKind: nonEmptyStringSchema,
|
|
20429
|
-
orderIndex: blockOrderIndexSchema
|
|
20430
|
-
}).strict();
|
|
20431
|
-
const draftPageBlockSnapshotSchema = z$1.object({
|
|
20432
|
-
kind: z$1.literal("draft-block-content"),
|
|
20433
|
-
blockId: designBlockIdSchema,
|
|
20434
|
-
contentVersionId: blockContentVersionIdSchema.nullable(),
|
|
20435
|
-
identifier: z$1.string().nullable(),
|
|
20436
|
-
blockKind: nonEmptyStringSchema,
|
|
20437
|
-
orderIndex: blockOrderIndexSchema
|
|
20438
|
-
}).strict();
|
|
20439
|
-
const missingPageBlockSnapshotSchema = z$1.object({
|
|
20440
|
-
kind: z$1.literal("missing-block-content"),
|
|
20441
|
-
blockId: designBlockIdSchema,
|
|
20442
|
-
identifier: z$1.string().nullable(),
|
|
20443
|
-
blockKind: nonEmptyStringSchema,
|
|
20444
|
-
orderIndex: blockOrderIndexSchema,
|
|
20445
|
-
reason: z$1.enum([
|
|
20446
|
-
"new-block",
|
|
20447
|
-
"deleted-content-version",
|
|
20448
|
-
"unpublished-block"
|
|
20449
|
-
])
|
|
20450
|
-
}).strict();
|
|
20451
|
-
const pageBlockSnapshotSchema = z$1.discriminatedUnion("kind", [
|
|
20452
|
-
publishedPageBlockSnapshotSchema,
|
|
20453
|
-
draftPageBlockSnapshotSchema,
|
|
20454
|
-
missingPageBlockSnapshotSchema
|
|
20455
|
-
]);
|
|
20456
|
-
const publishedPageBlockSequenceSchema = z$1.array(publishedPageBlockSnapshotSchema).superRefine(validateBlockSequence);
|
|
20457
|
-
const draftPageBlockSequenceSchema = z$1.array(pageBlockSnapshotSchema).superRefine(validateBlockSequence);
|
|
20458
|
-
const publishedPageDesignVersionDataV1Schema = z$1.object({
|
|
20459
|
-
schemaVersion: z$1.literal(1),
|
|
20460
|
-
stage: z$1.literal("published"),
|
|
20461
|
-
pageId: designPageIdSchema,
|
|
20462
|
-
blockSequence: publishedPageBlockSequenceSchema
|
|
20463
|
-
}).strict();
|
|
20464
|
-
const draftPageDesignVersionDataV1Schema = z$1.object({
|
|
20465
|
-
schemaVersion: z$1.literal(1),
|
|
20466
|
-
stage: z$1.literal("draft"),
|
|
20467
|
-
pageId: designPageIdSchema,
|
|
20468
|
-
blockSequence: draftPageBlockSequenceSchema
|
|
20469
|
-
}).strict();
|
|
20470
|
-
z$1.discriminatedUnion("stage", [publishedPageDesignVersionDataV1Schema, draftPageDesignVersionDataV1Schema]);
|
|
20471
|
-
z$1.discriminatedUnion("kind", [
|
|
20472
|
-
z$1.object({
|
|
20473
|
-
kind: z$1.literal("block"),
|
|
20474
|
-
blockId: designBlockIdSchema
|
|
20475
|
-
}).strict(),
|
|
20476
|
-
z$1.object({
|
|
20477
|
-
kind: z$1.literal("entry"),
|
|
20478
|
-
entryId: designEntryIdSchema
|
|
20479
|
-
}).strict(),
|
|
20480
|
-
z$1.object({
|
|
20481
|
-
kind: z$1.literal("page"),
|
|
20482
|
-
pageId: designPageIdSchema
|
|
20483
|
-
}).strict()
|
|
20484
|
-
]);
|
|
20485
|
-
function validateBlockSequence(snapshots, ctx) {
|
|
20486
|
-
for (const issue of collectBlockSequenceIssues(snapshots)) ctx.addIssue(issue);
|
|
20487
|
-
}
|
|
20488
|
-
function collectBlockSequenceIssues(snapshots) {
|
|
20489
|
-
const issues = [];
|
|
20490
|
-
const seenBlockIds = /* @__PURE__ */ new Set();
|
|
20491
|
-
const seenOrderIndexes = /* @__PURE__ */ new Set();
|
|
20492
|
-
let previousOrderIndex = null;
|
|
20493
|
-
snapshots.forEach((snapshot, index) => {
|
|
20494
|
-
if (seenBlockIds.has(snapshot.blockId)) issues.push({
|
|
20495
|
-
code: z$1.ZodIssueCode.custom,
|
|
20496
|
-
message: `Duplicate blockId in page design blockSequence: ${snapshot.blockId}`,
|
|
20497
|
-
path: [index, "blockId"]
|
|
20498
|
-
});
|
|
20499
|
-
seenBlockIds.add(snapshot.blockId);
|
|
20500
|
-
const orderIndex = snapshot.orderIndex;
|
|
20501
|
-
if (seenOrderIndexes.has(orderIndex)) issues.push({
|
|
20502
|
-
code: z$1.ZodIssueCode.custom,
|
|
20503
|
-
message: `Duplicate orderIndex in page design blockSequence: ${String(orderIndex)}`,
|
|
20504
|
-
path: [index, "orderIndex"]
|
|
20505
|
-
});
|
|
20506
|
-
seenOrderIndexes.add(orderIndex);
|
|
20507
|
-
if (previousOrderIndex !== null && orderIndex <= previousOrderIndex) issues.push({
|
|
20508
|
-
code: z$1.ZodIssueCode.custom,
|
|
20509
|
-
message: `Expected orderIndex greater than ${String(previousOrderIndex)} for blockSequence position ${String(index)}; received ${String(orderIndex)}.`,
|
|
20510
|
-
path: [index, "orderIndex"]
|
|
20511
|
-
});
|
|
20512
|
-
previousOrderIndex = orderIndex;
|
|
20513
|
-
});
|
|
20514
|
-
return issues;
|
|
20515
|
-
}
|
|
20516
|
-
//#endregion
|
|
20517
20434
|
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
20518
20435
|
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
20519
20436
|
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
@@ -20528,6 +20445,25 @@ function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
|
20528
20445
|
return token ? [token] : [];
|
|
20529
20446
|
});
|
|
20530
20447
|
}
|
|
20448
|
+
//#endregion
|
|
20449
|
+
//#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
|
|
20450
|
+
const headerNavTypographyPreferenceValues = [
|
|
20451
|
+
"inherit",
|
|
20452
|
+
"body",
|
|
20453
|
+
"heading"
|
|
20454
|
+
];
|
|
20455
|
+
const headerNavFontSizePreferenceValues = [
|
|
20456
|
+
"inherit",
|
|
20457
|
+
"sm",
|
|
20458
|
+
"base",
|
|
20459
|
+
"lg",
|
|
20460
|
+
"xl"
|
|
20461
|
+
];
|
|
20462
|
+
const defaultHeaderNavPreferences = {
|
|
20463
|
+
typography: "inherit",
|
|
20464
|
+
fontSize: "inherit",
|
|
20465
|
+
interactionPalette: "recommended"
|
|
20466
|
+
};
|
|
20531
20467
|
/**
|
|
20532
20468
|
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
20533
20469
|
* nav link style. This is where the precedence contract lands:
|
|
@@ -20554,6 +20490,322 @@ function readHeaderNavFontSizePreference(value) {
|
|
|
20554
20490
|
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
20555
20491
|
}
|
|
20556
20492
|
//#endregion
|
|
20493
|
+
//#region ../theme-core/src/site-styles/headerBrandPresentation.ts
|
|
20494
|
+
/**
|
|
20495
|
+
* Whether the header wears a brand presentation at all (#1318 Phase 4c).
|
|
20496
|
+
*
|
|
20497
|
+
* `applyHeaderBrandPresentation` is unconditional today: given a logo context
|
|
20498
|
+
* it always writes a `logo` object carrying a resolved display, size and
|
|
20499
|
+
* accommodation. That makes "this header has no brand presentation block" —
|
|
20500
|
+
* which is what several live sites persist, including one whose site *does*
|
|
20501
|
+
* have a logo asset — a state the preference record could not express, so a
|
|
20502
|
+
* recompile would silently give the site a logo treatment it has never
|
|
20503
|
+
* rendered.
|
|
20504
|
+
*
|
|
20505
|
+
* This is the third field in this phase with that shape, after the interaction
|
|
20506
|
+
* palette and the nav text size: the persisted bytes carry intent the reader
|
|
20507
|
+
* cannot say, so the recompile "corrects" it. The fix is the same one — widen
|
|
20508
|
+
* the closed vocabulary with a named member and derive it by baseline
|
|
20509
|
+
* comparison — and it is a *widening*, not a restyle: `'recommended'` is the
|
|
20510
|
+
* default and reproduces today's behaviour exactly, so no row whose bytes carry
|
|
20511
|
+
* a presentation compiles differently for `'plain'` existing.
|
|
20512
|
+
*
|
|
20513
|
+
* `'plain'` is deliberately not "preserve whatever bytes are there". It is a
|
|
20514
|
+
* constructive instruction — *write no presentation block* — so Phase 4e's
|
|
20515
|
+
* compiler, which builds the header from nothing, can produce it.
|
|
20516
|
+
*/
|
|
20517
|
+
const headerBrandPresentationValues = ["recommended", "plain"];
|
|
20518
|
+
/**
|
|
20519
|
+
* Key-order-safe structural equality over the persisted `logo` object.
|
|
20520
|
+
*
|
|
20521
|
+
* The persisted value comes back from `jsonb`, whose key order is not the order
|
|
20522
|
+
* the applier builds; a `JSON.stringify` comparison would read two equal logo
|
|
20523
|
+
* blocks as different and quietly classify a `'recommended'` row as
|
|
20524
|
+
* unrecognised — the same trap the interaction-palette transcription test
|
|
20525
|
+
* exists to catch.
|
|
20526
|
+
*/
|
|
20527
|
+
function jsonEquals(left, right) {
|
|
20528
|
+
if (left === right) return true;
|
|
20529
|
+
if (left === null || right === null) return false;
|
|
20530
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
20531
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
20532
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((value, index) => jsonEquals(value, right[index]));
|
|
20533
|
+
const leftRecord = left;
|
|
20534
|
+
const rightRecord = right;
|
|
20535
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(leftRecord), ...Object.keys(rightRecord)]);
|
|
20536
|
+
for (const key of keys) if (!jsonEquals(leftRecord[key], rightRecord[key])) return false;
|
|
20537
|
+
return true;
|
|
20538
|
+
}
|
|
20539
|
+
defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
|
|
20540
|
+
function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
|
|
20541
|
+
const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
|
|
20542
|
+
const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
|
|
20543
|
+
if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
|
|
20544
|
+
const transition = compileTransitionRuntime({
|
|
20545
|
+
treatment: boundary.treatment,
|
|
20546
|
+
from: fromSurface,
|
|
20547
|
+
to: toSurface
|
|
20548
|
+
});
|
|
20549
|
+
return {
|
|
20550
|
+
...boundary,
|
|
20551
|
+
from: transition.from,
|
|
20552
|
+
to: transition.to,
|
|
20553
|
+
cssVars: transition.cssVars
|
|
20554
|
+
};
|
|
20555
|
+
}
|
|
20556
|
+
function contentOwnedBackgroundEndpoint(content) {
|
|
20557
|
+
const background = readRecord(readRecord(content?._sectionStyles)?.background);
|
|
20558
|
+
if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
|
|
20559
|
+
const token = parseThemeColorToken(background.color);
|
|
20560
|
+
if (!token) return null;
|
|
20561
|
+
return {
|
|
20562
|
+
kind: "color-token",
|
|
20563
|
+
token
|
|
20564
|
+
};
|
|
20565
|
+
}
|
|
20566
|
+
function readRecord(value) {
|
|
20567
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
20568
|
+
}
|
|
20569
|
+
//#endregion
|
|
20570
|
+
//#region ../theme-core/src/site-styles/generatedSchemas.ts
|
|
20571
|
+
function uniqueNonEmpty(values) {
|
|
20572
|
+
const uniqueValues = [...new Set(values)];
|
|
20573
|
+
const firstValue = uniqueValues[0];
|
|
20574
|
+
if (!firstValue) throw new Error("Expected at least one generated schema option.");
|
|
20575
|
+
return [firstValue, ...uniqueValues.slice(1)];
|
|
20576
|
+
}
|
|
20577
|
+
const generatedPanelTreatments = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "panel").map((frame) => frame.treatment));
|
|
20578
|
+
const generatedFlushTreatments = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "flush-panel").map((frame) => frame.treatment));
|
|
20579
|
+
const generatedFlushBleeds = uniqueNonEmpty(generatedDesignOptionCatalog.contentFrames.filter((frame) => frame.kind === "flush-panel").map((frame) => frame.bleed));
|
|
20580
|
+
const generatedPanelFrameSchema = z$1.object({
|
|
20581
|
+
kind: z$1.literal("panel"),
|
|
20582
|
+
treatment: z$1.enum(generatedPanelTreatments)
|
|
20583
|
+
}).strict();
|
|
20584
|
+
const generatedFlushPanelFrameSchema = z$1.object({
|
|
20585
|
+
kind: z$1.literal("flush-panel"),
|
|
20586
|
+
treatment: z$1.enum(generatedFlushTreatments),
|
|
20587
|
+
bleed: z$1.enum(generatedFlushBleeds)
|
|
20588
|
+
}).strict();
|
|
20589
|
+
const generatedContentFrameIntentSchema = z$1.discriminatedUnion("kind", [
|
|
20590
|
+
z$1.object({ kind: z$1.literal("plain") }).strict(),
|
|
20591
|
+
generatedPanelFrameSchema,
|
|
20592
|
+
generatedFlushPanelFrameSchema
|
|
20593
|
+
]);
|
|
20594
|
+
const generatedSiteStyleIdSchema = z$1.enum(curatedSiteStyleIdValues).transform(asSiteStyleId);
|
|
20595
|
+
function generatedChoiceSchema(valueSchema) {
|
|
20596
|
+
return z$1.discriminatedUnion("kind", [z$1.object({
|
|
20597
|
+
source: z$1.literal("generated"),
|
|
20598
|
+
kind: z$1.literal("auto")
|
|
20599
|
+
}).strict(), z$1.object({
|
|
20600
|
+
source: z$1.literal("generated"),
|
|
20601
|
+
kind: z$1.literal("explicit"),
|
|
20602
|
+
value: valueSchema
|
|
20603
|
+
}).strict()]);
|
|
20604
|
+
}
|
|
20605
|
+
const generatedBlockDesignIntentSchema = z$1.object({
|
|
20606
|
+
sectionSurface: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.sectionSurfaces)),
|
|
20607
|
+
contentFrame: generatedChoiceSchema(generatedContentFrameIntentSchema),
|
|
20608
|
+
cardRole: generatedChoiceSchema(cardRoleWithLegacyAliasSchema),
|
|
20609
|
+
transitionAfter: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.transitions)),
|
|
20610
|
+
emphasis: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.emphases))
|
|
20611
|
+
}).strict();
|
|
20612
|
+
const generatedMediaCompositionIntentSchema = z$1.enum([
|
|
20613
|
+
"none",
|
|
20614
|
+
"supporting",
|
|
20615
|
+
"dominant"
|
|
20616
|
+
]);
|
|
20617
|
+
const generatedDecorativeCompositionIntentSchema = z$1.enum(["none", "supporting"]);
|
|
20618
|
+
const generatedHeroLegibilityStrategySchema = z$1.enum(["none", "scrim-gradient"]);
|
|
20619
|
+
const generatedBlockMediaIntentSchema = z$1.object({
|
|
20620
|
+
media: generatedMediaCompositionIntentSchema,
|
|
20621
|
+
decorative: generatedDecorativeCompositionIntentSchema,
|
|
20622
|
+
heroLegibility: generatedChoiceSchema(generatedHeroLegibilityStrategySchema)
|
|
20623
|
+
}).strict();
|
|
20624
|
+
const generatedBoundaryIntentSchema = z$1.object({ transitionAfter: generatedChoiceSchema(z$1.enum(generatedDesignOptionCatalog.transitions)) }).strict();
|
|
20625
|
+
const generatedPageDesignBlockIntentSchema = z$1.object({
|
|
20626
|
+
blockId: z$1.string().min(1),
|
|
20627
|
+
blockKind: z$1.string().min(1),
|
|
20628
|
+
purpose: z$1.string().min(1),
|
|
20629
|
+
design: generatedBlockDesignIntentSchema,
|
|
20630
|
+
mediaIntent: generatedBlockMediaIntentSchema,
|
|
20631
|
+
boundaryIntent: generatedBoundaryIntentSchema
|
|
20632
|
+
}).strict();
|
|
20633
|
+
z$1.object({
|
|
20634
|
+
siteStyleId: generatedSiteStyleIdSchema,
|
|
20635
|
+
blocks: z$1.array(generatedPageDesignBlockIntentSchema)
|
|
20636
|
+
}).strict();
|
|
20637
|
+
//#endregion
|
|
20638
|
+
//#region ../theme-core/src/site-styles/headerPreferencesPersistence.ts
|
|
20639
|
+
const backgroundColorTokenSchema = z$1.unknown().transform((value, ctx) => {
|
|
20640
|
+
const token = parseSiteChromeBackgroundColorToken(value);
|
|
20641
|
+
if (token === null) {
|
|
20642
|
+
ctx.addIssue({
|
|
20643
|
+
code: "custom",
|
|
20644
|
+
message: "Unknown site chrome background colour token"
|
|
20645
|
+
});
|
|
20646
|
+
return z$1.NEVER;
|
|
20647
|
+
}
|
|
20648
|
+
return token;
|
|
20649
|
+
});
|
|
20650
|
+
const backgroundOverrideSchema = z$1.discriminatedUnion("kind", [z$1.object({ kind: z$1.literal("default") }).strict(), z$1.object({
|
|
20651
|
+
kind: z$1.literal("color"),
|
|
20652
|
+
token: backgroundColorTokenSchema
|
|
20653
|
+
}).strict()]);
|
|
20654
|
+
const logoTextIntentSchema = z$1.object({
|
|
20655
|
+
mobileMaxWidth: z$1.enum([
|
|
20656
|
+
"xs",
|
|
20657
|
+
"sm",
|
|
20658
|
+
"md"
|
|
20659
|
+
]).optional(),
|
|
20660
|
+
mobileWrapLines: z$1.union([z$1.literal(1), z$1.literal(2)]).optional(),
|
|
20661
|
+
hideOnShrink: z$1.boolean().optional()
|
|
20662
|
+
}).strict();
|
|
20663
|
+
const structureSchema = z$1.object({
|
|
20664
|
+
positioning: z$1.union([z$1.literal("inherit"), z$1.enum(headerPositioningValues)]),
|
|
20665
|
+
shrinkOnScroll: z$1.union([z$1.literal("inherit"), z$1.boolean()]),
|
|
20666
|
+
logoText: z$1.union([
|
|
20667
|
+
z$1.literal("inherit"),
|
|
20668
|
+
z$1.literal("none"),
|
|
20669
|
+
logoTextIntentSchema
|
|
20670
|
+
])
|
|
20671
|
+
}).strict();
|
|
20672
|
+
const headerPreferencesShape = {
|
|
20673
|
+
brandPresentation: z$1.enum(headerBrandPresentationValues),
|
|
20674
|
+
brandDisplay: z$1.enum(brandDisplayModeValues),
|
|
20675
|
+
logoSize: z$1.enum(logoSizePolicyValues),
|
|
20676
|
+
navFontRole: z$1.enum(headerNavTypographyPreferenceValues),
|
|
20677
|
+
navTextSize: z$1.enum(headerNavFontSizePreferenceValues),
|
|
20678
|
+
interactionPalette: z$1.union([z$1.literal("recommended"), z$1.enum(headerInteractionPaletteValues)]),
|
|
20679
|
+
background: backgroundOverrideSchema,
|
|
20680
|
+
structure: structureSchema
|
|
20681
|
+
};
|
|
20682
|
+
z$1.object(headerPreferencesShape).strict();
|
|
20683
|
+
z$1.object(headerPreferencesShape).omit({
|
|
20684
|
+
brandPresentation: true,
|
|
20685
|
+
structure: true
|
|
20686
|
+
}).strict();
|
|
20687
|
+
//#endregion
|
|
20688
|
+
//#region ../theme-core/src/site-styles/headerBytesComparison.ts
|
|
20689
|
+
function isPlainObject$5(value) {
|
|
20690
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20691
|
+
}
|
|
20692
|
+
/**
|
|
20693
|
+
* Structural diff by JSON path. Values are compared through
|
|
20694
|
+
* `JSON.stringify`, so a key present with the value `undefined` and an absent
|
|
20695
|
+
* key read as the same thing — which is what `jsonb` does to them anyway
|
|
20696
|
+
* (#1318 Correction 5).
|
|
20697
|
+
*/
|
|
20698
|
+
function diffJsonPaths(left, right, path = "") {
|
|
20699
|
+
if (isPlainObject$5(left) && isPlainObject$5(right)) return [.../* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])].sort().flatMap((key) => diffJsonPaths(left[key], right[key], path ? `${path}.${key}` : key));
|
|
20700
|
+
if (JSON.stringify(left) === JSON.stringify(right)) return [];
|
|
20701
|
+
return [{
|
|
20702
|
+
path,
|
|
20703
|
+
left,
|
|
20704
|
+
right
|
|
20705
|
+
}];
|
|
20706
|
+
}
|
|
20707
|
+
//#endregion
|
|
20708
|
+
//#region ../theme-core/src/site-styles/pageDesignAuthority.ts
|
|
20709
|
+
const nonEmptyStringSchema = z$1.string().min(1);
|
|
20710
|
+
const blockContentVersionIdSchema = nonEmptyStringSchema.transform(asBlockContentVersionId);
|
|
20711
|
+
const designBlockIdSchema = nonEmptyStringSchema.transform(asDesignBlockId);
|
|
20712
|
+
const designPageIdSchema = nonEmptyStringSchema.transform(asDesignPageId);
|
|
20713
|
+
const designEntryIdSchema = nonEmptyStringSchema.transform(asDesignEntryId);
|
|
20714
|
+
const blockOrderIndexSchema = z$1.number().int().nonnegative().transform(asBlockOrderIndex);
|
|
20715
|
+
const publishedPageBlockSnapshotSchema = z$1.object({
|
|
20716
|
+
kind: z$1.literal("published-block-content"),
|
|
20717
|
+
blockId: designBlockIdSchema,
|
|
20718
|
+
contentVersionId: blockContentVersionIdSchema,
|
|
20719
|
+
identifier: z$1.string().nullable(),
|
|
20720
|
+
blockKind: nonEmptyStringSchema,
|
|
20721
|
+
orderIndex: blockOrderIndexSchema
|
|
20722
|
+
}).strict();
|
|
20723
|
+
const draftPageBlockSnapshotSchema = z$1.object({
|
|
20724
|
+
kind: z$1.literal("draft-block-content"),
|
|
20725
|
+
blockId: designBlockIdSchema,
|
|
20726
|
+
contentVersionId: blockContentVersionIdSchema.nullable(),
|
|
20727
|
+
identifier: z$1.string().nullable(),
|
|
20728
|
+
blockKind: nonEmptyStringSchema,
|
|
20729
|
+
orderIndex: blockOrderIndexSchema
|
|
20730
|
+
}).strict();
|
|
20731
|
+
const missingPageBlockSnapshotSchema = z$1.object({
|
|
20732
|
+
kind: z$1.literal("missing-block-content"),
|
|
20733
|
+
blockId: designBlockIdSchema,
|
|
20734
|
+
identifier: z$1.string().nullable(),
|
|
20735
|
+
blockKind: nonEmptyStringSchema,
|
|
20736
|
+
orderIndex: blockOrderIndexSchema,
|
|
20737
|
+
reason: z$1.enum([
|
|
20738
|
+
"new-block",
|
|
20739
|
+
"deleted-content-version",
|
|
20740
|
+
"unpublished-block"
|
|
20741
|
+
])
|
|
20742
|
+
}).strict();
|
|
20743
|
+
const pageBlockSnapshotSchema = z$1.discriminatedUnion("kind", [
|
|
20744
|
+
publishedPageBlockSnapshotSchema,
|
|
20745
|
+
draftPageBlockSnapshotSchema,
|
|
20746
|
+
missingPageBlockSnapshotSchema
|
|
20747
|
+
]);
|
|
20748
|
+
const publishedPageBlockSequenceSchema = z$1.array(publishedPageBlockSnapshotSchema).superRefine(validateBlockSequence);
|
|
20749
|
+
const draftPageBlockSequenceSchema = z$1.array(pageBlockSnapshotSchema).superRefine(validateBlockSequence);
|
|
20750
|
+
const publishedPageDesignVersionDataV1Schema = z$1.object({
|
|
20751
|
+
schemaVersion: z$1.literal(1),
|
|
20752
|
+
stage: z$1.literal("published"),
|
|
20753
|
+
pageId: designPageIdSchema,
|
|
20754
|
+
blockSequence: publishedPageBlockSequenceSchema
|
|
20755
|
+
}).strict();
|
|
20756
|
+
const draftPageDesignVersionDataV1Schema = z$1.object({
|
|
20757
|
+
schemaVersion: z$1.literal(1),
|
|
20758
|
+
stage: z$1.literal("draft"),
|
|
20759
|
+
pageId: designPageIdSchema,
|
|
20760
|
+
blockSequence: draftPageBlockSequenceSchema
|
|
20761
|
+
}).strict();
|
|
20762
|
+
z$1.discriminatedUnion("stage", [publishedPageDesignVersionDataV1Schema, draftPageDesignVersionDataV1Schema]);
|
|
20763
|
+
z$1.discriminatedUnion("kind", [
|
|
20764
|
+
z$1.object({
|
|
20765
|
+
kind: z$1.literal("block"),
|
|
20766
|
+
blockId: designBlockIdSchema
|
|
20767
|
+
}).strict(),
|
|
20768
|
+
z$1.object({
|
|
20769
|
+
kind: z$1.literal("entry"),
|
|
20770
|
+
entryId: designEntryIdSchema
|
|
20771
|
+
}).strict(),
|
|
20772
|
+
z$1.object({
|
|
20773
|
+
kind: z$1.literal("page"),
|
|
20774
|
+
pageId: designPageIdSchema
|
|
20775
|
+
}).strict()
|
|
20776
|
+
]);
|
|
20777
|
+
function validateBlockSequence(snapshots, ctx) {
|
|
20778
|
+
for (const issue of collectBlockSequenceIssues(snapshots)) ctx.addIssue(issue);
|
|
20779
|
+
}
|
|
20780
|
+
function collectBlockSequenceIssues(snapshots) {
|
|
20781
|
+
const issues = [];
|
|
20782
|
+
const seenBlockIds = /* @__PURE__ */ new Set();
|
|
20783
|
+
const seenOrderIndexes = /* @__PURE__ */ new Set();
|
|
20784
|
+
let previousOrderIndex = null;
|
|
20785
|
+
snapshots.forEach((snapshot, index) => {
|
|
20786
|
+
if (seenBlockIds.has(snapshot.blockId)) issues.push({
|
|
20787
|
+
code: z$1.ZodIssueCode.custom,
|
|
20788
|
+
message: `Duplicate blockId in page design blockSequence: ${snapshot.blockId}`,
|
|
20789
|
+
path: [index, "blockId"]
|
|
20790
|
+
});
|
|
20791
|
+
seenBlockIds.add(snapshot.blockId);
|
|
20792
|
+
const orderIndex = snapshot.orderIndex;
|
|
20793
|
+
if (seenOrderIndexes.has(orderIndex)) issues.push({
|
|
20794
|
+
code: z$1.ZodIssueCode.custom,
|
|
20795
|
+
message: `Duplicate orderIndex in page design blockSequence: ${String(orderIndex)}`,
|
|
20796
|
+
path: [index, "orderIndex"]
|
|
20797
|
+
});
|
|
20798
|
+
seenOrderIndexes.add(orderIndex);
|
|
20799
|
+
if (previousOrderIndex !== null && orderIndex <= previousOrderIndex) issues.push({
|
|
20800
|
+
code: z$1.ZodIssueCode.custom,
|
|
20801
|
+
message: `Expected orderIndex greater than ${String(previousOrderIndex)} for blockSequence position ${String(index)}; received ${String(orderIndex)}.`,
|
|
20802
|
+
path: [index, "orderIndex"]
|
|
20803
|
+
});
|
|
20804
|
+
previousOrderIndex = orderIndex;
|
|
20805
|
+
});
|
|
20806
|
+
return issues;
|
|
20807
|
+
}
|
|
20808
|
+
//#endregion
|
|
20557
20809
|
//#region ../theme-core/src/site-styles/tokenRecipes.ts
|
|
20558
20810
|
const themeTokenRecipeOptions = {
|
|
20559
20811
|
palette: [
|
|
@@ -32377,7 +32629,7 @@ const eventDisplayFields = [
|
|
|
32377
32629
|
{
|
|
32378
32630
|
id: "showCapacity",
|
|
32379
32631
|
type: "boolean",
|
|
32380
|
-
label: "Show available
|
|
32632
|
+
label: "Show available places",
|
|
32381
32633
|
description: "Display remaining capacity",
|
|
32382
32634
|
required: false,
|
|
32383
32635
|
defaultValue: true,
|
|
@@ -32702,7 +32954,272 @@ function planEventRegistrationScope(input) {
|
|
|
32702
32954
|
};
|
|
32703
32955
|
}
|
|
32704
32956
|
//#endregion
|
|
32705
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
32957
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
|
|
32958
|
+
/**
|
|
32959
|
+
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
32960
|
+
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
32961
|
+
* for uncapped events so call sites can render no capacity indicator at all.
|
|
32962
|
+
*
|
|
32963
|
+
* The single source of truth for capacity math across event cards, agenda
|
|
32964
|
+
* rows, and timetable cells — keep wording variations in the call sites.
|
|
32965
|
+
*/
|
|
32966
|
+
function getEventAvailability(event) {
|
|
32967
|
+
if (event.capacity == null) return {
|
|
32968
|
+
available: null,
|
|
32969
|
+
isFull: false
|
|
32970
|
+
};
|
|
32971
|
+
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
32972
|
+
if (available <= 0) return {
|
|
32973
|
+
available: 0,
|
|
32974
|
+
isFull: true
|
|
32975
|
+
};
|
|
32976
|
+
return {
|
|
32977
|
+
available,
|
|
32978
|
+
isFull: false
|
|
32979
|
+
};
|
|
32980
|
+
}
|
|
32981
|
+
/**
|
|
32982
|
+
* Pick the capacity row a booking decision should read: the selected
|
|
32983
|
+
* attendance mode's availability when the visitor has chosen one and the
|
|
32984
|
+
* event publishes it, otherwise the event-level aggregate.
|
|
32985
|
+
*
|
|
32986
|
+
* The mode row is used wholesale. A mode with `capacity: null` on a capped
|
|
32987
|
+
* event is uncapped *by design*: the registration write path applies the
|
|
32988
|
+
* same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
|
|
32989
|
+
* back to the event's `availableSpots` here would block bookings the server
|
|
32990
|
+
* accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
|
|
32991
|
+
*/
|
|
32992
|
+
function selectEventCapacitySource(event, attendanceMode) {
|
|
32993
|
+
if (!attendanceMode) return event;
|
|
32994
|
+
return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
|
|
32995
|
+
}
|
|
32996
|
+
/**
|
|
32997
|
+
* Whether the visitor's next step is the waitlist: the source is full *and*
|
|
32998
|
+
* a waitlist is open. The one place this is decided — cards, rows, the
|
|
32999
|
+
* wizard's steps and its action bar all route through here so they agree,
|
|
33000
|
+
* including for producers that omit `availableSpots` and rely on the
|
|
33001
|
+
* `capacity - registeredCount` fallback.
|
|
33002
|
+
*/
|
|
33003
|
+
function canJoinEventWaitlist(event, options) {
|
|
33004
|
+
const source = selectEventCapacitySource(event, options?.attendanceMode);
|
|
33005
|
+
return getEventAvailability(source).isFull && source.waitlistEnabled === true;
|
|
33006
|
+
}
|
|
33007
|
+
/**
|
|
33008
|
+
* Remaining places at or below which an event reads as scarce and the exact
|
|
33009
|
+
* count is worth showing. Above it, no number is shown at all: "147 places
|
|
33010
|
+
* left" is not something a visitor acts on, and it is the figure most likely
|
|
33011
|
+
* to be stale on a cached page.
|
|
33012
|
+
*
|
|
33013
|
+
* max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
|
|
33014
|
+
*
|
|
33015
|
+
* The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
|
|
33016
|
+
* 5% floor stops large events staying silent until three remain.
|
|
33017
|
+
* capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
|
|
33018
|
+
*/
|
|
33019
|
+
function lowCapacityThreshold(capacity) {
|
|
33020
|
+
return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
|
|
33021
|
+
}
|
|
33022
|
+
function getRemainingPlaces(source) {
|
|
33023
|
+
const { available, isFull } = getEventAvailability(source);
|
|
33024
|
+
if (available === null || source.capacity === null) return { kind: "unlimited" };
|
|
33025
|
+
if (isFull) return {
|
|
33026
|
+
kind: "full",
|
|
33027
|
+
waitlist: canJoinEventWaitlist(source)
|
|
33028
|
+
};
|
|
33029
|
+
if (available <= lowCapacityThreshold(source.capacity)) return {
|
|
33030
|
+
kind: "low",
|
|
33031
|
+
remaining: available
|
|
33032
|
+
};
|
|
33033
|
+
return { kind: "open" };
|
|
33034
|
+
}
|
|
33035
|
+
/**
|
|
33036
|
+
* The one place a remaining-places count becomes text. Returns `null` when
|
|
33037
|
+
* nothing should be shown: uncapped events, and capped events above the
|
|
33038
|
+
* scarcity threshold. Surfaces that need their own full-state wording pass
|
|
33039
|
+
* it in rather than re-deriving the count.
|
|
33040
|
+
*/
|
|
33041
|
+
function formatRemainingPlaces(source, wording = {}) {
|
|
33042
|
+
const remaining = getRemainingPlaces(source);
|
|
33043
|
+
switch (remaining.kind) {
|
|
33044
|
+
case "unlimited":
|
|
33045
|
+
case "open": return null;
|
|
33046
|
+
case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
|
|
33047
|
+
case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
|
|
33048
|
+
}
|
|
33049
|
+
}
|
|
33050
|
+
function formatPlacesLeft(count, compact) {
|
|
33051
|
+
if (compact) return `${count} left`;
|
|
33052
|
+
return count === 1 ? "1 place left" : `${count} places left`;
|
|
33053
|
+
}
|
|
33054
|
+
/** Total capacity as copy ("12 places"), for detail surfaces that state the size of an event. */
|
|
33055
|
+
function formatTotalCapacity(capacity) {
|
|
33056
|
+
if (capacity === null) return null;
|
|
33057
|
+
return capacity === 1 ? "1 place" : `${capacity} places`;
|
|
33058
|
+
}
|
|
33059
|
+
function getEventAvailabilityStatus(event) {
|
|
33060
|
+
const source = selectMostOpenCapacitySource(event);
|
|
33061
|
+
const remaining = getRemainingPlaces(source);
|
|
33062
|
+
const label = formatEventRemainingPlaces(event, { compact: true });
|
|
33063
|
+
switch (remaining.kind) {
|
|
33064
|
+
case "unlimited": return {
|
|
33065
|
+
status: "available",
|
|
33066
|
+
available: null,
|
|
33067
|
+
isFull: false,
|
|
33068
|
+
label
|
|
33069
|
+
};
|
|
33070
|
+
case "open": return {
|
|
33071
|
+
status: "available",
|
|
33072
|
+
available: getEventAvailability(source).available,
|
|
33073
|
+
isFull: false,
|
|
33074
|
+
label
|
|
33075
|
+
};
|
|
33076
|
+
case "low": return {
|
|
33077
|
+
status: "filling",
|
|
33078
|
+
available: remaining.remaining,
|
|
33079
|
+
isFull: false,
|
|
33080
|
+
label
|
|
33081
|
+
};
|
|
33082
|
+
case "full": return {
|
|
33083
|
+
status: "waitlist",
|
|
33084
|
+
available: 0,
|
|
33085
|
+
isFull: true,
|
|
33086
|
+
label
|
|
33087
|
+
};
|
|
33088
|
+
}
|
|
33089
|
+
}
|
|
33090
|
+
/**
|
|
33091
|
+
* Which capacity figure wins when they disagree.
|
|
33092
|
+
*
|
|
33093
|
+
* For a hybrid event that publishes both attendance-mode rows, **the mode
|
|
33094
|
+
* rows are authoritative and the event-level aggregate is only a summary**.
|
|
33095
|
+
* The registration write path admits or refuses per mode
|
|
33096
|
+
* (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
|
|
33097
|
+
* aggregate can read full while a mode still has room, and can read open
|
|
33098
|
+
* while the mode the visitor wants is full. Believing the aggregate on a
|
|
33099
|
+
* card is how a "places available" event turns into a dead end at step two.
|
|
33100
|
+
*
|
|
33101
|
+
* Everything else — single-mode events, events that publish no rows, and the
|
|
33102
|
+
* aggregate-only helpers {@link getEventAvailability} /
|
|
33103
|
+
* {@link getRemainingPlaces} that existing callers rely on — keeps reading
|
|
33104
|
+
* the aggregate, unchanged.
|
|
33105
|
+
*/
|
|
33106
|
+
function findHybridModeRows(event) {
|
|
33107
|
+
if (event.deliveryMode !== "hybrid") return null;
|
|
33108
|
+
const rows = event.attendanceModes ?? [];
|
|
33109
|
+
const inPerson = rows.find((row) => row.mode === "in_person");
|
|
33110
|
+
const online = rows.find((row) => row.mode === "online");
|
|
33111
|
+
return inPerson && online ? {
|
|
33112
|
+
inPerson,
|
|
33113
|
+
online
|
|
33114
|
+
} : null;
|
|
33115
|
+
}
|
|
33116
|
+
/** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
|
|
33117
|
+
function remainingPlacesRank(remaining) {
|
|
33118
|
+
switch (remaining.kind) {
|
|
33119
|
+
case "unlimited": return 4;
|
|
33120
|
+
case "open": return 3;
|
|
33121
|
+
case "low": return 2;
|
|
33122
|
+
case "full": return remaining.waitlist ? 1 : 0;
|
|
33123
|
+
}
|
|
33124
|
+
}
|
|
33125
|
+
function isMoreOpen(candidate, than) {
|
|
33126
|
+
const rank = remainingPlacesRank(candidate);
|
|
33127
|
+
const otherRank = remainingPlacesRank(than);
|
|
33128
|
+
if (rank !== otherRank) return rank > otherRank;
|
|
33129
|
+
return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
|
|
33130
|
+
}
|
|
33131
|
+
/** True when `candidate` has less room left than `than`. */
|
|
33132
|
+
function isScarcer(candidate, than) {
|
|
33133
|
+
return isMoreOpen(than, candidate);
|
|
33134
|
+
}
|
|
33135
|
+
/**
|
|
33136
|
+
* The capacity row a whole-event decision should read: the most open
|
|
33137
|
+
* attendance mode of a hybrid event, otherwise the aggregate. A hybrid event
|
|
33138
|
+
* is sold out only when both of its modes are, and its waitlist is reachable
|
|
33139
|
+
* when either full mode has one open.
|
|
33140
|
+
*/
|
|
33141
|
+
function selectMostOpenCapacitySource(event) {
|
|
33142
|
+
const rows = findHybridModeRows(event);
|
|
33143
|
+
if (!rows) return event;
|
|
33144
|
+
return isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? rows.online : rows.inPerson;
|
|
33145
|
+
}
|
|
33146
|
+
/** True when no attendance mode has room. Hybrid-aware; use this for sold-out decisions. */
|
|
33147
|
+
function isEventFull(event) {
|
|
33148
|
+
return getEventAvailability(selectMostOpenCapacitySource(event)).isFull;
|
|
33149
|
+
}
|
|
33150
|
+
/**
|
|
33151
|
+
* True when the event is full on every mode and at least one of those full
|
|
33152
|
+
* modes has a waitlist open. An event with a mode still bookable is not a
|
|
33153
|
+
* waitlist prospect: it should send the visitor to that mode instead.
|
|
33154
|
+
*/
|
|
33155
|
+
function canJoinEventWaitlistOnAnyMode(event) {
|
|
33156
|
+
return canJoinEventWaitlist(selectMostOpenCapacitySource(event));
|
|
33157
|
+
}
|
|
33158
|
+
const ATTENDANCE_MODE_LABEL = {
|
|
33159
|
+
in_person: "In person",
|
|
33160
|
+
online: "Online"
|
|
33161
|
+
};
|
|
33162
|
+
/** Wording for a mode with room to spare, where the count is deliberately withheld. */
|
|
33163
|
+
const PLACES_AVAILABLE = "Places available";
|
|
33164
|
+
/**
|
|
33165
|
+
* The browse-surface label for an event's remaining places. Single-mode
|
|
33166
|
+
* events get exactly the aggregate label {@link formatRemainingPlaces}
|
|
33167
|
+
* produces, and so does a hybrid event whose two modes tell the same story.
|
|
33168
|
+
* When the modes differ, both sides are named: "available" on a card is a
|
|
33169
|
+
* promise the registration wizard would otherwise have to break.
|
|
33170
|
+
*
|
|
33171
|
+
* Hybrid examples, in person full with 3 online places left:
|
|
33172
|
+
* default -> "Online: 3 places left · In person: Full"
|
|
33173
|
+
* { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
|
|
33174
|
+
* { compact: true } -> "Online: 3 left"
|
|
33175
|
+
*/
|
|
33176
|
+
function formatEventRemainingPlaces(event, wording = {}) {
|
|
33177
|
+
const rows = findHybridModeRows(event);
|
|
33178
|
+
if (!rows) return formatRemainingPlaces(event, wording);
|
|
33179
|
+
const inPerson = formatRemainingPlaces(rows.inPerson, wording);
|
|
33180
|
+
const online = formatRemainingPlaces(rows.online, wording);
|
|
33181
|
+
if (inPerson === online) return inPerson;
|
|
33182
|
+
if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
|
|
33183
|
+
return formatBothHybridSides(rows, {
|
|
33184
|
+
inPerson,
|
|
33185
|
+
online
|
|
33186
|
+
});
|
|
33187
|
+
}
|
|
33188
|
+
/**
|
|
33189
|
+
* Both sides, the one with room first: that is the half a visitor can act on,
|
|
33190
|
+
* and it should not arrive after a full stop's worth of bad news.
|
|
33191
|
+
*/
|
|
33192
|
+
function formatBothHybridSides(rows, labels) {
|
|
33193
|
+
return (isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? [["online", labels.online], ["in_person", labels.inPerson]] : [["in_person", labels.inPerson], ["online", labels.online]]).map(([mode, label]) => namedModePlaces(mode, label)).join(" · ");
|
|
33194
|
+
}
|
|
33195
|
+
function namedModePlaces(mode, label) {
|
|
33196
|
+
return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
|
|
33197
|
+
}
|
|
33198
|
+
/**
|
|
33199
|
+
* Pills and timetable cells have room for one short phrase, so they name the
|
|
33200
|
+
* single mode that carries news rather than listing both: the scarce side
|
|
33201
|
+
* when there is a count to show, and otherwise the side that has closed —
|
|
33202
|
+
* "In person: Full", never "Online only", which reads as a statement about
|
|
33203
|
+
* how the event is delivered rather than about its remaining places.
|
|
33204
|
+
*/
|
|
33205
|
+
function formatCompactHybridPlaces(rows, wording) {
|
|
33206
|
+
const inPerson = getRemainingPlaces(rows.inPerson);
|
|
33207
|
+
const online = getRemainingPlaces(rows.online);
|
|
33208
|
+
if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
|
|
33209
|
+
if (inPerson.kind === "full" || online.kind === "full") {
|
|
33210
|
+
const fullSide = inPerson.kind === "full" ? "in_person" : "online";
|
|
33211
|
+
const openSide = inPerson.kind === "full" ? "online" : "in_person";
|
|
33212
|
+
const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
|
|
33213
|
+
return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
|
|
33214
|
+
}
|
|
33215
|
+
const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
|
|
33216
|
+
return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
|
|
33217
|
+
}
|
|
33218
|
+
function rowFor(rows, mode) {
|
|
33219
|
+
return mode === "online" ? rows.online : rows.inPerson;
|
|
33220
|
+
}
|
|
33221
|
+
//#endregion
|
|
33222
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
|
|
32706
33223
|
function isRecord$24(value) {
|
|
32707
33224
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32708
33225
|
}
|
|
@@ -32720,7 +33237,701 @@ function resolveRuntimeEventDestination(event) {
|
|
|
32720
33237
|
};
|
|
32721
33238
|
}
|
|
32722
33239
|
//#endregion
|
|
32723
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
33240
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/cta.ts
|
|
33241
|
+
/**
|
|
33242
|
+
* Booking call-to-action resolution for events: where an event links to and
|
|
33243
|
+
* what the button says. Sold-out and waitlist decisions come from capacity.ts
|
|
33244
|
+
* so cards, rows and the wizard never disagree.
|
|
33245
|
+
*/
|
|
33246
|
+
function closedEventBookingLabel(reason) {
|
|
33247
|
+
switch (reason) {
|
|
33248
|
+
case "occurrence_cancelled":
|
|
33249
|
+
case "series_cancelled": return "Cancelled";
|
|
33250
|
+
case "occurrence_completed":
|
|
33251
|
+
case "series_completed": return "Completed";
|
|
33252
|
+
case "series_draft":
|
|
33253
|
+
case "series_archived":
|
|
33254
|
+
case "parent_archived":
|
|
33255
|
+
case "parent_inconsistent":
|
|
33256
|
+
case "not_public":
|
|
33257
|
+
case "already_started":
|
|
33258
|
+
case "enrollment_closed": return "Registration closed";
|
|
33259
|
+
default: return assertNever$23(reason);
|
|
33260
|
+
}
|
|
33261
|
+
}
|
|
33262
|
+
function resolveEventHref(event) {
|
|
33263
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
33264
|
+
return destination.kind === "external" ? destination.url : destination.path;
|
|
33265
|
+
}
|
|
33266
|
+
function resolveEventLinkProps(event) {
|
|
33267
|
+
const href = resolveEventHref(event);
|
|
33268
|
+
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
33269
|
+
href,
|
|
33270
|
+
target: "_blank",
|
|
33271
|
+
rel: "noopener noreferrer"
|
|
33272
|
+
} : { href };
|
|
33273
|
+
}
|
|
33274
|
+
/**
|
|
33275
|
+
* Resolve the CTA state for an event.
|
|
33276
|
+
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
33277
|
+
* and course-specific defaults consistent.
|
|
33278
|
+
*/
|
|
33279
|
+
function resolveEventCta(event, buttonText) {
|
|
33280
|
+
const isSoldOut = isEventFull(event);
|
|
33281
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
33282
|
+
const isExternal = destination.kind === "external";
|
|
33283
|
+
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
33284
|
+
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
33285
|
+
const href = resolveEventHref(event);
|
|
33286
|
+
if (event.bookingAvailability.kind === "closed") {
|
|
33287
|
+
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
33288
|
+
return {
|
|
33289
|
+
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
33290
|
+
href,
|
|
33291
|
+
external: isExternal,
|
|
33292
|
+
hidden: !linksToClosedWholeSeriesDetails
|
|
33293
|
+
};
|
|
33294
|
+
}
|
|
33295
|
+
if (!isExternal && isSoldOut) {
|
|
33296
|
+
if (canJoinEventWaitlistOnAnyMode(event)) return {
|
|
33297
|
+
label: "Join waitlist",
|
|
33298
|
+
href,
|
|
33299
|
+
external: false,
|
|
33300
|
+
hidden: false
|
|
33301
|
+
};
|
|
33302
|
+
return {
|
|
33303
|
+
label: "Sold out",
|
|
33304
|
+
href,
|
|
33305
|
+
external: false,
|
|
33306
|
+
hidden: true
|
|
33307
|
+
};
|
|
33308
|
+
}
|
|
33309
|
+
return {
|
|
33310
|
+
label: effectiveButtonText,
|
|
33311
|
+
href,
|
|
33312
|
+
external: isExternal,
|
|
33313
|
+
hidden: false
|
|
33314
|
+
};
|
|
33315
|
+
}
|
|
33316
|
+
//#endregion
|
|
33317
|
+
//#region ../calendar/src/grid.ts
|
|
33318
|
+
const DAY_NAMES = [
|
|
33319
|
+
"Sun",
|
|
33320
|
+
"Mon",
|
|
33321
|
+
"Tue",
|
|
33322
|
+
"Wed",
|
|
33323
|
+
"Thu",
|
|
33324
|
+
"Fri",
|
|
33325
|
+
"Sat"
|
|
33326
|
+
];
|
|
33327
|
+
const MONTH_NAMES = [
|
|
33328
|
+
"January",
|
|
33329
|
+
"February",
|
|
33330
|
+
"March",
|
|
33331
|
+
"April",
|
|
33332
|
+
"May",
|
|
33333
|
+
"June",
|
|
33334
|
+
"July",
|
|
33335
|
+
"August",
|
|
33336
|
+
"September",
|
|
33337
|
+
"October",
|
|
33338
|
+
"November",
|
|
33339
|
+
"December"
|
|
33340
|
+
];
|
|
33341
|
+
function formatDateKey(date) {
|
|
33342
|
+
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
33343
|
+
}
|
|
33344
|
+
function isToday(date, today) {
|
|
33345
|
+
return isSameCalendarDay(date, today);
|
|
33346
|
+
}
|
|
33347
|
+
function isSameCalendarDay(left, right) {
|
|
33348
|
+
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
33349
|
+
}
|
|
33350
|
+
function addDays(date, days) {
|
|
33351
|
+
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
33352
|
+
return {
|
|
33353
|
+
year: result.getUTCFullYear(),
|
|
33354
|
+
month: result.getUTCMonth() + 1,
|
|
33355
|
+
day: result.getUTCDate()
|
|
33356
|
+
};
|
|
33357
|
+
}
|
|
33358
|
+
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
33359
|
+
const firstDay = {
|
|
33360
|
+
year,
|
|
33361
|
+
month,
|
|
33362
|
+
day: 1
|
|
33363
|
+
};
|
|
33364
|
+
const dayOfWeek = getDayOfWeek(firstDay);
|
|
33365
|
+
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33366
|
+
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
33367
|
+
}
|
|
33368
|
+
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
33369
|
+
const dayOfWeek = getDayOfWeek(baseDate);
|
|
33370
|
+
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33371
|
+
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
33372
|
+
}
|
|
33373
|
+
function getDayOfWeek(date) {
|
|
33374
|
+
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
33375
|
+
}
|
|
33376
|
+
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
33377
|
+
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
33378
|
+
assertWeekCalendarDays(days);
|
|
33379
|
+
return days;
|
|
33380
|
+
}
|
|
33381
|
+
function assertWeekCalendarDays(days) {
|
|
33382
|
+
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
33383
|
+
}
|
|
33384
|
+
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
33385
|
+
if (startOfWeek === "monday") {
|
|
33386
|
+
const sunday = dayNames[0] ?? "Sun";
|
|
33387
|
+
return [...dayNames.slice(1), sunday];
|
|
33388
|
+
}
|
|
33389
|
+
return [...dayNames];
|
|
33390
|
+
}
|
|
33391
|
+
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
33392
|
+
if (view === "week") {
|
|
33393
|
+
const weekStart = days[0];
|
|
33394
|
+
const weekEnd = days[6];
|
|
33395
|
+
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33396
|
+
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
33397
|
+
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
33398
|
+
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
33399
|
+
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
33400
|
+
}
|
|
33401
|
+
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33402
|
+
}
|
|
33403
|
+
function buildCalendarViewport(input) {
|
|
33404
|
+
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
33405
|
+
if (input.view === "week") {
|
|
33406
|
+
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
33407
|
+
return {
|
|
33408
|
+
kind: "week",
|
|
33409
|
+
anchorDate: input.anchorDate,
|
|
33410
|
+
days,
|
|
33411
|
+
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
33412
|
+
};
|
|
33413
|
+
}
|
|
33414
|
+
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
33415
|
+
return {
|
|
33416
|
+
kind: "month",
|
|
33417
|
+
anchorDate: input.anchorDate,
|
|
33418
|
+
days,
|
|
33419
|
+
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
33420
|
+
};
|
|
33421
|
+
}
|
|
33422
|
+
//#endregion
|
|
33423
|
+
//#region ../calendar/src/lane-packing.ts
|
|
33424
|
+
/**
|
|
33425
|
+
* Assign lanes to a list of intervals.
|
|
33426
|
+
*
|
|
33427
|
+
* Returns one result per input in the same order as the input. Internally
|
|
33428
|
+
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
33429
|
+
* permutes results back to input order.
|
|
33430
|
+
*/
|
|
33431
|
+
function assignLanes(intervals) {
|
|
33432
|
+
if (intervals.length === 0) return [];
|
|
33433
|
+
const indexed = intervals.map((interval, index) => ({
|
|
33434
|
+
interval,
|
|
33435
|
+
index
|
|
33436
|
+
}));
|
|
33437
|
+
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
33438
|
+
const results = new Array(intervals.length);
|
|
33439
|
+
let cluster = [];
|
|
33440
|
+
let activeLaneEnds = [];
|
|
33441
|
+
let clusterMaxEnd = -Infinity;
|
|
33442
|
+
const closeCluster = () => {
|
|
33443
|
+
if (cluster.length === 0) return;
|
|
33444
|
+
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
33445
|
+
for (const entry of cluster) results[entry.index] = {
|
|
33446
|
+
laneIndex: entry.laneIndex,
|
|
33447
|
+
laneCount
|
|
33448
|
+
};
|
|
33449
|
+
cluster = [];
|
|
33450
|
+
activeLaneEnds = [];
|
|
33451
|
+
clusterMaxEnd = -Infinity;
|
|
33452
|
+
};
|
|
33453
|
+
for (const { interval, index } of indexed) {
|
|
33454
|
+
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
33455
|
+
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
33456
|
+
activeLaneEnds[laneIndex] = interval.end;
|
|
33457
|
+
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
33458
|
+
cluster.push({
|
|
33459
|
+
index,
|
|
33460
|
+
laneIndex
|
|
33461
|
+
});
|
|
33462
|
+
}
|
|
33463
|
+
closeCluster();
|
|
33464
|
+
return results;
|
|
33465
|
+
}
|
|
33466
|
+
function firstAvailableLane(activeLaneEnds, start) {
|
|
33467
|
+
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
33468
|
+
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
33469
|
+
}
|
|
33470
|
+
//#endregion
|
|
33471
|
+
//#region ../calendar/src/timezone.ts
|
|
33472
|
+
/**
|
|
33473
|
+
* Timezone boundary helpers — internal.
|
|
33474
|
+
*
|
|
33475
|
+
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
33476
|
+
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
33477
|
+
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
33478
|
+
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
33479
|
+
* public surface.
|
|
33480
|
+
*/
|
|
33481
|
+
const MINUTES_PER_HOUR = 60;
|
|
33482
|
+
/**
|
|
33483
|
+
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
33484
|
+
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
33485
|
+
*/
|
|
33486
|
+
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
33487
|
+
const date = new Date(isoTimestamp);
|
|
33488
|
+
if (Number.isNaN(date.getTime())) return NaN;
|
|
33489
|
+
if (!isValidTimeZone(timeZone)) return NaN;
|
|
33490
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
33491
|
+
timeZone,
|
|
33492
|
+
hour: "2-digit",
|
|
33493
|
+
minute: "2-digit",
|
|
33494
|
+
hourCycle: "h23"
|
|
33495
|
+
}).formatToParts(date);
|
|
33496
|
+
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
33497
|
+
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
33498
|
+
const hour = hourPart === 24 ? 0 : hourPart;
|
|
33499
|
+
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
33500
|
+
return hour * MINUTES_PER_HOUR + minute;
|
|
33501
|
+
}
|
|
33502
|
+
/**
|
|
33503
|
+
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
33504
|
+
* Returns null if the inputs cannot be parsed.
|
|
33505
|
+
*/
|
|
33506
|
+
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
33507
|
+
const date = new Date(isoTimestamp);
|
|
33508
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
33509
|
+
if (!isValidTimeZone(timeZone)) return null;
|
|
33510
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
33511
|
+
timeZone,
|
|
33512
|
+
year: "numeric",
|
|
33513
|
+
month: "2-digit",
|
|
33514
|
+
day: "2-digit"
|
|
33515
|
+
}).formatToParts(date);
|
|
33516
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
33517
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
33518
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
33519
|
+
if (year === void 0 || year.length === 0) return null;
|
|
33520
|
+
if (month === void 0 || month.length === 0) return null;
|
|
33521
|
+
if (day === void 0 || day.length === 0) return null;
|
|
33522
|
+
return `${year}-${month}-${day}`;
|
|
33523
|
+
}
|
|
33524
|
+
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
33525
|
+
function isValidTimeZone(timeZone) {
|
|
33526
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
33527
|
+
const cached = validTimeZoneCache.get(timeZone);
|
|
33528
|
+
if (cached !== void 0) return cached;
|
|
33529
|
+
try {
|
|
33530
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
33531
|
+
validTimeZoneCache.set(timeZone, true);
|
|
33532
|
+
return true;
|
|
33533
|
+
} catch {
|
|
33534
|
+
validTimeZoneCache.set(timeZone, false);
|
|
33535
|
+
return false;
|
|
33536
|
+
}
|
|
33537
|
+
}
|
|
33538
|
+
//#endregion
|
|
33539
|
+
//#region ../calendar/src/timetable.ts
|
|
33540
|
+
/**
|
|
33541
|
+
* Timetable model — the package's core surface.
|
|
33542
|
+
*
|
|
33543
|
+
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
33544
|
+
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
33545
|
+
* plus a diagnostics list for items dropped during processing.
|
|
33546
|
+
*
|
|
33547
|
+
* The function is pure: given identical input (including `now`), it returns
|
|
33548
|
+
* the same output. The model has no opinion on what an item is — pass
|
|
33549
|
+
* accessors.
|
|
33550
|
+
*/
|
|
33551
|
+
const DEFAULT_BOUNDS = {
|
|
33552
|
+
minStartHour: 5,
|
|
33553
|
+
maxEndHour: 23,
|
|
33554
|
+
defaultStartHour: 6,
|
|
33555
|
+
defaultEndHour: 21,
|
|
33556
|
+
minHeightPercent: 2.5
|
|
33557
|
+
};
|
|
33558
|
+
function buildTimetableModel$1(input) {
|
|
33559
|
+
const bounds = resolveBounds(input.bounds);
|
|
33560
|
+
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
33561
|
+
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
33562
|
+
const parsed = parseAllItems(input.items, input);
|
|
33563
|
+
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
33564
|
+
const hourRange = resolveHourRange(inViewport, bounds);
|
|
33565
|
+
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
33566
|
+
return {
|
|
33567
|
+
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
33568
|
+
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
33569
|
+
startHour: hourRange.startHour,
|
|
33570
|
+
endHour: hourRange.endHour,
|
|
33571
|
+
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
33572
|
+
hasItems: inViewport.length > 0,
|
|
33573
|
+
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
33574
|
+
};
|
|
33575
|
+
}
|
|
33576
|
+
function parseAllItems(items, accessors) {
|
|
33577
|
+
const drafts = [];
|
|
33578
|
+
const diagnostics = [];
|
|
33579
|
+
for (const item of items) {
|
|
33580
|
+
const result = parseDraft(item, accessors);
|
|
33581
|
+
if (result.ok) drafts.push(result.draft);
|
|
33582
|
+
else diagnostics.push({
|
|
33583
|
+
item,
|
|
33584
|
+
reason: result.reason
|
|
33585
|
+
});
|
|
33586
|
+
}
|
|
33587
|
+
return {
|
|
33588
|
+
drafts,
|
|
33589
|
+
diagnostics
|
|
33590
|
+
};
|
|
33591
|
+
}
|
|
33592
|
+
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
33593
|
+
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
33594
|
+
const rangeStartMinutes = hourRange.startHour * 60;
|
|
33595
|
+
const rangeEndMinutes = hourRange.endHour * 60;
|
|
33596
|
+
const segmentsByDate = groupSegmentsByDate(segments);
|
|
33597
|
+
const cellsByDate = /* @__PURE__ */ new Map();
|
|
33598
|
+
const diagnostics = [];
|
|
33599
|
+
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
33600
|
+
const clipped = [];
|
|
33601
|
+
for (const segment of daySegments) {
|
|
33602
|
+
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
33603
|
+
if (result.ok) clipped.push(result.clipped);
|
|
33604
|
+
else diagnostics.push({
|
|
33605
|
+
item: segment.draft.item,
|
|
33606
|
+
reason: result.reason
|
|
33607
|
+
});
|
|
33608
|
+
}
|
|
33609
|
+
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
33610
|
+
start: entry.visibleStart,
|
|
33611
|
+
end: entry.visibleEnd,
|
|
33612
|
+
orderKey: entry.segment.draft.orderKey
|
|
33613
|
+
})));
|
|
33614
|
+
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
33615
|
+
laneIndex: 0,
|
|
33616
|
+
laneCount: 1
|
|
33617
|
+
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
33618
|
+
}
|
|
33619
|
+
return {
|
|
33620
|
+
cellsByDate,
|
|
33621
|
+
diagnostics
|
|
33622
|
+
};
|
|
33623
|
+
}
|
|
33624
|
+
function assembleDays(viewportDays, cellsByDate, today) {
|
|
33625
|
+
return viewportDays.map((date) => {
|
|
33626
|
+
const dateKey = formatDateKey(date);
|
|
33627
|
+
return {
|
|
33628
|
+
date,
|
|
33629
|
+
dateKey,
|
|
33630
|
+
isToday: isSameCalendarDay(date, today),
|
|
33631
|
+
cells: cellsByDate.get(dateKey) ?? []
|
|
33632
|
+
};
|
|
33633
|
+
});
|
|
33634
|
+
}
|
|
33635
|
+
function parseDraft(item, accessors) {
|
|
33636
|
+
const startsAtIso = accessors.getStartsAt(item);
|
|
33637
|
+
const endsAtIso = accessors.getEndsAt(item);
|
|
33638
|
+
const timeZone = accessors.getTimeZone(item);
|
|
33639
|
+
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
33640
|
+
if (Number.isNaN(startsAtMinutes)) return {
|
|
33641
|
+
ok: false,
|
|
33642
|
+
reason: "invalid-start"
|
|
33643
|
+
};
|
|
33644
|
+
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
33645
|
+
if (startDateKey == null) return {
|
|
33646
|
+
ok: false,
|
|
33647
|
+
reason: "invalid-timezone"
|
|
33648
|
+
};
|
|
33649
|
+
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
33650
|
+
if (endDateKey == null) return {
|
|
33651
|
+
ok: false,
|
|
33652
|
+
reason: "invalid-end"
|
|
33653
|
+
};
|
|
33654
|
+
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
33655
|
+
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
33656
|
+
ok: false,
|
|
33657
|
+
reason: "invalid-end"
|
|
33658
|
+
};
|
|
33659
|
+
const spansMultipleDays = startDateKey < endDateKey;
|
|
33660
|
+
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
33661
|
+
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
33662
|
+
ok: false,
|
|
33663
|
+
reason: "non-positive-duration"
|
|
33664
|
+
};
|
|
33665
|
+
return {
|
|
33666
|
+
ok: true,
|
|
33667
|
+
draft: {
|
|
33668
|
+
item,
|
|
33669
|
+
startDateKey,
|
|
33670
|
+
endDateKey,
|
|
33671
|
+
startsAtMinutes,
|
|
33672
|
+
endsAtMinutes,
|
|
33673
|
+
color: accessors.getColor?.(item) ?? null,
|
|
33674
|
+
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
33675
|
+
}
|
|
33676
|
+
};
|
|
33677
|
+
}
|
|
33678
|
+
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
33679
|
+
const segments = [];
|
|
33680
|
+
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
33681
|
+
if (!viewportKeys.has(dateKey)) continue;
|
|
33682
|
+
const isStartDay = dateKey === draft.startDateKey;
|
|
33683
|
+
const isEndDay = dateKey === draft.endDateKey;
|
|
33684
|
+
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
33685
|
+
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
33686
|
+
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
33687
|
+
segments.push({
|
|
33688
|
+
draft,
|
|
33689
|
+
dateKey,
|
|
33690
|
+
startsAtMinutes,
|
|
33691
|
+
endsAtMinutes,
|
|
33692
|
+
continuesBefore: !isStartDay,
|
|
33693
|
+
continuesAfter: !isEndDay
|
|
33694
|
+
});
|
|
33695
|
+
}
|
|
33696
|
+
return segments;
|
|
33697
|
+
}
|
|
33698
|
+
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
33699
|
+
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
33700
|
+
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
33701
|
+
if (visibleEnd <= visibleStart) return {
|
|
33702
|
+
ok: false,
|
|
33703
|
+
reason: "out-of-visible-hours"
|
|
33704
|
+
};
|
|
33705
|
+
return {
|
|
33706
|
+
ok: true,
|
|
33707
|
+
clipped: {
|
|
33708
|
+
segment,
|
|
33709
|
+
visibleStart,
|
|
33710
|
+
visibleEnd
|
|
33711
|
+
}
|
|
33712
|
+
};
|
|
33713
|
+
}
|
|
33714
|
+
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
33715
|
+
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
33716
|
+
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
33717
|
+
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
33718
|
+
return {
|
|
33719
|
+
item: entry.segment.draft.item,
|
|
33720
|
+
dateKey: entry.segment.dateKey,
|
|
33721
|
+
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
33722
|
+
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
33723
|
+
visibleStartsAtMinutes: entry.visibleStart,
|
|
33724
|
+
visibleEndsAtMinutes: entry.visibleEnd,
|
|
33725
|
+
topPercent,
|
|
33726
|
+
heightPercent,
|
|
33727
|
+
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
33728
|
+
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
33729
|
+
laneIndex: lane.laneIndex,
|
|
33730
|
+
laneCount: lane.laneCount,
|
|
33731
|
+
color: entry.segment.draft.color
|
|
33732
|
+
};
|
|
33733
|
+
}
|
|
33734
|
+
function resolveBounds(bounds) {
|
|
33735
|
+
return {
|
|
33736
|
+
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
33737
|
+
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
33738
|
+
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
33739
|
+
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
33740
|
+
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
33741
|
+
};
|
|
33742
|
+
}
|
|
33743
|
+
function resolveHourRange(segments, bounds) {
|
|
33744
|
+
if (segments.length === 0) return {
|
|
33745
|
+
startHour: bounds.defaultStartHour,
|
|
33746
|
+
endHour: bounds.defaultEndHour
|
|
33747
|
+
};
|
|
33748
|
+
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
33749
|
+
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
33750
|
+
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
33751
|
+
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
33752
|
+
return {
|
|
33753
|
+
startHour,
|
|
33754
|
+
endHour: Math.max(startHour + 1, endHour)
|
|
33755
|
+
};
|
|
33756
|
+
}
|
|
33757
|
+
function buildHourTicks(startHour, endHour) {
|
|
33758
|
+
const ticks = [];
|
|
33759
|
+
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
33760
|
+
return ticks;
|
|
33761
|
+
}
|
|
33762
|
+
function groupSegmentsByDate(segments) {
|
|
33763
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
33764
|
+
for (const segment of segments) {
|
|
33765
|
+
const existing = grouped.get(segment.dateKey) ?? [];
|
|
33766
|
+
existing.push(segment);
|
|
33767
|
+
grouped.set(segment.dateKey, existing);
|
|
33768
|
+
}
|
|
33769
|
+
return grouped;
|
|
33770
|
+
}
|
|
33771
|
+
function toRangePercent(minutes, rangeMinutes) {
|
|
33772
|
+
if (rangeMinutes <= 0) return 0;
|
|
33773
|
+
return minutes / rangeMinutes * 100;
|
|
33774
|
+
}
|
|
33775
|
+
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
33776
|
+
if (view === "day") return [viewDate];
|
|
33777
|
+
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
33778
|
+
}
|
|
33779
|
+
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
33780
|
+
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
33781
|
+
const start = parseDateKey(startDateKey);
|
|
33782
|
+
parseDateKey(endDateKey);
|
|
33783
|
+
const keys = [];
|
|
33784
|
+
let cursor = start;
|
|
33785
|
+
while (formatDateKey(cursor) <= endDateKey) {
|
|
33786
|
+
keys.push(formatDateKey(cursor));
|
|
33787
|
+
cursor = addDays(cursor, 1);
|
|
33788
|
+
}
|
|
33789
|
+
return keys;
|
|
33790
|
+
}
|
|
33791
|
+
function parseDateKey(dateKey) {
|
|
33792
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
33793
|
+
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
33794
|
+
const [, year, month, day] = match;
|
|
33795
|
+
return {
|
|
33796
|
+
year: Number(year),
|
|
33797
|
+
month: Number(month),
|
|
33798
|
+
day: Number(day)
|
|
33799
|
+
};
|
|
33800
|
+
}
|
|
33801
|
+
//#endregion
|
|
33802
|
+
//#region ../calendar/src/group.ts
|
|
33803
|
+
/**
|
|
33804
|
+
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
33805
|
+
*
|
|
33806
|
+
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
33807
|
+
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
33808
|
+
*
|
|
33809
|
+
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
33810
|
+
* relative order of items within each date matches input order.
|
|
33811
|
+
*/
|
|
33812
|
+
function groupItemsByDate(items, accessors) {
|
|
33813
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
33814
|
+
for (const item of items) {
|
|
33815
|
+
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
33816
|
+
if (dateKey == null) continue;
|
|
33817
|
+
const existing = grouped.get(dateKey);
|
|
33818
|
+
if (existing !== void 0) existing.push(item);
|
|
33819
|
+
else grouped.set(dateKey, [item]);
|
|
33820
|
+
}
|
|
33821
|
+
return grouped;
|
|
33822
|
+
}
|
|
33823
|
+
//#endregion
|
|
33824
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
|
|
33825
|
+
/**
|
|
33826
|
+
* Date and time presentation for events: the single place an event's
|
|
33827
|
+
* timestamps become strings or date buckets. Calendar-grid primitives are
|
|
33828
|
+
* re-exported from @riverbankcms/calendar so block code has one import.
|
|
33829
|
+
*/
|
|
33830
|
+
/**
|
|
33831
|
+
* Format date for display.
|
|
33832
|
+
* Returns: "Monday, January 15, 2025"
|
|
33833
|
+
*/
|
|
33834
|
+
function formatDate$2(isoTimestamp, options) {
|
|
33835
|
+
return formatBookingDate(toScheduledBookingDateTime({
|
|
33836
|
+
startsAt: isoTimestamp,
|
|
33837
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33838
|
+
locale: options?.locale ?? "en-US"
|
|
33839
|
+
}), {
|
|
33840
|
+
weekday: "long",
|
|
33841
|
+
year: "numeric",
|
|
33842
|
+
month: "long",
|
|
33843
|
+
day: "numeric"
|
|
33844
|
+
});
|
|
33845
|
+
}
|
|
33846
|
+
/**
|
|
33847
|
+
* Format time for display.
|
|
33848
|
+
* Returns: "10:00 AM"
|
|
33849
|
+
*/
|
|
33850
|
+
function formatTime(isoTimestamp, options) {
|
|
33851
|
+
return formatBookingTime(toScheduledBookingDateTime({
|
|
33852
|
+
startsAt: isoTimestamp,
|
|
33853
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33854
|
+
locale: options?.locale ?? "en-US"
|
|
33855
|
+
}), {
|
|
33856
|
+
hour: "numeric",
|
|
33857
|
+
minute: "2-digit",
|
|
33858
|
+
hour12: true
|
|
33859
|
+
});
|
|
33860
|
+
}
|
|
33861
|
+
function formatEventDateParts(args) {
|
|
33862
|
+
const scheduledRange = toScheduledBookingDateRange({
|
|
33863
|
+
startsAt: args.startsAt,
|
|
33864
|
+
endsAt: args.endsAt,
|
|
33865
|
+
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
33866
|
+
locale: args.locale ?? "en-US"
|
|
33867
|
+
});
|
|
33868
|
+
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
33869
|
+
weekday: "long",
|
|
33870
|
+
year: "numeric",
|
|
33871
|
+
month: "long",
|
|
33872
|
+
day: "numeric"
|
|
33873
|
+
} });
|
|
33874
|
+
return {
|
|
33875
|
+
date,
|
|
33876
|
+
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
33877
|
+
startsAt: args.endsAt,
|
|
33878
|
+
timeZone: scheduledRange.timeZone,
|
|
33879
|
+
locale: args.locale ?? "en-US"
|
|
33880
|
+
}))}`
|
|
33881
|
+
};
|
|
33882
|
+
}
|
|
33883
|
+
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
33884
|
+
const dateTime = toScheduledBookingDateTime({
|
|
33885
|
+
startsAt: isoTimestamp,
|
|
33886
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33887
|
+
locale: options?.locale ?? "en-US"
|
|
33888
|
+
});
|
|
33889
|
+
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33890
|
+
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33891
|
+
return {
|
|
33892
|
+
weekday,
|
|
33893
|
+
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
33894
|
+
month,
|
|
33895
|
+
label: formatBookingDate(dateTime, {
|
|
33896
|
+
weekday: "long",
|
|
33897
|
+
year: "numeric",
|
|
33898
|
+
month: "long",
|
|
33899
|
+
day: "numeric"
|
|
33900
|
+
})
|
|
33901
|
+
};
|
|
33902
|
+
}
|
|
33903
|
+
/**
|
|
33904
|
+
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
33905
|
+
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
33906
|
+
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
33907
|
+
*/
|
|
33908
|
+
function groupEventsByDate(events, calendarTimeZone) {
|
|
33909
|
+
const grouped = groupItemsByDate(events, {
|
|
33910
|
+
getStartsAt: (event) => event.startsAt,
|
|
33911
|
+
getTimeZone: () => calendarTimeZone
|
|
33912
|
+
});
|
|
33913
|
+
const map = /* @__PURE__ */ new Map();
|
|
33914
|
+
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
33915
|
+
return map;
|
|
33916
|
+
}
|
|
33917
|
+
//#endregion
|
|
33918
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/people.ts
|
|
33919
|
+
function formatTeamMemberNames(names) {
|
|
33920
|
+
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
33921
|
+
if (normalized.length === 0) return null;
|
|
33922
|
+
const [firstName, secondName] = normalized;
|
|
33923
|
+
if (!firstName) return null;
|
|
33924
|
+
if (normalized.length === 1) return firstName;
|
|
33925
|
+
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
33926
|
+
const finalName = normalized[normalized.length - 1] ?? "";
|
|
33927
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
33928
|
+
}
|
|
33929
|
+
function formatEventTeacherLine(event) {
|
|
33930
|
+
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
33931
|
+
return names ? `with ${names}` : null;
|
|
33932
|
+
}
|
|
33933
|
+
//#endregion
|
|
33934
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
|
|
32724
33935
|
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
32725
33936
|
function resolveRuntimeEventPricing(event) {
|
|
32726
33937
|
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
@@ -32887,6 +34098,17 @@ function readCoursePriceDisplay(value, currency) {
|
|
|
32887
34098
|
function readAmount(value) {
|
|
32888
34099
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
32889
34100
|
}
|
|
34101
|
+
/**
|
|
34102
|
+
* Format event price for display.
|
|
34103
|
+
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
34104
|
+
*
|
|
34105
|
+
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
34106
|
+
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
34107
|
+
* surfaces stay consistent.
|
|
34108
|
+
*/
|
|
34109
|
+
function formatEventPrice(event) {
|
|
34110
|
+
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
34111
|
+
}
|
|
32890
34112
|
const eventCalendarDisplayModeValues = [
|
|
32891
34113
|
"combined",
|
|
32892
34114
|
"calendar",
|
|
@@ -33411,7 +34633,7 @@ const eventRegistrationBlockDefinition = {
|
|
|
33411
34633
|
{
|
|
33412
34634
|
id: "showCapacity",
|
|
33413
34635
|
type: "boolean",
|
|
33414
|
-
label: "Show available
|
|
34636
|
+
label: "Show available places",
|
|
33415
34637
|
description: "Display remaining capacity",
|
|
33416
34638
|
required: false,
|
|
33417
34639
|
defaultValue: true,
|
|
@@ -33956,591 +35178,26 @@ const eventSpotlightBlockDefinition = {
|
|
|
33956
35178
|
dataLoaders: { events: {
|
|
33957
35179
|
endpoint: "listPublicEvents",
|
|
33958
35180
|
params: {
|
|
33959
|
-
siteId: { $bind: { from: "$root.siteId" } },
|
|
33960
|
-
limit: { $bind: {
|
|
33961
|
-
from: "content.maxEvents",
|
|
33962
|
-
fallback: "3"
|
|
33963
|
-
} },
|
|
33964
|
-
stage: { $bind: {
|
|
33965
|
-
from: "$root.previewStage",
|
|
33966
|
-
fallback: "published"
|
|
33967
|
-
} },
|
|
33968
|
-
detailLevel: "listing",
|
|
33969
|
-
readProfile: "spotlight"
|
|
33970
|
-
},
|
|
33971
|
-
mode: "server"
|
|
33972
|
-
} }
|
|
33973
|
-
};
|
|
33974
|
-
//#endregion
|
|
33975
|
-
//#region ../calendar/src/grid.ts
|
|
33976
|
-
const DAY_NAMES = [
|
|
33977
|
-
"Sun",
|
|
33978
|
-
"Mon",
|
|
33979
|
-
"Tue",
|
|
33980
|
-
"Wed",
|
|
33981
|
-
"Thu",
|
|
33982
|
-
"Fri",
|
|
33983
|
-
"Sat"
|
|
33984
|
-
];
|
|
33985
|
-
const MONTH_NAMES = [
|
|
33986
|
-
"January",
|
|
33987
|
-
"February",
|
|
33988
|
-
"March",
|
|
33989
|
-
"April",
|
|
33990
|
-
"May",
|
|
33991
|
-
"June",
|
|
33992
|
-
"July",
|
|
33993
|
-
"August",
|
|
33994
|
-
"September",
|
|
33995
|
-
"October",
|
|
33996
|
-
"November",
|
|
33997
|
-
"December"
|
|
33998
|
-
];
|
|
33999
|
-
function formatDateKey(date) {
|
|
34000
|
-
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
34001
|
-
}
|
|
34002
|
-
function isToday(date, today) {
|
|
34003
|
-
return isSameCalendarDay(date, today);
|
|
34004
|
-
}
|
|
34005
|
-
function isSameCalendarDay(left, right) {
|
|
34006
|
-
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
34007
|
-
}
|
|
34008
|
-
function addDays(date, days) {
|
|
34009
|
-
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
34010
|
-
return {
|
|
34011
|
-
year: result.getUTCFullYear(),
|
|
34012
|
-
month: result.getUTCMonth() + 1,
|
|
34013
|
-
day: result.getUTCDate()
|
|
34014
|
-
};
|
|
34015
|
-
}
|
|
34016
|
-
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
34017
|
-
const firstDay = {
|
|
34018
|
-
year,
|
|
34019
|
-
month,
|
|
34020
|
-
day: 1
|
|
34021
|
-
};
|
|
34022
|
-
const dayOfWeek = getDayOfWeek(firstDay);
|
|
34023
|
-
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
34024
|
-
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
34025
|
-
}
|
|
34026
|
-
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
34027
|
-
const dayOfWeek = getDayOfWeek(baseDate);
|
|
34028
|
-
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
34029
|
-
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
34030
|
-
}
|
|
34031
|
-
function getDayOfWeek(date) {
|
|
34032
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
34033
|
-
}
|
|
34034
|
-
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
34035
|
-
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
34036
|
-
assertWeekCalendarDays(days);
|
|
34037
|
-
return days;
|
|
34038
|
-
}
|
|
34039
|
-
function assertWeekCalendarDays(days) {
|
|
34040
|
-
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
34041
|
-
}
|
|
34042
|
-
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
34043
|
-
if (startOfWeek === "monday") {
|
|
34044
|
-
const sunday = dayNames[0] ?? "Sun";
|
|
34045
|
-
return [...dayNames.slice(1), sunday];
|
|
34046
|
-
}
|
|
34047
|
-
return [...dayNames];
|
|
34048
|
-
}
|
|
34049
|
-
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
34050
|
-
if (view === "week") {
|
|
34051
|
-
const weekStart = days[0];
|
|
34052
|
-
const weekEnd = days[6];
|
|
34053
|
-
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
34054
|
-
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
34055
|
-
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
34056
|
-
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
34057
|
-
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
34058
|
-
}
|
|
34059
|
-
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
34060
|
-
}
|
|
34061
|
-
function buildCalendarViewport(input) {
|
|
34062
|
-
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
34063
|
-
if (input.view === "week") {
|
|
34064
|
-
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
34065
|
-
return {
|
|
34066
|
-
kind: "week",
|
|
34067
|
-
anchorDate: input.anchorDate,
|
|
34068
|
-
days,
|
|
34069
|
-
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
34070
|
-
};
|
|
34071
|
-
}
|
|
34072
|
-
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
34073
|
-
return {
|
|
34074
|
-
kind: "month",
|
|
34075
|
-
anchorDate: input.anchorDate,
|
|
34076
|
-
days,
|
|
34077
|
-
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
34078
|
-
};
|
|
34079
|
-
}
|
|
34080
|
-
//#endregion
|
|
34081
|
-
//#region ../calendar/src/lane-packing.ts
|
|
34082
|
-
/**
|
|
34083
|
-
* Assign lanes to a list of intervals.
|
|
34084
|
-
*
|
|
34085
|
-
* Returns one result per input in the same order as the input. Internally
|
|
34086
|
-
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
34087
|
-
* permutes results back to input order.
|
|
34088
|
-
*/
|
|
34089
|
-
function assignLanes(intervals) {
|
|
34090
|
-
if (intervals.length === 0) return [];
|
|
34091
|
-
const indexed = intervals.map((interval, index) => ({
|
|
34092
|
-
interval,
|
|
34093
|
-
index
|
|
34094
|
-
}));
|
|
34095
|
-
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
34096
|
-
const results = new Array(intervals.length);
|
|
34097
|
-
let cluster = [];
|
|
34098
|
-
let activeLaneEnds = [];
|
|
34099
|
-
let clusterMaxEnd = -Infinity;
|
|
34100
|
-
const closeCluster = () => {
|
|
34101
|
-
if (cluster.length === 0) return;
|
|
34102
|
-
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
34103
|
-
for (const entry of cluster) results[entry.index] = {
|
|
34104
|
-
laneIndex: entry.laneIndex,
|
|
34105
|
-
laneCount
|
|
34106
|
-
};
|
|
34107
|
-
cluster = [];
|
|
34108
|
-
activeLaneEnds = [];
|
|
34109
|
-
clusterMaxEnd = -Infinity;
|
|
34110
|
-
};
|
|
34111
|
-
for (const { interval, index } of indexed) {
|
|
34112
|
-
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
34113
|
-
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
34114
|
-
activeLaneEnds[laneIndex] = interval.end;
|
|
34115
|
-
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
34116
|
-
cluster.push({
|
|
34117
|
-
index,
|
|
34118
|
-
laneIndex
|
|
34119
|
-
});
|
|
34120
|
-
}
|
|
34121
|
-
closeCluster();
|
|
34122
|
-
return results;
|
|
34123
|
-
}
|
|
34124
|
-
function firstAvailableLane(activeLaneEnds, start) {
|
|
34125
|
-
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
34126
|
-
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
34127
|
-
}
|
|
34128
|
-
//#endregion
|
|
34129
|
-
//#region ../calendar/src/timezone.ts
|
|
34130
|
-
/**
|
|
34131
|
-
* Timezone boundary helpers — internal.
|
|
34132
|
-
*
|
|
34133
|
-
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
34134
|
-
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
34135
|
-
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
34136
|
-
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
34137
|
-
* public surface.
|
|
34138
|
-
*/
|
|
34139
|
-
const MINUTES_PER_HOUR = 60;
|
|
34140
|
-
/**
|
|
34141
|
-
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
34142
|
-
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
34143
|
-
*/
|
|
34144
|
-
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
34145
|
-
const date = new Date(isoTimestamp);
|
|
34146
|
-
if (Number.isNaN(date.getTime())) return NaN;
|
|
34147
|
-
if (!isValidTimeZone(timeZone)) return NaN;
|
|
34148
|
-
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
34149
|
-
timeZone,
|
|
34150
|
-
hour: "2-digit",
|
|
34151
|
-
minute: "2-digit",
|
|
34152
|
-
hourCycle: "h23"
|
|
34153
|
-
}).formatToParts(date);
|
|
34154
|
-
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
34155
|
-
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
34156
|
-
const hour = hourPart === 24 ? 0 : hourPart;
|
|
34157
|
-
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
34158
|
-
return hour * MINUTES_PER_HOUR + minute;
|
|
34159
|
-
}
|
|
34160
|
-
/**
|
|
34161
|
-
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
34162
|
-
* Returns null if the inputs cannot be parsed.
|
|
34163
|
-
*/
|
|
34164
|
-
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
34165
|
-
const date = new Date(isoTimestamp);
|
|
34166
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
34167
|
-
if (!isValidTimeZone(timeZone)) return null;
|
|
34168
|
-
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
34169
|
-
timeZone,
|
|
34170
|
-
year: "numeric",
|
|
34171
|
-
month: "2-digit",
|
|
34172
|
-
day: "2-digit"
|
|
34173
|
-
}).formatToParts(date);
|
|
34174
|
-
const year = parts.find((part) => part.type === "year")?.value;
|
|
34175
|
-
const month = parts.find((part) => part.type === "month")?.value;
|
|
34176
|
-
const day = parts.find((part) => part.type === "day")?.value;
|
|
34177
|
-
if (year === void 0 || year.length === 0) return null;
|
|
34178
|
-
if (month === void 0 || month.length === 0) return null;
|
|
34179
|
-
if (day === void 0 || day.length === 0) return null;
|
|
34180
|
-
return `${year}-${month}-${day}`;
|
|
34181
|
-
}
|
|
34182
|
-
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
34183
|
-
function isValidTimeZone(timeZone) {
|
|
34184
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
34185
|
-
const cached = validTimeZoneCache.get(timeZone);
|
|
34186
|
-
if (cached !== void 0) return cached;
|
|
34187
|
-
try {
|
|
34188
|
-
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
34189
|
-
validTimeZoneCache.set(timeZone, true);
|
|
34190
|
-
return true;
|
|
34191
|
-
} catch {
|
|
34192
|
-
validTimeZoneCache.set(timeZone, false);
|
|
34193
|
-
return false;
|
|
34194
|
-
}
|
|
34195
|
-
}
|
|
34196
|
-
//#endregion
|
|
34197
|
-
//#region ../calendar/src/timetable.ts
|
|
34198
|
-
/**
|
|
34199
|
-
* Timetable model — the package's core surface.
|
|
34200
|
-
*
|
|
34201
|
-
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
34202
|
-
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
34203
|
-
* plus a diagnostics list for items dropped during processing.
|
|
34204
|
-
*
|
|
34205
|
-
* The function is pure: given identical input (including `now`), it returns
|
|
34206
|
-
* the same output. The model has no opinion on what an item is — pass
|
|
34207
|
-
* accessors.
|
|
34208
|
-
*/
|
|
34209
|
-
const DEFAULT_BOUNDS = {
|
|
34210
|
-
minStartHour: 5,
|
|
34211
|
-
maxEndHour: 23,
|
|
34212
|
-
defaultStartHour: 6,
|
|
34213
|
-
defaultEndHour: 21,
|
|
34214
|
-
minHeightPercent: 2.5
|
|
35181
|
+
siteId: { $bind: { from: "$root.siteId" } },
|
|
35182
|
+
limit: { $bind: {
|
|
35183
|
+
from: "content.maxEvents",
|
|
35184
|
+
fallback: "3"
|
|
35185
|
+
} },
|
|
35186
|
+
stage: { $bind: {
|
|
35187
|
+
from: "$root.previewStage",
|
|
35188
|
+
fallback: "published"
|
|
35189
|
+
} },
|
|
35190
|
+
detailLevel: "listing",
|
|
35191
|
+
readProfile: "spotlight"
|
|
35192
|
+
},
|
|
35193
|
+
mode: "server"
|
|
35194
|
+
} }
|
|
34215
35195
|
};
|
|
34216
|
-
function buildTimetableModel$1(input) {
|
|
34217
|
-
const bounds = resolveBounds(input.bounds);
|
|
34218
|
-
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
34219
|
-
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
34220
|
-
const parsed = parseAllItems(input.items, input);
|
|
34221
|
-
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
34222
|
-
const hourRange = resolveHourRange(inViewport, bounds);
|
|
34223
|
-
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
34224
|
-
return {
|
|
34225
|
-
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
34226
|
-
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
34227
|
-
startHour: hourRange.startHour,
|
|
34228
|
-
endHour: hourRange.endHour,
|
|
34229
|
-
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
34230
|
-
hasItems: inViewport.length > 0,
|
|
34231
|
-
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
34232
|
-
};
|
|
34233
|
-
}
|
|
34234
|
-
function parseAllItems(items, accessors) {
|
|
34235
|
-
const drafts = [];
|
|
34236
|
-
const diagnostics = [];
|
|
34237
|
-
for (const item of items) {
|
|
34238
|
-
const result = parseDraft(item, accessors);
|
|
34239
|
-
if (result.ok) drafts.push(result.draft);
|
|
34240
|
-
else diagnostics.push({
|
|
34241
|
-
item,
|
|
34242
|
-
reason: result.reason
|
|
34243
|
-
});
|
|
34244
|
-
}
|
|
34245
|
-
return {
|
|
34246
|
-
drafts,
|
|
34247
|
-
diagnostics
|
|
34248
|
-
};
|
|
34249
|
-
}
|
|
34250
|
-
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
34251
|
-
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
34252
|
-
const rangeStartMinutes = hourRange.startHour * 60;
|
|
34253
|
-
const rangeEndMinutes = hourRange.endHour * 60;
|
|
34254
|
-
const segmentsByDate = groupSegmentsByDate(segments);
|
|
34255
|
-
const cellsByDate = /* @__PURE__ */ new Map();
|
|
34256
|
-
const diagnostics = [];
|
|
34257
|
-
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
34258
|
-
const clipped = [];
|
|
34259
|
-
for (const segment of daySegments) {
|
|
34260
|
-
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
34261
|
-
if (result.ok) clipped.push(result.clipped);
|
|
34262
|
-
else diagnostics.push({
|
|
34263
|
-
item: segment.draft.item,
|
|
34264
|
-
reason: result.reason
|
|
34265
|
-
});
|
|
34266
|
-
}
|
|
34267
|
-
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
34268
|
-
start: entry.visibleStart,
|
|
34269
|
-
end: entry.visibleEnd,
|
|
34270
|
-
orderKey: entry.segment.draft.orderKey
|
|
34271
|
-
})));
|
|
34272
|
-
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
34273
|
-
laneIndex: 0,
|
|
34274
|
-
laneCount: 1
|
|
34275
|
-
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
34276
|
-
}
|
|
34277
|
-
return {
|
|
34278
|
-
cellsByDate,
|
|
34279
|
-
diagnostics
|
|
34280
|
-
};
|
|
34281
|
-
}
|
|
34282
|
-
function assembleDays(viewportDays, cellsByDate, today) {
|
|
34283
|
-
return viewportDays.map((date) => {
|
|
34284
|
-
const dateKey = formatDateKey(date);
|
|
34285
|
-
return {
|
|
34286
|
-
date,
|
|
34287
|
-
dateKey,
|
|
34288
|
-
isToday: isSameCalendarDay(date, today),
|
|
34289
|
-
cells: cellsByDate.get(dateKey) ?? []
|
|
34290
|
-
};
|
|
34291
|
-
});
|
|
34292
|
-
}
|
|
34293
|
-
function parseDraft(item, accessors) {
|
|
34294
|
-
const startsAtIso = accessors.getStartsAt(item);
|
|
34295
|
-
const endsAtIso = accessors.getEndsAt(item);
|
|
34296
|
-
const timeZone = accessors.getTimeZone(item);
|
|
34297
|
-
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
34298
|
-
if (Number.isNaN(startsAtMinutes)) return {
|
|
34299
|
-
ok: false,
|
|
34300
|
-
reason: "invalid-start"
|
|
34301
|
-
};
|
|
34302
|
-
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
34303
|
-
if (startDateKey == null) return {
|
|
34304
|
-
ok: false,
|
|
34305
|
-
reason: "invalid-timezone"
|
|
34306
|
-
};
|
|
34307
|
-
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
34308
|
-
if (endDateKey == null) return {
|
|
34309
|
-
ok: false,
|
|
34310
|
-
reason: "invalid-end"
|
|
34311
|
-
};
|
|
34312
|
-
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
34313
|
-
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
34314
|
-
ok: false,
|
|
34315
|
-
reason: "invalid-end"
|
|
34316
|
-
};
|
|
34317
|
-
const spansMultipleDays = startDateKey < endDateKey;
|
|
34318
|
-
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
34319
|
-
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
34320
|
-
ok: false,
|
|
34321
|
-
reason: "non-positive-duration"
|
|
34322
|
-
};
|
|
34323
|
-
return {
|
|
34324
|
-
ok: true,
|
|
34325
|
-
draft: {
|
|
34326
|
-
item,
|
|
34327
|
-
startDateKey,
|
|
34328
|
-
endDateKey,
|
|
34329
|
-
startsAtMinutes,
|
|
34330
|
-
endsAtMinutes,
|
|
34331
|
-
color: accessors.getColor?.(item) ?? null,
|
|
34332
|
-
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
34333
|
-
}
|
|
34334
|
-
};
|
|
34335
|
-
}
|
|
34336
|
-
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
34337
|
-
const segments = [];
|
|
34338
|
-
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
34339
|
-
if (!viewportKeys.has(dateKey)) continue;
|
|
34340
|
-
const isStartDay = dateKey === draft.startDateKey;
|
|
34341
|
-
const isEndDay = dateKey === draft.endDateKey;
|
|
34342
|
-
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
34343
|
-
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
34344
|
-
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
34345
|
-
segments.push({
|
|
34346
|
-
draft,
|
|
34347
|
-
dateKey,
|
|
34348
|
-
startsAtMinutes,
|
|
34349
|
-
endsAtMinutes,
|
|
34350
|
-
continuesBefore: !isStartDay,
|
|
34351
|
-
continuesAfter: !isEndDay
|
|
34352
|
-
});
|
|
34353
|
-
}
|
|
34354
|
-
return segments;
|
|
34355
|
-
}
|
|
34356
|
-
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
34357
|
-
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
34358
|
-
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
34359
|
-
if (visibleEnd <= visibleStart) return {
|
|
34360
|
-
ok: false,
|
|
34361
|
-
reason: "out-of-visible-hours"
|
|
34362
|
-
};
|
|
34363
|
-
return {
|
|
34364
|
-
ok: true,
|
|
34365
|
-
clipped: {
|
|
34366
|
-
segment,
|
|
34367
|
-
visibleStart,
|
|
34368
|
-
visibleEnd
|
|
34369
|
-
}
|
|
34370
|
-
};
|
|
34371
|
-
}
|
|
34372
|
-
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
34373
|
-
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
34374
|
-
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
34375
|
-
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
34376
|
-
return {
|
|
34377
|
-
item: entry.segment.draft.item,
|
|
34378
|
-
dateKey: entry.segment.dateKey,
|
|
34379
|
-
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
34380
|
-
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
34381
|
-
visibleStartsAtMinutes: entry.visibleStart,
|
|
34382
|
-
visibleEndsAtMinutes: entry.visibleEnd,
|
|
34383
|
-
topPercent,
|
|
34384
|
-
heightPercent,
|
|
34385
|
-
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
34386
|
-
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
34387
|
-
laneIndex: lane.laneIndex,
|
|
34388
|
-
laneCount: lane.laneCount,
|
|
34389
|
-
color: entry.segment.draft.color
|
|
34390
|
-
};
|
|
34391
|
-
}
|
|
34392
|
-
function resolveBounds(bounds) {
|
|
34393
|
-
return {
|
|
34394
|
-
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
34395
|
-
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
34396
|
-
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
34397
|
-
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
34398
|
-
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
34399
|
-
};
|
|
34400
|
-
}
|
|
34401
|
-
function resolveHourRange(segments, bounds) {
|
|
34402
|
-
if (segments.length === 0) return {
|
|
34403
|
-
startHour: bounds.defaultStartHour,
|
|
34404
|
-
endHour: bounds.defaultEndHour
|
|
34405
|
-
};
|
|
34406
|
-
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
34407
|
-
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
34408
|
-
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
34409
|
-
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
34410
|
-
return {
|
|
34411
|
-
startHour,
|
|
34412
|
-
endHour: Math.max(startHour + 1, endHour)
|
|
34413
|
-
};
|
|
34414
|
-
}
|
|
34415
|
-
function buildHourTicks(startHour, endHour) {
|
|
34416
|
-
const ticks = [];
|
|
34417
|
-
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
34418
|
-
return ticks;
|
|
34419
|
-
}
|
|
34420
|
-
function groupSegmentsByDate(segments) {
|
|
34421
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
34422
|
-
for (const segment of segments) {
|
|
34423
|
-
const existing = grouped.get(segment.dateKey) ?? [];
|
|
34424
|
-
existing.push(segment);
|
|
34425
|
-
grouped.set(segment.dateKey, existing);
|
|
34426
|
-
}
|
|
34427
|
-
return grouped;
|
|
34428
|
-
}
|
|
34429
|
-
function toRangePercent(minutes, rangeMinutes) {
|
|
34430
|
-
if (rangeMinutes <= 0) return 0;
|
|
34431
|
-
return minutes / rangeMinutes * 100;
|
|
34432
|
-
}
|
|
34433
|
-
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
34434
|
-
if (view === "day") return [viewDate];
|
|
34435
|
-
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
34436
|
-
}
|
|
34437
|
-
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
34438
|
-
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
34439
|
-
const start = parseDateKey(startDateKey);
|
|
34440
|
-
parseDateKey(endDateKey);
|
|
34441
|
-
const keys = [];
|
|
34442
|
-
let cursor = start;
|
|
34443
|
-
while (formatDateKey(cursor) <= endDateKey) {
|
|
34444
|
-
keys.push(formatDateKey(cursor));
|
|
34445
|
-
cursor = addDays(cursor, 1);
|
|
34446
|
-
}
|
|
34447
|
-
return keys;
|
|
34448
|
-
}
|
|
34449
|
-
function parseDateKey(dateKey) {
|
|
34450
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
34451
|
-
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
34452
|
-
const [, year, month, day] = match;
|
|
34453
|
-
return {
|
|
34454
|
-
year: Number(year),
|
|
34455
|
-
month: Number(month),
|
|
34456
|
-
day: Number(day)
|
|
34457
|
-
};
|
|
34458
|
-
}
|
|
34459
|
-
//#endregion
|
|
34460
|
-
//#region ../calendar/src/group.ts
|
|
34461
|
-
/**
|
|
34462
|
-
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
34463
|
-
*
|
|
34464
|
-
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
34465
|
-
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
34466
|
-
*
|
|
34467
|
-
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
34468
|
-
* relative order of items within each date matches input order.
|
|
34469
|
-
*/
|
|
34470
|
-
function groupItemsByDate(items, accessors) {
|
|
34471
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
34472
|
-
for (const item of items) {
|
|
34473
|
-
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
34474
|
-
if (dateKey == null) continue;
|
|
34475
|
-
const existing = grouped.get(dateKey);
|
|
34476
|
-
if (existing !== void 0) existing.push(item);
|
|
34477
|
-
else grouped.set(dateKey, [item]);
|
|
34478
|
-
}
|
|
34479
|
-
return grouped;
|
|
34480
|
-
}
|
|
34481
|
-
//#endregion
|
|
34482
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/eventCapacity.ts
|
|
34483
|
-
/**
|
|
34484
|
-
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
34485
|
-
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
34486
|
-
* for uncapped events so call sites can render no capacity indicator at all.
|
|
34487
|
-
*
|
|
34488
|
-
* The single source of truth for capacity math across event cards, agenda
|
|
34489
|
-
* rows, and timetable cells — keep wording variations in the call sites.
|
|
34490
|
-
*/
|
|
34491
|
-
function getEventAvailability(event) {
|
|
34492
|
-
if (event.capacity == null) return {
|
|
34493
|
-
available: null,
|
|
34494
|
-
isFull: false
|
|
34495
|
-
};
|
|
34496
|
-
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
34497
|
-
if (available <= 0) return {
|
|
34498
|
-
available: 0,
|
|
34499
|
-
isFull: true
|
|
34500
|
-
};
|
|
34501
|
-
return {
|
|
34502
|
-
available,
|
|
34503
|
-
isFull: false
|
|
34504
|
-
};
|
|
34505
|
-
}
|
|
34506
|
-
/**
|
|
34507
|
-
* Derive the three-state availability status (available / filling / waitlist)
|
|
34508
|
-
* plus a short pill label. Builds on {@link getEventAvailability} so the
|
|
34509
|
-
* capacity math (availableSpots with a `capacity - registeredCount` fallback)
|
|
34510
|
-
* stays consistent with the binary helper used elsewhere.
|
|
34511
|
-
*/
|
|
34512
|
-
function getEventAvailabilityStatus(event, options) {
|
|
34513
|
-
const threshold = options?.fillingThreshold ?? 2;
|
|
34514
|
-
const { available, isFull } = getEventAvailability(event);
|
|
34515
|
-
if (available == null) return {
|
|
34516
|
-
status: "available",
|
|
34517
|
-
available: null,
|
|
34518
|
-
isFull: false,
|
|
34519
|
-
label: null
|
|
34520
|
-
};
|
|
34521
|
-
if (isFull) return {
|
|
34522
|
-
status: "waitlist",
|
|
34523
|
-
available: 0,
|
|
34524
|
-
isFull: true,
|
|
34525
|
-
label: event.waitlistEnabled ? "Waitlist" : "Full"
|
|
34526
|
-
};
|
|
34527
|
-
if (available <= threshold) return {
|
|
34528
|
-
status: "filling",
|
|
34529
|
-
available,
|
|
34530
|
-
isFull: false,
|
|
34531
|
-
label: `${available} left`
|
|
34532
|
-
};
|
|
34533
|
-
return {
|
|
34534
|
-
status: "available",
|
|
34535
|
-
available,
|
|
34536
|
-
isFull: false,
|
|
34537
|
-
label: `${available} places`
|
|
34538
|
-
};
|
|
34539
|
-
}
|
|
34540
35196
|
//#endregion
|
|
34541
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/
|
|
35197
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/listingLayout.ts
|
|
34542
35198
|
/**
|
|
34543
|
-
*
|
|
35199
|
+
* Layout classes for event listing containers and cards. Presentation
|
|
35200
|
+
* plumbing rather than event domain logic, so it lives beside the components.
|
|
34544
35201
|
*/
|
|
34545
35202
|
/**
|
|
34546
35203
|
* Column class mappings for grid layouts, derived from the canonical
|
|
@@ -34575,180 +35232,6 @@ function getCardOrientation(layout) {
|
|
|
34575
35232
|
case "compact": return "vertical";
|
|
34576
35233
|
}
|
|
34577
35234
|
}
|
|
34578
|
-
/**
|
|
34579
|
-
* Format date for display.
|
|
34580
|
-
* Returns: "Monday, January 15, 2025"
|
|
34581
|
-
*/
|
|
34582
|
-
function formatDate$2(isoTimestamp, options) {
|
|
34583
|
-
return formatBookingDate(toScheduledBookingDateTime({
|
|
34584
|
-
startsAt: isoTimestamp,
|
|
34585
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34586
|
-
locale: options?.locale ?? "en-US"
|
|
34587
|
-
}), {
|
|
34588
|
-
weekday: "long",
|
|
34589
|
-
year: "numeric",
|
|
34590
|
-
month: "long",
|
|
34591
|
-
day: "numeric"
|
|
34592
|
-
});
|
|
34593
|
-
}
|
|
34594
|
-
/**
|
|
34595
|
-
* Format time for display.
|
|
34596
|
-
* Returns: "10:00 AM"
|
|
34597
|
-
*/
|
|
34598
|
-
function formatTime(isoTimestamp, options) {
|
|
34599
|
-
return formatBookingTime(toScheduledBookingDateTime({
|
|
34600
|
-
startsAt: isoTimestamp,
|
|
34601
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34602
|
-
locale: options?.locale ?? "en-US"
|
|
34603
|
-
}), {
|
|
34604
|
-
hour: "numeric",
|
|
34605
|
-
minute: "2-digit",
|
|
34606
|
-
hour12: true
|
|
34607
|
-
});
|
|
34608
|
-
}
|
|
34609
|
-
function formatEventDateParts(args) {
|
|
34610
|
-
const scheduledRange = toScheduledBookingDateRange({
|
|
34611
|
-
startsAt: args.startsAt,
|
|
34612
|
-
endsAt: args.endsAt,
|
|
34613
|
-
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
34614
|
-
locale: args.locale ?? "en-US"
|
|
34615
|
-
});
|
|
34616
|
-
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
34617
|
-
weekday: "long",
|
|
34618
|
-
year: "numeric",
|
|
34619
|
-
month: "long",
|
|
34620
|
-
day: "numeric"
|
|
34621
|
-
} });
|
|
34622
|
-
return {
|
|
34623
|
-
date,
|
|
34624
|
-
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
34625
|
-
startsAt: args.endsAt,
|
|
34626
|
-
timeZone: scheduledRange.timeZone,
|
|
34627
|
-
locale: args.locale ?? "en-US"
|
|
34628
|
-
}))}`
|
|
34629
|
-
};
|
|
34630
|
-
}
|
|
34631
|
-
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
34632
|
-
const dateTime = toScheduledBookingDateTime({
|
|
34633
|
-
startsAt: isoTimestamp,
|
|
34634
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34635
|
-
locale: options?.locale ?? "en-US"
|
|
34636
|
-
});
|
|
34637
|
-
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
34638
|
-
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
34639
|
-
return {
|
|
34640
|
-
weekday,
|
|
34641
|
-
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
34642
|
-
month,
|
|
34643
|
-
label: formatBookingDate(dateTime, {
|
|
34644
|
-
weekday: "long",
|
|
34645
|
-
year: "numeric",
|
|
34646
|
-
month: "long",
|
|
34647
|
-
day: "numeric"
|
|
34648
|
-
})
|
|
34649
|
-
};
|
|
34650
|
-
}
|
|
34651
|
-
function formatTeamMemberNames(names) {
|
|
34652
|
-
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
34653
|
-
if (normalized.length === 0) return null;
|
|
34654
|
-
const [firstName, secondName] = normalized;
|
|
34655
|
-
if (!firstName) return null;
|
|
34656
|
-
if (normalized.length === 1) return firstName;
|
|
34657
|
-
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
34658
|
-
const finalName = normalized[normalized.length - 1] ?? "";
|
|
34659
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
34660
|
-
}
|
|
34661
|
-
function formatEventTeacherLine(event) {
|
|
34662
|
-
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
34663
|
-
return names ? `with ${names}` : null;
|
|
34664
|
-
}
|
|
34665
|
-
/**
|
|
34666
|
-
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
34667
|
-
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
34668
|
-
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
34669
|
-
*/
|
|
34670
|
-
function groupEventsByDate(events, calendarTimeZone) {
|
|
34671
|
-
const grouped = groupItemsByDate(events, {
|
|
34672
|
-
getStartsAt: (event) => event.startsAt,
|
|
34673
|
-
getTimeZone: () => calendarTimeZone
|
|
34674
|
-
});
|
|
34675
|
-
const map = /* @__PURE__ */ new Map();
|
|
34676
|
-
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
34677
|
-
return map;
|
|
34678
|
-
}
|
|
34679
|
-
/**
|
|
34680
|
-
* Format event price for display.
|
|
34681
|
-
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
34682
|
-
*
|
|
34683
|
-
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
34684
|
-
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
34685
|
-
* surfaces stay consistent.
|
|
34686
|
-
*/
|
|
34687
|
-
function formatEventPrice(event) {
|
|
34688
|
-
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
34689
|
-
}
|
|
34690
|
-
function closedEventBookingLabel(reason) {
|
|
34691
|
-
switch (reason) {
|
|
34692
|
-
case "occurrence_cancelled":
|
|
34693
|
-
case "series_cancelled": return "Cancelled";
|
|
34694
|
-
case "occurrence_completed":
|
|
34695
|
-
case "series_completed": return "Completed";
|
|
34696
|
-
case "series_draft":
|
|
34697
|
-
case "series_archived":
|
|
34698
|
-
case "parent_archived":
|
|
34699
|
-
case "parent_inconsistent":
|
|
34700
|
-
case "not_public":
|
|
34701
|
-
case "already_started":
|
|
34702
|
-
case "enrollment_closed": return "Registration closed";
|
|
34703
|
-
default: return assertNever$23(reason);
|
|
34704
|
-
}
|
|
34705
|
-
}
|
|
34706
|
-
function resolveEventHref(event) {
|
|
34707
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
34708
|
-
return destination.kind === "external" ? destination.url : destination.path;
|
|
34709
|
-
}
|
|
34710
|
-
function resolveEventLinkProps(event) {
|
|
34711
|
-
const href = resolveEventHref(event);
|
|
34712
|
-
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
34713
|
-
href,
|
|
34714
|
-
target: "_blank",
|
|
34715
|
-
rel: "noopener noreferrer"
|
|
34716
|
-
} : { href };
|
|
34717
|
-
}
|
|
34718
|
-
/**
|
|
34719
|
-
* Resolve the CTA state for an event.
|
|
34720
|
-
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
34721
|
-
* and course-specific defaults consistent.
|
|
34722
|
-
*/
|
|
34723
|
-
function resolveEventCta(event, buttonText) {
|
|
34724
|
-
const { isFull: isSoldOut } = getEventAvailability(event);
|
|
34725
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
34726
|
-
const isExternal = destination.kind === "external";
|
|
34727
|
-
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
34728
|
-
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
34729
|
-
const href = resolveEventHref(event);
|
|
34730
|
-
if (event.bookingAvailability.kind === "closed") {
|
|
34731
|
-
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
34732
|
-
return {
|
|
34733
|
-
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
34734
|
-
href,
|
|
34735
|
-
external: isExternal,
|
|
34736
|
-
hidden: !linksToClosedWholeSeriesDetails
|
|
34737
|
-
};
|
|
34738
|
-
}
|
|
34739
|
-
if (!isExternal && isSoldOut) return {
|
|
34740
|
-
label: "Sold out",
|
|
34741
|
-
href,
|
|
34742
|
-
external: false,
|
|
34743
|
-
hidden: true
|
|
34744
|
-
};
|
|
34745
|
-
return {
|
|
34746
|
-
label: effectiveButtonText,
|
|
34747
|
-
href,
|
|
34748
|
-
external: isExternal,
|
|
34749
|
-
hidden: false
|
|
34750
|
-
};
|
|
34751
|
-
}
|
|
34752
35235
|
//#endregion
|
|
34753
35236
|
//#region ../blocks/src/system/media/slotContracts.ts
|
|
34754
35237
|
const DEFAULT_MIN_SOURCE_WIDTH = 1200;
|
|
@@ -36044,7 +36527,6 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
36044
36527
|
const formattedDate = formatDate$2(event.startsAt, { timeZone });
|
|
36045
36528
|
const formattedTime = `${formatTime(event.startsAt, { timeZone })} - ${formatTime(event.endsAt, { timeZone })}`;
|
|
36046
36529
|
const cta = resolveEventCta(event, buttonText);
|
|
36047
|
-
const { available: spotsLeft, isFull: isSoldOut } = getEventAvailability(event);
|
|
36048
36530
|
const cardClass = boxStyleClassNames({ role: cardRole }, { baseClass: "event-card" });
|
|
36049
36531
|
const buttonClass = themeButtonClassName({
|
|
36050
36532
|
variant: buttonVariant,
|
|
@@ -36060,10 +36542,7 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
36060
36542
|
} : null;
|
|
36061
36543
|
const hasImage = Boolean(showImage && image);
|
|
36062
36544
|
const teacherLine = formatEventTeacherLine(event);
|
|
36063
|
-
const capacityText = showCapacity ?
|
|
36064
|
-
isSoldOut,
|
|
36065
|
-
spotsLeft
|
|
36066
|
-
}) : null;
|
|
36545
|
+
const capacityText = showCapacity ? formatEventRemainingPlaces(event, { full: "Sold out" }) : null;
|
|
36067
36546
|
return /* @__PURE__ */ jsxs("article", {
|
|
36068
36547
|
className: `${cardClass} group rb-flex rb-flex-col rb-h-full ${eventCardOrientationClass(orientation)}`,
|
|
36069
36548
|
"data-orientation": orientation,
|
|
@@ -36129,7 +36608,11 @@ function EventCardContent(props) {
|
|
|
36129
36608
|
}),
|
|
36130
36609
|
/* @__PURE__ */ jsx("h3", {
|
|
36131
36610
|
className: "card-title rb-mb-3",
|
|
36132
|
-
children:
|
|
36611
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
36612
|
+
className: "event-title-link",
|
|
36613
|
+
...resolveEventLinkProps(event),
|
|
36614
|
+
children: props.title
|
|
36615
|
+
})
|
|
36133
36616
|
}),
|
|
36134
36617
|
/* @__PURE__ */ jsx("div", {
|
|
36135
36618
|
className: "rb-mb-2 rb-caption",
|
|
@@ -36206,13 +36689,6 @@ function eventCardOrientationClass(orientation) {
|
|
|
36206
36689
|
case "vertical": return "event-card--vertical";
|
|
36207
36690
|
}
|
|
36208
36691
|
}
|
|
36209
|
-
function formatCapacityText(event, options) {
|
|
36210
|
-
if (event.capacity == null) return null;
|
|
36211
|
-
if (options.isSoldOut) return "Sold out";
|
|
36212
|
-
if (options.spotsLeft == null) return null;
|
|
36213
|
-
const count = options.spotsLeft;
|
|
36214
|
-
return `${count} ${count === 1 ? "spot" : "spots"} available`;
|
|
36215
|
-
}
|
|
36216
36692
|
function getCategoryBadgeStyle(background) {
|
|
36217
36693
|
const normalized = background?.trim() ?? "";
|
|
36218
36694
|
if (!normalized) return {
|
|
@@ -36281,7 +36757,11 @@ const EventCompactRow = ({ event, buttonVariant = "primary", buttonText }) => {
|
|
|
36281
36757
|
className: "event-compact-row-content",
|
|
36282
36758
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
36283
36759
|
className: "event-compact-row-title",
|
|
36284
|
-
children:
|
|
36760
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
36761
|
+
className: "event-title-link",
|
|
36762
|
+
...resolveEventLinkProps(event),
|
|
36763
|
+
children: event.title
|
|
36764
|
+
})
|
|
36285
36765
|
}), /* @__PURE__ */ jsxs("div", {
|
|
36286
36766
|
className: "event-compact-row-meta",
|
|
36287
36767
|
children: [
|
|
@@ -36370,6 +36850,23 @@ function DateTimeTile({ startsAt, endsAt, timeZone }) {
|
|
|
36370
36850
|
});
|
|
36371
36851
|
}
|
|
36372
36852
|
//#endregion
|
|
36853
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/OfferingTitle.tsx
|
|
36854
|
+
/**
|
|
36855
|
+
* Heading for an offering card or row. Links to the offering's page when one
|
|
36856
|
+
* exists so a sold-out or closed offering stays reachable (issue 1372); the
|
|
36857
|
+
* `.event-title-link` rule lives in theme-core's event-list.css.
|
|
36858
|
+
*/
|
|
36859
|
+
function OfferingTitle({ className, path, title }) {
|
|
36860
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
36861
|
+
className,
|
|
36862
|
+
children: path ? /* @__PURE__ */ jsx("a", {
|
|
36863
|
+
className: "event-title-link",
|
|
36864
|
+
href: path,
|
|
36865
|
+
children: title
|
|
36866
|
+
}) : title
|
|
36867
|
+
});
|
|
36868
|
+
}
|
|
36869
|
+
//#endregion
|
|
36373
36870
|
//#region ../blocks/src/system/runtime/nodes/events/shared/listOptions.ts
|
|
36374
36871
|
const eventPageSizeValues = [
|
|
36375
36872
|
3,
|
|
@@ -37714,7 +38211,7 @@ const eventCalendarAdditionalFields = [
|
|
|
37714
38211
|
{
|
|
37715
38212
|
id: "showCapacity",
|
|
37716
38213
|
type: "boolean",
|
|
37717
|
-
label: "Show available
|
|
38214
|
+
label: "Show available places",
|
|
37718
38215
|
description: "Display remaining capacity on event cards and timetable cells",
|
|
37719
38216
|
required: false,
|
|
37720
38217
|
defaultValue: true,
|
|
@@ -47464,7 +47961,7 @@ function EventFacts({ selection, selected, venue, teamMembers, pricing, showCapa
|
|
|
47464
47961
|
children: [/* @__PURE__ */ jsx("div", {
|
|
47465
47962
|
className: "rb-label rb-mb-2",
|
|
47466
47963
|
children: "Places"
|
|
47467
|
-
}), /* @__PURE__ */
|
|
47964
|
+
}), /* @__PURE__ */ jsx("p", { children: formatTotalCapacity(selected.capacity) })]
|
|
47468
47965
|
}) : null,
|
|
47469
47966
|
pricing,
|
|
47470
47967
|
selected.bookingScope === "whole_series" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -48308,9 +48805,10 @@ function CourseCompactRow({ course, path }) {
|
|
|
48308
48805
|
}),
|
|
48309
48806
|
/* @__PURE__ */ jsxs("div", {
|
|
48310
48807
|
className: "event-compact-row-content",
|
|
48311
|
-
children: [/* @__PURE__ */ jsx(
|
|
48808
|
+
children: [/* @__PURE__ */ jsx(OfferingTitle, {
|
|
48312
48809
|
className: "event-compact-row-title",
|
|
48313
|
-
|
|
48810
|
+
path,
|
|
48811
|
+
title: course.title
|
|
48314
48812
|
}), /* @__PURE__ */ jsxs("div", {
|
|
48315
48813
|
className: "event-compact-row-meta",
|
|
48316
48814
|
children: [
|
|
@@ -48367,9 +48865,10 @@ function CourseFeatureCard({ course, path, buttonVariant, buttonText }) {
|
|
|
48367
48865
|
children: /* @__PURE__ */ jsxs("div", {
|
|
48368
48866
|
className: "card-content rb-flex rb-flex-1 rb-flex-col",
|
|
48369
48867
|
children: [
|
|
48370
|
-
/* @__PURE__ */ jsx(
|
|
48868
|
+
/* @__PURE__ */ jsx(OfferingTitle, {
|
|
48371
48869
|
className: "card-title rb-mb-3",
|
|
48372
|
-
|
|
48870
|
+
path,
|
|
48871
|
+
title: course.title
|
|
48373
48872
|
}),
|
|
48374
48873
|
/* @__PURE__ */ jsxs("div", {
|
|
48375
48874
|
className: "rb-mb-2 rb-caption",
|
|
@@ -49225,8 +49724,8 @@ const AgendaEventCard = ({ event, showCapacity, onClick }) => {
|
|
|
49225
49724
|
const eventLinkProps = resolveEventLinkProps(event);
|
|
49226
49725
|
const categoryColor = event.eventCategory?.color || "rgb(var(--tb-accent))";
|
|
49227
49726
|
const price = formatEventPrice(event);
|
|
49228
|
-
const
|
|
49229
|
-
const
|
|
49727
|
+
const capacity = showCapacity ? formatEventRemainingPlaces(event) : null;
|
|
49728
|
+
const isFull = isEventFull(event);
|
|
49230
49729
|
const title = event.title;
|
|
49231
49730
|
const timeZone = event.timeZone ?? null;
|
|
49232
49731
|
const teacherLine = formatEventTeacherLine(event);
|
|
@@ -49668,7 +50167,7 @@ function buildTimetableModel(input) {
|
|
|
49668
50167
|
getEndsAt: (event) => event.endsAt,
|
|
49669
50168
|
getTimeZone: (event) => event.timeZone ?? "UTC",
|
|
49670
50169
|
getColor: (event) => event.eventCategory?.color ?? null,
|
|
49671
|
-
getOrderKey: (event) => `${event.title}
|
|
50170
|
+
getOrderKey: (event) => `${event.title}\u0000${event.id}`
|
|
49672
50171
|
});
|
|
49673
50172
|
return {
|
|
49674
50173
|
hourTicks: [...generic.hourTicks],
|
|
@@ -49705,18 +50204,9 @@ function toEventCell(cell) {
|
|
|
49705
50204
|
};
|
|
49706
50205
|
}
|
|
49707
50206
|
function formatTimetableCapacity(event) {
|
|
49708
|
-
const { available, isFull } = getEventAvailability(event);
|
|
49709
|
-
if (available == null) return {
|
|
49710
|
-
label: null,
|
|
49711
|
-
isFull: false
|
|
49712
|
-
};
|
|
49713
|
-
if (isFull) return {
|
|
49714
|
-
label: "Full",
|
|
49715
|
-
isFull: true
|
|
49716
|
-
};
|
|
49717
50207
|
return {
|
|
49718
|
-
label:
|
|
49719
|
-
isFull:
|
|
50208
|
+
label: formatEventRemainingPlaces(event, { compact: true }),
|
|
50209
|
+
isFull: isEventFull(event)
|
|
49720
50210
|
};
|
|
49721
50211
|
}
|
|
49722
50212
|
function formatEventTimeRange(event) {
|
|
@@ -50192,7 +50682,7 @@ function EventRegistrationSSR({ blockId, subject, children, ...sourceProps }) {
|
|
|
50192
50682
|
className: `event-registration ${className ?? ""}`.trim(),
|
|
50193
50683
|
"data-block": "event-registration",
|
|
50194
50684
|
"data-rb-island-required": "true",
|
|
50195
|
-
"data-rb-island-error-message": "
|
|
50685
|
+
"data-rb-island-error-message": "We couldn’t load the booking options for this event. Please refresh this page and try again. If this keeps happening, contact the organiser for help.",
|
|
50196
50686
|
"data-rb-island-error-class": "event-registration__hydration-error",
|
|
50197
50687
|
...getIslandRootProps("event-registration", blockId),
|
|
50198
50688
|
children: [
|
|
@@ -56102,6 +56592,32 @@ function isSiteStyleTransitionStyleGroup(value) {
|
|
|
56102
56592
|
function boundaryMapForAdjacentBlockIds(boundaries) {
|
|
56103
56593
|
return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
|
|
56104
56594
|
}
|
|
56595
|
+
/** Keep a surviving block's outgoing treatment when omitted blocks change its neighbor. */
|
|
56596
|
+
function boundariesForSurvivingBlocks(boundaries, blocks) {
|
|
56597
|
+
const outgoing = new Map(boundaries.map((boundary) => [boundary.previousBlockId, boundary]));
|
|
56598
|
+
const incoming = new Map(boundaries.map((boundary) => [boundary.nextBlockId, boundary]));
|
|
56599
|
+
return blocks.flatMap((block, index) => {
|
|
56600
|
+
const next = blocks[index + 1];
|
|
56601
|
+
if (!block.id || !next?.id) return [];
|
|
56602
|
+
const boundary = outgoing.get(block.id);
|
|
56603
|
+
if (!boundary) return [];
|
|
56604
|
+
if (boundary.nextBlockId === next.id) return [boundary];
|
|
56605
|
+
const destination = incoming.get(next.id);
|
|
56606
|
+
return [retargetCompiledBoundaryRuntimeForContentAdjacency({
|
|
56607
|
+
...boundary,
|
|
56608
|
+
boundaryId: `boundary:${block.id}:${next.id}`,
|
|
56609
|
+
nextBlockId: next.id,
|
|
56610
|
+
to: destination?.to ?? boundary.to,
|
|
56611
|
+
cssVars: {
|
|
56612
|
+
...boundary.cssVars,
|
|
56613
|
+
to: destination?.cssVars.to ?? boundary.cssVars.to
|
|
56614
|
+
}
|
|
56615
|
+
}, {
|
|
56616
|
+
currentContent: block.content,
|
|
56617
|
+
nextContent: next.content
|
|
56618
|
+
})];
|
|
56619
|
+
});
|
|
56620
|
+
}
|
|
56105
56621
|
function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
|
|
56106
56622
|
const previousBlockId = previousBlock?.id ?? null;
|
|
56107
56623
|
const nextBlockId = nextBlock?.id ?? null;
|
|
@@ -56203,48 +56719,187 @@ function toEditorPresentationPage(page) {
|
|
|
56203
56719
|
};
|
|
56204
56720
|
}
|
|
56205
56721
|
//#endregion
|
|
56722
|
+
//#region ../blocks/src/pageRendererBlockSupport.tsx
|
|
56723
|
+
/**
|
|
56724
|
+
* Support for rendering one block section: what a block that cannot render
|
|
56725
|
+
* leaves behind, how an override is found, and the props both override paths
|
|
56726
|
+
* hand to a component. `PageRenderer` owns the page-level composition; this
|
|
56727
|
+
* module owns the pieces it reaches for per block.
|
|
56728
|
+
*/
|
|
56729
|
+
function fallback(message) {
|
|
56730
|
+
return /* @__PURE__ */ jsx("div", {
|
|
56731
|
+
className: "rb-p-6 rb-caption",
|
|
56732
|
+
style: textColorStyle("mutedText"),
|
|
56733
|
+
children: message
|
|
56734
|
+
});
|
|
56735
|
+
}
|
|
56736
|
+
/**
|
|
56737
|
+
* A page with nothing to show. Authoring surfaces get the diagnostic; a public
|
|
56738
|
+
* page renders nothing at all rather than telling a visitor about the outline.
|
|
56739
|
+
*/
|
|
56740
|
+
function emptyPageNotice(audience) {
|
|
56741
|
+
if (audience === "public") return null;
|
|
56742
|
+
return /* @__PURE__ */ jsx("div", {
|
|
56743
|
+
className: "rb-p-6 rb-caption",
|
|
56744
|
+
style: textColorStyle("mutedText"),
|
|
56745
|
+
children: "No blocks found."
|
|
56746
|
+
});
|
|
56747
|
+
}
|
|
56748
|
+
/**
|
|
56749
|
+
* A public page dropped a block, so nothing in the rendered HTML says the
|
|
56750
|
+
* content is missing. Say it in the logs instead.
|
|
56751
|
+
*/
|
|
56752
|
+
function warnOmittedFromPublicPage(input) {
|
|
56753
|
+
try {
|
|
56754
|
+
console.warn("[blocks:PageRenderer] omitted a non-renderable block from a public page", {
|
|
56755
|
+
blockId: input.block.id ?? null,
|
|
56756
|
+
blockKind: input.block.kind,
|
|
56757
|
+
outcome: input.outcome,
|
|
56758
|
+
...input.admissionError ? { error: input.admissionError } : {}
|
|
56759
|
+
});
|
|
56760
|
+
} catch {}
|
|
56761
|
+
}
|
|
56762
|
+
function shouldDebugCustomBlocks() {
|
|
56763
|
+
try {
|
|
56764
|
+
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
56765
|
+
} catch {
|
|
56766
|
+
return false;
|
|
56767
|
+
}
|
|
56768
|
+
}
|
|
56769
|
+
/**
|
|
56770
|
+
* Look up a block override component by block kind.
|
|
56771
|
+
* Supports both full ("block.hero") and short ("hero") keys.
|
|
56772
|
+
*/
|
|
56773
|
+
function getBlockOverride(blockKind, overrides) {
|
|
56774
|
+
if (!overrides) return void 0;
|
|
56775
|
+
if (overrides[blockKind]) return overrides[blockKind];
|
|
56776
|
+
const shortKind = blockKind.replace(/^block\./, "");
|
|
56777
|
+
if (overrides[shortKind]) return overrides[shortKind];
|
|
56778
|
+
}
|
|
56779
|
+
/**
|
|
56780
|
+
* Resolves block content based on stage priority.
|
|
56781
|
+
* For draft/preview: draftContent takes priority over content.
|
|
56782
|
+
* For published: content is the source of truth.
|
|
56783
|
+
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
56784
|
+
*/
|
|
56785
|
+
function getRenderableContent(block, previewStage = "published") {
|
|
56786
|
+
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
56787
|
+
if (!isObjectRecord$1(source)) return {};
|
|
56788
|
+
return source;
|
|
56789
|
+
}
|
|
56790
|
+
function resolvePageRendererImageResolveContext(dataContext) {
|
|
56791
|
+
const input = dataContext?.imageResolveContext;
|
|
56792
|
+
return buildResolveImageUrlContext({
|
|
56793
|
+
siteId: input?.siteId ?? dataContext?.siteId,
|
|
56794
|
+
supabaseUrl: input?.supabaseUrl ?? dataContext?.supabaseUrl,
|
|
56795
|
+
mediaAssetIndex: input?.mediaAssetIndex,
|
|
56796
|
+
mediaAssets: input?.mediaAssets ?? dataContext?.mediaAssets
|
|
56797
|
+
});
|
|
56798
|
+
}
|
|
56799
|
+
/**
|
|
56800
|
+
* The RSC-safe props every override component receives, whether the block
|
|
56801
|
+
* resolved a manifest or is being rendered from an override alone.
|
|
56802
|
+
*/
|
|
56803
|
+
function buildBlockOverrideProps(input) {
|
|
56804
|
+
return {
|
|
56805
|
+
content: input.content,
|
|
56806
|
+
...overrideThemeProps(input.themeResources),
|
|
56807
|
+
...blockOverrideContextProps(input.dataContext),
|
|
56808
|
+
data: input.data,
|
|
56809
|
+
imageResolveContext: input.imageResolveContext,
|
|
56810
|
+
blockId: input.block.id ?? null,
|
|
56811
|
+
blockKind: input.block.kind
|
|
56812
|
+
};
|
|
56813
|
+
}
|
|
56814
|
+
/** The render-context half of the override props, absent context included. */
|
|
56815
|
+
function blockOverrideContextProps(dataContext) {
|
|
56816
|
+
const context = dataContext ?? {};
|
|
56817
|
+
return {
|
|
56818
|
+
siteId: context.siteId,
|
|
56819
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
56820
|
+
bookingTimeZone: context.bookingTimeZone,
|
|
56821
|
+
portalToken: context.portalToken ?? null,
|
|
56822
|
+
previewToken: context.previewToken ?? null,
|
|
56823
|
+
stage: context.previewStage,
|
|
56824
|
+
occurrenceContext: context.occurrenceContext ?? null,
|
|
56825
|
+
entrySubrouteContext: context.entrySubrouteContext ?? null,
|
|
56826
|
+
contentEntry: context.contentEntry ?? null,
|
|
56827
|
+
pageSurface: context.pageSurface ?? null,
|
|
56828
|
+
subject: context.subject ?? null,
|
|
56829
|
+
supabaseUrl: context.supabaseUrl
|
|
56830
|
+
};
|
|
56831
|
+
}
|
|
56832
|
+
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
56833
|
+
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
56834
|
+
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
56835
|
+
function getPerfSlowBlockRenderThresholdMs() {
|
|
56836
|
+
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
56837
|
+
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
56838
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
56839
|
+
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
56840
|
+
return cachedPerfSlowBlockRenderThresholdMs;
|
|
56841
|
+
}
|
|
56842
|
+
function getCachedBlockComponent(manifest) {
|
|
56843
|
+
const cached = blockComponentCache.get(manifest);
|
|
56844
|
+
if (cached) return cached;
|
|
56845
|
+
const created = makeDefaultBlockComponent({ manifest });
|
|
56846
|
+
blockComponentCache.set(manifest, created);
|
|
56847
|
+
return created;
|
|
56848
|
+
}
|
|
56849
|
+
//#endregion
|
|
56206
56850
|
//#region ../blocks/src/PageRenderer.tsx
|
|
56207
56851
|
function PageRenderer$1(props) {
|
|
56208
56852
|
const page = React$1.useMemo(() => toPresentationPage(props.page), [props.page]);
|
|
56209
56853
|
return /* @__PURE__ */ jsx(PageRendererCore, {
|
|
56210
56854
|
...props,
|
|
56211
|
-
page
|
|
56855
|
+
page,
|
|
56856
|
+
audience: "public"
|
|
56212
56857
|
});
|
|
56213
56858
|
}
|
|
56214
56859
|
function EditorPageRenderer$1(props) {
|
|
56215
56860
|
const page = React$1.useMemo(() => toEditorPresentationPage(props.page), [props.page]);
|
|
56216
56861
|
return /* @__PURE__ */ jsx(PageRendererCore, {
|
|
56217
56862
|
...props,
|
|
56218
|
-
page
|
|
56863
|
+
page,
|
|
56864
|
+
audience: "authoring"
|
|
56219
56865
|
});
|
|
56220
56866
|
}
|
|
56221
56867
|
function PageRendererCore(props) {
|
|
56222
|
-
const { page, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
56868
|
+
const { page, audience, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
56223
56869
|
const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
|
|
56224
56870
|
dataContext?.imageResolveContext,
|
|
56225
56871
|
dataContext?.mediaAssets,
|
|
56226
56872
|
dataContext?.siteId,
|
|
56227
56873
|
dataContext?.supabaseUrl
|
|
56228
56874
|
]);
|
|
56229
|
-
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(runtimeBoundaries ?? []), [runtimeBoundaries]);
|
|
56230
56875
|
const usesRuntimeBoundaries = runtimeBoundaries !== void 0;
|
|
56231
56876
|
const themeResources = React$1.useMemo(() => resolvePageThemeResources(props), [
|
|
56232
56877
|
props.rendering,
|
|
56233
56878
|
props.theme,
|
|
56234
56879
|
props.themeTokens
|
|
56235
56880
|
]);
|
|
56236
|
-
if (!page || page.blocks.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
56237
|
-
className: "rb-p-6 rb-caption",
|
|
56238
|
-
style: textColorStyle("mutedText"),
|
|
56239
|
-
children: "No blocks found."
|
|
56240
|
-
});
|
|
56241
56881
|
const resolvedRegistry = registry ?? getDefaultComponentRegistry();
|
|
56242
56882
|
setContextResolveImageUrlContext(imageResolveContext);
|
|
56243
|
-
|
|
56244
|
-
|
|
56245
|
-
|
|
56246
|
-
|
|
56247
|
-
|
|
56883
|
+
const sections = usePageSections(props, {
|
|
56884
|
+
page,
|
|
56885
|
+
themeResources,
|
|
56886
|
+
imageResolveContext,
|
|
56887
|
+
registry: resolvedRegistry
|
|
56888
|
+
});
|
|
56889
|
+
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(boundariesForSurvivingBlocks(runtimeBoundaries ?? [], sections.map(({ block }) => ({
|
|
56890
|
+
...block,
|
|
56891
|
+
content: getRenderableContent(block, dataContext?.previewStage)
|
|
56892
|
+
})))), [
|
|
56893
|
+
runtimeBoundaries,
|
|
56894
|
+
sections,
|
|
56895
|
+
dataContext?.previewStage
|
|
56896
|
+
]);
|
|
56897
|
+
if (sections.length === 0) return emptyPageNotice(audience);
|
|
56898
|
+
return /* @__PURE__ */ jsx("div", { children: sections.map((section, index) => {
|
|
56899
|
+
const { block, blockKey, node } = section;
|
|
56900
|
+
const blockPosition = pageBlockPosition(index, sections.length);
|
|
56901
|
+
const previousBlock = sections[index - 1]?.block ?? null;
|
|
56902
|
+
const nextBlock = sections[index + 1]?.block ?? null;
|
|
56248
56903
|
const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
|
|
56249
56904
|
const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
|
|
56250
56905
|
const boundaryElement = renderBoundaryAfterBlock({
|
|
@@ -56259,19 +56914,7 @@ function PageRendererCore(props) {
|
|
|
56259
56914
|
const managesOwnWrapper = definition?.manifest.behaviours?.managesOwnWrapper;
|
|
56260
56915
|
const renderableContent = getRenderableContent(block, dataContext?.previewStage);
|
|
56261
56916
|
const variantKey = definition?.manifest ? resolveVariantKey(definition.manifest, renderableContent) : null;
|
|
56262
|
-
|
|
56263
|
-
const blockSectionProps = buildBlockSectionProps({
|
|
56264
|
-
props,
|
|
56265
|
-
themeResources,
|
|
56266
|
-
block,
|
|
56267
|
-
blockKey,
|
|
56268
|
-
imageResolveContext,
|
|
56269
|
-
registry: resolvedRegistry
|
|
56270
|
-
});
|
|
56271
|
-
if (!shouldUseWrapper) {
|
|
56272
|
-
const rendered = /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
56273
|
-
return boundaryElement ? /* @__PURE__ */ jsxs(React$1.Fragment, { children: [rendered, boundaryElement] }, blockKey) : rendered;
|
|
56274
|
-
}
|
|
56917
|
+
if (!(!managesOwnWrapper || Boolean(wrapBlock))) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [node, boundaryElement] }, blockKey);
|
|
56275
56918
|
const wrapperClassName = [`block-${cleanKind}`, wrapBlock ? "group relative" : null].filter(Boolean).join(" ");
|
|
56276
56919
|
const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
|
|
56277
56920
|
const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
|
|
@@ -56296,7 +56939,7 @@ function PageRendererCore(props) {
|
|
|
56296
56939
|
"data-site-style-boundary-after": boundaryAfter?.metrics.size,
|
|
56297
56940
|
"data-style-group": styleGroup,
|
|
56298
56941
|
style: blockStyle,
|
|
56299
|
-
children:
|
|
56942
|
+
children: node
|
|
56300
56943
|
}, blockKey);
|
|
56301
56944
|
if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
|
|
56302
56945
|
if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
|
|
@@ -56309,18 +56952,69 @@ function PageRendererCore(props) {
|
|
|
56309
56952
|
})] }, blockKey);
|
|
56310
56953
|
}) });
|
|
56311
56954
|
}
|
|
56312
|
-
|
|
56313
|
-
|
|
56314
|
-
|
|
56315
|
-
|
|
56316
|
-
|
|
56317
|
-
|
|
56318
|
-
|
|
56955
|
+
/**
|
|
56956
|
+
* Memoised so that a re-render with unchanged inputs reuses the same section
|
|
56957
|
+
* elements and React can skip the block subtrees, exactly as the per-block memo
|
|
56958
|
+
* did before public pages started resolving their bodies up front.
|
|
56959
|
+
*/
|
|
56960
|
+
function usePageSections(props, input) {
|
|
56961
|
+
const { page, themeResources, imageResolveContext, registry } = input;
|
|
56962
|
+
return React$1.useMemo(() => admitPageSections({
|
|
56963
|
+
props,
|
|
56964
|
+
blocks: page?.blocks ?? [],
|
|
56965
|
+
audience: props.audience,
|
|
56966
|
+
themeResources,
|
|
56967
|
+
imageResolveContext,
|
|
56968
|
+
registry
|
|
56969
|
+
}), [
|
|
56970
|
+
page,
|
|
56971
|
+
props.audience,
|
|
56972
|
+
themeResources,
|
|
56973
|
+
imageResolveContext,
|
|
56974
|
+
registry,
|
|
56975
|
+
props.dataContext,
|
|
56976
|
+
props.routeMap,
|
|
56977
|
+
props.wrapBlock,
|
|
56978
|
+
props.blockOverrides,
|
|
56979
|
+
props.sdkConfig,
|
|
56980
|
+
props.customBlocks,
|
|
56981
|
+
props.previewAuthoring
|
|
56982
|
+
]);
|
|
56983
|
+
}
|
|
56984
|
+
/**
|
|
56985
|
+
* Resolves each block's rendered body before the page decides what to wrap.
|
|
56986
|
+
*
|
|
56987
|
+
* On a public page the body is resolved eagerly so a block that cannot render
|
|
56988
|
+
* is dropped along with its wrapper, its data attributes, its boundary reserve
|
|
56989
|
+
* space and its style-group sibling — the section body is the single source of
|
|
56990
|
+
* truth for whether the block is on the page (issue 1378). Authoring surfaces
|
|
56991
|
+
* keep the memoised component so the editor's render-loop guards still hold.
|
|
56992
|
+
*/
|
|
56993
|
+
function admitPageSections(input) {
|
|
56994
|
+
const sections = [];
|
|
56995
|
+
input.blocks.forEach((block, index) => {
|
|
56996
|
+
const blockKey = block.id ?? `${block.kind}-${index}`;
|
|
56997
|
+
const blockSectionProps = buildBlockSectionProps({
|
|
56998
|
+
props: input.props,
|
|
56999
|
+
themeResources: input.themeResources,
|
|
57000
|
+
block,
|
|
57001
|
+
blockKey,
|
|
57002
|
+
imageResolveContext: input.imageResolveContext,
|
|
57003
|
+
registry: input.registry
|
|
57004
|
+
});
|
|
57005
|
+
const node = input.audience === "public" ? renderBlockSection(blockSectionProps) : /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
57006
|
+
if (node === null || node === void 0) return;
|
|
57007
|
+
sections.push({
|
|
57008
|
+
block,
|
|
57009
|
+
blockKey,
|
|
57010
|
+
node
|
|
57011
|
+
});
|
|
56319
57012
|
});
|
|
57013
|
+
return sections;
|
|
56320
57014
|
}
|
|
56321
|
-
const resolveBookingTimeZone = (dataContext) => dataContext?.bookingTimeZone;
|
|
56322
57015
|
function buildBlockSectionProps(input) {
|
|
56323
57016
|
return {
|
|
57017
|
+
audience: input.props.audience,
|
|
56324
57018
|
themeResources: input.themeResources,
|
|
56325
57019
|
block: input.block,
|
|
56326
57020
|
dataContext: input.props.dataContext,
|
|
@@ -56336,40 +57030,24 @@ function buildBlockSectionProps(input) {
|
|
|
56336
57030
|
};
|
|
56337
57031
|
}
|
|
56338
57032
|
/**
|
|
56339
|
-
* Look up a block override component by block kind.
|
|
56340
|
-
* Supports both full ("block.hero") and short ("hero") keys.
|
|
56341
|
-
*/
|
|
56342
|
-
function getBlockOverride(blockKind, overrides) {
|
|
56343
|
-
if (!overrides) return void 0;
|
|
56344
|
-
if (overrides[blockKind]) return overrides[blockKind];
|
|
56345
|
-
const shortKind = blockKind.replace(/^block\./, "");
|
|
56346
|
-
if (overrides[shortKind]) return overrides[shortKind];
|
|
56347
|
-
}
|
|
56348
|
-
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
56349
|
-
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
56350
|
-
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
56351
|
-
function getPerfSlowBlockRenderThresholdMs() {
|
|
56352
|
-
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
56353
|
-
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
56354
|
-
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
56355
|
-
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
56356
|
-
return cachedPerfSlowBlockRenderThresholdMs;
|
|
56357
|
-
}
|
|
56358
|
-
function getCachedBlockComponent(manifest) {
|
|
56359
|
-
const cached = blockComponentCache.get(manifest);
|
|
56360
|
-
if (cached) return cached;
|
|
56361
|
-
const created = makeDefaultBlockComponent({ manifest });
|
|
56362
|
-
blockComponentCache.set(manifest, created);
|
|
56363
|
-
return created;
|
|
56364
|
-
}
|
|
56365
|
-
/**
|
|
56366
57033
|
* Memoized block section component.
|
|
56367
57034
|
*
|
|
56368
57035
|
* Custom memoization prevents render loops during editing by explicitly checking
|
|
56369
57036
|
* referential equality of all props that affect rendering. Standard React.memo
|
|
56370
57037
|
* wouldn't catch all the render triggers from the editor store.
|
|
56371
57038
|
*/
|
|
56372
|
-
const MemoBlockSection = React$1.memo(function MemoBlockSection(
|
|
57039
|
+
const MemoBlockSection = React$1.memo(function MemoBlockSection(props) {
|
|
57040
|
+
return renderBlockSection(props);
|
|
57041
|
+
}, (prev, next) => {
|
|
57042
|
+
return prev.audience === next.audience && prev.block === next.block && prev.registry === next.registry && prev.dataContext === next.dataContext && prev.themeResources === next.themeResources && prev.routeMap === next.routeMap && prev.block.presentationAdmission === next.block.presentationAdmission && prev.wrapBlock === next.wrapBlock && prev.blockOverrides === next.blockOverrides && prev.sdkConfig === next.sdkConfig && prev.customBlocks === next.customBlocks && prev.imageResolveContext === next.imageResolveContext && prev.previewAuthoring === next.previewAuthoring;
|
|
57043
|
+
});
|
|
57044
|
+
/**
|
|
57045
|
+
* Renders a single block body.
|
|
57046
|
+
*
|
|
57047
|
+
* Returns `null` when the block cannot render and the audience is `public`;
|
|
57048
|
+
* the caller treats that as "this block is not on the page".
|
|
57049
|
+
*/
|
|
57050
|
+
function renderBlockSection({ audience, themeResources, block, dataContext, registry, routeMap, wrapBlock, blockOverrides, sdkConfig, customBlocks, imageResolveContext, previewAuthoring }) {
|
|
56373
57051
|
const perfStart = globalThis.process?.env?.DEBUG_PERF === "1" && typeof window === "undefined" ? Date.now() : 0;
|
|
56374
57052
|
const fallbackImageResolveContext = getCurrentResolveImageUrlContext();
|
|
56375
57053
|
const resolvedImageResolveContext = imageResolveContext ?? fallbackImageResolveContext;
|
|
@@ -56389,19 +57067,30 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56389
57067
|
} catch {}
|
|
56390
57068
|
return node;
|
|
56391
57069
|
}
|
|
56392
|
-
|
|
56393
|
-
|
|
56394
|
-
|
|
56395
|
-
|
|
56396
|
-
|
|
56397
|
-
|
|
56398
|
-
|
|
56399
|
-
|
|
56400
|
-
|
|
56401
|
-
|
|
56402
|
-
override: false
|
|
57070
|
+
/**
|
|
57071
|
+
* The one place that decides what a block that cannot render leaves behind.
|
|
57072
|
+
* A public page gets nothing (and a log line, since dropping content on a
|
|
57073
|
+
* 200 is otherwise invisible); authoring surfaces get the diagnostic.
|
|
57074
|
+
*/
|
|
57075
|
+
function diagnostic(message, detail) {
|
|
57076
|
+
if (audience !== "public") return fallback(message);
|
|
57077
|
+
warnOmittedFromPublicPage({
|
|
57078
|
+
block,
|
|
57079
|
+
...detail
|
|
56403
57080
|
});
|
|
57081
|
+
return null;
|
|
57082
|
+
}
|
|
57083
|
+
function withBlockWrapper(node) {
|
|
57084
|
+
if (node === null) return null;
|
|
57085
|
+
return wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, node) }) : /* @__PURE__ */ jsx(Fragment, { children: node });
|
|
56404
57086
|
}
|
|
57087
|
+
if (block.presentationAdmission.kind === "invalid") return finish(diagnostic(`Invalid content for block kind "${block.kind}".`, {
|
|
57088
|
+
outcome: "invalidContent",
|
|
57089
|
+
admissionError: block.presentationAdmission.error
|
|
57090
|
+
}), {
|
|
57091
|
+
outcome: "invalidContent",
|
|
57092
|
+
override: false
|
|
57093
|
+
});
|
|
56405
57094
|
const OverrideComponent = getBlockOverride(block.kind, blockOverrides);
|
|
56406
57095
|
requireBlockOverride(themeResources, OverrideComponent, block.kind);
|
|
56407
57096
|
let definition = getBlockDefinition(block.kind);
|
|
@@ -56414,25 +57103,14 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56414
57103
|
}).findManifest(block.kind)?.descriptor.definition;
|
|
56415
57104
|
if (!definition) {
|
|
56416
57105
|
if (OverrideComponent) {
|
|
56417
|
-
const rendered = /* @__PURE__ */ jsx(OverrideComponent, {
|
|
57106
|
+
const rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...buildBlockOverrideProps({
|
|
57107
|
+
block,
|
|
56418
57108
|
content: getRenderableContent(block, dataContext?.previewStage),
|
|
56419
|
-
...overrideThemeProps(themeResources),
|
|
56420
57109
|
data: void 0,
|
|
56421
|
-
|
|
56422
|
-
|
|
56423
|
-
|
|
56424
|
-
|
|
56425
|
-
stage: dataContext?.previewStage,
|
|
56426
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
56427
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
56428
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
56429
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
56430
|
-
subject: dataContext?.subject ?? null,
|
|
56431
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
56432
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
56433
|
-
blockId: block.id ?? null,
|
|
56434
|
-
blockKind: block.kind
|
|
56435
|
-
});
|
|
57110
|
+
themeResources,
|
|
57111
|
+
dataContext,
|
|
57112
|
+
imageResolveContext: resolvedImageResolveContext
|
|
57113
|
+
}) });
|
|
56436
57114
|
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56437
57115
|
outcome: "overrideWithoutDefinition",
|
|
56438
57116
|
override: true,
|
|
@@ -56448,8 +57126,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56448
57126
|
dbCustomBlocksCount: customBlocks?.length ?? 0
|
|
56449
57127
|
});
|
|
56450
57128
|
} catch {}
|
|
56451
|
-
|
|
56452
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
57129
|
+
return finish(withBlockWrapper(diagnostic(block.kind.startsWith("custom.") ? `No renderer available for block kind "${block.kind}". The custom block definition was not found (missing manifest).` : `No renderer available for block kind "${block.kind}".`, { outcome: "missingDefinition" })), {
|
|
56453
57130
|
outcome: "missingDefinition",
|
|
56454
57131
|
override: false
|
|
56455
57132
|
});
|
|
@@ -56464,8 +57141,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56464
57141
|
manifestKeys: Object.keys(definition.manifest ?? {}).sort()
|
|
56465
57142
|
});
|
|
56466
57143
|
} catch {}
|
|
56467
|
-
|
|
56468
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
57144
|
+
return finish(withBlockWrapper(diagnostic(block.kind.startsWith("custom.") ? `No renderer available for block kind "${block.kind}". This custom block has no layout/variants, so it must be rendered via blockOverrides["${block.kind}"].` : `No renderer available for block kind "${block.kind}".`, { outcome: "missingLayout" })), {
|
|
56469
57145
|
outcome: "missingLayout",
|
|
56470
57146
|
override: false
|
|
56471
57147
|
});
|
|
@@ -56475,39 +57151,24 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56475
57151
|
if (block.presentationAdmission.kind === "editor") {
|
|
56476
57152
|
const draft = validateManifestDraft(compileManifestContentContract(definition.manifest), content);
|
|
56477
57153
|
if (draft.kind === "previewable") data = draft.data;
|
|
56478
|
-
else {
|
|
56479
|
-
|
|
56480
|
-
|
|
56481
|
-
|
|
56482
|
-
override: false
|
|
56483
|
-
});
|
|
56484
|
-
}
|
|
57154
|
+
else return finish(withBlockWrapper(diagnostic(`Unable to render block kind "${block.kind}".`, { outcome: "invalidPlaceholder" })), {
|
|
57155
|
+
outcome: "invalidPlaceholder",
|
|
57156
|
+
override: false
|
|
57157
|
+
});
|
|
56485
57158
|
}
|
|
56486
57159
|
try {
|
|
56487
57160
|
const routes = routeMap ?? dataContext?.routes;
|
|
56488
57161
|
const hydratedContent = routes ? hydrateManifestLinks(definition.manifest, data, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : data;
|
|
56489
57162
|
const resolved = resolveBlockData$1(block.id, definition.dataLoaders, dataContext, routes);
|
|
56490
57163
|
const hasResolvedData = !!resolved && typeof resolved === "object" && Object.keys(resolved).length > 0;
|
|
56491
|
-
const baseProps = {
|
|
57164
|
+
const baseProps = buildBlockOverrideProps({
|
|
57165
|
+
block,
|
|
56492
57166
|
content: hydratedContent,
|
|
56493
|
-
...overrideThemeProps(themeResources),
|
|
56494
57167
|
data: resolved,
|
|
56495
|
-
|
|
56496
|
-
|
|
56497
|
-
|
|
56498
|
-
|
|
56499
|
-
stage: dataContext?.previewStage,
|
|
56500
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
56501
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
56502
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
56503
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
56504
|
-
subject: dataContext?.subject ?? null,
|
|
56505
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
56506
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
56507
|
-
blockId: block.id ?? null,
|
|
56508
|
-
blockKind: block.kind
|
|
56509
|
-
};
|
|
56510
|
-
baseProps.bookingTimeZone = resolveBookingTimeZone(dataContext);
|
|
57168
|
+
themeResources,
|
|
57169
|
+
dataContext,
|
|
57170
|
+
imageResolveContext: resolvedImageResolveContext
|
|
57171
|
+
});
|
|
56511
57172
|
let rendered;
|
|
56512
57173
|
if (OverrideComponent) rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...baseProps });
|
|
56513
57174
|
else {
|
|
@@ -56554,39 +57215,11 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56554
57215
|
hasResolvedData
|
|
56555
57216
|
});
|
|
56556
57217
|
} catch {
|
|
56557
|
-
|
|
56558
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
57218
|
+
return finish(withBlockWrapper(diagnostic(block.presentationAdmission.kind === "editor" ? `Unable to render block kind "${block.kind}".` : `Invalid content for block kind "${block.kind}".`, { outcome: "exception" })), {
|
|
56559
57219
|
outcome: "exception",
|
|
56560
57220
|
override: false
|
|
56561
57221
|
});
|
|
56562
57222
|
}
|
|
56563
|
-
}, (prev, next) => {
|
|
56564
|
-
return prev.block === next.block && prev.registry === next.registry && prev.dataContext === next.dataContext && prev.themeResources === next.themeResources && prev.routeMap === next.routeMap && prev.block.presentationAdmission === next.block.presentationAdmission && prev.wrapBlock === next.wrapBlock && prev.blockOverrides === next.blockOverrides && prev.sdkConfig === next.sdkConfig && prev.customBlocks === next.customBlocks && prev.imageResolveContext === next.imageResolveContext && prev.previewAuthoring === next.previewAuthoring;
|
|
56565
|
-
});
|
|
56566
|
-
function fallback(message) {
|
|
56567
|
-
return /* @__PURE__ */ jsx("div", {
|
|
56568
|
-
className: "rb-p-6 rb-caption",
|
|
56569
|
-
style: textColorStyle("mutedText"),
|
|
56570
|
-
children: message
|
|
56571
|
-
});
|
|
56572
|
-
}
|
|
56573
|
-
function shouldDebugCustomBlocks() {
|
|
56574
|
-
try {
|
|
56575
|
-
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
56576
|
-
} catch {
|
|
56577
|
-
return false;
|
|
56578
|
-
}
|
|
56579
|
-
}
|
|
56580
|
-
/**
|
|
56581
|
-
* Resolves block content based on stage priority.
|
|
56582
|
-
* For draft/preview: draftContent takes priority over content.
|
|
56583
|
-
* For published: content is the source of truth.
|
|
56584
|
-
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
56585
|
-
*/
|
|
56586
|
-
function getRenderableContent(block, previewStage = "published") {
|
|
56587
|
-
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
56588
|
-
if (!isObjectRecord$1(source)) return {};
|
|
56589
|
-
return source;
|
|
56590
57223
|
}
|
|
56591
57224
|
//#endregion
|
|
56592
57225
|
//#region ../theme-core/src/generated/containerResponsiveThemeCss.ts
|
|
@@ -65512,6 +66145,21 @@ const containerResponsiveThemeCss = `/*
|
|
|
65512
66145
|
outline-offset: 2px;
|
|
65513
66146
|
}
|
|
65514
66147
|
|
|
66148
|
+
/* Event titles always link to the event page, so a card or row whose CTA is
|
|
66149
|
+
hidden (sold out, closed) is never a dead end (issue 1372). The link inherits
|
|
66150
|
+
the heading's colour and only reveals itself on hover or keyboard focus.
|
|
66151
|
+
Shared by EventCard, EventCompactRow and the offerings-preview course cards via \`.event-title-link\`. */
|
|
66152
|
+
:where(.theme-scope) .event-title-link {
|
|
66153
|
+
color: inherit;
|
|
66154
|
+
text-decoration: none;
|
|
66155
|
+
}
|
|
66156
|
+
|
|
66157
|
+
:where(.theme-scope) .event-title-link:hover,
|
|
66158
|
+
:where(.theme-scope) .event-title-link:focus-visible {
|
|
66159
|
+
text-decoration: underline;
|
|
66160
|
+
text-underline-offset: 0.12em;
|
|
66161
|
+
}
|
|
66162
|
+
|
|
65515
66163
|
/* =============================================================================
|
|
65516
66164
|
* Block: Carousel Navigation (Phase 3 client component migration)
|
|
65517
66165
|
* =============================================================================
|
|
@@ -73838,6 +74486,83 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
73838
74486
|
errors: ["validation:invalid_input", "server:internal_error"]
|
|
73839
74487
|
}
|
|
73840
74488
|
});
|
|
74489
|
+
//#endregion
|
|
74490
|
+
//#region ../api/src/sdkMediaEndpointDefinitions.ts
|
|
74491
|
+
const SDK_MEDIA_ENDPOINT_DEFINITIONS = {
|
|
74492
|
+
sdkMediaExists: {
|
|
74493
|
+
path: "/sdk/{siteId}/media/exists",
|
|
74494
|
+
method: "POST",
|
|
74495
|
+
errors: ["validation:invalid_input"],
|
|
74496
|
+
auth: "service",
|
|
74497
|
+
responseKind: "json"
|
|
74498
|
+
},
|
|
74499
|
+
sdkMediaFindByChecksum: {
|
|
74500
|
+
path: "/sdk/{siteId}/media/find-by-checksum",
|
|
74501
|
+
method: "POST",
|
|
74502
|
+
errors: ["server:internal_error"],
|
|
74503
|
+
auth: "service",
|
|
74504
|
+
responseKind: "json"
|
|
74505
|
+
},
|
|
74506
|
+
sdkMediaResolve: {
|
|
74507
|
+
path: "/sdk/{siteId}/media/resolve",
|
|
74508
|
+
method: "POST",
|
|
74509
|
+
errors: ["server:internal_error"],
|
|
74510
|
+
auth: "service",
|
|
74511
|
+
responseKind: "json"
|
|
74512
|
+
},
|
|
74513
|
+
sdkMediaList: {
|
|
74514
|
+
path: "/sdk/{siteId}/media",
|
|
74515
|
+
method: "GET",
|
|
74516
|
+
auth: "service",
|
|
74517
|
+
responseKind: "json"
|
|
74518
|
+
},
|
|
74519
|
+
sdkMediaDownload: {
|
|
74520
|
+
path: "/sdk/{siteId}/media/{identifier}/download",
|
|
74521
|
+
method: "GET",
|
|
74522
|
+
auth: "service",
|
|
74523
|
+
responseKind: "json"
|
|
74524
|
+
},
|
|
74525
|
+
sdkMediaDeletePreview: {
|
|
74526
|
+
path: "/sdk/{siteId}/media/{identifier}/delete-preview",
|
|
74527
|
+
method: "POST",
|
|
74528
|
+
auth: "service",
|
|
74529
|
+
responseKind: "json"
|
|
74530
|
+
},
|
|
74531
|
+
sdkMediaDeleteApply: {
|
|
74532
|
+
path: "/sdk/{siteId}/media/{identifier}",
|
|
74533
|
+
method: "DELETE",
|
|
74534
|
+
auth: "service",
|
|
74535
|
+
responseKind: "json"
|
|
74536
|
+
},
|
|
74537
|
+
sdkMediaUploadUrl: {
|
|
74538
|
+
path: "/sdk/{siteId}/media/upload-url",
|
|
74539
|
+
method: "POST",
|
|
74540
|
+
errors: [
|
|
74541
|
+
"resource:not_found",
|
|
74542
|
+
"validation:invalid_input",
|
|
74543
|
+
"resource:conflict",
|
|
74544
|
+
"validation:payload_too_large",
|
|
74545
|
+
"billing:plan_limit_exceeded",
|
|
74546
|
+
"server:internal_error"
|
|
74547
|
+
],
|
|
74548
|
+
auth: "service",
|
|
74549
|
+
responseKind: "json"
|
|
74550
|
+
},
|
|
74551
|
+
sdkMediaUploadFinalize: {
|
|
74552
|
+
path: "/sdk/{siteId}/media/upload",
|
|
74553
|
+
method: "POST",
|
|
74554
|
+
errors: [
|
|
74555
|
+
"resource:not_found",
|
|
74556
|
+
"validation:invalid_input",
|
|
74557
|
+
"resource:conflict",
|
|
74558
|
+
"validation:payload_too_large",
|
|
74559
|
+
"billing:plan_limit_exceeded",
|
|
74560
|
+
"server:internal_error"
|
|
74561
|
+
],
|
|
74562
|
+
auth: "service",
|
|
74563
|
+
responseKind: "json"
|
|
74564
|
+
}
|
|
74565
|
+
};
|
|
73841
74566
|
({
|
|
73842
74567
|
...ACCOUNT_ENDPOINT_DEFINITIONS,
|
|
73843
74568
|
...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
|
|
@@ -73846,7 +74571,7 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
73846
74571
|
...NEWSLETTER_ENDPOINT_DEFINITIONS,
|
|
73847
74572
|
...APPOINTMENT_ENDPOINT_DEFINITIONS,
|
|
73848
74573
|
...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
|
|
73849
|
-
}, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.formById, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEventFilters, { ...PUBLIC_WHOLE_SERIES_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicTeamMembers, TAG_TEMPLATES.publicWholeSeriesOfferings, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicPasses, TAG_TEMPLATES.publicMemberships, TAG_TEMPLATES.publicProducts, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
|
|
74574
|
+
}, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.formById, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEventFilters, { ...PUBLIC_WHOLE_SERIES_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicTeamMembers, TAG_TEMPLATES.publicWholeSeriesOfferings, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicPasses, TAG_TEMPLATES.publicMemberships, TAG_TEMPLATES.publicProducts, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_MEDIA_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
|
|
73850
74575
|
z$1.object({ refreshToken: z$1.string().min(1).max(4096) }).strict();
|
|
73851
74576
|
z$1.object({
|
|
73852
74577
|
accessToken: z$1.string().min(1).max(4096),
|
|
@@ -74499,6 +75224,50 @@ z$1.object({
|
|
|
74499
75224
|
title: z$1.string(),
|
|
74500
75225
|
slug: z$1.string().min(1)
|
|
74501
75226
|
});
|
|
75227
|
+
z$1.object({
|
|
75228
|
+
signedUrl: z$1.string().url(),
|
|
75229
|
+
token: z$1.string().min(1),
|
|
75230
|
+
storagePath: z$1.string().min(1)
|
|
75231
|
+
}).strict();
|
|
75232
|
+
const backupManifestSchema = z$1.object({
|
|
75233
|
+
siteId: z$1.string().min(1),
|
|
75234
|
+
siteSlug: z$1.string().min(1),
|
|
75235
|
+
siteTitle: z$1.string(),
|
|
75236
|
+
exportedAt: z$1.string().min(1),
|
|
75237
|
+
tableCount: z$1.number().int().nonnegative(),
|
|
75238
|
+
totalRecords: z$1.number().int().nonnegative(),
|
|
75239
|
+
pageCount: z$1.number().int().nonnegative(),
|
|
75240
|
+
blockCount: z$1.number().int().nonnegative(),
|
|
75241
|
+
contentEntryCount: z$1.number().int().nonnegative(),
|
|
75242
|
+
formCount: z$1.number().int().nonnegative(),
|
|
75243
|
+
navigationMenuCount: z$1.number().int().nonnegative(),
|
|
75244
|
+
mediaCount: z$1.number().int().nonnegative(),
|
|
75245
|
+
mediaBytes: z$1.number().int().nonnegative()
|
|
75246
|
+
}).strict();
|
|
75247
|
+
z$1.object({
|
|
75248
|
+
ok: z$1.literal(true),
|
|
75249
|
+
manifest: backupManifestSchema,
|
|
75250
|
+
storagePath: z$1.string().min(1)
|
|
75251
|
+
}).strict();
|
|
75252
|
+
z$1.object({
|
|
75253
|
+
ok: z$1.literal(true),
|
|
75254
|
+
siteId: brandedIdSchema(parseSiteId, "site id"),
|
|
75255
|
+
slug: z$1.string().min(1),
|
|
75256
|
+
title: z$1.string(),
|
|
75257
|
+
counts: z$1.record(z$1.string(), z$1.object({
|
|
75258
|
+
created: z$1.number().int().nonnegative(),
|
|
75259
|
+
updated: z$1.number().int().nonnegative(),
|
|
75260
|
+
skipped: z$1.number().int().nonnegative(),
|
|
75261
|
+
failed: z$1.number().int().nonnegative()
|
|
75262
|
+
}).strict()),
|
|
75263
|
+
warnings: z$1.array(z$1.string()),
|
|
75264
|
+
errors: z$1.array(z$1.object({
|
|
75265
|
+
table: z$1.string(),
|
|
75266
|
+
id: z$1.string(),
|
|
75267
|
+
error: z$1.string()
|
|
75268
|
+
}).strict())
|
|
75269
|
+
}).strict();
|
|
75270
|
+
z$1.object({ deleted: z$1.boolean() }).strict();
|
|
74502
75271
|
const apiKeyIdSchema = brandedIdSchema(parseApiKeyId, "API key id");
|
|
74503
75272
|
const standardKeyIdsSchema = z$1.object({
|
|
74504
75273
|
live: apiKeyIdSchema.optional(),
|
|
@@ -78860,9 +79629,7 @@ function admitMaterializedSdkTemplateBlockContent(input) {
|
|
|
78860
79629
|
const baseContent = sanitizeSdkRuntimeBlockContent(input.block.blockKind, input.block.content ?? {});
|
|
78861
79630
|
const bindings = input.block.bindings ?? {};
|
|
78862
79631
|
const materialized = input.block.scope === "entry" && Object.keys(bindings).length > 0 ? applyBindingsToContent(baseContent, bindings, input.bindingContext) : baseContent;
|
|
78863
|
-
|
|
78864
|
-
const stored = admitStoredManifestContent(contract, materialized);
|
|
78865
|
-
return stored.ok ? deriveRenderableManifestContent(contract, stored.value, (content) => content) : stored;
|
|
79632
|
+
return admitTransportedRenderableManifestContent(compileManifestContentContract(input.manifestOrFields), materialized);
|
|
78866
79633
|
}
|
|
78867
79634
|
function createTemplateRenderTarget(source, templateId) {
|
|
78868
79635
|
switch (source.kind) {
|
|
@@ -78994,13 +79761,7 @@ function admitEntrySlotBlock(block, customBlocks) {
|
|
|
78994
79761
|
if (transportedAdmission) return invalidRenderableBlock(base, transportedAdmission.error);
|
|
78995
79762
|
const manifest = resolveSdkBlockManifest(block.kind, customBlocks);
|
|
78996
79763
|
if (manifest === null) return invalidRenderableBlock(base, { kind: "manifest_unavailable" });
|
|
78997
|
-
const
|
|
78998
|
-
const stored = admitStoredManifestContent(contract, block.content ?? {});
|
|
78999
|
-
if (!stored.ok) return invalidRenderableBlock(base, {
|
|
79000
|
-
kind: "invalid_manifest_content",
|
|
79001
|
-
error: stored.error
|
|
79002
|
-
});
|
|
79003
|
-
const renderable = deriveRenderableManifestContent(contract, stored.value, (content) => content);
|
|
79764
|
+
const renderable = admitTransportedRenderableManifestContent(compileManifestContentContract(manifest), block.content ?? {});
|
|
79004
79765
|
return renderable.ok ? {
|
|
79005
79766
|
...base,
|
|
79006
79767
|
admission: {
|