@prenta/admin 1.0.0 → 1.1.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +5 -56
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/breakpoint-controls.render.test.js +4 -4
  6. package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -1
  7. package/dist/__tests__/lib/open-public-site-tab.test.d.ts +2 -0
  8. package/dist/__tests__/lib/open-public-site-tab.test.d.ts.map +1 -0
  9. package/dist/__tests__/lib/open-public-site-tab.test.js +64 -0
  10. package/dist/__tests__/lib/open-public-site-tab.test.js.map +1 -0
  11. package/dist/__tests__/lib/view-site-chrome.test.d.ts +2 -0
  12. package/dist/__tests__/lib/view-site-chrome.test.d.ts.map +1 -0
  13. package/dist/__tests__/lib/view-site-chrome.test.js +59 -0
  14. package/dist/__tests__/lib/view-site-chrome.test.js.map +1 -0
  15. package/dist/__tests__/router/admin-routes.test.js +4 -0
  16. package/dist/__tests__/router/admin-routes.test.js.map +1 -1
  17. package/dist/__tests__/views/canvas-surface-shared.test.js +0 -16
  18. package/dist/__tests__/views/canvas-surface-shared.test.js.map +1 -1
  19. package/dist/__tests__/views/editor-top-bar.render.test.js +44 -22
  20. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  21. package/dist/__tests__/views/page-section-editor.test.js +30 -9
  22. package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
  23. package/dist/__tests__/views/post-section-editor.test.js +23 -0
  24. package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
  25. package/dist/components/BreakpointPicker.d.ts +3 -2
  26. package/dist/components/BreakpointPicker.d.ts.map +1 -1
  27. package/dist/components/BreakpointPicker.js +5 -3
  28. package/dist/components/BreakpointPicker.js.map +1 -1
  29. package/dist/components/CanvasEditTip.js +1 -1
  30. package/dist/components/CanvasEditTip.js.map +1 -1
  31. package/dist/lib/editor-preview.d.ts +4 -1
  32. package/dist/lib/editor-preview.d.ts.map +1 -1
  33. package/dist/lib/editor-preview.js +4 -1
  34. package/dist/lib/editor-preview.js.map +1 -1
  35. package/dist/lib/open-public-site-tab.d.ts +14 -0
  36. package/dist/lib/open-public-site-tab.d.ts.map +1 -0
  37. package/dist/lib/open-public-site-tab.js +46 -0
  38. package/dist/lib/open-public-site-tab.js.map +1 -0
  39. package/dist/lib/view-site-chrome.d.ts +16 -0
  40. package/dist/lib/view-site-chrome.d.ts.map +1 -0
  41. package/dist/lib/view-site-chrome.js +15 -0
  42. package/dist/lib/view-site-chrome.js.map +1 -0
  43. package/dist/router/admin-routes.d.ts.map +1 -1
  44. package/dist/router/admin-routes.js +0 -2
  45. package/dist/router/admin-routes.js.map +1 -1
  46. package/dist/views/page-editor/EditorCanvas.d.ts +1 -2
  47. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  48. package/dist/views/page-editor/EditorCanvas.js +2 -2
  49. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  50. package/dist/views/page-editor/EditorTopBar.d.ts +13 -17
  51. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  52. package/dist/views/page-editor/EditorTopBar.js +19 -21
  53. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  54. package/dist/views/page-editor/PageSectionEditor.d.ts +1 -7
  55. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  56. package/dist/views/page-editor/PageSectionEditor.js +29 -95
  57. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  58. package/dist/views/page-editor/StructureNotice.d.ts +2 -5
  59. package/dist/views/page-editor/StructureNotice.d.ts.map +1 -1
  60. package/dist/views/page-editor/StructureNotice.js +6 -10
  61. package/dist/views/page-editor/StructureNotice.js.map +1 -1
  62. package/dist/views/post-editor/PostEditorCanvas.d.ts +1 -3
  63. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  64. package/dist/views/post-editor/PostEditorCanvas.js +3 -3
  65. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  66. package/dist/views/post-editor/PostSectionEditor.d.ts +1 -15
  67. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  68. package/dist/views/post-editor/PostSectionEditor.js +30 -88
  69. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  70. package/package.json +4 -4
  71. package/src/AdminRoot.tsx +0 -70
  72. package/src/__tests__/components/breakpoint-controls.render.test.tsx +6 -4
  73. package/src/__tests__/lib/open-public-site-tab.test.ts +74 -0
  74. package/src/__tests__/lib/view-site-chrome.test.ts +65 -0
  75. package/src/__tests__/router/admin-routes.test.ts +5 -0
  76. package/src/__tests__/views/canvas-surface-shared.test.ts +0 -19
  77. package/src/__tests__/views/editor-top-bar.render.test.tsx +85 -22
  78. package/src/__tests__/views/page-section-editor.test.tsx +42 -9
  79. package/src/__tests__/views/post-section-editor.test.tsx +35 -0
  80. package/src/components/BreakpointPicker.tsx +6 -3
  81. package/src/components/CanvasEditTip.tsx +2 -2
  82. package/src/lib/editor-preview.ts +4 -1
  83. package/src/lib/open-public-site-tab.ts +57 -0
  84. package/src/lib/view-site-chrome.ts +34 -0
  85. package/src/router/admin-routes.ts +0 -2
  86. package/src/styles/theme.css +4 -4
  87. package/src/views/page-editor/EditorCanvas.tsx +1 -6
  88. package/src/views/page-editor/EditorTopBar.tsx +81 -82
  89. package/src/views/page-editor/PageSectionEditor.tsx +77 -157
  90. package/src/views/page-editor/StructureNotice.tsx +7 -42
  91. package/src/views/post-editor/PostEditorCanvas.tsx +2 -8
  92. package/src/views/post-editor/PostSectionEditor.tsx +66 -138
  93. package/dist/__tests__/views/use-site-preview.render.test.d.ts +0 -2
  94. package/dist/__tests__/views/use-site-preview.render.test.d.ts.map +0 -1
  95. package/dist/__tests__/views/use-site-preview.render.test.js +0 -120
  96. package/dist/__tests__/views/use-site-preview.render.test.js.map +0 -1
  97. package/dist/views/page-editor/PagePreview.d.ts +0 -13
  98. package/dist/views/page-editor/PagePreview.d.ts.map +0 -1
  99. package/dist/views/page-editor/PagePreview.js +0 -46
  100. package/dist/views/page-editor/PagePreview.js.map +0 -1
  101. package/dist/views/page-editor/SitePreviewFrame.d.ts +0 -43
  102. package/dist/views/page-editor/SitePreviewFrame.d.ts.map +0 -1
  103. package/dist/views/page-editor/SitePreviewFrame.js +0 -165
  104. package/dist/views/page-editor/SitePreviewFrame.js.map +0 -1
  105. package/dist/views/page-editor/useSitePreview.d.ts +0 -47
  106. package/dist/views/page-editor/useSitePreview.d.ts.map +0 -1
  107. package/dist/views/page-editor/useSitePreview.js +0 -96
  108. package/dist/views/page-editor/useSitePreview.js.map +0 -1
  109. package/dist/views/post-editor/PostPreview.d.ts +0 -14
  110. package/dist/views/post-editor/PostPreview.d.ts.map +0 -1
  111. package/dist/views/post-editor/PostPreview.js +0 -63
  112. package/dist/views/post-editor/PostPreview.js.map +0 -1
  113. package/src/__tests__/views/use-site-preview.render.test.tsx +0 -164
  114. package/src/views/page-editor/PagePreview.tsx +0 -105
  115. package/src/views/page-editor/SitePreviewFrame.tsx +0 -250
  116. package/src/views/page-editor/useSitePreview.ts +0 -158
  117. package/src/views/post-editor/PostPreview.tsx +0 -125
