@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
|
@@ -27,18 +27,14 @@ import {
|
|
|
27
27
|
savePageDraft,
|
|
28
28
|
toggleSectionVisibility,
|
|
29
29
|
updateSectionContent,
|
|
30
|
-
updateSectionContentAtPath,
|
|
31
30
|
updateSectionMeta,
|
|
32
31
|
validatePage,
|
|
33
32
|
type EditorPage,
|
|
34
33
|
type ValidationResult,
|
|
35
34
|
} from '../../lib/page-editor-service.js'
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
buildPublicUrl,
|
|
40
|
-
createPreviewToken,
|
|
41
|
-
} from '../../lib/preview-link.js'
|
|
35
|
+
import { SharePreviewLinkDialog } from '../../components/SharePreviewLinkDialog.js'
|
|
36
|
+
import { openPublicSiteTab } from '../../lib/open-public-site-tab.js'
|
|
37
|
+
import type { ViewSiteIntent } from '../../lib/view-site-chrome.js'
|
|
42
38
|
import { usePublicSiteUrl } from '../../hooks/usePublicSiteUrl.js'
|
|
43
39
|
import { DEFAULT_VIEWPORT, type ViewportId } from './viewports.js'
|
|
44
40
|
import type { Zoom } from '../../lib/breakpoints.js'
|
|
@@ -59,14 +55,11 @@ import { EditorTopBar, type SaveState } from './EditorTopBar.js'
|
|
|
59
55
|
import { SectionsList } from './SectionsPanel.js'
|
|
60
56
|
import { EditorCanvas } from './EditorCanvas.js'
|
|
61
57
|
import { ReflowContext } from './reflow-context.js'
|
|
62
|
-
import { SitePreviewFrame } from './SitePreviewFrame.js'
|
|
63
|
-
import { useSitePreview } from './useSitePreview.js'
|
|
64
58
|
import { SectionEditForm } from './SectionEditForm.js'
|
|
65
59
|
import { PageSettingsModal } from './PageSettingsModal.js'
|
|
66
60
|
import { ValidationSummary } from './ValidationSummary.js'
|
|
67
|
-
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
|
|
68
61
|
import { buildEditorContentHtml } from '../../lib/editor-seo-content.js'
|
|
69
|
-
import {
|
|
62
|
+
import { inspectorIdFromFieldPath } from '../../lib/preview-bridge.js'
|
|
70
63
|
|
|
71
64
|
interface PageSectionEditorProps {
|
|
72
65
|
/** undefined when creating a new page (`/pages/new`) */
|
|
@@ -80,12 +73,6 @@ interface PageSectionEditorProps {
|
|
|
80
73
|
config?: any
|
|
81
74
|
session?: any
|
|
82
75
|
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
76
|
}
|
|
90
77
|
|
|
91
78
|
/**
|
|
@@ -137,7 +124,6 @@ export function PageSectionEditor({
|
|
|
137
124
|
config,
|
|
138
125
|
session,
|
|
139
126
|
onNavigate,
|
|
140
|
-
onOpenPreview,
|
|
141
127
|
}: PageSectionEditorProps) {
|
|
142
128
|
const [page, setPage] = useState<EditorPage | null>(null)
|
|
143
129
|
const [loading, setLoading] = useState(true)
|
|
@@ -146,13 +132,6 @@ export function PageSectionEditor({
|
|
|
146
132
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
147
133
|
/** Canvas field click — focused after the inspector mounts for `selectedId`. */
|
|
148
134
|
const [pendingFocusPath, setPendingFocusPath] = useState<string | null>(null)
|
|
149
|
-
/** Bump to skip the next `prenta-preview:sections` echo after a canvas text edit. */
|
|
150
|
-
const [suppressSectionsEcho, setSuppressSectionsEcho] = useState(0)
|
|
151
|
-
/** Canvas image Replace — opens MediaPickerModal for this field path. */
|
|
152
|
-
const [canvasMediaTarget, setCanvasMediaTarget] = useState<{
|
|
153
|
-
sectionId: string
|
|
154
|
-
fieldPath: string
|
|
155
|
-
} | null>(null)
|
|
156
135
|
const [viewport, setViewport] = useState<ViewportId>(DEFAULT_VIEWPORT)
|
|
157
136
|
const [zoom, setZoom] = useState<Zoom>('fit')
|
|
158
137
|
/**
|
|
@@ -165,8 +144,7 @@ export function PageSectionEditor({
|
|
|
165
144
|
const [canvasScale, setCanvasScale] = useState(1)
|
|
166
145
|
/**
|
|
167
146
|
* 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.
|
|
147
|
+
* ref because the context value has to update when it mounts.
|
|
170
148
|
*/
|
|
171
149
|
const [canvasEl, setCanvasEl] = useState<HTMLElement | null>(null)
|
|
172
150
|
/**
|
|
@@ -234,12 +212,13 @@ export function PageSectionEditor({
|
|
|
234
212
|
const [restoring, setRestoring] = useState(false)
|
|
235
213
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
236
214
|
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
215
|
+
const [shareOpen, setShareOpen] = useState(false)
|
|
237
216
|
const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
|
|
238
217
|
const [validation, setValidation] = useState<ValidationResult | null>(null)
|
|
239
218
|
|
|
240
219
|
const { canEdit, canPublish, canCreateRedirect } = useMemo(() => deriveCan(session), [session])
|
|
241
220
|
const customRenderers = useAdminSectionRenderers()
|
|
242
|
-
const { siteUrl: publicSiteUrl } = usePublicSiteUrl(config?.seo?.siteUrl)
|
|
221
|
+
const { siteUrl: publicSiteUrl, matchesAdminOrigin } = usePublicSiteUrl(config?.seo?.siteUrl)
|
|
243
222
|
|
|
244
223
|
// A misconfigured `pages` collection (missing the fields the editor
|
|
245
224
|
// writes) would silently drop content on save — detect it up front.
|
|
@@ -579,22 +558,6 @@ export function PageSectionEditor({
|
|
|
579
558
|
}
|
|
580
559
|
}, [page, documentId, onNavigate, saveDirectives, acceptPage])
|
|
581
560
|
|
|
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
561
|
const requestPublish = useCallback(() => {
|
|
599
562
|
if (!page) return
|
|
600
563
|
const result = validatePage(page, true)
|
|
@@ -744,39 +707,14 @@ export function PageSectionEditor({
|
|
|
744
707
|
// rail has no close affordance at all — the chevron and the row both
|
|
745
708
|
// re-select what is already selected and nothing happens.
|
|
746
709
|
//
|
|
747
|
-
// Except when a fieldPath came with it:
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
// user is trying to edit.
|
|
710
|
+
// Except when a fieldPath came with it: a canvas field click must open
|
|
711
|
+
// and focus that input. Toggling would close the form on the second
|
|
712
|
+
// click of a field the user is trying to inspect.
|
|
751
713
|
if (fieldPath) return id
|
|
752
714
|
return current === id ? null : id
|
|
753
715
|
})
|
|
754
716
|
}, [])
|
|
755
717
|
|
|
756
|
-
// `effectiveCanEdit`, not `canEdit`: the site preview is a second editing
|
|
757
|
-
// surface, so a role check alone let it write through both the version-review
|
|
758
|
-
// lock and the builder-owned lock the rest of the editor honours.
|
|
759
|
-
const handleCanvasEdit = useCallback(
|
|
760
|
-
(edit: { sectionId: string; fieldPath: string; value: string }) => {
|
|
761
|
-
if (!effectiveCanEdit) return
|
|
762
|
-
setSuppressSectionsEcho((n) => n + 1)
|
|
763
|
-
mutateSections((s) =>
|
|
764
|
-
updateSectionContentAtPath(s, edit.sectionId, edit.fieldPath, edit.value),
|
|
765
|
-
)
|
|
766
|
-
setSelectedId(edit.sectionId)
|
|
767
|
-
},
|
|
768
|
-
[effectiveCanEdit, mutateSections],
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
const handleCanvasRequestMedia = useCallback(
|
|
772
|
-
(request: { sectionId: string; fieldPath: string }) => {
|
|
773
|
-
if (!effectiveCanEdit) return
|
|
774
|
-
setSelectedId(request.sectionId)
|
|
775
|
-
setCanvasMediaTarget(request)
|
|
776
|
-
},
|
|
777
|
-
[effectiveCanEdit],
|
|
778
|
-
)
|
|
779
|
-
|
|
780
718
|
// After a canvas field click, focus the matching inspector input once painted.
|
|
781
719
|
useEffect(() => {
|
|
782
720
|
if (!pendingFocusPath || !selectedId) return
|
|
@@ -797,82 +735,36 @@ export function PageSectionEditor({
|
|
|
797
735
|
[dirty, onNavigate],
|
|
798
736
|
)
|
|
799
737
|
|
|
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')
|
|
738
|
+
const handleViewOnSite = useCallback(
|
|
739
|
+
(intent: ViewSiteIntent) => {
|
|
740
|
+
if (!page?.id || !page.slug) {
|
|
741
|
+
toast.error('Save the page before viewing it on the site')
|
|
742
|
+
return
|
|
873
743
|
}
|
|
874
|
-
|
|
875
|
-
|
|
744
|
+
void openPublicSiteTab({
|
|
745
|
+
intent,
|
|
746
|
+
siteUrl: publicSiteUrl,
|
|
747
|
+
collection: 'pages',
|
|
748
|
+
documentId: page.id,
|
|
749
|
+
slug: page.slug,
|
|
750
|
+
path: page.path,
|
|
751
|
+
dirty,
|
|
752
|
+
saveDraft: handleSaveDraft,
|
|
753
|
+
}).catch((err) => {
|
|
754
|
+
toast.error(err instanceof Error ? err.message : 'Failed to open the site')
|
|
755
|
+
})
|
|
756
|
+
},
|
|
757
|
+
[page, dirty, handleSaveDraft, publicSiteUrl],
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
const handleShare = useCallback(async () => {
|
|
761
|
+
if (!page?.id) return
|
|
762
|
+
if (dirty) {
|
|
763
|
+
const ok = await handleSaveDraft()
|
|
764
|
+
if (!ok) return
|
|
765
|
+
}
|
|
766
|
+
setShareOpen(true)
|
|
767
|
+
}, [page, dirty, handleSaveDraft])
|
|
876
768
|
|
|
877
769
|
// ─── Render states ───────────────────────────────────────────────────
|
|
878
770
|
if (loading) {
|
|
@@ -945,16 +837,14 @@ export function PageSectionEditor({
|
|
|
945
837
|
publishDisabledReason={publishDisabledReason}
|
|
946
838
|
viewport={viewport}
|
|
947
839
|
onViewport={setViewport}
|
|
948
|
-
{
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
{}
|
|
952
|
-
: { zoom, canvasScale, onZoom: setZoom })}
|
|
840
|
+
zoom={zoom}
|
|
841
|
+
canvasScale={canvasScale}
|
|
842
|
+
onZoom={setZoom}
|
|
953
843
|
onBack={() => guardedNavigate('/pages')}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
844
|
+
onViewOnSite={page.id && page.slug ? handleViewOnSite : undefined}
|
|
845
|
+
onShare={page.id ? handleShare : undefined}
|
|
846
|
+
hasPublicSiteUrl={Boolean(publicSiteUrl)}
|
|
847
|
+
matchesAdminOrigin={matchesAdminOrigin}
|
|
958
848
|
historyOpen={historyOpen}
|
|
959
849
|
onHistory={page.id ? () => (historyOpen ? closeHistory() : openHistory()) : undefined}
|
|
960
850
|
onOpenSettings={() => setSettingsOpen(true)}
|
|
@@ -1105,46 +995,30 @@ export function PageSectionEditor({
|
|
|
1105
995
|
</EditorRail>
|
|
1106
996
|
|
|
1107
997
|
<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
|
-
)}
|
|
998
|
+
<EditorCanvas
|
|
999
|
+
sections={canvasSections}
|
|
1000
|
+
selectedId={selectedId}
|
|
1001
|
+
breakpoint={viewport}
|
|
1002
|
+
zoom={zoom}
|
|
1003
|
+
onScaleChange={setCanvasScale}
|
|
1004
|
+
onCanvasElement={setCanvasEl}
|
|
1005
|
+
onSelect={handleSelectSection}
|
|
1006
|
+
structuralTypes={structuralTypes}
|
|
1007
|
+
reviewing={reviewing}
|
|
1008
|
+
changedSectionIds={reviewChangedIds}
|
|
1009
|
+
reviewBar={
|
|
1010
|
+
reviewVersion ? (
|
|
1011
|
+
<ReviewBar
|
|
1012
|
+
version={reviewVersion}
|
|
1013
|
+
changedCount={reviewChangedIds.size}
|
|
1014
|
+
onRestore={() => void handleRestoreVersion(reviewVersion)}
|
|
1015
|
+
onBackToCurrent={() => setVersionIndex(0)}
|
|
1016
|
+
restoring={restoring}
|
|
1017
|
+
canRestore={canRestoreVersion}
|
|
1018
|
+
/>
|
|
1019
|
+
) : null
|
|
1020
|
+
}
|
|
1021
|
+
/>
|
|
1148
1022
|
</div>
|
|
1149
1023
|
</div>
|
|
1150
1024
|
</ReflowContext.Provider>
|
|
@@ -1179,6 +1053,19 @@ export function PageSectionEditor({
|
|
|
1179
1053
|
/>
|
|
1180
1054
|
)}
|
|
1181
1055
|
|
|
1056
|
+
{page.id && (
|
|
1057
|
+
<SharePreviewLinkDialog
|
|
1058
|
+
collectionSlug="pages"
|
|
1059
|
+
documentId={page.id}
|
|
1060
|
+
documentSlug={page.slug}
|
|
1061
|
+
documentPath={page.path}
|
|
1062
|
+
siteUrl={publicSiteUrl}
|
|
1063
|
+
urlPrefix=""
|
|
1064
|
+
open={shareOpen}
|
|
1065
|
+
onClose={() => setShareOpen(false)}
|
|
1066
|
+
/>
|
|
1067
|
+
)}
|
|
1068
|
+
|
|
1182
1069
|
<ConfirmDialog
|
|
1183
1070
|
open={leaveTarget !== null}
|
|
1184
1071
|
onClose={() => setLeaveTarget(null)}
|
|
@@ -1194,30 +1081,6 @@ export function PageSectionEditor({
|
|
|
1194
1081
|
cancelLabel="Stay"
|
|
1195
1082
|
destructive
|
|
1196
1083
|
/>
|
|
1197
|
-
|
|
1198
|
-
<MediaPickerModal
|
|
1199
|
-
open={canvasMediaTarget !== null}
|
|
1200
|
-
onClose={() => setCanvasMediaTarget(null)}
|
|
1201
|
-
accept="image/*"
|
|
1202
|
-
onSelectItem={(item) => {
|
|
1203
|
-
if (!canvasMediaTarget) return
|
|
1204
|
-
const { sectionId, fieldPath } = canvasMediaTarget
|
|
1205
|
-
const { widthPath, heightPath } = companionDimensionPaths(fieldPath)
|
|
1206
|
-
// Media Replace must echo sections so the canvas image updates.
|
|
1207
|
-
mutateSections((s) => {
|
|
1208
|
-
let next = updateSectionContentAtPath(s, sectionId, fieldPath, item.url)
|
|
1209
|
-
if (typeof item.width === 'number') {
|
|
1210
|
-
next = updateSectionContentAtPath(next, sectionId, widthPath, item.width)
|
|
1211
|
-
}
|
|
1212
|
-
if (typeof item.height === 'number') {
|
|
1213
|
-
next = updateSectionContentAtPath(next, sectionId, heightPath, item.height)
|
|
1214
|
-
}
|
|
1215
|
-
return next
|
|
1216
|
-
})
|
|
1217
|
-
setCanvasMediaTarget(null)
|
|
1218
|
-
}}
|
|
1219
|
-
onSelect={() => setCanvasMediaTarget(null)}
|
|
1220
|
-
/>
|
|
1221
1084
|
</ErrorBoundary>
|
|
1222
1085
|
)
|
|
1223
1086
|
}
|
|
@@ -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
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Puzzle } from 'lucide-react'
|
|
4
|
+
import { fieldPathFromEventTarget } from '../../../lib/preview-bridge.js'
|
|
4
5
|
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
5
6
|
import { ErrorBoundary } from '../../../components/ErrorBoundary.js'
|
|
6
7
|
import { getSectionType } from '../section-types.js'
|
|
@@ -163,7 +164,7 @@ export interface SectionRendererProps {
|
|
|
163
164
|
* renders hidden sections muted instead of removing them. */
|
|
164
165
|
editable?: boolean
|
|
165
166
|
selected?: boolean
|
|
166
|
-
onSelect?: (id: string) => void
|
|
167
|
+
onSelect?: (id: string, fieldPath?: string) => void
|
|
167
168
|
/** Lets the canvas register the DOM node for scroll-to-section. */
|
|
168
169
|
registerRef?: (id: string, el: HTMLElement | null) => void
|
|
169
170
|
/** Live post data for post-oriented section renderers (pages omit this). */
|
|
@@ -234,7 +235,9 @@ export function SectionRenderer({
|
|
|
234
235
|
? 'outline-transparent hover:outline hover:outline-[color-mix(in_oklab,var(--acc)_40%,transparent)]'
|
|
235
236
|
: 'outline-transparent'
|
|
236
237
|
}`}
|
|
237
|
-
onClick={
|
|
238
|
+
onClick={
|
|
239
|
+
interactive ? (e) => onSelect?.(section.id, fieldPathFromEventTarget(e.target)) : undefined
|
|
240
|
+
}
|
|
238
241
|
// Selection is a convenience in the editor; the section list is the
|
|
239
242
|
// primary, fully-accessible control surface, so this stays a region.
|
|
240
243
|
aria-label={
|
|
@@ -17,15 +17,13 @@ interface PostEditorCanvasProps {
|
|
|
17
17
|
selectedId: string | null
|
|
18
18
|
viewport: ViewportId
|
|
19
19
|
zoom?: Zoom
|
|
20
|
-
onSelect: (id: string) => void
|
|
20
|
+
onSelect: (id: string, fieldPath?: string) => void
|
|
21
21
|
onScaleChange?: (scale: number) => void
|
|
22
22
|
onCanvasElement?: (el: HTMLElement | null) => void
|
|
23
23
|
/** Add-section CTA on the empty canvas. Omitted by design-owned editors. */
|
|
24
24
|
onAddSection?: () => void
|
|
25
25
|
/** Section types rendered as placeholders — shows the "Structure view" notice. */
|
|
26
26
|
structuralTypes?: string[]
|
|
27
|
-
/** Site preview toggle is available — show WYSIWYG nudge on the canvas. */
|
|
28
|
-
sitePreviewAvailable?: boolean
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -53,7 +51,6 @@ export function PostEditorCanvas({
|
|
|
53
51
|
onCanvasElement,
|
|
54
52
|
onAddSection,
|
|
55
53
|
structuralTypes = [],
|
|
56
|
-
sitePreviewAvailable = false,
|
|
57
54
|
}: PostEditorCanvasProps) {
|
|
58
55
|
const registerRef = useSectionRefs(selectedId)
|
|
59
56
|
|
|
@@ -67,10 +64,7 @@ export function PostEditorCanvas({
|
|
|
67
64
|
chrome={
|
|
68
65
|
<>
|
|
69
66
|
{sections.length > 0 && <CanvasEditTip />}
|
|
70
|
-
<StructureNotice
|
|
71
|
-
structuralTypes={structuralTypes}
|
|
72
|
-
sitePreviewAvailable={sitePreviewAvailable}
|
|
73
|
-
/>
|
|
67
|
+
<StructureNotice structuralTypes={structuralTypes} />
|
|
74
68
|
</>
|
|
75
69
|
}
|
|
76
70
|
>
|
|
@@ -109,7 +103,7 @@ function EmptyBody({ onAddSection }: { onAddSection?: () => void }) {
|
|
|
109
103
|
</span>
|
|
110
104
|
<p className="text-muted-foreground text-sm">
|
|
111
105
|
{onAddSection
|
|
112
|
-
? 'The header is set by the template. Add body sections to build out this post. Hover sections for a light outline, click to open its editor in the section list
|
|
106
|
+
? 'The header is set by the template. Add body sections to build out this post. Hover sections for a light outline, click to open its editor in the section list.'
|
|
113
107
|
: 'This post’s template renders no body sections. Layout is design-owned — sections come from the template in code. Pick a different template in Post settings, or edit the template itself.'}
|
|
114
108
|
</p>
|
|
115
109
|
{onAddSection && (
|