@prenta/admin 1.0.0 → 1.2.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 +12 -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/canvas-edit-chrome.test.js +1 -22
- package/dist/__tests__/lib/canvas-edit-chrome.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/preview-bridge.test.js +19 -79
- package/dist/__tests__/lib/preview-bridge.test.js.map +1 -1
- 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 +69 -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-review-mode.render.test.js +11 -1
- package/dist/__tests__/views/canvas-review-mode.render.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 +52 -22
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +53 -18
- 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/canvas-edit-chrome.d.ts +0 -17
- package/dist/lib/canvas-edit-chrome.d.ts.map +1 -1
- package/dist/lib/canvas-edit-chrome.js +0 -62
- package/dist/lib/canvas-edit-chrome.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/preview-bridge.d.ts +7 -74
- package/dist/lib/preview-bridge.d.ts.map +1 -1
- package/dist/lib/preview-bridge.js +12 -73
- package/dist/lib/preview-bridge.js.map +1 -1
- package/dist/lib/view-site-chrome.d.ts +19 -0
- package/dist/lib/view-site-chrome.d.ts.map +1 -0
- package/dist/lib/view-site-chrome.js +24 -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 +2 -3
- 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 +15 -17
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +20 -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 +36 -141
- 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/page-editor/sections/SectionRenderer.d.ts +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +2 -1
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -4
- 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 +36 -128
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/SeoSettingsTab.js +2 -1
- package/dist/views/settings/SeoSettingsTab.js.map +1 -1
- package/dist/views/settings/SiteInformationCard.d.ts.map +1 -1
- package/dist/views/settings/SiteInformationCard.js +3 -1
- package/dist/views/settings/SiteInformationCard.js.map +1 -1
- package/package.json +6 -6
- package/src/AdminRoot.tsx +0 -70
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +6 -4
- package/src/__tests__/lib/canvas-edit-chrome.test.ts +0 -27
- package/src/__tests__/lib/open-public-site-tab.test.ts +74 -0
- package/src/__tests__/lib/preview-bridge.test.ts +21 -110
- package/src/__tests__/lib/view-site-chrome.test.ts +80 -0
- package/src/__tests__/router/admin-routes.test.ts +5 -0
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +14 -1
- package/src/__tests__/views/canvas-surface-shared.test.ts +0 -19
- package/src/__tests__/views/editor-top-bar.render.test.tsx +104 -22
- package/src/__tests__/views/page-section-editor.test.tsx +65 -22
- 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/canvas-edit-chrome.ts +0 -64
- package/src/lib/editor-preview.ts +4 -1
- package/src/lib/open-public-site-tab.ts +57 -0
- package/src/lib/preview-bridge.ts +11 -157
- package/src/lib/view-site-chrome.ts +46 -0
- package/src/router/admin-routes.ts +0 -2
- package/src/styles/theme.css +4 -4
- package/src/views/page-editor/EditorCanvas.tsx +2 -7
- package/src/views/page-editor/EditorTopBar.tsx +85 -82
- package/src/views/page-editor/PageSectionEditor.tsx +83 -220
- package/src/views/page-editor/StructureNotice.tsx +7 -42
- package/src/views/page-editor/sections/SectionRenderer.tsx +5 -2
- package/src/views/post-editor/PostEditorCanvas.tsx +3 -9
- package/src/views/post-editor/PostSectionEditor.tsx +71 -195
- package/src/views/settings/SeoSettingsTab.tsx +21 -0
- package/src/views/settings/SiteInformationCard.tsx +7 -0
- 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
|
@@ -1,140 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Next editing canvas (iframe). Namespaced with colons so it does not collide
|
|
4
|
-
* with the legacy `prenta-preview-update` DocumentEdit bus.
|
|
2
|
+
* Field-path helpers shared by the editor canvas and the inspector.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* The old `prenta-preview:*` postMessage protocol is gone — View site opens a
|
|
5
|
+
* normal tab and does not talk back. `data-cms-field` on first-party section
|
|
6
|
+
* renderers still feeds the reflow guard and canvas field-focus.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export const PREVIEW_BRIDGE_VERSION = 1 as const
|
|
10
|
-
|
|
11
|
-
/** Debounce for live section pushes while typing in the inspector. */
|
|
12
|
-
export const PREVIEW_SECTIONS_DEBOUNCE_MS = 120
|
|
13
|
-
|
|
14
|
-
export type PreviewBridgeMessageType =
|
|
15
|
-
| 'prenta-preview:ready'
|
|
16
|
-
| 'prenta-preview:select'
|
|
17
|
-
| 'prenta-preview:highlight'
|
|
18
|
-
| 'prenta-preview:sections'
|
|
19
|
-
| 'prenta-preview:mode'
|
|
20
|
-
| 'prenta-preview:edit'
|
|
21
|
-
| 'prenta-preview:request-media'
|
|
22
|
-
|
|
23
|
-
export interface PreviewBridgeEnvelope {
|
|
24
|
-
type: PreviewBridgeMessageType
|
|
25
|
-
v: typeof PREVIEW_BRIDGE_VERSION
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface PreviewReadyMessage extends PreviewBridgeEnvelope {
|
|
29
|
-
type: 'prenta-preview:ready'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface PreviewSelectMessage extends PreviewBridgeEnvelope {
|
|
33
|
-
type: 'prenta-preview:select'
|
|
34
|
-
sectionId: string
|
|
35
|
-
fieldPath?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface PreviewHighlightMessage extends PreviewBridgeEnvelope {
|
|
39
|
-
type: 'prenta-preview:highlight'
|
|
40
|
-
sectionId: string | null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Opaque JSON object on the wire. Uses `object` (not `Record<string, unknown>`)
|
|
45
|
-
* so concrete editor interfaces like `SectionSettings` remain assignable.
|
|
46
|
-
*/
|
|
47
|
-
export type PreviewObjectPayload = object
|
|
48
|
-
|
|
49
|
-
/** Wire shape for live canvas updates — matches `PublicSection` / editor sections. */
|
|
50
|
-
export interface PreviewSectionPayload {
|
|
51
|
-
id: string
|
|
52
|
-
sectionType: string
|
|
53
|
-
name?: string
|
|
54
|
-
visible?: boolean
|
|
55
|
-
content?: PreviewObjectPayload
|
|
56
|
-
settings?: PreviewObjectPayload
|
|
57
|
-
unmanaged?: boolean
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface PreviewSectionsMessage extends PreviewBridgeEnvelope {
|
|
61
|
-
type: 'prenta-preview:sections'
|
|
62
|
-
sections: PreviewSectionPayload[]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Admin → canvas: enable/disable inline editing affordances. */
|
|
66
|
-
export interface PreviewModeMessage extends PreviewBridgeEnvelope {
|
|
67
|
-
type: 'prenta-preview:mode'
|
|
68
|
-
editing: boolean
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Canvas → admin: text field edit from contenteditable. */
|
|
72
|
-
export interface PreviewEditMessage extends PreviewBridgeEnvelope {
|
|
73
|
-
type: 'prenta-preview:edit'
|
|
74
|
-
sectionId: string
|
|
75
|
-
fieldPath: string
|
|
76
|
-
value: string
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Canvas → admin: open media picker for an image field. */
|
|
80
|
-
export interface PreviewRequestMediaMessage extends PreviewBridgeEnvelope {
|
|
81
|
-
type: 'prenta-preview:request-media'
|
|
82
|
-
sectionId: string
|
|
83
|
-
fieldPath: string
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type PreviewBridgeMessage =
|
|
87
|
-
| PreviewReadyMessage
|
|
88
|
-
| PreviewSelectMessage
|
|
89
|
-
| PreviewHighlightMessage
|
|
90
|
-
| PreviewSectionsMessage
|
|
91
|
-
| PreviewModeMessage
|
|
92
|
-
| PreviewEditMessage
|
|
93
|
-
| PreviewRequestMediaMessage
|
|
94
|
-
|
|
95
|
-
export function isPreviewBridgeMessage(data: unknown): data is PreviewBridgeMessage {
|
|
96
|
-
if (!data || typeof data !== 'object') return false
|
|
97
|
-
const msg = data as Record<string, unknown>
|
|
98
|
-
if (msg.v !== PREVIEW_BRIDGE_VERSION) return false
|
|
99
|
-
if (typeof msg.type !== 'string' || !msg.type.startsWith('prenta-preview:')) return false
|
|
100
|
-
return true
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function isPreviewEditMessage(data: unknown): data is PreviewEditMessage {
|
|
104
|
-
if (!isPreviewBridgeMessage(data) || data.type !== 'prenta-preview:edit') return false
|
|
105
|
-
return (
|
|
106
|
-
typeof data.sectionId === 'string' &&
|
|
107
|
-
data.sectionId.length > 0 &&
|
|
108
|
-
typeof data.fieldPath === 'string' &&
|
|
109
|
-
data.fieldPath.length > 0 &&
|
|
110
|
-
typeof data.value === 'string'
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function isPreviewRequestMediaMessage(data: unknown): data is PreviewRequestMediaMessage {
|
|
115
|
-
if (!isPreviewBridgeMessage(data) || data.type !== 'prenta-preview:request-media') return false
|
|
116
|
-
return (
|
|
117
|
-
typeof data.sectionId === 'string' &&
|
|
118
|
-
data.sectionId.length > 0 &&
|
|
119
|
-
typeof data.fieldPath === 'string' &&
|
|
120
|
-
data.fieldPath.length > 0
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function isPreviewModeMessage(data: unknown): data is PreviewModeMessage {
|
|
125
|
-
if (!isPreviewBridgeMessage(data) || data.type !== 'prenta-preview:mode') return false
|
|
126
|
-
return typeof data.editing === 'boolean'
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function canvasOriginFromSrc(src: string | null | undefined): string | null {
|
|
130
|
-
if (!src) return null
|
|
131
|
-
try {
|
|
132
|
-
return new URL(src, typeof window !== 'undefined' ? window.location.origin : undefined).origin
|
|
133
|
-
} catch {
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
9
|
/**
|
|
139
10
|
* Map a dotted content field path (`lead`, `stats.0.num`) to the inspector
|
|
140
11
|
* input id (`insp-field-lead`, `insp-field-stats-0-num`). Same string shape
|
|
@@ -181,29 +52,12 @@ export function companionDimensionPaths(fieldPath: string): {
|
|
|
181
52
|
}
|
|
182
53
|
|
|
183
54
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
55
|
+
* Read `data-cms-field` from a canvas click target (or its ancestor).
|
|
56
|
+
* Used so a field click opens the accordion on the matching inspector input.
|
|
186
57
|
*/
|
|
187
|
-
export function
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
// Gallery rows use `images.N.url` (not link CTAs).
|
|
193
|
-
return leaf === 'url' && fieldPath.includes('images.')
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Strip editor chrome to the public wire shape before posting to the canvas. */
|
|
197
|
-
export function toPreviewSectionPayload(
|
|
198
|
-
section: PreviewSectionPayload & { internalLabel?: string },
|
|
199
|
-
): PreviewSectionPayload {
|
|
200
|
-
return {
|
|
201
|
-
id: section.id,
|
|
202
|
-
sectionType: section.sectionType,
|
|
203
|
-
name: section.name,
|
|
204
|
-
visible: section.visible,
|
|
205
|
-
content: section.content ?? {},
|
|
206
|
-
settings: section.settings ?? {},
|
|
207
|
-
unmanaged: section.unmanaged,
|
|
208
|
-
}
|
|
58
|
+
export function fieldPathFromEventTarget(target: EventTarget | null): string | undefined {
|
|
59
|
+
if (!(target instanceof Element)) return undefined
|
|
60
|
+
const field = target.closest('[data-cms-field]')
|
|
61
|
+
const path = field?.getAttribute('data-cms-field')?.trim()
|
|
62
|
+
return path || undefined
|
|
209
63
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PageStatus } from './page-editor-service.js'
|
|
2
|
+
|
|
3
|
+
export type ViewSiteIntent = 'preview-draft' | 'view-live'
|
|
4
|
+
|
|
5
|
+
export const NO_PUBLIC_SITE_URL_REASON =
|
|
6
|
+
'Set the public site URL in Settings → SEO to open the live design.'
|
|
7
|
+
|
|
8
|
+
export const ADMIN_ORIGIN_SITE_URL_REASON =
|
|
9
|
+
'This origin is the admin, not the public site. Set the public site URL in Settings → SEO.'
|
|
10
|
+
|
|
11
|
+
export function resolveViewSiteChrome(input: {
|
|
12
|
+
status: PageStatus
|
|
13
|
+
dirty: boolean
|
|
14
|
+
hasPublicSiteUrl: boolean
|
|
15
|
+
hasIdentity: boolean
|
|
16
|
+
/** True when the configured site URL is the CMS host (Astro-split misconfig). */
|
|
17
|
+
matchesAdminOrigin?: boolean
|
|
18
|
+
}): {
|
|
19
|
+
viewEnabled: boolean
|
|
20
|
+
viewDisabledReason?: string
|
|
21
|
+
primaryIntent: ViewSiteIntent
|
|
22
|
+
showViewLive: boolean
|
|
23
|
+
shareVisible: boolean
|
|
24
|
+
} {
|
|
25
|
+
const unpublished = input.status !== 'PUBLISHED' || input.dirty
|
|
26
|
+
const primaryIntent: ViewSiteIntent =
|
|
27
|
+
input.status === 'PUBLISHED' && !input.dirty ? 'view-live' : 'preview-draft'
|
|
28
|
+
const shareVisible = input.hasIdentity && unpublished
|
|
29
|
+
const usablePublicUrl = input.hasPublicSiteUrl && !input.matchesAdminOrigin
|
|
30
|
+
const viewEnabled = input.hasIdentity && usablePublicUrl
|
|
31
|
+
|
|
32
|
+
let viewDisabledReason: string | undefined
|
|
33
|
+
if (input.hasIdentity && !input.hasPublicSiteUrl) {
|
|
34
|
+
viewDisabledReason = NO_PUBLIC_SITE_URL_REASON
|
|
35
|
+
} else if (input.hasIdentity && input.matchesAdminOrigin) {
|
|
36
|
+
viewDisabledReason = ADMIN_ORIGIN_SITE_URL_REASON
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
viewEnabled,
|
|
41
|
+
viewDisabledReason,
|
|
42
|
+
primaryIntent,
|
|
43
|
+
showViewLive: input.hasIdentity && input.status === 'PUBLISHED' && input.dirty,
|
|
44
|
+
shareVisible,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -40,14 +40,12 @@ export const ADMIN_STATIC_ROUTE_PATTERNS: readonly string[] = [
|
|
|
40
40
|
'/pages',
|
|
41
41
|
'/pages/:id',
|
|
42
42
|
'/pages/:id/edit',
|
|
43
|
-
'/pages/:id/preview',
|
|
44
43
|
'/pages/new',
|
|
45
44
|
'/pages/tags',
|
|
46
45
|
'/pages/tags/:id/edit',
|
|
47
46
|
'/pages/tags/new',
|
|
48
47
|
'/posts',
|
|
49
48
|
'/posts/:type/:id/edit',
|
|
50
|
-
'/posts/:type/:id/preview',
|
|
51
49
|
'/posts/:type/new',
|
|
52
50
|
'/posts/new',
|
|
53
51
|
'/posts/types',
|
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
|
|
@@ -13,12 +13,11 @@ interface EditorCanvasProps {
|
|
|
13
13
|
selectedId: string | null
|
|
14
14
|
breakpoint: BreakpointId
|
|
15
15
|
zoom?: Zoom
|
|
16
|
-
onSelect: (id: string) => void
|
|
16
|
+
onSelect: (id: string, fieldPath?: string) => void
|
|
17
17
|
onScaleChange?: (scale: number) => void
|
|
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,18 @@ 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
|
|
58
|
+
/** When true, View site and Share stay disabled — the URL is the CMS host. */
|
|
59
|
+
matchesAdminOrigin?: boolean
|
|
52
60
|
/**
|
|
53
61
|
* Toggle the version history panel. When omitted the History button is
|
|
54
62
|
* hidden — e.g. for unsaved documents that have no versions yet.
|
|
@@ -60,13 +68,6 @@ interface EditorTopBarProps {
|
|
|
60
68
|
* is nothing in the chrome saying which mode the editor is in.
|
|
61
69
|
*/
|
|
62
70
|
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
71
|
onOpenSettings: () => void
|
|
71
72
|
/** Open the document SEO panel (meta, social, schema, analysis). */
|
|
72
73
|
onOpenSeo?: () => void
|
|
@@ -90,9 +91,8 @@ interface EditorTopBarProps {
|
|
|
90
91
|
scheduledUnpublishAt?: string | null
|
|
91
92
|
/**
|
|
92
93
|
* Section types the canvas renders as placeholders (custom / unmanaged).
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* labeled button as the primary design preview.
|
|
94
|
+
* Used for structure notice / other chrome. View site is always a labeled
|
|
95
|
+
* button regardless of this list.
|
|
96
96
|
*/
|
|
97
97
|
structuralTypes?: string[]
|
|
98
98
|
}
|
|
@@ -121,12 +121,12 @@ export function EditorTopBar({
|
|
|
121
121
|
backLabel = 'Pages',
|
|
122
122
|
onViewport,
|
|
123
123
|
onBack,
|
|
124
|
-
onPreview,
|
|
125
124
|
onViewOnSite,
|
|
125
|
+
onShare,
|
|
126
|
+
hasPublicSiteUrl,
|
|
127
|
+
matchesAdminOrigin,
|
|
126
128
|
onHistory,
|
|
127
129
|
historyOpen = false,
|
|
128
|
-
sitePreview = false,
|
|
129
|
-
onToggleSitePreview,
|
|
130
130
|
onOpenSettings,
|
|
131
131
|
onOpenSeo,
|
|
132
132
|
seoOpen = false,
|
|
@@ -138,19 +138,17 @@ export function EditorTopBar({
|
|
|
138
138
|
scheduleReady = true,
|
|
139
139
|
scheduledAt = null,
|
|
140
140
|
scheduledUnpublishAt = null,
|
|
141
|
-
structuralTypes = [],
|
|
142
141
|
}: EditorTopBarProps) {
|
|
143
142
|
const statusInfo = STATUS_TEXT[status]
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const isLive = status === 'PUBLISHED'
|
|
143
|
+
const chrome = onViewOnSite
|
|
144
|
+
? resolveViewSiteChrome({
|
|
145
|
+
status,
|
|
146
|
+
dirty,
|
|
147
|
+
hasPublicSiteUrl: Boolean(hasPublicSiteUrl),
|
|
148
|
+
matchesAdminOrigin: Boolean(matchesAdminOrigin),
|
|
149
|
+
hasIdentity: true,
|
|
150
|
+
})
|
|
151
|
+
: null
|
|
154
152
|
|
|
155
153
|
const publishBlocked = !canPublish || !publishReady || publishing || saveState === 'saving'
|
|
156
154
|
const publishTitle = publishing
|
|
@@ -281,63 +279,68 @@ export function EditorTopBar({
|
|
|
281
279
|
</button>
|
|
282
280
|
)}
|
|
283
281
|
|
|
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 ? (
|
|
282
|
+
{chrome && (
|
|
283
|
+
<>
|
|
317
284
|
<button
|
|
318
285
|
type="button"
|
|
319
|
-
onClick={onViewOnSite}
|
|
286
|
+
onClick={() => onViewOnSite?.(chrome.primaryIntent)}
|
|
287
|
+
disabled={!chrome.viewEnabled}
|
|
320
288
|
title={
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
289
|
+
chrome.viewDisabledReason ??
|
|
290
|
+
(chrome.primaryIntent === 'view-live'
|
|
291
|
+
? 'Open the published page in a new tab.'
|
|
292
|
+
: 'Open the draft on your site in a new tab.')
|
|
324
293
|
}
|
|
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"
|
|
294
|
+
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
295
|
>
|
|
327
296
|
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
|
|
328
|
-
{
|
|
297
|
+
{chrome.primaryIntent === 'view-live' ? 'View live' : 'View site'}
|
|
329
298
|
</button>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
299
|
+
{chrome.showViewLive && (
|
|
300
|
+
<DropdownMenu.Root>
|
|
301
|
+
<DropdownMenu.Trigger asChild>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
disabled={!chrome.viewEnabled}
|
|
305
|
+
aria-label="More view options"
|
|
306
|
+
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"
|
|
307
|
+
>
|
|
308
|
+
<ChevronDown className="h-3.5 w-3.5" aria-hidden />
|
|
309
|
+
</button>
|
|
310
|
+
</DropdownMenu.Trigger>
|
|
311
|
+
<DropdownMenu.Portal container={adminPortalContainer()}>
|
|
312
|
+
<DropdownMenu.Content
|
|
313
|
+
align="end"
|
|
314
|
+
sideOffset={6}
|
|
315
|
+
className="border-border bg-popover z-[120] rounded-[10px] border p-[5px] shadow-[0_12px_34px_rgba(26,24,39,0.17)]"
|
|
316
|
+
>
|
|
317
|
+
<DropdownMenu.Item
|
|
318
|
+
onSelect={() => onViewOnSite?.('view-live')}
|
|
319
|
+
className="hover:bg-accent focus:bg-accent cursor-pointer rounded-md px-2.5 py-1.5 text-sm outline-none"
|
|
320
|
+
>
|
|
321
|
+
View live
|
|
322
|
+
</DropdownMenu.Item>
|
|
323
|
+
</DropdownMenu.Content>
|
|
324
|
+
</DropdownMenu.Portal>
|
|
325
|
+
</DropdownMenu.Root>
|
|
326
|
+
)}
|
|
327
|
+
{chrome.shareVisible && onShare && (
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
onClick={onShare}
|
|
331
|
+
disabled={!chrome.viewEnabled}
|
|
332
|
+
title={
|
|
333
|
+
chrome.viewDisabledReason ??
|
|
334
|
+
'Copy a link so someone can view this draft without signing in.'
|
|
335
|
+
}
|
|
336
|
+
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"
|
|
337
|
+
>
|
|
338
|
+
<Share2 className="h-3.5 w-3.5" aria-hidden />
|
|
339
|
+
Share
|
|
340
|
+
</button>
|
|
341
|
+
)}
|
|
342
|
+
</>
|
|
343
|
+
)}
|
|
341
344
|
|
|
342
345
|
<button
|
|
343
346
|
type="button"
|