@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
|
@@ -7665,6 +7665,11 @@ const gradientConfigSchema = z.object({
|
|
|
7665
7665
|
position: z.string().optional(),
|
|
7666
7666
|
stops: z.array(gradientStopSchema).min(2)
|
|
7667
7667
|
});
|
|
7668
|
+
const gradientsSchema = z.object({
|
|
7669
|
+
button: z.string().min(1).nullable().optional(),
|
|
7670
|
+
hero: z.string().min(1).nullable().optional(),
|
|
7671
|
+
background: z.string().min(1).nullable().optional()
|
|
7672
|
+
});
|
|
7668
7673
|
//#endregion
|
|
7669
7674
|
//#region ../theme-core/src/interactive/baseSchema.ts
|
|
7670
7675
|
/**
|
|
@@ -8666,11 +8671,30 @@ const headerContainerSchema = z.object({
|
|
|
8666
8671
|
tint: z.string().nullable().optional(),
|
|
8667
8672
|
opacity: z.number().min(0).max(1).default(.12)
|
|
8668
8673
|
}).optional();
|
|
8669
|
-
|
|
8674
|
+
//#endregion
|
|
8675
|
+
//#region ../theme-core/src/header/interactionPalette.ts
|
|
8676
|
+
/**
|
|
8677
|
+
* The palettes that fill the nav link with a colour on hover and focus. Each
|
|
8678
|
+
* one names a background/foreground token pair.
|
|
8679
|
+
*/
|
|
8680
|
+
const headerInteractionPillPaletteValues = [
|
|
8670
8681
|
"primary",
|
|
8671
8682
|
"secondary",
|
|
8672
8683
|
"accent"
|
|
8673
|
-
]
|
|
8684
|
+
];
|
|
8685
|
+
/**
|
|
8686
|
+
* "No pill": the nav link keeps whatever hover treatment its recipe compiled,
|
|
8687
|
+
* and the palette step writes nothing at all (#1318 Phase 4c decision 2).
|
|
8688
|
+
*
|
|
8689
|
+
* It exists because "a colour change on hover, no filled background" was not
|
|
8690
|
+
* expressible in the vocabulary, and it is what
|
|
8691
|
+
* `anna-raven-therapy-and-yoga` and `kirtan-lucy` have rendered since before
|
|
8692
|
+
* the palette existed. Without it those two rows can only be classified as a
|
|
8693
|
+
* conscious visual change.
|
|
8694
|
+
*/
|
|
8695
|
+
const headerInteractionQuietPalette = "quiet";
|
|
8696
|
+
const headerInteractionPaletteValues = [...headerInteractionPillPaletteValues, headerInteractionQuietPalette];
|
|
8697
|
+
const headerInteractionPaletteSchema = z.enum(headerInteractionPaletteValues);
|
|
8674
8698
|
//#endregion
|
|
8675
8699
|
//#region ../theme-core/src/boxBackgroundSchema.ts
|
|
8676
8700
|
const boxBackgroundOverlaySchema = z.preprocess(normalizeAuthoringOpacity, z.discriminatedUnion("type", [
|
|
@@ -9052,11 +9076,12 @@ const inputStyle = z.object({
|
|
|
9052
9076
|
]),
|
|
9053
9077
|
label: z.enum(["inside", "above"])
|
|
9054
9078
|
});
|
|
9055
|
-
const
|
|
9079
|
+
const headerPositioningValues = [
|
|
9056
9080
|
"static",
|
|
9057
9081
|
"sticky",
|
|
9058
9082
|
"fixed"
|
|
9059
|
-
]
|
|
9083
|
+
];
|
|
9084
|
+
const headerPositioning = z.enum(headerPositioningValues);
|
|
9060
9085
|
const headerNavStyle = z.enum([
|
|
9061
9086
|
"minimal",
|
|
9062
9087
|
"underline",
|
|
@@ -9191,7 +9216,7 @@ const headerSchema = z.object({
|
|
|
9191
9216
|
variant: headerVariantWithLegacyAliasSchema,
|
|
9192
9217
|
positioning: headerPositioning,
|
|
9193
9218
|
shrinkOnScroll: z.boolean(),
|
|
9194
|
-
maxWidth: headerMaxWidth,
|
|
9219
|
+
maxWidth: headerMaxWidth.default("container"),
|
|
9195
9220
|
scrollTreatment: headerScrollTreatmentSchema,
|
|
9196
9221
|
background: headerBackgroundSchema.default({
|
|
9197
9222
|
type: "color",
|
|
@@ -9390,11 +9415,6 @@ const bodyTextSchema = z.object({ intro: z.object({
|
|
|
9390
9415
|
paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
|
|
9391
9416
|
headingSize: bodyTextIntroTypographySizeSchema.optional()
|
|
9392
9417
|
}).optional() }).optional();
|
|
9393
|
-
const gradientsSchema = z.object({
|
|
9394
|
-
button: z.string().min(1).nullable().optional(),
|
|
9395
|
-
hero: z.string().min(1).nullable().optional(),
|
|
9396
|
-
background: z.string().min(1).nullable().optional()
|
|
9397
|
-
});
|
|
9398
9418
|
/**
|
|
9399
9419
|
* Testimonial quote-mark treatment, owned by the site style (analogous to the
|
|
9400
9420
|
* accordion icon). `style` is the coarse selector; the optional fields fine-tune
|
|
@@ -14831,6 +14851,12 @@ function cardRoleFromLegacyVocabulary(value) {
|
|
|
14831
14851
|
*/
|
|
14832
14852
|
const cardRoleWithLegacyAliasSchema = z.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
|
|
14833
14853
|
//#endregion
|
|
14854
|
+
//#region ../theme-core/src/site-styles/compilationPlan.ts
|
|
14855
|
+
const defaultHeaderBrandPreferences = {
|
|
14856
|
+
brandDisplay: "auto",
|
|
14857
|
+
logoSize: "auto"
|
|
14858
|
+
};
|
|
14859
|
+
//#endregion
|
|
14834
14860
|
//#region ../theme-core/src/site-styles/sectionTransitions.ts
|
|
14835
14861
|
function boundaryRuntimeMetrics(family, size, heightExpression) {
|
|
14836
14862
|
return {
|
|
@@ -17220,6 +17246,12 @@ const overlayLogoAccommodations = [
|
|
|
17220
17246
|
inlineMarkWithTitleLogoAccommodation,
|
|
17221
17247
|
unbackedOverlayLogoAccommodation
|
|
17222
17248
|
];
|
|
17249
|
+
const allCuratedLogoAccommodations = [
|
|
17250
|
+
...inlineClassicLogoAccommodations,
|
|
17251
|
+
...centeredLogoAccommodations,
|
|
17252
|
+
...overlayLogoAccommodations
|
|
17253
|
+
];
|
|
17254
|
+
new Set(allCuratedLogoAccommodations.filter((option) => option.providesBacking === true).map((option) => option.pattern));
|
|
17223
17255
|
const headerLayoutCatalog = defineExactCatalog()([
|
|
17224
17256
|
{
|
|
17225
17257
|
id: asHeaderLayoutId("classic"),
|
|
@@ -17369,6 +17401,10 @@ defineLookOptionGroup({
|
|
|
17369
17401
|
{
|
|
17370
17402
|
id: "secondary",
|
|
17371
17403
|
label: "Secondary"
|
|
17404
|
+
},
|
|
17405
|
+
{
|
|
17406
|
+
id: headerInteractionQuietPalette,
|
|
17407
|
+
label: "Text only"
|
|
17372
17408
|
}
|
|
17373
17409
|
]
|
|
17374
17410
|
});
|
|
@@ -18084,6 +18120,37 @@ function uniformSectionForeground(token) {
|
|
|
18084
18120
|
supportingColor: token
|
|
18085
18121
|
};
|
|
18086
18122
|
}
|
|
18123
|
+
function compileTransitionRuntime(input) {
|
|
18124
|
+
return {
|
|
18125
|
+
treatment: input.treatment,
|
|
18126
|
+
from: input.from,
|
|
18127
|
+
to: input.to,
|
|
18128
|
+
cssVars: {
|
|
18129
|
+
from: transitionEndpointCssValue(input.from),
|
|
18130
|
+
to: transitionEndpointCssValue(input.to)
|
|
18131
|
+
}
|
|
18132
|
+
};
|
|
18133
|
+
}
|
|
18134
|
+
function transitionEndpointCssValue(endpoint) {
|
|
18135
|
+
switch (endpoint.kind) {
|
|
18136
|
+
case "color-token": return themeColorTokenCssValue(endpoint.token);
|
|
18137
|
+
case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
|
|
18138
|
+
default: return assertNever$4(endpoint);
|
|
18139
|
+
}
|
|
18140
|
+
}
|
|
18141
|
+
function tokenForTransitionSurface(surface) {
|
|
18142
|
+
switch (surface) {
|
|
18143
|
+
case "base":
|
|
18144
|
+
case "hero-image": return "background";
|
|
18145
|
+
case "muted-band": return "muted";
|
|
18146
|
+
case "accent-band": return "accent";
|
|
18147
|
+
case "brand-depth-band": return "primary-800";
|
|
18148
|
+
default: return assertNever$4(surface);
|
|
18149
|
+
}
|
|
18150
|
+
}
|
|
18151
|
+
function themeColorTokenCssValue(token) {
|
|
18152
|
+
return `rgb(var(--tb-${token}))`;
|
|
18153
|
+
}
|
|
18087
18154
|
//#endregion
|
|
18088
18155
|
//#region ../theme-core/src/site-styles/curatedSiteStyles.ts
|
|
18089
18156
|
const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
|
|
@@ -19793,6 +19860,123 @@ const blockDesignFallback = {
|
|
|
19793
19860
|
};
|
|
19794
19861
|
blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
|
|
19795
19862
|
//#endregion
|
|
19863
|
+
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
19864
|
+
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
19865
|
+
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
19866
|
+
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
19867
|
+
function parseSiteChromeBackgroundColorToken(value) {
|
|
19868
|
+
const token = parseBackgroundAppearanceColorToken(value);
|
|
19869
|
+
return token ? token : null;
|
|
19870
|
+
}
|
|
19871
|
+
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
19872
|
+
return tokenNames.flatMap((name) => {
|
|
19873
|
+
const token = parseSiteChromeBackgroundColorToken(name);
|
|
19874
|
+
return token ? [token] : [];
|
|
19875
|
+
});
|
|
19876
|
+
}
|
|
19877
|
+
//#endregion
|
|
19878
|
+
//#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
|
|
19879
|
+
const headerNavTypographyPreferenceValues = [
|
|
19880
|
+
"inherit",
|
|
19881
|
+
"body",
|
|
19882
|
+
"heading"
|
|
19883
|
+
];
|
|
19884
|
+
const headerNavFontSizePreferenceValues = [
|
|
19885
|
+
"inherit",
|
|
19886
|
+
"sm",
|
|
19887
|
+
"base",
|
|
19888
|
+
"lg",
|
|
19889
|
+
"xl"
|
|
19890
|
+
];
|
|
19891
|
+
const defaultHeaderNavPreferences = {
|
|
19892
|
+
typography: "inherit",
|
|
19893
|
+
fontSize: "inherit",
|
|
19894
|
+
interactionPalette: "recommended"
|
|
19895
|
+
};
|
|
19896
|
+
/**
|
|
19897
|
+
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
19898
|
+
* nav link style. This is where the precedence contract lands:
|
|
19899
|
+
*
|
|
19900
|
+
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
19901
|
+
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
19902
|
+
* baked into `style` before this call (see `applyNavTypography` in
|
|
19903
|
+
* headerLayoutStyles.ts).
|
|
19904
|
+
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
19905
|
+
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
19906
|
+
* through the body role.
|
|
19907
|
+
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
19908
|
+
* overrides which font ROLE the treatment resolves through. It never changes
|
|
19909
|
+
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
19910
|
+
*/
|
|
19911
|
+
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
19912
|
+
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
19913
|
+
...style,
|
|
19914
|
+
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
19915
|
+
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
19916
|
+
};
|
|
19917
|
+
}
|
|
19918
|
+
function readHeaderNavFontSizePreference(value) {
|
|
19919
|
+
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
19920
|
+
}
|
|
19921
|
+
//#endregion
|
|
19922
|
+
//#region ../theme-core/src/site-styles/headerBrandPresentation.ts
|
|
19923
|
+
/**
|
|
19924
|
+
* Whether the header wears a brand presentation at all (#1318 Phase 4c).
|
|
19925
|
+
*
|
|
19926
|
+
* `applyHeaderBrandPresentation` is unconditional today: given a logo context
|
|
19927
|
+
* it always writes a `logo` object carrying a resolved display, size and
|
|
19928
|
+
* accommodation. That makes "this header has no brand presentation block" —
|
|
19929
|
+
* which is what several live sites persist, including one whose site *does*
|
|
19930
|
+
* have a logo asset — a state the preference record could not express, so a
|
|
19931
|
+
* recompile would silently give the site a logo treatment it has never
|
|
19932
|
+
* rendered.
|
|
19933
|
+
*
|
|
19934
|
+
* This is the third field in this phase with that shape, after the interaction
|
|
19935
|
+
* palette and the nav text size: the persisted bytes carry intent the reader
|
|
19936
|
+
* cannot say, so the recompile "corrects" it. The fix is the same one — widen
|
|
19937
|
+
* the closed vocabulary with a named member and derive it by baseline
|
|
19938
|
+
* comparison — and it is a *widening*, not a restyle: `'recommended'` is the
|
|
19939
|
+
* default and reproduces today's behaviour exactly, so no row whose bytes carry
|
|
19940
|
+
* a presentation compiles differently for `'plain'` existing.
|
|
19941
|
+
*
|
|
19942
|
+
* `'plain'` is deliberately not "preserve whatever bytes are there". It is a
|
|
19943
|
+
* constructive instruction — *write no presentation block* — so Phase 4e's
|
|
19944
|
+
* compiler, which builds the header from nothing, can produce it.
|
|
19945
|
+
*/
|
|
19946
|
+
const headerBrandPresentationValues = ["recommended", "plain"];
|
|
19947
|
+
defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
|
|
19948
|
+
//#endregion
|
|
19949
|
+
//#region ../theme-core/src/site-styles/previewCompiler.ts
|
|
19950
|
+
function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
|
|
19951
|
+
const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
|
|
19952
|
+
const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
|
|
19953
|
+
if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
|
|
19954
|
+
const transition = compileTransitionRuntime({
|
|
19955
|
+
treatment: boundary.treatment,
|
|
19956
|
+
from: fromSurface,
|
|
19957
|
+
to: toSurface
|
|
19958
|
+
});
|
|
19959
|
+
return {
|
|
19960
|
+
...boundary,
|
|
19961
|
+
from: transition.from,
|
|
19962
|
+
to: transition.to,
|
|
19963
|
+
cssVars: transition.cssVars
|
|
19964
|
+
};
|
|
19965
|
+
}
|
|
19966
|
+
function contentOwnedBackgroundEndpoint(content) {
|
|
19967
|
+
const background = readRecord(readRecord(content?._sectionStyles)?.background);
|
|
19968
|
+
if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
|
|
19969
|
+
const token = parseThemeColorToken(background.color);
|
|
19970
|
+
if (!token) return null;
|
|
19971
|
+
return {
|
|
19972
|
+
kind: "color-token",
|
|
19973
|
+
token
|
|
19974
|
+
};
|
|
19975
|
+
}
|
|
19976
|
+
function readRecord(value) {
|
|
19977
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
19978
|
+
}
|
|
19979
|
+
//#endregion
|
|
19796
19980
|
//#region ../theme-core/src/site-styles/generatedSchemas.ts
|
|
19797
19981
|
function uniqueNonEmpty(values) {
|
|
19798
19982
|
const uniqueValues = [...new Set(values)];
|
|
@@ -19861,6 +20045,56 @@ z.object({
|
|
|
19861
20045
|
blocks: z.array(generatedPageDesignBlockIntentSchema)
|
|
19862
20046
|
}).strict();
|
|
19863
20047
|
//#endregion
|
|
20048
|
+
//#region ../theme-core/src/site-styles/headerPreferencesPersistence.ts
|
|
20049
|
+
const backgroundColorTokenSchema = z.unknown().transform((value, ctx) => {
|
|
20050
|
+
const token = parseSiteChromeBackgroundColorToken(value);
|
|
20051
|
+
if (token === null) {
|
|
20052
|
+
ctx.addIssue({
|
|
20053
|
+
code: "custom",
|
|
20054
|
+
message: "Unknown site chrome background colour token"
|
|
20055
|
+
});
|
|
20056
|
+
return z.NEVER;
|
|
20057
|
+
}
|
|
20058
|
+
return token;
|
|
20059
|
+
});
|
|
20060
|
+
const backgroundOverrideSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("default") }).strict(), z.object({
|
|
20061
|
+
kind: z.literal("color"),
|
|
20062
|
+
token: backgroundColorTokenSchema
|
|
20063
|
+
}).strict()]);
|
|
20064
|
+
const logoTextIntentSchema = z.object({
|
|
20065
|
+
mobileMaxWidth: z.enum([
|
|
20066
|
+
"xs",
|
|
20067
|
+
"sm",
|
|
20068
|
+
"md"
|
|
20069
|
+
]).optional(),
|
|
20070
|
+
mobileWrapLines: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
20071
|
+
hideOnShrink: z.boolean().optional()
|
|
20072
|
+
}).strict();
|
|
20073
|
+
const structureSchema = z.object({
|
|
20074
|
+
positioning: z.union([z.literal("inherit"), z.enum(headerPositioningValues)]),
|
|
20075
|
+
shrinkOnScroll: z.union([z.literal("inherit"), z.boolean()]),
|
|
20076
|
+
logoText: z.union([
|
|
20077
|
+
z.literal("inherit"),
|
|
20078
|
+
z.literal("none"),
|
|
20079
|
+
logoTextIntentSchema
|
|
20080
|
+
])
|
|
20081
|
+
}).strict();
|
|
20082
|
+
const headerPreferencesShape = {
|
|
20083
|
+
brandPresentation: z.enum(headerBrandPresentationValues),
|
|
20084
|
+
brandDisplay: z.enum(brandDisplayModeValues),
|
|
20085
|
+
logoSize: z.enum(logoSizePolicyValues),
|
|
20086
|
+
navFontRole: z.enum(headerNavTypographyPreferenceValues),
|
|
20087
|
+
navTextSize: z.enum(headerNavFontSizePreferenceValues),
|
|
20088
|
+
interactionPalette: z.union([z.literal("recommended"), z.enum(headerInteractionPaletteValues)]),
|
|
20089
|
+
background: backgroundOverrideSchema,
|
|
20090
|
+
structure: structureSchema
|
|
20091
|
+
};
|
|
20092
|
+
z.object(headerPreferencesShape).strict();
|
|
20093
|
+
z.object(headerPreferencesShape).omit({
|
|
20094
|
+
brandPresentation: true,
|
|
20095
|
+
structure: true
|
|
20096
|
+
}).strict();
|
|
20097
|
+
//#endregion
|
|
19864
20098
|
//#region ../theme-core/src/site-styles/pageDesignAuthority.ts
|
|
19865
20099
|
const nonEmptyStringSchema = z.string().min(1);
|
|
19866
20100
|
const blockContentVersionIdSchema = nonEmptyStringSchema.transform(asBlockContentVersionId);
|
|
@@ -19962,48 +20196,6 @@ function collectBlockSequenceIssues(snapshots) {
|
|
|
19962
20196
|
return issues;
|
|
19963
20197
|
}
|
|
19964
20198
|
//#endregion
|
|
19965
|
-
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
19966
|
-
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
19967
|
-
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
19968
|
-
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
19969
|
-
function parseSiteChromeBackgroundColorToken(value) {
|
|
19970
|
-
const token = parseBackgroundAppearanceColorToken(value);
|
|
19971
|
-
return token ? token : null;
|
|
19972
|
-
}
|
|
19973
|
-
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
19974
|
-
return tokenNames.flatMap((name) => {
|
|
19975
|
-
const token = parseSiteChromeBackgroundColorToken(name);
|
|
19976
|
-
return token ? [token] : [];
|
|
19977
|
-
});
|
|
19978
|
-
}
|
|
19979
|
-
//#endregion
|
|
19980
|
-
//#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
|
|
19981
|
-
/**
|
|
19982
|
-
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
19983
|
-
* nav link style. This is where the precedence contract lands:
|
|
19984
|
-
*
|
|
19985
|
-
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
19986
|
-
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
19987
|
-
* baked into `style` before this call (see `applyNavTypography` in
|
|
19988
|
-
* headerLayoutStyles.ts).
|
|
19989
|
-
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
19990
|
-
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
19991
|
-
* through the body role.
|
|
19992
|
-
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
19993
|
-
* overrides which font ROLE the treatment resolves through. It never changes
|
|
19994
|
-
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
19995
|
-
*/
|
|
19996
|
-
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
19997
|
-
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
19998
|
-
...style,
|
|
19999
|
-
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
20000
|
-
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
20001
|
-
};
|
|
20002
|
-
}
|
|
20003
|
-
function readHeaderNavFontSizePreference(value) {
|
|
20004
|
-
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
20005
|
-
}
|
|
20006
|
-
//#endregion
|
|
20007
20199
|
//#region ../theme-core/src/site-styles/tokenRecipes.ts
|
|
20008
20200
|
const themeTokenRecipeOptions = {
|
|
20009
20201
|
palette: [
|
|
@@ -31558,7 +31750,7 @@ const eventDisplayFields = [
|
|
|
31558
31750
|
{
|
|
31559
31751
|
id: "showCapacity",
|
|
31560
31752
|
type: "boolean",
|
|
31561
|
-
label: "Show available
|
|
31753
|
+
label: "Show available places",
|
|
31562
31754
|
description: "Display remaining capacity",
|
|
31563
31755
|
required: false,
|
|
31564
31756
|
defaultValue: true,
|
|
@@ -31883,330 +32075,1300 @@ function planEventRegistrationScope(input) {
|
|
|
31883
32075
|
};
|
|
31884
32076
|
}
|
|
31885
32077
|
//#endregion
|
|
31886
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
31887
|
-
|
|
31888
|
-
|
|
31889
|
-
}
|
|
31890
|
-
|
|
31891
|
-
|
|
31892
|
-
|
|
31893
|
-
|
|
31894
|
-
|
|
31895
|
-
function
|
|
31896
|
-
if (
|
|
31897
|
-
|
|
31898
|
-
|
|
31899
|
-
mode: event.eventDestinationMode === "system-managed" ? "system-managed" : "cms-managed",
|
|
31900
|
-
path: typeof event.path === "string" ? event.path : ""
|
|
31901
|
-
};
|
|
31902
|
-
}
|
|
31903
|
-
//#endregion
|
|
31904
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/pricing.ts
|
|
31905
|
-
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
31906
|
-
function resolveRuntimeEventPricing(event) {
|
|
31907
|
-
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
31908
|
-
if (resolveRuntimeEventDestination(event).kind === "external") return {
|
|
31909
|
-
kind: "external",
|
|
31910
|
-
display: {
|
|
31911
|
-
kind: "external",
|
|
31912
|
-
label: "External registration"
|
|
31913
|
-
}
|
|
31914
|
-
};
|
|
31915
|
-
const legacy = event;
|
|
31916
|
-
const currency = readCurrency(legacy.currency);
|
|
31917
|
-
if (event.requiresRegistration === false) return {
|
|
31918
|
-
kind: "drop_in",
|
|
31919
|
-
currency,
|
|
31920
|
-
display: {
|
|
31921
|
-
kind: "free",
|
|
31922
|
-
currency
|
|
31923
|
-
}
|
|
31924
|
-
};
|
|
31925
|
-
if (legacy.pricingMode === "ticket_types") return {
|
|
31926
|
-
kind: "ticket_types",
|
|
31927
|
-
currency,
|
|
31928
|
-
ticketTypes: Array.isArray(legacy.ticketTypes) ? legacy.ticketTypes.filter(isPublicEventTicketType) : [],
|
|
31929
|
-
display: readTicketTypePriceDisplay(legacy.priceDisplay, currency)
|
|
32078
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
|
|
32079
|
+
/**
|
|
32080
|
+
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
32081
|
+
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
32082
|
+
* for uncapped events so call sites can render no capacity indicator at all.
|
|
32083
|
+
*
|
|
32084
|
+
* The single source of truth for capacity math across event cards, agenda
|
|
32085
|
+
* rows, and timetable cells — keep wording variations in the call sites.
|
|
32086
|
+
*/
|
|
32087
|
+
function getEventAvailability(event) {
|
|
32088
|
+
if (event.capacity == null) return {
|
|
32089
|
+
available: null,
|
|
32090
|
+
isFull: false
|
|
31930
32091
|
};
|
|
31931
|
-
const
|
|
31932
|
-
if (
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
currency,
|
|
31936
|
-
display: {
|
|
31937
|
-
kind: "exact",
|
|
31938
|
-
amount,
|
|
31939
|
-
currency
|
|
31940
|
-
}
|
|
32092
|
+
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
32093
|
+
if (available <= 0) return {
|
|
32094
|
+
available: 0,
|
|
32095
|
+
isFull: true
|
|
31941
32096
|
};
|
|
31942
32097
|
return {
|
|
31943
|
-
|
|
31944
|
-
|
|
31945
|
-
display: {
|
|
31946
|
-
kind: "free",
|
|
31947
|
-
currency
|
|
31948
|
-
}
|
|
32098
|
+
available,
|
|
32099
|
+
isFull: false
|
|
31949
32100
|
};
|
|
31950
32101
|
}
|
|
31951
|
-
|
|
31952
|
-
|
|
31953
|
-
|
|
31954
|
-
|
|
31955
|
-
|
|
31956
|
-
|
|
31957
|
-
|
|
31958
|
-
|
|
31959
|
-
|
|
32102
|
+
/**
|
|
32103
|
+
* Pick the capacity row a booking decision should read: the selected
|
|
32104
|
+
* attendance mode's availability when the visitor has chosen one and the
|
|
32105
|
+
* event publishes it, otherwise the event-level aggregate.
|
|
32106
|
+
*
|
|
32107
|
+
* The mode row is used wholesale. A mode with `capacity: null` on a capped
|
|
32108
|
+
* event is uncapped *by design*: the registration write path applies the
|
|
32109
|
+
* same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
|
|
32110
|
+
* back to the event's `availableSpots` here would block bookings the server
|
|
32111
|
+
* accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
|
|
32112
|
+
*/
|
|
32113
|
+
function selectEventCapacitySource(event, attendanceMode) {
|
|
32114
|
+
if (!attendanceMode) return event;
|
|
32115
|
+
return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
|
|
31960
32116
|
}
|
|
31961
|
-
|
|
31962
|
-
|
|
32117
|
+
/**
|
|
32118
|
+
* Whether the visitor's next step is the waitlist: the source is full *and*
|
|
32119
|
+
* a waitlist is open. The one place this is decided — cards, rows, the
|
|
32120
|
+
* wizard's steps and its action bar all route through here so they agree,
|
|
32121
|
+
* including for producers that omit `availableSpots` and rely on the
|
|
32122
|
+
* `capacity - registeredCount` fallback.
|
|
32123
|
+
*/
|
|
32124
|
+
function canJoinEventWaitlist(event, options) {
|
|
32125
|
+
const source = selectEventCapacitySource(event, options?.attendanceMode);
|
|
32126
|
+
return getEventAvailability(source).isFull && source.waitlistEnabled === true;
|
|
31963
32127
|
}
|
|
31964
|
-
|
|
31965
|
-
|
|
31966
|
-
|
|
31967
|
-
|
|
31968
|
-
|
|
31969
|
-
|
|
32128
|
+
/**
|
|
32129
|
+
* Remaining places at or below which an event reads as scarce and the exact
|
|
32130
|
+
* count is worth showing. Above it, no number is shown at all: "147 places
|
|
32131
|
+
* left" is not something a visitor acts on, and it is the figure most likely
|
|
32132
|
+
* to be stale on a cached page.
|
|
32133
|
+
*
|
|
32134
|
+
* max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
|
|
32135
|
+
*
|
|
32136
|
+
* The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
|
|
32137
|
+
* 5% floor stops large events staying silent until three remain.
|
|
32138
|
+
* capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
|
|
32139
|
+
*/
|
|
32140
|
+
function lowCapacityThreshold(capacity) {
|
|
32141
|
+
return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
|
|
31970
32142
|
}
|
|
31971
|
-
function
|
|
31972
|
-
|
|
31973
|
-
|
|
31974
|
-
|
|
31975
|
-
|
|
31976
|
-
|
|
31977
|
-
|
|
31978
|
-
|
|
31979
|
-
|
|
31980
|
-
|
|
31981
|
-
|
|
31982
|
-
|
|
31983
|
-
|
|
31984
|
-
|
|
31985
|
-
|
|
31986
|
-
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
|
|
31992
|
-
|
|
31993
|
-
|
|
31994
|
-
|
|
31995
|
-
|
|
31996
|
-
|
|
31997
|
-
|
|
31998
|
-
|
|
31999
|
-
|
|
32000
|
-
|
|
32001
|
-
|
|
32002
|
-
|
|
32003
|
-
|
|
32004
|
-
|
|
32005
|
-
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
|
|
32009
|
-
|
|
32010
|
-
|
|
32011
|
-
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
|
|
32015
|
-
|
|
32016
|
-
|
|
32017
|
-
|
|
32018
|
-
currency
|
|
32143
|
+
function getRemainingPlaces(source) {
|
|
32144
|
+
const { available, isFull } = getEventAvailability(source);
|
|
32145
|
+
if (available === null || source.capacity === null) return { kind: "unlimited" };
|
|
32146
|
+
if (isFull) return {
|
|
32147
|
+
kind: "full",
|
|
32148
|
+
waitlist: canJoinEventWaitlist(source)
|
|
32149
|
+
};
|
|
32150
|
+
if (available <= lowCapacityThreshold(source.capacity)) return {
|
|
32151
|
+
kind: "low",
|
|
32152
|
+
remaining: available
|
|
32153
|
+
};
|
|
32154
|
+
return { kind: "open" };
|
|
32155
|
+
}
|
|
32156
|
+
/**
|
|
32157
|
+
* The one place a remaining-places count becomes text. Returns `null` when
|
|
32158
|
+
* nothing should be shown: uncapped events, and capped events above the
|
|
32159
|
+
* scarcity threshold. Surfaces that need their own full-state wording pass
|
|
32160
|
+
* it in rather than re-deriving the count.
|
|
32161
|
+
*/
|
|
32162
|
+
function formatRemainingPlaces(source, wording = {}) {
|
|
32163
|
+
const remaining = getRemainingPlaces(source);
|
|
32164
|
+
switch (remaining.kind) {
|
|
32165
|
+
case "unlimited":
|
|
32166
|
+
case "open": return null;
|
|
32167
|
+
case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
|
|
32168
|
+
case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
|
|
32169
|
+
}
|
|
32170
|
+
}
|
|
32171
|
+
function formatPlacesLeft(count, compact) {
|
|
32172
|
+
if (compact) return `${count} left`;
|
|
32173
|
+
return count === 1 ? "1 place left" : `${count} places left`;
|
|
32174
|
+
}
|
|
32175
|
+
/** Total capacity as copy ("12 places"), for detail surfaces that state the size of an event. */
|
|
32176
|
+
function formatTotalCapacity(capacity) {
|
|
32177
|
+
if (capacity === null) return null;
|
|
32178
|
+
return capacity === 1 ? "1 place" : `${capacity} places`;
|
|
32179
|
+
}
|
|
32180
|
+
function getEventAvailabilityStatus(event) {
|
|
32181
|
+
const source = selectMostOpenCapacitySource(event);
|
|
32182
|
+
const remaining = getRemainingPlaces(source);
|
|
32183
|
+
const label = formatEventRemainingPlaces(event, { compact: true });
|
|
32184
|
+
switch (remaining.kind) {
|
|
32185
|
+
case "unlimited": return {
|
|
32186
|
+
status: "available",
|
|
32187
|
+
available: null,
|
|
32188
|
+
isFull: false,
|
|
32189
|
+
label
|
|
32019
32190
|
};
|
|
32020
|
-
case "
|
|
32021
|
-
|
|
32022
|
-
|
|
32191
|
+
case "open": return {
|
|
32192
|
+
status: "available",
|
|
32193
|
+
available: getEventAvailability(source).available,
|
|
32194
|
+
isFull: false,
|
|
32195
|
+
label
|
|
32023
32196
|
};
|
|
32024
|
-
case "
|
|
32025
|
-
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
display: {
|
|
32030
|
-
kind: "unavailable",
|
|
32031
|
-
currency: displayCurrency
|
|
32032
|
-
}
|
|
32197
|
+
case "low": return {
|
|
32198
|
+
status: "filling",
|
|
32199
|
+
available: remaining.remaining,
|
|
32200
|
+
isFull: false,
|
|
32201
|
+
label
|
|
32033
32202
|
};
|
|
32034
|
-
|
|
32035
|
-
|
|
32036
|
-
|
|
32203
|
+
case "full": return {
|
|
32204
|
+
status: "waitlist",
|
|
32205
|
+
available: 0,
|
|
32206
|
+
isFull: true,
|
|
32207
|
+
label
|
|
32037
32208
|
};
|
|
32038
32209
|
}
|
|
32039
32210
|
}
|
|
32040
|
-
|
|
32041
|
-
|
|
32042
|
-
|
|
32043
|
-
|
|
32044
|
-
|
|
32045
|
-
|
|
32046
|
-
|
|
32047
|
-
|
|
32048
|
-
|
|
32049
|
-
|
|
32050
|
-
|
|
32051
|
-
|
|
32052
|
-
|
|
32053
|
-
|
|
32054
|
-
|
|
32055
|
-
|
|
32056
|
-
|
|
32057
|
-
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
32063
|
-
|
|
32064
|
-
|
|
32065
|
-
|
|
32211
|
+
/**
|
|
32212
|
+
* Which capacity figure wins when they disagree.
|
|
32213
|
+
*
|
|
32214
|
+
* For a hybrid event that publishes both attendance-mode rows, **the mode
|
|
32215
|
+
* rows are authoritative and the event-level aggregate is only a summary**.
|
|
32216
|
+
* The registration write path admits or refuses per mode
|
|
32217
|
+
* (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
|
|
32218
|
+
* aggregate can read full while a mode still has room, and can read open
|
|
32219
|
+
* while the mode the visitor wants is full. Believing the aggregate on a
|
|
32220
|
+
* card is how a "places available" event turns into a dead end at step two.
|
|
32221
|
+
*
|
|
32222
|
+
* Everything else — single-mode events, events that publish no rows, and the
|
|
32223
|
+
* aggregate-only helpers {@link getEventAvailability} /
|
|
32224
|
+
* {@link getRemainingPlaces} that existing callers rely on — keeps reading
|
|
32225
|
+
* the aggregate, unchanged.
|
|
32226
|
+
*/
|
|
32227
|
+
function findHybridModeRows(event) {
|
|
32228
|
+
if (event.deliveryMode !== "hybrid") return null;
|
|
32229
|
+
const rows = event.attendanceModes ?? [];
|
|
32230
|
+
const inPerson = rows.find((row) => row.mode === "in_person");
|
|
32231
|
+
const online = rows.find((row) => row.mode === "online");
|
|
32232
|
+
return inPerson && online ? {
|
|
32233
|
+
inPerson,
|
|
32234
|
+
online
|
|
32235
|
+
} : null;
|
|
32236
|
+
}
|
|
32237
|
+
/** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
|
|
32238
|
+
function remainingPlacesRank(remaining) {
|
|
32239
|
+
switch (remaining.kind) {
|
|
32240
|
+
case "unlimited": return 4;
|
|
32241
|
+
case "open": return 3;
|
|
32242
|
+
case "low": return 2;
|
|
32243
|
+
case "full": return remaining.waitlist ? 1 : 0;
|
|
32066
32244
|
}
|
|
32067
32245
|
}
|
|
32068
|
-
function
|
|
32069
|
-
|
|
32246
|
+
function isMoreOpen(candidate, than) {
|
|
32247
|
+
const rank = remainingPlacesRank(candidate);
|
|
32248
|
+
const otherRank = remainingPlacesRank(than);
|
|
32249
|
+
if (rank !== otherRank) return rank > otherRank;
|
|
32250
|
+
return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
|
|
32070
32251
|
}
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
"combined",
|
|
32075
|
-
"calendar",
|
|
32076
|
-
"list",
|
|
32077
|
-
"timetable",
|
|
32078
|
-
"week-board"
|
|
32079
|
-
];
|
|
32080
|
-
const eventCalendarDefaultViewValues = ["month", "week"];
|
|
32081
|
-
const eventCalendarListLayoutValues = [
|
|
32082
|
-
"stack",
|
|
32083
|
-
"grid",
|
|
32084
|
-
"compact"
|
|
32085
|
-
];
|
|
32086
|
-
const eventCalendarListLayoutOptions = [
|
|
32087
|
-
{
|
|
32088
|
-
id: "stack",
|
|
32089
|
-
label: "Stack"
|
|
32090
|
-
},
|
|
32091
|
-
{
|
|
32092
|
-
id: "grid",
|
|
32093
|
-
label: "Grid"
|
|
32094
|
-
},
|
|
32095
|
-
{
|
|
32096
|
-
id: "compact",
|
|
32097
|
-
label: "Compact"
|
|
32098
|
-
}
|
|
32099
|
-
];
|
|
32100
|
-
function isEventCalendarDisplayMode(value) {
|
|
32101
|
-
return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
|
|
32252
|
+
/** True when `candidate` has less room left than `than`. */
|
|
32253
|
+
function isScarcer(candidate, than) {
|
|
32254
|
+
return isMoreOpen(than, candidate);
|
|
32102
32255
|
}
|
|
32103
|
-
|
|
32104
|
-
|
|
32256
|
+
/**
|
|
32257
|
+
* The capacity row a whole-event decision should read: the most open
|
|
32258
|
+
* attendance mode of a hybrid event, otherwise the aggregate. A hybrid event
|
|
32259
|
+
* is sold out only when both of its modes are, and its waitlist is reachable
|
|
32260
|
+
* when either full mode has one open.
|
|
32261
|
+
*/
|
|
32262
|
+
function selectMostOpenCapacitySource(event) {
|
|
32263
|
+
const rows = findHybridModeRows(event);
|
|
32264
|
+
if (!rows) return event;
|
|
32265
|
+
return isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? rows.online : rows.inPerson;
|
|
32105
32266
|
}
|
|
32106
|
-
|
|
32107
|
-
|
|
32267
|
+
/** True when no attendance mode has room. Hybrid-aware; use this for sold-out decisions. */
|
|
32268
|
+
function isEventFull(event) {
|
|
32269
|
+
return getEventAvailability(selectMostOpenCapacitySource(event)).isFull;
|
|
32108
32270
|
}
|
|
32109
|
-
//#endregion
|
|
32110
|
-
//#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
|
|
32111
|
-
const previewStageSchema = z.enum(["preview", "published"]);
|
|
32112
|
-
const registrationButtonVariantSchema = blockButtonRoleSchema;
|
|
32113
|
-
const eventButtonVariantSchema = blockButtonRoleSchema;
|
|
32114
|
-
const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
|
|
32115
|
-
const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
|
|
32116
|
-
const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
|
|
32117
|
-
const columnOptionSchema = z.enum([
|
|
32118
|
-
"2",
|
|
32119
|
-
"3",
|
|
32120
|
-
"4"
|
|
32121
|
-
]);
|
|
32122
|
-
const weekStartSchema = z.enum(["sunday", "monday"]);
|
|
32123
|
-
const filterAutoShowSchema = z.enum([
|
|
32124
|
-
"auto",
|
|
32125
|
-
"always",
|
|
32126
|
-
"never"
|
|
32127
|
-
]);
|
|
32128
|
-
const calendarTabViewsSchema = z.enum([
|
|
32129
|
-
"both",
|
|
32130
|
-
"month",
|
|
32131
|
-
"week"
|
|
32132
|
-
]);
|
|
32133
|
-
//#endregion
|
|
32134
|
-
//#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
|
|
32135
32271
|
/**
|
|
32136
|
-
*
|
|
32272
|
+
* True when the event is full on every mode and at least one of those full
|
|
32273
|
+
* modes has a waitlist open. An event with a mode still bookable is not a
|
|
32274
|
+
* waitlist prospect: it should send the visitor to that mode instead.
|
|
32275
|
+
*/
|
|
32276
|
+
function canJoinEventWaitlistOnAnyMode(event) {
|
|
32277
|
+
return canJoinEventWaitlist(selectMostOpenCapacitySource(event));
|
|
32278
|
+
}
|
|
32279
|
+
const ATTENDANCE_MODE_LABEL = {
|
|
32280
|
+
in_person: "In person",
|
|
32281
|
+
online: "Online"
|
|
32282
|
+
};
|
|
32283
|
+
/** Wording for a mode with room to spare, where the count is deliberately withheld. */
|
|
32284
|
+
const PLACES_AVAILABLE = "Places available";
|
|
32285
|
+
/**
|
|
32286
|
+
* The browse-surface label for an event's remaining places. Single-mode
|
|
32287
|
+
* events get exactly the aggregate label {@link formatRemainingPlaces}
|
|
32288
|
+
* produces, and so does a hybrid event whose two modes tell the same story.
|
|
32289
|
+
* When the modes differ, both sides are named: "available" on a card is a
|
|
32290
|
+
* promise the registration wizard would otherwise have to break.
|
|
32137
32291
|
*
|
|
32138
|
-
*
|
|
32139
|
-
*
|
|
32140
|
-
*
|
|
32292
|
+
* Hybrid examples, in person full with 3 online places left:
|
|
32293
|
+
* default -> "Online: 3 places left · In person: Full"
|
|
32294
|
+
* { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
|
|
32295
|
+
* { compact: true } -> "Online: 3 left"
|
|
32296
|
+
*/
|
|
32297
|
+
function formatEventRemainingPlaces(event, wording = {}) {
|
|
32298
|
+
const rows = findHybridModeRows(event);
|
|
32299
|
+
if (!rows) return formatRemainingPlaces(event, wording);
|
|
32300
|
+
const inPerson = formatRemainingPlaces(rows.inPerson, wording);
|
|
32301
|
+
const online = formatRemainingPlaces(rows.online, wording);
|
|
32302
|
+
if (inPerson === online) return inPerson;
|
|
32303
|
+
if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
|
|
32304
|
+
return formatBothHybridSides(rows, {
|
|
32305
|
+
inPerson,
|
|
32306
|
+
online
|
|
32307
|
+
});
|
|
32308
|
+
}
|
|
32309
|
+
/**
|
|
32310
|
+
* Both sides, the one with room first: that is the half a visitor can act on,
|
|
32311
|
+
* and it should not arrive after a full stop's worth of bad news.
|
|
32141
32312
|
*/
|
|
32142
|
-
function
|
|
32143
|
-
|
|
32144
|
-
"select-event",
|
|
32145
|
-
"select-tickets",
|
|
32146
|
-
"enter-details"
|
|
32147
|
-
];
|
|
32148
|
-
if (!needsPaymentStep) return baseSteps;
|
|
32149
|
-
return [...baseSteps, "select-payment"];
|
|
32313
|
+
function formatBothHybridSides(rows, labels) {
|
|
32314
|
+
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(" · ");
|
|
32150
32315
|
}
|
|
32151
|
-
|
|
32152
|
-
|
|
32153
|
-
function eventMatchesOccurrenceId(event, occurrenceId) {
|
|
32154
|
-
return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
|
|
32316
|
+
function namedModePlaces(mode, label) {
|
|
32317
|
+
return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
|
|
32155
32318
|
}
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32159
|
-
|
|
32160
|
-
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32165
|
-
|
|
32166
|
-
|
|
32319
|
+
/**
|
|
32320
|
+
* Pills and timetable cells have room for one short phrase, so they name the
|
|
32321
|
+
* single mode that carries news rather than listing both: the scarce side
|
|
32322
|
+
* when there is a count to show, and otherwise the side that has closed —
|
|
32323
|
+
* "In person: Full", never "Online only", which reads as a statement about
|
|
32324
|
+
* how the event is delivered rather than about its remaining places.
|
|
32325
|
+
*/
|
|
32326
|
+
function formatCompactHybridPlaces(rows, wording) {
|
|
32327
|
+
const inPerson = getRemainingPlaces(rows.inPerson);
|
|
32328
|
+
const online = getRemainingPlaces(rows.online);
|
|
32329
|
+
if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
|
|
32330
|
+
if (inPerson.kind === "full" || online.kind === "full") {
|
|
32331
|
+
const fullSide = inPerson.kind === "full" ? "in_person" : "online";
|
|
32332
|
+
const openSide = inPerson.kind === "full" ? "online" : "in_person";
|
|
32333
|
+
const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
|
|
32334
|
+
return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
|
|
32335
|
+
}
|
|
32336
|
+
const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
|
|
32337
|
+
return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
|
|
32167
32338
|
}
|
|
32168
|
-
function
|
|
32169
|
-
|
|
32170
|
-
if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
|
|
32171
|
-
if (!input.event) return false;
|
|
32172
|
-
return publicEventPricingType(resolveRuntimeEventPricing(input.event)) === "paid";
|
|
32339
|
+
function rowFor(rows, mode) {
|
|
32340
|
+
return mode === "online" ? rows.online : rows.inPerson;
|
|
32173
32341
|
}
|
|
32174
32342
|
//#endregion
|
|
32175
|
-
//#region ../blocks/src/system/runtime/nodes/
|
|
32176
|
-
function
|
|
32177
|
-
return
|
|
32178
|
-
...event,
|
|
32179
|
-
pricing: resolveRuntimeEventPricing(event),
|
|
32180
|
-
selectionId: event.id,
|
|
32181
|
-
registrationSubject: {
|
|
32182
|
-
kind: "occurrence",
|
|
32183
|
-
occurrenceId: event.occurrenceId
|
|
32184
|
-
}
|
|
32185
|
-
};
|
|
32343
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
|
|
32344
|
+
function isRecord$14(value) {
|
|
32345
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32186
32346
|
}
|
|
32187
|
-
function
|
|
32188
|
-
|
|
32347
|
+
function isRuntimeEventDestination(value) {
|
|
32348
|
+
if (!isRecord$14(value) || typeof value.kind !== "string") return false;
|
|
32349
|
+
if (value.kind === "native") return (value.mode === "cms-managed" || value.mode === "system-managed") && typeof value.path === "string";
|
|
32350
|
+
return value.kind === "external" && typeof value.url === "string" && typeof value.label === "string";
|
|
32189
32351
|
}
|
|
32190
|
-
function
|
|
32352
|
+
function resolveRuntimeEventDestination(event) {
|
|
32353
|
+
if (isRuntimeEventDestination(event.destination)) return event.destination;
|
|
32191
32354
|
return {
|
|
32192
|
-
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
availability: {
|
|
32196
|
-
kind: "available",
|
|
32197
|
-
remainingCapacity: option.capacityLimit
|
|
32198
|
-
}
|
|
32355
|
+
kind: "native",
|
|
32356
|
+
mode: event.eventDestinationMode === "system-managed" ? "system-managed" : "cms-managed",
|
|
32357
|
+
path: typeof event.path === "string" ? event.path : ""
|
|
32199
32358
|
};
|
|
32200
32359
|
}
|
|
32201
|
-
|
|
32202
|
-
|
|
32203
|
-
|
|
32204
|
-
|
|
32205
|
-
|
|
32206
|
-
|
|
32207
|
-
|
|
32208
|
-
|
|
32209
|
-
|
|
32360
|
+
//#endregion
|
|
32361
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/cta.ts
|
|
32362
|
+
/**
|
|
32363
|
+
* Booking call-to-action resolution for events: where an event links to and
|
|
32364
|
+
* what the button says. Sold-out and waitlist decisions come from capacity.ts
|
|
32365
|
+
* so cards, rows and the wizard never disagree.
|
|
32366
|
+
*/
|
|
32367
|
+
function closedEventBookingLabel(reason) {
|
|
32368
|
+
switch (reason) {
|
|
32369
|
+
case "occurrence_cancelled":
|
|
32370
|
+
case "series_cancelled": return "Cancelled";
|
|
32371
|
+
case "occurrence_completed":
|
|
32372
|
+
case "series_completed": return "Completed";
|
|
32373
|
+
case "series_draft":
|
|
32374
|
+
case "series_archived":
|
|
32375
|
+
case "parent_archived":
|
|
32376
|
+
case "parent_inconsistent":
|
|
32377
|
+
case "not_public":
|
|
32378
|
+
case "already_started":
|
|
32379
|
+
case "enrollment_closed": return "Registration closed";
|
|
32380
|
+
default: return assertNever$3(reason);
|
|
32381
|
+
}
|
|
32382
|
+
}
|
|
32383
|
+
function resolveEventHref(event) {
|
|
32384
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
32385
|
+
return destination.kind === "external" ? destination.url : destination.path;
|
|
32386
|
+
}
|
|
32387
|
+
function resolveEventLinkProps(event) {
|
|
32388
|
+
const href = resolveEventHref(event);
|
|
32389
|
+
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
32390
|
+
href,
|
|
32391
|
+
target: "_blank",
|
|
32392
|
+
rel: "noopener noreferrer"
|
|
32393
|
+
} : { href };
|
|
32394
|
+
}
|
|
32395
|
+
/**
|
|
32396
|
+
* Resolve the CTA state for an event.
|
|
32397
|
+
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
32398
|
+
* and course-specific defaults consistent.
|
|
32399
|
+
*/
|
|
32400
|
+
function resolveEventCta(event, buttonText) {
|
|
32401
|
+
const isSoldOut = isEventFull(event);
|
|
32402
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
32403
|
+
const isExternal = destination.kind === "external";
|
|
32404
|
+
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
32405
|
+
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
32406
|
+
const href = resolveEventHref(event);
|
|
32407
|
+
if (event.bookingAvailability.kind === "closed") {
|
|
32408
|
+
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
32409
|
+
return {
|
|
32410
|
+
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
32411
|
+
href,
|
|
32412
|
+
external: isExternal,
|
|
32413
|
+
hidden: !linksToClosedWholeSeriesDetails
|
|
32414
|
+
};
|
|
32415
|
+
}
|
|
32416
|
+
if (!isExternal && isSoldOut) {
|
|
32417
|
+
if (canJoinEventWaitlistOnAnyMode(event)) return {
|
|
32418
|
+
label: "Join waitlist",
|
|
32419
|
+
href,
|
|
32420
|
+
external: false,
|
|
32421
|
+
hidden: false
|
|
32422
|
+
};
|
|
32423
|
+
return {
|
|
32424
|
+
label: "Sold out",
|
|
32425
|
+
href,
|
|
32426
|
+
external: false,
|
|
32427
|
+
hidden: true
|
|
32428
|
+
};
|
|
32429
|
+
}
|
|
32430
|
+
return {
|
|
32431
|
+
label: effectiveButtonText,
|
|
32432
|
+
href,
|
|
32433
|
+
external: isExternal,
|
|
32434
|
+
hidden: false
|
|
32435
|
+
};
|
|
32436
|
+
}
|
|
32437
|
+
//#endregion
|
|
32438
|
+
//#region ../calendar/src/grid.ts
|
|
32439
|
+
const DAY_NAMES = [
|
|
32440
|
+
"Sun",
|
|
32441
|
+
"Mon",
|
|
32442
|
+
"Tue",
|
|
32443
|
+
"Wed",
|
|
32444
|
+
"Thu",
|
|
32445
|
+
"Fri",
|
|
32446
|
+
"Sat"
|
|
32447
|
+
];
|
|
32448
|
+
const MONTH_NAMES = [
|
|
32449
|
+
"January",
|
|
32450
|
+
"February",
|
|
32451
|
+
"March",
|
|
32452
|
+
"April",
|
|
32453
|
+
"May",
|
|
32454
|
+
"June",
|
|
32455
|
+
"July",
|
|
32456
|
+
"August",
|
|
32457
|
+
"September",
|
|
32458
|
+
"October",
|
|
32459
|
+
"November",
|
|
32460
|
+
"December"
|
|
32461
|
+
];
|
|
32462
|
+
function formatDateKey(date) {
|
|
32463
|
+
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
32464
|
+
}
|
|
32465
|
+
function isToday(date, today) {
|
|
32466
|
+
return isSameCalendarDay(date, today);
|
|
32467
|
+
}
|
|
32468
|
+
function isSameCalendarDay(left, right) {
|
|
32469
|
+
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
32470
|
+
}
|
|
32471
|
+
function addDays(date, days) {
|
|
32472
|
+
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
32473
|
+
return {
|
|
32474
|
+
year: result.getUTCFullYear(),
|
|
32475
|
+
month: result.getUTCMonth() + 1,
|
|
32476
|
+
day: result.getUTCDate()
|
|
32477
|
+
};
|
|
32478
|
+
}
|
|
32479
|
+
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
32480
|
+
const firstDay = {
|
|
32481
|
+
year,
|
|
32482
|
+
month,
|
|
32483
|
+
day: 1
|
|
32484
|
+
};
|
|
32485
|
+
const dayOfWeek = getDayOfWeek(firstDay);
|
|
32486
|
+
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
32487
|
+
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
32488
|
+
}
|
|
32489
|
+
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
32490
|
+
const dayOfWeek = getDayOfWeek(baseDate);
|
|
32491
|
+
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
32492
|
+
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
32493
|
+
}
|
|
32494
|
+
function getDayOfWeek(date) {
|
|
32495
|
+
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
32496
|
+
}
|
|
32497
|
+
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
32498
|
+
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
32499
|
+
assertWeekCalendarDays(days);
|
|
32500
|
+
return days;
|
|
32501
|
+
}
|
|
32502
|
+
function assertWeekCalendarDays(days) {
|
|
32503
|
+
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
32504
|
+
}
|
|
32505
|
+
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
32506
|
+
if (startOfWeek === "monday") {
|
|
32507
|
+
const sunday = dayNames[0] ?? "Sun";
|
|
32508
|
+
return [...dayNames.slice(1), sunday];
|
|
32509
|
+
}
|
|
32510
|
+
return [...dayNames];
|
|
32511
|
+
}
|
|
32512
|
+
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
32513
|
+
if (view === "week") {
|
|
32514
|
+
const weekStart = days[0];
|
|
32515
|
+
const weekEnd = days[6];
|
|
32516
|
+
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
32517
|
+
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
32518
|
+
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
32519
|
+
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
32520
|
+
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
32521
|
+
}
|
|
32522
|
+
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
32523
|
+
}
|
|
32524
|
+
function buildCalendarViewport(input) {
|
|
32525
|
+
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
32526
|
+
if (input.view === "week") {
|
|
32527
|
+
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
32528
|
+
return {
|
|
32529
|
+
kind: "week",
|
|
32530
|
+
anchorDate: input.anchorDate,
|
|
32531
|
+
days,
|
|
32532
|
+
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
32533
|
+
};
|
|
32534
|
+
}
|
|
32535
|
+
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
32536
|
+
return {
|
|
32537
|
+
kind: "month",
|
|
32538
|
+
anchorDate: input.anchorDate,
|
|
32539
|
+
days,
|
|
32540
|
+
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
32541
|
+
};
|
|
32542
|
+
}
|
|
32543
|
+
//#endregion
|
|
32544
|
+
//#region ../calendar/src/lane-packing.ts
|
|
32545
|
+
/**
|
|
32546
|
+
* Assign lanes to a list of intervals.
|
|
32547
|
+
*
|
|
32548
|
+
* Returns one result per input in the same order as the input. Internally
|
|
32549
|
+
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
32550
|
+
* permutes results back to input order.
|
|
32551
|
+
*/
|
|
32552
|
+
function assignLanes(intervals) {
|
|
32553
|
+
if (intervals.length === 0) return [];
|
|
32554
|
+
const indexed = intervals.map((interval, index) => ({
|
|
32555
|
+
interval,
|
|
32556
|
+
index
|
|
32557
|
+
}));
|
|
32558
|
+
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
32559
|
+
const results = new Array(intervals.length);
|
|
32560
|
+
let cluster = [];
|
|
32561
|
+
let activeLaneEnds = [];
|
|
32562
|
+
let clusterMaxEnd = -Infinity;
|
|
32563
|
+
const closeCluster = () => {
|
|
32564
|
+
if (cluster.length === 0) return;
|
|
32565
|
+
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
32566
|
+
for (const entry of cluster) results[entry.index] = {
|
|
32567
|
+
laneIndex: entry.laneIndex,
|
|
32568
|
+
laneCount
|
|
32569
|
+
};
|
|
32570
|
+
cluster = [];
|
|
32571
|
+
activeLaneEnds = [];
|
|
32572
|
+
clusterMaxEnd = -Infinity;
|
|
32573
|
+
};
|
|
32574
|
+
for (const { interval, index } of indexed) {
|
|
32575
|
+
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
32576
|
+
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
32577
|
+
activeLaneEnds[laneIndex] = interval.end;
|
|
32578
|
+
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
32579
|
+
cluster.push({
|
|
32580
|
+
index,
|
|
32581
|
+
laneIndex
|
|
32582
|
+
});
|
|
32583
|
+
}
|
|
32584
|
+
closeCluster();
|
|
32585
|
+
return results;
|
|
32586
|
+
}
|
|
32587
|
+
function firstAvailableLane(activeLaneEnds, start) {
|
|
32588
|
+
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
32589
|
+
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
32590
|
+
}
|
|
32591
|
+
//#endregion
|
|
32592
|
+
//#region ../calendar/src/timezone.ts
|
|
32593
|
+
/**
|
|
32594
|
+
* Timezone boundary helpers — internal.
|
|
32595
|
+
*
|
|
32596
|
+
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
32597
|
+
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
32598
|
+
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
32599
|
+
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
32600
|
+
* public surface.
|
|
32601
|
+
*/
|
|
32602
|
+
const MINUTES_PER_HOUR = 60;
|
|
32603
|
+
/**
|
|
32604
|
+
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
32605
|
+
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
32606
|
+
*/
|
|
32607
|
+
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
32608
|
+
const date = new Date(isoTimestamp);
|
|
32609
|
+
if (Number.isNaN(date.getTime())) return NaN;
|
|
32610
|
+
if (!isValidTimeZone(timeZone)) return NaN;
|
|
32611
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
32612
|
+
timeZone,
|
|
32613
|
+
hour: "2-digit",
|
|
32614
|
+
minute: "2-digit",
|
|
32615
|
+
hourCycle: "h23"
|
|
32616
|
+
}).formatToParts(date);
|
|
32617
|
+
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
32618
|
+
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
32619
|
+
const hour = hourPart === 24 ? 0 : hourPart;
|
|
32620
|
+
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
32621
|
+
return hour * MINUTES_PER_HOUR + minute;
|
|
32622
|
+
}
|
|
32623
|
+
/**
|
|
32624
|
+
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
32625
|
+
* Returns null if the inputs cannot be parsed.
|
|
32626
|
+
*/
|
|
32627
|
+
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
32628
|
+
const date = new Date(isoTimestamp);
|
|
32629
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
32630
|
+
if (!isValidTimeZone(timeZone)) return null;
|
|
32631
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
32632
|
+
timeZone,
|
|
32633
|
+
year: "numeric",
|
|
32634
|
+
month: "2-digit",
|
|
32635
|
+
day: "2-digit"
|
|
32636
|
+
}).formatToParts(date);
|
|
32637
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
32638
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
32639
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
32640
|
+
if (year === void 0 || year.length === 0) return null;
|
|
32641
|
+
if (month === void 0 || month.length === 0) return null;
|
|
32642
|
+
if (day === void 0 || day.length === 0) return null;
|
|
32643
|
+
return `${year}-${month}-${day}`;
|
|
32644
|
+
}
|
|
32645
|
+
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
32646
|
+
function isValidTimeZone(timeZone) {
|
|
32647
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
32648
|
+
const cached = validTimeZoneCache.get(timeZone);
|
|
32649
|
+
if (cached !== void 0) return cached;
|
|
32650
|
+
try {
|
|
32651
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
32652
|
+
validTimeZoneCache.set(timeZone, true);
|
|
32653
|
+
return true;
|
|
32654
|
+
} catch {
|
|
32655
|
+
validTimeZoneCache.set(timeZone, false);
|
|
32656
|
+
return false;
|
|
32657
|
+
}
|
|
32658
|
+
}
|
|
32659
|
+
//#endregion
|
|
32660
|
+
//#region ../calendar/src/timetable.ts
|
|
32661
|
+
/**
|
|
32662
|
+
* Timetable model — the package's core surface.
|
|
32663
|
+
*
|
|
32664
|
+
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
32665
|
+
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
32666
|
+
* plus a diagnostics list for items dropped during processing.
|
|
32667
|
+
*
|
|
32668
|
+
* The function is pure: given identical input (including `now`), it returns
|
|
32669
|
+
* the same output. The model has no opinion on what an item is — pass
|
|
32670
|
+
* accessors.
|
|
32671
|
+
*/
|
|
32672
|
+
const DEFAULT_BOUNDS = {
|
|
32673
|
+
minStartHour: 5,
|
|
32674
|
+
maxEndHour: 23,
|
|
32675
|
+
defaultStartHour: 6,
|
|
32676
|
+
defaultEndHour: 21,
|
|
32677
|
+
minHeightPercent: 2.5
|
|
32678
|
+
};
|
|
32679
|
+
function buildTimetableModel$1(input) {
|
|
32680
|
+
const bounds = resolveBounds(input.bounds);
|
|
32681
|
+
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
32682
|
+
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
32683
|
+
const parsed = parseAllItems(input.items, input);
|
|
32684
|
+
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
32685
|
+
const hourRange = resolveHourRange(inViewport, bounds);
|
|
32686
|
+
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
32687
|
+
return {
|
|
32688
|
+
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
32689
|
+
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
32690
|
+
startHour: hourRange.startHour,
|
|
32691
|
+
endHour: hourRange.endHour,
|
|
32692
|
+
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
32693
|
+
hasItems: inViewport.length > 0,
|
|
32694
|
+
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
32695
|
+
};
|
|
32696
|
+
}
|
|
32697
|
+
function parseAllItems(items, accessors) {
|
|
32698
|
+
const drafts = [];
|
|
32699
|
+
const diagnostics = [];
|
|
32700
|
+
for (const item of items) {
|
|
32701
|
+
const result = parseDraft(item, accessors);
|
|
32702
|
+
if (result.ok) drafts.push(result.draft);
|
|
32703
|
+
else diagnostics.push({
|
|
32704
|
+
item,
|
|
32705
|
+
reason: result.reason
|
|
32706
|
+
});
|
|
32707
|
+
}
|
|
32708
|
+
return {
|
|
32709
|
+
drafts,
|
|
32710
|
+
diagnostics
|
|
32711
|
+
};
|
|
32712
|
+
}
|
|
32713
|
+
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
32714
|
+
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
32715
|
+
const rangeStartMinutes = hourRange.startHour * 60;
|
|
32716
|
+
const rangeEndMinutes = hourRange.endHour * 60;
|
|
32717
|
+
const segmentsByDate = groupSegmentsByDate(segments);
|
|
32718
|
+
const cellsByDate = /* @__PURE__ */ new Map();
|
|
32719
|
+
const diagnostics = [];
|
|
32720
|
+
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
32721
|
+
const clipped = [];
|
|
32722
|
+
for (const segment of daySegments) {
|
|
32723
|
+
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
32724
|
+
if (result.ok) clipped.push(result.clipped);
|
|
32725
|
+
else diagnostics.push({
|
|
32726
|
+
item: segment.draft.item,
|
|
32727
|
+
reason: result.reason
|
|
32728
|
+
});
|
|
32729
|
+
}
|
|
32730
|
+
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
32731
|
+
start: entry.visibleStart,
|
|
32732
|
+
end: entry.visibleEnd,
|
|
32733
|
+
orderKey: entry.segment.draft.orderKey
|
|
32734
|
+
})));
|
|
32735
|
+
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
32736
|
+
laneIndex: 0,
|
|
32737
|
+
laneCount: 1
|
|
32738
|
+
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
32739
|
+
}
|
|
32740
|
+
return {
|
|
32741
|
+
cellsByDate,
|
|
32742
|
+
diagnostics
|
|
32743
|
+
};
|
|
32744
|
+
}
|
|
32745
|
+
function assembleDays(viewportDays, cellsByDate, today) {
|
|
32746
|
+
return viewportDays.map((date) => {
|
|
32747
|
+
const dateKey = formatDateKey(date);
|
|
32748
|
+
return {
|
|
32749
|
+
date,
|
|
32750
|
+
dateKey,
|
|
32751
|
+
isToday: isSameCalendarDay(date, today),
|
|
32752
|
+
cells: cellsByDate.get(dateKey) ?? []
|
|
32753
|
+
};
|
|
32754
|
+
});
|
|
32755
|
+
}
|
|
32756
|
+
function parseDraft(item, accessors) {
|
|
32757
|
+
const startsAtIso = accessors.getStartsAt(item);
|
|
32758
|
+
const endsAtIso = accessors.getEndsAt(item);
|
|
32759
|
+
const timeZone = accessors.getTimeZone(item);
|
|
32760
|
+
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
32761
|
+
if (Number.isNaN(startsAtMinutes)) return {
|
|
32762
|
+
ok: false,
|
|
32763
|
+
reason: "invalid-start"
|
|
32764
|
+
};
|
|
32765
|
+
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
32766
|
+
if (startDateKey == null) return {
|
|
32767
|
+
ok: false,
|
|
32768
|
+
reason: "invalid-timezone"
|
|
32769
|
+
};
|
|
32770
|
+
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
32771
|
+
if (endDateKey == null) return {
|
|
32772
|
+
ok: false,
|
|
32773
|
+
reason: "invalid-end"
|
|
32774
|
+
};
|
|
32775
|
+
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
32776
|
+
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
32777
|
+
ok: false,
|
|
32778
|
+
reason: "invalid-end"
|
|
32779
|
+
};
|
|
32780
|
+
const spansMultipleDays = startDateKey < endDateKey;
|
|
32781
|
+
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
32782
|
+
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
32783
|
+
ok: false,
|
|
32784
|
+
reason: "non-positive-duration"
|
|
32785
|
+
};
|
|
32786
|
+
return {
|
|
32787
|
+
ok: true,
|
|
32788
|
+
draft: {
|
|
32789
|
+
item,
|
|
32790
|
+
startDateKey,
|
|
32791
|
+
endDateKey,
|
|
32792
|
+
startsAtMinutes,
|
|
32793
|
+
endsAtMinutes,
|
|
32794
|
+
color: accessors.getColor?.(item) ?? null,
|
|
32795
|
+
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
32796
|
+
}
|
|
32797
|
+
};
|
|
32798
|
+
}
|
|
32799
|
+
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
32800
|
+
const segments = [];
|
|
32801
|
+
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
32802
|
+
if (!viewportKeys.has(dateKey)) continue;
|
|
32803
|
+
const isStartDay = dateKey === draft.startDateKey;
|
|
32804
|
+
const isEndDay = dateKey === draft.endDateKey;
|
|
32805
|
+
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
32806
|
+
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
32807
|
+
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
32808
|
+
segments.push({
|
|
32809
|
+
draft,
|
|
32810
|
+
dateKey,
|
|
32811
|
+
startsAtMinutes,
|
|
32812
|
+
endsAtMinutes,
|
|
32813
|
+
continuesBefore: !isStartDay,
|
|
32814
|
+
continuesAfter: !isEndDay
|
|
32815
|
+
});
|
|
32816
|
+
}
|
|
32817
|
+
return segments;
|
|
32818
|
+
}
|
|
32819
|
+
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
32820
|
+
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
32821
|
+
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
32822
|
+
if (visibleEnd <= visibleStart) return {
|
|
32823
|
+
ok: false,
|
|
32824
|
+
reason: "out-of-visible-hours"
|
|
32825
|
+
};
|
|
32826
|
+
return {
|
|
32827
|
+
ok: true,
|
|
32828
|
+
clipped: {
|
|
32829
|
+
segment,
|
|
32830
|
+
visibleStart,
|
|
32831
|
+
visibleEnd
|
|
32832
|
+
}
|
|
32833
|
+
};
|
|
32834
|
+
}
|
|
32835
|
+
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
32836
|
+
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
32837
|
+
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
32838
|
+
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
32839
|
+
return {
|
|
32840
|
+
item: entry.segment.draft.item,
|
|
32841
|
+
dateKey: entry.segment.dateKey,
|
|
32842
|
+
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
32843
|
+
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
32844
|
+
visibleStartsAtMinutes: entry.visibleStart,
|
|
32845
|
+
visibleEndsAtMinutes: entry.visibleEnd,
|
|
32846
|
+
topPercent,
|
|
32847
|
+
heightPercent,
|
|
32848
|
+
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
32849
|
+
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
32850
|
+
laneIndex: lane.laneIndex,
|
|
32851
|
+
laneCount: lane.laneCount,
|
|
32852
|
+
color: entry.segment.draft.color
|
|
32853
|
+
};
|
|
32854
|
+
}
|
|
32855
|
+
function resolveBounds(bounds) {
|
|
32856
|
+
return {
|
|
32857
|
+
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
32858
|
+
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
32859
|
+
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
32860
|
+
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
32861
|
+
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
32862
|
+
};
|
|
32863
|
+
}
|
|
32864
|
+
function resolveHourRange(segments, bounds) {
|
|
32865
|
+
if (segments.length === 0) return {
|
|
32866
|
+
startHour: bounds.defaultStartHour,
|
|
32867
|
+
endHour: bounds.defaultEndHour
|
|
32868
|
+
};
|
|
32869
|
+
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
32870
|
+
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
32871
|
+
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
32872
|
+
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
32873
|
+
return {
|
|
32874
|
+
startHour,
|
|
32875
|
+
endHour: Math.max(startHour + 1, endHour)
|
|
32876
|
+
};
|
|
32877
|
+
}
|
|
32878
|
+
function buildHourTicks(startHour, endHour) {
|
|
32879
|
+
const ticks = [];
|
|
32880
|
+
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
32881
|
+
return ticks;
|
|
32882
|
+
}
|
|
32883
|
+
function groupSegmentsByDate(segments) {
|
|
32884
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
32885
|
+
for (const segment of segments) {
|
|
32886
|
+
const existing = grouped.get(segment.dateKey) ?? [];
|
|
32887
|
+
existing.push(segment);
|
|
32888
|
+
grouped.set(segment.dateKey, existing);
|
|
32889
|
+
}
|
|
32890
|
+
return grouped;
|
|
32891
|
+
}
|
|
32892
|
+
function toRangePercent(minutes, rangeMinutes) {
|
|
32893
|
+
if (rangeMinutes <= 0) return 0;
|
|
32894
|
+
return minutes / rangeMinutes * 100;
|
|
32895
|
+
}
|
|
32896
|
+
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
32897
|
+
if (view === "day") return [viewDate];
|
|
32898
|
+
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
32899
|
+
}
|
|
32900
|
+
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
32901
|
+
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
32902
|
+
const start = parseDateKey(startDateKey);
|
|
32903
|
+
parseDateKey(endDateKey);
|
|
32904
|
+
const keys = [];
|
|
32905
|
+
let cursor = start;
|
|
32906
|
+
while (formatDateKey(cursor) <= endDateKey) {
|
|
32907
|
+
keys.push(formatDateKey(cursor));
|
|
32908
|
+
cursor = addDays(cursor, 1);
|
|
32909
|
+
}
|
|
32910
|
+
return keys;
|
|
32911
|
+
}
|
|
32912
|
+
function parseDateKey(dateKey) {
|
|
32913
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
32914
|
+
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
32915
|
+
const [, year, month, day] = match;
|
|
32916
|
+
return {
|
|
32917
|
+
year: Number(year),
|
|
32918
|
+
month: Number(month),
|
|
32919
|
+
day: Number(day)
|
|
32920
|
+
};
|
|
32921
|
+
}
|
|
32922
|
+
//#endregion
|
|
32923
|
+
//#region ../calendar/src/group.ts
|
|
32924
|
+
/**
|
|
32925
|
+
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
32926
|
+
*
|
|
32927
|
+
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
32928
|
+
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
32929
|
+
*
|
|
32930
|
+
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
32931
|
+
* relative order of items within each date matches input order.
|
|
32932
|
+
*/
|
|
32933
|
+
function groupItemsByDate(items, accessors) {
|
|
32934
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
32935
|
+
for (const item of items) {
|
|
32936
|
+
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
32937
|
+
if (dateKey == null) continue;
|
|
32938
|
+
const existing = grouped.get(dateKey);
|
|
32939
|
+
if (existing !== void 0) existing.push(item);
|
|
32940
|
+
else grouped.set(dateKey, [item]);
|
|
32941
|
+
}
|
|
32942
|
+
return grouped;
|
|
32943
|
+
}
|
|
32944
|
+
//#endregion
|
|
32945
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
|
|
32946
|
+
/**
|
|
32947
|
+
* Date and time presentation for events: the single place an event's
|
|
32948
|
+
* timestamps become strings or date buckets. Calendar-grid primitives are
|
|
32949
|
+
* re-exported from @riverbankcms/calendar so block code has one import.
|
|
32950
|
+
*/
|
|
32951
|
+
/**
|
|
32952
|
+
* Format date for display.
|
|
32953
|
+
* Returns: "Monday, January 15, 2025"
|
|
32954
|
+
*/
|
|
32955
|
+
function formatDate(isoTimestamp, options) {
|
|
32956
|
+
return formatBookingDate(toScheduledBookingDateTime({
|
|
32957
|
+
startsAt: isoTimestamp,
|
|
32958
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
32959
|
+
locale: options?.locale ?? "en-US"
|
|
32960
|
+
}), {
|
|
32961
|
+
weekday: "long",
|
|
32962
|
+
year: "numeric",
|
|
32963
|
+
month: "long",
|
|
32964
|
+
day: "numeric"
|
|
32965
|
+
});
|
|
32966
|
+
}
|
|
32967
|
+
/**
|
|
32968
|
+
* Format time for display.
|
|
32969
|
+
* Returns: "10:00 AM"
|
|
32970
|
+
*/
|
|
32971
|
+
function formatTime(isoTimestamp, options) {
|
|
32972
|
+
return formatBookingTime(toScheduledBookingDateTime({
|
|
32973
|
+
startsAt: isoTimestamp,
|
|
32974
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
32975
|
+
locale: options?.locale ?? "en-US"
|
|
32976
|
+
}), {
|
|
32977
|
+
hour: "numeric",
|
|
32978
|
+
minute: "2-digit",
|
|
32979
|
+
hour12: true
|
|
32980
|
+
});
|
|
32981
|
+
}
|
|
32982
|
+
function formatEventDateParts(args) {
|
|
32983
|
+
const scheduledRange = toScheduledBookingDateRange({
|
|
32984
|
+
startsAt: args.startsAt,
|
|
32985
|
+
endsAt: args.endsAt,
|
|
32986
|
+
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
32987
|
+
locale: args.locale ?? "en-US"
|
|
32988
|
+
});
|
|
32989
|
+
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
32990
|
+
weekday: "long",
|
|
32991
|
+
year: "numeric",
|
|
32992
|
+
month: "long",
|
|
32993
|
+
day: "numeric"
|
|
32994
|
+
} });
|
|
32995
|
+
return {
|
|
32996
|
+
date,
|
|
32997
|
+
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
32998
|
+
startsAt: args.endsAt,
|
|
32999
|
+
timeZone: scheduledRange.timeZone,
|
|
33000
|
+
locale: args.locale ?? "en-US"
|
|
33001
|
+
}))}`
|
|
33002
|
+
};
|
|
33003
|
+
}
|
|
33004
|
+
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
33005
|
+
const dateTime = toScheduledBookingDateTime({
|
|
33006
|
+
startsAt: isoTimestamp,
|
|
33007
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33008
|
+
locale: options?.locale ?? "en-US"
|
|
33009
|
+
});
|
|
33010
|
+
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33011
|
+
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33012
|
+
return {
|
|
33013
|
+
weekday,
|
|
33014
|
+
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
33015
|
+
month,
|
|
33016
|
+
label: formatBookingDate(dateTime, {
|
|
33017
|
+
weekday: "long",
|
|
33018
|
+
year: "numeric",
|
|
33019
|
+
month: "long",
|
|
33020
|
+
day: "numeric"
|
|
33021
|
+
})
|
|
33022
|
+
};
|
|
33023
|
+
}
|
|
33024
|
+
/**
|
|
33025
|
+
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
33026
|
+
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
33027
|
+
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
33028
|
+
*/
|
|
33029
|
+
function groupEventsByDate(events, calendarTimeZone) {
|
|
33030
|
+
const grouped = groupItemsByDate(events, {
|
|
33031
|
+
getStartsAt: (event) => event.startsAt,
|
|
33032
|
+
getTimeZone: () => calendarTimeZone
|
|
33033
|
+
});
|
|
33034
|
+
const map = /* @__PURE__ */ new Map();
|
|
33035
|
+
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
33036
|
+
return map;
|
|
33037
|
+
}
|
|
33038
|
+
//#endregion
|
|
33039
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/people.ts
|
|
33040
|
+
function formatTeamMemberNames(names) {
|
|
33041
|
+
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
33042
|
+
if (normalized.length === 0) return null;
|
|
33043
|
+
const [firstName, secondName] = normalized;
|
|
33044
|
+
if (!firstName) return null;
|
|
33045
|
+
if (normalized.length === 1) return firstName;
|
|
33046
|
+
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
33047
|
+
const finalName = normalized[normalized.length - 1] ?? "";
|
|
33048
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
33049
|
+
}
|
|
33050
|
+
function formatEventTeacherLine(event) {
|
|
33051
|
+
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
33052
|
+
return names ? `with ${names}` : null;
|
|
33053
|
+
}
|
|
33054
|
+
//#endregion
|
|
33055
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
|
|
33056
|
+
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
33057
|
+
function resolveRuntimeEventPricing(event) {
|
|
33058
|
+
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
33059
|
+
if (resolveRuntimeEventDestination(event).kind === "external") return {
|
|
33060
|
+
kind: "external",
|
|
33061
|
+
display: {
|
|
33062
|
+
kind: "external",
|
|
33063
|
+
label: "External registration"
|
|
33064
|
+
}
|
|
33065
|
+
};
|
|
33066
|
+
const legacy = event;
|
|
33067
|
+
const currency = readCurrency(legacy.currency);
|
|
33068
|
+
if (event.requiresRegistration === false) return {
|
|
33069
|
+
kind: "drop_in",
|
|
33070
|
+
currency,
|
|
33071
|
+
display: {
|
|
33072
|
+
kind: "free",
|
|
33073
|
+
currency
|
|
33074
|
+
}
|
|
33075
|
+
};
|
|
33076
|
+
if (legacy.pricingMode === "ticket_types") return {
|
|
33077
|
+
kind: "ticket_types",
|
|
33078
|
+
currency,
|
|
33079
|
+
ticketTypes: Array.isArray(legacy.ticketTypes) ? legacy.ticketTypes.filter(isPublicEventTicketType) : [],
|
|
33080
|
+
display: readTicketTypePriceDisplay(legacy.priceDisplay, currency)
|
|
33081
|
+
};
|
|
33082
|
+
const amount = typeof legacy.price === "number" ? legacy.price : 0;
|
|
33083
|
+
if (legacy.pricingType === "paid" && amount > 0) return {
|
|
33084
|
+
kind: "single_paid",
|
|
33085
|
+
amount,
|
|
33086
|
+
currency,
|
|
33087
|
+
display: {
|
|
33088
|
+
kind: "exact",
|
|
33089
|
+
amount,
|
|
33090
|
+
currency
|
|
33091
|
+
}
|
|
33092
|
+
};
|
|
33093
|
+
return {
|
|
33094
|
+
kind: "free_registration",
|
|
33095
|
+
currency,
|
|
33096
|
+
display: {
|
|
33097
|
+
kind: "free",
|
|
33098
|
+
currency
|
|
33099
|
+
}
|
|
33100
|
+
};
|
|
33101
|
+
}
|
|
33102
|
+
function readCurrency(value) {
|
|
33103
|
+
return typeof value === "string" && value.trim().length > 0 ? value : DEFAULT_EVENT_CURRENCY;
|
|
33104
|
+
}
|
|
33105
|
+
function isRecord$13(value) {
|
|
33106
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33107
|
+
}
|
|
33108
|
+
function isPublicEventPricing(value) {
|
|
33109
|
+
if (!isRecord$13(value) || typeof value.kind !== "string") return false;
|
|
33110
|
+
return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "sliding_scale" || value.kind === "ticket_types" || value.kind === "whole_series" || value.kind === "external";
|
|
33111
|
+
}
|
|
33112
|
+
function isPublicEventTicketType(value) {
|
|
33113
|
+
return isRecord$13(value) && typeof value.id === "string";
|
|
33114
|
+
}
|
|
33115
|
+
function readTicketTypePriceDisplay(value, currency) {
|
|
33116
|
+
const display = readLegacyPriceDisplay(value, currency);
|
|
33117
|
+
return display.kind === "whole_series" || display.kind === "external" ? {
|
|
33118
|
+
kind: "unavailable",
|
|
33119
|
+
currency
|
|
33120
|
+
} : display;
|
|
33121
|
+
}
|
|
33122
|
+
function readLegacyPriceDisplay(value, currency) {
|
|
33123
|
+
if (!isRecord$13(value) || typeof value.kind !== "string") return {
|
|
33124
|
+
kind: "unavailable",
|
|
33125
|
+
currency
|
|
33126
|
+
};
|
|
33127
|
+
const displayCurrency = readCurrency(value.currency);
|
|
33128
|
+
switch (value.kind) {
|
|
33129
|
+
case "free": return {
|
|
33130
|
+
kind: "free",
|
|
33131
|
+
currency: displayCurrency
|
|
33132
|
+
};
|
|
33133
|
+
case "exact": return readAmount(value.amount) !== null ? {
|
|
33134
|
+
kind: "exact",
|
|
33135
|
+
amount: readAmount(value.amount) ?? 0,
|
|
33136
|
+
currency: displayCurrency
|
|
33137
|
+
} : {
|
|
33138
|
+
kind: "unavailable",
|
|
33139
|
+
currency
|
|
33140
|
+
};
|
|
33141
|
+
case "from": return readAmount(value.amount) !== null ? {
|
|
33142
|
+
kind: "from",
|
|
33143
|
+
amount: readAmount(value.amount) ?? 0,
|
|
33144
|
+
currency: displayCurrency
|
|
33145
|
+
} : {
|
|
33146
|
+
kind: "unavailable",
|
|
33147
|
+
currency
|
|
33148
|
+
};
|
|
33149
|
+
case "range": {
|
|
33150
|
+
const minAmount = readAmount(value.minAmount);
|
|
33151
|
+
const maxAmount = readAmount(value.maxAmount);
|
|
33152
|
+
return minAmount !== null && maxAmount !== null ? {
|
|
33153
|
+
kind: "range",
|
|
33154
|
+
minAmount,
|
|
33155
|
+
maxAmount,
|
|
33156
|
+
currency: displayCurrency
|
|
33157
|
+
} : {
|
|
33158
|
+
kind: "unavailable",
|
|
33159
|
+
currency
|
|
33160
|
+
};
|
|
33161
|
+
}
|
|
33162
|
+
case "mixed": return readAmount(value.paidFromAmount) !== null ? {
|
|
33163
|
+
kind: "mixed",
|
|
33164
|
+
paidFromAmount: readAmount(value.paidFromAmount) ?? 0,
|
|
33165
|
+
paidToAmount: readAmount(value.paidToAmount) ?? readAmount(value.paidFromAmount) ?? 0,
|
|
33166
|
+
currency: displayCurrency
|
|
33167
|
+
} : {
|
|
33168
|
+
kind: "unavailable",
|
|
33169
|
+
currency
|
|
33170
|
+
};
|
|
33171
|
+
case "unavailable": return {
|
|
33172
|
+
kind: "unavailable",
|
|
33173
|
+
currency: displayCurrency
|
|
33174
|
+
};
|
|
33175
|
+
case "whole_series": return isRecord$13(value.display) ? {
|
|
33176
|
+
kind: "whole_series",
|
|
33177
|
+
display: readCoursePriceDisplay(value.display, displayCurrency)
|
|
33178
|
+
} : {
|
|
33179
|
+
kind: "whole_series",
|
|
33180
|
+
display: {
|
|
33181
|
+
kind: "unavailable",
|
|
33182
|
+
currency: displayCurrency
|
|
33183
|
+
}
|
|
33184
|
+
};
|
|
33185
|
+
default: return {
|
|
33186
|
+
kind: "unavailable",
|
|
33187
|
+
currency
|
|
33188
|
+
};
|
|
33189
|
+
}
|
|
33190
|
+
}
|
|
33191
|
+
function readCoursePriceDisplay(value, currency) {
|
|
33192
|
+
switch (value.kind) {
|
|
33193
|
+
case "free": return {
|
|
33194
|
+
kind: "free",
|
|
33195
|
+
currency: readCurrency(value.currency)
|
|
33196
|
+
};
|
|
33197
|
+
case "exact": return readAmount(value.amount) !== null ? {
|
|
33198
|
+
kind: "exact",
|
|
33199
|
+
amount: readAmount(value.amount) ?? 0,
|
|
33200
|
+
currency: readCurrency(value.currency)
|
|
33201
|
+
} : {
|
|
33202
|
+
kind: "unavailable",
|
|
33203
|
+
currency
|
|
33204
|
+
};
|
|
33205
|
+
case "from": return readAmount(value.amount) !== null ? {
|
|
33206
|
+
kind: "from",
|
|
33207
|
+
amount: readAmount(value.amount) ?? 0,
|
|
33208
|
+
currency: readCurrency(value.currency)
|
|
33209
|
+
} : {
|
|
33210
|
+
kind: "unavailable",
|
|
33211
|
+
currency
|
|
33212
|
+
};
|
|
33213
|
+
default: return {
|
|
33214
|
+
kind: "unavailable",
|
|
33215
|
+
currency
|
|
33216
|
+
};
|
|
33217
|
+
}
|
|
33218
|
+
}
|
|
33219
|
+
function readAmount(value) {
|
|
33220
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
33221
|
+
}
|
|
33222
|
+
/**
|
|
33223
|
+
* Format event price for display.
|
|
33224
|
+
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
33225
|
+
*
|
|
33226
|
+
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
33227
|
+
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
33228
|
+
* surfaces stay consistent.
|
|
33229
|
+
*/
|
|
33230
|
+
function formatEventPrice(event) {
|
|
33231
|
+
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
33232
|
+
}
|
|
33233
|
+
//#endregion
|
|
33234
|
+
//#region ../blocks/src/system/constants/events.ts
|
|
33235
|
+
const eventCalendarDisplayModeValues = [
|
|
33236
|
+
"combined",
|
|
33237
|
+
"calendar",
|
|
33238
|
+
"list",
|
|
33239
|
+
"timetable",
|
|
33240
|
+
"week-board"
|
|
33241
|
+
];
|
|
33242
|
+
const eventCalendarDefaultViewValues = ["month", "week"];
|
|
33243
|
+
const eventCalendarListLayoutValues = [
|
|
33244
|
+
"stack",
|
|
33245
|
+
"grid",
|
|
33246
|
+
"compact"
|
|
33247
|
+
];
|
|
33248
|
+
const eventCalendarListLayoutOptions = [
|
|
33249
|
+
{
|
|
33250
|
+
id: "stack",
|
|
33251
|
+
label: "Stack"
|
|
33252
|
+
},
|
|
33253
|
+
{
|
|
33254
|
+
id: "grid",
|
|
33255
|
+
label: "Grid"
|
|
33256
|
+
},
|
|
33257
|
+
{
|
|
33258
|
+
id: "compact",
|
|
33259
|
+
label: "Compact"
|
|
33260
|
+
}
|
|
33261
|
+
];
|
|
33262
|
+
function isEventCalendarDisplayMode(value) {
|
|
33263
|
+
return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
|
|
33264
|
+
}
|
|
33265
|
+
function isEventCalendarDefaultView(value) {
|
|
33266
|
+
return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
|
|
33267
|
+
}
|
|
33268
|
+
function isEventCalendarListLayout(value) {
|
|
33269
|
+
return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
|
|
33270
|
+
}
|
|
33271
|
+
//#endregion
|
|
33272
|
+
//#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
|
|
33273
|
+
const previewStageSchema = z.enum(["preview", "published"]);
|
|
33274
|
+
const registrationButtonVariantSchema = blockButtonRoleSchema;
|
|
33275
|
+
const eventButtonVariantSchema = blockButtonRoleSchema;
|
|
33276
|
+
const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
|
|
33277
|
+
const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
|
|
33278
|
+
const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
|
|
33279
|
+
const columnOptionSchema = z.enum([
|
|
33280
|
+
"2",
|
|
33281
|
+
"3",
|
|
33282
|
+
"4"
|
|
33283
|
+
]);
|
|
33284
|
+
const weekStartSchema = z.enum(["sunday", "monday"]);
|
|
33285
|
+
const filterAutoShowSchema = z.enum([
|
|
33286
|
+
"auto",
|
|
33287
|
+
"always",
|
|
33288
|
+
"never"
|
|
33289
|
+
]);
|
|
33290
|
+
const calendarTabViewsSchema = z.enum([
|
|
33291
|
+
"both",
|
|
33292
|
+
"month",
|
|
33293
|
+
"week"
|
|
33294
|
+
]);
|
|
33295
|
+
//#endregion
|
|
33296
|
+
//#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
|
|
33297
|
+
/**
|
|
33298
|
+
* Determine which wizard steps to show based on context and payment requirements.
|
|
33299
|
+
*
|
|
33300
|
+
* @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
|
|
33301
|
+
* @param needsPaymentStep - Whether user must complete payment method selection
|
|
33302
|
+
* @returns Array of wizard steps to display
|
|
33303
|
+
*/
|
|
33304
|
+
function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep) {
|
|
33305
|
+
const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
|
|
33306
|
+
"select-event",
|
|
33307
|
+
"select-tickets",
|
|
33308
|
+
"enter-details"
|
|
33309
|
+
];
|
|
33310
|
+
if (!needsPaymentStep) return baseSteps;
|
|
33311
|
+
return [...baseSteps, "select-payment"];
|
|
33312
|
+
}
|
|
33313
|
+
//#endregion
|
|
33314
|
+
//#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
|
|
33315
|
+
function eventMatchesOccurrenceId(event, occurrenceId) {
|
|
33316
|
+
return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
|
|
33317
|
+
}
|
|
33318
|
+
function findPreSelectedEvent(input) {
|
|
33319
|
+
const occurrenceContext = input.occurrenceContext;
|
|
33320
|
+
if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
|
|
33321
|
+
const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
|
|
33322
|
+
startsAt: occurrenceContext.startsAt,
|
|
33323
|
+
timeZone: occurrenceContext.timeZone
|
|
33324
|
+
}));
|
|
33325
|
+
return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
|
|
33326
|
+
startsAt: event.startsAt,
|
|
33327
|
+
timeZone: event.timeZone
|
|
33328
|
+
})) === contextDate) ?? null;
|
|
33329
|
+
}
|
|
33330
|
+
function resolveEventRequiresPayment(input) {
|
|
33331
|
+
if (input.eligibility) return input.eligibility.requiresPayment;
|
|
33332
|
+
if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
|
|
33333
|
+
if (!input.event) return false;
|
|
33334
|
+
return publicEventPricingType(resolveRuntimeEventPricing(input.event)) === "paid";
|
|
33335
|
+
}
|
|
33336
|
+
//#endregion
|
|
33337
|
+
//#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
|
|
33338
|
+
function toRegistrationEvent(event) {
|
|
33339
|
+
return {
|
|
33340
|
+
...event,
|
|
33341
|
+
pricing: resolveRuntimeEventPricing(event),
|
|
33342
|
+
selectionId: event.id,
|
|
33343
|
+
registrationSubject: {
|
|
33344
|
+
kind: "occurrence",
|
|
33345
|
+
occurrenceId: event.occurrenceId
|
|
33346
|
+
}
|
|
33347
|
+
};
|
|
33348
|
+
}
|
|
33349
|
+
function toRegistrationEvents(events) {
|
|
33350
|
+
return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
|
|
33351
|
+
}
|
|
33352
|
+
function toWholeSeriesTicketType(option) {
|
|
33353
|
+
return {
|
|
33354
|
+
...option,
|
|
33355
|
+
capacityMode: option.capacityLimit === null ? "unlimited" : "limited",
|
|
33356
|
+
attendanceScope: option.attendanceScope ?? void 0,
|
|
33357
|
+
availability: {
|
|
33358
|
+
kind: "available",
|
|
33359
|
+
remainingCapacity: option.capacityLimit
|
|
33360
|
+
}
|
|
33361
|
+
};
|
|
33362
|
+
}
|
|
33363
|
+
function toWholeSeriesPricing(offering) {
|
|
33364
|
+
const resolved = resolveWholeSeriesPricingState(offering);
|
|
33365
|
+
switch (offering.pricing.pricingStrategy) {
|
|
33366
|
+
case "free": return {
|
|
33367
|
+
kind: "free_registration",
|
|
33368
|
+
currency: offering.currency,
|
|
33369
|
+
display: {
|
|
33370
|
+
kind: "free",
|
|
33371
|
+
currency: offering.currency
|
|
32210
33372
|
}
|
|
32211
33373
|
};
|
|
32212
33374
|
case "single": {
|
|
@@ -32595,7 +33757,7 @@ const eventRegistrationBlockDefinition = {
|
|
|
32595
33757
|
{
|
|
32596
33758
|
id: "showCapacity",
|
|
32597
33759
|
type: "boolean",
|
|
32598
|
-
label: "Show available
|
|
33760
|
+
label: "Show available places",
|
|
32599
33761
|
description: "Display remaining capacity",
|
|
32600
33762
|
required: false,
|
|
32601
33763
|
defaultValue: true,
|
|
@@ -33140,591 +34302,26 @@ const eventSpotlightBlockDefinition = {
|
|
|
33140
34302
|
dataLoaders: { events: {
|
|
33141
34303
|
endpoint: "listPublicEvents",
|
|
33142
34304
|
params: {
|
|
33143
|
-
siteId: { $bind: { from: "$root.siteId" } },
|
|
33144
|
-
limit: { $bind: {
|
|
33145
|
-
from: "content.maxEvents",
|
|
33146
|
-
fallback: "3"
|
|
33147
|
-
} },
|
|
33148
|
-
stage: { $bind: {
|
|
33149
|
-
from: "$root.previewStage",
|
|
33150
|
-
fallback: "published"
|
|
33151
|
-
} },
|
|
33152
|
-
detailLevel: "listing",
|
|
33153
|
-
readProfile: "spotlight"
|
|
33154
|
-
},
|
|
33155
|
-
mode: "server"
|
|
33156
|
-
} }
|
|
33157
|
-
};
|
|
33158
|
-
//#endregion
|
|
33159
|
-
//#region ../calendar/src/grid.ts
|
|
33160
|
-
const DAY_NAMES = [
|
|
33161
|
-
"Sun",
|
|
33162
|
-
"Mon",
|
|
33163
|
-
"Tue",
|
|
33164
|
-
"Wed",
|
|
33165
|
-
"Thu",
|
|
33166
|
-
"Fri",
|
|
33167
|
-
"Sat"
|
|
33168
|
-
];
|
|
33169
|
-
const MONTH_NAMES = [
|
|
33170
|
-
"January",
|
|
33171
|
-
"February",
|
|
33172
|
-
"March",
|
|
33173
|
-
"April",
|
|
33174
|
-
"May",
|
|
33175
|
-
"June",
|
|
33176
|
-
"July",
|
|
33177
|
-
"August",
|
|
33178
|
-
"September",
|
|
33179
|
-
"October",
|
|
33180
|
-
"November",
|
|
33181
|
-
"December"
|
|
33182
|
-
];
|
|
33183
|
-
function formatDateKey(date) {
|
|
33184
|
-
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
33185
|
-
}
|
|
33186
|
-
function isToday(date, today) {
|
|
33187
|
-
return isSameCalendarDay(date, today);
|
|
33188
|
-
}
|
|
33189
|
-
function isSameCalendarDay(left, right) {
|
|
33190
|
-
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
33191
|
-
}
|
|
33192
|
-
function addDays(date, days) {
|
|
33193
|
-
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
33194
|
-
return {
|
|
33195
|
-
year: result.getUTCFullYear(),
|
|
33196
|
-
month: result.getUTCMonth() + 1,
|
|
33197
|
-
day: result.getUTCDate()
|
|
33198
|
-
};
|
|
33199
|
-
}
|
|
33200
|
-
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
33201
|
-
const firstDay = {
|
|
33202
|
-
year,
|
|
33203
|
-
month,
|
|
33204
|
-
day: 1
|
|
33205
|
-
};
|
|
33206
|
-
const dayOfWeek = getDayOfWeek(firstDay);
|
|
33207
|
-
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33208
|
-
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
33209
|
-
}
|
|
33210
|
-
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
33211
|
-
const dayOfWeek = getDayOfWeek(baseDate);
|
|
33212
|
-
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33213
|
-
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
33214
|
-
}
|
|
33215
|
-
function getDayOfWeek(date) {
|
|
33216
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
33217
|
-
}
|
|
33218
|
-
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
33219
|
-
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
33220
|
-
assertWeekCalendarDays(days);
|
|
33221
|
-
return days;
|
|
33222
|
-
}
|
|
33223
|
-
function assertWeekCalendarDays(days) {
|
|
33224
|
-
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
33225
|
-
}
|
|
33226
|
-
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
33227
|
-
if (startOfWeek === "monday") {
|
|
33228
|
-
const sunday = dayNames[0] ?? "Sun";
|
|
33229
|
-
return [...dayNames.slice(1), sunday];
|
|
33230
|
-
}
|
|
33231
|
-
return [...dayNames];
|
|
33232
|
-
}
|
|
33233
|
-
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
33234
|
-
if (view === "week") {
|
|
33235
|
-
const weekStart = days[0];
|
|
33236
|
-
const weekEnd = days[6];
|
|
33237
|
-
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33238
|
-
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
33239
|
-
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
33240
|
-
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
33241
|
-
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
33242
|
-
}
|
|
33243
|
-
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33244
|
-
}
|
|
33245
|
-
function buildCalendarViewport(input) {
|
|
33246
|
-
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
33247
|
-
if (input.view === "week") {
|
|
33248
|
-
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
33249
|
-
return {
|
|
33250
|
-
kind: "week",
|
|
33251
|
-
anchorDate: input.anchorDate,
|
|
33252
|
-
days,
|
|
33253
|
-
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
33254
|
-
};
|
|
33255
|
-
}
|
|
33256
|
-
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
33257
|
-
return {
|
|
33258
|
-
kind: "month",
|
|
33259
|
-
anchorDate: input.anchorDate,
|
|
33260
|
-
days,
|
|
33261
|
-
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
33262
|
-
};
|
|
33263
|
-
}
|
|
33264
|
-
//#endregion
|
|
33265
|
-
//#region ../calendar/src/lane-packing.ts
|
|
33266
|
-
/**
|
|
33267
|
-
* Assign lanes to a list of intervals.
|
|
33268
|
-
*
|
|
33269
|
-
* Returns one result per input in the same order as the input. Internally
|
|
33270
|
-
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
33271
|
-
* permutes results back to input order.
|
|
33272
|
-
*/
|
|
33273
|
-
function assignLanes(intervals) {
|
|
33274
|
-
if (intervals.length === 0) return [];
|
|
33275
|
-
const indexed = intervals.map((interval, index) => ({
|
|
33276
|
-
interval,
|
|
33277
|
-
index
|
|
33278
|
-
}));
|
|
33279
|
-
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
33280
|
-
const results = new Array(intervals.length);
|
|
33281
|
-
let cluster = [];
|
|
33282
|
-
let activeLaneEnds = [];
|
|
33283
|
-
let clusterMaxEnd = -Infinity;
|
|
33284
|
-
const closeCluster = () => {
|
|
33285
|
-
if (cluster.length === 0) return;
|
|
33286
|
-
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
33287
|
-
for (const entry of cluster) results[entry.index] = {
|
|
33288
|
-
laneIndex: entry.laneIndex,
|
|
33289
|
-
laneCount
|
|
33290
|
-
};
|
|
33291
|
-
cluster = [];
|
|
33292
|
-
activeLaneEnds = [];
|
|
33293
|
-
clusterMaxEnd = -Infinity;
|
|
33294
|
-
};
|
|
33295
|
-
for (const { interval, index } of indexed) {
|
|
33296
|
-
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
33297
|
-
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
33298
|
-
activeLaneEnds[laneIndex] = interval.end;
|
|
33299
|
-
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
33300
|
-
cluster.push({
|
|
33301
|
-
index,
|
|
33302
|
-
laneIndex
|
|
33303
|
-
});
|
|
33304
|
-
}
|
|
33305
|
-
closeCluster();
|
|
33306
|
-
return results;
|
|
33307
|
-
}
|
|
33308
|
-
function firstAvailableLane(activeLaneEnds, start) {
|
|
33309
|
-
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
33310
|
-
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
33311
|
-
}
|
|
33312
|
-
//#endregion
|
|
33313
|
-
//#region ../calendar/src/timezone.ts
|
|
33314
|
-
/**
|
|
33315
|
-
* Timezone boundary helpers — internal.
|
|
33316
|
-
*
|
|
33317
|
-
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
33318
|
-
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
33319
|
-
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
33320
|
-
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
33321
|
-
* public surface.
|
|
33322
|
-
*/
|
|
33323
|
-
const MINUTES_PER_HOUR = 60;
|
|
33324
|
-
/**
|
|
33325
|
-
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
33326
|
-
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
33327
|
-
*/
|
|
33328
|
-
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
33329
|
-
const date = new Date(isoTimestamp);
|
|
33330
|
-
if (Number.isNaN(date.getTime())) return NaN;
|
|
33331
|
-
if (!isValidTimeZone(timeZone)) return NaN;
|
|
33332
|
-
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
33333
|
-
timeZone,
|
|
33334
|
-
hour: "2-digit",
|
|
33335
|
-
minute: "2-digit",
|
|
33336
|
-
hourCycle: "h23"
|
|
33337
|
-
}).formatToParts(date);
|
|
33338
|
-
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
33339
|
-
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
33340
|
-
const hour = hourPart === 24 ? 0 : hourPart;
|
|
33341
|
-
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
33342
|
-
return hour * MINUTES_PER_HOUR + minute;
|
|
33343
|
-
}
|
|
33344
|
-
/**
|
|
33345
|
-
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
33346
|
-
* Returns null if the inputs cannot be parsed.
|
|
33347
|
-
*/
|
|
33348
|
-
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
33349
|
-
const date = new Date(isoTimestamp);
|
|
33350
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
33351
|
-
if (!isValidTimeZone(timeZone)) return null;
|
|
33352
|
-
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
33353
|
-
timeZone,
|
|
33354
|
-
year: "numeric",
|
|
33355
|
-
month: "2-digit",
|
|
33356
|
-
day: "2-digit"
|
|
33357
|
-
}).formatToParts(date);
|
|
33358
|
-
const year = parts.find((part) => part.type === "year")?.value;
|
|
33359
|
-
const month = parts.find((part) => part.type === "month")?.value;
|
|
33360
|
-
const day = parts.find((part) => part.type === "day")?.value;
|
|
33361
|
-
if (year === void 0 || year.length === 0) return null;
|
|
33362
|
-
if (month === void 0 || month.length === 0) return null;
|
|
33363
|
-
if (day === void 0 || day.length === 0) return null;
|
|
33364
|
-
return `${year}-${month}-${day}`;
|
|
33365
|
-
}
|
|
33366
|
-
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
33367
|
-
function isValidTimeZone(timeZone) {
|
|
33368
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
33369
|
-
const cached = validTimeZoneCache.get(timeZone);
|
|
33370
|
-
if (cached !== void 0) return cached;
|
|
33371
|
-
try {
|
|
33372
|
-
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
33373
|
-
validTimeZoneCache.set(timeZone, true);
|
|
33374
|
-
return true;
|
|
33375
|
-
} catch {
|
|
33376
|
-
validTimeZoneCache.set(timeZone, false);
|
|
33377
|
-
return false;
|
|
33378
|
-
}
|
|
33379
|
-
}
|
|
33380
|
-
//#endregion
|
|
33381
|
-
//#region ../calendar/src/timetable.ts
|
|
33382
|
-
/**
|
|
33383
|
-
* Timetable model — the package's core surface.
|
|
33384
|
-
*
|
|
33385
|
-
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
33386
|
-
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
33387
|
-
* plus a diagnostics list for items dropped during processing.
|
|
33388
|
-
*
|
|
33389
|
-
* The function is pure: given identical input (including `now`), it returns
|
|
33390
|
-
* the same output. The model has no opinion on what an item is — pass
|
|
33391
|
-
* accessors.
|
|
33392
|
-
*/
|
|
33393
|
-
const DEFAULT_BOUNDS = {
|
|
33394
|
-
minStartHour: 5,
|
|
33395
|
-
maxEndHour: 23,
|
|
33396
|
-
defaultStartHour: 6,
|
|
33397
|
-
defaultEndHour: 21,
|
|
33398
|
-
minHeightPercent: 2.5
|
|
34305
|
+
siteId: { $bind: { from: "$root.siteId" } },
|
|
34306
|
+
limit: { $bind: {
|
|
34307
|
+
from: "content.maxEvents",
|
|
34308
|
+
fallback: "3"
|
|
34309
|
+
} },
|
|
34310
|
+
stage: { $bind: {
|
|
34311
|
+
from: "$root.previewStage",
|
|
34312
|
+
fallback: "published"
|
|
34313
|
+
} },
|
|
34314
|
+
detailLevel: "listing",
|
|
34315
|
+
readProfile: "spotlight"
|
|
34316
|
+
},
|
|
34317
|
+
mode: "server"
|
|
34318
|
+
} }
|
|
33399
34319
|
};
|
|
33400
|
-
function buildTimetableModel$1(input) {
|
|
33401
|
-
const bounds = resolveBounds(input.bounds);
|
|
33402
|
-
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
33403
|
-
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
33404
|
-
const parsed = parseAllItems(input.items, input);
|
|
33405
|
-
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
33406
|
-
const hourRange = resolveHourRange(inViewport, bounds);
|
|
33407
|
-
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
33408
|
-
return {
|
|
33409
|
-
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
33410
|
-
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
33411
|
-
startHour: hourRange.startHour,
|
|
33412
|
-
endHour: hourRange.endHour,
|
|
33413
|
-
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
33414
|
-
hasItems: inViewport.length > 0,
|
|
33415
|
-
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
33416
|
-
};
|
|
33417
|
-
}
|
|
33418
|
-
function parseAllItems(items, accessors) {
|
|
33419
|
-
const drafts = [];
|
|
33420
|
-
const diagnostics = [];
|
|
33421
|
-
for (const item of items) {
|
|
33422
|
-
const result = parseDraft(item, accessors);
|
|
33423
|
-
if (result.ok) drafts.push(result.draft);
|
|
33424
|
-
else diagnostics.push({
|
|
33425
|
-
item,
|
|
33426
|
-
reason: result.reason
|
|
33427
|
-
});
|
|
33428
|
-
}
|
|
33429
|
-
return {
|
|
33430
|
-
drafts,
|
|
33431
|
-
diagnostics
|
|
33432
|
-
};
|
|
33433
|
-
}
|
|
33434
|
-
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
33435
|
-
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
33436
|
-
const rangeStartMinutes = hourRange.startHour * 60;
|
|
33437
|
-
const rangeEndMinutes = hourRange.endHour * 60;
|
|
33438
|
-
const segmentsByDate = groupSegmentsByDate(segments);
|
|
33439
|
-
const cellsByDate = /* @__PURE__ */ new Map();
|
|
33440
|
-
const diagnostics = [];
|
|
33441
|
-
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
33442
|
-
const clipped = [];
|
|
33443
|
-
for (const segment of daySegments) {
|
|
33444
|
-
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
33445
|
-
if (result.ok) clipped.push(result.clipped);
|
|
33446
|
-
else diagnostics.push({
|
|
33447
|
-
item: segment.draft.item,
|
|
33448
|
-
reason: result.reason
|
|
33449
|
-
});
|
|
33450
|
-
}
|
|
33451
|
-
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
33452
|
-
start: entry.visibleStart,
|
|
33453
|
-
end: entry.visibleEnd,
|
|
33454
|
-
orderKey: entry.segment.draft.orderKey
|
|
33455
|
-
})));
|
|
33456
|
-
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
33457
|
-
laneIndex: 0,
|
|
33458
|
-
laneCount: 1
|
|
33459
|
-
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
33460
|
-
}
|
|
33461
|
-
return {
|
|
33462
|
-
cellsByDate,
|
|
33463
|
-
diagnostics
|
|
33464
|
-
};
|
|
33465
|
-
}
|
|
33466
|
-
function assembleDays(viewportDays, cellsByDate, today) {
|
|
33467
|
-
return viewportDays.map((date) => {
|
|
33468
|
-
const dateKey = formatDateKey(date);
|
|
33469
|
-
return {
|
|
33470
|
-
date,
|
|
33471
|
-
dateKey,
|
|
33472
|
-
isToday: isSameCalendarDay(date, today),
|
|
33473
|
-
cells: cellsByDate.get(dateKey) ?? []
|
|
33474
|
-
};
|
|
33475
|
-
});
|
|
33476
|
-
}
|
|
33477
|
-
function parseDraft(item, accessors) {
|
|
33478
|
-
const startsAtIso = accessors.getStartsAt(item);
|
|
33479
|
-
const endsAtIso = accessors.getEndsAt(item);
|
|
33480
|
-
const timeZone = accessors.getTimeZone(item);
|
|
33481
|
-
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
33482
|
-
if (Number.isNaN(startsAtMinutes)) return {
|
|
33483
|
-
ok: false,
|
|
33484
|
-
reason: "invalid-start"
|
|
33485
|
-
};
|
|
33486
|
-
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
33487
|
-
if (startDateKey == null) return {
|
|
33488
|
-
ok: false,
|
|
33489
|
-
reason: "invalid-timezone"
|
|
33490
|
-
};
|
|
33491
|
-
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
33492
|
-
if (endDateKey == null) return {
|
|
33493
|
-
ok: false,
|
|
33494
|
-
reason: "invalid-end"
|
|
33495
|
-
};
|
|
33496
|
-
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
33497
|
-
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
33498
|
-
ok: false,
|
|
33499
|
-
reason: "invalid-end"
|
|
33500
|
-
};
|
|
33501
|
-
const spansMultipleDays = startDateKey < endDateKey;
|
|
33502
|
-
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
33503
|
-
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
33504
|
-
ok: false,
|
|
33505
|
-
reason: "non-positive-duration"
|
|
33506
|
-
};
|
|
33507
|
-
return {
|
|
33508
|
-
ok: true,
|
|
33509
|
-
draft: {
|
|
33510
|
-
item,
|
|
33511
|
-
startDateKey,
|
|
33512
|
-
endDateKey,
|
|
33513
|
-
startsAtMinutes,
|
|
33514
|
-
endsAtMinutes,
|
|
33515
|
-
color: accessors.getColor?.(item) ?? null,
|
|
33516
|
-
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
33517
|
-
}
|
|
33518
|
-
};
|
|
33519
|
-
}
|
|
33520
|
-
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
33521
|
-
const segments = [];
|
|
33522
|
-
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
33523
|
-
if (!viewportKeys.has(dateKey)) continue;
|
|
33524
|
-
const isStartDay = dateKey === draft.startDateKey;
|
|
33525
|
-
const isEndDay = dateKey === draft.endDateKey;
|
|
33526
|
-
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
33527
|
-
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
33528
|
-
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
33529
|
-
segments.push({
|
|
33530
|
-
draft,
|
|
33531
|
-
dateKey,
|
|
33532
|
-
startsAtMinutes,
|
|
33533
|
-
endsAtMinutes,
|
|
33534
|
-
continuesBefore: !isStartDay,
|
|
33535
|
-
continuesAfter: !isEndDay
|
|
33536
|
-
});
|
|
33537
|
-
}
|
|
33538
|
-
return segments;
|
|
33539
|
-
}
|
|
33540
|
-
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
33541
|
-
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
33542
|
-
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
33543
|
-
if (visibleEnd <= visibleStart) return {
|
|
33544
|
-
ok: false,
|
|
33545
|
-
reason: "out-of-visible-hours"
|
|
33546
|
-
};
|
|
33547
|
-
return {
|
|
33548
|
-
ok: true,
|
|
33549
|
-
clipped: {
|
|
33550
|
-
segment,
|
|
33551
|
-
visibleStart,
|
|
33552
|
-
visibleEnd
|
|
33553
|
-
}
|
|
33554
|
-
};
|
|
33555
|
-
}
|
|
33556
|
-
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
33557
|
-
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
33558
|
-
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
33559
|
-
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
33560
|
-
return {
|
|
33561
|
-
item: entry.segment.draft.item,
|
|
33562
|
-
dateKey: entry.segment.dateKey,
|
|
33563
|
-
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
33564
|
-
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
33565
|
-
visibleStartsAtMinutes: entry.visibleStart,
|
|
33566
|
-
visibleEndsAtMinutes: entry.visibleEnd,
|
|
33567
|
-
topPercent,
|
|
33568
|
-
heightPercent,
|
|
33569
|
-
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
33570
|
-
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
33571
|
-
laneIndex: lane.laneIndex,
|
|
33572
|
-
laneCount: lane.laneCount,
|
|
33573
|
-
color: entry.segment.draft.color
|
|
33574
|
-
};
|
|
33575
|
-
}
|
|
33576
|
-
function resolveBounds(bounds) {
|
|
33577
|
-
return {
|
|
33578
|
-
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
33579
|
-
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
33580
|
-
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
33581
|
-
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
33582
|
-
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
33583
|
-
};
|
|
33584
|
-
}
|
|
33585
|
-
function resolveHourRange(segments, bounds) {
|
|
33586
|
-
if (segments.length === 0) return {
|
|
33587
|
-
startHour: bounds.defaultStartHour,
|
|
33588
|
-
endHour: bounds.defaultEndHour
|
|
33589
|
-
};
|
|
33590
|
-
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
33591
|
-
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
33592
|
-
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
33593
|
-
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
33594
|
-
return {
|
|
33595
|
-
startHour,
|
|
33596
|
-
endHour: Math.max(startHour + 1, endHour)
|
|
33597
|
-
};
|
|
33598
|
-
}
|
|
33599
|
-
function buildHourTicks(startHour, endHour) {
|
|
33600
|
-
const ticks = [];
|
|
33601
|
-
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
33602
|
-
return ticks;
|
|
33603
|
-
}
|
|
33604
|
-
function groupSegmentsByDate(segments) {
|
|
33605
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
33606
|
-
for (const segment of segments) {
|
|
33607
|
-
const existing = grouped.get(segment.dateKey) ?? [];
|
|
33608
|
-
existing.push(segment);
|
|
33609
|
-
grouped.set(segment.dateKey, existing);
|
|
33610
|
-
}
|
|
33611
|
-
return grouped;
|
|
33612
|
-
}
|
|
33613
|
-
function toRangePercent(minutes, rangeMinutes) {
|
|
33614
|
-
if (rangeMinutes <= 0) return 0;
|
|
33615
|
-
return minutes / rangeMinutes * 100;
|
|
33616
|
-
}
|
|
33617
|
-
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
33618
|
-
if (view === "day") return [viewDate];
|
|
33619
|
-
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
33620
|
-
}
|
|
33621
|
-
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
33622
|
-
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
33623
|
-
const start = parseDateKey(startDateKey);
|
|
33624
|
-
parseDateKey(endDateKey);
|
|
33625
|
-
const keys = [];
|
|
33626
|
-
let cursor = start;
|
|
33627
|
-
while (formatDateKey(cursor) <= endDateKey) {
|
|
33628
|
-
keys.push(formatDateKey(cursor));
|
|
33629
|
-
cursor = addDays(cursor, 1);
|
|
33630
|
-
}
|
|
33631
|
-
return keys;
|
|
33632
|
-
}
|
|
33633
|
-
function parseDateKey(dateKey) {
|
|
33634
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
33635
|
-
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
33636
|
-
const [, year, month, day] = match;
|
|
33637
|
-
return {
|
|
33638
|
-
year: Number(year),
|
|
33639
|
-
month: Number(month),
|
|
33640
|
-
day: Number(day)
|
|
33641
|
-
};
|
|
33642
|
-
}
|
|
33643
|
-
//#endregion
|
|
33644
|
-
//#region ../calendar/src/group.ts
|
|
33645
|
-
/**
|
|
33646
|
-
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
33647
|
-
*
|
|
33648
|
-
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
33649
|
-
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
33650
|
-
*
|
|
33651
|
-
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
33652
|
-
* relative order of items within each date matches input order.
|
|
33653
|
-
*/
|
|
33654
|
-
function groupItemsByDate(items, accessors) {
|
|
33655
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
33656
|
-
for (const item of items) {
|
|
33657
|
-
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
33658
|
-
if (dateKey == null) continue;
|
|
33659
|
-
const existing = grouped.get(dateKey);
|
|
33660
|
-
if (existing !== void 0) existing.push(item);
|
|
33661
|
-
else grouped.set(dateKey, [item]);
|
|
33662
|
-
}
|
|
33663
|
-
return grouped;
|
|
33664
|
-
}
|
|
33665
|
-
//#endregion
|
|
33666
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/eventCapacity.ts
|
|
33667
|
-
/**
|
|
33668
|
-
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
33669
|
-
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
33670
|
-
* for uncapped events so call sites can render no capacity indicator at all.
|
|
33671
|
-
*
|
|
33672
|
-
* The single source of truth for capacity math across event cards, agenda
|
|
33673
|
-
* rows, and timetable cells — keep wording variations in the call sites.
|
|
33674
|
-
*/
|
|
33675
|
-
function getEventAvailability(event) {
|
|
33676
|
-
if (event.capacity == null) return {
|
|
33677
|
-
available: null,
|
|
33678
|
-
isFull: false
|
|
33679
|
-
};
|
|
33680
|
-
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
33681
|
-
if (available <= 0) return {
|
|
33682
|
-
available: 0,
|
|
33683
|
-
isFull: true
|
|
33684
|
-
};
|
|
33685
|
-
return {
|
|
33686
|
-
available,
|
|
33687
|
-
isFull: false
|
|
33688
|
-
};
|
|
33689
|
-
}
|
|
33690
|
-
/**
|
|
33691
|
-
* Derive the three-state availability status (available / filling / waitlist)
|
|
33692
|
-
* plus a short pill label. Builds on {@link getEventAvailability} so the
|
|
33693
|
-
* capacity math (availableSpots with a `capacity - registeredCount` fallback)
|
|
33694
|
-
* stays consistent with the binary helper used elsewhere.
|
|
33695
|
-
*/
|
|
33696
|
-
function getEventAvailabilityStatus(event, options) {
|
|
33697
|
-
const threshold = options?.fillingThreshold ?? 2;
|
|
33698
|
-
const { available, isFull } = getEventAvailability(event);
|
|
33699
|
-
if (available == null) return {
|
|
33700
|
-
status: "available",
|
|
33701
|
-
available: null,
|
|
33702
|
-
isFull: false,
|
|
33703
|
-
label: null
|
|
33704
|
-
};
|
|
33705
|
-
if (isFull) return {
|
|
33706
|
-
status: "waitlist",
|
|
33707
|
-
available: 0,
|
|
33708
|
-
isFull: true,
|
|
33709
|
-
label: event.waitlistEnabled ? "Waitlist" : "Full"
|
|
33710
|
-
};
|
|
33711
|
-
if (available <= threshold) return {
|
|
33712
|
-
status: "filling",
|
|
33713
|
-
available,
|
|
33714
|
-
isFull: false,
|
|
33715
|
-
label: `${available} left`
|
|
33716
|
-
};
|
|
33717
|
-
return {
|
|
33718
|
-
status: "available",
|
|
33719
|
-
available,
|
|
33720
|
-
isFull: false,
|
|
33721
|
-
label: `${available} places`
|
|
33722
|
-
};
|
|
33723
|
-
}
|
|
33724
34320
|
//#endregion
|
|
33725
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/
|
|
34321
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/listingLayout.ts
|
|
33726
34322
|
/**
|
|
33727
|
-
*
|
|
34323
|
+
* Layout classes for event listing containers and cards. Presentation
|
|
34324
|
+
* plumbing rather than event domain logic, so it lives beside the components.
|
|
33728
34325
|
*/
|
|
33729
34326
|
/**
|
|
33730
34327
|
* Column class mappings for grid layouts, derived from the canonical
|
|
@@ -33759,180 +34356,6 @@ function getCardOrientation(layout) {
|
|
|
33759
34356
|
case "compact": return "vertical";
|
|
33760
34357
|
}
|
|
33761
34358
|
}
|
|
33762
|
-
/**
|
|
33763
|
-
* Format date for display.
|
|
33764
|
-
* Returns: "Monday, January 15, 2025"
|
|
33765
|
-
*/
|
|
33766
|
-
function formatDate(isoTimestamp, options) {
|
|
33767
|
-
return formatBookingDate(toScheduledBookingDateTime({
|
|
33768
|
-
startsAt: isoTimestamp,
|
|
33769
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33770
|
-
locale: options?.locale ?? "en-US"
|
|
33771
|
-
}), {
|
|
33772
|
-
weekday: "long",
|
|
33773
|
-
year: "numeric",
|
|
33774
|
-
month: "long",
|
|
33775
|
-
day: "numeric"
|
|
33776
|
-
});
|
|
33777
|
-
}
|
|
33778
|
-
/**
|
|
33779
|
-
* Format time for display.
|
|
33780
|
-
* Returns: "10:00 AM"
|
|
33781
|
-
*/
|
|
33782
|
-
function formatTime(isoTimestamp, options) {
|
|
33783
|
-
return formatBookingTime(toScheduledBookingDateTime({
|
|
33784
|
-
startsAt: isoTimestamp,
|
|
33785
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33786
|
-
locale: options?.locale ?? "en-US"
|
|
33787
|
-
}), {
|
|
33788
|
-
hour: "numeric",
|
|
33789
|
-
minute: "2-digit",
|
|
33790
|
-
hour12: true
|
|
33791
|
-
});
|
|
33792
|
-
}
|
|
33793
|
-
function formatEventDateParts(args) {
|
|
33794
|
-
const scheduledRange = toScheduledBookingDateRange({
|
|
33795
|
-
startsAt: args.startsAt,
|
|
33796
|
-
endsAt: args.endsAt,
|
|
33797
|
-
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
33798
|
-
locale: args.locale ?? "en-US"
|
|
33799
|
-
});
|
|
33800
|
-
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
33801
|
-
weekday: "long",
|
|
33802
|
-
year: "numeric",
|
|
33803
|
-
month: "long",
|
|
33804
|
-
day: "numeric"
|
|
33805
|
-
} });
|
|
33806
|
-
return {
|
|
33807
|
-
date,
|
|
33808
|
-
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
33809
|
-
startsAt: args.endsAt,
|
|
33810
|
-
timeZone: scheduledRange.timeZone,
|
|
33811
|
-
locale: args.locale ?? "en-US"
|
|
33812
|
-
}))}`
|
|
33813
|
-
};
|
|
33814
|
-
}
|
|
33815
|
-
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
33816
|
-
const dateTime = toScheduledBookingDateTime({
|
|
33817
|
-
startsAt: isoTimestamp,
|
|
33818
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33819
|
-
locale: options?.locale ?? "en-US"
|
|
33820
|
-
});
|
|
33821
|
-
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33822
|
-
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33823
|
-
return {
|
|
33824
|
-
weekday,
|
|
33825
|
-
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
33826
|
-
month,
|
|
33827
|
-
label: formatBookingDate(dateTime, {
|
|
33828
|
-
weekday: "long",
|
|
33829
|
-
year: "numeric",
|
|
33830
|
-
month: "long",
|
|
33831
|
-
day: "numeric"
|
|
33832
|
-
})
|
|
33833
|
-
};
|
|
33834
|
-
}
|
|
33835
|
-
function formatTeamMemberNames(names) {
|
|
33836
|
-
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
33837
|
-
if (normalized.length === 0) return null;
|
|
33838
|
-
const [firstName, secondName] = normalized;
|
|
33839
|
-
if (!firstName) return null;
|
|
33840
|
-
if (normalized.length === 1) return firstName;
|
|
33841
|
-
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
33842
|
-
const finalName = normalized[normalized.length - 1] ?? "";
|
|
33843
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
33844
|
-
}
|
|
33845
|
-
function formatEventTeacherLine(event) {
|
|
33846
|
-
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
33847
|
-
return names ? `with ${names}` : null;
|
|
33848
|
-
}
|
|
33849
|
-
/**
|
|
33850
|
-
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
33851
|
-
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
33852
|
-
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
33853
|
-
*/
|
|
33854
|
-
function groupEventsByDate(events, calendarTimeZone) {
|
|
33855
|
-
const grouped = groupItemsByDate(events, {
|
|
33856
|
-
getStartsAt: (event) => event.startsAt,
|
|
33857
|
-
getTimeZone: () => calendarTimeZone
|
|
33858
|
-
});
|
|
33859
|
-
const map = /* @__PURE__ */ new Map();
|
|
33860
|
-
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
33861
|
-
return map;
|
|
33862
|
-
}
|
|
33863
|
-
/**
|
|
33864
|
-
* Format event price for display.
|
|
33865
|
-
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
33866
|
-
*
|
|
33867
|
-
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
33868
|
-
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
33869
|
-
* surfaces stay consistent.
|
|
33870
|
-
*/
|
|
33871
|
-
function formatEventPrice(event) {
|
|
33872
|
-
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
33873
|
-
}
|
|
33874
|
-
function closedEventBookingLabel(reason) {
|
|
33875
|
-
switch (reason) {
|
|
33876
|
-
case "occurrence_cancelled":
|
|
33877
|
-
case "series_cancelled": return "Cancelled";
|
|
33878
|
-
case "occurrence_completed":
|
|
33879
|
-
case "series_completed": return "Completed";
|
|
33880
|
-
case "series_draft":
|
|
33881
|
-
case "series_archived":
|
|
33882
|
-
case "parent_archived":
|
|
33883
|
-
case "parent_inconsistent":
|
|
33884
|
-
case "not_public":
|
|
33885
|
-
case "already_started":
|
|
33886
|
-
case "enrollment_closed": return "Registration closed";
|
|
33887
|
-
default: return assertNever$3(reason);
|
|
33888
|
-
}
|
|
33889
|
-
}
|
|
33890
|
-
function resolveEventHref(event) {
|
|
33891
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
33892
|
-
return destination.kind === "external" ? destination.url : destination.path;
|
|
33893
|
-
}
|
|
33894
|
-
function resolveEventLinkProps(event) {
|
|
33895
|
-
const href = resolveEventHref(event);
|
|
33896
|
-
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
33897
|
-
href,
|
|
33898
|
-
target: "_blank",
|
|
33899
|
-
rel: "noopener noreferrer"
|
|
33900
|
-
} : { href };
|
|
33901
|
-
}
|
|
33902
|
-
/**
|
|
33903
|
-
* Resolve the CTA state for an event.
|
|
33904
|
-
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
33905
|
-
* and course-specific defaults consistent.
|
|
33906
|
-
*/
|
|
33907
|
-
function resolveEventCta(event, buttonText) {
|
|
33908
|
-
const { isFull: isSoldOut } = getEventAvailability(event);
|
|
33909
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
33910
|
-
const isExternal = destination.kind === "external";
|
|
33911
|
-
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
33912
|
-
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
33913
|
-
const href = resolveEventHref(event);
|
|
33914
|
-
if (event.bookingAvailability.kind === "closed") {
|
|
33915
|
-
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
33916
|
-
return {
|
|
33917
|
-
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
33918
|
-
href,
|
|
33919
|
-
external: isExternal,
|
|
33920
|
-
hidden: !linksToClosedWholeSeriesDetails
|
|
33921
|
-
};
|
|
33922
|
-
}
|
|
33923
|
-
if (!isExternal && isSoldOut) return {
|
|
33924
|
-
label: "Sold out",
|
|
33925
|
-
href,
|
|
33926
|
-
external: false,
|
|
33927
|
-
hidden: true
|
|
33928
|
-
};
|
|
33929
|
-
return {
|
|
33930
|
-
label: effectiveButtonText,
|
|
33931
|
-
href,
|
|
33932
|
-
external: isExternal,
|
|
33933
|
-
hidden: false
|
|
33934
|
-
};
|
|
33935
|
-
}
|
|
33936
34359
|
//#endregion
|
|
33937
34360
|
//#region ../blocks/src/system/media/slotContracts.ts
|
|
33938
34361
|
const DEFAULT_MIN_SOURCE_WIDTH = 1200;
|
|
@@ -35196,7 +35619,6 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
35196
35619
|
const formattedDate = formatDate(event.startsAt, { timeZone });
|
|
35197
35620
|
const formattedTime = `${formatTime(event.startsAt, { timeZone })} - ${formatTime(event.endsAt, { timeZone })}`;
|
|
35198
35621
|
const cta = resolveEventCta(event, buttonText);
|
|
35199
|
-
const { available: spotsLeft, isFull: isSoldOut } = getEventAvailability(event);
|
|
35200
35622
|
const cardClass = boxStyleClassNames({ role: cardRole }, { baseClass: "event-card" });
|
|
35201
35623
|
const buttonClass = themeButtonClassName({
|
|
35202
35624
|
variant: buttonVariant,
|
|
@@ -35212,10 +35634,7 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
35212
35634
|
} : null;
|
|
35213
35635
|
const hasImage = Boolean(showImage && image);
|
|
35214
35636
|
const teacherLine = formatEventTeacherLine(event);
|
|
35215
|
-
const capacityText = showCapacity ?
|
|
35216
|
-
isSoldOut,
|
|
35217
|
-
spotsLeft
|
|
35218
|
-
}) : null;
|
|
35637
|
+
const capacityText = showCapacity ? formatEventRemainingPlaces(event, { full: "Sold out" }) : null;
|
|
35219
35638
|
return /* @__PURE__ */ jsxs("article", {
|
|
35220
35639
|
className: `${cardClass} group rb-flex rb-flex-col rb-h-full ${eventCardOrientationClass(orientation)}`,
|
|
35221
35640
|
"data-orientation": orientation,
|
|
@@ -35281,7 +35700,11 @@ function EventCardContent(props) {
|
|
|
35281
35700
|
}),
|
|
35282
35701
|
/* @__PURE__ */ jsx("h3", {
|
|
35283
35702
|
className: "card-title rb-mb-3",
|
|
35284
|
-
children:
|
|
35703
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
35704
|
+
className: "event-title-link",
|
|
35705
|
+
...resolveEventLinkProps(event),
|
|
35706
|
+
children: props.title
|
|
35707
|
+
})
|
|
35285
35708
|
}),
|
|
35286
35709
|
/* @__PURE__ */ jsx("div", {
|
|
35287
35710
|
className: "rb-mb-2 rb-caption",
|
|
@@ -35358,13 +35781,6 @@ function eventCardOrientationClass(orientation) {
|
|
|
35358
35781
|
case "vertical": return "event-card--vertical";
|
|
35359
35782
|
}
|
|
35360
35783
|
}
|
|
35361
|
-
function formatCapacityText(event, options) {
|
|
35362
|
-
if (event.capacity == null) return null;
|
|
35363
|
-
if (options.isSoldOut) return "Sold out";
|
|
35364
|
-
if (options.spotsLeft == null) return null;
|
|
35365
|
-
const count = options.spotsLeft;
|
|
35366
|
-
return `${count} ${count === 1 ? "spot" : "spots"} available`;
|
|
35367
|
-
}
|
|
35368
35784
|
function getCategoryBadgeStyle(background) {
|
|
35369
35785
|
const normalized = background?.trim() ?? "";
|
|
35370
35786
|
if (!normalized) return {
|
|
@@ -35433,7 +35849,11 @@ const EventCompactRow = ({ event, buttonVariant = "primary", buttonText }) => {
|
|
|
35433
35849
|
className: "event-compact-row-content",
|
|
35434
35850
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
35435
35851
|
className: "event-compact-row-title",
|
|
35436
|
-
children:
|
|
35852
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
35853
|
+
className: "event-title-link",
|
|
35854
|
+
...resolveEventLinkProps(event),
|
|
35855
|
+
children: event.title
|
|
35856
|
+
})
|
|
35437
35857
|
}), /* @__PURE__ */ jsxs("div", {
|
|
35438
35858
|
className: "event-compact-row-meta",
|
|
35439
35859
|
children: [
|
|
@@ -35499,6 +35919,23 @@ const EmptyState = ({ message = "No upcoming events scheduled.", className, hasA
|
|
|
35499
35919
|
});
|
|
35500
35920
|
};
|
|
35501
35921
|
//#endregion
|
|
35922
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/OfferingTitle.tsx
|
|
35923
|
+
/**
|
|
35924
|
+
* Heading for an offering card or row. Links to the offering's page when one
|
|
35925
|
+
* exists so a sold-out or closed offering stays reachable (issue 1372); the
|
|
35926
|
+
* `.event-title-link` rule lives in theme-core's event-list.css.
|
|
35927
|
+
*/
|
|
35928
|
+
function OfferingTitle({ className, path, title }) {
|
|
35929
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
35930
|
+
className,
|
|
35931
|
+
children: path ? /* @__PURE__ */ jsx("a", {
|
|
35932
|
+
className: "event-title-link",
|
|
35933
|
+
href: path,
|
|
35934
|
+
children: title
|
|
35935
|
+
}) : title
|
|
35936
|
+
});
|
|
35937
|
+
}
|
|
35938
|
+
//#endregion
|
|
35502
35939
|
//#region ../blocks/src/system/runtime/nodes/events/shared/listOptions.ts
|
|
35503
35940
|
const eventPageSizeValues = [
|
|
35504
35941
|
3,
|
|
@@ -36843,7 +37280,7 @@ const eventCalendarAdditionalFields = [
|
|
|
36843
37280
|
{
|
|
36844
37281
|
id: "showCapacity",
|
|
36845
37282
|
type: "boolean",
|
|
36846
|
-
label: "Show available
|
|
37283
|
+
label: "Show available places",
|
|
36847
37284
|
description: "Display remaining capacity on event cards and timetable cells",
|
|
36848
37285
|
required: false,
|
|
36849
37286
|
defaultValue: true,
|
|
@@ -46604,7 +47041,7 @@ function EventFacts({ selection, selected, venue, teamMembers, pricing, showCapa
|
|
|
46604
47041
|
children: [/* @__PURE__ */ jsx("div", {
|
|
46605
47042
|
className: "rb-label rb-mb-2",
|
|
46606
47043
|
children: "Places"
|
|
46607
|
-
}), /* @__PURE__ */
|
|
47044
|
+
}), /* @__PURE__ */ jsx("p", { children: formatTotalCapacity(selected.capacity) })]
|
|
46608
47045
|
}) : null,
|
|
46609
47046
|
pricing,
|
|
46610
47047
|
selected.bookingScope === "whole_series" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -47448,9 +47885,10 @@ function CourseCompactRow({ course, path }) {
|
|
|
47448
47885
|
}),
|
|
47449
47886
|
/* @__PURE__ */ jsxs("div", {
|
|
47450
47887
|
className: "event-compact-row-content",
|
|
47451
|
-
children: [/* @__PURE__ */ jsx(
|
|
47888
|
+
children: [/* @__PURE__ */ jsx(OfferingTitle, {
|
|
47452
47889
|
className: "event-compact-row-title",
|
|
47453
|
-
|
|
47890
|
+
path,
|
|
47891
|
+
title: course.title
|
|
47454
47892
|
}), /* @__PURE__ */ jsxs("div", {
|
|
47455
47893
|
className: "event-compact-row-meta",
|
|
47456
47894
|
children: [
|
|
@@ -47507,9 +47945,10 @@ function CourseFeatureCard({ course, path, buttonVariant, buttonText }) {
|
|
|
47507
47945
|
children: /* @__PURE__ */ jsxs("div", {
|
|
47508
47946
|
className: "card-content rb-flex rb-flex-1 rb-flex-col",
|
|
47509
47947
|
children: [
|
|
47510
|
-
/* @__PURE__ */ jsx(
|
|
47948
|
+
/* @__PURE__ */ jsx(OfferingTitle, {
|
|
47511
47949
|
className: "card-title rb-mb-3",
|
|
47512
|
-
|
|
47950
|
+
path,
|
|
47951
|
+
title: course.title
|
|
47513
47952
|
}),
|
|
47514
47953
|
/* @__PURE__ */ jsxs("div", {
|
|
47515
47954
|
className: "rb-mb-2 rb-caption",
|
|
@@ -48365,8 +48804,8 @@ const AgendaEventCard = ({ event, showCapacity, onClick }) => {
|
|
|
48365
48804
|
const eventLinkProps = resolveEventLinkProps(event);
|
|
48366
48805
|
const categoryColor = event.eventCategory?.color || "rgb(var(--tb-accent))";
|
|
48367
48806
|
const price = formatEventPrice(event);
|
|
48368
|
-
const
|
|
48369
|
-
const
|
|
48807
|
+
const capacity = showCapacity ? formatEventRemainingPlaces(event) : null;
|
|
48808
|
+
const isFull = isEventFull(event);
|
|
48370
48809
|
const title = event.title;
|
|
48371
48810
|
const timeZone = event.timeZone ?? null;
|
|
48372
48811
|
const teacherLine = formatEventTeacherLine(event);
|
|
@@ -48808,7 +49247,7 @@ function buildTimetableModel(input) {
|
|
|
48808
49247
|
getEndsAt: (event) => event.endsAt,
|
|
48809
49248
|
getTimeZone: (event) => event.timeZone ?? "UTC",
|
|
48810
49249
|
getColor: (event) => event.eventCategory?.color ?? null,
|
|
48811
|
-
getOrderKey: (event) => `${event.title}
|
|
49250
|
+
getOrderKey: (event) => `${event.title}\u0000${event.id}`
|
|
48812
49251
|
});
|
|
48813
49252
|
return {
|
|
48814
49253
|
hourTicks: [...generic.hourTicks],
|
|
@@ -48845,18 +49284,9 @@ function toEventCell(cell) {
|
|
|
48845
49284
|
};
|
|
48846
49285
|
}
|
|
48847
49286
|
function formatTimetableCapacity(event) {
|
|
48848
|
-
const { available, isFull } = getEventAvailability(event);
|
|
48849
|
-
if (available == null) return {
|
|
48850
|
-
label: null,
|
|
48851
|
-
isFull: false
|
|
48852
|
-
};
|
|
48853
|
-
if (isFull) return {
|
|
48854
|
-
label: "Full",
|
|
48855
|
-
isFull: true
|
|
48856
|
-
};
|
|
48857
49287
|
return {
|
|
48858
|
-
label:
|
|
48859
|
-
isFull:
|
|
49288
|
+
label: formatEventRemainingPlaces(event, { compact: true }),
|
|
49289
|
+
isFull: isEventFull(event)
|
|
48860
49290
|
};
|
|
48861
49291
|
}
|
|
48862
49292
|
function formatEventTimeRange(event) {
|
|
@@ -49332,7 +49762,7 @@ function EventRegistrationSSR({ blockId, subject, children, ...sourceProps }) {
|
|
|
49332
49762
|
className: `event-registration ${className ?? ""}`.trim(),
|
|
49333
49763
|
"data-block": "event-registration",
|
|
49334
49764
|
"data-rb-island-required": "true",
|
|
49335
|
-
"data-rb-island-error-message": "
|
|
49765
|
+
"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.",
|
|
49336
49766
|
"data-rb-island-error-class": "event-registration__hydration-error",
|
|
49337
49767
|
...getIslandRootProps("event-registration", blockId),
|
|
49338
49768
|
children: [
|
|
@@ -55157,6 +55587,32 @@ function isSiteStyleTransitionStyleGroup(value) {
|
|
|
55157
55587
|
function boundaryMapForAdjacentBlockIds(boundaries) {
|
|
55158
55588
|
return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
|
|
55159
55589
|
}
|
|
55590
|
+
/** Keep a surviving block's outgoing treatment when omitted blocks change its neighbor. */
|
|
55591
|
+
function boundariesForSurvivingBlocks(boundaries, blocks) {
|
|
55592
|
+
const outgoing = new Map(boundaries.map((boundary) => [boundary.previousBlockId, boundary]));
|
|
55593
|
+
const incoming = new Map(boundaries.map((boundary) => [boundary.nextBlockId, boundary]));
|
|
55594
|
+
return blocks.flatMap((block, index) => {
|
|
55595
|
+
const next = blocks[index + 1];
|
|
55596
|
+
if (!block.id || !next?.id) return [];
|
|
55597
|
+
const boundary = outgoing.get(block.id);
|
|
55598
|
+
if (!boundary) return [];
|
|
55599
|
+
if (boundary.nextBlockId === next.id) return [boundary];
|
|
55600
|
+
const destination = incoming.get(next.id);
|
|
55601
|
+
return [retargetCompiledBoundaryRuntimeForContentAdjacency({
|
|
55602
|
+
...boundary,
|
|
55603
|
+
boundaryId: `boundary:${block.id}:${next.id}`,
|
|
55604
|
+
nextBlockId: next.id,
|
|
55605
|
+
to: destination?.to ?? boundary.to,
|
|
55606
|
+
cssVars: {
|
|
55607
|
+
...boundary.cssVars,
|
|
55608
|
+
to: destination?.cssVars.to ?? boundary.cssVars.to
|
|
55609
|
+
}
|
|
55610
|
+
}, {
|
|
55611
|
+
currentContent: block.content,
|
|
55612
|
+
nextContent: next.content
|
|
55613
|
+
})];
|
|
55614
|
+
});
|
|
55615
|
+
}
|
|
55160
55616
|
function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
|
|
55161
55617
|
const previousBlockId = previousBlock?.id ?? null;
|
|
55162
55618
|
const nextBlockId = nextBlock?.id ?? null;
|
|
@@ -55248,41 +55704,173 @@ function toPresentationPage(page) {
|
|
|
55248
55704
|
};
|
|
55249
55705
|
}
|
|
55250
55706
|
//#endregion
|
|
55707
|
+
//#region ../blocks/src/pageRendererBlockSupport.tsx
|
|
55708
|
+
function fallback(message) {
|
|
55709
|
+
return /* @__PURE__ */ jsx("div", {
|
|
55710
|
+
className: "rb-p-6 rb-caption",
|
|
55711
|
+
style: textColorStyle("mutedText"),
|
|
55712
|
+
children: message
|
|
55713
|
+
});
|
|
55714
|
+
}
|
|
55715
|
+
/**
|
|
55716
|
+
* A page with nothing to show. Authoring surfaces get the diagnostic; a public
|
|
55717
|
+
* page renders nothing at all rather than telling a visitor about the outline.
|
|
55718
|
+
*/
|
|
55719
|
+
function emptyPageNotice(audience) {
|
|
55720
|
+
if (audience === "public") return null;
|
|
55721
|
+
return /* @__PURE__ */ jsx("div", {
|
|
55722
|
+
className: "rb-p-6 rb-caption",
|
|
55723
|
+
style: textColorStyle("mutedText"),
|
|
55724
|
+
children: "No blocks found."
|
|
55725
|
+
});
|
|
55726
|
+
}
|
|
55727
|
+
/**
|
|
55728
|
+
* A public page dropped a block, so nothing in the rendered HTML says the
|
|
55729
|
+
* content is missing. Say it in the logs instead.
|
|
55730
|
+
*/
|
|
55731
|
+
function warnOmittedFromPublicPage(input) {
|
|
55732
|
+
try {
|
|
55733
|
+
console.warn("[blocks:PageRenderer] omitted a non-renderable block from a public page", {
|
|
55734
|
+
blockId: input.block.id ?? null,
|
|
55735
|
+
blockKind: input.block.kind,
|
|
55736
|
+
outcome: input.outcome,
|
|
55737
|
+
...input.admissionError ? { error: input.admissionError } : {}
|
|
55738
|
+
});
|
|
55739
|
+
} catch {}
|
|
55740
|
+
}
|
|
55741
|
+
function shouldDebugCustomBlocks() {
|
|
55742
|
+
try {
|
|
55743
|
+
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
55744
|
+
} catch {
|
|
55745
|
+
return false;
|
|
55746
|
+
}
|
|
55747
|
+
}
|
|
55748
|
+
/**
|
|
55749
|
+
* Look up a block override component by block kind.
|
|
55750
|
+
* Supports both full ("block.hero") and short ("hero") keys.
|
|
55751
|
+
*/
|
|
55752
|
+
function getBlockOverride(blockKind, overrides) {
|
|
55753
|
+
if (!overrides) return void 0;
|
|
55754
|
+
if (overrides[blockKind]) return overrides[blockKind];
|
|
55755
|
+
const shortKind = blockKind.replace(/^block\./, "");
|
|
55756
|
+
if (overrides[shortKind]) return overrides[shortKind];
|
|
55757
|
+
}
|
|
55758
|
+
/**
|
|
55759
|
+
* Resolves block content based on stage priority.
|
|
55760
|
+
* For draft/preview: draftContent takes priority over content.
|
|
55761
|
+
* For published: content is the source of truth.
|
|
55762
|
+
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
55763
|
+
*/
|
|
55764
|
+
function getRenderableContent(block, previewStage = "published") {
|
|
55765
|
+
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
55766
|
+
if (!isObjectRecord$1(source)) return {};
|
|
55767
|
+
return source;
|
|
55768
|
+
}
|
|
55769
|
+
function resolvePageRendererImageResolveContext(dataContext) {
|
|
55770
|
+
const input = dataContext?.imageResolveContext;
|
|
55771
|
+
return buildResolveImageUrlContext({
|
|
55772
|
+
siteId: input?.siteId ?? dataContext?.siteId,
|
|
55773
|
+
supabaseUrl: input?.supabaseUrl ?? dataContext?.supabaseUrl,
|
|
55774
|
+
mediaAssetIndex: input?.mediaAssetIndex,
|
|
55775
|
+
mediaAssets: input?.mediaAssets ?? dataContext?.mediaAssets
|
|
55776
|
+
});
|
|
55777
|
+
}
|
|
55778
|
+
/**
|
|
55779
|
+
* The RSC-safe props every override component receives, whether the block
|
|
55780
|
+
* resolved a manifest or is being rendered from an override alone.
|
|
55781
|
+
*/
|
|
55782
|
+
function buildBlockOverrideProps(input) {
|
|
55783
|
+
return {
|
|
55784
|
+
content: input.content,
|
|
55785
|
+
...overrideThemeProps(input.themeResources),
|
|
55786
|
+
...blockOverrideContextProps(input.dataContext),
|
|
55787
|
+
data: input.data,
|
|
55788
|
+
imageResolveContext: input.imageResolveContext,
|
|
55789
|
+
blockId: input.block.id ?? null,
|
|
55790
|
+
blockKind: input.block.kind
|
|
55791
|
+
};
|
|
55792
|
+
}
|
|
55793
|
+
/** The render-context half of the override props, absent context included. */
|
|
55794
|
+
function blockOverrideContextProps(dataContext) {
|
|
55795
|
+
const context = dataContext ?? {};
|
|
55796
|
+
return {
|
|
55797
|
+
siteId: context.siteId,
|
|
55798
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
55799
|
+
bookingTimeZone: context.bookingTimeZone,
|
|
55800
|
+
portalToken: context.portalToken ?? null,
|
|
55801
|
+
previewToken: context.previewToken ?? null,
|
|
55802
|
+
stage: context.previewStage,
|
|
55803
|
+
occurrenceContext: context.occurrenceContext ?? null,
|
|
55804
|
+
entrySubrouteContext: context.entrySubrouteContext ?? null,
|
|
55805
|
+
contentEntry: context.contentEntry ?? null,
|
|
55806
|
+
pageSurface: context.pageSurface ?? null,
|
|
55807
|
+
subject: context.subject ?? null,
|
|
55808
|
+
supabaseUrl: context.supabaseUrl
|
|
55809
|
+
};
|
|
55810
|
+
}
|
|
55811
|
+
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
55812
|
+
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
55813
|
+
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
55814
|
+
function getPerfSlowBlockRenderThresholdMs() {
|
|
55815
|
+
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
55816
|
+
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
55817
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
55818
|
+
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
55819
|
+
return cachedPerfSlowBlockRenderThresholdMs;
|
|
55820
|
+
}
|
|
55821
|
+
function getCachedBlockComponent(manifest) {
|
|
55822
|
+
const cached = blockComponentCache.get(manifest);
|
|
55823
|
+
if (cached) return cached;
|
|
55824
|
+
const created = makeDefaultBlockComponent({ manifest });
|
|
55825
|
+
blockComponentCache.set(manifest, created);
|
|
55826
|
+
return created;
|
|
55827
|
+
}
|
|
55828
|
+
//#endregion
|
|
55251
55829
|
//#region ../blocks/src/PageRenderer.tsx
|
|
55252
55830
|
function PageRenderer$1(props) {
|
|
55253
55831
|
const page = React$1.useMemo(() => toPresentationPage(props.page), [props.page]);
|
|
55254
55832
|
return /* @__PURE__ */ jsx(PageRendererCore, {
|
|
55255
55833
|
...props,
|
|
55256
|
-
page
|
|
55834
|
+
page,
|
|
55835
|
+
audience: "public"
|
|
55257
55836
|
});
|
|
55258
55837
|
}
|
|
55259
55838
|
function PageRendererCore(props) {
|
|
55260
|
-
const { page, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
55839
|
+
const { page, audience, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
55261
55840
|
const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
|
|
55262
55841
|
dataContext?.imageResolveContext,
|
|
55263
55842
|
dataContext?.mediaAssets,
|
|
55264
55843
|
dataContext?.siteId,
|
|
55265
55844
|
dataContext?.supabaseUrl
|
|
55266
55845
|
]);
|
|
55267
|
-
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(runtimeBoundaries ?? []), [runtimeBoundaries]);
|
|
55268
55846
|
const usesRuntimeBoundaries = runtimeBoundaries !== void 0;
|
|
55269
55847
|
const themeResources = React$1.useMemo(() => resolvePageThemeResources(props), [
|
|
55270
55848
|
props.rendering,
|
|
55271
55849
|
props.theme,
|
|
55272
55850
|
props.themeTokens
|
|
55273
55851
|
]);
|
|
55274
|
-
if (!page || page.blocks.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
55275
|
-
className: "rb-p-6 rb-caption",
|
|
55276
|
-
style: textColorStyle("mutedText"),
|
|
55277
|
-
children: "No blocks found."
|
|
55278
|
-
});
|
|
55279
55852
|
const resolvedRegistry = registry ?? getDefaultComponentRegistry();
|
|
55280
55853
|
setContextResolveImageUrlContext(imageResolveContext);
|
|
55281
|
-
|
|
55282
|
-
|
|
55283
|
-
|
|
55284
|
-
|
|
55285
|
-
|
|
55854
|
+
const sections = usePageSections(props, {
|
|
55855
|
+
page,
|
|
55856
|
+
themeResources,
|
|
55857
|
+
imageResolveContext,
|
|
55858
|
+
registry: resolvedRegistry
|
|
55859
|
+
});
|
|
55860
|
+
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(boundariesForSurvivingBlocks(runtimeBoundaries ?? [], sections.map(({ block }) => ({
|
|
55861
|
+
...block,
|
|
55862
|
+
content: getRenderableContent(block, dataContext?.previewStage)
|
|
55863
|
+
})))), [
|
|
55864
|
+
runtimeBoundaries,
|
|
55865
|
+
sections,
|
|
55866
|
+
dataContext?.previewStage
|
|
55867
|
+
]);
|
|
55868
|
+
if (sections.length === 0) return emptyPageNotice(audience);
|
|
55869
|
+
return /* @__PURE__ */ jsx("div", { children: sections.map((section, index) => {
|
|
55870
|
+
const { block, blockKey, node } = section;
|
|
55871
|
+
const blockPosition = pageBlockPosition(index, sections.length);
|
|
55872
|
+
const previousBlock = sections[index - 1]?.block ?? null;
|
|
55873
|
+
const nextBlock = sections[index + 1]?.block ?? null;
|
|
55286
55874
|
const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
|
|
55287
55875
|
const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
|
|
55288
55876
|
const boundaryElement = renderBoundaryAfterBlock({
|
|
@@ -55297,19 +55885,7 @@ function PageRendererCore(props) {
|
|
|
55297
55885
|
const managesOwnWrapper = definition?.manifest.behaviours?.managesOwnWrapper;
|
|
55298
55886
|
const renderableContent = getRenderableContent(block, dataContext?.previewStage);
|
|
55299
55887
|
const variantKey = definition?.manifest ? resolveVariantKey(definition.manifest, renderableContent) : null;
|
|
55300
|
-
|
|
55301
|
-
const blockSectionProps = buildBlockSectionProps({
|
|
55302
|
-
props,
|
|
55303
|
-
themeResources,
|
|
55304
|
-
block,
|
|
55305
|
-
blockKey,
|
|
55306
|
-
imageResolveContext,
|
|
55307
|
-
registry: resolvedRegistry
|
|
55308
|
-
});
|
|
55309
|
-
if (!shouldUseWrapper) {
|
|
55310
|
-
const rendered = /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
55311
|
-
return boundaryElement ? /* @__PURE__ */ jsxs(React$1.Fragment, { children: [rendered, boundaryElement] }, blockKey) : rendered;
|
|
55312
|
-
}
|
|
55888
|
+
if (!(!managesOwnWrapper || Boolean(wrapBlock))) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [node, boundaryElement] }, blockKey);
|
|
55313
55889
|
const wrapperClassName = [`block-${cleanKind}`, wrapBlock ? "group relative" : null].filter(Boolean).join(" ");
|
|
55314
55890
|
const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
|
|
55315
55891
|
const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
|
|
@@ -55334,7 +55910,7 @@ function PageRendererCore(props) {
|
|
|
55334
55910
|
"data-site-style-boundary-after": boundaryAfter?.metrics.size,
|
|
55335
55911
|
"data-style-group": styleGroup,
|
|
55336
55912
|
style: blockStyle,
|
|
55337
|
-
children:
|
|
55913
|
+
children: node
|
|
55338
55914
|
}, blockKey);
|
|
55339
55915
|
if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
|
|
55340
55916
|
if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
|
|
@@ -55347,18 +55923,69 @@ function PageRendererCore(props) {
|
|
|
55347
55923
|
})] }, blockKey);
|
|
55348
55924
|
}) });
|
|
55349
55925
|
}
|
|
55350
|
-
|
|
55351
|
-
|
|
55352
|
-
|
|
55353
|
-
|
|
55354
|
-
|
|
55355
|
-
|
|
55356
|
-
|
|
55926
|
+
/**
|
|
55927
|
+
* Memoised so that a re-render with unchanged inputs reuses the same section
|
|
55928
|
+
* elements and React can skip the block subtrees, exactly as the per-block memo
|
|
55929
|
+
* did before public pages started resolving their bodies up front.
|
|
55930
|
+
*/
|
|
55931
|
+
function usePageSections(props, input) {
|
|
55932
|
+
const { page, themeResources, imageResolveContext, registry } = input;
|
|
55933
|
+
return React$1.useMemo(() => admitPageSections({
|
|
55934
|
+
props,
|
|
55935
|
+
blocks: page?.blocks ?? [],
|
|
55936
|
+
audience: props.audience,
|
|
55937
|
+
themeResources,
|
|
55938
|
+
imageResolveContext,
|
|
55939
|
+
registry
|
|
55940
|
+
}), [
|
|
55941
|
+
page,
|
|
55942
|
+
props.audience,
|
|
55943
|
+
themeResources,
|
|
55944
|
+
imageResolveContext,
|
|
55945
|
+
registry,
|
|
55946
|
+
props.dataContext,
|
|
55947
|
+
props.routeMap,
|
|
55948
|
+
props.wrapBlock,
|
|
55949
|
+
props.blockOverrides,
|
|
55950
|
+
props.sdkConfig,
|
|
55951
|
+
props.customBlocks,
|
|
55952
|
+
props.previewAuthoring
|
|
55953
|
+
]);
|
|
55954
|
+
}
|
|
55955
|
+
/**
|
|
55956
|
+
* Resolves each block's rendered body before the page decides what to wrap.
|
|
55957
|
+
*
|
|
55958
|
+
* On a public page the body is resolved eagerly so a block that cannot render
|
|
55959
|
+
* is dropped along with its wrapper, its data attributes, its boundary reserve
|
|
55960
|
+
* space and its style-group sibling — the section body is the single source of
|
|
55961
|
+
* truth for whether the block is on the page (issue 1378). Authoring surfaces
|
|
55962
|
+
* keep the memoised component so the editor's render-loop guards still hold.
|
|
55963
|
+
*/
|
|
55964
|
+
function admitPageSections(input) {
|
|
55965
|
+
const sections = [];
|
|
55966
|
+
input.blocks.forEach((block, index) => {
|
|
55967
|
+
const blockKey = block.id ?? `${block.kind}-${index}`;
|
|
55968
|
+
const blockSectionProps = buildBlockSectionProps({
|
|
55969
|
+
props: input.props,
|
|
55970
|
+
themeResources: input.themeResources,
|
|
55971
|
+
block,
|
|
55972
|
+
blockKey,
|
|
55973
|
+
imageResolveContext: input.imageResolveContext,
|
|
55974
|
+
registry: input.registry
|
|
55975
|
+
});
|
|
55976
|
+
const node = input.audience === "public" ? renderBlockSection(blockSectionProps) : /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
55977
|
+
if (node === null || node === void 0) return;
|
|
55978
|
+
sections.push({
|
|
55979
|
+
block,
|
|
55980
|
+
blockKey,
|
|
55981
|
+
node
|
|
55982
|
+
});
|
|
55357
55983
|
});
|
|
55984
|
+
return sections;
|
|
55358
55985
|
}
|
|
55359
|
-
const resolveBookingTimeZone = (dataContext) => dataContext?.bookingTimeZone;
|
|
55360
55986
|
function buildBlockSectionProps(input) {
|
|
55361
55987
|
return {
|
|
55988
|
+
audience: input.props.audience,
|
|
55362
55989
|
themeResources: input.themeResources,
|
|
55363
55990
|
block: input.block,
|
|
55364
55991
|
dataContext: input.props.dataContext,
|
|
@@ -55374,40 +56001,24 @@ function buildBlockSectionProps(input) {
|
|
|
55374
56001
|
};
|
|
55375
56002
|
}
|
|
55376
56003
|
/**
|
|
55377
|
-
* Look up a block override component by block kind.
|
|
55378
|
-
* Supports both full ("block.hero") and short ("hero") keys.
|
|
55379
|
-
*/
|
|
55380
|
-
function getBlockOverride(blockKind, overrides) {
|
|
55381
|
-
if (!overrides) return void 0;
|
|
55382
|
-
if (overrides[blockKind]) return overrides[blockKind];
|
|
55383
|
-
const shortKind = blockKind.replace(/^block\./, "");
|
|
55384
|
-
if (overrides[shortKind]) return overrides[shortKind];
|
|
55385
|
-
}
|
|
55386
|
-
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
55387
|
-
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
55388
|
-
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
55389
|
-
function getPerfSlowBlockRenderThresholdMs() {
|
|
55390
|
-
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
55391
|
-
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
55392
|
-
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
55393
|
-
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
55394
|
-
return cachedPerfSlowBlockRenderThresholdMs;
|
|
55395
|
-
}
|
|
55396
|
-
function getCachedBlockComponent(manifest) {
|
|
55397
|
-
const cached = blockComponentCache.get(manifest);
|
|
55398
|
-
if (cached) return cached;
|
|
55399
|
-
const created = makeDefaultBlockComponent({ manifest });
|
|
55400
|
-
blockComponentCache.set(manifest, created);
|
|
55401
|
-
return created;
|
|
55402
|
-
}
|
|
55403
|
-
/**
|
|
55404
56004
|
* Memoized block section component.
|
|
55405
56005
|
*
|
|
55406
56006
|
* Custom memoization prevents render loops during editing by explicitly checking
|
|
55407
56007
|
* referential equality of all props that affect rendering. Standard React.memo
|
|
55408
56008
|
* wouldn't catch all the render triggers from the editor store.
|
|
55409
56009
|
*/
|
|
55410
|
-
const MemoBlockSection = React$1.memo(function MemoBlockSection(
|
|
56010
|
+
const MemoBlockSection = React$1.memo(function MemoBlockSection(props) {
|
|
56011
|
+
return renderBlockSection(props);
|
|
56012
|
+
}, (prev, next) => {
|
|
56013
|
+
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;
|
|
56014
|
+
});
|
|
56015
|
+
/**
|
|
56016
|
+
* Renders a single block body.
|
|
56017
|
+
*
|
|
56018
|
+
* Returns `null` when the block cannot render and the audience is `public`;
|
|
56019
|
+
* the caller treats that as "this block is not on the page".
|
|
56020
|
+
*/
|
|
56021
|
+
function renderBlockSection({ audience, themeResources, block, dataContext, registry, routeMap, wrapBlock, blockOverrides, sdkConfig, customBlocks, imageResolveContext, previewAuthoring }) {
|
|
55411
56022
|
const perfStart = globalThis.process?.env?.DEBUG_PERF === "1" && typeof window === "undefined" ? Date.now() : 0;
|
|
55412
56023
|
const fallbackImageResolveContext = getCurrentResolveImageUrlContext();
|
|
55413
56024
|
const resolvedImageResolveContext = imageResolveContext ?? fallbackImageResolveContext;
|
|
@@ -55427,19 +56038,30 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55427
56038
|
} catch {}
|
|
55428
56039
|
return node;
|
|
55429
56040
|
}
|
|
55430
|
-
|
|
55431
|
-
|
|
55432
|
-
|
|
55433
|
-
|
|
55434
|
-
|
|
55435
|
-
|
|
55436
|
-
|
|
55437
|
-
|
|
55438
|
-
|
|
55439
|
-
|
|
55440
|
-
override: false
|
|
56041
|
+
/**
|
|
56042
|
+
* The one place that decides what a block that cannot render leaves behind.
|
|
56043
|
+
* A public page gets nothing (and a log line, since dropping content on a
|
|
56044
|
+
* 200 is otherwise invisible); authoring surfaces get the diagnostic.
|
|
56045
|
+
*/
|
|
56046
|
+
function diagnostic(message, detail) {
|
|
56047
|
+
if (audience !== "public") return fallback(message);
|
|
56048
|
+
warnOmittedFromPublicPage({
|
|
56049
|
+
block,
|
|
56050
|
+
...detail
|
|
55441
56051
|
});
|
|
56052
|
+
return null;
|
|
55442
56053
|
}
|
|
56054
|
+
function withBlockWrapper(node) {
|
|
56055
|
+
if (node === null) return null;
|
|
56056
|
+
return wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, node) }) : /* @__PURE__ */ jsx(Fragment, { children: node });
|
|
56057
|
+
}
|
|
56058
|
+
if (block.presentationAdmission.kind === "invalid") return finish(diagnostic(`Invalid content for block kind "${block.kind}".`, {
|
|
56059
|
+
outcome: "invalidContent",
|
|
56060
|
+
admissionError: block.presentationAdmission.error
|
|
56061
|
+
}), {
|
|
56062
|
+
outcome: "invalidContent",
|
|
56063
|
+
override: false
|
|
56064
|
+
});
|
|
55443
56065
|
const OverrideComponent = getBlockOverride(block.kind, blockOverrides);
|
|
55444
56066
|
requireBlockOverride(themeResources, OverrideComponent, block.kind);
|
|
55445
56067
|
let definition = getBlockDefinition(block.kind);
|
|
@@ -55452,25 +56074,14 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55452
56074
|
}).findManifest(block.kind)?.descriptor.definition;
|
|
55453
56075
|
if (!definition) {
|
|
55454
56076
|
if (OverrideComponent) {
|
|
55455
|
-
const rendered = /* @__PURE__ */ jsx(OverrideComponent, {
|
|
56077
|
+
const rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...buildBlockOverrideProps({
|
|
56078
|
+
block,
|
|
55456
56079
|
content: getRenderableContent(block, dataContext?.previewStage),
|
|
55457
|
-
...overrideThemeProps(themeResources),
|
|
55458
56080
|
data: void 0,
|
|
55459
|
-
|
|
55460
|
-
|
|
55461
|
-
|
|
55462
|
-
|
|
55463
|
-
stage: dataContext?.previewStage,
|
|
55464
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
55465
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
55466
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
55467
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
55468
|
-
subject: dataContext?.subject ?? null,
|
|
55469
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
55470
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
55471
|
-
blockId: block.id ?? null,
|
|
55472
|
-
blockKind: block.kind
|
|
55473
|
-
});
|
|
56081
|
+
themeResources,
|
|
56082
|
+
dataContext,
|
|
56083
|
+
imageResolveContext: resolvedImageResolveContext
|
|
56084
|
+
}) });
|
|
55474
56085
|
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
55475
56086
|
outcome: "overrideWithoutDefinition",
|
|
55476
56087
|
override: true,
|
|
@@ -55486,8 +56097,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55486
56097
|
dbCustomBlocksCount: customBlocks?.length ?? 0
|
|
55487
56098
|
});
|
|
55488
56099
|
} catch {}
|
|
55489
|
-
|
|
55490
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56100
|
+
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" })), {
|
|
55491
56101
|
outcome: "missingDefinition",
|
|
55492
56102
|
override: false
|
|
55493
56103
|
});
|
|
@@ -55502,8 +56112,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55502
56112
|
manifestKeys: Object.keys(definition.manifest ?? {}).sort()
|
|
55503
56113
|
});
|
|
55504
56114
|
} catch {}
|
|
55505
|
-
|
|
55506
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56115
|
+
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" })), {
|
|
55507
56116
|
outcome: "missingLayout",
|
|
55508
56117
|
override: false
|
|
55509
56118
|
});
|
|
@@ -55513,39 +56122,24 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55513
56122
|
if (block.presentationAdmission.kind === "editor") {
|
|
55514
56123
|
const draft = validateManifestDraft(compileManifestContentContract(definition.manifest), content);
|
|
55515
56124
|
if (draft.kind === "previewable") data = draft.data;
|
|
55516
|
-
else {
|
|
55517
|
-
|
|
55518
|
-
|
|
55519
|
-
|
|
55520
|
-
override: false
|
|
55521
|
-
});
|
|
55522
|
-
}
|
|
56125
|
+
else return finish(withBlockWrapper(diagnostic(`Unable to render block kind "${block.kind}".`, { outcome: "invalidPlaceholder" })), {
|
|
56126
|
+
outcome: "invalidPlaceholder",
|
|
56127
|
+
override: false
|
|
56128
|
+
});
|
|
55523
56129
|
}
|
|
55524
56130
|
try {
|
|
55525
56131
|
const routes = routeMap ?? dataContext?.routes;
|
|
55526
56132
|
const hydratedContent = routes ? hydrateManifestLinks(definition.manifest, data, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : data;
|
|
55527
56133
|
const resolved = resolveBlockData(block.id, definition.dataLoaders, dataContext, routes);
|
|
55528
56134
|
const hasResolvedData = !!resolved && typeof resolved === "object" && Object.keys(resolved).length > 0;
|
|
55529
|
-
const baseProps = {
|
|
56135
|
+
const baseProps = buildBlockOverrideProps({
|
|
56136
|
+
block,
|
|
55530
56137
|
content: hydratedContent,
|
|
55531
|
-
...overrideThemeProps(themeResources),
|
|
55532
56138
|
data: resolved,
|
|
55533
|
-
|
|
55534
|
-
|
|
55535
|
-
|
|
55536
|
-
|
|
55537
|
-
stage: dataContext?.previewStage,
|
|
55538
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
55539
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
55540
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
55541
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
55542
|
-
subject: dataContext?.subject ?? null,
|
|
55543
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
55544
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
55545
|
-
blockId: block.id ?? null,
|
|
55546
|
-
blockKind: block.kind
|
|
55547
|
-
};
|
|
55548
|
-
baseProps.bookingTimeZone = resolveBookingTimeZone(dataContext);
|
|
56139
|
+
themeResources,
|
|
56140
|
+
dataContext,
|
|
56141
|
+
imageResolveContext: resolvedImageResolveContext
|
|
56142
|
+
});
|
|
55549
56143
|
let rendered;
|
|
55550
56144
|
if (OverrideComponent) rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...baseProps });
|
|
55551
56145
|
else {
|
|
@@ -55592,39 +56186,11 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55592
56186
|
hasResolvedData
|
|
55593
56187
|
});
|
|
55594
56188
|
} catch {
|
|
55595
|
-
|
|
55596
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56189
|
+
return finish(withBlockWrapper(diagnostic(block.presentationAdmission.kind === "editor" ? `Unable to render block kind "${block.kind}".` : `Invalid content for block kind "${block.kind}".`, { outcome: "exception" })), {
|
|
55597
56190
|
outcome: "exception",
|
|
55598
56191
|
override: false
|
|
55599
56192
|
});
|
|
55600
56193
|
}
|
|
55601
|
-
}, (prev, next) => {
|
|
55602
|
-
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;
|
|
55603
|
-
});
|
|
55604
|
-
function fallback(message) {
|
|
55605
|
-
return /* @__PURE__ */ jsx("div", {
|
|
55606
|
-
className: "rb-p-6 rb-caption",
|
|
55607
|
-
style: textColorStyle("mutedText"),
|
|
55608
|
-
children: message
|
|
55609
|
-
});
|
|
55610
|
-
}
|
|
55611
|
-
function shouldDebugCustomBlocks() {
|
|
55612
|
-
try {
|
|
55613
|
-
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
55614
|
-
} catch {
|
|
55615
|
-
return false;
|
|
55616
|
-
}
|
|
55617
|
-
}
|
|
55618
|
-
/**
|
|
55619
|
-
* Resolves block content based on stage priority.
|
|
55620
|
-
* For draft/preview: draftContent takes priority over content.
|
|
55621
|
-
* For published: content is the source of truth.
|
|
55622
|
-
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
55623
|
-
*/
|
|
55624
|
-
function getRenderableContent(block, previewStage = "published") {
|
|
55625
|
-
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
55626
|
-
if (!isObjectRecord$1(source)) return {};
|
|
55627
|
-
return source;
|
|
55628
56194
|
}
|
|
55629
56195
|
//#endregion
|
|
55630
56196
|
//#region ../theme-core/src/footer/mode.ts
|
|
@@ -64587,6 +65153,21 @@ const containerResponsiveThemeCss = `/*
|
|
|
64587
65153
|
outline-offset: 2px;
|
|
64588
65154
|
}
|
|
64589
65155
|
|
|
65156
|
+
/* Event titles always link to the event page, so a card or row whose CTA is
|
|
65157
|
+
hidden (sold out, closed) is never a dead end (issue 1372). The link inherits
|
|
65158
|
+
the heading's colour and only reveals itself on hover or keyboard focus.
|
|
65159
|
+
Shared by EventCard, EventCompactRow and the offerings-preview course cards via \`.event-title-link\`. */
|
|
65160
|
+
:where(.theme-scope) .event-title-link {
|
|
65161
|
+
color: inherit;
|
|
65162
|
+
text-decoration: none;
|
|
65163
|
+
}
|
|
65164
|
+
|
|
65165
|
+
:where(.theme-scope) .event-title-link:hover,
|
|
65166
|
+
:where(.theme-scope) .event-title-link:focus-visible {
|
|
65167
|
+
text-decoration: underline;
|
|
65168
|
+
text-underline-offset: 0.12em;
|
|
65169
|
+
}
|
|
65170
|
+
|
|
64590
65171
|
/* =============================================================================
|
|
64591
65172
|
* Block: Carousel Navigation (Phase 3 client component migration)
|
|
64592
65173
|
* =============================================================================
|
|
@@ -71675,6 +72256,83 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
71675
72256
|
errors: ["validation:invalid_input", "server:internal_error"]
|
|
71676
72257
|
}
|
|
71677
72258
|
});
|
|
72259
|
+
//#endregion
|
|
72260
|
+
//#region ../api/src/sdkMediaEndpointDefinitions.ts
|
|
72261
|
+
const SDK_MEDIA_ENDPOINT_DEFINITIONS = {
|
|
72262
|
+
sdkMediaExists: {
|
|
72263
|
+
path: "/sdk/{siteId}/media/exists",
|
|
72264
|
+
method: "POST",
|
|
72265
|
+
errors: ["validation:invalid_input"],
|
|
72266
|
+
auth: "service",
|
|
72267
|
+
responseKind: "json"
|
|
72268
|
+
},
|
|
72269
|
+
sdkMediaFindByChecksum: {
|
|
72270
|
+
path: "/sdk/{siteId}/media/find-by-checksum",
|
|
72271
|
+
method: "POST",
|
|
72272
|
+
errors: ["server:internal_error"],
|
|
72273
|
+
auth: "service",
|
|
72274
|
+
responseKind: "json"
|
|
72275
|
+
},
|
|
72276
|
+
sdkMediaResolve: {
|
|
72277
|
+
path: "/sdk/{siteId}/media/resolve",
|
|
72278
|
+
method: "POST",
|
|
72279
|
+
errors: ["server:internal_error"],
|
|
72280
|
+
auth: "service",
|
|
72281
|
+
responseKind: "json"
|
|
72282
|
+
},
|
|
72283
|
+
sdkMediaList: {
|
|
72284
|
+
path: "/sdk/{siteId}/media",
|
|
72285
|
+
method: "GET",
|
|
72286
|
+
auth: "service",
|
|
72287
|
+
responseKind: "json"
|
|
72288
|
+
},
|
|
72289
|
+
sdkMediaDownload: {
|
|
72290
|
+
path: "/sdk/{siteId}/media/{identifier}/download",
|
|
72291
|
+
method: "GET",
|
|
72292
|
+
auth: "service",
|
|
72293
|
+
responseKind: "json"
|
|
72294
|
+
},
|
|
72295
|
+
sdkMediaDeletePreview: {
|
|
72296
|
+
path: "/sdk/{siteId}/media/{identifier}/delete-preview",
|
|
72297
|
+
method: "POST",
|
|
72298
|
+
auth: "service",
|
|
72299
|
+
responseKind: "json"
|
|
72300
|
+
},
|
|
72301
|
+
sdkMediaDeleteApply: {
|
|
72302
|
+
path: "/sdk/{siteId}/media/{identifier}",
|
|
72303
|
+
method: "DELETE",
|
|
72304
|
+
auth: "service",
|
|
72305
|
+
responseKind: "json"
|
|
72306
|
+
},
|
|
72307
|
+
sdkMediaUploadUrl: {
|
|
72308
|
+
path: "/sdk/{siteId}/media/upload-url",
|
|
72309
|
+
method: "POST",
|
|
72310
|
+
errors: [
|
|
72311
|
+
"resource:not_found",
|
|
72312
|
+
"validation:invalid_input",
|
|
72313
|
+
"resource:conflict",
|
|
72314
|
+
"validation:payload_too_large",
|
|
72315
|
+
"billing:plan_limit_exceeded",
|
|
72316
|
+
"server:internal_error"
|
|
72317
|
+
],
|
|
72318
|
+
auth: "service",
|
|
72319
|
+
responseKind: "json"
|
|
72320
|
+
},
|
|
72321
|
+
sdkMediaUploadFinalize: {
|
|
72322
|
+
path: "/sdk/{siteId}/media/upload",
|
|
72323
|
+
method: "POST",
|
|
72324
|
+
errors: [
|
|
72325
|
+
"resource:not_found",
|
|
72326
|
+
"validation:invalid_input",
|
|
72327
|
+
"resource:conflict",
|
|
72328
|
+
"validation:payload_too_large",
|
|
72329
|
+
"billing:plan_limit_exceeded",
|
|
72330
|
+
"server:internal_error"
|
|
72331
|
+
],
|
|
72332
|
+
auth: "service",
|
|
72333
|
+
responseKind: "json"
|
|
72334
|
+
}
|
|
72335
|
+
};
|
|
71678
72336
|
({
|
|
71679
72337
|
...ACCOUNT_ENDPOINT_DEFINITIONS,
|
|
71680
72338
|
...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
|
|
@@ -71683,7 +72341,7 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
71683
72341
|
...NEWSLETTER_ENDPOINT_DEFINITIONS,
|
|
71684
72342
|
...APPOINTMENT_ENDPOINT_DEFINITIONS,
|
|
71685
72343
|
...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
|
|
71686
|
-
}, 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 };
|
|
72344
|
+
}, 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 };
|
|
71687
72345
|
z.object({ refreshToken: z.string().min(1).max(4096) }).strict();
|
|
71688
72346
|
z.object({
|
|
71689
72347
|
accessToken: z.string().min(1).max(4096),
|
|
@@ -72207,6 +72865,50 @@ z.object({
|
|
|
72207
72865
|
title: z.string(),
|
|
72208
72866
|
slug: z.string().min(1)
|
|
72209
72867
|
});
|
|
72868
|
+
z.object({
|
|
72869
|
+
signedUrl: z.string().url(),
|
|
72870
|
+
token: z.string().min(1),
|
|
72871
|
+
storagePath: z.string().min(1)
|
|
72872
|
+
}).strict();
|
|
72873
|
+
const backupManifestSchema = z.object({
|
|
72874
|
+
siteId: z.string().min(1),
|
|
72875
|
+
siteSlug: z.string().min(1),
|
|
72876
|
+
siteTitle: z.string(),
|
|
72877
|
+
exportedAt: z.string().min(1),
|
|
72878
|
+
tableCount: z.number().int().nonnegative(),
|
|
72879
|
+
totalRecords: z.number().int().nonnegative(),
|
|
72880
|
+
pageCount: z.number().int().nonnegative(),
|
|
72881
|
+
blockCount: z.number().int().nonnegative(),
|
|
72882
|
+
contentEntryCount: z.number().int().nonnegative(),
|
|
72883
|
+
formCount: z.number().int().nonnegative(),
|
|
72884
|
+
navigationMenuCount: z.number().int().nonnegative(),
|
|
72885
|
+
mediaCount: z.number().int().nonnegative(),
|
|
72886
|
+
mediaBytes: z.number().int().nonnegative()
|
|
72887
|
+
}).strict();
|
|
72888
|
+
z.object({
|
|
72889
|
+
ok: z.literal(true),
|
|
72890
|
+
manifest: backupManifestSchema,
|
|
72891
|
+
storagePath: z.string().min(1)
|
|
72892
|
+
}).strict();
|
|
72893
|
+
z.object({
|
|
72894
|
+
ok: z.literal(true),
|
|
72895
|
+
siteId: brandedIdSchema(parseSiteId, "site id"),
|
|
72896
|
+
slug: z.string().min(1),
|
|
72897
|
+
title: z.string(),
|
|
72898
|
+
counts: z.record(z.string(), z.object({
|
|
72899
|
+
created: z.number().int().nonnegative(),
|
|
72900
|
+
updated: z.number().int().nonnegative(),
|
|
72901
|
+
skipped: z.number().int().nonnegative(),
|
|
72902
|
+
failed: z.number().int().nonnegative()
|
|
72903
|
+
}).strict()),
|
|
72904
|
+
warnings: z.array(z.string()),
|
|
72905
|
+
errors: z.array(z.object({
|
|
72906
|
+
table: z.string(),
|
|
72907
|
+
id: z.string(),
|
|
72908
|
+
error: z.string()
|
|
72909
|
+
}).strict())
|
|
72910
|
+
}).strict();
|
|
72911
|
+
z.object({ deleted: z.boolean() }).strict();
|
|
72210
72912
|
const apiKeyIdSchema = brandedIdSchema(parseApiKeyId, "API key id");
|
|
72211
72913
|
const standardKeyIdsSchema = z.object({
|
|
72212
72914
|
live: apiKeyIdSchema.optional(),
|