ferst-core 0.4.8 → 0.4.10

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.
@@ -172,12 +172,23 @@ const metaDescription = description ?? identity.description;
172
172
  document.addEventListener('astro:page-load', ferstMarkExternalLinks);
173
173
  </script>
174
174
  <script>
175
- // Live theme preview: only when this page is FRAMED with ?preview=1 (the
176
- // portal Theme Studio or the CMS design-tokens pane), listen for draft theme
177
- // settings over postMessage and re-theme live. Dynamically imported so the
178
- // code never ships to normal visitors. Inert otherwise.
179
- if (window.parent !== window && new URLSearchParams(location.search).has('preview')) {
180
- import('../lib/theme-preview').then((m) => m.initThemePreviewListener());
175
+ // Live theme preview: only when this page is FRAMED and either loaded with
176
+ // ?preview=1 OR already in a preview session (so the draft survives clicking
177
+ // links inside the frame, which drop the query). Listens for draft theme
178
+ // settings over postMessage and re-themes live. Dynamically imported so the
179
+ // code never ships to normal visitors; never runs unframed.
180
+ try {
181
+ var inPreview =
182
+ new URLSearchParams(location.search).has('preview') ||
183
+ sessionStorage.getItem('ferst-preview-on') === '1';
184
+ if (window.parent !== window && inPreview) {
185
+ import('../lib/theme-preview').then((m) => m.initThemePreviewListener());
186
+ }
187
+ } catch (_) {
188
+ /* sessionStorage blocked — fall back to the ?preview query only */
189
+ if (window.parent !== window && new URLSearchParams(location.search).has('preview')) {
190
+ import('../lib/theme-preview').then((m) => m.initThemePreviewListener());
191
+ }
181
192
  }
182
193
  </script>
183
194
  </head>
@@ -28,13 +28,36 @@ export const PREVIEW_MESSAGES = {
28
28
  } as const;
29
29
 
30
30
  const STYLE_ID = 'ferst-preview';
31
+ /** Marks a live preview session so the draft survives navigation WITHIN the frame
32
+ * (internal links drop ?preview=1). Base.astro's gate also checks this flag. */
33
+ export const PREVIEW_SESSION_FLAG = 'ferst-preview-on';
34
+ const PREVIEW_DRAFT_KEY = 'ferst-preview-draft';
31
35
 
32
- /** Origins allowed to drive the preview (besides same-origin, i.e. the CMS pane). */
36
+ function rememberDraft(theme: ThemeSettings): void {
37
+ try {
38
+ sessionStorage.setItem(PREVIEW_DRAFT_KEY, JSON.stringify(theme));
39
+ } catch {
40
+ /* storage blocked — the page still themes from the live postMessage */
41
+ }
42
+ }
43
+
44
+ function recallDraft(): ThemeSettings | null {
45
+ try {
46
+ const raw = sessionStorage.getItem(PREVIEW_DRAFT_KEY);
47
+ return raw ? (JSON.parse(raw) as ThemeSettings) : null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ /** Origins allowed to drive the preview (besides same-origin, i.e. the CMS pane).
54
+ * The Ferst portal — both its custom domains and the bare Cloudflare Pages URLs
55
+ * it's actually served from (dev has no custom domain today). */
33
56
  const PORTAL_ORIGINS = [
34
57
  'https://portal.ferst.co.uk',
35
58
  'https://portal-dev.ferst.co.uk',
36
- 'https://signin.ferst.co.uk',
37
- 'https://signin-dev.ferst.co.uk',
59
+ 'https://myai4-portal.pages.dev',
60
+ 'https://myai4-portal-dev.pages.dev',
38
61
  ];
39
62
 
40
63
  /** True for same-origin (CMS design-tokens pane) or a known Ferst portal origin. */
@@ -64,8 +87,30 @@ export function applyPreviewTheme(theme: ThemeSettings): void {
64
87
  else delete document.documentElement.dataset.field;
65
88
  }
66
89
 
67
- /** Wire the page to receive drafts from an allowed surface, and announce readiness. */
90
+ /** Wire the page to receive drafts from an allowed surface, and announce readiness.
91
+ * Persists the draft for the session so it survives navigation inside the frame. */
68
92
  export function initThemePreviewListener(): void {
93
+ try {
94
+ sessionStorage.setItem(PREVIEW_SESSION_FLAG, '1'); // keep previewing across in-frame nav
95
+ } catch {
96
+ /* storage blocked — the portal re-push on ready still re-themes each new page */
97
+ }
98
+
99
+ const reapply = () => {
100
+ const draft = recallDraft();
101
+ if (draft) {
102
+ try {
103
+ applyPreviewTheme(draft);
104
+ } catch {
105
+ /* ignore a bad stored draft */
106
+ }
107
+ }
108
+ };
109
+
110
+ // A freshly-navigated page in the session: re-theme immediately from the stored
111
+ // draft (before the portal re-pushes), so tokens don't drop on link clicks.
112
+ reapply();
113
+
69
114
  window.addEventListener('message', (event: MessageEvent) => {
70
115
  if (!isAllowedPreviewOrigin(event.origin)) return;
71
116
  const data = event.data as { type?: string; theme?: ThemeSettings; mode?: string } | null;
@@ -73,6 +118,7 @@ export function initThemePreviewListener(): void {
73
118
  if (data.type === PREVIEW_MESSAGES.theme && data.theme) {
74
119
  try {
75
120
  applyPreviewTheme(data.theme);
121
+ rememberDraft(data.theme);
76
122
  } catch {
77
123
  /* a malformed draft must never break the preview page */
78
124
  }
@@ -80,10 +126,15 @@ export function initThemePreviewListener(): void {
80
126
  document.documentElement.dataset.theme = data.mode === 'dark' ? 'dark' : 'light';
81
127
  }
82
128
  });
83
- // Tell whoever framed us that we're listening, so it can push the current draft.
129
+
130
+ // View-transition (ClientRouter) pages swap the DOM without a full load, which
131
+ // drops the injected <style> — re-apply after each swap.
132
+ document.addEventListener('astro:page-load', reapply);
133
+
134
+ // Tell whoever framed us that we're listening, so it can push the latest draft.
84
135
  try {
85
136
  window.parent?.postMessage({ type: PREVIEW_MESSAGES.ready }, '*');
86
137
  } catch {
87
- /* no parent / blocked — the surface can still push on its own timer */
138
+ /* no parent / blocked — the stored draft already re-themed this page */
88
139
  }
89
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferst-core",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "type": "module",
5
5
  "description": "Ferst Core — the shared, brand-agnostic client-site system: Astro components, layouts, content schemas, the layered config resolver, and a neutral styling architecture that every client overrides.",
6
6
  "license": "BUSL-1.1",