@@ -1,165 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useLayoutEffect, useRef, useState } from 'react';
4
- import { Loader2 } from 'lucide-react';
5
- import { injectPreviewEditChrome } from '../../lib/canvas-edit-chrome.js';
6
- import { canvasOriginFromSrc, isPreviewBridgeMessage, isPreviewEditMessage, isPreviewRequestMediaMessage, PREVIEW_BRIDGE_VERSION, PREVIEW_SECTIONS_DEBOUNCE_MS, toPreviewSectionPayload, } from '../../lib/preview-bridge.js';
7
- import { getViewport } from './viewports.js';
8
- /**
9
- * Inline branded preview surface — Next builder route in an iframe at the
10
- * selected viewport's *true* CSS width, CSS-`transform: scale()`'d to fit
11
- * (never crop). Stage 3: mode / edit / request-media + suppress-echo for
12
- * canvas-originated text edits.
13
- */
14
- export function SitePreviewFrame({ src, viewport, reloadKey, title = 'Site preview', selectedId = null, sections, suppressSectionsEcho = 0, onSelectSection, onCanvasEdit, onCanvasRequestMedia, }) {
15
- const vp = getViewport(viewport);
16
- const containerRef = useRef(null);
17
- const sentinelRef = useRef(null);
18
- const iframeRef = useRef(null);
19
- const [scale, setScale] = useState(1);
20
- const [frameHeight, setFrameHeight] = useState(600);
21
- const [loaded, setLoaded] = useState(false);
22
- const [canvasReady, setCanvasReady] = useState(false);
23
- const canvasOrigin = canvasOriginFromSrc(src);
24
- const lastSuppressRef = useRef(suppressSectionsEcho);
25
- /**
26
- * Measure from a zero-height sentinel, never `clientWidth - 48`.
27
- *
28
- * The 48 was `p-6` written down a second time, in a second unit, four
29
- * hundred lines from the class it mirrors. It disagrees with reality the
30
- * moment a scrollbar appears, and it rots silently if the gutter ever
31
- * changes — nothing fails, the preview just scales slightly wrong.
32
- *
33
- * The sentinel is subject to the same padding, the same scrollbar and the
34
- * same host scaling as the frame, so its `offsetWidth` IS the usable width
35
- * and there is no arithmetic to get wrong. Same rule as `CanvasSurface`,
36
- * which is where this was first learned; this file is the third copy of the
37
- * arithmetic and the last one to go.
38
- *
39
- * Height still comes from the container, because the sentinel is
40
- * deliberately zero-height and can say nothing about it — but it is read as
41
- * `clientHeight - (clientWidth - sentinelWidth)`, deriving the vertical
42
- * gutter from the measured horizontal one rather than hardcoding it again.
43
- * That holds for the symmetric `p-6` this uses and degrades to the old
44
- * behaviour rather than breaking if it ever stops being symmetric.
45
- */
46
- useLayoutEffect(() => {
47
- const container = containerRef.current;
48
- const sentinel = sentinelRef.current;
49
- if (!container || !sentinel)
50
- return;
51
- const recompute = () => {
52
- const availableW = sentinel.offsetWidth;
53
- if (availableW <= 0)
54
- return;
55
- const gutter = Math.max(0, container.clientWidth - availableW);
56
- const availableH = container.clientHeight - gutter;
57
- const next = Math.min(1, availableW / vp.width);
58
- setScale(next > 0 ? next : 1);
59
- setFrameHeight(availableH > 0 ? availableH : 600);
60
- };
61
- recompute();
62
- const ro = new ResizeObserver(recompute);
63
- ro.observe(container);
64
- ro.observe(sentinel);
65
- return () => ro.disconnect();
66
- }, [vp.width]);
67
- useEffect(() => {
68
- setLoaded(false);
69
- setCanvasReady(false);
70
- }, [src, reloadKey]);
71
- // Canvas → admin: ready handshake + select + edit + request-media.
72
- useEffect(() => {
73
- if (!canvasOrigin)
74
- return;
75
- const onMessage = (event) => {
76
- if (event.origin !== canvasOrigin)
77
- return;
78
- if (!isPreviewBridgeMessage(event.data))
79
- return;
80
- if (event.data.type === 'prenta-preview:ready') {
81
- setCanvasReady(true);
82
- return;
83
- }
84
- if (event.data.type === 'prenta-preview:select' && onSelectSection) {
85
- onSelectSection(event.data.sectionId, event.data.fieldPath);
86
- return;
87
- }
88
- if (isPreviewEditMessage(event.data) && onCanvasEdit) {
89
- onCanvasEdit({
90
- sectionId: event.data.sectionId,
91
- fieldPath: event.data.fieldPath,
92
- value: event.data.value,
93
- });
94
- return;
95
- }
96
- if (isPreviewRequestMediaMessage(event.data) && onCanvasRequestMedia) {
97
- onCanvasRequestMedia({
98
- sectionId: event.data.sectionId,
99
- fieldPath: event.data.fieldPath,
100
- });
101
- }
102
- };
103
- window.addEventListener('message', onMessage);
104
- return () => window.removeEventListener('message', onMessage);
105
- }, [canvasOrigin, onSelectSection, onCanvasEdit, onCanvasRequestMedia]);
106
- // Admin → canvas: enable inline editing once the canvas is ready.
107
- useEffect(() => {
108
- if (!canvasOrigin || !loaded || !canvasReady)
109
- return;
110
- const win = iframeRef.current?.contentWindow;
111
- if (!win)
112
- return;
113
- const msg = {
114
- type: 'prenta-preview:mode',
115
- v: PREVIEW_BRIDGE_VERSION,
116
- editing: true,
117
- };
118
- win.postMessage(msg, canvasOrigin);
119
- // Same-origin only — cross-origin previews skip silently.
120
- injectPreviewEditChrome(iframeRef.current);
121
- }, [canvasOrigin, loaded, canvasReady, reloadKey]);
122
- // Admin → canvas: highlight the selected section.
123
- useEffect(() => {
124
- if (!canvasOrigin || !loaded || !canvasReady)
125
- return;
126
- const win = iframeRef.current?.contentWindow;
127
- if (!win)
128
- return;
129
- const msg = {
130
- type: 'prenta-preview:highlight',
131
- v: PREVIEW_BRIDGE_VERSION,
132
- sectionId: selectedId,
133
- };
134
- win.postMessage(msg, canvasOrigin);
135
- }, [canvasOrigin, selectedId, loaded, canvasReady, reloadKey]);
136
- // Admin → canvas: live section content (debounced). Skip one cycle after
137
- // canvas-originated text edits so contenteditable does not remount.
138
- useEffect(() => {
139
- if (!canvasOrigin || !loaded || !canvasReady || !sections)
140
- return;
141
- if (suppressSectionsEcho !== lastSuppressRef.current) {
142
- lastSuppressRef.current = suppressSectionsEcho;
143
- return;
144
- }
145
- const win = iframeRef.current?.contentWindow;
146
- if (!win)
147
- return;
148
- const timer = window.setTimeout(() => {
149
- const msg = {
150
- type: 'prenta-preview:sections',
151
- v: PREVIEW_BRIDGE_VERSION,
152
- sections: sections.map(toPreviewSectionPayload),
153
- };
154
- win.postMessage(msg, canvasOrigin);
155
- }, PREVIEW_SECTIONS_DEBOUNCE_MS);
156
- return () => window.clearTimeout(timer);
157
- }, [canvasOrigin, loaded, canvasReady, sections, reloadKey, suppressSectionsEcho]);
158
- return (_jsxs("div", { ref: containerRef, className: "bg-muted relative h-full overflow-hidden p-6", children: [_jsx("div", { ref: sentinelRef, "aria-hidden": true, className: "h-0" }), (!src || !loaded) && (_jsxs("div", { className: "absolute inset-0 z-10 flex flex-col items-center justify-center gap-2", "aria-live": "polite", children: [_jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin", "aria-hidden": true }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading site preview\u2026" })] })), src && (_jsx("div", { className: "mx-auto", style: { width: vp.width * scale, height: frameHeight }, children: _jsx("iframe", { ref: iframeRef, src: src, title: title, onLoad: () => setLoaded(true), className: "border-border bg-card rounded-xl border shadow-sm", style: {
159
- width: vp.width,
160
- height: frameHeight / scale,
161
- transform: `scale(${scale})`,
162
- transformOrigin: 'top left',
163
- } }, reloadKey) }))] }));
164
- }
165
- //# sourceMappingURL=SitePreviewFrame.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SitePreviewFrame.js","sourceRoot":"","sources":["../../../src/views/page-editor/SitePreviewFrame.tsx"],"names":[],"mappings":"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"}
@@ -1,47 +0,0 @@
1
- import type { SaveState } from './EditorTopBar.js';
2
- interface UseSitePreviewOptions {
3
- collection: string;
4
- documentId?: string | null;
5
- slug?: string | null;
6
- /** Explicit public path (`data.path`); wins over slug when set. */
7
- path?: string | null;
8
- /** Collection URL prefix (e.g. `blog`). Empty/null for top-level pages. */
9
- urlPrefix?: string | null;
10
- /**
11
- * Origin of the Next editing canvas (same host as the admin in Astro-split
12
- * and 100% Next builds). Defaults to `window.location.origin` so Site
13
- * preview loads the Next builder, not the Astro public apex.
14
- */
15
- canvasUrl?: string | null;
16
- /**
17
- * @deprecated Prefer {@link canvasUrl}. Kept as a fallback when `canvasUrl`
18
- * is unset and `window` is unavailable. "View on site" still uses the
19
- * public apex via {@link buildPublicPreviewUrl} at the call site.
20
- */
21
- siteUrl?: string | null;
22
- dirty: boolean;
23
- saveState: SaveState;
24
- /** Persist the draft; resolves `false` when the save failed. */
25
- saveDraft: () => Promise<boolean>;
26
- /** Open Site preview on load when the document is saved (has id + slug). */
27
- defaultActive?: boolean;
28
- }
29
- export interface SitePreviewState {
30
- /** Whether the inline iframe preview replaces the editor canvas. */
31
- active: boolean;
32
- /** Canvas draft URL (`/<slug>?preview=<jwt>`); null while the token mints. */
33
- url: string | null;
34
- /** Bumped after each completed save so the iframe reloads the fresh draft. */
35
- reloadKey: number;
36
- toggle: () => void;
37
- }
38
- /**
39
- * Inline branded preview — drives an in-editor iframe that renders the draft
40
- * through the consumer's Next builder route (same `?preview=<token>` contract
41
- * as "View on site"), so editors see actual site CSS without leaving the
42
- * admin. On Astro-split deploys the canvas is the CMS host, not the public
43
- * Astro apex. Saves first when dirty, and reloads the frame after each save.
44
- */
45
- export declare function useSitePreview({ collection, documentId, slug, path, urlPrefix, canvasUrl, siteUrl, dirty, saveState, saveDraft, defaultActive, }: UseSitePreviewOptions): SitePreviewState;
46
- export {};
47
- //# sourceMappingURL=useSitePreview.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useSitePreview.d.ts","sourceRoot":"","sources":["../../../src/views/page-editor/useSitePreview.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAElD,UAAU,qBAAqB;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,gEAAgE;IAChE,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,4EAA4E;IAC5E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAA;IACf,8EAA8E;IAC9E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAWD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,SAAS,EACT,OAAO,EACP,KAAK,EACL,SAAS,EACT,SAAS,EACT,aAAqB,GACtB,EAAE,qBAAqB,GAAG,gBAAgB,CAgF1C"}
@@ -1,96 +0,0 @@
1
- 'use client';
2
- import { useCallback, useEffect, useRef, useState } from 'react';
3
- import { toast } from 'sonner';
4
- import { buildPublicPreviewUrl, createPreviewToken, resolveSiteUrl, } from '../../lib/preview-link.js';
5
- /** Prefer the Next canvas origin (admin host); fall back to siteUrl only offline. */
6
- function resolveCanvasOrigin(canvasUrl, siteUrl) {
7
- if (canvasUrl)
8
- return resolveSiteUrl(canvasUrl);
9
- if (typeof window !== 'undefined' && window.location?.origin) {
10
- return window.location.origin;
11
- }
12
- return resolveSiteUrl(siteUrl);
13
- }
14
- /**
15
- * Inline branded preview — drives an in-editor iframe that renders the draft
16
- * through the consumer's Next builder route (same `?preview=<token>` contract
17
- * as "View on site"), so editors see actual site CSS without leaving the
18
- * admin. On Astro-split deploys the canvas is the CMS host, not the public
19
- * Astro apex. Saves first when dirty, and reloads the frame after each save.
20
- */
21
- export function useSitePreview({ collection, documentId, slug, path, urlPrefix, canvasUrl, siteUrl, dirty, saveState, saveDraft, defaultActive = false, }) {
22
- const [active, setActive] = useState(false);
23
- const [url, setUrl] = useState(null);
24
- const [reloadKey, setReloadKey] = useState(0);
25
- const tokenRef = useRef(null);
26
- const prevSaveState = useRef(saveState);
27
- const autoOpenRef = useRef(false);
28
- const activatePreview = useCallback(async () => {
29
- if (!documentId || !slug) {
30
- toast.error('Save the page before opening the site preview');
31
- return false;
32
- }
33
- setActive(true);
34
- setUrl(null);
35
- try {
36
- if (dirty) {
37
- const ok = await saveDraft();
38
- if (!ok) {
39
- setActive(false);
40
- return false;
41
- }
42
- }
43
- const { token } = await createPreviewToken(collection, documentId);
44
- tokenRef.current = token;
45
- setUrl(buildPublicPreviewUrl({
46
- siteUrl: resolveCanvasOrigin(canvasUrl, siteUrl),
47
- urlPrefix: urlPrefix ?? '',
48
- slug,
49
- path,
50
- token,
51
- }));
52
- return true;
53
- }
54
- catch (err) {
55
- toast.error(err instanceof Error ? err.message : 'Failed to open site preview');
56
- setActive(false);
57
- return false;
58
- }
59
- }, [collection, documentId, slug, path, urlPrefix, canvasUrl, siteUrl, dirty, saveDraft]);
60
- // Opt-in default: open the iframe once a saved document is loaded.
61
- useEffect(() => {
62
- if (!defaultActive || autoOpenRef.current)
63
- return;
64
- if (!documentId || !slug)
65
- return;
66
- autoOpenRef.current = true;
67
- void activatePreview();
68
- }, [defaultActive, documentId, slug, activatePreview]);
69
- // After a save completes while the preview is open, rebuild the URL (the
70
- // slug/path may have changed in settings — the token is bound to the document
71
- // id, not the path) and reload the frame.
72
- useEffect(() => {
73
- if (active && prevSaveState.current === 'saving' && saveState === 'saved') {
74
- if (tokenRef.current && slug) {
75
- setUrl(buildPublicPreviewUrl({
76
- siteUrl: resolveCanvasOrigin(canvasUrl, siteUrl),
77
- urlPrefix: urlPrefix ?? '',
78
- slug,
79
- path,
80
- token: tokenRef.current,
81
- }));
82
- }
83
- setReloadKey((k) => k + 1);
84
- }
85
- prevSaveState.current = saveState;
86
- }, [active, saveState, slug, path, canvasUrl, siteUrl, urlPrefix]);
87
- const toggle = useCallback(() => {
88
- if (active) {
89
- setActive(false);
90
- return;
91
- }
92
- void activatePreview();
93
- }, [active, activatePreview]);
94
- return { active, url, reloadKey, toggle };
95
- }
96
- //# sourceMappingURL=useSitePreview.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useSitePreview.js","sourceRoot":"","sources":["../../../src/views/page-editor/useSitePreview.ts"],"names":[],"mappings":"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"}
@@ -1,14 +0,0 @@
1
- interface PostPreviewProps {
2
- postType: string;
3
- documentId: string;
4
- session?: any;
5
- onNavigate: (path: string) => void;
6
- }
7
- /**
8
- * Chrome-free render of a post using the same header + section renderers as
9
- * the editor canvas, so the preview reflects what ships. Only visible
10
- * sections render (hidden ones are omitted, like the public page).
11
- */
12
- export declare function PostPreview({ postType, documentId, session, onNavigate }: PostPreviewProps): import("react/jsx-runtime").JSX.Element;
13
- export {};
14
- //# sourceMappingURL=PostPreview.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostPreview.d.ts","sourceRoot":"","sources":["../../../src/views/post-editor/PostPreview.tsx"],"names":[],"mappings":"AAcA,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,GAAG,CAAA;IACb,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,gBAAgB,2CAkG1F"}
@@ -1,63 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useState } from 'react';
4
- import { AlertTriangle, Loader2, Pencil, X } from 'lucide-react';
5
- import { fetchPostForEditor, fetchTemplateForEditor, } from '../../lib/post-editor-service.js';
6
- import { defaultPostHeader } from '@prenta/core/page-sections';
7
- import { SectionRenderer } from '../page-editor/sections/index.js';
8
- import { PostHeader } from './PostHeader.js';
9
- /**
10
- * Chrome-free render of a post using the same header + section renderers as
11
- * the editor canvas, so the preview reflects what ships. Only visible
12
- * sections render (hidden ones are omitted, like the public page).
13
- */
14
- export function PostPreview({ postType, documentId, session, onNavigate }) {
15
- const [post, setPost] = useState(null);
16
- const [header, setHeader] = useState(defaultPostHeader());
17
- const [loading, setLoading] = useState(true);
18
- const [error, setError] = useState(null);
19
- useEffect(() => {
20
- let cancelled = false;
21
- setLoading(true);
22
- fetchTemplateForEditor(postType)
23
- .then((t) => !cancelled && setHeader(t.header))
24
- .catch(() => {
25
- /* default header */
26
- });
27
- fetchPostForEditor(postType, documentId)
28
- .then((p) => !cancelled && setPost(p))
29
- .catch((err) => !cancelled && setError(err instanceof Error ? err.message : 'Failed to load'))
30
- .finally(() => !cancelled && setLoading(false));
31
- return () => {
32
- cancelled = true;
33
- };
34
- }, [postType, documentId]);
35
- if (loading) {
36
- return (_jsxs("div", { className: "bg-background flex h-full items-center justify-center", children: [_jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin", "aria-hidden": true }), _jsx("span", { className: "sr-only", children: "Loading preview\u2026" })] }));
37
- }
38
- if (error || !post) {
39
- return (_jsx("div", { className: "bg-background flex h-full items-center justify-center p-6", children: _jsxs("div", { className: "border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4", children: [_jsx(AlertTriangle, { className: "text-destructive shrink-0", size: 20, "aria-hidden": true }), _jsx("p", { className: "text-foreground text-sm font-medium", children: error ?? 'Post not found.' })] }) }));
40
- }
41
- const context = {
42
- title: post.title,
43
- excerpt: post.excerpt,
44
- body: post.body,
45
- featuredImageUrl: post.featuredImage,
46
- publishDate: post.publishDate ?? post.updatedAt ?? null,
47
- category: post.category,
48
- author: { name: session?.user?.name || session?.user?.email },
49
- };
50
- const visible = post.sections.filter((s) => s.visible);
51
- // `window.close()` only works for tabs JS opened (the "View preview" action
52
- // opens this view via window.open). When the user reached it through in-app
53
- // navigation there is no opener, so close() is a silent no-op — fall back to
54
- // the editor after a tick so the Close button always does something.
55
- const handleClose = () => {
56
- window.close();
57
- setTimeout(() => {
58
- onNavigate(`/posts/${postType}/${documentId}/edit`);
59
- }, 120);
60
- };
61
- return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "bg-card border-border flex h-12 shrink-0 items-center justify-between border-b px-4", children: [_jsxs("button", { type: "button", onClick: handleClose, className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm", "aria-label": "Close preview tab", children: [_jsx(X, { className: "h-4 w-4", "aria-hidden": true }), "Close"] }), _jsxs("span", { className: "text-muted-foreground text-xs", children: ["Preview \u00B7 ", post.title || 'Untitled'] }), _jsxs("button", { type: "button", onClick: () => onNavigate(`/posts/${postType}/${documentId}/edit`), className: "bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium", children: [_jsx(Pencil, { className: "h-3.5 w-3.5", "aria-hidden": true }), "Open editor"] })] }), _jsxs("div", { className: "prenta-canvas bg-card flex-1 overflow-auto", children: [_jsx(PostHeader, { config: header, context: context }), visible.map((section) => (_jsx(SectionRenderer, { section: section, context: context }, section.id)))] })] }));
62
- }
63
- //# sourceMappingURL=PostPreview.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostPreview.js","sourceRoot":"","sources":["../../../src/views/post-editor/PostPreview.tsx"],"names":[],"mappings":"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"}
@@ -1,164 +0,0 @@
1
- // @vitest-environment happy-dom
2
- import { beforeEach, describe, expect, it, vi } from 'vitest'
3
- import { fireEvent, render, screen, waitFor } from '@testing-library/react'
4
-
5
- /**
6
- * useSitePreview — drives the inline branded preview (iframe of the public
7
- * site rendering a draft). Pins: token minted + URL built on activation,
8
- * unsaved drafts are persisted first (a failed save cancels activation), and
9
- * the frame reloads (reloadKey bump) after each completed save.
10
- */
11
-
12
- const createPreviewToken = vi.fn(async () => ({
13
- token: 'tok-123',
14
- expiresAt: '2026-06-19T00:00:00Z',
15
- }))
16
-
17
- vi.mock('../../lib/preview-link.js', () => ({
18
- createPreviewToken: (...args: unknown[]) => createPreviewToken(...(args as [])),
19
- buildPublicPreviewUrl: ({ siteUrl, urlPrefix, slug, path, token }: any) => {
20
- const p =
21
- path === '/' || path === '' ? '/' : path || `${urlPrefix ? `/${urlPrefix}` : ''}/${slug}`
22
- return p === '/'
23
- ? `${siteUrl}/?preview=${token}`
24
- : `${siteUrl}${p.startsWith('/') ? p : `/${p}`}?preview=${token}`
25
- },
26
- resolveSiteUrl: (u?: string | null) => u ?? 'https://fallback.test',
27
- }))
28
-
29
- vi.mock('sonner', () => ({
30
- toast: { error: vi.fn(), success: vi.fn() },
31
- }))
32
-
33
- const { useSitePreview } = await import('../../views/page-editor/useSitePreview.js')
34
- const { toast } = await import('sonner')
35
-
36
- import type { SaveState } from '../../views/page-editor/EditorTopBar.js'
37
-
38
- function Harness({
39
- documentId = 'doc-1',
40
- slug = 'about',
41
- dirty = false,
42
- saveState = 'idle' as SaveState,
43
- saveDraft = vi.fn(async () => true),
44
- defaultActive = false,
45
- }: {
46
- documentId?: string | null
47
- slug?: string | null
48
- dirty?: boolean
49
- saveState?: SaveState
50
- saveDraft?: () => Promise<boolean>
51
- defaultActive?: boolean
52
- }) {
53
- const sp = useSitePreview({
54
- collection: 'pages',
55
- documentId,
56
- slug,
57
- urlPrefix: '',
58
- // Canvas defaults to window.location.origin (Next builder / CMS host).
59
- dirty,
60
- saveState,
61
- saveDraft,
62
- defaultActive,
63
- })
64
- return (
65
- <div>
66
- <button type="button" onClick={sp.toggle}>
67
- toggle
68
- </button>
69
- <output data-testid="state">
70
- {JSON.stringify({ active: sp.active, url: sp.url, reloadKey: sp.reloadKey })}
71
- </output>
72
- </div>
73
- )
74
- }
75
-
76
- function state(): { active: boolean; url: string | null; reloadKey: number } {
77
- return JSON.parse(screen.getByTestId('state').textContent ?? '{}')
78
- }
79
-
80
- beforeEach(() => {
81
- createPreviewToken.mockClear()
82
- vi.mocked(toast.error).mockClear()
83
- })
84
-
85
- describe('useSitePreview', () => {
86
- it('mints a token and builds the canvas draft URL on activation', async () => {
87
- render(<Harness />)
88
- fireEvent.click(screen.getByText('toggle'))
89
- expect(state().active).toBe(true)
90
- await waitFor(() => {
91
- expect(state().url).toBe(`${window.location.origin}/about?preview=tok-123`)
92
- })
93
- expect(createPreviewToken).toHaveBeenCalledWith('pages', 'doc-1')
94
- })
95
-
96
- it('saves a dirty draft before minting; a failed save cancels activation', async () => {
97
- const saveDraft = vi.fn(async () => false)
98
- render(<Harness dirty saveDraft={saveDraft} />)
99
- fireEvent.click(screen.getByText('toggle'))
100
- await waitFor(() => expect(saveDraft).toHaveBeenCalled())
101
- await waitFor(() => expect(state().active).toBe(false))
102
- expect(createPreviewToken).not.toHaveBeenCalled()
103
- })
104
-
105
- it('refuses to activate for unsaved documents', () => {
106
- render(<Harness documentId={null} />)
107
- fireEvent.click(screen.getByText('toggle'))
108
- expect(state().active).toBe(false)
109
- expect(toast.error).toHaveBeenCalled()
110
- })
111
-
112
- it('toggles back to the canvas on second click', async () => {
113
- render(<Harness />)
114
- fireEvent.click(screen.getByText('toggle'))
115
- await waitFor(() => expect(state().url).not.toBeNull())
116
- fireEvent.click(screen.getByText('toggle'))
117
- expect(state().active).toBe(false)
118
- })
119
-
120
- it('auto-activates when defaultActive is true', async () => {
121
- render(<Harness defaultActive />)
122
- await waitFor(() => {
123
- expect(state().active).toBe(true)
124
- expect(state().url).toBe(`${window.location.origin}/about?preview=tok-123`)
125
- })
126
- expect(createPreviewToken).toHaveBeenCalledWith('pages', 'doc-1')
127
- })
128
-
129
- it('honors an explicit canvasUrl override', async () => {
130
- function CanvasHarness() {
131
- const sp = useSitePreview({
132
- collection: 'pages',
133
- documentId: 'doc-1',
134
- slug: 'about',
135
- urlPrefix: '',
136
- canvasUrl: 'https://cms.test',
137
- dirty: false,
138
- saveState: 'idle',
139
- saveDraft: vi.fn(async () => true),
140
- })
141
- return (
142
- <button type="button" onClick={sp.toggle}>
143
- {sp.url ?? 'none'}
144
- </button>
145
- )
146
- }
147
- render(<CanvasHarness />)
148
- fireEvent.click(screen.getByRole('button'))
149
- await waitFor(() => {
150
- expect(screen.getByRole('button').textContent).toBe('https://cms.test/about?preview=tok-123')
151
- })
152
- })
153
-
154
- it('bumps reloadKey after a save completes while the preview is open', async () => {
155
- const { rerender } = render(<Harness saveState="idle" />)
156
- fireEvent.click(screen.getByText('toggle'))
157
- await waitFor(() => expect(state().url).not.toBeNull())
158
- expect(state().reloadKey).toBe(0)
159
-
160
- rerender(<Harness saveState="saving" />)
161
- rerender(<Harness saveState="saved" />)
162
- await waitFor(() => expect(state().reloadKey).toBe(1))
163
- })
164
- })