@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.
- package/CHANGELOG.md +6 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +5 -56
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breakpoint-controls.render.test.js +4 -4
- package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -1
- package/dist/__tests__/lib/open-public-site-tab.test.d.ts +2 -0
- package/dist/__tests__/lib/open-public-site-tab.test.d.ts.map +1 -0
- package/dist/__tests__/lib/open-public-site-tab.test.js +64 -0
- package/dist/__tests__/lib/open-public-site-tab.test.js.map +1 -0
- package/dist/__tests__/lib/view-site-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/view-site-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/view-site-chrome.test.js +59 -0
- package/dist/__tests__/lib/view-site-chrome.test.js.map +1 -0
- package/dist/__tests__/router/admin-routes.test.js +4 -0
- package/dist/__tests__/router/admin-routes.test.js.map +1 -1
- package/dist/__tests__/views/canvas-surface-shared.test.js +0 -16
- package/dist/__tests__/views/canvas-surface-shared.test.js.map +1 -1
- package/dist/__tests__/views/editor-top-bar.render.test.js +44 -22
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +30 -9
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/post-section-editor.test.js +23 -0
- package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
- package/dist/components/BreakpointPicker.d.ts +3 -2
- package/dist/components/BreakpointPicker.d.ts.map +1 -1
- package/dist/components/BreakpointPicker.js +5 -3
- package/dist/components/BreakpointPicker.js.map +1 -1
- package/dist/components/CanvasEditTip.js +1 -1
- package/dist/components/CanvasEditTip.js.map +1 -1
- package/dist/lib/editor-preview.d.ts +4 -1
- package/dist/lib/editor-preview.d.ts.map +1 -1
- package/dist/lib/editor-preview.js +4 -1
- package/dist/lib/editor-preview.js.map +1 -1
- package/dist/lib/open-public-site-tab.d.ts +14 -0
- package/dist/lib/open-public-site-tab.d.ts.map +1 -0
- package/dist/lib/open-public-site-tab.js +46 -0
- package/dist/lib/open-public-site-tab.js.map +1 -0
- package/dist/lib/view-site-chrome.d.ts +16 -0
- package/dist/lib/view-site-chrome.d.ts.map +1 -0
- package/dist/lib/view-site-chrome.js +15 -0
- package/dist/lib/view-site-chrome.js.map +1 -0
- package/dist/router/admin-routes.d.ts.map +1 -1
- package/dist/router/admin-routes.js +0 -2
- package/dist/router/admin-routes.js.map +1 -1
- package/dist/views/page-editor/EditorCanvas.d.ts +1 -2
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +2 -2
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +13 -17
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +19 -21
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts +1 -7
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +29 -95
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/StructureNotice.d.ts +2 -5
- package/dist/views/page-editor/StructureNotice.d.ts.map +1 -1
- package/dist/views/page-editor/StructureNotice.js +6 -10
- package/dist/views/page-editor/StructureNotice.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +1 -3
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +3 -3
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts +1 -15
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +30 -88
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/package.json +4 -4
- package/src/AdminRoot.tsx +0 -70
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +6 -4
- package/src/__tests__/lib/open-public-site-tab.test.ts +74 -0
- package/src/__tests__/lib/view-site-chrome.test.ts +65 -0
- package/src/__tests__/router/admin-routes.test.ts +5 -0
- package/src/__tests__/views/canvas-surface-shared.test.ts +0 -19
- package/src/__tests__/views/editor-top-bar.render.test.tsx +85 -22
- package/src/__tests__/views/page-section-editor.test.tsx +42 -9
- package/src/__tests__/views/post-section-editor.test.tsx +35 -0
- package/src/components/BreakpointPicker.tsx +6 -3
- package/src/components/CanvasEditTip.tsx +2 -2
- package/src/lib/editor-preview.ts +4 -1
- package/src/lib/open-public-site-tab.ts +57 -0
- package/src/lib/view-site-chrome.ts +34 -0
- package/src/router/admin-routes.ts +0 -2
- package/src/styles/theme.css +4 -4
- package/src/views/page-editor/EditorCanvas.tsx +1 -6
- package/src/views/page-editor/EditorTopBar.tsx +81 -82
- package/src/views/page-editor/PageSectionEditor.tsx +77 -157
- package/src/views/page-editor/StructureNotice.tsx +7 -42
- package/src/views/post-editor/PostEditorCanvas.tsx +2 -8
- package/src/views/post-editor/PostSectionEditor.tsx +66 -138
- package/dist/__tests__/views/use-site-preview.render.test.d.ts +0 -2
- package/dist/__tests__/views/use-site-preview.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/use-site-preview.render.test.js +0 -120
- package/dist/__tests__/views/use-site-preview.render.test.js.map +0 -1
- package/dist/views/page-editor/PagePreview.d.ts +0 -13
- package/dist/views/page-editor/PagePreview.d.ts.map +0 -1
- package/dist/views/page-editor/PagePreview.js +0 -46
- package/dist/views/page-editor/PagePreview.js.map +0 -1
- package/dist/views/page-editor/SitePreviewFrame.d.ts +0 -43
- package/dist/views/page-editor/SitePreviewFrame.d.ts.map +0 -1
- package/dist/views/page-editor/SitePreviewFrame.js +0 -165
- package/dist/views/page-editor/SitePreviewFrame.js.map +0 -1
- package/dist/views/page-editor/useSitePreview.d.ts +0 -47
- package/dist/views/page-editor/useSitePreview.d.ts.map +0 -1
- package/dist/views/page-editor/useSitePreview.js +0 -96
- package/dist/views/page-editor/useSitePreview.js.map +0 -1
- package/dist/views/post-editor/PostPreview.d.ts +0 -14
- package/dist/views/post-editor/PostPreview.d.ts.map +0 -1
- package/dist/views/post-editor/PostPreview.js +0 -63
- package/dist/views/post-editor/PostPreview.js.map +0 -1
- package/src/__tests__/views/use-site-preview.render.test.tsx +0 -164
- package/src/views/page-editor/PagePreview.tsx +0 -105
- package/src/views/page-editor/SitePreviewFrame.tsx +0 -250
- package/src/views/page-editor/useSitePreview.ts +0 -158
- package/src/views/post-editor/PostPreview.tsx +0 -125
package/src/styles/theme.css
CHANGED
|
@@ -174,8 +174,8 @@
|
|
|
174
174
|
* `views/page-editor/sections/` must use `@pv-*`, never `sm:`/`md:`/`lg:`.
|
|
175
175
|
*
|
|
176
176
|
* Values come from the page-builder design handoff. Any element carrying
|
|
177
|
-
* these variants needs an ancestor with `@container` — EditorCanvas
|
|
178
|
-
*
|
|
177
|
+
* these variants needs an ancestor with `@container` — EditorCanvas
|
|
178
|
+
* and PostEditorCanvas both declare it.
|
|
179
179
|
*/
|
|
180
180
|
@theme {
|
|
181
181
|
--container-pv-sm: 460px; /* below: stats 1-up, buttons full width */
|
|
@@ -281,8 +281,8 @@
|
|
|
281
281
|
/*
|
|
282
282
|
* The canvas is the query container for every previewed section (`@pv-*`
|
|
283
283
|
* variants — see the step scale above). It lives here rather than as an
|
|
284
|
-
* `@container` utility on each host because
|
|
285
|
-
* sections — EditorCanvas
|
|
284
|
+
* `@container` utility on each host because TWO surfaces render these
|
|
285
|
+
* sections — EditorCanvas and PostEditorCanvas — and
|
|
286
286
|
* a host that forgets it degrades silently: with no container ancestor,
|
|
287
287
|
* every `@pv-*` variant simply never matches and the page renders at its
|
|
288
288
|
* narrowest layout regardless of the selected breakpoint. Tying containment
|
|
@@ -18,7 +18,6 @@ interface EditorCanvasProps {
|
|
|
18
18
|
onCanvasElement?: (el: HTMLElement | null) => void
|
|
19
19
|
/** Section types rendered as placeholders — shows the "Structure view" notice. */
|
|
20
20
|
structuralTypes?: string[]
|
|
21
|
-
sitePreviewAvailable?: boolean
|
|
22
21
|
/**
|
|
23
22
|
* Read-only render of a past version. Suppresses selection and the editing
|
|
24
23
|
* affordances, and turns on the changed-section outlines.
|
|
@@ -50,7 +49,6 @@ export function EditorCanvas({
|
|
|
50
49
|
onScaleChange,
|
|
51
50
|
onCanvasElement,
|
|
52
51
|
structuralTypes = [],
|
|
53
|
-
sitePreviewAvailable = false,
|
|
54
52
|
reviewing = false,
|
|
55
53
|
changedSectionIds,
|
|
56
54
|
reviewBar,
|
|
@@ -84,10 +82,7 @@ export function EditorCanvas({
|
|
|
84
82
|
) : (
|
|
85
83
|
<>
|
|
86
84
|
<CanvasEditTip />
|
|
87
|
-
<StructureNotice
|
|
88
|
-
structuralTypes={structuralTypes}
|
|
89
|
-
sitePreviewAvailable={sitePreviewAvailable}
|
|
90
|
-
/>
|
|
85
|
+
<StructureNotice structuralTypes={structuralTypes} />
|
|
91
86
|
</>
|
|
92
87
|
)
|
|
93
88
|
}
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
|
3
4
|
import {
|
|
4
5
|
Calendar,
|
|
5
6
|
Check,
|
|
7
|
+
ChevronDown,
|
|
6
8
|
ExternalLink,
|
|
7
|
-
Eye,
|
|
8
|
-
Globe,
|
|
9
9
|
History,
|
|
10
10
|
Loader2,
|
|
11
11
|
Settings2,
|
|
12
|
+
Share2,
|
|
12
13
|
TriangleAlert,
|
|
13
14
|
} from 'lucide-react'
|
|
14
15
|
import { EditorSeoScoreBadge } from '../../components/seo/EditorSeoScoreBadge.js'
|
|
15
16
|
import { BreakpointPicker } from '../../components/BreakpointPicker.js'
|
|
16
17
|
import { ZoomChip } from '../../components/ZoomChip.js'
|
|
18
|
+
import { adminPortalContainer } from '../../lib/portal-container.js'
|
|
17
19
|
import type { PageStatus } from '../../lib/page-editor-service.js'
|
|
18
20
|
import type { Zoom } from '../../lib/breakpoints.js'
|
|
21
|
+
import { resolveViewSiteChrome, type ViewSiteIntent } from '../../lib/view-site-chrome.js'
|
|
19
22
|
import { type ViewportId } from './viewports.js'
|
|
20
23
|
|
|
21
24
|
export type SaveState = 'idle' | 'saving' | 'saved' | 'error'
|
|
@@ -33,7 +36,7 @@ interface EditorTopBarProps {
|
|
|
33
36
|
/** Shown on hover/focus when publish is blocked by validation (not role). */
|
|
34
37
|
publishDisabledReason?: string
|
|
35
38
|
viewport: ViewportId
|
|
36
|
-
/** Canvas zoom mode; omit to hide the zoom chip
|
|
39
|
+
/** Canvas zoom mode; omit to hide the zoom chip. */
|
|
37
40
|
zoom?: Zoom
|
|
38
41
|
/** Scale the canvas actually measured, 0–1 — displayed by the zoom chip. */
|
|
39
42
|
canvasScale?: number
|
|
@@ -42,13 +45,16 @@ interface EditorTopBarProps {
|
|
|
42
45
|
backLabel?: string
|
|
43
46
|
onViewport: (v: ViewportId) => void
|
|
44
47
|
onBack: () => void
|
|
45
|
-
onPreview: () => void
|
|
46
48
|
/**
|
|
47
|
-
* Open the
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* Open the public site in a new tab. Called with `preview-draft` or
|
|
50
|
+
* `view-live` depending on status and dirty. Omitted for unsaved documents
|
|
51
|
+
* (no id/slug yet) — View site / Share / View live are then hidden.
|
|
50
52
|
*/
|
|
51
|
-
onViewOnSite?: () => void
|
|
53
|
+
onViewOnSite?: (intent: ViewSiteIntent) => void
|
|
54
|
+
/** Copy a shareable draft preview link. Hidden when `shareVisible` is false. */
|
|
55
|
+
onShare?: () => void
|
|
56
|
+
/** When false, View site and Share are disabled with the SEO settings reason. */
|
|
57
|
+
hasPublicSiteUrl?: boolean
|
|
52
58
|
/**
|
|
53
59
|
* Toggle the version history panel. When omitted the History button is
|
|
54
60
|
* hidden — e.g. for unsaved documents that have no versions yet.
|
|
@@ -60,13 +66,6 @@ interface EditorTopBarProps {
|
|
|
60
66
|
* is nothing in the chrome saying which mode the editor is in.
|
|
61
67
|
*/
|
|
62
68
|
historyOpen?: boolean
|
|
63
|
-
/** Whether the inline site preview (iframe of the public site) is showing. */
|
|
64
|
-
sitePreview?: boolean
|
|
65
|
-
/**
|
|
66
|
-
* Toggle the inline site preview — renders the draft through the consumer's
|
|
67
|
-
* real public route inside the editor. Hidden when omitted (unsaved docs).
|
|
68
|
-
*/
|
|
69
|
-
onToggleSitePreview?: () => void
|
|
70
69
|
onOpenSettings: () => void
|
|
71
70
|
/** Open the document SEO panel (meta, social, schema, analysis). */
|
|
72
71
|
onOpenSeo?: () => void
|
|
@@ -90,9 +89,8 @@ interface EditorTopBarProps {
|
|
|
90
89
|
scheduledUnpublishAt?: string | null
|
|
91
90
|
/**
|
|
92
91
|
* Section types the canvas renders as placeholders (custom / unmanaged).
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* labeled button as the primary design preview.
|
|
92
|
+
* Used for structure notice / other chrome. View site is always a labeled
|
|
93
|
+
* button regardless of this list.
|
|
96
94
|
*/
|
|
97
95
|
structuralTypes?: string[]
|
|
98
96
|
}
|
|
@@ -121,12 +119,11 @@ export function EditorTopBar({
|
|
|
121
119
|
backLabel = 'Pages',
|
|
122
120
|
onViewport,
|
|
123
121
|
onBack,
|
|
124
|
-
onPreview,
|
|
125
122
|
onViewOnSite,
|
|
123
|
+
onShare,
|
|
124
|
+
hasPublicSiteUrl,
|
|
126
125
|
onHistory,
|
|
127
126
|
historyOpen = false,
|
|
128
|
-
sitePreview = false,
|
|
129
|
-
onToggleSitePreview,
|
|
130
127
|
onOpenSettings,
|
|
131
128
|
onOpenSeo,
|
|
132
129
|
seoOpen = false,
|
|
@@ -138,19 +135,16 @@ export function EditorTopBar({
|
|
|
138
135
|
scheduleReady = true,
|
|
139
136
|
scheduledAt = null,
|
|
140
137
|
scheduledUnpublishAt = null,
|
|
141
|
-
structuralTypes = [],
|
|
142
138
|
}: EditorTopBarProps) {
|
|
143
139
|
const statusInfo = STATUS_TEXT[status]
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// preview token); otherwise it opens a token-gated preview of the draft.
|
|
153
|
-
const isLive = status === 'PUBLISHED'
|
|
140
|
+
const chrome = onViewOnSite
|
|
141
|
+
? resolveViewSiteChrome({
|
|
142
|
+
status,
|
|
143
|
+
dirty,
|
|
144
|
+
hasPublicSiteUrl: Boolean(hasPublicSiteUrl),
|
|
145
|
+
hasIdentity: true,
|
|
146
|
+
})
|
|
147
|
+
: null
|
|
154
148
|
|
|
155
149
|
const publishBlocked = !canPublish || !publishReady || publishing || saveState === 'saving'
|
|
156
150
|
const publishTitle = publishing
|
|
@@ -281,63 +275,68 @@ export function EditorTopBar({
|
|
|
281
275
|
</button>
|
|
282
276
|
)}
|
|
283
277
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
onClick={onPreview}
|
|
287
|
-
aria-label={previewLabel}
|
|
288
|
-
title={previewLabel}
|
|
289
|
-
className="prenta-touch-target text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg p-2"
|
|
290
|
-
>
|
|
291
|
-
<Eye className="h-4 w-4" aria-hidden />
|
|
292
|
-
</button>
|
|
293
|
-
|
|
294
|
-
{onToggleSitePreview && (
|
|
295
|
-
<button
|
|
296
|
-
type="button"
|
|
297
|
-
onClick={onToggleSitePreview}
|
|
298
|
-
aria-pressed={sitePreview}
|
|
299
|
-
aria-label={sitePreview ? 'Back to editor canvas' : 'Inline site preview'}
|
|
300
|
-
title={
|
|
301
|
-
sitePreview
|
|
302
|
-
? 'Back to editor canvas'
|
|
303
|
-
: 'Inline site preview — render the draft through your public site, in place'
|
|
304
|
-
}
|
|
305
|
-
className={`prenta-touch-target rounded-lg p-2 transition-colors ${
|
|
306
|
-
sitePreview
|
|
307
|
-
? 'bg-primary text-primary-foreground'
|
|
308
|
-
: 'text-muted-foreground hover:text-foreground hover:bg-accent'
|
|
309
|
-
}`}
|
|
310
|
-
>
|
|
311
|
-
<Globe className="h-4 w-4" aria-hidden />
|
|
312
|
-
</button>
|
|
313
|
-
)}
|
|
314
|
-
|
|
315
|
-
{onViewOnSite &&
|
|
316
|
-
(structural ? (
|
|
278
|
+
{chrome && (
|
|
279
|
+
<>
|
|
317
280
|
<button
|
|
318
281
|
type="button"
|
|
319
|
-
onClick={onViewOnSite}
|
|
282
|
+
onClick={() => onViewOnSite?.(chrome.primaryIntent)}
|
|
283
|
+
disabled={!chrome.viewEnabled}
|
|
320
284
|
title={
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
285
|
+
chrome.viewDisabledReason ??
|
|
286
|
+
(chrome.primaryIntent === 'view-live'
|
|
287
|
+
? 'Open the published page in a new tab.'
|
|
288
|
+
: 'Open the draft on your site in a new tab.')
|
|
324
289
|
}
|
|
325
|
-
className="border-border text-foreground hover:bg-accent inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors"
|
|
290
|
+
className="border-border text-foreground hover:bg-accent inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
326
291
|
>
|
|
327
292
|
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
|
|
328
|
-
{
|
|
293
|
+
{chrome.primaryIntent === 'view-live' ? 'View live' : 'View site'}
|
|
329
294
|
</button>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
295
|
+
{chrome.showViewLive && (
|
|
296
|
+
<DropdownMenu.Root>
|
|
297
|
+
<DropdownMenu.Trigger asChild>
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
disabled={!chrome.viewEnabled}
|
|
301
|
+
aria-label="More view options"
|
|
302
|
+
className="prenta-touch-target border-border text-foreground hover:bg-accent inline-flex h-[34px] items-center rounded-lg border px-2 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-50"
|
|
303
|
+
>
|
|
304
|
+
<ChevronDown className="h-3.5 w-3.5" aria-hidden />
|
|
305
|
+
</button>
|
|
306
|
+
</DropdownMenu.Trigger>
|
|
307
|
+
<DropdownMenu.Portal container={adminPortalContainer()}>
|
|
308
|
+
<DropdownMenu.Content
|
|
309
|
+
align="end"
|
|
310
|
+
sideOffset={6}
|
|
311
|
+
className="border-border bg-popover z-[120] rounded-[10px] border p-[5px] shadow-[0_12px_34px_rgba(26,24,39,0.17)]"
|
|
312
|
+
>
|
|
313
|
+
<DropdownMenu.Item
|
|
314
|
+
onSelect={() => onViewOnSite?.('view-live')}
|
|
315
|
+
className="hover:bg-accent focus:bg-accent cursor-pointer rounded-md px-2.5 py-1.5 text-sm outline-none"
|
|
316
|
+
>
|
|
317
|
+
View live
|
|
318
|
+
</DropdownMenu.Item>
|
|
319
|
+
</DropdownMenu.Content>
|
|
320
|
+
</DropdownMenu.Portal>
|
|
321
|
+
</DropdownMenu.Root>
|
|
322
|
+
)}
|
|
323
|
+
{chrome.shareVisible && onShare && (
|
|
324
|
+
<button
|
|
325
|
+
type="button"
|
|
326
|
+
onClick={onShare}
|
|
327
|
+
disabled={!chrome.viewEnabled}
|
|
328
|
+
title={
|
|
329
|
+
chrome.viewDisabledReason ??
|
|
330
|
+
'Copy a link so someone can view this draft without signing in.'
|
|
331
|
+
}
|
|
332
|
+
className="border-border text-foreground hover:bg-accent inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
333
|
+
>
|
|
334
|
+
<Share2 className="h-3.5 w-3.5" aria-hidden />
|
|
335
|
+
Share
|
|
336
|
+
</button>
|
|
337
|
+
)}
|
|
338
|
+
</>
|
|
339
|
+
)}
|
|
341
340
|
|
|
342
341
|
<button
|
|
343
342
|
type="button"
|
|
@@ -34,11 +34,9 @@ import {
|
|
|
34
34
|
type ValidationResult,
|
|
35
35
|
} from '../../lib/page-editor-service.js'
|
|
36
36
|
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
createPreviewToken,
|
|
41
|
-
} from '../../lib/preview-link.js'
|
|
37
|
+
import { SharePreviewLinkDialog } from '../../components/SharePreviewLinkDialog.js'
|
|
38
|
+
import { openPublicSiteTab } from '../../lib/open-public-site-tab.js'
|
|
39
|
+
import type { ViewSiteIntent } from '../../lib/view-site-chrome.js'
|
|
42
40
|
import { usePublicSiteUrl } from '../../hooks/usePublicSiteUrl.js'
|
|
43
41
|
import { DEFAULT_VIEWPORT, type ViewportId } from './viewports.js'
|
|
44
42
|
import type { Zoom } from '../../lib/breakpoints.js'
|
|
@@ -59,12 +57,9 @@ import { EditorTopBar, type SaveState } from './EditorTopBar.js'
|
|
|
59
57
|
import { SectionsList } from './SectionsPanel.js'
|
|
60
58
|
import { EditorCanvas } from './EditorCanvas.js'
|
|
61
59
|
import { ReflowContext } from './reflow-context.js'
|
|
62
|
-
import { SitePreviewFrame } from './SitePreviewFrame.js'
|
|
63
|
-
import { useSitePreview } from './useSitePreview.js'
|
|
64
60
|
import { SectionEditForm } from './SectionEditForm.js'
|
|
65
61
|
import { PageSettingsModal } from './PageSettingsModal.js'
|
|
66
62
|
import { ValidationSummary } from './ValidationSummary.js'
|
|
67
|
-
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
|
|
68
63
|
import { buildEditorContentHtml } from '../../lib/editor-seo-content.js'
|
|
69
64
|
import { companionDimensionPaths, inspectorIdFromFieldPath } from '../../lib/preview-bridge.js'
|
|
70
65
|
|
|
@@ -80,12 +75,6 @@ interface PageSectionEditorProps {
|
|
|
80
75
|
config?: any
|
|
81
76
|
session?: any
|
|
82
77
|
onNavigate: (path: string) => void
|
|
83
|
-
/**
|
|
84
|
-
* Open a preview route in a new browser tab. `prepare` runs before the tab
|
|
85
|
-
* navigates (used to save an unsaved draft first); returning `false` cancels
|
|
86
|
-
* the open. Falls back to in-tab navigation when not provided.
|
|
87
|
-
*/
|
|
88
|
-
onOpenPreview?: (path: string, prepare?: () => Promise<boolean | void> | boolean | void) => void
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
/**
|
|
@@ -137,7 +126,6 @@ export function PageSectionEditor({
|
|
|
137
126
|
config,
|
|
138
127
|
session,
|
|
139
128
|
onNavigate,
|
|
140
|
-
onOpenPreview,
|
|
141
129
|
}: PageSectionEditorProps) {
|
|
142
130
|
const [page, setPage] = useState<EditorPage | null>(null)
|
|
143
131
|
const [loading, setLoading] = useState(true)
|
|
@@ -165,8 +153,7 @@ export function PageSectionEditor({
|
|
|
165
153
|
const [canvasScale, setCanvasScale] = useState(1)
|
|
166
154
|
/**
|
|
167
155
|
* The live canvas surface, for the reflow guard to clone. State rather than a
|
|
168
|
-
* ref because the context value has to update when it mounts
|
|
169
|
-
* unmounts whenever the site-preview frame takes over.
|
|
156
|
+
* ref because the context value has to update when it mounts.
|
|
170
157
|
*/
|
|
171
158
|
const [canvasEl, setCanvasEl] = useState<HTMLElement | null>(null)
|
|
172
159
|
/**
|
|
@@ -234,6 +221,7 @@ export function PageSectionEditor({
|
|
|
234
221
|
const [restoring, setRestoring] = useState(false)
|
|
235
222
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
236
223
|
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
224
|
+
const [shareOpen, setShareOpen] = useState(false)
|
|
237
225
|
const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
|
|
238
226
|
const [validation, setValidation] = useState<ValidationResult | null>(null)
|
|
239
227
|
|
|
@@ -579,22 +567,6 @@ export function PageSectionEditor({
|
|
|
579
567
|
}
|
|
580
568
|
}, [page, documentId, onNavigate, saveDirectives, acceptPage])
|
|
581
569
|
|
|
582
|
-
// Inline branded preview — Next builder canvas (same origin as admin), not
|
|
583
|
-
// the Astro public apex. "View on site" still uses publicSiteUrl below.
|
|
584
|
-
const sitePreview = useSitePreview({
|
|
585
|
-
collection: 'pages',
|
|
586
|
-
documentId: page?.id,
|
|
587
|
-
slug: page?.slug,
|
|
588
|
-
path: page?.path,
|
|
589
|
-
urlPrefix: '',
|
|
590
|
-
dirty,
|
|
591
|
-
saveState,
|
|
592
|
-
saveDraft: handleSaveDraft,
|
|
593
|
-
defaultActive: resolveDefaultSitePreview(config?.admin?.editor, 'pages'),
|
|
594
|
-
})
|
|
595
|
-
|
|
596
|
-
const sitePreviewAvailable = Boolean(page?.id && page?.slug)
|
|
597
|
-
|
|
598
570
|
const requestPublish = useCallback(() => {
|
|
599
571
|
if (!page) return
|
|
600
572
|
const result = validatePage(page, true)
|
|
@@ -797,82 +769,36 @@ export function PageSectionEditor({
|
|
|
797
769
|
[dirty, onNavigate],
|
|
798
770
|
)
|
|
799
771
|
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const path = `/pages/${page.id}/preview`
|
|
806
|
-
// New-tab preview: save first when there are unsaved edits so the preview
|
|
807
|
-
// (which reads the persisted document) reflects what's on screen.
|
|
808
|
-
if (onOpenPreview) {
|
|
809
|
-
onOpenPreview(path, dirty ? handleSaveDraft : undefined)
|
|
810
|
-
} else {
|
|
811
|
-
guardedNavigate(path)
|
|
812
|
-
}
|
|
813
|
-
}, [page, dirty, onOpenPreview, handleSaveDraft, guardedNavigate])
|
|
814
|
-
|
|
815
|
-
// "View on site": render the draft through the consumer's real public route
|
|
816
|
-
// via a signed preview token. The blank tab is opened synchronously inside
|
|
817
|
-
// the click (popup-safe), then pointed at the public URL once any unsaved
|
|
818
|
-
// draft is persisted and the token is minted.
|
|
819
|
-
const handleViewOnSite = useCallback(() => {
|
|
820
|
-
if (!page?.id || !page.slug) {
|
|
821
|
-
toast.error('Save the page before viewing it on the site')
|
|
822
|
-
return
|
|
823
|
-
}
|
|
824
|
-
// Published → jump straight to the LIVE page: no preview token, no forced
|
|
825
|
-
// save. This is the fast "see it live" path. Pending edits stay previewable
|
|
826
|
-
// via the eye (canvas) and globe (inline site preview).
|
|
827
|
-
if (page.status === 'PUBLISHED') {
|
|
828
|
-
window.open(
|
|
829
|
-
buildPublicUrl({
|
|
830
|
-
siteUrl: publicSiteUrl,
|
|
831
|
-
urlPrefix: '',
|
|
832
|
-
slug: page.slug,
|
|
833
|
-
path: page.path,
|
|
834
|
-
}),
|
|
835
|
-
'_blank',
|
|
836
|
-
'noopener',
|
|
837
|
-
)
|
|
838
|
-
return
|
|
839
|
-
}
|
|
840
|
-
// Not yet public (draft / scheduled / archived) → token-gated preview of
|
|
841
|
-
// the current draft through the real front-end.
|
|
842
|
-
const tab = window.open('about:blank', '_blank')
|
|
843
|
-
if (!tab) {
|
|
844
|
-
toast.error('Enable pop-ups for this site to open the preview in a new tab.')
|
|
845
|
-
return
|
|
846
|
-
}
|
|
847
|
-
void (async () => {
|
|
848
|
-
try {
|
|
849
|
-
if (dirty) {
|
|
850
|
-
const ok = await handleSaveDraft()
|
|
851
|
-
if (!ok) {
|
|
852
|
-
tab.close()
|
|
853
|
-
return
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
const { token } = await createPreviewToken('pages', page.id!)
|
|
857
|
-
const target = buildPublicPreviewUrl({
|
|
858
|
-
siteUrl: publicSiteUrl,
|
|
859
|
-
urlPrefix: '',
|
|
860
|
-
slug: page.slug,
|
|
861
|
-
path: page.path,
|
|
862
|
-
token,
|
|
863
|
-
})
|
|
864
|
-
try {
|
|
865
|
-
tab.opener = null
|
|
866
|
-
} catch {
|
|
867
|
-
/* same-origin guard */
|
|
868
|
-
}
|
|
869
|
-
tab.location.replace(target)
|
|
870
|
-
} catch (err) {
|
|
871
|
-
tab.close()
|
|
872
|
-
toast.error(err instanceof Error ? err.message : 'Failed to open site preview')
|
|
772
|
+
const handleViewOnSite = useCallback(
|
|
773
|
+
(intent: ViewSiteIntent) => {
|
|
774
|
+
if (!page?.id || !page.slug) {
|
|
775
|
+
toast.error('Save the page before viewing it on the site')
|
|
776
|
+
return
|
|
873
777
|
}
|
|
874
|
-
|
|
875
|
-
|
|
778
|
+
void openPublicSiteTab({
|
|
779
|
+
intent,
|
|
780
|
+
siteUrl: publicSiteUrl,
|
|
781
|
+
collection: 'pages',
|
|
782
|
+
documentId: page.id,
|
|
783
|
+
slug: page.slug,
|
|
784
|
+
path: page.path,
|
|
785
|
+
dirty,
|
|
786
|
+
saveDraft: handleSaveDraft,
|
|
787
|
+
}).catch((err) => {
|
|
788
|
+
toast.error(err instanceof Error ? err.message : 'Failed to open the site')
|
|
789
|
+
})
|
|
790
|
+
},
|
|
791
|
+
[page, dirty, handleSaveDraft, publicSiteUrl],
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
const handleShare = useCallback(async () => {
|
|
795
|
+
if (!page?.id) return
|
|
796
|
+
if (dirty) {
|
|
797
|
+
const ok = await handleSaveDraft()
|
|
798
|
+
if (!ok) return
|
|
799
|
+
}
|
|
800
|
+
setShareOpen(true)
|
|
801
|
+
}, [page, dirty, handleSaveDraft])
|
|
876
802
|
|
|
877
803
|
// ─── Render states ───────────────────────────────────────────────────
|
|
878
804
|
if (loading) {
|
|
@@ -945,16 +871,13 @@ export function PageSectionEditor({
|
|
|
945
871
|
publishDisabledReason={publishDisabledReason}
|
|
946
872
|
viewport={viewport}
|
|
947
873
|
onViewport={setViewport}
|
|
948
|
-
{
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
{}
|
|
952
|
-
: { zoom, canvasScale, onZoom: setZoom })}
|
|
874
|
+
zoom={zoom}
|
|
875
|
+
canvasScale={canvasScale}
|
|
876
|
+
onZoom={setZoom}
|
|
953
877
|
onBack={() => guardedNavigate('/pages')}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
onToggleSitePreview={page.id && page.slug ? sitePreview.toggle : undefined}
|
|
878
|
+
onViewOnSite={page.id && page.slug ? handleViewOnSite : undefined}
|
|
879
|
+
onShare={page.id ? handleShare : undefined}
|
|
880
|
+
hasPublicSiteUrl={Boolean(publicSiteUrl)}
|
|
958
881
|
historyOpen={historyOpen}
|
|
959
882
|
onHistory={page.id ? () => (historyOpen ? closeHistory() : openHistory()) : undefined}
|
|
960
883
|
onOpenSettings={() => setSettingsOpen(true)}
|
|
@@ -1105,46 +1028,30 @@ export function PageSectionEditor({
|
|
|
1105
1028
|
</EditorRail>
|
|
1106
1029
|
|
|
1107
1030
|
<div className="flex-1 overflow-hidden">
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
reviewing={reviewing}
|
|
1133
|
-
changedSectionIds={reviewChangedIds}
|
|
1134
|
-
reviewBar={
|
|
1135
|
-
reviewVersion ? (
|
|
1136
|
-
<ReviewBar
|
|
1137
|
-
version={reviewVersion}
|
|
1138
|
-
changedCount={reviewChangedIds.size}
|
|
1139
|
-
onRestore={() => void handleRestoreVersion(reviewVersion)}
|
|
1140
|
-
onBackToCurrent={() => setVersionIndex(0)}
|
|
1141
|
-
restoring={restoring}
|
|
1142
|
-
canRestore={canRestoreVersion}
|
|
1143
|
-
/>
|
|
1144
|
-
) : null
|
|
1145
|
-
}
|
|
1146
|
-
/>
|
|
1147
|
-
)}
|
|
1031
|
+
<EditorCanvas
|
|
1032
|
+
sections={canvasSections}
|
|
1033
|
+
selectedId={selectedId}
|
|
1034
|
+
breakpoint={viewport}
|
|
1035
|
+
zoom={zoom}
|
|
1036
|
+
onScaleChange={setCanvasScale}
|
|
1037
|
+
onCanvasElement={setCanvasEl}
|
|
1038
|
+
onSelect={handleSelectSection}
|
|
1039
|
+
structuralTypes={structuralTypes}
|
|
1040
|
+
reviewing={reviewing}
|
|
1041
|
+
changedSectionIds={reviewChangedIds}
|
|
1042
|
+
reviewBar={
|
|
1043
|
+
reviewVersion ? (
|
|
1044
|
+
<ReviewBar
|
|
1045
|
+
version={reviewVersion}
|
|
1046
|
+
changedCount={reviewChangedIds.size}
|
|
1047
|
+
onRestore={() => void handleRestoreVersion(reviewVersion)}
|
|
1048
|
+
onBackToCurrent={() => setVersionIndex(0)}
|
|
1049
|
+
restoring={restoring}
|
|
1050
|
+
canRestore={canRestoreVersion}
|
|
1051
|
+
/>
|
|
1052
|
+
) : null
|
|
1053
|
+
}
|
|
1054
|
+
/>
|
|
1148
1055
|
</div>
|
|
1149
1056
|
</div>
|
|
1150
1057
|
</ReflowContext.Provider>
|
|
@@ -1179,6 +1086,19 @@ export function PageSectionEditor({
|
|
|
1179
1086
|
/>
|
|
1180
1087
|
)}
|
|
1181
1088
|
|
|
1089
|
+
{page.id && (
|
|
1090
|
+
<SharePreviewLinkDialog
|
|
1091
|
+
collectionSlug="pages"
|
|
1092
|
+
documentId={page.id}
|
|
1093
|
+
documentSlug={page.slug}
|
|
1094
|
+
documentPath={page.path}
|
|
1095
|
+
siteUrl={publicSiteUrl}
|
|
1096
|
+
urlPrefix=""
|
|
1097
|
+
open={shareOpen}
|
|
1098
|
+
onClose={() => setShareOpen(false)}
|
|
1099
|
+
/>
|
|
1100
|
+
)}
|
|
1101
|
+
|
|
1182
1102
|
<ConfirmDialog
|
|
1183
1103
|
open={leaveTarget !== null}
|
|
1184
1104
|
onClose={() => setLeaveTarget(null)}
|
|
@@ -4,48 +4,12 @@ import { Puzzle } from 'lucide-react'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Banner shown above the canvas surface when the document contains section
|
|
7
|
-
* types the canvas can only render as placeholders (custom / unmanaged
|
|
8
|
-
* types), or when Site preview is available for accurate styling.
|
|
7
|
+
* types the canvas can only render as placeholders (custom / unmanaged types).
|
|
9
8
|
*/
|
|
10
|
-
export function StructureNotice({
|
|
11
|
-
structuralTypes
|
|
12
|
-
sitePreviewAvailable = false,
|
|
13
|
-
}: {
|
|
14
|
-
structuralTypes: string[]
|
|
15
|
-
/** When true, nudge editors toward the Site preview toggle for WYSIWYG. */
|
|
16
|
-
sitePreviewAvailable?: boolean
|
|
17
|
-
}) {
|
|
18
|
-
if (structuralTypes.length === 0 && !sitePreviewAvailable) return null
|
|
19
|
-
|
|
20
|
-
if (structuralTypes.length > 0) {
|
|
21
|
-
const count = structuralTypes.length
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
role="note"
|
|
25
|
-
className="border-border bg-card text-muted-foreground mx-auto mb-4 flex w-fit max-w-full items-center gap-2 rounded-lg border px-3 py-1.5 text-xs"
|
|
26
|
-
>
|
|
27
|
-
<Puzzle className="h-3.5 w-3.5 shrink-0" aria-hidden />
|
|
28
|
-
<span>
|
|
29
|
-
<span className="text-foreground font-medium">Structure view</span> — {count} custom
|
|
30
|
-
section type{count > 1 ? 's' : ''} shown as placeholders.
|
|
31
|
-
{sitePreviewAvailable ? (
|
|
32
|
-
<>
|
|
33
|
-
{' '}
|
|
34
|
-
Use <span className="text-foreground font-medium">Site preview</span> for accurate
|
|
35
|
-
styling.
|
|
36
|
-
</>
|
|
37
|
-
) : (
|
|
38
|
-
<>
|
|
39
|
-
{' '}
|
|
40
|
-
Use <span className="text-foreground font-medium">View on site</span> for the real
|
|
41
|
-
design.
|
|
42
|
-
</>
|
|
43
|
-
)}
|
|
44
|
-
</span>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
9
|
+
export function StructureNotice({ structuralTypes }: { structuralTypes: string[] }) {
|
|
10
|
+
if (structuralTypes.length === 0) return null
|
|
48
11
|
|
|
12
|
+
const count = structuralTypes.length
|
|
49
13
|
return (
|
|
50
14
|
<div
|
|
51
15
|
role="note"
|
|
@@ -53,8 +17,9 @@ export function StructureNotice({
|
|
|
53
17
|
>
|
|
54
18
|
<Puzzle className="h-3.5 w-3.5 shrink-0" aria-hidden />
|
|
55
19
|
<span>
|
|
56
|
-
<span className="text-foreground font-medium">Structure
|
|
57
|
-
|
|
20
|
+
<span className="text-foreground font-medium">Structure view</span> — {count} custom section
|
|
21
|
+
type{count > 1 ? 's' : ''} shown as placeholders. Use{' '}
|
|
22
|
+
<span className="text-foreground font-medium">View site</span> for the real design.
|
|
58
23
|
</span>
|
|
59
24
|
</div>
|
|
60
25
|
)
|