@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
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
4
|
+
import { AlertTriangle, Check, Copy, ExternalLink, Layers, Lock, Trash2 } from 'lucide-react'
|
|
5
|
+
import { toast } from 'sonner'
|
|
6
|
+
import {
|
|
7
|
+
restrictedCount,
|
|
8
|
+
robotsMetaLine,
|
|
9
|
+
type MaxImagePreview,
|
|
10
|
+
type RobotsPermissions,
|
|
11
|
+
} from '@prenta/core/seo/robots-directives'
|
|
12
|
+
import {
|
|
13
|
+
SeoAccordion,
|
|
14
|
+
SeoAccordionBadge,
|
|
15
|
+
SeoAccordionItem,
|
|
16
|
+
} from '../../components/seo/SeoAccordion.js'
|
|
17
|
+
import { AuthorPicker } from '../../components/page-tab/AuthorPicker.js'
|
|
18
|
+
import { DirectiveSwitch } from '../../components/page-tab/DirectiveSwitch.js'
|
|
19
|
+
import { pendingSlugChange } from '../../lib/page-redirect.js'
|
|
20
|
+
import type { EditorPage } from '../../lib/page-editor-service.js'
|
|
21
|
+
|
|
22
|
+
const INPUT =
|
|
23
|
+
'border-border bg-input-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-lg border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none'
|
|
24
|
+
const LABEL = 'text-foreground mb-1 block text-xs font-medium'
|
|
25
|
+
|
|
26
|
+
export interface PageTabProps {
|
|
27
|
+
page: EditorPage
|
|
28
|
+
canEdit: boolean
|
|
29
|
+
/** Path as it is on the live site, or null when never published. */
|
|
30
|
+
publishedPath: string | null
|
|
31
|
+
/** Site origin, for resolving the full URL. */
|
|
32
|
+
siteUrl: string
|
|
33
|
+
directives: RobotsPermissions
|
|
34
|
+
onDirectivesChange: (next: RobotsPermissions) => void
|
|
35
|
+
/** Whether a 301 should be created when the moved page is published. */
|
|
36
|
+
redirectOnPublish: boolean
|
|
37
|
+
onRedirectOnPublishChange: (next: boolean) => void
|
|
38
|
+
/**
|
|
39
|
+
* Whether this user can create redirects. `POST /redirects` is limited to
|
|
40
|
+
* publishers who may also publish the page; when false the URL-changed
|
|
41
|
+
* warning still shows, but the checkbox that would promise a 301 does not.
|
|
42
|
+
*/
|
|
43
|
+
canCreateRedirect?: boolean
|
|
44
|
+
onPageChange: (patch: Partial<EditorPage>) => void
|
|
45
|
+
onDuplicate: () => void
|
|
46
|
+
onTrash: () => void
|
|
47
|
+
/** Template/schema binding, shown read-only. */
|
|
48
|
+
templateName?: string
|
|
49
|
+
schemaType?: string
|
|
50
|
+
/** Offered as the brand byline in the author picker. */
|
|
51
|
+
brandName?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Rail tab 3 — everything about the page that is not its content or its search
|
|
56
|
+
* copy: where it lives, when it goes live, what crawlers may do with it, what
|
|
57
|
+
* owns its layout, and how to get rid of it.
|
|
58
|
+
*/
|
|
59
|
+
export function PageTab({
|
|
60
|
+
page,
|
|
61
|
+
canEdit,
|
|
62
|
+
publishedPath,
|
|
63
|
+
siteUrl,
|
|
64
|
+
directives,
|
|
65
|
+
onDirectivesChange,
|
|
66
|
+
redirectOnPublish,
|
|
67
|
+
onRedirectOnPublishChange,
|
|
68
|
+
canCreateRedirect = true,
|
|
69
|
+
onPageChange,
|
|
70
|
+
onDuplicate,
|
|
71
|
+
onTrash,
|
|
72
|
+
templateName,
|
|
73
|
+
schemaType,
|
|
74
|
+
brandName,
|
|
75
|
+
}: PageTabProps) {
|
|
76
|
+
const [copied, setCopied] = useState(false)
|
|
77
|
+
const [confirmTrash, setConfirmTrash] = useState(false)
|
|
78
|
+
|
|
79
|
+
const fullUrl = useMemo(() => {
|
|
80
|
+
const origin = siteUrl.replace(/\/+$/, '')
|
|
81
|
+
const path = page.path.startsWith('/') ? page.path : `/${page.path}`
|
|
82
|
+
return `${origin}${path}`
|
|
83
|
+
}, [siteUrl, page.path])
|
|
84
|
+
|
|
85
|
+
const move = pendingSlugChange(publishedPath, page.path)
|
|
86
|
+
const restricted = restrictedCount(directives)
|
|
87
|
+
const set = (patch: Partial<RobotsPermissions>) => onDirectivesChange({ ...directives, ...patch })
|
|
88
|
+
|
|
89
|
+
async function copyUrl() {
|
|
90
|
+
try {
|
|
91
|
+
await navigator.clipboard.writeText(fullUrl)
|
|
92
|
+
setCopied(true)
|
|
93
|
+
setTimeout(() => setCopied(false), 1600)
|
|
94
|
+
} catch {
|
|
95
|
+
toast.error('Could not copy — your browser blocked clipboard access.')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<SeoAccordion defaultOpen={['address', 'publishing']}>
|
|
101
|
+
{/* ── Address ────────────────────────────────────────────────────── */}
|
|
102
|
+
<SeoAccordionItem
|
|
103
|
+
value="address"
|
|
104
|
+
title="Address"
|
|
105
|
+
badge={move ? <SeoAccordionBadge tone="warn">URL CHANGED</SeoAccordionBadge> : undefined}
|
|
106
|
+
>
|
|
107
|
+
<div>
|
|
108
|
+
<label className={LABEL} htmlFor="pt-slug">
|
|
109
|
+
URL slug
|
|
110
|
+
</label>
|
|
111
|
+
<input
|
|
112
|
+
id="pt-slug"
|
|
113
|
+
className={INPUT}
|
|
114
|
+
value={page.slug}
|
|
115
|
+
disabled={!canEdit}
|
|
116
|
+
onChange={(e) => {
|
|
117
|
+
// Path is the public URL the redirect detector and the displayed
|
|
118
|
+
// full link both read. Keep it in lockstep when it was derived
|
|
119
|
+
// from the previous slug — the same rule as PageSettingsModal.
|
|
120
|
+
// A custom path (e.g. `/team/about` while slug is `about`) is
|
|
121
|
+
// left alone; the editor must change that elsewhere on purpose.
|
|
122
|
+
const slug = e.target.value
|
|
123
|
+
const patch: Partial<EditorPage> = { slug }
|
|
124
|
+
if (!page.path || page.path === `/${page.slug}`) {
|
|
125
|
+
patch.path = slug ? `/${slug}` : '/'
|
|
126
|
+
}
|
|
127
|
+
onPageChange(patch)
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
<div className="mt-1.5 flex items-center gap-1.5">
|
|
131
|
+
{/* `data-volatile` so visual baselines mask it: the origin comes
|
|
132
|
+
from `NEXT_PUBLIC_SITE_URL`, which is set in some environments
|
|
133
|
+
and not others, and a screenshot that bakes one in fails in the
|
|
134
|
+
other. */}
|
|
135
|
+
<code
|
|
136
|
+
data-volatile
|
|
137
|
+
className="text-muted-foreground min-w-0 flex-1 truncate font-mono text-[11px]"
|
|
138
|
+
>
|
|
139
|
+
{fullUrl}
|
|
140
|
+
</code>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={copyUrl}
|
|
144
|
+
className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1"
|
|
145
|
+
aria-label={copied ? 'Full URL copied' : 'Copy full URL'}
|
|
146
|
+
>
|
|
147
|
+
{copied ? (
|
|
148
|
+
<Check size={13} className="text-success" aria-hidden />
|
|
149
|
+
) : (
|
|
150
|
+
<Copy size={13} aria-hidden />
|
|
151
|
+
)}
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Only for a page that is actually live. A slug change on a draft
|
|
157
|
+
breaks nothing, and warning about it would train editors to dismiss
|
|
158
|
+
the warning that matters. */}
|
|
159
|
+
{move && (
|
|
160
|
+
<div className="bg-warning/10 border-warning/30 space-y-2 rounded-lg border p-2.5">
|
|
161
|
+
<div className="flex gap-2">
|
|
162
|
+
<AlertTriangle size={14} className="text-warning mt-px shrink-0" aria-hidden />
|
|
163
|
+
<p className="text-[11.5px] leading-normal">
|
|
164
|
+
This page is live at <b className="font-medium">{move.from}</b>. Publishing moves
|
|
165
|
+
it, and every existing link to the old address will 404.
|
|
166
|
+
</p>
|
|
167
|
+
</div>
|
|
168
|
+
{canCreateRedirect ? (
|
|
169
|
+
<label className="flex cursor-pointer items-start gap-2 text-[11.5px]">
|
|
170
|
+
<input
|
|
171
|
+
type="checkbox"
|
|
172
|
+
checked={redirectOnPublish}
|
|
173
|
+
disabled={!canEdit}
|
|
174
|
+
onChange={(e) => onRedirectOnPublishChange(e.target.checked)}
|
|
175
|
+
className="accent-primary mt-0.5"
|
|
176
|
+
/>
|
|
177
|
+
<span>
|
|
178
|
+
Create a 301 redirect on publish
|
|
179
|
+
<span className="text-muted-foreground mt-0.5 block font-mono text-[10.5px]">
|
|
180
|
+
{move.from} → {move.to}
|
|
181
|
+
</span>
|
|
182
|
+
</span>
|
|
183
|
+
</label>
|
|
184
|
+
) : (
|
|
185
|
+
<p className="text-muted-foreground text-[11.5px] leading-normal">
|
|
186
|
+
Ask an admin to create a 301 from{' '}
|
|
187
|
+
<span className="font-mono text-[10.5px]">
|
|
188
|
+
{move.from} → {move.to}
|
|
189
|
+
</span>{' '}
|
|
190
|
+
so existing links keep working.
|
|
191
|
+
</p>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{page.status === 'PUBLISHED' && (
|
|
197
|
+
<a
|
|
198
|
+
href={fullUrl}
|
|
199
|
+
target="_blank"
|
|
200
|
+
rel="noreferrer"
|
|
201
|
+
className="border-border hover:bg-accent inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium"
|
|
202
|
+
>
|
|
203
|
+
<ExternalLink size={12} aria-hidden />
|
|
204
|
+
Open live page
|
|
205
|
+
</a>
|
|
206
|
+
)}
|
|
207
|
+
</SeoAccordionItem>
|
|
208
|
+
|
|
209
|
+
{/* ── Publishing ─────────────────────────────────────────────────── */}
|
|
210
|
+
<SeoAccordionItem
|
|
211
|
+
value="publishing"
|
|
212
|
+
title="Publishing"
|
|
213
|
+
badge={
|
|
214
|
+
<SeoAccordionBadge tone={page.status === 'PUBLISHED' ? 'good' : 'neutral'}>
|
|
215
|
+
{page.status}
|
|
216
|
+
</SeoAccordionBadge>
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-[11.5px]">
|
|
220
|
+
<dt className="text-muted-foreground">Last edited</dt>
|
|
221
|
+
<dd data-volatile>{formatStamp(page.updatedAt) ?? '—'}</dd>
|
|
222
|
+
<dt className="text-muted-foreground">Published</dt>
|
|
223
|
+
<dd data-volatile>{formatStamp(page.publishedAt) ?? 'Not yet published'}</dd>
|
|
224
|
+
{page.scheduledAt && (
|
|
225
|
+
<>
|
|
226
|
+
<dt className="text-muted-foreground">Goes live</dt>
|
|
227
|
+
<dd data-volatile>{formatStamp(page.scheduledAt)}</dd>
|
|
228
|
+
</>
|
|
229
|
+
)}
|
|
230
|
+
{page.scheduledUnpublishAt && (
|
|
231
|
+
<>
|
|
232
|
+
<dt className="text-muted-foreground">Comes down</dt>
|
|
233
|
+
<dd data-volatile>{formatStamp(page.scheduledUnpublishAt)}</dd>
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</dl>
|
|
237
|
+
<div>
|
|
238
|
+
<p className={LABEL}>Byline</p>
|
|
239
|
+
<AuthorPicker
|
|
240
|
+
value={{ name: page.authorName, id: page.authorId }}
|
|
241
|
+
onChange={(next) => onPageChange({ authorName: next.name, authorId: next.id })}
|
|
242
|
+
brandName={brandName}
|
|
243
|
+
disabled={!canEdit}
|
|
244
|
+
/>
|
|
245
|
+
<p className="text-muted-foreground mt-1 text-[11px] leading-snug">
|
|
246
|
+
Shown on the page and in its structured data. Separate from who created the record,
|
|
247
|
+
which is not reassigned.
|
|
248
|
+
</p>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<p className="text-muted-foreground text-[11px] leading-snug">
|
|
252
|
+
Status is changed with Publish and Schedule in the toolbar, so there is one way to do it
|
|
253
|
+
rather than two that can disagree.
|
|
254
|
+
</p>
|
|
255
|
+
</SeoAccordionItem>
|
|
256
|
+
|
|
257
|
+
{/* ── Search directives ──────────────────────────────────────────── */}
|
|
258
|
+
<SeoAccordionItem
|
|
259
|
+
value="directives"
|
|
260
|
+
title="Search directives"
|
|
261
|
+
badge={
|
|
262
|
+
restricted === 0 ? (
|
|
263
|
+
<SeoAccordionBadge tone="good">OPEN</SeoAccordionBadge>
|
|
264
|
+
) : (
|
|
265
|
+
<SeoAccordionBadge tone="warn">{restricted} RESTRICTED</SeoAccordionBadge>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
>
|
|
269
|
+
<div className="divide-border divide-y">
|
|
270
|
+
<DirectiveSwitch
|
|
271
|
+
id="pt-index"
|
|
272
|
+
label="Show in search results"
|
|
273
|
+
hint="Off removes the page from Google and every other index."
|
|
274
|
+
checked={directives.index}
|
|
275
|
+
onChange={(v) => set({ index: v })}
|
|
276
|
+
permissiveToken="index"
|
|
277
|
+
restrictiveToken="noindex"
|
|
278
|
+
disabled={!canEdit}
|
|
279
|
+
/>
|
|
280
|
+
<DirectiveSwitch
|
|
281
|
+
id="pt-follow"
|
|
282
|
+
label="Follow links on this page"
|
|
283
|
+
hint="Off stops crawlers passing authority to anything this page links to."
|
|
284
|
+
checked={directives.follow}
|
|
285
|
+
onChange={(v) => set({ follow: v })}
|
|
286
|
+
permissiveToken="follow"
|
|
287
|
+
restrictiveToken="nofollow"
|
|
288
|
+
disabled={!canEdit}
|
|
289
|
+
/>
|
|
290
|
+
<DirectiveSwitch
|
|
291
|
+
id="pt-snippet"
|
|
292
|
+
label="Allow text snippets"
|
|
293
|
+
hint="Off blocks AI Overviews and chat citations entirely."
|
|
294
|
+
checked={directives.snippet}
|
|
295
|
+
onChange={(v) => set({ snippet: v })}
|
|
296
|
+
permissiveToken="snippet"
|
|
297
|
+
restrictiveToken="nosnippet"
|
|
298
|
+
disabled={!canEdit || !directives.index}
|
|
299
|
+
/>
|
|
300
|
+
<DirectiveSwitch
|
|
301
|
+
id="pt-archive"
|
|
302
|
+
label="Allow cached copies"
|
|
303
|
+
checked={directives.archive}
|
|
304
|
+
onChange={(v) => set({ archive: v })}
|
|
305
|
+
permissiveToken="archive"
|
|
306
|
+
restrictiveToken="noarchive"
|
|
307
|
+
disabled={!canEdit || !directives.index}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div>
|
|
312
|
+
<label className={LABEL} htmlFor="pt-img-preview">
|
|
313
|
+
Image preview size
|
|
314
|
+
</label>
|
|
315
|
+
<select
|
|
316
|
+
id="pt-img-preview"
|
|
317
|
+
className={INPUT}
|
|
318
|
+
value={directives.maxImagePreview}
|
|
319
|
+
disabled={!canEdit || !directives.index || !directives.snippet}
|
|
320
|
+
onChange={(e) => set({ maxImagePreview: e.target.value as MaxImagePreview })}
|
|
321
|
+
>
|
|
322
|
+
<option value="large">Large — full-width image in results</option>
|
|
323
|
+
<option value="standard">Standard — thumbnail</option>
|
|
324
|
+
<option value="none">None</option>
|
|
325
|
+
</select>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<div>
|
|
329
|
+
<p className={LABEL}>What ships in the page head</p>
|
|
330
|
+
{/* The literal line, composed by the same function the renderer uses.
|
|
331
|
+
An editor should never have to trust that this UI and the output
|
|
332
|
+
agree — they are the same code. */}
|
|
333
|
+
<code className="bg-muted text-muted-foreground block overflow-x-auto rounded-lg px-2.5 py-2 font-mono text-[10.5px] whitespace-pre">
|
|
334
|
+
{robotsMetaLine(directives)}
|
|
335
|
+
</code>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{!directives.index && (
|
|
339
|
+
<div className="bg-warning/10 border-warning/30 flex gap-2 rounded-lg border p-2.5">
|
|
340
|
+
<AlertTriangle size={14} className="text-warning mt-px shrink-0" aria-hidden />
|
|
341
|
+
<p className="text-[11.5px] leading-normal">
|
|
342
|
+
<b className="font-semibold">This page is hidden from search.</b> Publishing will not
|
|
343
|
+
make it findable.
|
|
344
|
+
</p>
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
</SeoAccordionItem>
|
|
348
|
+
|
|
349
|
+
{/* ── Layout ─────────────────────────────────────────────────────── */}
|
|
350
|
+
<SeoAccordionItem
|
|
351
|
+
value="layout"
|
|
352
|
+
title="Layout"
|
|
353
|
+
badge={<SeoAccordionBadge tone="neutral">LOCKED</SeoAccordionBadge>}
|
|
354
|
+
>
|
|
355
|
+
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-[11.5px]">
|
|
356
|
+
<dt className="text-muted-foreground">Template</dt>
|
|
357
|
+
<dd className="flex items-center gap-1.5">
|
|
358
|
+
<Layers size={12} className="text-muted-foreground" aria-hidden />
|
|
359
|
+
{templateName ?? 'Default'}
|
|
360
|
+
</dd>
|
|
361
|
+
<dt className="text-muted-foreground">Schema</dt>
|
|
362
|
+
<dd>{schemaType ?? 'WebPage'}</dd>
|
|
363
|
+
</dl>
|
|
364
|
+
<div className="text-muted-foreground flex gap-2 text-[11px] leading-snug">
|
|
365
|
+
<Lock size={13} className="mt-px shrink-0" aria-hidden />
|
|
366
|
+
<p>
|
|
367
|
+
Layout and styling are design-owned — built in code, not configured here. This tab edits
|
|
368
|
+
where the page lives and how it is indexed.
|
|
369
|
+
</p>
|
|
370
|
+
</div>
|
|
371
|
+
</SeoAccordionItem>
|
|
372
|
+
|
|
373
|
+
{/* ── Danger zone ────────────────────────────────────────────────── */}
|
|
374
|
+
<SeoAccordionItem value="danger" title="Danger zone">
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
onClick={onDuplicate}
|
|
378
|
+
disabled={!canEdit || !page.id}
|
|
379
|
+
className="border-border hover:border-destructive hover:bg-destructive/10 hover:text-destructive flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-colors disabled:opacity-50"
|
|
380
|
+
>
|
|
381
|
+
<Copy size={13} aria-hidden />
|
|
382
|
+
Duplicate page
|
|
383
|
+
</button>
|
|
384
|
+
|
|
385
|
+
{confirmTrash ? (
|
|
386
|
+
<div className="border-destructive/40 bg-destructive/10 space-y-2 rounded-lg border p-2.5">
|
|
387
|
+
<p className="text-[11.5px] leading-normal">
|
|
388
|
+
Move <b className="font-semibold">{page.title || 'this page'}</b> to trash?
|
|
389
|
+
{page.status === 'PUBLISHED' && ' It is live, and will stop being served.'}
|
|
390
|
+
</p>
|
|
391
|
+
<div className="flex gap-2">
|
|
392
|
+
<button
|
|
393
|
+
type="button"
|
|
394
|
+
onClick={onTrash}
|
|
395
|
+
className="bg-destructive text-destructive-foreground rounded-md px-2.5 py-1 text-[11px] font-semibold"
|
|
396
|
+
>
|
|
397
|
+
Move to trash
|
|
398
|
+
</button>
|
|
399
|
+
<button
|
|
400
|
+
type="button"
|
|
401
|
+
onClick={() => setConfirmTrash(false)}
|
|
402
|
+
className="border-border rounded-md border px-2.5 py-1 text-[11px] font-medium"
|
|
403
|
+
>
|
|
404
|
+
Cancel
|
|
405
|
+
</button>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
) : (
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
onClick={() => setConfirmTrash(true)}
|
|
412
|
+
disabled={!canEdit || !page.id}
|
|
413
|
+
className="border-border hover:border-destructive hover:bg-destructive/10 hover:text-destructive flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-colors disabled:opacity-50"
|
|
414
|
+
>
|
|
415
|
+
<Trash2 size={13} aria-hidden />
|
|
416
|
+
Move to trash
|
|
417
|
+
</button>
|
|
418
|
+
)}
|
|
419
|
+
</SeoAccordionItem>
|
|
420
|
+
</SeoAccordion>
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** `null` rather than a placeholder string, so callers choose their own dash. */
|
|
425
|
+
function formatStamp(value: string | null): string | null {
|
|
426
|
+
if (!value) return null
|
|
427
|
+
const d = new Date(value)
|
|
428
|
+
if (Number.isNaN(d.getTime())) return null
|
|
429
|
+
return d.toLocaleString(undefined, {
|
|
430
|
+
dateStyle: 'medium',
|
|
431
|
+
timeStyle: 'short',
|
|
432
|
+
})
|
|
433
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { History, Loader2 } from 'lucide-react'
|
|
4
|
+
import { dayLabel, timeLabel, type PageVersion } from '../../lib/version-history.js'
|
|
5
|
+
|
|
6
|
+
export interface ReviewBarProps {
|
|
7
|
+
version: PageVersion
|
|
8
|
+
changedCount: number
|
|
9
|
+
onRestore: () => void
|
|
10
|
+
onBackToCurrent: () => void
|
|
11
|
+
restoring: boolean
|
|
12
|
+
canRestore: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The strip above the canvas while a past version is on it.
|
|
17
|
+
*
|
|
18
|
+
* Occupies the same slot as the editing tip and the structure notice, and
|
|
19
|
+
* replaces both rather than stacking with them: everything that slot normally
|
|
20
|
+
* says is about editing the current page, which is precisely what you cannot
|
|
21
|
+
* do here. It also has to be unmissable — the canvas below looks like a
|
|
22
|
+
* perfectly ordinary editable page, and the only thing separating "reviewing
|
|
23
|
+
* history" from "editing" is this bar.
|
|
24
|
+
*/
|
|
25
|
+
export function ReviewBar({
|
|
26
|
+
version,
|
|
27
|
+
changedCount,
|
|
28
|
+
onRestore,
|
|
29
|
+
onBackToCurrent,
|
|
30
|
+
restoring,
|
|
31
|
+
canRestore,
|
|
32
|
+
}: ReviewBarProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
// `status`, not a bare div: entering review mode is a change of what the
|
|
36
|
+
// whole canvas means, and a screen-reader user gets no equivalent of the
|
|
37
|
+
// amber colour that announces it to everyone else.
|
|
38
|
+
role="status"
|
|
39
|
+
className="border-warning/35 bg-warning/10 text-foreground mx-auto mb-3 flex w-fit max-w-full flex-wrap items-center gap-2.5 rounded-full border py-1.5 pr-1.5 pl-3.5 text-[11.5px]"
|
|
40
|
+
>
|
|
41
|
+
<History className="text-warning h-3.5 w-3.5 shrink-0" aria-hidden />
|
|
42
|
+
<span className="min-w-0">
|
|
43
|
+
Viewing{' '}
|
|
44
|
+
<strong className="font-semibold">
|
|
45
|
+
{dayLabel(version.createdAt)} {timeLabel(version.createdAt)}
|
|
46
|
+
</strong>{' '}
|
|
47
|
+
by <strong className="font-semibold">{version.author.name}</strong>
|
|
48
|
+
{changedCount > 0 && (
|
|
49
|
+
<>
|
|
50
|
+
{' · '}
|
|
51
|
+
{changedCount} section{changedCount === 1 ? '' : 's'} changed
|
|
52
|
+
</>
|
|
53
|
+
)}
|
|
54
|
+
</span>
|
|
55
|
+
|
|
56
|
+
{canRestore && (
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={onRestore}
|
|
60
|
+
disabled={restoring}
|
|
61
|
+
className="border-border bg-card text-foreground hover:bg-accent inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
62
|
+
>
|
|
63
|
+
{restoring && <Loader2 className="h-3 w-3 animate-spin" aria-hidden />}
|
|
64
|
+
Restore
|
|
65
|
+
</button>
|
|
66
|
+
)}
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={onBackToCurrent}
|
|
70
|
+
className="border-border bg-card text-foreground hover:bg-accent shrink-0 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors"
|
|
71
|
+
>
|
|
72
|
+
Back to current
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|