@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.
Files changed (149) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +5 -56
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/breakpoint-controls.render.test.js +4 -4
  6. package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -1
  7. package/dist/__tests__/lib/canvas-edit-chrome.test.js +1 -22
  8. package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -1
  9. package/dist/__tests__/lib/open-public-site-tab.test.d.ts +2 -0
  10. package/dist/__tests__/lib/open-public-site-tab.test.d.ts.map +1 -0
  11. package/dist/__tests__/lib/open-public-site-tab.test.js +64 -0
  12. package/dist/__tests__/lib/open-public-site-tab.test.js.map +1 -0
  13. package/dist/__tests__/lib/preview-bridge.test.js +19 -79
  14. package/dist/__tests__/lib/preview-bridge.test.js.map +1 -1
  15. package/dist/__tests__/lib/view-site-chrome.test.d.ts +2 -0
  16. package/dist/__tests__/lib/view-site-chrome.test.d.ts.map +1 -0
  17. package/dist/__tests__/lib/view-site-chrome.test.js +69 -0
  18. package/dist/__tests__/lib/view-site-chrome.test.js.map +1 -0
  19. package/dist/__tests__/router/admin-routes.test.js +4 -0
  20. package/dist/__tests__/router/admin-routes.test.js.map +1 -1
  21. package/dist/__tests__/views/canvas-review-mode.render.test.js +11 -1
  22. package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -1
  23. package/dist/__tests__/views/canvas-surface-shared.test.js +0 -16
  24. package/dist/__tests__/views/canvas-surface-shared.test.js.map +1 -1
  25. package/dist/__tests__/views/editor-top-bar.render.test.js +52 -22
  26. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  27. package/dist/__tests__/views/page-section-editor.test.js +53 -18
  28. package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
  29. package/dist/__tests__/views/post-section-editor.test.js +23 -0
  30. package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
  31. package/dist/components/BreakpointPicker.d.ts +3 -2
  32. package/dist/components/BreakpointPicker.d.ts.map +1 -1
  33. package/dist/components/BreakpointPicker.js +5 -3
  34. package/dist/components/BreakpointPicker.js.map +1 -1
  35. package/dist/components/CanvasEditTip.js +1 -1
  36. package/dist/components/CanvasEditTip.js.map +1 -1
  37. package/dist/lib/canvas-edit-chrome.d.ts +0 -17
  38. package/dist/lib/canvas-edit-chrome.d.ts.map +1 -1
  39. package/dist/lib/canvas-edit-chrome.js +0 -62
  40. package/dist/lib/canvas-edit-chrome.js.map +1 -1
  41. package/dist/lib/editor-preview.d.ts +4 -1
  42. package/dist/lib/editor-preview.d.ts.map +1 -1
  43. package/dist/lib/editor-preview.js +4 -1
  44. package/dist/lib/editor-preview.js.map +1 -1
  45. package/dist/lib/open-public-site-tab.d.ts +14 -0
  46. package/dist/lib/open-public-site-tab.d.ts.map +1 -0
  47. package/dist/lib/open-public-site-tab.js +46 -0
  48. package/dist/lib/open-public-site-tab.js.map +1 -0
  49. package/dist/lib/preview-bridge.d.ts +7 -74
  50. package/dist/lib/preview-bridge.d.ts.map +1 -1
  51. package/dist/lib/preview-bridge.js +12 -73
  52. package/dist/lib/preview-bridge.js.map +1 -1
  53. package/dist/lib/view-site-chrome.d.ts +19 -0
  54. package/dist/lib/view-site-chrome.d.ts.map +1 -0
  55. package/dist/lib/view-site-chrome.js +24 -0
  56. package/dist/lib/view-site-chrome.js.map +1 -0
  57. package/dist/router/admin-routes.d.ts.map +1 -1
  58. package/dist/router/admin-routes.js +0 -2
  59. package/dist/router/admin-routes.js.map +1 -1
  60. package/dist/views/page-editor/EditorCanvas.d.ts +2 -3
  61. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  62. package/dist/views/page-editor/EditorCanvas.js +2 -2
  63. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  64. package/dist/views/page-editor/EditorTopBar.d.ts +15 -17
  65. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  66. package/dist/views/page-editor/EditorTopBar.js +20 -21
  67. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  68. package/dist/views/page-editor/PageSectionEditor.d.ts +1 -7
  69. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  70. package/dist/views/page-editor/PageSectionEditor.js +36 -141
  71. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  72. package/dist/views/page-editor/StructureNotice.d.ts +2 -5
  73. package/dist/views/page-editor/StructureNotice.d.ts.map +1 -1
  74. package/dist/views/page-editor/StructureNotice.js +6 -10
  75. package/dist/views/page-editor/StructureNotice.js.map +1 -1
  76. package/dist/views/page-editor/sections/SectionRenderer.d.ts +1 -1
  77. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  78. package/dist/views/page-editor/sections/SectionRenderer.js +2 -1
  79. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  80. package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -4
  81. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  82. package/dist/views/post-editor/PostEditorCanvas.js +3 -3
  83. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  84. package/dist/views/post-editor/PostSectionEditor.d.ts +1 -15
  85. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  86. package/dist/views/post-editor/PostSectionEditor.js +36 -128
  87. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  88. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  89. package/dist/views/settings/SeoSettingsTab.js +2 -1
  90. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  91. package/dist/views/settings/SiteInformationCard.d.ts.map +1 -1
  92. package/dist/views/settings/SiteInformationCard.js +3 -1
  93. package/dist/views/settings/SiteInformationCard.js.map +1 -1
  94. package/package.json +6 -6
  95. package/src/AdminRoot.tsx +0 -70
  96. package/src/__tests__/components/breakpoint-controls.render.test.tsx +6 -4
  97. package/src/__tests__/lib/canvas-edit-chrome.test.ts +0 -27
  98. package/src/__tests__/lib/open-public-site-tab.test.ts +74 -0
  99. package/src/__tests__/lib/preview-bridge.test.ts +21 -110
  100. package/src/__tests__/lib/view-site-chrome.test.ts +80 -0
  101. package/src/__tests__/router/admin-routes.test.ts +5 -0
  102. package/src/__tests__/views/canvas-review-mode.render.test.tsx +14 -1
  103. package/src/__tests__/views/canvas-surface-shared.test.ts +0 -19
  104. package/src/__tests__/views/editor-top-bar.render.test.tsx +104 -22
  105. package/src/__tests__/views/page-section-editor.test.tsx +65 -22
  106. package/src/__tests__/views/post-section-editor.test.tsx +35 -0
  107. package/src/components/BreakpointPicker.tsx +6 -3
  108. package/src/components/CanvasEditTip.tsx +2 -2
  109. package/src/lib/canvas-edit-chrome.ts +0 -64
  110. package/src/lib/editor-preview.ts +4 -1
  111. package/src/lib/open-public-site-tab.ts +57 -0
  112. package/src/lib/preview-bridge.ts +11 -157
  113. package/src/lib/view-site-chrome.ts +46 -0
  114. package/src/router/admin-routes.ts +0 -2
  115. package/src/styles/theme.css +4 -4
  116. package/src/views/page-editor/EditorCanvas.tsx +2 -7
  117. package/src/views/page-editor/EditorTopBar.tsx +85 -82
  118. package/src/views/page-editor/PageSectionEditor.tsx +83 -220
  119. package/src/views/page-editor/StructureNotice.tsx +7 -42
  120. package/src/views/page-editor/sections/SectionRenderer.tsx +5 -2
  121. package/src/views/post-editor/PostEditorCanvas.tsx +3 -9
  122. package/src/views/post-editor/PostSectionEditor.tsx +71 -195
  123. package/src/views/settings/SeoSettingsTab.tsx +21 -0
  124. package/src/views/settings/SiteInformationCard.tsx +7 -0
  125. package/dist/__tests__/views/use-site-preview.render.test.d.ts +0 -2
  126. package/dist/__tests__/views/use-site-preview.render.test.d.ts.map +0 -1
  127. package/dist/__tests__/views/use-site-preview.render.test.js +0 -120
  128. package/dist/__tests__/views/use-site-preview.render.test.js.map +0 -1
  129. package/dist/views/page-editor/PagePreview.d.ts +0 -13
  130. package/dist/views/page-editor/PagePreview.d.ts.map +0 -1
  131. package/dist/views/page-editor/PagePreview.js +0 -46
  132. package/dist/views/page-editor/PagePreview.js.map +0 -1
  133. package/dist/views/page-editor/SitePreviewFrame.d.ts +0 -43
  134. package/dist/views/page-editor/SitePreviewFrame.d.ts.map +0 -1
  135. package/dist/views/page-editor/SitePreviewFrame.js +0 -165
  136. package/dist/views/page-editor/SitePreviewFrame.js.map +0 -1
  137. package/dist/views/page-editor/useSitePreview.d.ts +0 -47
  138. package/dist/views/page-editor/useSitePreview.d.ts.map +0 -1
  139. package/dist/views/page-editor/useSitePreview.js +0 -96
  140. package/dist/views/page-editor/useSitePreview.js.map +0 -1
  141. package/dist/views/post-editor/PostPreview.d.ts +0 -14
  142. package/dist/views/post-editor/PostPreview.d.ts.map +0 -1
  143. package/dist/views/post-editor/PostPreview.js +0 -63
  144. package/dist/views/post-editor/PostPreview.js.map +0 -1
  145. package/src/__tests__/views/use-site-preview.render.test.tsx +0 -164
  146. package/src/views/page-editor/PagePreview.tsx +0 -105
  147. package/src/views/page-editor/SitePreviewFrame.tsx +0 -250
  148. package/src/views/page-editor/useSitePreview.ts +0 -158
  149. 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 { MediaPickerModal } from '../../components/MediaPickerModal.js'
