13-Jun-2026 — per-event email overrides with ContentBlockEditor, merge field fixes, and walk pipeline... #273
What's new
Booking email overrides now use ContentBlockEditor
- Rewrote the per-event email override section in the Bookings admin page to use
the same ContentBlockEditor component used by site-level email templates
- Removed the old markdown editor with override/revert buttons
- Added section-toggle tabs (Standard / Customise / Leave Out) matching the
site-level template editor experience
- Removed the disabled booking-default-preview showing the raw default text
- Preview toggle button redesigned with sunrise colour scheme
- ContentBlockEditor gained new inputs to support this reuse:
omitAllowed — controls whether the "Leave out" tab appears
stateQueryParamKey — persists the active tab choice in URL query params
frameless — removes the heading, description, and accordion wrapper for
an inline editing mode
blockDefaults — when a block is in DEFAULT state, renders the default
content in a disabled tiptap editor so the admin can see what would be sent
- MailNotificationTemplateEditor now merges booking block defaults from
DEFAULT_BOOKING_EMAIL_BLOCKS into the discovered defaults so the disabled
preview works for booking email types
Booking merge fields preserved in markdown link URLs
- booking-template-resolver.ts now uses renderMarkdownPreservingTokens instead of
renderMarkdownToHtml when resolving booking email body content (default, site
override, and per-event override)
- Merge field placeholders like {{params.bookingMergeFields.EVENT_LINK}}
inside markdown link URLs are no longer percent-encoded during HTML rendering
- messages.ts template substitution now runs 3 passes (was 1) to resolve
parameters that might contain other parameter references
- Booking merge field values are now included in the test params for the
welcome-to-the-group template spec, confirming they resolve correctly
Booking reminder email uses correct NGX domain link
- booking-reminder-job.ts passes null instead of event.groupEvent?.url for the
event link parameter, so publicEventLink() constructs the group's own domain
URL rather than the Ramblers national URL
Walk description diff in publish status
- Added LCS-based word diff that compares the transformed website description
against the Ramblers-stored description in toPublishStatus()
- Words only in the Ramblers version are shown with red strikethrough
- Words only in the website version are shown in green
- Output is trusted HTML rendered via [innerHTML] so coloured spans display
- RamblersEventSummaryResponse interface and the list-events.ts mapper now
include the description field
CSV export contraction expansion and character cleanup
- replaceSpecialCharacters now:
- Decodes HTML entities ' / ' back to apostrophes
- Expands 30+ common contractions via lookup map (don't -> do not, etc.)
- Strips apostrophes on unrecognised words (possessive forms)
- Converts & to and (avoids double-encoding by Ramblers CSV import)
- walkLeader() no longer calls replaceSpecialCharacters so names like
"O'Grady" preserve their apostrophe in the CSV export
Styling: disabled tiptap editor state
- Added .tiptap-editor-shell-disabled and child classes for a visually distinct
disabled appearance
- Activated via [class.tiptap-editor-shell-disabled]="!editable" on the shell
Code quality
- Removed unused BrevoButtonComponent imports from
mail-notification-template-editor.ts and notification-config-selector.ts
- Added EMAIL_STATE to the StoredValue enum for content block editor state
persistence
Test changes
- Added booking merge field fixture (ATTENDEE_NAME, EVENT_TITLE, EVENT_DATE,
EVENT_LINK, ATTENDEE_LIST, PLACES_COUNT) to messages.spec.ts params
- Added spec confirming booking merge fields in markdown link URLs are
preserved during template rendering
- Updated resolveBookingBody spec to expect HTML rendering and preserved
merge field hrefs instead of raw DEFAULT_BOOKING_EMAIL_BLOCKS value
- template-diff.ts returns booking block defaults as raw trimmed markdown
(not HTML)
Ramblers Walks Manager E2E
- DRY: combined disableCookieBannerPermanently, forceDismissCookieBanners,
and cookieBannerIfVisible into a single dismissCookieBanners() method
- Updated all call sites (ClickWhenReady, Login, Start, UploadWalks) to use
the combined method
Further fixes
- Section-toggle tabs and Preview button now sit on the same row (removed
flex-wrap from the container)
- Preview now refreshes when switching email type tabs while preview is
active (made onEmailTypeTabChange async with await Promise.resolve()
so Angular change detection settles the view before calling preview)
- Batch-loaded events in loadEvents() using a single all() call with the
ids parameter instead of N+1 individual queryById() calls — cuts
group-event/all requests from N (one per unique event referenced in
bookings) to 1