@prenta/admin 0.92.0 → 0.94.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 +663 -0
- package/design-system/tokens.css +173 -71
- package/design-system/tokens.json +244 -63
- package/design-system/tokens.scss +203 -98
- package/design-system/tokens.tailwind.cjs +93 -38
- package/dist/AdminRoot.d.ts +27 -6
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.js +138 -0
- package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
- package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
- package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/media-block.render.test.js +108 -0
- package/dist/__tests__/components/media-block.render.test.js.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
- package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
- package/dist/__tests__/components/seo-char-count.test.js +24 -6
- package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
- package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
- package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/serp-preview.render.test.js +25 -0
- package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.js +67 -0
- package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
- package/dist/__tests__/lib/media-format.test.d.ts +2 -0
- package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
- package/dist/__tests__/lib/media-format.test.js +70 -0
- package/dist/__tests__/lib/media-format.test.js.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.js +122 -0
- package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +2 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
- package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-redirect.test.js +26 -0
- package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
- package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
- package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
- package/dist/__tests__/lib/rail-width.test.js +69 -0
- package/dist/__tests__/lib/rail-width.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
- package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.js +73 -0
- package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
- package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
- package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-diff.test.js +230 -0
- package/dist/__tests__/lib/version-diff.test.js.map +1 -0
- package/dist/__tests__/lib/version-history.test.d.ts +2 -0
- package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-history.test.js +125 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
- package/dist/__tests__/styles/packaging.test.d.ts +2 -0
- package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
- package/dist/__tests__/styles/packaging.test.js +31 -0
- package/dist/__tests__/styles/packaging.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +127 -0
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.js +92 -0
- package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
- package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
- package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
- package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/page-tab.render.test.js +151 -0
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.js +81 -0
- package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
- package/dist/components/BreakpointPicker.d.ts +18 -0
- package/dist/components/BreakpointPicker.d.ts.map +1 -0
- package/dist/components/BreakpointPicker.js +31 -0
- package/dist/components/BreakpointPicker.js.map +1 -0
- package/dist/components/CanvasEditTip.d.ts +6 -0
- package/dist/components/CanvasEditTip.d.ts.map +1 -0
- package/dist/components/CanvasEditTip.js +22 -0
- package/dist/components/CanvasEditTip.js.map +1 -0
- package/dist/components/EditorRail.d.ts +36 -0
- package/dist/components/EditorRail.d.ts.map +1 -0
- package/dist/components/EditorRail.js +104 -0
- package/dist/components/EditorRail.js.map +1 -0
- package/dist/components/EditorRailTabs.d.ts +32 -0
- package/dist/components/EditorRailTabs.d.ts.map +1 -0
- package/dist/components/EditorRailTabs.js +45 -0
- package/dist/components/EditorRailTabs.js.map +1 -0
- package/dist/components/MediaBlock.d.ts +49 -0
- package/dist/components/MediaBlock.d.ts.map +1 -0
- package/dist/components/MediaBlock.js +50 -0
- package/dist/components/MediaBlock.js.map +1 -0
- package/dist/components/ReflowMeter.d.ts +28 -0
- package/dist/components/ReflowMeter.d.ts.map +1 -0
- package/dist/components/ReflowMeter.js +42 -0
- package/dist/components/ReflowMeter.js.map +1 -0
- package/dist/components/ZoomChip.d.ts +17 -0
- package/dist/components/ZoomChip.d.ts.map +1 -0
- package/dist/components/ZoomChip.js +17 -0
- package/dist/components/ZoomChip.js.map +1 -0
- package/dist/components/breakpoint-icons.d.ts +3 -0
- package/dist/components/breakpoint-icons.d.ts.map +1 -0
- package/dist/components/breakpoint-icons.js +16 -0
- package/dist/components/breakpoint-icons.js.map +1 -0
- package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
- package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
- package/dist/components/page-tab/AuthorPicker.js +95 -0
- package/dist/components/page-tab/AuthorPicker.js.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.js +33 -0
- package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
- package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
- package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
- package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
- package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
- package/dist/components/seo/PageAuditPanel.d.ts +14 -0
- package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
- package/dist/components/seo/PageAuditPanel.js +59 -0
- package/dist/components/seo/PageAuditPanel.js.map +1 -0
- package/dist/components/seo/SeoCharCount.d.ts +19 -7
- package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
- package/dist/components/seo/SeoCharCount.js +30 -16
- package/dist/components/seo/SeoCharCount.js.map +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts +15 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +118 -21
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/seo/SerpPreview.d.ts +20 -0
- package/dist/components/seo/SerpPreview.d.ts.map +1 -0
- package/dist/components/seo/SerpPreview.js +6 -0
- package/dist/components/seo/SerpPreview.js.map +1 -0
- package/dist/hooks/usePageAudit.d.ts +41 -0
- package/dist/hooks/usePageAudit.d.ts.map +1 -0
- package/dist/hooks/usePageAudit.js +68 -0
- package/dist/hooks/usePageAudit.js.map +1 -0
- package/dist/hooks/useReflowGuard.d.ts +47 -0
- package/dist/hooks/useReflowGuard.d.ts.map +1 -0
- package/dist/hooks/useReflowGuard.js +52 -0
- package/dist/hooks/useReflowGuard.js.map +1 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +2 -2
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/lib/breakpoints.d.ts +51 -0
- package/dist/lib/breakpoints.d.ts.map +1 -0
- package/dist/lib/breakpoints.js +84 -0
- package/dist/lib/breakpoints.js.map +1 -0
- package/dist/lib/canvas-edit-chrome.d.ts +22 -0
- package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
- package/dist/lib/canvas-edit-chrome.js +81 -0
- package/dist/lib/canvas-edit-chrome.js.map +1 -0
- package/dist/lib/media-format.d.ts +55 -0
- package/dist/lib/media-format.d.ts.map +1 -0
- package/dist/lib/media-format.js +93 -0
- package/dist/lib/media-format.js.map +1 -0
- package/dist/lib/page-audit-input.d.ts +45 -0
- package/dist/lib/page-audit-input.d.ts.map +1 -0
- package/dist/lib/page-audit-input.js +53 -0
- package/dist/lib/page-audit-input.js.map +1 -0
- package/dist/lib/page-editor-service.d.ts +12 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +6 -0
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/page-redirect.d.ts +39 -0
- package/dist/lib/page-redirect.d.ts.map +1 -0
- package/dist/lib/page-redirect.js +38 -0
- package/dist/lib/page-redirect.js.map +1 -0
- package/dist/lib/rail-width.d.ts +23 -0
- package/dist/lib/rail-width.d.ts.map +1 -0
- package/dist/lib/rail-width.js +59 -0
- package/dist/lib/rail-width.js.map +1 -0
- package/dist/lib/reflow-baseline.d.ts +45 -0
- package/dist/lib/reflow-baseline.d.ts.map +1 -0
- package/dist/lib/reflow-baseline.js +38 -0
- package/dist/lib/reflow-baseline.js.map +1 -0
- package/dist/lib/reflow-probe.d.ts +159 -0
- package/dist/lib/reflow-probe.d.ts.map +1 -0
- package/dist/lib/reflow-probe.js +228 -0
- package/dist/lib/reflow-probe.js.map +1 -0
- package/dist/lib/seo-service.d.ts +10 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/lib/version-diff.d.ts +50 -0
- package/dist/lib/version-diff.d.ts.map +1 -0
- package/dist/lib/version-diff.js +294 -0
- package/dist/lib/version-diff.js.map +1 -0
- package/dist/lib/version-history.d.ts +73 -0
- package/dist/lib/version-history.d.ts.map +1 -0
- package/dist/lib/version-history.js +126 -0
- package/dist/lib/version-history.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
- package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
- package/dist/views/page-editor/CanvasMetaLine.js +23 -0
- package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +68 -34
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +13 -20
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +383 -78
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/PageTab.d.ts +36 -0
- package/dist/views/page-editor/PageTab.d.ts.map +1 -0
- package/dist/views/page-editor/PageTab.js +65 -0
- package/dist/views/page-editor/PageTab.js.map +1 -0
- package/dist/views/page-editor/ReviewBar.d.ts +21 -0
- package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
- package/dist/views/page-editor/ReviewBar.js +22 -0
- package/dist/views/page-editor/ReviewBar.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +64 -20
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +38 -7
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js +3 -0
- package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
- package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
- package/dist/views/page-editor/reflow-context.d.ts +28 -0
- package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
- package/dist/views/page-editor/reflow-context.js +14 -0
- package/dist/views/page-editor/reflow-context.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.js +7 -3
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.js +1 -1
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.js +27 -15
- package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
- package/dist/views/page-editor/sections/parts.d.ts +0 -1
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
- package/dist/views/page-editor/sections/parts.js +41 -16
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/page-editor/viewports.d.ts +7 -14
- package/dist/views/page-editor/viewports.d.ts.map +1 -1
- package/dist/views/page-editor/viewports.js +7 -12
- package/dist/views/page-editor/viewports.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +5 -2
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +2 -20
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js +4 -1
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
- package/package.json +5 -3
- package/src/AdminRoot.tsx +27 -6
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
- package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
- package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
- package/src/__tests__/components/media-block.render.test.tsx +126 -0
- package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
- package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
- package/src/__tests__/components/seo-char-count.test.tsx +26 -6
- package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
- package/src/__tests__/lib/breakpoints.test.ts +84 -0
- package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
- package/src/__tests__/lib/media-format.test.ts +89 -0
- package/src/__tests__/lib/page-audit-input.test.ts +144 -0
- package/src/__tests__/lib/page-editor-service.test.ts +2 -0
- package/src/__tests__/lib/page-redirect.test.ts +29 -0
- package/src/__tests__/lib/rail-width.test.ts +86 -0
- package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
- package/src/__tests__/lib/reflow-probe.test.ts +95 -0
- package/src/__tests__/lib/version-diff.test.ts +278 -0
- package/src/__tests__/lib/version-history.test.ts +166 -0
- package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
- package/src/__tests__/styles/packaging.test.ts +38 -0
- package/src/__tests__/styles/responsive-layer.test.ts +152 -0
- package/src/__tests__/styles/tokens-drift.test.ts +110 -0
- package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
- package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
- package/src/__tests__/views/page-tab.render.test.tsx +226 -0
- package/src/__tests__/views/section-class-parity.test.ts +98 -0
- package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
- package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
- package/src/components/BreakpointPicker.tsx +114 -0
- package/src/components/CanvasEditTip.tsx +46 -0
- package/src/components/EditorRail.tsx +219 -0
- package/src/components/EditorRailTabs.tsx +117 -0
- package/src/components/MediaBlock.tsx +223 -0
- package/src/components/ReflowMeter.tsx +125 -0
- package/src/components/ZoomChip.tsx +39 -0
- package/src/components/breakpoint-icons.ts +17 -0
- package/src/components/page-tab/AuthorPicker.tsx +240 -0
- package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
- package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
- package/src/components/seo/PageAuditPanel.tsx +192 -0
- package/src/components/seo/SeoCharCount.tsx +55 -24
- package/src/components/seo/SeoEditorPane.tsx +213 -31
- package/src/components/seo/SerpPreview.tsx +41 -0
- package/src/hooks/usePageAudit.ts +98 -0
- package/src/hooks/useReflowGuard.ts +96 -0
- package/src/layout/primitives/AdminShell.tsx +13 -1
- package/src/lib/breakpoints.ts +107 -0
- package/src/lib/canvas-edit-chrome.ts +82 -0
- package/src/lib/media-format.ts +99 -0
- package/src/lib/page-audit-input.ts +104 -0
- package/src/lib/page-editor-service.ts +18 -0
- package/src/lib/page-redirect.ts +60 -0
- package/src/lib/rail-width.ts +58 -0
- package/src/lib/reflow-baseline.ts +70 -0
- package/src/lib/reflow-probe.ts +299 -0
- package/src/lib/seo-service.ts +10 -0
- package/src/lib/version-diff.ts +376 -0
- package/src/lib/version-history.ts +192 -0
- package/src/styles/prenta-tokens.css +120 -42
- package/src/styles/responsive.css +281 -0
- package/src/styles/theme.css +138 -0
- package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
- package/src/views/page-editor/EditorCanvas.tsx +140 -54
- package/src/views/page-editor/EditorTopBar.tsx +55 -51
- package/src/views/page-editor/PageSectionEditor.tsx +575 -185
- package/src/views/page-editor/PageTab.tsx +433 -0
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionInspector.tsx +84 -42
- package/src/views/page-editor/SectionsPanel.tsx +282 -141
- package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/reflow-context.ts +43 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
- package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
- package/src/views/page-editor/sections/GallerySection.tsx +7 -3
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/MissionSection.tsx +4 -2
- package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
- package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
- package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
- package/src/views/page-editor/sections/parts.tsx +46 -18
- package/src/views/page-editor/viewports.ts +13 -26
- package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
- package/src/views/post-editor/PostSectionEditor.tsx +1 -56
- package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
|
@@ -1,32 +1,64 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { AlertTriangle, Blocks, Loader2, Lock } from 'lucide-react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
|
|
7
7
|
import { useNavigationBlocker } from '../../router/index.js';
|
|
8
8
|
import { SchedulePublishDialog } from '../../components/SchedulePublishDialog.js';
|
|
9
|
-
import {
|
|
9
|
+
import { SeoEditorPane } from '../../components/seo/SeoEditorPane.js';
|
|
10
|
+
import { EditorRail, RailHeader } from '../../components/EditorRail.js';
|
|
11
|
+
import { EditorRailTabs } from '../../components/EditorRailTabs.js';
|
|
10
12
|
import { ErrorBoundary } from '../../components/ErrorBoundary.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
+
import { VersionHistoryPanel } from './VersionHistoryPanel.js';
|
|
14
|
+
import { ReviewBar } from './ReviewBar.js';
|
|
15
|
+
import { fetchPageVersions, restorePageVersion, } from '../../lib/version-history.js';
|
|
16
|
+
import { changedSectionIds, diffVersions } from '../../lib/version-diff.js';
|
|
17
|
+
import { emptyPage, fetchPageForEditor, missingPageFields, publishPage, PAGES_COLLECTION, savePageDraft, toggleSectionVisibility, updateSectionContent, updateSectionContentAtPath, updateSectionMeta, updateSectionSettings, validatePage, } from '../../lib/page-editor-service.js';
|
|
13
18
|
import { MediaPickerModal } from '../../components/MediaPickerModal.js';
|
|
14
19
|
import { buildPublicPreviewUrl, buildPublicUrl, createPreviewToken, } from '../../lib/preview-link.js';
|
|
15
20
|
import { usePublicSiteUrl } from '../../hooks/usePublicSiteUrl.js';
|
|
16
21
|
import { DEFAULT_VIEWPORT } from './viewports.js';
|
|
22
|
+
import { snapshotBaseline } from '../../lib/reflow-baseline.js';
|
|
23
|
+
import { DEFAULT_DIRECTIVES, directivesFromData, } from '@prenta/core/seo/robots-directives';
|
|
24
|
+
import { createPublishRedirect, pendingSlugChange } from '../../lib/page-redirect.js';
|
|
25
|
+
import { updateSeoFields } from '../../lib/seo-service.js';
|
|
26
|
+
import { PageTab } from './PageTab.js';
|
|
27
|
+
import { duplicatePage, deletePage } from '../../lib/pages-service.js';
|
|
28
|
+
import { scoreBand } from '@prenta/core/seo/page-audit';
|
|
29
|
+
import { usePageAudit } from '../../hooks/usePageAudit.js';
|
|
17
30
|
import { hasCanvasRenderer, useAdminSectionRenderers } from './sections/index.js';
|
|
18
31
|
import { EditorTopBar } from './EditorTopBar.js';
|
|
19
|
-
import {
|
|
32
|
+
import { SectionsList } from './SectionsPanel.js';
|
|
20
33
|
import { EditorCanvas } from './EditorCanvas.js';
|
|
34
|
+
import { ReflowContext } from './reflow-context.js';
|
|
21
35
|
import { SitePreviewFrame } from './SitePreviewFrame.js';
|
|
22
36
|
import { useSitePreview } from './useSitePreview.js';
|
|
23
37
|
import { SectionInspector } from './SectionInspector.js';
|
|
24
|
-
import { AddSectionModal } from './AddSectionModal.js';
|
|
25
38
|
import { PageSettingsModal } from './PageSettingsModal.js';
|
|
26
39
|
import { ValidationSummary } from './ValidationSummary.js';
|
|
27
40
|
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js';
|
|
28
|
-
import {
|
|
41
|
+
import { buildEditorContentHtml } from '../../lib/editor-seo-content.js';
|
|
29
42
|
import { companionDimensionPaths, inspectorIdFromFieldPath } from '../../lib/preview-bridge.js';
|
|
43
|
+
/**
|
|
44
|
+
* The lower of the two audit scores — the tab dot should reflect the half that
|
|
45
|
+
* needs work, not an average that hides it.
|
|
46
|
+
*
|
|
47
|
+
* Three-valued on purpose, and it must NOT fall back to another scorer:
|
|
48
|
+
* undefined — the audit has not reported yet (the SEO tab has never been
|
|
49
|
+
* opened). No dot beats a dot from a different model.
|
|
50
|
+
* null — both groups are gated off, so there is genuinely nothing to
|
|
51
|
+
* rate. An earlier version fell back to `computeLiveSeoScore`
|
|
52
|
+
* here, which put a number from the content analyser — different
|
|
53
|
+
* model, different bands — on a tab summarising the audit. That
|
|
54
|
+
* is the disagreement this wiring exists to remove.
|
|
55
|
+
*/
|
|
56
|
+
function weakestAuditScore(audit) {
|
|
57
|
+
if (!audit)
|
|
58
|
+
return undefined;
|
|
59
|
+
const scores = [audit.seo.score, audit.geo.score].filter((s) => s !== null);
|
|
60
|
+
return scores.length ? Math.min(...scores) : null;
|
|
61
|
+
}
|
|
30
62
|
function deriveCan(session) {
|
|
31
63
|
const role = session?.user?.role;
|
|
32
64
|
// Default closed: when we can't identify a role, hide the edit/publish
|
|
@@ -34,10 +66,18 @@ function deriveCan(session) {
|
|
|
34
66
|
// access on every write, so this is a UX guard — but it should fail safe.
|
|
35
67
|
// (Matches the Post editor's behavior.)
|
|
36
68
|
if (!role)
|
|
37
|
-
return { canEdit: false, canPublish: false };
|
|
69
|
+
return { canEdit: false, canPublish: false, canCreateRedirect: false };
|
|
38
70
|
const editors = ['ADMIN', 'EDITOR', 'AUTHOR'];
|
|
39
71
|
const publishers = ['ADMIN', 'EDITOR'];
|
|
40
|
-
|
|
72
|
+
// Same roles that may publish a page may create the 301 that follows a
|
|
73
|
+
// slug move. Offering the checkbox to anyone else promises a redirect
|
|
74
|
+
// `POST /redirects` will reject.
|
|
75
|
+
const redirectCreators = ['OWNER', 'ADMIN', 'EDITOR'];
|
|
76
|
+
return {
|
|
77
|
+
canEdit: editors.includes(role),
|
|
78
|
+
canPublish: publishers.includes(role),
|
|
79
|
+
canCreateRedirect: redirectCreators.includes(role),
|
|
80
|
+
};
|
|
41
81
|
}
|
|
42
82
|
export function PageSectionEditor({ documentId, initialParentId, config, session, onNavigate, onOpenPreview, }) {
|
|
43
83
|
const [page, setPage] = useState(null);
|
|
@@ -51,19 +91,83 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
51
91
|
/** Canvas image Replace — opens MediaPickerModal for this field path. */
|
|
52
92
|
const [canvasMediaTarget, setCanvasMediaTarget] = useState(null);
|
|
53
93
|
const [viewport, setViewport] = useState(DEFAULT_VIEWPORT);
|
|
94
|
+
const [zoom, setZoom] = useState('fit');
|
|
95
|
+
/**
|
|
96
|
+
* Live audit reported by the SEO pane, built from unsaved form state. Null
|
|
97
|
+
* until that pane has mounted — `audit` below falls back to the saved-record
|
|
98
|
+
* audit so the header chip and tab dot always have a number.
|
|
99
|
+
*/
|
|
100
|
+
const [liveAudit, setLiveAudit] = useState(null);
|
|
101
|
+
/** Scale the canvas measured — reported upward so the zoom chip shows a real %. */
|
|
102
|
+
const [canvasScale, setCanvasScale] = useState(1);
|
|
103
|
+
/**
|
|
104
|
+
* The live canvas surface, for the reflow guard to clone. State rather than a
|
|
105
|
+
* ref because the context value has to update when it mounts — the canvas
|
|
106
|
+
* unmounts whenever the site-preview frame takes over.
|
|
107
|
+
*/
|
|
108
|
+
const [canvasEl, setCanvasEl] = useState(null);
|
|
109
|
+
/**
|
|
110
|
+
* Section content as it is on the live site. Captured whenever the editor
|
|
111
|
+
* takes delivery of a PUBLISHED page — on load, and after each save and
|
|
112
|
+
* publish, both of which write straight through to the live content.
|
|
113
|
+
*/
|
|
114
|
+
const [baseline, setBaseline] = useState(null);
|
|
115
|
+
/**
|
|
116
|
+
* Path as it is on the live site, captured with the reflow baseline and for
|
|
117
|
+
* the same reason: a slug change only breaks links if something is already
|
|
118
|
+
* live at the old address.
|
|
119
|
+
*/
|
|
120
|
+
const [publishedPath, setPublishedPath] = useState(null);
|
|
121
|
+
const [directives, setDirectives] = useState(DEFAULT_DIRECTIVES);
|
|
122
|
+
/**
|
|
123
|
+
* Defaults ON. The editor changing a live page's slug almost always wants the
|
|
124
|
+
* redirect, and the cost of an unwanted one is a rule they can delete, while
|
|
125
|
+
* the cost of a missing one is 404s discovered by visitors.
|
|
126
|
+
*/
|
|
127
|
+
const [redirectOnPublish, setRedirectOnPublish] = useState(true);
|
|
128
|
+
// The reflow guard's inputs, gathered in one place. Memoised because a new
|
|
129
|
+
// object identity on every render would restart the debounce in every guarded
|
|
130
|
+
// field's effect — the measurement would then never settle while typing,
|
|
131
|
+
// which is precisely when it is wanted.
|
|
132
|
+
const reflowContext = useMemo(() => ({
|
|
133
|
+
canvas: canvasEl,
|
|
134
|
+
sectionId: selectedId,
|
|
135
|
+
baseline,
|
|
136
|
+
device: viewport,
|
|
137
|
+
onProof: setViewport,
|
|
138
|
+
}), [canvasEl, selectedId, baseline, viewport]);
|
|
139
|
+
/**
|
|
140
|
+
* Take delivery of server state.
|
|
141
|
+
*
|
|
142
|
+
* Every path that receives a fresh page from the API goes through here so the
|
|
143
|
+
* reflow baseline is re-captured with it. Saving a PUBLISHED page writes
|
|
144
|
+
* straight through to the live content — there is no draft/published split —
|
|
145
|
+
* so a save moves the baseline exactly as a publish does. Missing one would
|
|
146
|
+
* leave the guard comparing against content that is no longer on the site.
|
|
147
|
+
*/
|
|
148
|
+
const acceptPage = useCallback((p) => {
|
|
149
|
+
setPage(p);
|
|
150
|
+
setBaseline(snapshotBaseline(p.sections, p.status));
|
|
151
|
+
setPublishedPath(p.status === 'PUBLISHED' ? p.path : null);
|
|
152
|
+
}, []);
|
|
54
153
|
const [dirty, setDirty] = useState(false);
|
|
55
154
|
const [saveState, setSaveState] = useState('idle');
|
|
56
155
|
const [publishing, setPublishing] = useState(false);
|
|
57
|
-
const [addOpen, setAddOpen] = useState(false);
|
|
58
156
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
59
|
-
|
|
157
|
+
/** Active rail tab. Replaces the old boolean `seoOpen` right-hand aside. */
|
|
158
|
+
const [railTab, setRailTab] = useState('sections');
|
|
60
159
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
61
|
-
const [
|
|
160
|
+
const [versions, setVersions] = useState([]);
|
|
161
|
+
const [versionsLoading, setVersionsLoading] = useState(false);
|
|
162
|
+
const [versionsError, setVersionsError] = useState(null);
|
|
163
|
+
/** Index into `versions`; 0 is the newest, which is the current content. */
|
|
164
|
+
const [versionIndex, setVersionIndex] = useState(0);
|
|
165
|
+
const [restoring, setRestoring] = useState(false);
|
|
62
166
|
const [confirmPublish, setConfirmPublish] = useState(false);
|
|
63
167
|
const [scheduleOpen, setScheduleOpen] = useState(false);
|
|
64
168
|
const [leaveTarget, setLeaveTarget] = useState(null);
|
|
65
169
|
const [validation, setValidation] = useState(null);
|
|
66
|
-
const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session]);
|
|
170
|
+
const { canEdit, canPublish, canCreateRedirect } = useMemo(() => deriveCan(session), [session]);
|
|
67
171
|
const customRenderers = useAdminSectionRenderers();
|
|
68
172
|
const { siteUrl: publicSiteUrl } = usePublicSiteUrl(config?.seo?.siteUrl);
|
|
69
173
|
// A misconfigured `pages` collection (missing the fields the editor
|
|
@@ -81,18 +185,152 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
81
185
|
}, [config]);
|
|
82
186
|
const publishCheck = useMemo(() => (page ? validatePage(page, true) : { errors: [''], warnings: [] }), [page]);
|
|
83
187
|
const publishReady = Boolean(page) && publishCheck.errors.length === 0;
|
|
84
|
-
const liveSeoScore = useMemo(() => {
|
|
85
|
-
if (!page)
|
|
86
|
-
return 0;
|
|
87
|
-
return computeLiveSeoScore({
|
|
88
|
-
title: page.title,
|
|
89
|
-
slug: page.slug,
|
|
90
|
-
seoTitle: page.seoTitle,
|
|
91
|
-
seoDescription: page.seoDescription,
|
|
92
|
-
sections: page.sections,
|
|
93
|
-
});
|
|
94
|
-
}, [page]);
|
|
95
188
|
const publishDisabledReason = publishCheck.errors[0];
|
|
189
|
+
// Saved-record audit, available from first paint. Both this and the pane's
|
|
190
|
+
// live audit go through `buildPageAuditInput`, so they can differ only in
|
|
191
|
+
// saved-versus-unsaved metadata — never in how a check is decided.
|
|
192
|
+
const { audit: savedAudit, record: seoRecord, reload: reloadAudit, } = usePageAudit({
|
|
193
|
+
entityType: 'page',
|
|
194
|
+
entityId: page?.id ?? null,
|
|
195
|
+
sections: page?.sections ?? [],
|
|
196
|
+
slug: page?.slug ?? '',
|
|
197
|
+
fallback: { title: page?.seoTitle, description: page?.seoDescription },
|
|
198
|
+
});
|
|
199
|
+
// Seed directives from the saved record. Keyed on the record's identity, not
|
|
200
|
+
// on its fields — re-running on every field change would discard whatever the
|
|
201
|
+
// editor had just toggled and not yet saved.
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!seoRecord)
|
|
204
|
+
return;
|
|
205
|
+
setDirectives(directivesFromData({
|
|
206
|
+
noindex: seoRecord.noindex,
|
|
207
|
+
nofollow: seoRecord.nofollow,
|
|
208
|
+
nosnippet: seoRecord.nosnippet,
|
|
209
|
+
noarchive: seoRecord.noarchive,
|
|
210
|
+
maxImagePreview: seoRecord.maxImagePreview,
|
|
211
|
+
}));
|
|
212
|
+
}, [seoRecord?.id]);
|
|
213
|
+
// Unsaved edits win: while the SEO pane is open its number is the truthful
|
|
214
|
+
// one, and every surface reads this single value.
|
|
215
|
+
const audit = liveAudit ?? savedAudit;
|
|
216
|
+
// Content the SEO pane analyses. Hoisted out of the old aside wrapper so the
|
|
217
|
+
// rail can render the pane directly — the aside only ever existed to supply
|
|
218
|
+
// a resizable container, which the rail now is.
|
|
219
|
+
const seoContentHtml = useMemo(() => buildEditorContentHtml(page?.sections ?? []), [page?.sections]);
|
|
220
|
+
const railTabs = useMemo(() => [
|
|
221
|
+
{
|
|
222
|
+
id: 'sections',
|
|
223
|
+
label: 'Sections',
|
|
224
|
+
title: 'Page content',
|
|
225
|
+
count: page?.sections.length ?? 0,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'seo',
|
|
229
|
+
label: 'SEO & GEO',
|
|
230
|
+
title: 'Search & AI readiness',
|
|
231
|
+
// The weaker of the two: a page can be perfectly crawlable and still
|
|
232
|
+
// invisible to answer engines, and a dot that averaged them would hide
|
|
233
|
+
// whichever half is failing. `null` means a gate is off, which the tab
|
|
234
|
+
// reports as unrated rather than as a low score.
|
|
235
|
+
score: weakestAuditScore(audit),
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 'page',
|
|
239
|
+
label: 'Page',
|
|
240
|
+
title: 'Page settings',
|
|
241
|
+
},
|
|
242
|
+
], [page?.sections.length, audit]);
|
|
243
|
+
const activeRailTab = railTabs.find((tab) => tab.id === railTab) ?? railTabs[0];
|
|
244
|
+
// ─── Version history ─────────────────────────────────────────────────
|
|
245
|
+
// History is a MODE, not a dialog: it overlays the rail and drives the
|
|
246
|
+
// canvas, so the version being reviewed is editor state rather than
|
|
247
|
+
// something owned by a panel that can be unmounted.
|
|
248
|
+
const loadVersions = useCallback(async (pageId) => {
|
|
249
|
+
setVersionsLoading(true);
|
|
250
|
+
setVersionsError(null);
|
|
251
|
+
try {
|
|
252
|
+
setVersions(await fetchPageVersions(PAGES_COLLECTION, pageId));
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
setVersions([]);
|
|
256
|
+
setVersionsError(err instanceof Error ? err.message : 'Failed to load version history');
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
setVersionsLoading(false);
|
|
260
|
+
}
|
|
261
|
+
}, []);
|
|
262
|
+
const openHistory = useCallback(() => {
|
|
263
|
+
if (!page?.id)
|
|
264
|
+
return;
|
|
265
|
+
setHistoryOpen(true);
|
|
266
|
+
// Always land on "current" so opening history never silently swaps the
|
|
267
|
+
// canvas out from under the editor.
|
|
268
|
+
setVersionIndex(0);
|
|
269
|
+
void loadVersions(page.id);
|
|
270
|
+
}, [page?.id, loadVersions]);
|
|
271
|
+
const closeHistory = useCallback(() => {
|
|
272
|
+
setHistoryOpen(false);
|
|
273
|
+
setVersionIndex(0);
|
|
274
|
+
}, []);
|
|
275
|
+
/**
|
|
276
|
+
* Index 0 is the current content, which the canvas is already showing — so
|
|
277
|
+
* selecting it is "back to current", not a review. Only an older index puts
|
|
278
|
+
* the editor into review mode.
|
|
279
|
+
*/
|
|
280
|
+
const reviewing = historyOpen && versionIndex > 0;
|
|
281
|
+
const reviewVersion = reviewing ? (versions[versionIndex] ?? null) : null;
|
|
282
|
+
// What that version's save CHANGED — diffed against the one before it, which
|
|
283
|
+
// is the question the outlines on the canvas answer.
|
|
284
|
+
const reviewChanges = useMemo(() => reviewVersion
|
|
285
|
+
? diffVersions(reviewVersion.sections, versions[versionIndex + 1]?.sections)
|
|
286
|
+
: [], [reviewVersion, versions, versionIndex]);
|
|
287
|
+
const reviewChangedIds = useMemo(() => changedSectionIds(reviewChanges), [reviewChanges]);
|
|
288
|
+
const handleRestoreVersion = useCallback(async (version) => {
|
|
289
|
+
if (!page?.id)
|
|
290
|
+
return;
|
|
291
|
+
setRestoring(true);
|
|
292
|
+
try {
|
|
293
|
+
await restorePageVersion(PAGES_COLLECTION, page.id, version.id);
|
|
294
|
+
// Re-read rather than trusting the snapshot we already hold: the
|
|
295
|
+
// restore goes through the normal update path, so hooks and derived
|
|
296
|
+
// fields may have altered what actually landed.
|
|
297
|
+
const restored = await fetchPageForEditor(page.id);
|
|
298
|
+
acceptPage(restored);
|
|
299
|
+
setSelectedId(null);
|
|
300
|
+
setDirty(false);
|
|
301
|
+
setSaveState('idle');
|
|
302
|
+
setValidation(null);
|
|
303
|
+
closeHistory();
|
|
304
|
+
void loadVersions(page.id);
|
|
305
|
+
toast.success('Version restored');
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
toast.error(err instanceof Error ? err.message : 'Failed to restore version');
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
setRestoring(false);
|
|
312
|
+
}
|
|
313
|
+
}, [page?.id, acceptPage, closeHistory, loadVersions]);
|
|
314
|
+
// ─── Edit permission ─────────────────────────────────────────────────
|
|
315
|
+
/**
|
|
316
|
+
* ADR 0002: a Page-Builder-owned page (it has a `_layout`) compiles its
|
|
317
|
+
* sections from the builder layout on every save, so editing them here would
|
|
318
|
+
* be overwritten. The server independently refuses such edits with 409.
|
|
319
|
+
*/
|
|
320
|
+
const readOnly = page?.builderOwned ?? false;
|
|
321
|
+
/**
|
|
322
|
+
* The single gate every writing surface reads.
|
|
323
|
+
*
|
|
324
|
+
* Hoisted above the callbacks rather than derived at render time, because
|
|
325
|
+
* the two that were left behind were the ones that mattered: the site
|
|
326
|
+
* preview's in-place edit handlers close over `canEdit` (the raw role) and
|
|
327
|
+
* so kept accepting edits while a past version was under review — and while
|
|
328
|
+
* a builder-owned page was supposedly read-only. The iframe is a second
|
|
329
|
+
* editing surface, and a lock that only covers the rail is not a lock.
|
|
330
|
+
*/
|
|
331
|
+
const effectiveCanEdit = canEdit && !readOnly && !reviewing;
|
|
332
|
+
/** Restoring is not editing: it is allowed precisely while reviewing. */
|
|
333
|
+
const canRestoreVersion = canEdit && !readOnly;
|
|
96
334
|
// ─── Load ──────────────────────────────────────────────────────────
|
|
97
335
|
useEffect(() => {
|
|
98
336
|
let cancelled = false;
|
|
@@ -106,7 +344,7 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
106
344
|
return;
|
|
107
345
|
}
|
|
108
346
|
if (!documentId) {
|
|
109
|
-
|
|
347
|
+
acceptPage(emptyPage(initialParentId ?? null));
|
|
110
348
|
setLoading(false);
|
|
111
349
|
// Start a brand-new page the way WordPress does: name it first. The
|
|
112
350
|
// settings modal opens with the title focused and the slug/path
|
|
@@ -118,7 +356,7 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
118
356
|
.then((p) => {
|
|
119
357
|
if (cancelled)
|
|
120
358
|
return;
|
|
121
|
-
|
|
359
|
+
acceptPage(p);
|
|
122
360
|
// Default to a clean canvas (no selection / no inspector) to match
|
|
123
361
|
// the editor's default composition; the inspector opens only once
|
|
124
362
|
// the user picks a section to edit.
|
|
@@ -144,22 +382,7 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
144
382
|
setDirty(true);
|
|
145
383
|
setSaveState('idle');
|
|
146
384
|
}, []);
|
|
147
|
-
const handleAdd = useCallback((type) => {
|
|
148
|
-
const section = createSection(type);
|
|
149
|
-
mutateSections((s) => addSection(s, section));
|
|
150
|
-
setSelectedId(section.id);
|
|
151
|
-
setAddOpen(false);
|
|
152
|
-
}, [mutateSections]);
|
|
153
|
-
const handleReorder = useCallback((orderedIds) => mutateSections((s) => reorderSections(s, orderedIds)), [mutateSections]);
|
|
154
385
|
const handleToggleVisibility = useCallback((id) => mutateSections((s) => toggleSectionVisibility(s, id)), [mutateSections]);
|
|
155
|
-
const handleDuplicate = useCallback((id) => mutateSections((s) => duplicateSection(s, id)), [mutateSections]);
|
|
156
|
-
const confirmDelete = useCallback(() => {
|
|
157
|
-
if (!deleteTargetId)
|
|
158
|
-
return;
|
|
159
|
-
mutateSections((s) => removeSection(s, deleteTargetId));
|
|
160
|
-
setSelectedId((cur) => (cur === deleteTargetId ? null : cur));
|
|
161
|
-
setDeleteTargetId(null);
|
|
162
|
-
}, [deleteTargetId, mutateSections]);
|
|
163
386
|
const handleContentChange = useCallback((patch) => {
|
|
164
387
|
if (!selectedId)
|
|
165
388
|
return;
|
|
@@ -182,6 +405,31 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
182
405
|
setSaveState('idle');
|
|
183
406
|
}, []);
|
|
184
407
|
// ─── Save / publish ──────────────────────────────────────────────────
|
|
408
|
+
/**
|
|
409
|
+
* Persist robots directives. They live on the SEO record, not the document
|
|
410
|
+
* body, so they need their own write — and it has to happen on both save and
|
|
411
|
+
* publish, or a toggle would appear to stick until the next reload.
|
|
412
|
+
*
|
|
413
|
+
* Reported on failure rather than swallowed: a directive that silently failed
|
|
414
|
+
* to save is the same defect as one that never reached the head.
|
|
415
|
+
*/
|
|
416
|
+
const saveDirectives = useCallback(async (pageId) => {
|
|
417
|
+
const { error } = await updateSeoFields('page', pageId, {
|
|
418
|
+
noindex: !directives.index,
|
|
419
|
+
nofollow: !directives.follow,
|
|
420
|
+
nosnippet: !directives.snippet,
|
|
421
|
+
noarchive: !directives.archive,
|
|
422
|
+
maxImagePreview: directives.maxImagePreview,
|
|
423
|
+
});
|
|
424
|
+
if (error) {
|
|
425
|
+
toast.error(`Search directives were not saved: ${error}`);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
// The header chip and rail dot read the saved SEO record. Without a
|
|
429
|
+
// reload they keep the pre-save score after noindex/nosnippet flips.
|
|
430
|
+
await reloadAudit();
|
|
431
|
+
return true;
|
|
432
|
+
}, [directives, reloadAudit]);
|
|
185
433
|
const handleSaveDraft = useCallback(async () => {
|
|
186
434
|
if (!page)
|
|
187
435
|
return false;
|
|
@@ -195,7 +443,9 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
195
443
|
setSaveState('saving');
|
|
196
444
|
try {
|
|
197
445
|
const saved = await savePageDraft(page);
|
|
198
|
-
|
|
446
|
+
if (saved.id)
|
|
447
|
+
await saveDirectives(saved.id);
|
|
448
|
+
acceptPage(saved);
|
|
199
449
|
setDirty(false);
|
|
200
450
|
setSaveState('saved');
|
|
201
451
|
setValidation(null);
|
|
@@ -209,7 +459,7 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
209
459
|
toast.error(err instanceof Error ? err.message : 'Failed to save draft');
|
|
210
460
|
return false;
|
|
211
461
|
}
|
|
212
|
-
}, [page, documentId, onNavigate]);
|
|
462
|
+
}, [page, documentId, onNavigate, saveDirectives, acceptPage]);
|
|
213
463
|
// Inline branded preview — Next builder canvas (same origin as admin), not
|
|
214
464
|
// the Astro public apex. "View on site" still uses publicSiteUrl below.
|
|
215
465
|
const sitePreview = useSitePreview({
|
|
@@ -271,8 +521,25 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
271
521
|
return;
|
|
272
522
|
setPublishing(true);
|
|
273
523
|
try {
|
|
524
|
+
// The move has to be read BEFORE publishing — `acceptPage` moves the
|
|
525
|
+
// published path to the new one, after which there is nothing to detect.
|
|
526
|
+
const move = redirectOnPublish && canCreateRedirect ? pendingSlugChange(publishedPath, page.path) : null;
|
|
274
527
|
const published = await publishPage(page);
|
|
275
|
-
|
|
528
|
+
if (published.id)
|
|
529
|
+
await saveDirectives(published.id);
|
|
530
|
+
if (move) {
|
|
531
|
+
const { error } = await createPublishRedirect(move);
|
|
532
|
+
if (error) {
|
|
533
|
+
// Loud, and naming both paths: the page has already moved, so a
|
|
534
|
+
// silently-failed redirect leaves exactly the broken links the switch
|
|
535
|
+
// promised to prevent.
|
|
536
|
+
toast.error(`Published, but the redirect ${move.from} → ${move.to} failed: ${error}`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
toast.success(`Redirect created: ${move.from} → ${move.to}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
acceptPage(published);
|
|
276
543
|
setDirty(false);
|
|
277
544
|
setSaveState('saved');
|
|
278
545
|
setValidation(null);
|
|
@@ -286,59 +553,84 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
286
553
|
finally {
|
|
287
554
|
setPublishing(false);
|
|
288
555
|
}
|
|
289
|
-
}, [
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
556
|
+
}, [
|
|
557
|
+
page,
|
|
558
|
+
documentId,
|
|
559
|
+
onNavigate,
|
|
560
|
+
saveDirectives,
|
|
561
|
+
redirectOnPublish,
|
|
562
|
+
canCreateRedirect,
|
|
563
|
+
publishedPath,
|
|
564
|
+
acceptPage,
|
|
565
|
+
]);
|
|
566
|
+
const handleDuplicate = useCallback(async () => {
|
|
293
567
|
if (!page?.id)
|
|
294
568
|
return;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
569
|
+
const { id, error } = await duplicatePage(page.id);
|
|
570
|
+
if (error || !id) {
|
|
571
|
+
toast.error(error ?? 'Could not duplicate the page');
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
toast.success('Page duplicated');
|
|
575
|
+
// Navigate to the copy rather than staying put. The duplicate is a draft
|
|
576
|
+
// named "(Copy)", and leaving the editor on the original makes it look as
|
|
577
|
+
// though nothing happened.
|
|
578
|
+
onNavigate(`/pages/${id}/edit`);
|
|
579
|
+
}, [page?.id, onNavigate]);
|
|
580
|
+
const handleTrash = useCallback(async () => {
|
|
581
|
+
if (!page?.id)
|
|
582
|
+
return;
|
|
583
|
+
const { error } = await deletePage(page.id);
|
|
584
|
+
if (error) {
|
|
585
|
+
toast.error(error);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
toast.success('Page moved to trash');
|
|
589
|
+
onNavigate('/pages');
|
|
590
|
+
}, [page?.id, onNavigate]);
|
|
308
591
|
const handleSeoSaved = useCallback(() => {
|
|
592
|
+
reloadAudit();
|
|
309
593
|
if (!page?.id)
|
|
310
594
|
return;
|
|
311
595
|
fetchPageForEditor(page.id)
|
|
312
|
-
.then((p) =>
|
|
596
|
+
.then((p) => acceptPage(p))
|
|
313
597
|
.catch(() => {
|
|
314
598
|
/* SEO API saved; editor meta will sync on next full load */
|
|
315
599
|
});
|
|
316
|
-
}, [page?.id]);
|
|
600
|
+
}, [page?.id, reloadAudit]);
|
|
601
|
+
// The header's SEO chip is a shortcut to the rail's SEO tab. Toggling back
|
|
602
|
+
// returns to Sections rather than leaving the rail on a tab the chip no
|
|
603
|
+
// longer appears to be showing.
|
|
317
604
|
const handleToggleSeo = useCallback(() => {
|
|
318
|
-
|
|
319
|
-
if (
|
|
605
|
+
setRailTab((tab) => {
|
|
606
|
+
if (tab !== 'seo')
|
|
320
607
|
setSelectedId(null);
|
|
321
|
-
return
|
|
608
|
+
return tab === 'seo' ? 'sections' : 'seo';
|
|
322
609
|
});
|
|
323
610
|
}, []);
|
|
324
611
|
const handleSelectSection = useCallback((id, fieldPath) => {
|
|
325
|
-
|
|
612
|
+
// Selecting on the canvas is a request to edit that section — show the
|
|
613
|
+
// outline it belongs to, whichever tab was open.
|
|
614
|
+
setRailTab('sections');
|
|
326
615
|
setSelectedId(id);
|
|
327
616
|
setPendingFocusPath(fieldPath ?? null);
|
|
328
617
|
}, []);
|
|
618
|
+
// `effectiveCanEdit`, not `canEdit`: the site preview is a second editing
|
|
619
|
+
// surface, so a role check alone let it write through both the version-review
|
|
620
|
+
// lock and the builder-owned lock the rest of the editor honours.
|
|
329
621
|
const handleCanvasEdit = useCallback((edit) => {
|
|
330
|
-
if (!
|
|
622
|
+
if (!effectiveCanEdit)
|
|
331
623
|
return;
|
|
332
624
|
setSuppressSectionsEcho((n) => n + 1);
|
|
333
625
|
mutateSections((s) => updateSectionContentAtPath(s, edit.sectionId, edit.fieldPath, edit.value));
|
|
334
626
|
setSelectedId(edit.sectionId);
|
|
335
|
-
}, [
|
|
627
|
+
}, [effectiveCanEdit, mutateSections]);
|
|
336
628
|
const handleCanvasRequestMedia = useCallback((request) => {
|
|
337
|
-
if (!
|
|
629
|
+
if (!effectiveCanEdit)
|
|
338
630
|
return;
|
|
339
631
|
setSelectedId(request.sectionId);
|
|
340
632
|
setCanvasMediaTarget(request);
|
|
341
|
-
}, [
|
|
633
|
+
}, [effectiveCanEdit]);
|
|
342
634
|
// After a canvas field click, focus the matching inspector input once painted.
|
|
343
635
|
useEffect(() => {
|
|
344
636
|
if (!pendingFocusPath || !selectedId)
|
|
@@ -439,12 +731,11 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
439
731
|
return (_jsx("div", { className: "bg-background flex h-full items-center justify-center p-6", children: _jsxs("div", { className: "border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4", children: [_jsx(AlertTriangle, { className: "text-destructive shrink-0", size: 20, "aria-hidden": true }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: "Failed to load page" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: loadError ?? 'Page not found.' }), _jsx("button", { type: "button", onClick: () => onNavigate('/pages'), className: "text-primary mt-2 text-sm font-medium hover:underline", children: "Back to Pages" })] })] }) }));
|
|
440
732
|
}
|
|
441
733
|
const selected = page.sections.find((s) => s.id === selectedId) ?? null;
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
const
|
|
447
|
-
const effectiveCanEdit = canEdit && !readOnly;
|
|
734
|
+
// `readOnly` / `effectiveCanEdit` / `canRestoreVersion` are computed above,
|
|
735
|
+
// with the hooks, so the callbacks defined there share this one gate.
|
|
736
|
+
// The canvas shows the reviewed snapshot, but `page.sections` stays the
|
|
737
|
+
// editor's own content throughout — nothing about review mode writes to it.
|
|
738
|
+
const canvasSections = reviewVersion ? reviewVersion.sections : page.sections;
|
|
448
739
|
// Sections whose type has no registered definition (seed/code-owned, e.g.
|
|
449
740
|
// `opal-*`). They are preserved verbatim on save but can't be edited here.
|
|
450
741
|
const unmanagedTypes = Array.from(new Set(page.sections.filter((s) => s.unmanaged).map((s) => s.sectionType)));
|
|
@@ -455,7 +746,21 @@ export function PageSectionEditor({ documentId, initialParentId, config, session
|
|
|
455
746
|
const structuralTypes = Array.from(new Set(page.sections
|
|
456
747
|
.filter((s) => !hasCanvasRenderer(s.sectionType) && !customRenderers[s.sectionType])
|
|
457
748
|
.map((s) => s.sectionType)));
|
|
458
|
-
return (_jsxs(ErrorBoundary, { children: [_jsxs("div", { className: "bg-background flex h-full flex-col overflow-hidden", children: [_jsx(EditorTopBar, { title: page.title, status: page.status, dirty: dirty, saveState: saveState, publishing: publishing, canEdit: effectiveCanEdit, canPublish: canPublish && effectiveCanEdit, publishReady: publishReady, publishDisabledReason: publishDisabledReason, viewport: viewport, onViewport: setViewport,
|
|
749
|
+
return (_jsxs(ErrorBoundary, { children: [_jsxs("div", { className: "bg-background flex h-full flex-col overflow-hidden", children: [_jsx(EditorTopBar, { title: page.title, status: page.status, dirty: dirty, saveState: saveState, publishing: publishing, canEdit: effectiveCanEdit, canPublish: canPublish && effectiveCanEdit, publishReady: publishReady, publishDisabledReason: publishDisabledReason, viewport: viewport, onViewport: setViewport, ...(sitePreview.active
|
|
750
|
+
? // The site preview is an iframe of the real front end, not a
|
|
751
|
+
// scaled canvas — a zoom percentage there would be a lie.
|
|
752
|
+
{}
|
|
753
|
+
: { zoom, canvasScale, onZoom: setZoom }), onBack: () => guardedNavigate('/pages'), onPreview: handlePreview, onViewOnSite: page.id ? handleViewOnSite : undefined, sitePreview: sitePreview.active, onToggleSitePreview: page.id && page.slug ? sitePreview.toggle : undefined, historyOpen: historyOpen, onHistory: page.id ? () => (historyOpen ? closeHistory() : openHistory()) : undefined, onOpenSettings: () => setSettingsOpen(true), onOpenSeo: handleToggleSeo, seoOpen: railTab === 'seo',
|
|
754
|
+
// The audit's SEO score, banded by the audit's own scale. Previously
|
|
755
|
+
// this chip showed `computeLiveSeoScore` — a different model — so the
|
|
756
|
+
// header read 53 while the rings beside it read 0 and 57.
|
|
757
|
+
seoScore: audit?.seo.score ?? undefined, seoScoreBand: typeof audit?.seo.score === 'number' ? scoreBand(audit.seo.score) : undefined, onSaveDraft: handleSaveDraft, onPublish: requestPublish, onSchedule: page.id ? requestSchedule : undefined, scheduleReady: Boolean(page.id), scheduledAt: page.scheduledAt, scheduledUnpublishAt: page.scheduledUnpublishAt, structuralTypes: structuralTypes }), readOnly && (_jsxs("div", { role: "status", className: "border-border bg-accent/40 flex items-center gap-3 border-b px-4 py-2.5", children: [_jsx(Blocks, { className: "text-muted-foreground shrink-0", size: 16, "aria-hidden": true }), _jsxs("p", { className: "text-muted-foreground flex-1 text-sm", children: ["This page is built in the", ' ', _jsx("span", { className: "text-foreground font-medium", children: "Page Builder" }), ". Its sections are generated from the builder layout and are read-only here."] }), page.id && (_jsx("button", { type: "button", onClick: () => guardedNavigate(`/page-builder/${page.id}`), className: "bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors", children: "Open in Page Builder" }))] })), unmanagedTypes.length > 0 && (_jsxs("div", { role: "status", className: "border-border bg-muted/50 flex items-start gap-3 border-b px-4 py-2.5", children: [_jsx(Lock, { className: "text-muted-foreground mt-0.5 shrink-0", size: 16, "aria-hidden": true }), _jsxs("p", { className: "text-muted-foreground flex-1 text-sm", children: ["This page has", ' ', _jsxs("span", { className: "text-foreground font-medium", children: [unmanagedTypes.length, " section type", unmanagedTypes.length > 1 ? 's' : ''] }), ' ', "managed outside the editor (", unmanagedTypes.join(', '), "). They are preserved on save but edited in your seed script or code, not here."] })] })), validation && (_jsx(ValidationSummary, { errors: validation.errors, warnings: validation.warnings, onDismiss: () => setValidation(null) })), _jsx(ReflowContext.Provider, { value: reflowContext, children: _jsxs("div", { className: "prenta-editor-body relative flex flex-1 overflow-hidden", children: [_jsx(EditorRail, { collapsedLabel: activeRailTab.label, children: ({ width, dragging, collapse }) => (_jsxs(_Fragment, { children: [_jsx(EditorRailTabs, { tabs: railTabs, active: railTab, onChange: setRailTab }), _jsx(RailHeader, { title: activeRailTab.title, width: width, dragging: dragging, onCollapse: collapse }), _jsx("div", { role: "tabpanel", id: `rail-panel-${railTab}`, "aria-labelledby": `rail-tab-${railTab}`, className: "flex min-h-0 flex-1 flex-col", children: railTab === 'sections' ? (_jsx(SectionsList, { sections: page.sections, selectedId: selectedId, canEdit: effectiveCanEdit, onSelect: handleSelectSection, onToggleVisibility: handleToggleVisibility })) : railTab === 'page' ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(PageTab, { page: page, canEdit: effectiveCanEdit, publishedPath: publishedPath, siteUrl: publicSiteUrl, directives: directives, onDirectivesChange: (next) => {
|
|
758
|
+
setDirectives(next);
|
|
759
|
+
setDirty(true);
|
|
760
|
+
}, redirectOnPublish: redirectOnPublish, onRedirectOnPublishChange: setRedirectOnPublish, canCreateRedirect: canCreateRedirect, onPageChange: handlePageSettings, onDuplicate: handleDuplicate, onTrash: handleTrash, schemaType: seoRecord?.structuredDataType ?? undefined, brandName: config?.seo?.siteName ?? undefined }) })) : (_jsx(SeoEditorPane, { entityType: "page", entityId: page.id, slug: page.slug, auditSections: page.sections, onAuditChange: setLiveAudit, contentHtml: seoContentHtml, draftMeta: {
|
|
761
|
+
seoTitle: page.seoTitle,
|
|
762
|
+
seoDescription: page.seoDescription,
|
|
763
|
+
}, onDraftChange: handlePageSettings, onClose: () => setRailTab('sections'), onSaved: handleSeoSaved })) }), historyOpen && (_jsx(VersionHistoryPanel, { versions: versions, loading: versionsLoading, error: versionsError, selectedIndex: versionIndex, onSelect: setVersionIndex, onClose: closeHistory, onRestore: handleRestoreVersion, restoring: restoring, canRestore: canRestoreVersion }))] })) }), _jsx("div", { className: "flex-1 overflow-hidden", children: sitePreview.active ? (_jsx(SitePreviewFrame, { src: sitePreview.url, viewport: viewport, reloadKey: sitePreview.reloadKey, title: `Site preview: ${page.title || 'Untitled page'}`, selectedId: selectedId, sections: page.sections, suppressSectionsEcho: suppressSectionsEcho, onSelectSection: handleSelectSection, onCanvasEdit: handleCanvasEdit, onCanvasRequestMedia: handleCanvasRequestMedia })) : (_jsx(EditorCanvas, { sections: canvasSections, selectedId: selectedId, breakpoint: viewport, zoom: zoom, onScaleChange: setCanvasScale, onCanvasElement: setCanvasEl, onSelect: handleSelectSection, structuralTypes: structuralTypes, sitePreviewAvailable: sitePreviewAvailable && !sitePreview.active, reviewing: reviewing, changedSectionIds: reviewChangedIds, reviewBar: reviewVersion ? (_jsx(ReviewBar, { version: reviewVersion, changedCount: reviewChangedIds.size, onRestore: () => void handleRestoreVersion(reviewVersion), onBackToCurrent: () => setVersionIndex(0), restoring: restoring, canRestore: canRestoreVersion })) : null })) }), selected && railTab === 'sections' && !reviewing && (_jsx(SectionInspector, { section: selected, canEdit: effectiveCanEdit, onClose: () => setSelectedId(null), onContentChange: handleContentChange, onSettingsChange: handleSettingsChange, onMetaChange: handleMetaChange, onToggleVisibility: () => handleToggleVisibility(selected.id) }))] }) })] }), _jsx(PageSettingsModal, { open: settingsOpen, page: page, canEdit: effectiveCanEdit, onClose: () => setSettingsOpen(false), onSave: handlePageSettings }), _jsx(ConfirmDialog, { open: confirmPublish, onClose: () => setConfirmPublish(false), onConfirm: doPublish, title: "Publish this page?", description: "The current content will go live and replace the published version.", confirmLabel: "Publish" }), page.id && (_jsx(SchedulePublishDialog, { collectionSlug: PAGES_COLLECTION, documentId: page.id, scheduledAt: page.scheduledAt, scheduledUnpublishAt: page.scheduledUnpublishAt, open: scheduleOpen, onClose: () => setScheduleOpen(false), onScheduled: handleScheduled })), _jsx(ConfirmDialog, { open: leaveTarget !== null, onClose: () => setLeaveTarget(null), onConfirm: () => {
|
|
459
764
|
const target = leaveTarget;
|
|
460
765
|
setLeaveTarget(null);
|
|
461
766
|
setDirty(false);
|