37
- import {
38
- buildPublicPreviewUrl,
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 { companionDimensionPaths, inspectorIdFromFieldPath } from '../../lib/preview-bridge.js'
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 — the canvas
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: that is the site preview saying
748
- // "the editor clicked THIS field", which must open and focus it. Making
749
- // that toggle would close the form on the second click of a field the
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 handlePreview = useCallback(() => {
801
- if (!page?.id) {
802
- toast.error('Save the page before previewing')
803
- return
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
- }, [page, dirty, handleSaveDraft, publicSiteUrl])
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
- {...(sitePreview.active
949
- ? // The site preview is an iframe of the real front end, not a
950
- // scaled canvas — a zoom percentage there would be a lie.
951
- {}
952
- : { zoom, canvasScale, onZoom: setZoom })}
840
+ zoom={zoom}
841
+ canvasScale={canvasScale}
842
+ onZoom={setZoom}
953
843
  onBack={() => guardedNavigate('/pages')}
954
- onPreview={handlePreview}
955
- onViewOnSite={page.id ? handleViewOnSite : undefined}
956
- sitePreview={sitePreview.active}
957
- onToggleSitePreview={page.id && page.slug ? sitePreview.toggle : undefined}
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
- {sitePreview.active ? (
1109
- <SitePreviewFrame
1110
- src={sitePreview.url}
1111
- viewport={viewport}
1112
- reloadKey={sitePreview.reloadKey}
1113
- title={`Site preview: ${page.title || 'Untitled page'}`}
1114
- selectedId={selectedId}
1115
- sections={page.sections}
1116
- suppressSectionsEcho={suppressSectionsEcho}
1117
- onSelectSection={handleSelectSection}
1118
- onCanvasEdit={handleCanvasEdit}
1119
- onCanvasRequestMedia={handleCanvasRequestMedia}
1120
- />
1121
- ) : (
1122
- <EditorCanvas
1123
- sections={canvasSections}
1124
- selectedId={selectedId}
1125
- breakpoint={viewport}
1126
- zoom={zoom}
1127
- onScaleChange={setCanvasScale}
1128
- onCanvasElement={setCanvasEl}
1129
- onSelect={handleSelectSection}
1130
- structuralTypes={structuralTypes}
1131
- sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
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 preview</span> — use{' '}
57
- <span className="text-foreground font-medium">Site preview</span> for accurate styling.
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={interactive ? () => onSelect?.(section.id) : undefined}
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 — or use Site preview to click text and images in place.'
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 && (