@bygghammar/cms-platform 0.95.0 → 0.97.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 +121 -0
- package/docs/PAGES.md +45 -2
- package/package.json +3 -1
- package/src/admin/describe-log.ts +2 -0
- package/src/backoffice/BookingLandingSection.tsx +672 -0
- package/src/backoffice/BookingSettingsCard.tsx +34 -4
- package/src/backoffice/PageEditorView.tsx +47 -24
- package/src/backoffice/routes/host.ts +7 -0
- package/src/backoffice/routes/index.tsx +22 -3
- package/src/blocks/components/CountdownBlock.tsx +3 -1
- package/src/blocks/components/CtaBannerBlock.tsx +8 -2
- package/src/blocks/components/HeroFullBlock.tsx +7 -1
- package/src/blocks/components/IconGridBlock.tsx +5 -1
- package/src/blocks/components/InstagramBlock.tsx +5 -1
- package/src/blocks/components/InstagramTiles.tsx +3 -1
- package/src/blocks/components/LinkTilesBlock.tsx +8 -1
- package/src/blocks/components/MediaSplitBlock.tsx +8 -3
- package/src/blocks/components/PriceTableBlock.tsx +6 -5
- package/src/blocks/components/TripListBlock.tsx +5 -1
- package/src/cms/booking-landing.ts +359 -0
- package/src/cms/booking-links.ts +24 -0
- package/src/cms/page-visibility.ts +67 -1
- package/src/forms/form-audit.ts +22 -2
- package/src/html/html-tokenizer.ts +254 -0
- package/src/html/sanitize-html.ts +71 -36
- package/src/navigation/visibility.ts +65 -3
- package/src/popups/runtime.tsx +75 -32
- package/src/server/forms-core.ts +9 -0
- package/src/server/forms-delivery-core.ts +29 -22
- package/src/server/forms-submissions-core.ts +8 -5
- package/src/server/media-usage-core.ts +53 -13
- package/src/server/outbound-address.ts +131 -0
- package/src/server/outbound-http.ts +565 -0
- package/src/server/page-visibility-core.ts +231 -5
- package/src/server/popup-optin-webhook-core.ts +79 -140
- package/src/server/schemas.ts +45 -0
- package/src/site/BookingOffNotice.tsx +73 -0
- package/src/site/PageRenderer.tsx +59 -3
- package/src/site/link-visibility.tsx +21 -2
- package/src/site/preview-messaging.ts +232 -0
- package/src/site/routes/preview-page.tsx +37 -29
- package/src/util/redact-url.ts +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,127 @@ matches `package.json` exactly. See
|
|
|
8
8
|
Versions 0.1.0 through 0.78.0 predate this repository and were reconstructed
|
|
9
9
|
from the published npm tarballs.
|
|
10
10
|
|
|
11
|
+
## 0.97.0
|
|
12
|
+
|
|
13
|
+
Booking-off search landing pages. No migration: the settings live in the
|
|
14
|
+
existing `site_settings` table (the migration manifest is unchanged at 60). Two
|
|
15
|
+
exports were added — `./cms/booking-landing` and `./site/BookingOffNotice` —
|
|
16
|
+
and none removed. With nothing stored, every site behaves exactly as on 0.96.0.
|
|
17
|
+
|
|
18
|
+
- **Pages hidden from the website while booking is hidden** (Site settings →
|
|
19
|
+
Booking, after the pages that keep their booking links). While booking is
|
|
20
|
+
hidden, a page on this list is left out of every structured link the website
|
|
21
|
+
offers, and returns when booking is shown again. It stays live (200),
|
|
22
|
+
self-canonical, indexable and in the sitemap, so a visitor arriving from a
|
|
23
|
+
search result still gets the page: `pageIsIndexable` and `unlistedPages` are
|
|
24
|
+
unchanged, link-only stays `noindex`, offline stays a 404. Being indexable is
|
|
25
|
+
not a ranking promise, and the screen says so.
|
|
26
|
+
- **One rule for structured links** (`useDiscoverableLink`,
|
|
27
|
+
`isHiddenFromDiscovery`, `hiddenPaths` reason `booking-off`): header menu,
|
|
28
|
+
mobile menu and footer; link tiles (dropped); hero, banner, media-split,
|
|
29
|
+
price-table, countdown and trip buttons (dropped); media-split slide links,
|
|
30
|
+
icon-grid card links and Instagram links (link removed, picture or card
|
|
31
|
+
kept). Applied in render from the link policy the server rendered with, so
|
|
32
|
+
the server's HTML and later client navigations agree. Links inside rich text
|
|
33
|
+
are the one documented exception. `restrictToLinks` keeps `booking-off`
|
|
34
|
+
entries, which name public pages.
|
|
35
|
+
- **Popups** that lead to a hidden page are retired in the runtime the way
|
|
36
|
+
booking popups are; `withoutPopupsLeadingTo` lets a host withhold them on the
|
|
37
|
+
server.
|
|
38
|
+
- **The booking-off notice** (`BookingOffNotice`, placed by the page renderer's
|
|
39
|
+
new `notice` prop after a leading hero): a plain-text heading, message and an
|
|
40
|
+
optional link, from a site-wide default with optional per-page replacements,
|
|
41
|
+
field by field, Swedish and English. The link must be an internal path or an
|
|
42
|
+
`https://` address and never leads to booking or to a hidden page: hosts name
|
|
43
|
+
their booking page (`LinkVisibilityPolicy.bookingPagePaths`, checked when the
|
|
44
|
+
notice renders) and check links on save (`VisibilityDeps.noticeTargetProblem`,
|
|
45
|
+
with `noticeTargetProblem(href, routes)` doing the work from the host's
|
|
46
|
+
routes). The platform names no route.
|
|
47
|
+
- **Storage and staff writes**: rows `booking.landing.<page uuid>` and
|
|
48
|
+
`booking.landing.default`, written by `setBookingLandingCore` and
|
|
49
|
+
`setBookingLandingDefaultCore` — staff only, validated, compare-and-swap on
|
|
50
|
+
`updated_at`, audited, cache-invalidating. Protected pages and booking
|
|
51
|
+
exceptions are refused, and adding an exception for a landing page is refused
|
|
52
|
+
too (`BookingConflictError`); rows that say both are treated as an exception
|
|
53
|
+
only, and the Booking card warns. After a refused save the page-message editor
|
|
54
|
+
shows the stored version and says why, so a stale draft is never resubmitted
|
|
55
|
+
unseen.
|
|
56
|
+
- **Host wiring (optional)**: `booking.fns.setBookingLanding` and
|
|
57
|
+
`setBookingLandingDefault` show the new subsection; without them the Booking
|
|
58
|
+
card is unchanged.
|
|
59
|
+
|
|
60
|
+
## 0.96.0
|
|
61
|
+
|
|
62
|
+
Security release. No migration, and no export was added or removed.
|
|
63
|
+
|
|
64
|
+
- **Rich text is sanitized by a parser, not by patterns** (`./html/sanitize-html`).
|
|
65
|
+
The old sanitizer copied the text between the tags it recognised through as
|
|
66
|
+
written, so an unterminated tag at the end of a fragment (for example an image
|
|
67
|
+
tag with an event handler and no closing `>`) survived, and in server
|
|
68
|
+
rendering the markup after the fragment completed it into a live element.
|
|
69
|
+
Markup is now read the way a browser's HTML tokenizer reads it (every quoting
|
|
70
|
+
form, comments, DOCTYPE, CDATA, raw-text elements, a tag cut off by the end of
|
|
71
|
+
the input) and the output is written from scratch: allowlisted elements and
|
|
72
|
+
attributes only, attributes decoded once and escaped once, and every `<` and
|
|
73
|
+
`>` in text escaped. The editor's allowlist, URL rules and booking-link
|
|
74
|
+
`&` handling are unchanged.
|
|
75
|
+
- Tightened: a `data:` image is accepted only in an image's `src`, never as a
|
|
76
|
+
link target. `svg`, `math`, `template`, `textarea`, `title`, `xmp`,
|
|
77
|
+
`noembed`, `noframes` and `plaintext` are dropped together with their
|
|
78
|
+
content.
|
|
79
|
+
- `htmlToText` now returns plain text, with entities decoded.
|
|
80
|
+
- **The builder and its preview only talk to each other**
|
|
81
|
+
(`./site/routes/preview-page`, the page builder). The public preview route
|
|
82
|
+
used to render blocks posted by any window that put a marker string in its
|
|
83
|
+
message, and both ends posted to `"*"`. Now:
|
|
84
|
+
- the preview accepts the builder's messages only from its parent frame,
|
|
85
|
+
from its own origin or an origin the host lists, in one builder session
|
|
86
|
+
with increasing sequence numbers, shape-checked and at most 4 MB;
|
|
87
|
+
- the builder accepts the preview's messages only from its own iframe, and
|
|
88
|
+
every message names its target origin.
|
|
89
|
+
- **Host change, only for a builder on another origin:** list that origin in
|
|
90
|
+
the preview page deps as `trustedEditorOrigins`. A builder on the site's own
|
|
91
|
+
origin (Voyara's) needs no configuration.
|
|
92
|
+
- **One outbound request policy** for every address staff or configuration
|
|
93
|
+
chooses: media import, form webhooks and popup opt-in webhooks. Each of these
|
|
94
|
+
was a server-side request forgery primitive. Every request now:
|
|
95
|
+
- allows only the schemes its policy names (webhooks: https only), with no
|
|
96
|
+
credentials in the URL, no local or single-label host names and no
|
|
97
|
+
privileged ports other than 80 and 443;
|
|
98
|
+
- looks the host up and goes ahead only if every address it resolves to is
|
|
99
|
+
public (IPv4 and IPv6 special-purpose ranges, including mapped, NAT64 and
|
|
100
|
+
6to4 forms), then connects to the address it checked (pinned, so a DNS
|
|
101
|
+
answer that changes in between cannot redirect it), with TLS still verified
|
|
102
|
+
for the host name;
|
|
103
|
+
- follows redirects itself, checking and pinning every hop, up to a cap;
|
|
104
|
+
- runs under one deadline for the whole request, which also holds when a
|
|
105
|
+
lookup, connection or response body ignores cancellation (Node's DNS
|
|
106
|
+
lookup cannot be cancelled), and reads at most a fixed number of bytes,
|
|
107
|
+
streaming, whatever `Content-Length` says;
|
|
108
|
+
- reports failures by category, with messages that carry no address, query
|
|
109
|
+
string or response body.
|
|
110
|
+
- **What each path does with that policy:**
|
|
111
|
+
- **Media import:** a downloaded file is stored as what its first bytes say
|
|
112
|
+
it is (JPEG, PNG, GIF, WebP, AVIF, MP4, MOV, M4V, WebM, MP3, WAV, Ogg or
|
|
113
|
+
PDF), with that extension, never as the server's content type or the URL's
|
|
114
|
+
extension. SVG and HTML are refused. Public redirects to other sites are
|
|
115
|
+
still followed, carrying only plain request headers.
|
|
116
|
+
- **Form and popup webhooks never follow a redirect to another site.** A
|
|
117
|
+
signed body, its signature, timestamp, event id and idempotency key reach
|
|
118
|
+
only the configured origin (a redirect within that origin is followed;
|
|
119
|
+
popup webhooks report the refusal as `blocked-endpoint`). When a redirect
|
|
120
|
+
turns a request into a GET, entity and signature headers are dropped.
|
|
121
|
+
**Receivers that redirect to another host must be configured with their
|
|
122
|
+
final address.**
|
|
123
|
+
- **Form webhooks** are checked when the form is saved, report a failure by
|
|
124
|
+
status only (the receiver's reply is never copied into it), and are never
|
|
125
|
+
sent unsigned when signing fails.
|
|
126
|
+
- **Addresses in records keep the origin only.** Webhook delivery records,
|
|
127
|
+
errors and the form audit trail show a webhook as its origin plus a fixed
|
|
128
|
+
`/[redacted]` marker, never its path, query string or credentials, because
|
|
129
|
+
receivers put keys in paths as well as in query strings. Settings whose name
|
|
130
|
+
says they are a secret are recorded as changed, never with a value.
|
|
131
|
+
|
|
11
132
|
## 0.95.0
|
|
12
133
|
|
|
13
134
|
- **Pages can be link-only, and unpublished from the page itself.** Listing is
|
package/docs/PAGES.md
CHANGED
|
@@ -541,11 +541,50 @@ Listing is a second property beside publication, and the two never touch:
|
|
|
541
541
|
booking links in their own content. Removal (`stripBookingDeep`,
|
|
542
542
|
`withoutBookingPopups`) is for the host to apply to data before it leaves the
|
|
543
543
|
server; stored content is never changed.
|
|
544
|
+
- **Booking-off landing pages** (`cms/booking-landing`). While booking is off, a
|
|
545
|
+
page in this list leaves every structured link the website offers (see the
|
|
546
|
+
inventory below; `hiddenPaths` reason `booking-off`, `isHiddenFromDiscovery`,
|
|
547
|
+
`useDiscoverableLink`), but stays live, self-canonical, indexable and in the
|
|
548
|
+
sitemap: `pageIsIndexable` and `unlistedPages` do not change. Indexable is
|
|
549
|
+
not a ranking promise: removing a page's internal links can change how search
|
|
550
|
+
engines value it. Its booking links go as on any page, and it shows a
|
|
551
|
+
plain-text notice (`BookingOffNotice`, placed by the page renderer's `notice`
|
|
552
|
+
prop after a leading hero) with an optional link that never leads to booking
|
|
553
|
+
or to another hidden page. Staff write a site-wide default and optional
|
|
554
|
+
per-page replacements, field by field (`resolveNotice`). A page cannot be both
|
|
555
|
+
a booking exception and a landing page (`BookingConflictError`); stored rows
|
|
556
|
+
that say both are treated as an exception only, and the Booking card warns.
|
|
557
|
+
Offline still wins (404); link-only stays `noindex`. Protected pages are
|
|
558
|
+
refused. With booking on, nothing changes.
|
|
559
|
+
|
|
560
|
+
### Booking-off landing pages: structured links (reviewed 2026-10-17)
|
|
561
|
+
|
|
562
|
+
Enforced in render from the link policy the server rendered with (and every
|
|
563
|
+
client navigation refreshes), so server HTML and the browser agree. Stored
|
|
564
|
+
content is never changed. `tests/booking-landing-discovery-inventory.test.tsx`
|
|
565
|
+
fails when a block type is added without being classified here.
|
|
566
|
+
|
|
567
|
+
| Surface | While booking is hidden, a link to a landing page… |
|
|
568
|
+
| --- | --- |
|
|
569
|
+
| Header menu, mobile menu, footer, header button | not rendered (`hiddenPaths`); an emptied footer column goes |
|
|
570
|
+
| `linkTiles` | tile dropped |
|
|
571
|
+
| `heroFull`, `ctaBanner`, `mediaSplit` (both buttons), `priceTable`, `countdown` (expired button), `tripList`/`courseList` button | button dropped |
|
|
572
|
+
| `mediaSplit` slide link, `iconGrid` card link, `instagram` profile and tiles | link removed, picture or card kept |
|
|
573
|
+
| Popups (link block, button or answer `openUrl`, form success `openUrl`) | popup withheld on the server (`withoutPopupsLeadingTo`) and retired in the runtime, like a booking popup |
|
|
574
|
+
| Booking-off notice link | dropped when it leads to booking (a booking site or the host's `bookingPagePaths`) or to a hidden page; refused at save by the host's `noticeTargetProblem` |
|
|
575
|
+
| `richText` | **exception**: links in editor-written text stay as written |
|
|
576
|
+
| `form` thank-you redirect | exempt: the result of submitting, not a link offered |
|
|
577
|
+
| `layout`, `faq`, `prose`, `checklist`, `imageMarquee`, `cabinTabs` | no links of their own |
|
|
578
|
+
|
|
579
|
+
Hosts add their own sections to the same rule with `useDiscoverableLink()`
|
|
580
|
+
(Voyara: its legacy hero buttons and destination cards).
|
|
544
581
|
|
|
545
582
|
### Storage
|
|
546
583
|
|
|
547
584
|
No migration: one `site_settings` row per decision, keyed by page id —
|
|
548
|
-
`page.listing.<uuid>`, `booking`, `booking.exception.<uuid
|
|
585
|
+
`page.listing.<uuid>`, `booking`, `booking.exception.<uuid>`,
|
|
586
|
+
`booking.landing.<uuid>` (`{ landing, notice? }`) and `booking.landing.default`
|
|
587
|
+
(`{ notice }`). Every write is a
|
|
549
588
|
compare-and-swap on `updated_at` (`VisibilityConflictError` when someone else
|
|
550
589
|
changed the row first). None is an `OVERRIDABLE_SETTING_KEYS` key, so the generic
|
|
551
590
|
settings save and reset cannot reach them.
|
|
@@ -570,7 +609,11 @@ shown. The public page route takes a per-page `pagePolicy` query and adds
|
|
|
570
609
|
|
|
571
610
|
```ts
|
|
572
611
|
pages: { fns: { setPageStatus?, setPageListing?, ... } }
|
|
573
|
-
booking?: { fns: { getBookingSettings, setBookingEnabled, setBookingException
|
|
612
|
+
booking?: { fns: { getBookingSettings, setBookingEnabled, setBookingException,
|
|
613
|
+
setBookingLanding?, setBookingLandingDefault? } }
|
|
614
|
+
// server: VisibilityDeps.noticeTargetProblem?(href) — the host's route check
|
|
615
|
+
// (noticeTargetProblem(href, routes) does the work from the host's routes)
|
|
616
|
+
// browser policy: LinkVisibilityPolicy.bookingPagePaths — the host's booking page, every alias
|
|
574
617
|
useLinkVisibility?: () => LinkVisibilityPolicy | undefined
|
|
575
618
|
```
|
|
576
619
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -210,6 +210,7 @@
|
|
|
210
210
|
"./cms/page-publication": "./src/cms/page-publication.ts",
|
|
211
211
|
"./cms/page-visibility": "./src/cms/page-visibility.ts",
|
|
212
212
|
"./cms/booking-links": "./src/cms/booking-links.ts",
|
|
213
|
+
"./cms/booking-landing": "./src/cms/booking-landing.ts",
|
|
213
214
|
"./site/routes": "./src/site/routes.ts",
|
|
214
215
|
"./site/PageRenderer": "./src/site/PageRenderer.tsx",
|
|
215
216
|
"./server/env": "./src/server/env.ts",
|
|
@@ -239,6 +240,7 @@
|
|
|
239
240
|
"./site/route-index": "./src/site/route-index.ts",
|
|
240
241
|
"./site/route-aliases": "./src/site/route-aliases.ts",
|
|
241
242
|
"./site/link-visibility": "./src/site/link-visibility.tsx",
|
|
243
|
+
"./site/BookingOffNotice": "./src/site/BookingOffNotice.tsx",
|
|
242
244
|
"./site/SiteHeader": "./src/site/SiteHeader.tsx",
|
|
243
245
|
"./site/SiteFooter": "./src/site/SiteFooter.tsx",
|
|
244
246
|
"./server/schemas": "./src/server/schemas.ts",
|
|
@@ -37,6 +37,8 @@ export const ACTION_LABELS: Record<string, string> = {
|
|
|
37
37
|
"page.listing": "Changed whether a page is listed",
|
|
38
38
|
"booking.settings": "Switched booking on or off",
|
|
39
39
|
"booking.exception": "Changed which pages keep booking links",
|
|
40
|
+
"booking.landing": "Changed which pages are hidden from the website while booking is hidden",
|
|
41
|
+
"booking.landing.default": "Changed the booking-off landing page message",
|
|
40
42
|
"page.version.create": "Saved page version",
|
|
41
43
|
"page.version.restore": "Restored page version",
|
|
42
44
|
"template.create": "Created block template",
|