@drawnagency/primitives 0.1.113 → 0.1.114
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/README.md +1 -1
- package/dist/auth/cookies.d.ts +39 -0
- package/dist/auth/cookies.d.ts.map +1 -1
- package/dist/auth/index.js +3 -3
- package/dist/{chunk-GPPVYLEI.js → chunk-45FA7Z55.js} +1 -1
- package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
- package/dist/chunk-5GFACGSN.js +6 -0
- package/dist/chunk-5KSAS4VY.js +275 -0
- package/dist/{chunk-Z7W3DQTR.js → chunk-7SAAJVDE.js} +408 -28
- package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
- package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
- package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
- package/dist/chunk-DIMDRBFK.js +11 -0
- package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
- package/dist/{chunk-NHN2LSIU.js → chunk-IP2SEBAJ.js} +5 -1
- package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
- package/dist/chunk-JR24U7FJ.js +120 -0
- package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
- package/dist/{chunk-KR2MHTNJ.js → chunk-N3G7DOU3.js} +609 -61
- package/dist/chunk-OAGJBCSH.js +67 -0
- package/dist/{chunk-6BZ2KNTP.js → chunk-PLANHXS7.js} +191 -61
- package/dist/{chunk-VWWY5TL5.js → chunk-PNSCDBU7.js} +38 -56
- package/dist/{chunk-AHALSCQH.js → chunk-PTY2RG2K.js} +1 -1
- package/dist/{chunk-BDFTMGCL.js → chunk-PZPGJZRG.js} +9 -8
- package/dist/{chunk-FRE2CCLV.js → chunk-QHMKIODQ.js} +17 -4
- package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
- package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
- package/dist/chunk-VUYCRBJK.js +174 -0
- package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
- package/dist/{chunk-DS4GCYV2.js → chunk-Y5QECJX7.js} +223 -16
- package/dist/collab/rebase.d.ts +2 -1
- package/dist/collab/rebase.d.ts.map +1 -1
- package/dist/collab/rebase.js +20 -5
- package/dist/components/brandguide/ColorsEditable.d.ts.map +1 -1
- package/dist/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/components/chat/ChatWidget.d.ts.map +1 -1
- package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
- package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
- package/dist/components/editor/ConflictDialog.d.ts.map +1 -1
- package/dist/components/editor/DragHandle.d.ts +12 -2
- package/dist/components/editor/DragHandle.d.ts.map +1 -1
- package/dist/components/editor/DropEdgeIndicator.d.ts +15 -6
- package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
- package/dist/components/editor/NavGroupRow.d.ts +4 -1
- package/dist/components/editor/NavGroupRow.d.ts.map +1 -1
- package/dist/components/editor/PagesModal.d.ts.map +1 -1
- package/dist/components/editor/SectionWrapper.d.ts +1 -1
- package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
- package/dist/components/editor/SettingsForm.d.ts.map +1 -1
- package/dist/components/editor/TagIndicator.d.ts +15 -0
- package/dist/components/editor/TagIndicator.d.ts.map +1 -0
- package/dist/components/editor/index.js +148 -77
- package/dist/components/primitives/DocumentDropZone.d.ts +1 -1
- package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
- package/dist/components/primitives/EditableList.d.ts.map +1 -1
- package/dist/components/primitives/HeadingSection.d.ts +4 -1
- package/dist/components/primitives/HeadingSection.d.ts.map +1 -1
- package/dist/components/primitives/ResolvedMedia.d.ts +5 -1
- package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
- package/dist/components/primitives/{defineHeadingSection.d.ts → define-heading-section.d.ts} +1 -1
- package/dist/components/primitives/define-heading-section.d.ts.map +1 -0
- package/dist/components/primitives/{gridDrop.d.ts → grid-drop.d.ts} +1 -1
- package/dist/components/primitives/grid-drop.d.ts.map +1 -0
- package/dist/components/primitives/index.d.ts +1 -1
- package/dist/components/primitives/index.d.ts.map +1 -1
- package/dist/components/primitives/index.js +17 -154
- package/dist/components/sections/Button/CTAButton.d.ts.map +1 -1
- package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +2 -6
- package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -1
- package/dist/components/sections/DoDontMedia/index.d.ts +8 -0
- package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
- package/dist/components/sections/IconList/IconList.d.ts +2 -1
- package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
- package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
- package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
- package/dist/components/sections/IconList/index.d.ts +1 -0
- package/dist/components/sections/IconList/index.d.ts.map +1 -1
- package/dist/components/sections/Media/MediaBlock.d.ts +2 -1
- package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
- package/dist/components/sections/Media/index.d.ts +8 -0
- package/dist/components/sections/Media/index.d.ts.map +1 -1
- package/dist/components/sections/Reference/ReferenceList.d.ts +35 -0
- package/dist/components/sections/Reference/ReferenceList.d.ts.map +1 -0
- package/dist/components/sections/Reference/ReferenceSettings.d.ts +12 -0
- package/dist/components/sections/Reference/ReferenceSettings.d.ts.map +1 -0
- package/dist/components/sections/Reference/index.d.ts +9 -0
- package/dist/components/sections/Reference/index.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts +38 -0
- package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts +20 -0
- package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/index.d.ts +33 -0
- package/dist/components/sections/SectionSummary/index.d.ts.map +1 -0
- package/dist/components/sections/all-sections.d.ts +53 -0
- package/dist/components/sections/all-sections.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.d.ts +41 -2
- package/dist/components/sections/brand-guide.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +12 -10
- package/dist/components/sections/core-sections.d.ts +14 -0
- package/dist/components/sections/core-sections.d.ts.map +1 -1
- package/dist/components/sections/register-schemas.js +19 -17
- package/dist/components/sections/register.js +19 -17
- package/dist/components/shared/BrandSwatchesContext.d.ts +9 -0
- package/dist/components/shared/BrandSwatchesContext.d.ts.map +1 -0
- package/dist/components/shared/Button.d.ts +11 -3
- package/dist/components/shared/Button.d.ts.map +1 -1
- package/dist/components/shared/Checkbox.d.ts +6 -0
- package/dist/components/shared/Checkbox.d.ts.map +1 -1
- package/dist/components/shared/ConfirmDialog.d.ts +25 -0
- package/dist/components/shared/ConfirmDialog.d.ts.map +1 -0
- package/dist/components/shared/ConfirmInline.d.ts +25 -0
- package/dist/components/shared/ConfirmInline.d.ts.map +1 -0
- package/dist/components/shared/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/shared/Fab.d.ts +11 -2
- package/dist/components/shared/Fab.d.ts.map +1 -1
- package/dist/components/shared/FabStack.d.ts +22 -0
- package/dist/components/shared/FabStack.d.ts.map +1 -0
- package/dist/components/shared/HistoryChangesModal.d.ts.map +1 -1
- package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
- package/dist/components/shared/ImageBackgroundField.d.ts +9 -0
- package/dist/components/shared/ImageBackgroundField.d.ts.map +1 -0
- package/dist/components/shared/{UploadRejectionAlert.d.ts → InlineAlert.d.ts} +4 -2
- package/dist/components/shared/InlineAlert.d.ts.map +1 -0
- package/dist/components/shared/Input.d.ts +10 -0
- package/dist/components/shared/Input.d.ts.map +1 -1
- package/dist/components/shared/Navigation.d.ts.map +1 -1
- package/dist/components/shared/Notice.d.ts +10 -0
- package/dist/components/shared/Notice.d.ts.map +1 -0
- package/dist/components/shared/Popover.d.ts.map +1 -1
- package/dist/components/shared/ReferenceSourcesContext.d.ts +25 -0
- package/dist/components/shared/ReferenceSourcesContext.d.ts.map +1 -0
- package/dist/components/shared/Select.d.ts +3 -2
- package/dist/components/shared/Select.d.ts.map +1 -1
- package/dist/components/shared/SplitButton.d.ts.map +1 -1
- package/dist/components/shared/SummaryAnchorsContext.d.ts +10 -0
- package/dist/components/shared/SummaryAnchorsContext.d.ts.map +1 -0
- package/dist/components/shared/Textarea.d.ts +7 -0
- package/dist/components/shared/Textarea.d.ts.map +1 -1
- package/dist/components/shared/ToolbarFrame.d.ts +17 -0
- package/dist/components/shared/ToolbarFrame.d.ts.map +1 -0
- package/dist/components/shared/copy.d.ts +13 -0
- package/dist/components/shared/copy.d.ts.map +1 -0
- package/dist/components/shared/icons.d.ts +0 -12
- package/dist/components/shared/icons.d.ts.map +1 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts +7 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
- package/dist/components/shell/AudienceRow.d.ts.map +1 -1
- package/dist/components/shell/BugReportFAB.d.ts +7 -1
- package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
- package/dist/components/shell/CredentialList.d.ts.map +1 -1
- package/dist/components/shell/DocInspector.d.ts.map +1 -1
- package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -1
- package/dist/components/shell/EditorContext.d.ts +4 -0
- package/dist/components/shell/EditorContext.d.ts.map +1 -1
- package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
- package/dist/components/shell/EditorModal.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
- package/dist/components/shell/MediaFolderTile.d.ts +4 -1
- package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/RestoreModal.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsTags.d.ts +13 -0
- package/dist/components/shell/SiteSettingsTags.d.ts.map +1 -0
- package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
- package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
- package/dist/components/shell/{blockMoveDispatch.d.ts → block-move-dispatch.d.ts} +1 -1
- package/dist/components/shell/block-move-dispatch.d.ts.map +1 -0
- package/dist/components/shell/reference-sources.d.ts +22 -0
- package/dist/components/shell/reference-sources.d.ts.map +1 -0
- package/dist/deck/index.d.ts +2 -2
- package/dist/deck/index.d.ts.map +1 -1
- package/dist/deck/index.js +1 -3
- package/dist/deck-builder/index.js +11 -18
- package/dist/document/index.d.ts +2 -2
- package/dist/document/index.d.ts.map +1 -1
- package/dist/hooks/useBlockDnd.d.ts +11 -0
- package/dist/hooks/useBlockDnd.d.ts.map +1 -1
- package/dist/hooks/useBuildStatus.d.ts +1 -1
- package/dist/hooks/useBuildStatus.d.ts.map +1 -1
- package/dist/hooks/useDocumentUploads.d.ts +1 -1
- package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
- package/dist/hooks/useEditorPersistence.d.ts +5 -1
- package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +2 -2
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useNotice.d.ts +19 -0
- package/dist/hooks/useNotice.d.ts.map +1 -0
- package/dist/hooks/useSortableNavRow.d.ts +11 -0
- package/dist/hooks/useSortableNavRow.d.ts.map +1 -1
- package/dist/hooks/useSortableRow.d.ts +11 -0
- package/dist/hooks/useSortableRow.d.ts.map +1 -1
- package/dist/hooks/useStackedEscape.d.ts +32 -0
- package/dist/hooks/useStackedEscape.d.ts.map +1 -0
- package/dist/index.js +67 -49
- package/dist/lib/audience-secret.js +2 -2
- package/dist/lib/block-dnd.d.ts +5 -0
- package/dist/lib/block-dnd.d.ts.map +1 -1
- package/dist/lib/block-move.d.ts.map +1 -1
- package/dist/lib/brand-swatches.d.ts +12 -0
- package/dist/lib/brand-swatches.d.ts.map +1 -0
- package/dist/lib/container-ops.d.ts +3 -2
- package/dist/lib/container-ops.d.ts.map +1 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +9 -1
- package/dist/lib/env.d.ts +4 -0
- package/dist/lib/env.d.ts.map +1 -1
- package/dist/lib/env.js +5 -3
- package/dist/lib/events.d.ts +1 -2
- package/dist/lib/events.d.ts.map +1 -1
- package/dist/lib/image-background.d.ts +8 -0
- package/dist/lib/image-background.d.ts.map +1 -0
- package/dist/lib/index-ops.js +3 -3
- package/dist/lib/index.d.ts +6 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +29 -21
- package/dist/lib/loader.d.ts +21 -7
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/nav-tree.d.ts +1 -0
- package/dist/lib/nav-tree.d.ts.map +1 -1
- package/dist/lib/nav.d.ts +38 -0
- package/dist/lib/nav.d.ts.map +1 -1
- package/dist/lib/platform-broker.d.ts +4 -2
- package/dist/lib/platform-broker.d.ts.map +1 -1
- package/dist/lib/platform-broker.js +21 -13
- package/dist/lib/reference-type.d.ts +2 -0
- package/dist/lib/reference-type.d.ts.map +1 -0
- package/dist/lib/reference-validation.d.ts +31 -0
- package/dist/lib/reference-validation.d.ts.map +1 -0
- package/dist/lib/reference-validation.js +39 -0
- package/dist/lib/references.d.ts +74 -0
- package/dist/lib/references.d.ts.map +1 -0
- package/dist/lib/registry.d.ts +35 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/{safeRedirect.d.ts → safe-redirect.d.ts} +1 -1
- package/dist/lib/safe-redirect.d.ts.map +1 -0
- package/dist/lib/tags.d.ts +58 -0
- package/dist/lib/tags.d.ts.map +1 -0
- package/dist/lib/theme-vars.d.ts +9 -0
- package/dist/lib/theme-vars.d.ts.map +1 -1
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +8 -4
- package/dist/media/utils.d.ts +2 -0
- package/dist/media/utils.d.ts.map +1 -1
- package/dist/media/{videoPoster.d.ts → video-poster.d.ts} +1 -1
- package/dist/media/video-poster.d.ts.map +1 -0
- package/dist/schemas/audience.js +2 -2
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +14 -8
- package/dist/schemas/shared.d.ts +26 -0
- package/dist/schemas/shared.d.ts.map +1 -1
- package/dist/schemas/site-config.d.ts +6 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/dist/schemas/tags.d.ts +27 -0
- package/dist/schemas/tags.d.ts.map +1 -0
- package/dist/storage/index.js +1 -1
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/auth/cookies.ts +111 -0
- package/src/collab/rebase.ts +26 -2
- package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
- package/src/components/brandguide/ColorsEditable.tsx +1 -0
- package/src/components/chat/ChatPanel.tsx +2 -3
- package/src/components/chat/ChatWidget.tsx +2 -3
- package/src/components/docfile/DocFilePane.tsx +4 -12
- package/src/components/editor/ChildBlockWrapper.tsx +13 -19
- package/src/components/editor/ConflictDialog.tsx +27 -35
- package/src/components/editor/DeleteButton.tsx +2 -2
- package/src/components/editor/DragHandle.tsx +16 -3
- package/src/components/editor/DropEdgeIndicator.tsx +36 -10
- package/src/components/editor/InsertButton.tsx +2 -2
- package/src/components/editor/NavGroupRow.tsx +43 -14
- package/src/components/editor/PagesModal.tsx +99 -26
- package/src/components/editor/SectionWrapper.tsx +64 -23
- package/src/components/editor/SettingsButton.tsx +2 -2
- package/src/components/editor/SettingsForm.tsx +19 -2
- package/src/components/editor/StatusIndicator.tsx +4 -4
- package/src/components/editor/TagIndicator.tsx +80 -0
- package/src/components/primitives/DocumentDropZone.tsx +1 -1
- package/src/components/primitives/EditableGrid.tsx +15 -14
- package/src/components/primitives/EditableList.tsx +27 -74
- package/src/components/primitives/HeadingSection.tsx +5 -2
- package/src/components/primitives/ResolvedMedia.tsx +13 -0
- package/src/components/primitives/{defineHeadingSection.tsx → define-heading-section.tsx} +1 -0
- package/src/components/primitives/index.ts +1 -1
- package/src/components/primitives/useEditablePlainText.ts +5 -5
- package/src/components/sections/Button/CTAButton.tsx +3 -1
- package/src/components/sections/DoDontMedia/DoDontMediaBlock.tsx +4 -4
- package/src/components/sections/DoDontMedia/index.tsx +12 -3
- package/src/components/sections/IconList/DoDontToggle.tsx +2 -2
- package/src/components/sections/IconList/IconList.tsx +70 -37
- package/src/components/sections/IconList/IconListSettings.tsx +55 -17
- package/src/components/sections/IconList/index.tsx +10 -3
- package/src/components/sections/LinkHeading/index.tsx +1 -1
- package/src/components/sections/Media/MediaBlock.tsx +13 -2
- package/src/components/sections/Media/index.tsx +12 -4
- package/src/components/sections/Reference/ReferenceList.tsx +214 -0
- package/src/components/sections/Reference/ReferenceSettings.tsx +159 -0
- package/src/components/sections/Reference/index.tsx +47 -0
- package/src/components/sections/SectionSummary/SectionSummaryBlock.tsx +476 -0
- package/src/components/sections/SectionSummary/SectionSummarySettings.tsx +126 -0
- package/src/components/sections/SectionSummary/index.tsx +149 -0
- package/src/components/sections/SubHeading/index.tsx +1 -1
- package/src/components/sections/SubSubHeading/index.tsx +1 -1
- package/src/components/sections/brand-guide.ts +4 -3
- package/src/components/sections/core-sections.ts +2 -0
- package/src/components/shared/BrandSwatchesContext.ts +13 -0
- package/src/components/shared/Button.tsx +47 -34
- package/src/components/shared/Checkbox.tsx +21 -12
- package/src/components/shared/ConfirmDialog.tsx +87 -0
- package/src/components/shared/ConfirmInline.tsx +90 -0
- package/src/components/shared/ErrorBoundary.tsx +6 -18
- package/src/components/shared/Fab.tsx +15 -5
- package/src/components/shared/FabStack.tsx +28 -0
- package/src/components/shared/HistoryChangesModal.tsx +43 -41
- package/src/components/shared/HistoryPopover.tsx +7 -20
- package/src/components/shared/ImageBackgroundField.tsx +92 -0
- package/src/components/shared/{UploadRejectionAlert.tsx → InlineAlert.tsx} +21 -5
- package/src/components/shared/Input.tsx +23 -7
- package/src/components/shared/Navigation.tsx +22 -17
- package/src/components/shared/Notice.tsx +59 -0
- package/src/components/shared/Popover.tsx +7 -11
- package/src/components/shared/ReferenceSourcesContext.ts +27 -0
- package/src/components/shared/Select.tsx +5 -2
- package/src/components/shared/SplitButton.tsx +11 -17
- package/src/components/shared/SummaryAnchorsContext.ts +11 -0
- package/src/components/shared/Textarea.tsx +17 -2
- package/src/components/shared/ToolbarFrame.tsx +28 -0
- package/src/components/shared/VideoSourceField.tsx +1 -1
- package/src/components/shared/copy.ts +18 -0
- package/src/components/shared/icons.tsx +1 -17
- package/src/components/shell/AudienceAddForm.tsx +2 -2
- package/src/components/shell/AudienceDetailsPanel.tsx +23 -5
- package/src/components/shell/AudienceRow.tsx +22 -15
- package/src/components/shell/BugReportFAB.tsx +50 -40
- package/src/components/shell/CredentialList.tsx +33 -13
- package/src/components/shell/DocInspector.tsx +3 -12
- package/src/components/shell/DocumentEditorOverlay.tsx +33 -23
- package/src/components/shell/EditorContext.tsx +7 -0
- package/src/components/shell/EditorLoginForm.tsx +26 -36
- package/src/components/shell/EditorModal.tsx +8 -10
- package/src/components/shell/EditorShell.tsx +499 -339
- package/src/components/shell/HistoryToolbar.tsx +24 -26
- package/src/components/shell/MediaFolderTile.tsx +8 -3
- package/src/components/shell/MediaLibraryModal.tsx +59 -60
- package/src/components/shell/ProcessingIndicator.tsx +2 -2
- package/src/components/shell/RestoreModal.tsx +17 -16
- package/src/components/shell/SetPasswordForm.tsx +3 -3
- package/src/components/shell/SiteSettingsChatbot.tsx +61 -42
- package/src/components/shell/SiteSettingsDisplay.tsx +10 -20
- package/src/components/shell/SiteSettingsModal.tsx +24 -2
- package/src/components/shell/SiteSettingsTags.tsx +143 -0
- package/src/components/shell/SiteSettingsUsers.tsx +36 -26
- package/src/components/shell/SiteSettingsViewerAccess.tsx +2 -2
- package/src/components/shell/ViewerLoginForm.tsx +5 -4
- package/src/components/shell/reference-sources.ts +36 -0
- package/src/deck/index.ts +2 -2
- package/src/document/index.ts +2 -2
- package/src/hooks/useBlockDnd.ts +11 -0
- package/src/hooks/useBuildStatus.ts +53 -6
- package/src/hooks/useDocumentUploads.ts +1 -1
- package/src/hooks/useEditorPersistence.ts +141 -35
- package/src/hooks/useEditorPublish.ts +18 -27
- package/src/hooks/useMediaPipeline.ts +1 -1
- package/src/hooks/useNotice.ts +45 -0
- package/src/hooks/useSortableNavRow.ts +11 -0
- package/src/hooks/useSortableRow.ts +22 -2
- package/src/hooks/useStackedEscape.ts +62 -0
- package/src/lib/block-dnd.ts +6 -1
- package/src/lib/block-move.ts +3 -0
- package/src/lib/brand-swatches.ts +32 -0
- package/src/lib/container-ops.ts +5 -3
- package/src/lib/dexie.ts +15 -1
- package/src/lib/env.ts +9 -0
- package/src/lib/events.ts +1 -2
- package/src/lib/image-background.ts +31 -0
- package/src/lib/index.ts +5 -4
- package/src/lib/loader.ts +54 -55
- package/src/lib/nav-tree.ts +71 -0
- package/src/lib/nav.ts +196 -36
- package/src/lib/platform-broker.ts +32 -14
- package/src/lib/reference-type.ts +3 -0
- package/src/lib/reference-validation.ts +69 -0
- package/src/lib/references.ts +130 -0
- package/src/lib/registry.ts +37 -0
- package/src/lib/tags.ts +148 -0
- package/src/lib/theme-vars.ts +23 -0
- package/src/media/index.ts +1 -1
- package/src/media/utils.ts +18 -0
- package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
- package/src/schemas/index.ts +1 -0
- package/src/schemas/shared.ts +24 -1
- package/src/schemas/site-config.ts +6 -1
- package/src/schemas/tags.ts +33 -0
- package/src/storage/types.ts +1 -1
- package/src/types/database.ts +2 -0
- package/dist/chunk-DNY72JZN.js +0 -86
- package/dist/chunk-F2LYQPXV.js +0 -77
- package/dist/chunk-JH762VUH.js +0 -128
- package/dist/chunk-K7DYZXVU.js +0 -138
- package/dist/components/primitives/defineHeadingSection.d.ts.map +0 -1
- package/dist/components/primitives/gridDrop.d.ts.map +0 -1
- package/dist/components/shared/UploadRejectionAlert.d.ts.map +0 -1
- package/dist/components/shell/BuildStatusIndicator.d.ts +0 -7
- package/dist/components/shell/BuildStatusIndicator.d.ts.map +0 -1
- package/dist/components/shell/blockMoveDispatch.d.ts.map +0 -1
- package/dist/hooks/index.d.ts +0 -9
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/lib/grid.d.ts +0 -2
- package/dist/lib/grid.d.ts.map +0 -1
- package/dist/lib/safeRedirect.d.ts.map +0 -1
- package/dist/media/videoPoster.d.ts.map +0 -1
- package/src/components/brandguide/ColorSwatchSettings.d.ts.map +0 -1
- package/src/components/brandguide/Colors.d.ts.map +0 -1
- package/src/components/primitives/CustomParagraph.d.ts.map +0 -1
- package/src/components/primitives/EditableGrid.d.ts.map +0 -1
- package/src/components/primitives/EditablePlainText.d.ts.map +0 -1
- package/src/components/primitives/EditableRichText.d.ts.map +0 -1
- package/src/components/primitives/HeadingSection.d.ts.map +0 -1
- package/src/components/primitives/IconPicker.d.ts.map +0 -1
- package/src/components/primitives/LinkPopover.d.ts.map +0 -1
- package/src/components/primitives/MediaSettingsForms.d.ts.map +0 -1
- package/src/components/primitives/ResolvedMedia.d.ts.map +0 -1
- package/src/components/primitives/RichTextToolbar.d.ts.map +0 -1
- package/src/components/primitives/tiptap-presets.d.ts.map +0 -1
- package/src/components/primitives/useEditableCollection.d.ts.map +0 -1
- package/src/components/primitives/useEditablePlainText.d.ts.map +0 -1
- package/src/components/primitives/useEditableRichText.d.ts.map +0 -1
- package/src/components/sections/Button/CTAButton.d.ts.map +0 -1
- package/src/components/sections/Button/index.d.ts.map +0 -1
- package/src/components/sections/Colors/index.d.ts.map +0 -1
- package/src/components/sections/IconList/IconList.d.ts.map +0 -1
- package/src/components/sections/IconList/IconListSettings.d.ts.map +0 -1
- package/src/components/sections/IconList/index.d.ts.map +0 -1
- package/src/components/sections/LinkHeading/index.d.ts.map +0 -1
- package/src/components/sections/Prose/Prose.d.ts.map +0 -1
- package/src/components/sections/Prose/index.d.ts.map +0 -1
- package/src/components/sections/SubHeading/index.d.ts.map +0 -1
- package/src/components/sections/SubSubHeading/index.d.ts.map +0 -1
- package/src/components/sections/register-schemas.d.ts.map +0 -1
- package/src/components/shared/Button.d.ts.map +0 -1
- package/src/components/shared/Checkbox.d.ts.map +0 -1
- package/src/components/shared/FormLabel.d.ts.map +0 -1
- package/src/components/shared/IconButton.d.ts.map +0 -1
- package/src/components/shared/Input.d.ts.map +0 -1
- package/src/components/shared/Select.d.ts.map +0 -1
- package/src/components/shared/icons.d.ts.map +0 -1
- package/src/components/shell/BuildStatusIndicator.tsx +0 -59
- package/src/components/shell/MediaLibraryContext.d.ts.map +0 -1
- package/src/hooks/index.ts +0 -8
- package/src/hooks/useResolvedMedia.d.ts.map +0 -1
- package/src/lib/cn.d.ts.map +0 -1
- package/src/lib/grid.d.ts.map +0 -1
- package/src/lib/grid.ts +0 -7
- package/src/lib/icons.d.ts.map +0 -1
- package/src/lib/loader.d.ts.map +0 -1
- package/src/lib/nav.d.ts.map +0 -1
- package/src/lib/registry.d.ts.map +0 -1
- package/src/lib/sanitize.d.ts.map +0 -1
- package/src/media/github.d.ts.map +0 -1
- package/src/media/index.d.ts.map +0 -1
- package/src/media/queue.d.ts.map +0 -1
- package/src/media/resolve.d.ts.map +0 -1
- package/src/media/types.d.ts.map +0 -1
- package/src/media/utils.d.ts.map +0 -1
- package/src/media/videoPoster.d.ts.map +0 -1
- package/src/schemas/auth.d.ts.map +0 -1
- package/src/schemas/media-grid-options.d.ts.map +0 -1
- package/src/schemas/media.d.ts.map +0 -1
- package/src/schemas/sections.d.ts.map +0 -1
- package/src/schemas/shared.d.ts.map +0 -1
- package/src/schemas/site-config.d.ts.map +0 -1
- /package/dist/{chunk-B7VBSE5X.js → chunk-3JDTXOIG.js} +0 -0
- /package/dist/{chunk-CXZ5YJG4.js → chunk-L45X7DKX.js} +0 -0
- /package/src/components/primitives/{gridDrop.ts → grid-drop.ts} +0 -0
- /package/src/components/shell/{blockMoveDispatch.ts → block-move-dispatch.ts} +0 -0
- /package/src/lib/{safeRedirect.ts → safe-redirect.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, Suspense, lazy, useState, useCallback, useEffect, useRef, useMemo, type ReactNode } from "react";
|
|
1
|
+
import { Fragment, Suspense, lazy, useContext, useState, useCallback, useEffect, useRef, useMemo, type ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
4
4
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
@@ -23,7 +23,7 @@ import { SiteConfigSchema } from "../../schemas/site-config";
|
|
|
23
23
|
import { ensureSanitizer } from "../../lib/sanitize";
|
|
24
24
|
import { resolveChatbotName } from "../../lib/chatbot";
|
|
25
25
|
import { EditorProvider, useEditorContext, type HistoryState } from "./EditorContext";
|
|
26
|
-
import { makeBlockMoveDispatch } from "./
|
|
26
|
+
import { makeBlockMoveDispatch } from "./block-move-dispatch";
|
|
27
27
|
import { ROOT_CONTAINER_ID, resolveDropIndex } from "../../lib/block-dnd";
|
|
28
28
|
import type { BlockMove } from "../../lib/block-move";
|
|
29
29
|
import { EditorModalProvider, useEditorModal } from "./EditorModalContext";
|
|
@@ -36,7 +36,7 @@ import { ProcessingIndicator } from "./ProcessingIndicator";
|
|
|
36
36
|
import { SectionSkeleton } from "./SectionSkeleton";
|
|
37
37
|
import { SectionTypePicker } from "./SectionTypePicker";
|
|
38
38
|
import { ensureSectionsRegistered } from "../sections/register";
|
|
39
|
-
import { getSection, getAllSections, getSchema } from "../../lib/registry";
|
|
39
|
+
import { getSection, getAllSections, getSchema, type SiteTagging } from "../../lib/registry";
|
|
40
40
|
import { cornerRadiusValue } from "../../lib/corner-radius";
|
|
41
41
|
import { primaryVarValue, accentVarValue, onPrimaryVarValue } from "../../lib/theme-vars";
|
|
42
42
|
import { rebaseMediaManifest } from "../../lib/media-folders";
|
|
@@ -87,10 +87,14 @@ import { useContentLifecycle } from "../../hooks/useContentLifecycle";
|
|
|
87
87
|
import { useBuildStatus } from "../../hooks/useBuildStatus";
|
|
88
88
|
import { useMediaPipeline } from "../../hooks/useMediaPipeline";
|
|
89
89
|
import { formatTimestamp } from "../../lib/timestamp";
|
|
90
|
-
import { generateSiteNav } from "../../lib/nav";
|
|
90
|
+
import { generateSiteNav, summaryNavAnchors, pageReferenceCopies } from "../../lib/nav";
|
|
91
|
+
import { expandReferences, type ReferenceContent } from "../../lib/references";
|
|
92
|
+
import { REFERENCE_TYPE } from "../../lib/reference-type";
|
|
93
|
+
import { referenceShowsInPreview } from "../sections/Reference/ReferenceList";
|
|
91
94
|
import { deriveSiteDocuments, type SiteDocument } from "../../lib/documents";
|
|
92
95
|
import { setSiteDocuments, closeDocFile } from "../docfile/doc-file-state";
|
|
93
|
-
import { pagesInNavOrder, moveNavEntry, addGroup, renameGroup, setGroupStartCollapsed, deleteGroup, type NavLocation } from "../../lib/nav-tree";
|
|
96
|
+
import { pagesInNavOrder, moveNavEntry, describeNavMove, addGroup, renameGroup, setGroupStartCollapsed, deleteGroup, type NavLocation } from "../../lib/nav-tree";
|
|
97
|
+
import { siteTags, addTag, renameTag, deleteTag, setSectionTagged, findTagByLabel } from "../../lib/tags";
|
|
94
98
|
import { siteNavChangeEvent, pageSelectEvent, darkModeEvent, siteNameChangeEvent, historySelectEvent, historyRestoreEvent, historyRestoreFailedEvent, editorDirtyEvent, audienceDetailsEvent } from "../../lib/events";
|
|
95
99
|
import { AudienceDetailsPanel, type AudienceDetail } from "./AudienceDetailsPanel";
|
|
96
100
|
import { buildAudienceOverview, type SectionDisplay } from "../../lib/audience-overview";
|
|
@@ -103,18 +107,28 @@ import {
|
|
|
103
107
|
moveSection as moveSectionReducer,
|
|
104
108
|
} from "../../lib/pages";
|
|
105
109
|
import { PagesContext } from "../shared/PagesContext";
|
|
110
|
+
import { ReferenceSourcesContext, type ReferenceSources } from "../shared/ReferenceSourcesContext";
|
|
111
|
+
import { publishedReferenceSources } from "./reference-sources";
|
|
112
|
+
import { BrandSwatchesContext } from "../shared/BrandSwatchesContext";
|
|
113
|
+
import { collectBrandSwatches } from "../../lib/brand-swatches";
|
|
114
|
+
import { SummaryAnchorsContext } from "../shared/SummaryAnchorsContext";
|
|
106
115
|
import { PagesModal } from "../editor/PagesModal";
|
|
107
116
|
import { cn } from "../../lib/cn";
|
|
108
|
-
import {
|
|
117
|
+
import { InlineAlert, formatRejections } from "../shared/InlineAlert";
|
|
118
|
+
import { Notice } from "../shared/Notice";
|
|
119
|
+
import { ConfirmDialog } from "../shared/ConfirmDialog";
|
|
120
|
+
import type { NoticeState } from "../../hooks/useNotice";
|
|
121
|
+
import { useNotice } from "../../hooks/useNotice";
|
|
109
122
|
import { Button } from "../shared/Button";
|
|
110
123
|
import { SplitButton } from "../shared/SplitButton";
|
|
124
|
+
import { ToolbarFrame } from "../shared/ToolbarFrame";
|
|
111
125
|
import { IconButton } from "../shared/IconButton";
|
|
112
126
|
import Fab from "../shared/Fab";
|
|
127
|
+
import { FabStack } from "../shared/FabStack";
|
|
113
128
|
import { Tooltip } from "../shared/Tooltip";
|
|
114
129
|
import { SegmentedControl } from "../shared/SegmentedControl";
|
|
115
130
|
import { computeDisplaySections } from "./view-branch";
|
|
116
|
-
import {
|
|
117
|
-
import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, X } from "lucide-react";
|
|
131
|
+
import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, Settings, X, FileText, Presentation, Eye, Pencil } from "lucide-react";
|
|
118
132
|
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
119
133
|
import { HistoryToolbar } from "./HistoryToolbar";
|
|
120
134
|
import { RestoreModal } from "./RestoreModal";
|
|
@@ -149,6 +163,9 @@ interface SavedContent {
|
|
|
149
163
|
* Their bodies are absent from `sections` while their index slots survive —
|
|
150
164
|
* the editor must surface them, not silently render around them. */
|
|
151
165
|
unparseableSectionIds: string[];
|
|
166
|
+
/** tags.json held entries this version couldn't represent: tags are
|
|
167
|
+
* read-only this session (the loader's tagsDegraded contract). */
|
|
168
|
+
tagsDegraded: boolean;
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
interface Props {
|
|
@@ -241,6 +258,14 @@ export default function EditorShell({
|
|
|
241
258
|
const deletedSectionsRef = useRef<Set<string>>(deletedSections);
|
|
242
259
|
useEffect(() => { deletedSectionsRef.current = deletedSections; }, [deletedSections]);
|
|
243
260
|
const [pendingDeleteSectionId, setPendingDeleteSectionId] = useState<string | null>(null);
|
|
261
|
+
// One shared aria-live region for every reorder — sections (via
|
|
262
|
+
// onReorderSections, the dispatch point shared by SectionOrderingModal's
|
|
263
|
+
// drag and SectionWrapper's Move up/down) and pages/groups (via
|
|
264
|
+
// handleMoveNavEntry, shared the same way by PagesModal/NavGroupRow's drag
|
|
265
|
+
// and their own Move up/down). The section canvas's own per-block drag
|
|
266
|
+
// (onBlockMove) is a separate, unlisted reorder surface and does not
|
|
267
|
+
// announce here.
|
|
268
|
+
const [reorderAnnouncement, setReorderAnnouncement] = useState("");
|
|
244
269
|
const [savedSha, setSavedSha] = useState<string | null>(null);
|
|
245
270
|
const [mainSha, setMainSha] = useState<string | null>(null);
|
|
246
271
|
// Mirror savedSha into a ref so the save handler reads the latest draft
|
|
@@ -259,6 +284,13 @@ export default function EditorShell({
|
|
|
259
284
|
const mainIndexRef = useRef<SiteIndex | null>(null);
|
|
260
285
|
useEffect(() => { mainIndexRef.current = mainIndex; }, [mainIndex]);
|
|
261
286
|
const [viewSections, setViewSections] = useState<LoadedSection[] | null>(null);
|
|
287
|
+
// The fetched main index that goes with viewSections — References resolve
|
|
288
|
+
// against it in the Published preview, exactly as the viewer does.
|
|
289
|
+
const [viewIndex, setViewIndex] = useState<SiteIndex | null>(null);
|
|
290
|
+
const setView = useCallback((view: { sections: LoadedSection[]; index: SiteIndex | null } | null) => {
|
|
291
|
+
setViewSections(view?.sections ?? null);
|
|
292
|
+
setViewIndex(view?.index ?? null);
|
|
293
|
+
}, []);
|
|
262
294
|
const [showRestoreModal, setShowRestoreModal] = useState(false);
|
|
263
295
|
const [showOrderingModal, setShowOrderingModal] = useState(false);
|
|
264
296
|
const [showPagesModal, setShowPagesModal] = useState(false);
|
|
@@ -278,6 +310,7 @@ export default function EditorShell({
|
|
|
278
310
|
// — surfaced as a persistent banner so a "missing" section isn't mistaken for
|
|
279
311
|
// a deleted one and repaired by data loss.
|
|
280
312
|
const [unparseableSectionIds, setUnparseableSectionIds] = useState<string[]>([]);
|
|
313
|
+
const [tagsDegraded, setTagsDegraded] = useState(false);
|
|
281
314
|
// Non-fatal warnings from the last successful save (e.g. nav.json replacement
|
|
282
315
|
// declined by the degraded-nav guard) — without a surface, the save reads as
|
|
283
316
|
// fully landed when part of it was refused.
|
|
@@ -299,15 +332,8 @@ export default function EditorShell({
|
|
|
299
332
|
>(null);
|
|
300
333
|
// Collaboration: transient toast ("Drew took over Hero …") and the section a
|
|
301
334
|
// takeover confirmation is pending for.
|
|
302
|
-
const
|
|
335
|
+
const { notice: remoteNotice, show: showCollabNotice } = useNotice();
|
|
303
336
|
const [pendingTakeoverId, setPendingTakeoverId] = useState<string | null>(null);
|
|
304
|
-
// Lifted from EditorContext.historyState by HistoryWatcher (mirrors
|
|
305
|
-
// ViewBranchWatcher's setViewSections lift) — the shell body renders the
|
|
306
|
-
// EditorProvider, so it can't read historyState via useEditorContext directly.
|
|
307
|
-
// Drives the Document FAB gate below: history is presentation only, never
|
|
308
|
-
// editing (branch-preview already routes to DocumentPresentOverlay; this
|
|
309
|
-
// covers the separate historySelectEvent commit view).
|
|
310
|
-
const [historyActive, setHistoryActive] = useState(false);
|
|
311
337
|
|
|
312
338
|
const siteIndexRef = useRef<SiteIndex>({ siteId, pages: [], sections: {} });
|
|
313
339
|
// Ids of sections that exist on the remote (loaded from server, or persisted by
|
|
@@ -352,8 +378,6 @@ export default function EditorShell({
|
|
|
352
378
|
// Mirror of the live collab session, read by handlers/callbacks defined before
|
|
353
379
|
// useCollabSession is instantiated (breaks the definition-order cycle).
|
|
354
380
|
const collabRef = useRef<UseCollabSessionResult | null>(null);
|
|
355
|
-
const noticeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
356
|
-
useEffect(() => () => { if (noticeTimerRef.current) clearTimeout(noticeTimerRef.current); }, []);
|
|
357
381
|
// Per-section memoized onEditIntent closures — SectionWrapper re-binds its
|
|
358
382
|
// focus listeners whenever onEditIntent's identity changes, so each section
|
|
359
383
|
// must get a STABLE function instance across renders.
|
|
@@ -363,12 +387,14 @@ export default function EditorShell({
|
|
|
363
387
|
// one's lock is released if it carries no unsaved work.
|
|
364
388
|
const focusedSectionIdRef = useRef<string | null>(null);
|
|
365
389
|
|
|
366
|
-
// Transient collaboration toast (single slot, 8s auto-dismiss
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
390
|
+
// Transient collaboration toast (single slot, 8s auto-dismiss unless
|
|
391
|
+
// `tone` is "error", which useNotice leaves sticky — a load failure needs a
|
|
392
|
+
// deliberate dismiss, not an 8s countdown). Its own useNotice instance —
|
|
393
|
+
// must stay independent of toolbar publishFeedback, which can be visible at
|
|
394
|
+
// the same time.
|
|
395
|
+
const showRemoteNotice = useCallback((text: string, tone: NoticeState["tone"] = "info") => {
|
|
396
|
+
showCollabNotice(tone, text, tone === "error" ? undefined : { ttlMs: 8000 });
|
|
397
|
+
}, [showCollabNotice]);
|
|
372
398
|
|
|
373
399
|
// Broadcast piggyback: after a debounced flush, forward held-lock section
|
|
374
400
|
// edits + index changes to peers. Only sections THIS editor holds are
|
|
@@ -401,6 +427,19 @@ export default function EditorShell({
|
|
|
401
427
|
return resolvedActivePage.order.map((id) => sectionsById[id]).filter((s): s is LoadedSection => !!s);
|
|
402
428
|
}, [resolvedActivePage, sectionsById]);
|
|
403
429
|
|
|
430
|
+
// The page as its nav sees it: each Reference replaced by the copies it
|
|
431
|
+
// would publish, so their headings join the sidebar as on the viewer. The
|
|
432
|
+
// canvas renders those copies from referenceCopies, stamped over the same
|
|
433
|
+
// list, so every sidebar link lands on a rendered id.
|
|
434
|
+
const navSections = useMemo(
|
|
435
|
+
() => expandReferences(activeSections, { index: siteIndex, allSections: sections, audience: null }),
|
|
436
|
+
[activeSections, siteIndex, sections],
|
|
437
|
+
);
|
|
438
|
+
const referenceCopies = useMemo(
|
|
439
|
+
() => pageReferenceCopies(activeSections, { index: siteIndex, allSections: sections }),
|
|
440
|
+
[activeSections, siteIndex, sections],
|
|
441
|
+
);
|
|
442
|
+
|
|
404
443
|
// Same model the /audiences route builds server-side, from the shell's own
|
|
405
444
|
// live state — so an editor sees their SAVED draft's access, not the published
|
|
406
445
|
// snapshot the route would show.
|
|
@@ -487,6 +526,8 @@ export default function EditorShell({
|
|
|
487
526
|
[siteIndex.pages, getPageHeadings],
|
|
488
527
|
);
|
|
489
528
|
|
|
529
|
+
const brandSwatches = useMemo(() => collectBrandSwatches(sections.map((s) => s.section)), [sections]);
|
|
530
|
+
|
|
490
531
|
const mediaPipeline = useMediaPipeline({
|
|
491
532
|
siteConfig,
|
|
492
533
|
mediaManifest,
|
|
@@ -513,7 +554,7 @@ export default function EditorShell({
|
|
|
513
554
|
// draft head the same way.
|
|
514
555
|
const fetchSavedContent = useCallback(async (): Promise<SavedContent> => {
|
|
515
556
|
const response = await fetch("/api/content?branch=saved");
|
|
516
|
-
if (!response.ok) throw new Error(`
|
|
557
|
+
if (!response.ok) throw new Error(`Couldn't load content: ${response.status}`);
|
|
517
558
|
const data = await response.json();
|
|
518
559
|
return {
|
|
519
560
|
sha: data.sha,
|
|
@@ -526,6 +567,7 @@ export default function EditorShell({
|
|
|
526
567
|
mediaManifest: data.mediaManifest as MediaManifest | undefined,
|
|
527
568
|
documentManifest: data.documentManifest as DocumentManifest | undefined,
|
|
528
569
|
unparseableSectionIds: (data.unparseableSectionIds ?? []) as string[],
|
|
570
|
+
tagsDegraded: data.tagsDegraded === true,
|
|
529
571
|
};
|
|
530
572
|
}, []);
|
|
531
573
|
|
|
@@ -539,6 +581,7 @@ export default function EditorShell({
|
|
|
539
581
|
try {
|
|
540
582
|
const remote = await fetchSavedContent();
|
|
541
583
|
setUnparseableSectionIds(remote.unparseableSectionIds);
|
|
584
|
+
setTagsDegraded(remote.tagsDegraded);
|
|
542
585
|
const dirtyRows = await getDirtySectionRows();
|
|
543
586
|
const dirtyIds = new Set(dirtyRows.map((r) => r.sectionId));
|
|
544
587
|
|
|
@@ -1085,7 +1128,7 @@ export default function EditorShell({
|
|
|
1085
1128
|
useEffect(() => {
|
|
1086
1129
|
if (!resolvedActivePage) return;
|
|
1087
1130
|
const nav = generateSiteNav({
|
|
1088
|
-
index: siteIndex, activeSections,
|
|
1131
|
+
index: siteIndex, activeSections: navSections,
|
|
1089
1132
|
// The Audience Details surface is not a page: clearing the active id drops
|
|
1090
1133
|
// the previous page's highlight (and its heading tree), so the sidebar
|
|
1091
1134
|
// never shows you in two places at once.
|
|
@@ -1094,7 +1137,7 @@ export default function EditorShell({
|
|
|
1094
1137
|
documents: siteDocuments ?? [],
|
|
1095
1138
|
});
|
|
1096
1139
|
siteNavChangeEvent.dispatch(nav);
|
|
1097
|
-
}, [siteIndex,
|
|
1140
|
+
}, [siteIndex, navSections, resolvedActivePage, siteDocuments, showAudienceDetails]);
|
|
1098
1141
|
|
|
1099
1142
|
const goToPage = useCallback((pageId: string) => {
|
|
1100
1143
|
const page = pageById(siteIndexRef.current, pageId);
|
|
@@ -1112,6 +1155,30 @@ export default function EditorShell({
|
|
|
1112
1155
|
|
|
1113
1156
|
// Nav island → shell page selection.
|
|
1114
1157
|
useEffect(() => pageSelectEvent.listen((id) => { setShowAudienceDetails(false); goToPage(id); }), [goToPage]);
|
|
1158
|
+
|
|
1159
|
+
// A Reference's "Edit source": open the source's page, then scroll to it once
|
|
1160
|
+
// that page's sections have rendered.
|
|
1161
|
+
const [scrollToSectionId, setScrollToSectionId] = useState<string | null>(null);
|
|
1162
|
+
const jumpToSection = useCallback((sectionId: string) => {
|
|
1163
|
+
const page = siteIndexRef.current.pages.find((p) => p.order.includes(sectionId));
|
|
1164
|
+
if (!page) return;
|
|
1165
|
+
pageSelectEvent.dispatch(page.id);
|
|
1166
|
+
setScrollToSectionId(sectionId);
|
|
1167
|
+
}, []);
|
|
1168
|
+
// One attempt, cleared either way: a section the canvas isn't showing (a
|
|
1169
|
+
// draft in the published preview) must not leave a scroll armed for later.
|
|
1170
|
+
useEffect(() => {
|
|
1171
|
+
if (!scrollToSectionId) return;
|
|
1172
|
+
document.querySelector(`[data-section-id="${CSS.escape(scrollToSectionId)}"]`)
|
|
1173
|
+
?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1174
|
+
setScrollToSectionId(null);
|
|
1175
|
+
}, [scrollToSectionId, activeSections]);
|
|
1176
|
+
|
|
1177
|
+
const referenceSources = useMemo<ReferenceSources>(() => publishedReferenceSources({
|
|
1178
|
+
viewSections, viewIndex, mainIndex, activePageId: resolvedActivePage?.id ?? null, audiences, mediaManifest, jumpToSection,
|
|
1179
|
+
}) ?? { index: siteIndex, sections, audiences, mediaManifest, copies: referenceCopies, jumpToSection },
|
|
1180
|
+
[viewSections, viewIndex, mainIndex, resolvedActivePage, siteIndex, sections, audiences, mediaManifest, referenceCopies, jumpToSection]);
|
|
1181
|
+
|
|
1115
1182
|
useEffect(() => audienceDetailsEvent.listen(() => setShowAudienceDetails(true)), []);
|
|
1116
1183
|
|
|
1117
1184
|
// Fetched on open rather than at mount: most editing sessions never look at
|
|
@@ -1251,8 +1318,11 @@ export default function EditorShell({
|
|
|
1251
1318
|
loadedIndex = data.index;
|
|
1252
1319
|
loadedConfig = SiteConfigSchema.parse(data.siteConfig);
|
|
1253
1320
|
if (data.mediaManifest) loadedManifest = data.mediaManifest;
|
|
1321
|
+
// A degraded tags read is cached as "not loaded", so a later cache-hit
|
|
1322
|
+
// load stays read-only for tags instead of trusting the partial list.
|
|
1323
|
+
const { tags: _partialTags, ...untaggedIndex } = data.index;
|
|
1254
1324
|
await cacheContent(
|
|
1255
|
-
data.sha, data.sections, data.index, data.siteConfig,
|
|
1325
|
+
data.sha, data.sections, data.tagsDegraded ? untaggedIndex : data.index, data.siteConfig,
|
|
1256
1326
|
{ media: loadedManifest, documents: data.documentManifest ?? {} },
|
|
1257
1327
|
{
|
|
1258
1328
|
savedBranchSha: data.savedBranchSha,
|
|
@@ -1269,6 +1339,7 @@ export default function EditorShell({
|
|
|
1269
1339
|
setChangedSectionIds(new Set(data.changedSectionIds));
|
|
1270
1340
|
setMainIndex(data.mainIndex);
|
|
1271
1341
|
setUnparseableSectionIds(data.unparseableSectionIds);
|
|
1342
|
+
setTagsDegraded(data.tagsDegraded);
|
|
1272
1343
|
}
|
|
1273
1344
|
|
|
1274
1345
|
if (cancelled) return;
|
|
@@ -1321,7 +1392,7 @@ export default function EditorShell({
|
|
|
1321
1392
|
|
|
1322
1393
|
loadContent().catch((err) => {
|
|
1323
1394
|
console.error("Failed to load editor content:", err);
|
|
1324
|
-
setShellState({ phase: "error", message: err instanceof Error ? err.message : "
|
|
1395
|
+
setShellState({ phase: "error", message: err instanceof Error ? err.message : "Couldn't load content" });
|
|
1325
1396
|
});
|
|
1326
1397
|
return () => { cancelled = true; };
|
|
1327
1398
|
}, [headSha, draftHeadSha, siteId, applySiteConfigPreview, fetchSavedContent]);
|
|
@@ -1354,7 +1425,12 @@ export default function EditorShell({
|
|
|
1354
1425
|
return null;
|
|
1355
1426
|
}).filter((s): s is LoadedSection => s !== null),
|
|
1356
1427
|
);
|
|
1357
|
-
|
|
1428
|
+
// A row written before tags shipped carries none, and that build could
|
|
1429
|
+
// not have edited them — keep the freshly fetched registry rather than
|
|
1430
|
+
// installing an index that reads as "tags never loaded".
|
|
1431
|
+
setSiteIndex(restoredIndex.tags === undefined && siteIndexRef.current.tags !== undefined
|
|
1432
|
+
? { ...restoredIndex, tags: siteIndexRef.current.tags }
|
|
1433
|
+
: restoredIndex);
|
|
1358
1434
|
// Pending deletions come from the snapshot's own persisted intent, and are
|
|
1359
1435
|
// intersected with what the branch still has so an already-applied deletion
|
|
1360
1436
|
// can't be replayed.
|
|
@@ -1470,6 +1546,11 @@ export default function EditorShell({
|
|
|
1470
1546
|
);
|
|
1471
1547
|
|
|
1472
1548
|
const handleDiscard = useCallback(async () => {
|
|
1549
|
+
// Drop Site Settings' debounced config write too, or its trailing edge
|
|
1550
|
+
// would write the discarded config back into Dexie.
|
|
1551
|
+
if (configFlushTimerRef.current) { clearTimeout(configFlushTimerRef.current); configFlushTimerRef.current = null; }
|
|
1552
|
+
pendingConfigRef.current = null;
|
|
1553
|
+
await persistence.discardPending();
|
|
1473
1554
|
await discardLocalChanges();
|
|
1474
1555
|
setLocalChangesExist(false);
|
|
1475
1556
|
setDirtySectionIds(new Set());
|
|
@@ -1614,6 +1695,9 @@ export default function EditorShell({
|
|
|
1614
1695
|
setSiteIndex((prev) => reorderVisibleSectionInPage(prev, resolvedActivePage.id, visibleIds, fromIndex, toIndex));
|
|
1615
1696
|
persistence.markIndexDirty();
|
|
1616
1697
|
setLocalChangesExist(true);
|
|
1698
|
+
const movedType = activeSections[fromIndex]?.section.type;
|
|
1699
|
+
const label = (movedType && getSection(movedType)?.label) || "Section";
|
|
1700
|
+
setReorderAnnouncement(`Moved ${label} to position ${toIndex + 1} of ${visibleIds.length}`);
|
|
1617
1701
|
},
|
|
1618
1702
|
[persistence, resolvedActivePage, activeSections],
|
|
1619
1703
|
);
|
|
@@ -1655,8 +1739,16 @@ export default function EditorShell({
|
|
|
1655
1739
|
}, [persistence]);
|
|
1656
1740
|
|
|
1657
1741
|
const handleMoveNavEntry = useCallback((from: NavLocation, to: NavLocation) => {
|
|
1658
|
-
|
|
1742
|
+
// Read the pre-move index from the ref (mirrors onBlockMove's getState
|
|
1743
|
+
// pattern) rather than the updater's `prev`, so describeNavMove can
|
|
1744
|
+
// resolve `from`'s label/kind against the SAME snapshot moveNavEntry acts
|
|
1745
|
+
// on, and the two calls can't observe different states.
|
|
1746
|
+
const prevIndex = siteIndexRef.current;
|
|
1747
|
+
const nextIndex = moveNavEntry(prevIndex, from, to);
|
|
1748
|
+
setSiteIndex(() => nextIndex);
|
|
1659
1749
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1750
|
+
const message = describeNavMove(prevIndex, nextIndex, from);
|
|
1751
|
+
if (message) setReorderAnnouncement(message);
|
|
1660
1752
|
}, [persistence]);
|
|
1661
1753
|
|
|
1662
1754
|
const handleAddGroup = useCallback(() => {
|
|
@@ -1682,6 +1774,48 @@ export default function EditorShell({
|
|
|
1682
1774
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1683
1775
|
}, [persistence]);
|
|
1684
1776
|
|
|
1777
|
+
// Section tags: the same index-state edit + markIndexDirty as the nav group
|
|
1778
|
+
// handlers above. Every save then carries the runtime `tags`, which
|
|
1779
|
+
// content-ops writes to the tags.json sidecar.
|
|
1780
|
+
const handleCreateTag = useCallback((label: string, sectionId?: string) => {
|
|
1781
|
+
const id = typeof crypto !== "undefined" && crypto.randomUUID ? `tag_${crypto.randomUUID()}` : `tag_${Date.now()}`;
|
|
1782
|
+
setSiteIndex((prev) => {
|
|
1783
|
+
// Typing an existing tag's name reuses it rather than minting a twin.
|
|
1784
|
+
const existing = findTagByLabel(siteTags(prev), label);
|
|
1785
|
+
if (existing) return sectionId ? setSectionTagged(prev, sectionId, existing.id, true) : prev;
|
|
1786
|
+
return addTag(prev, id, label, sectionId);
|
|
1787
|
+
});
|
|
1788
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1789
|
+
}, [persistence]);
|
|
1790
|
+
|
|
1791
|
+
const handleRenameTag = useCallback((tagId: string, label: string) => {
|
|
1792
|
+
setSiteIndex((prev) => renameTag(prev, tagId, label));
|
|
1793
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1794
|
+
}, [persistence]);
|
|
1795
|
+
|
|
1796
|
+
const handleDeleteTag = useCallback((tagId: string) => {
|
|
1797
|
+
setSiteIndex((prev) => deleteTag(prev, tagId));
|
|
1798
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1799
|
+
}, [persistence]);
|
|
1800
|
+
|
|
1801
|
+
const handleSectionTagToggle = useCallback((sectionId: string, tagId: string, tagged: boolean) => {
|
|
1802
|
+
setSiteIndex((prev) => setSectionTagged(prev, sectionId, tagId, tagged));
|
|
1803
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1804
|
+
}, [persistence]);
|
|
1805
|
+
|
|
1806
|
+
const tags = useMemo(() => siteTags(siteIndex), [siteIndex]);
|
|
1807
|
+
// Absent `tags` means the registry was never loaded (a Dexie row or cache
|
|
1808
|
+
// entry from a build that predates tags) — NOT an empty registry. Editing it
|
|
1809
|
+
// would post `tags: [new]` and wholesale-replace the real tags.json, so
|
|
1810
|
+
// every tag edit is off until a load that read the sidecar. A degraded read
|
|
1811
|
+
// (tags.json held entries this version couldn't parse) is the same hazard:
|
|
1812
|
+
// saving would drop the entries it skipped.
|
|
1813
|
+
const tagsEditable = siteIndex.tags !== undefined && !tagsDegraded;
|
|
1814
|
+
const tagging = useMemo<SiteTagging>(() => ({
|
|
1815
|
+
tags, editable: tagsEditable,
|
|
1816
|
+
onCreate: handleCreateTag, onRename: handleRenameTag, onDelete: handleDeleteTag, onToggle: handleSectionTagToggle,
|
|
1817
|
+
}), [tags, tagsEditable, handleCreateTag, handleRenameTag, handleDeleteTag, handleSectionTagToggle]);
|
|
1818
|
+
|
|
1685
1819
|
const handleSetHome = useCallback((id: string) => {
|
|
1686
1820
|
setSiteIndex((prev) => setHomePage(prev, id));
|
|
1687
1821
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
@@ -1902,7 +2036,7 @@ export default function EditorShell({
|
|
|
1902
2036
|
return historyRestoreEvent.listen(async ({ sha }) => {
|
|
1903
2037
|
try {
|
|
1904
2038
|
const res = await fetch(`/api/content/history?sha=${sha}`);
|
|
1905
|
-
if (!res.ok) throw new Error(`
|
|
2039
|
+
if (!res.ok) throw new Error(`Couldn't load version: ${res.status}`);
|
|
1906
2040
|
const data = await res.json();
|
|
1907
2041
|
const ok = await handleHistoryRestore({
|
|
1908
2042
|
sections: data.sections,
|
|
@@ -1933,7 +2067,7 @@ export default function EditorShell({
|
|
|
1933
2067
|
return (
|
|
1934
2068
|
<div className="flex min-h-[50vh] items-center justify-center">
|
|
1935
2069
|
<div className="text-center">
|
|
1936
|
-
<p className="text-sm text-
|
|
2070
|
+
<p className="text-sm text-danger">{shellState.message}</p>
|
|
1937
2071
|
<Button variant="secondary" size="md" className="mt-3" onClick={() => window.location.reload()}>
|
|
1938
2072
|
Retry
|
|
1939
2073
|
</Button>
|
|
@@ -1963,15 +2097,21 @@ export default function EditorShell({
|
|
|
1963
2097
|
|
|
1964
2098
|
return (
|
|
1965
2099
|
<PagesContext.Provider value={pagesContextValue}>
|
|
2100
|
+
<ReferenceSourcesContext.Provider value={referenceSources}>
|
|
2101
|
+
<BrandSwatchesContext.Provider value={brandSwatches}>
|
|
1966
2102
|
<EditorProvider>
|
|
1967
2103
|
<ViewBranchWatcher
|
|
1968
|
-
|
|
1969
|
-
onError={
|
|
2104
|
+
setView={setView}
|
|
2105
|
+
onError={showRemoteNotice}
|
|
1970
2106
|
/>
|
|
1971
|
-
<HistoryWatcher
|
|
2107
|
+
<HistoryWatcher />
|
|
1972
2108
|
<EditorModalProvider>
|
|
1973
2109
|
<MediaLibraryContext.Provider value={mediaLibraryValue}>
|
|
1974
2110
|
<div className="editor-shell relative">
|
|
2111
|
+
{/* Announces every section/page/group reorder — from drag or the
|
|
2112
|
+
Move up/down buttons — since neither leaves a visible confirmation
|
|
2113
|
+
a screen reader user would otherwise get. */}
|
|
2114
|
+
<p aria-live="polite" className="sr-only">{reorderAnnouncement}</p>
|
|
1975
2115
|
<EditorToolbar
|
|
1976
2116
|
buttonState={buttonState}
|
|
1977
2117
|
localChangesExist={localChangesExist}
|
|
@@ -2000,42 +2140,22 @@ export default function EditorShell({
|
|
|
2000
2140
|
audiences={audiences}
|
|
2001
2141
|
authoringDefaults={authoringDefaults}
|
|
2002
2142
|
onAuthoringDefaultsChange={onAuthoringDefaultsChange}
|
|
2143
|
+
docEditEnabled={Boolean(resolvedActivePage?.document?.enabled)}
|
|
2144
|
+
onDocEditClick={() => setShowDocumentEditor(true)}
|
|
2145
|
+
// Bug reports persist via Supabase. On the password-only
|
|
2146
|
+
// (zero-Supabase) path there's no backend and the editor session
|
|
2147
|
+
// has userId: null, so the button would always 401 — hide it there.
|
|
2148
|
+
showBugReport={!capabilities.passwordOnly}
|
|
2003
2149
|
/>
|
|
2004
2150
|
|
|
2005
|
-
{/* Bug reports persist via Supabase. On the password-only (zero-Supabase)
|
|
2006
|
-
path there's no backend and the editor session has userId: null, so
|
|
2007
|
-
the button would always 401 — hide it there. */}
|
|
2008
|
-
{!capabilities.passwordOnly && <BugReportFAB />}
|
|
2009
|
-
|
|
2010
|
-
{/* Document editor: opens the full-screen editing surface for the
|
|
2011
|
-
active page when its document mode is enabled. Mirrors the viewer
|
|
2012
|
-
document FAB's icon; sits above BugReportFAB (bottom-40 clears the
|
|
2013
|
-
bottom-28 bug button + gap) so the two never collide.
|
|
2014
|
-
!historyActive: history is presentation only, never editing — while
|
|
2015
|
-
viewing a specific historical commit (historySelectEvent →
|
|
2016
|
-
EditorContext.historyState, lifted here by HistoryWatcher since this
|
|
2017
|
-
scope renders the provider and can't read the context directly),
|
|
2018
|
-
hide the FAB so it can't open the draft editor over a history view. */}
|
|
2019
|
-
{resolvedActivePage?.document?.enabled && !historyActive && (
|
|
2020
|
-
<Fab label="Edit as document" column="editor" className="bottom-40" onClick={() => setShowDocumentEditor(true)}>
|
|
2021
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
|
2022
|
-
<rect x="3" y="5" width="18" height="14" rx="1" />
|
|
2023
|
-
<path d="M7 10h10" />
|
|
2024
|
-
<path d="M7 14h7" />
|
|
2025
|
-
</svg>
|
|
2026
|
-
</Fab>
|
|
2027
|
-
)}
|
|
2028
2151
|
{/* Deck Builder (#34). Editors are super-admins over decks and are
|
|
2029
2152
|
never gated by deckBuilder.audiences, so this is unconditional.
|
|
2030
2153
|
An <a>, not a click handler: builder mode is its own route.
|
|
2031
|
-
|
|
2154
|
+
Standalone (not in EditorToolbar's FabStack): bottom-60 clears
|
|
2155
|
+
the stack's three members with room to spare. Taking over the
|
|
2032
2156
|
editor top bar in place is #38 — this just goes there. */}
|
|
2033
|
-
<Fab href="/decks?next=%2Fedit" label="Build a deck" column="editor" className="bottom-
|
|
2034
|
-
<
|
|
2035
|
-
<rect x="3" y="4" width="18" height="12" rx="1" />
|
|
2036
|
-
<path d="M12 16v4" />
|
|
2037
|
-
<path d="M9 20h6" />
|
|
2038
|
-
</svg>
|
|
2157
|
+
<Fab href="/decks?next=%2Fedit" label="Build a deck" column="editor" className="bottom-60">
|
|
2158
|
+
<Presentation size={20} aria-hidden="true" />
|
|
2039
2159
|
</Fab>
|
|
2040
2160
|
{showDocumentEditor && resolvedActivePage?.document?.enabled && (
|
|
2041
2161
|
<Suspense fallback={<DocumentOverlayFallback />}>
|
|
@@ -2047,8 +2167,9 @@ export default function EditorShell({
|
|
|
2047
2167
|
editor. NOTE: viewSections drives the "Published" branch preview
|
|
2048
2168
|
only; the separate historySelectEvent commit view
|
|
2049
2169
|
(EditorContext.historyState) is the OTHER read-only case — the
|
|
2050
|
-
|
|
2051
|
-
ever being opened onto it in the
|
|
2170
|
+
document FAB in EditorToolbar is gated on !historyState, which
|
|
2171
|
+
keeps this overlay from ever being opened onto it in the
|
|
2172
|
+
first place. */}
|
|
2052
2173
|
{viewSections !== null ? (
|
|
2053
2174
|
<DocumentPresentOverlay
|
|
2054
2175
|
page={resolvedActivePage}
|
|
@@ -2086,7 +2207,7 @@ export default function EditorShell({
|
|
|
2086
2207
|
|
|
2087
2208
|
{rejectedUploads.length > 0 && (
|
|
2088
2209
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2089
|
-
<
|
|
2210
|
+
<InlineAlert
|
|
2090
2211
|
message={formatRejections(rejectedUploads, siteConfig?.media.maxFileSize)}
|
|
2091
2212
|
onDismiss={() => setRejectedUploads([])}
|
|
2092
2213
|
/>
|
|
@@ -2100,7 +2221,7 @@ export default function EditorShell({
|
|
|
2100
2221
|
never show the same message twice. */}
|
|
2101
2222
|
{uploadErrorMessage && !showMediaLibrary && (
|
|
2102
2223
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2103
|
-
<
|
|
2224
|
+
<InlineAlert
|
|
2104
2225
|
message={uploadErrorMessage}
|
|
2105
2226
|
onDismiss={() => setUploadErrorMessage(null)}
|
|
2106
2227
|
/>
|
|
@@ -2109,29 +2230,34 @@ export default function EditorShell({
|
|
|
2109
2230
|
|
|
2110
2231
|
{unparseableSectionIds.length > 0 && (
|
|
2111
2232
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2112
|
-
<
|
|
2233
|
+
<InlineAlert
|
|
2113
2234
|
message={`${unparseableSectionIds.length} section file${unparseableSectionIds.length === 1 ? "" : "s"} on this site cannot be read (${unparseableSectionIds.join(", ")}). Damaged sections keep their place on the page and are not deleted by saving — repair the file to restore them.`}
|
|
2114
2235
|
onDismiss={() => setUnparseableSectionIds([])}
|
|
2115
2236
|
/>
|
|
2116
2237
|
</div>
|
|
2117
2238
|
)}
|
|
2118
2239
|
|
|
2119
|
-
{
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2240
|
+
{/* One sticky container so the two banners stack instead of overlapping. */}
|
|
2241
|
+
{(persistence.persistenceError || saveWarnings.length > 0) && (
|
|
2242
|
+
<div className="sticky top-2 z-30 mx-auto flex w-full max-w-3xl flex-col gap-2 px-4">
|
|
2243
|
+
{persistence.persistenceError && (
|
|
2244
|
+
<Notice tone="error" placement="inline">
|
|
2245
|
+
{persistence.persistenceError}
|
|
2246
|
+
</Notice>
|
|
2247
|
+
)}
|
|
2248
|
+
{saveWarnings.length > 0 && (
|
|
2249
|
+
<InlineAlert
|
|
2250
|
+
message={saveWarnings.join(" ")}
|
|
2251
|
+
onDismiss={() => setSaveWarnings([])}
|
|
2252
|
+
/>
|
|
2253
|
+
)}
|
|
2125
2254
|
</div>
|
|
2126
2255
|
)}
|
|
2127
2256
|
|
|
2128
2257
|
{remoteNotice && (
|
|
2129
|
-
<
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
>
|
|
2133
|
-
{remoteNotice}
|
|
2134
|
-
</div>
|
|
2258
|
+
<Notice tone={remoteNotice.tone} placement="toast">
|
|
2259
|
+
{remoteNotice.message}
|
|
2260
|
+
</Notice>
|
|
2135
2261
|
)}
|
|
2136
2262
|
|
|
2137
2263
|
{/* bp-band-shift: rides the chat dock's FLIP glide (base.css). On
|
|
@@ -2164,7 +2290,7 @@ export default function EditorShell({
|
|
|
2164
2290
|
) : (
|
|
2165
2291
|
<HistoryOrEditorContent
|
|
2166
2292
|
index={siteIndex}
|
|
2167
|
-
activeSections={
|
|
2293
|
+
activeSections={navSections}
|
|
2168
2294
|
activePageId={resolvedActivePage?.id ?? null}
|
|
2169
2295
|
>
|
|
2170
2296
|
<EditorContent
|
|
@@ -2181,6 +2307,7 @@ export default function EditorShell({
|
|
|
2181
2307
|
onBlockMove={onBlockMove}
|
|
2182
2308
|
onMoveSection={siteIndex.pages.length > 1 ? setMovingSectionId : undefined}
|
|
2183
2309
|
onAccessChange={onAccessChange}
|
|
2310
|
+
tagging={tagging}
|
|
2184
2311
|
onStatusChange={onStatusChange}
|
|
2185
2312
|
mainIndex={mainIndex}
|
|
2186
2313
|
changedSectionIds={changedSectionIds}
|
|
@@ -2203,41 +2330,34 @@ export default function EditorShell({
|
|
|
2203
2330
|
chatbotPlatformEnabled={chatbotPlatformEnabled}
|
|
2204
2331
|
capabilities={capabilities}
|
|
2205
2332
|
currentUser={currentUser}
|
|
2333
|
+
tagging={tagging}
|
|
2206
2334
|
/>
|
|
2207
|
-
<
|
|
2335
|
+
<ConfirmDialog
|
|
2208
2336
|
isOpen={pendingDeleteSectionId !== null}
|
|
2209
|
-
onClose={() => setPendingDeleteSectionId(null)}
|
|
2210
2337
|
title="Delete Section"
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
</div>
|
|
2223
|
-
</EditorModal>
|
|
2224
|
-
<EditorModal
|
|
2338
|
+
body={
|
|
2339
|
+
<p className="text-sm text-base-contrast-light">
|
|
2340
|
+
Delete this section? It will be removed from the page now and deleted permanently when you save or publish.
|
|
2341
|
+
</p>
|
|
2342
|
+
}
|
|
2343
|
+
confirmLabel="Delete"
|
|
2344
|
+
tone="destructive"
|
|
2345
|
+
onConfirm={confirmDeleteSection}
|
|
2346
|
+
onCancel={() => setPendingDeleteSectionId(null)}
|
|
2347
|
+
/>
|
|
2348
|
+
<ConfirmDialog
|
|
2225
2349
|
isOpen={showDiscardConfirm}
|
|
2226
|
-
onClose={() => setShowDiscardConfirm(false)}
|
|
2227
2350
|
title="Discard Changes"
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
</Button>
|
|
2239
|
-
</div>
|
|
2240
|
-
</EditorModal>
|
|
2351
|
+
body={
|
|
2352
|
+
<p className="text-sm text-base-contrast-light">
|
|
2353
|
+
Discard all unsaved changes? This will reload with the last published content.
|
|
2354
|
+
</p>
|
|
2355
|
+
}
|
|
2356
|
+
confirmLabel="Discard"
|
|
2357
|
+
tone="destructive"
|
|
2358
|
+
onConfirm={handleToolbarDiscard}
|
|
2359
|
+
onCancel={() => setShowDiscardConfirm(false)}
|
|
2360
|
+
/>
|
|
2241
2361
|
<EditorModal
|
|
2242
2362
|
isOpen={showMediaLibrary}
|
|
2243
2363
|
onClose={() => {
|
|
@@ -2357,16 +2477,18 @@ export default function EditorShell({
|
|
|
2357
2477
|
</MediaLibraryContext.Provider>
|
|
2358
2478
|
</EditorModalProvider>
|
|
2359
2479
|
</EditorProvider>
|
|
2480
|
+
</BrandSwatchesContext.Provider>
|
|
2481
|
+
</ReferenceSourcesContext.Provider>
|
|
2360
2482
|
</PagesContext.Provider>
|
|
2361
2483
|
);
|
|
2362
2484
|
}
|
|
2363
2485
|
|
|
2364
2486
|
function ViewBranchWatcher({
|
|
2365
|
-
|
|
2487
|
+
setView,
|
|
2366
2488
|
onError,
|
|
2367
2489
|
}: {
|
|
2368
|
-
|
|
2369
|
-
onError: (message: string) => void;
|
|
2490
|
+
setView: (view: { sections: LoadedSection[]; index: SiteIndex | null } | null) => void;
|
|
2491
|
+
onError: (message: string, tone?: NoticeState["tone"]) => void;
|
|
2370
2492
|
}) {
|
|
2371
2493
|
const { isEditMode, viewBranch } = useEditorContext();
|
|
2372
2494
|
|
|
@@ -2376,7 +2498,7 @@ function ViewBranchWatcher({
|
|
|
2376
2498
|
// fetch (fetching `saved` would 404 after a publish and fall back to main,
|
|
2377
2499
|
// clobbering unsaved edits). Null viewSections routes to the in-memory path.
|
|
2378
2500
|
if (isEditMode || viewBranch !== "live") {
|
|
2379
|
-
|
|
2501
|
+
setView(null);
|
|
2380
2502
|
return;
|
|
2381
2503
|
}
|
|
2382
2504
|
|
|
@@ -2387,42 +2509,34 @@ function ViewBranchWatcher({
|
|
|
2387
2509
|
.then((res) => {
|
|
2388
2510
|
// A non-JSON 5xx used to parse to `undefined` and render an empty
|
|
2389
2511
|
// "Published" view with nothing explaining it.
|
|
2390
|
-
if (!res.ok) throw new Error(`
|
|
2512
|
+
if (!res.ok) throw new Error(`Couldn't load the published view: ${res.status}`);
|
|
2391
2513
|
return res.json();
|
|
2392
2514
|
})
|
|
2393
2515
|
.then((data) => {
|
|
2394
2516
|
if (!Array.isArray(data?.sections)) throw new Error("Malformed published-view response");
|
|
2395
|
-
|
|
2517
|
+
setView({ sections: data.sections, index: data.index ?? null });
|
|
2396
2518
|
})
|
|
2397
2519
|
.catch((err) => {
|
|
2398
2520
|
if (err?.name === "AbortError") return;
|
|
2399
2521
|
console.error(err);
|
|
2400
2522
|
// Fall back to the in-memory path rather than showing an empty page.
|
|
2401
|
-
|
|
2402
|
-
onError("Couldn't load the published view — showing your current edits.");
|
|
2523
|
+
setView(null);
|
|
2524
|
+
onError("Couldn't load the published view — showing your current edits.", "error");
|
|
2403
2525
|
});
|
|
2404
2526
|
return () => controller.abort();
|
|
2405
|
-
}, [viewBranch, isEditMode,
|
|
2527
|
+
}, [viewBranch, isEditMode, setView, onError]);
|
|
2406
2528
|
|
|
2407
2529
|
return null;
|
|
2408
2530
|
}
|
|
2409
2531
|
|
|
2410
|
-
function HistoryWatcher({
|
|
2411
|
-
|
|
2412
|
-
}: {
|
|
2413
|
-
// Lifts historyState's active/inactive status to the shell body (mirrors
|
|
2414
|
-
// ViewBranchWatcher's setViewSections lift) — the body renders the
|
|
2415
|
-
// EditorProvider this component reads from, so it can't call
|
|
2416
|
-
// useEditorContext() itself to get at historyState.
|
|
2417
|
-
onHistoryActiveChange?: (active: boolean) => void;
|
|
2418
|
-
}) {
|
|
2419
|
-
const { historyState, setHistoryState } = useEditorContext();
|
|
2532
|
+
function HistoryWatcher() {
|
|
2533
|
+
const { setHistoryState } = useEditorContext();
|
|
2420
2534
|
|
|
2421
2535
|
useEffect(() => {
|
|
2422
2536
|
const unlisten = historySelectEvent.listen(async ({ sha, date }) => {
|
|
2423
2537
|
try {
|
|
2424
2538
|
const res = await fetch(`/api/content/history?sha=${sha}`);
|
|
2425
|
-
if (!res.ok) throw new Error(`
|
|
2539
|
+
if (!res.ok) throw new Error(`Couldn't load history: ${res.status}`);
|
|
2426
2540
|
const data = await res.json();
|
|
2427
2541
|
setHistoryState({
|
|
2428
2542
|
sha,
|
|
@@ -2442,10 +2556,6 @@ function HistoryWatcher({
|
|
|
2442
2556
|
return unlisten;
|
|
2443
2557
|
}, [setHistoryState]);
|
|
2444
2558
|
|
|
2445
|
-
useEffect(() => {
|
|
2446
|
-
onHistoryActiveChange?.(historyState !== null);
|
|
2447
|
-
}, [historyState, onHistoryActiveChange]);
|
|
2448
|
-
|
|
2449
2559
|
return null;
|
|
2450
2560
|
}
|
|
2451
2561
|
|
|
@@ -2556,6 +2666,7 @@ function EditorContent({
|
|
|
2556
2666
|
onBlockMove,
|
|
2557
2667
|
onMoveSection,
|
|
2558
2668
|
onAccessChange,
|
|
2669
|
+
tagging,
|
|
2559
2670
|
onStatusChange,
|
|
2560
2671
|
mainIndex,
|
|
2561
2672
|
changedSectionIds,
|
|
@@ -2577,6 +2688,7 @@ function EditorContent({
|
|
|
2577
2688
|
onBlockMove: (move: BlockMove) => void;
|
|
2578
2689
|
onMoveSection?: (sectionId: string) => void;
|
|
2579
2690
|
onAccessChange: (sectionId: string, access: string[]) => void;
|
|
2691
|
+
tagging: SiteTagging;
|
|
2580
2692
|
onStatusChange: (sectionId: string, status: "draft" | "live" | "archived") => void;
|
|
2581
2693
|
mainIndex: SiteIndex | null;
|
|
2582
2694
|
changedSectionIds: Set<string>;
|
|
@@ -2589,6 +2701,7 @@ function EditorContent({
|
|
|
2589
2701
|
}) {
|
|
2590
2702
|
const { isEditMode, viewBranch, previewAudience } = useEditorContext();
|
|
2591
2703
|
const { openModal, closeModal } = useEditorModal();
|
|
2704
|
+
const referenceSources = useContext(ReferenceSourcesContext);
|
|
2592
2705
|
const [pendingInsertIndex, setPendingInsertIndex] = useState<number | null>(null);
|
|
2593
2706
|
const dismissPendingInsert = useCallback(() => setPendingInsertIndex(null), []);
|
|
2594
2707
|
|
|
@@ -2606,6 +2719,10 @@ function EditorContent({
|
|
|
2606
2719
|
viewSections,
|
|
2607
2720
|
activePageSectionIds,
|
|
2608
2721
|
});
|
|
2722
|
+
// TL;DR / Summary plates render their nav anchor from this. Derived from the
|
|
2723
|
+
// same list the sidebar nav is built from (not displaySections, which preview
|
|
2724
|
+
// modes filter), so a plate's id always matches the href its nav entry links to.
|
|
2725
|
+
const summaryAnchors = useMemo(() => summaryNavAnchors(sections), [sections]);
|
|
2609
2726
|
|
|
2610
2727
|
useEffect(() => {
|
|
2611
2728
|
return combine(
|
|
@@ -2651,157 +2768,174 @@ function EditorContent({
|
|
|
2651
2768
|
// `.section-wrapper:first-child` zeroes the 2.25rem that every other section
|
|
2652
2769
|
// reserves above itself for its hover chrome, so the first section's controls
|
|
2653
2770
|
// would sit under the fixed toolbar. Re-reserve it on the list instead.
|
|
2654
|
-
<
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2771
|
+
<SummaryAnchorsContext.Provider value={summaryAnchors}>
|
|
2772
|
+
<div className="pt-9">
|
|
2773
|
+
{displaySections.map(({ section, meta }, index) => {
|
|
2774
|
+
const definition = getSection(section.type);
|
|
2775
|
+
if (!definition) {
|
|
2776
|
+
return (
|
|
2777
|
+
<div
|
|
2778
|
+
key={section.id}
|
|
2779
|
+
className="border border-warning bg-warning-bg px-4 py-3 text-sm text-warning"
|
|
2780
|
+
role="alert"
|
|
2781
|
+
>
|
|
2782
|
+
Unknown section type: <code className="font-mono font-semibold">{section.type}</code>
|
|
2783
|
+
</div>
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
const Component = definition.component;
|
|
2787
|
+
// The viewer renders no wrapper for a Reference with nothing to show,
|
|
2788
|
+
// so neither does the preview — its spacing would otherwise remain.
|
|
2789
|
+
if (
|
|
2790
|
+
!isEditMode && section.type === REFERENCE_TYPE && referenceSources &&
|
|
2791
|
+
!referenceShowsInPreview(referenceSources, section.id, section.content as unknown as ReferenceContent, viewBranch)
|
|
2792
|
+
) {
|
|
2793
|
+
return null;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2658
2796
|
return (
|
|
2659
|
-
<
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
return (
|
|
2671
|
-
<Fragment key={section.id}>
|
|
2672
|
-
{editingEnabled && pendingInsertIndex === index && (
|
|
2673
|
-
<SectionSkeleton
|
|
2674
|
-
types={typeOptions}
|
|
2675
|
-
onSelect={(type) => {
|
|
2676
|
-
onAddSection(index, type);
|
|
2677
|
-
setPendingInsertIndex(null);
|
|
2678
|
-
}}
|
|
2679
|
-
onDismiss={dismissPendingInsert}
|
|
2680
|
-
/>
|
|
2681
|
-
)}
|
|
2797
|
+
<Fragment key={section.id}>
|
|
2798
|
+
{editingEnabled && pendingInsertIndex === index && (
|
|
2799
|
+
<SectionSkeleton
|
|
2800
|
+
types={typeOptions}
|
|
2801
|
+
onSelect={(type) => {
|
|
2802
|
+
onAddSection(index, type);
|
|
2803
|
+
setPendingInsertIndex(null);
|
|
2804
|
+
}}
|
|
2805
|
+
onDismiss={dismissPendingInsert}
|
|
2806
|
+
/>
|
|
2807
|
+
)}
|
|
2682
2808
|
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
status={meta.status}
|
|
2687
|
-
dimNonPublished={!isEditMode}
|
|
2688
|
-
dimmed={previewAudience !== null && !audiencePasses(meta.access, previewAudience)}
|
|
2689
|
-
>
|
|
2690
|
-
<SectionWrapper
|
|
2691
|
-
sectionId={section.id}
|
|
2692
|
-
sectionType={section.type}
|
|
2809
|
+
<ErrorBoundary label={`${definition.label} (${section.type})`}>
|
|
2810
|
+
<SectionLayout
|
|
2811
|
+
type={section.type}
|
|
2693
2812
|
status={meta.status}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
isLast={index === displaySections.length - 1}
|
|
2697
|
-
containerId={ROOT_CONTAINER_ID}
|
|
2698
|
-
isContainerBlock={section.type === "container"}
|
|
2699
|
-
definition={definition}
|
|
2700
|
-
mainStatus={mainIndex?.sections[section.id]?.status ?? null}
|
|
2701
|
-
contentDiffersFromMain={changedSectionIds.has(section.id) || dirtySectionIds.has(section.id)}
|
|
2702
|
-
isLocalOnly={dirtySectionIds.has(section.id) && !changedSectionIds.has(section.id)}
|
|
2703
|
-
lockedBy={locks.get(section.id) ? { email: locks.get(section.id)!.email } : null}
|
|
2704
|
-
onTakeover={() => onTakeover(section.id)}
|
|
2705
|
-
onEditIntent={getEditIntent(section.id)}
|
|
2706
|
-
options={{
|
|
2707
|
-
...(section.content as Record<string, unknown>),
|
|
2708
|
-
...("options" in section ? (section.options as Record<string, unknown>) : {}),
|
|
2709
|
-
}}
|
|
2710
|
-
audiences={audiences}
|
|
2711
|
-
access={meta.access}
|
|
2712
|
-
onAccessChange={editingEnabled ? (a) => onAccessChange(section.id, a) : undefined}
|
|
2713
|
-
onStatusChange={editingEnabled ? (s) => onStatusChange(section.id, s) : undefined}
|
|
2714
|
-
onSectionChange={editingEnabled ? (settingsResult) => {
|
|
2715
|
-
const result = settingsResult as Record<string, unknown>;
|
|
2716
|
-
if (result && typeof result === "object" && "content" in result && "options" in result) {
|
|
2717
|
-
const contentPatch = result.content as Record<string, unknown>;
|
|
2718
|
-
const optionsPatch = result.options as Record<string, unknown>;
|
|
2719
|
-
onSectionChange(section.id, {
|
|
2720
|
-
...section,
|
|
2721
|
-
content: { ...section.content, ...contentPatch },
|
|
2722
|
-
options: { ...("options" in section ? section.options : {}), ...optionsPatch },
|
|
2723
|
-
} as SectionContent);
|
|
2724
|
-
} else {
|
|
2725
|
-
onSectionChange(section.id, {
|
|
2726
|
-
...section,
|
|
2727
|
-
...result,
|
|
2728
|
-
} as SectionContent);
|
|
2729
|
-
}
|
|
2730
|
-
} : undefined}
|
|
2731
|
-
onReorder={editingEnabled ? onReorderSections : undefined}
|
|
2732
|
-
onMoveSection={editingEnabled && onMoveSection ? () => onMoveSection(section.id) : undefined}
|
|
2733
|
-
onRequestInsert={editingEnabled ? (i) => setPendingInsertIndex(i) : undefined}
|
|
2734
|
-
onDelete={editingEnabled ? () => onDeleteSection(section.id) : undefined}
|
|
2735
|
-
onAddChild={
|
|
2736
|
-
editingEnabled && section.type === "container"
|
|
2737
|
-
? () => {
|
|
2738
|
-
openModal(
|
|
2739
|
-
"Add block",
|
|
2740
|
-
<SectionTypePicker
|
|
2741
|
-
types={childInsertableTypes()}
|
|
2742
|
-
onClose={closeModal}
|
|
2743
|
-
onSelect={(type) => {
|
|
2744
|
-
const def = getSection(type);
|
|
2745
|
-
if (def) {
|
|
2746
|
-
const id =
|
|
2747
|
-
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
2748
|
-
? crypto.randomUUID()
|
|
2749
|
-
: `child-${Date.now()}`;
|
|
2750
|
-
const child = { id, ...(def.defaults() as object) } as Section;
|
|
2751
|
-
const containerSection: Section = {
|
|
2752
|
-
id: section.id,
|
|
2753
|
-
type: "container",
|
|
2754
|
-
content: section.content as Record<string, unknown>,
|
|
2755
|
-
};
|
|
2756
|
-
const childCount =
|
|
2757
|
-
(section.content as { children?: unknown[] }).children?.length ?? 0;
|
|
2758
|
-
const next = insertChildAt(containerSection, child, childCount);
|
|
2759
|
-
onSectionChange(section.id, {
|
|
2760
|
-
...section,
|
|
2761
|
-
content: next.content,
|
|
2762
|
-
} as SectionContent);
|
|
2763
|
-
}
|
|
2764
|
-
closeModal();
|
|
2765
|
-
}}
|
|
2766
|
-
/>,
|
|
2767
|
-
);
|
|
2768
|
-
}
|
|
2769
|
-
: undefined
|
|
2770
|
-
}
|
|
2813
|
+
dimNonPublished={!isEditMode}
|
|
2814
|
+
dimmed={previewAudience !== null && !audiencePasses(meta.access, previewAudience)}
|
|
2771
2815
|
>
|
|
2772
|
-
<
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2816
|
+
<SectionWrapper
|
|
2817
|
+
sectionId={section.id}
|
|
2818
|
+
sectionType={section.type}
|
|
2819
|
+
status={meta.status}
|
|
2820
|
+
dirty={dirtySectionIds.has(section.id)}
|
|
2821
|
+
index={index}
|
|
2822
|
+
isLast={index === displaySections.length - 1}
|
|
2823
|
+
sectionCount={displaySections.length}
|
|
2824
|
+
containerId={ROOT_CONTAINER_ID}
|
|
2825
|
+
isContainerBlock={section.type === "container"}
|
|
2826
|
+
definition={definition}
|
|
2827
|
+
mainStatus={mainIndex?.sections[section.id]?.status ?? null}
|
|
2828
|
+
contentDiffersFromMain={changedSectionIds.has(section.id) || dirtySectionIds.has(section.id)}
|
|
2829
|
+
isLocalOnly={dirtySectionIds.has(section.id) && !changedSectionIds.has(section.id)}
|
|
2830
|
+
lockedBy={locks.get(section.id) ? { email: locks.get(section.id)!.email } : null}
|
|
2831
|
+
onTakeover={() => onTakeover(section.id)}
|
|
2832
|
+
onEditIntent={getEditIntent(section.id)}
|
|
2833
|
+
options={{
|
|
2834
|
+
...(section.content as Record<string, unknown>),
|
|
2835
|
+
...("options" in section ? (section.options as Record<string, unknown>) : {}),
|
|
2836
|
+
}}
|
|
2837
|
+
audiences={audiences}
|
|
2838
|
+
access={meta.access}
|
|
2839
|
+
onAccessChange={editingEnabled ? (a) => onAccessChange(section.id, a) : undefined}
|
|
2840
|
+
tagging={{
|
|
2841
|
+
tags: tagging.tags,
|
|
2842
|
+
assignedIds: tagging.tags.filter((t) => t.sectionIds.includes(section.id)).map((t) => t.id),
|
|
2843
|
+
onToggle: editingEnabled && tagging.editable ? (tagId, tagged) => tagging.onToggle(section.id, tagId, tagged) : undefined,
|
|
2844
|
+
onCreate: editingEnabled && tagging.editable ? (label) => tagging.onCreate(label, section.id) : undefined,
|
|
2845
|
+
}}
|
|
2846
|
+
onStatusChange={editingEnabled ? (s) => onStatusChange(section.id, s) : undefined}
|
|
2847
|
+
onSectionChange={editingEnabled ? (settingsResult) => {
|
|
2848
|
+
const result = settingsResult as Record<string, unknown>;
|
|
2849
|
+
if (result && typeof result === "object" && "content" in result && "options" in result) {
|
|
2850
|
+
const contentPatch = result.content as Record<string, unknown>;
|
|
2851
|
+
const optionsPatch = result.options as Record<string, unknown>;
|
|
2852
|
+
onSectionChange(section.id, {
|
|
2853
|
+
...section,
|
|
2854
|
+
content: { ...section.content, ...contentPatch },
|
|
2855
|
+
options: { ...("options" in section ? section.options : {}), ...optionsPatch },
|
|
2856
|
+
} as SectionContent);
|
|
2857
|
+
} else {
|
|
2858
|
+
onSectionChange(section.id, {
|
|
2859
|
+
...section,
|
|
2860
|
+
...result,
|
|
2861
|
+
} as SectionContent);
|
|
2862
|
+
}
|
|
2863
|
+
} : undefined}
|
|
2864
|
+
onReorder={editingEnabled ? onReorderSections : undefined}
|
|
2865
|
+
onMoveSection={editingEnabled && onMoveSection ? () => onMoveSection(section.id) : undefined}
|
|
2866
|
+
onRequestInsert={editingEnabled ? (i) => setPendingInsertIndex(i) : undefined}
|
|
2867
|
+
onDelete={editingEnabled ? () => onDeleteSection(section.id) : undefined}
|
|
2868
|
+
onAddChild={
|
|
2869
|
+
editingEnabled && section.type === "container"
|
|
2870
|
+
? () => {
|
|
2871
|
+
openModal(
|
|
2872
|
+
"Add block",
|
|
2873
|
+
<SectionTypePicker
|
|
2874
|
+
types={childInsertableTypes()}
|
|
2875
|
+
onClose={closeModal}
|
|
2876
|
+
onSelect={(type) => {
|
|
2877
|
+
const def = getSection(type);
|
|
2878
|
+
if (def) {
|
|
2879
|
+
const id =
|
|
2880
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
2881
|
+
? crypto.randomUUID()
|
|
2882
|
+
: `child-${Date.now()}`;
|
|
2883
|
+
const child = { id, ...(def.defaults() as object) } as Section;
|
|
2884
|
+
const containerSection: Section = {
|
|
2885
|
+
id: section.id,
|
|
2886
|
+
type: "container",
|
|
2887
|
+
content: section.content as Record<string, unknown>,
|
|
2888
|
+
};
|
|
2889
|
+
const childCount =
|
|
2890
|
+
(section.content as { children?: unknown[] }).children?.length ?? 0;
|
|
2891
|
+
const next = insertChildAt(containerSection, child, childCount);
|
|
2892
|
+
onSectionChange(section.id, {
|
|
2893
|
+
...section,
|
|
2894
|
+
content: next.content,
|
|
2895
|
+
} as SectionContent);
|
|
2896
|
+
}
|
|
2897
|
+
closeModal();
|
|
2898
|
+
}}
|
|
2899
|
+
/>,
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
: undefined
|
|
2903
|
+
}
|
|
2904
|
+
>
|
|
2905
|
+
<Component
|
|
2906
|
+
content={section}
|
|
2907
|
+
options={"options" in section ? (section.options as Record<string, unknown>) : undefined}
|
|
2908
|
+
onChange={editingEnabled ? (newContent) => onSectionChange(section.id, newContent as SectionContent) : undefined}
|
|
2909
|
+
isEditMode={isEditMode}
|
|
2910
|
+
openModal={editingEnabled ? openModal : undefined}
|
|
2911
|
+
// Immediate (no confirm modal): used when a colors section's
|
|
2912
|
+
// last swatch is dragged out, auto-removing the empty section.
|
|
2913
|
+
onDelete={editingEnabled ? () => onDeleteSectionImmediate(section.id) : undefined}
|
|
2914
|
+
/>
|
|
2915
|
+
</SectionWrapper>
|
|
2916
|
+
</SectionLayout>
|
|
2917
|
+
</ErrorBoundary>
|
|
2918
|
+
</Fragment>
|
|
2919
|
+
);
|
|
2920
|
+
})}
|
|
2921
|
+
|
|
2922
|
+
{/* Trailing selector: shown when an insert was requested after the last
|
|
2923
|
+
section, or always on an empty page — an empty page has no section
|
|
2924
|
+
chrome to insert from, so the selector IS its default state (not
|
|
2925
|
+
dismissible; there is nothing behind it). */}
|
|
2926
|
+
{editingEnabled && (displaySections.length === 0 || pendingInsertIndex === displaySections.length) && (
|
|
2927
|
+
<SectionSkeleton
|
|
2928
|
+
types={typeOptions}
|
|
2929
|
+
onSelect={(type) => {
|
|
2930
|
+
onAddSection(displaySections.length, type);
|
|
2931
|
+
setPendingInsertIndex(null);
|
|
2932
|
+
}}
|
|
2933
|
+
onDismiss={dismissPendingInsert}
|
|
2934
|
+
/>
|
|
2935
|
+
)}
|
|
2803
2936
|
|
|
2804
|
-
|
|
2937
|
+
</div>
|
|
2938
|
+
</SummaryAnchorsContext.Provider>
|
|
2805
2939
|
);
|
|
2806
2940
|
}
|
|
2807
2941
|
|
|
@@ -2847,7 +2981,7 @@ function formatElapsed(seconds: number): string {
|
|
|
2847
2981
|
function DismissButton({ onClick }: { onClick: () => void }) {
|
|
2848
2982
|
return (
|
|
2849
2983
|
<button type="button" onClick={onClick} className="cursor-pointer" aria-label="Dismiss">
|
|
2850
|
-
<X size={12} className="text-base-
|
|
2984
|
+
<X size={12} className="text-base-contrast/30 hover:text-base-contrast/60" />
|
|
2851
2985
|
</button>
|
|
2852
2986
|
);
|
|
2853
2987
|
}
|
|
@@ -2861,7 +2995,7 @@ function StatusText({
|
|
|
2861
2995
|
onDismiss,
|
|
2862
2996
|
}: {
|
|
2863
2997
|
publishAction: "idle" | "saving" | "publishing";
|
|
2864
|
-
publishFeedback:
|
|
2998
|
+
publishFeedback: NoticeState | null;
|
|
2865
2999
|
saveProgress: string | null;
|
|
2866
3000
|
buildState: "idle" | "building" | "ready" | "error" | "fading";
|
|
2867
3001
|
buildElapsed: number;
|
|
@@ -2869,14 +3003,14 @@ function StatusText({
|
|
|
2869
3003
|
}) {
|
|
2870
3004
|
if (publishAction === "saving") {
|
|
2871
3005
|
return (
|
|
2872
|
-
<span className="relative text-xs font-medium text-base-
|
|
3006
|
+
<span className="relative text-xs font-medium text-base-contrast/60">
|
|
2873
3007
|
{saveProgress ?? <>Saving changes<LoadingEllipsis /></>}
|
|
2874
3008
|
</span>
|
|
2875
3009
|
);
|
|
2876
3010
|
}
|
|
2877
3011
|
if (publishAction === "publishing" || buildState === "building") {
|
|
2878
3012
|
return (
|
|
2879
|
-
<span className="text-xs font-medium text-
|
|
3013
|
+
<span className="text-xs font-medium text-warning">
|
|
2880
3014
|
Publishing ({formatElapsed(buildElapsed)})
|
|
2881
3015
|
</span>
|
|
2882
3016
|
);
|
|
@@ -2884,7 +3018,7 @@ function StatusText({
|
|
|
2884
3018
|
if (buildState === "ready" || buildState === "fading") {
|
|
2885
3019
|
return (
|
|
2886
3020
|
<span className={cn(
|
|
2887
|
-
"inline-flex items-center gap-1.5 text-xs font-medium text-
|
|
3021
|
+
"inline-flex items-center gap-1.5 text-xs font-medium text-success transition-opacity duration-1000",
|
|
2888
3022
|
buildState === "fading" ? "opacity-0" : "opacity-100",
|
|
2889
3023
|
)}>
|
|
2890
3024
|
Published in {formatElapsed(buildElapsed)}
|
|
@@ -2894,19 +3028,22 @@ function StatusText({
|
|
|
2894
3028
|
}
|
|
2895
3029
|
if (buildState === "error") {
|
|
2896
3030
|
return (
|
|
2897
|
-
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-
|
|
3031
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-danger">
|
|
2898
3032
|
Publish failed
|
|
2899
3033
|
<DismissButton onClick={onDismiss} />
|
|
2900
3034
|
</span>
|
|
2901
3035
|
);
|
|
2902
3036
|
}
|
|
2903
3037
|
if (publishFeedback) {
|
|
3038
|
+
const toneClass: Record<NoticeState["tone"], string> = {
|
|
3039
|
+
success: "text-success",
|
|
3040
|
+
info: "text-base-contrast",
|
|
3041
|
+
warning: "text-warning",
|
|
3042
|
+
error: "text-danger",
|
|
3043
|
+
};
|
|
2904
3044
|
return (
|
|
2905
|
-
<span className={cn(
|
|
2906
|
-
|
|
2907
|
-
publishFeedback === "Saved" ? "text-green-600" : "text-red-600",
|
|
2908
|
-
)}>
|
|
2909
|
-
{publishFeedback}
|
|
3045
|
+
<span className={cn("text-xs font-medium", toneClass[publishFeedback.tone])}>
|
|
3046
|
+
{publishFeedback.message}
|
|
2910
3047
|
</span>
|
|
2911
3048
|
);
|
|
2912
3049
|
}
|
|
@@ -2938,11 +3075,14 @@ function EditorToolbar({
|
|
|
2938
3075
|
audiences,
|
|
2939
3076
|
authoringDefaults,
|
|
2940
3077
|
onAuthoringDefaultsChange,
|
|
3078
|
+
docEditEnabled,
|
|
3079
|
+
onDocEditClick,
|
|
3080
|
+
showBugReport,
|
|
2941
3081
|
}: {
|
|
2942
3082
|
buttonState: "synced" | "publish" | "saveAndPublish";
|
|
2943
3083
|
localChangesExist: boolean;
|
|
2944
3084
|
publishAction: "idle" | "saving" | "publishing";
|
|
2945
|
-
publishFeedback:
|
|
3085
|
+
publishFeedback: NoticeState | null;
|
|
2946
3086
|
saveProgress: string | null;
|
|
2947
3087
|
onSave: () => void;
|
|
2948
3088
|
onPublish: () => void;
|
|
@@ -2963,6 +3103,14 @@ function EditorToolbar({
|
|
|
2963
3103
|
audiences: Audience[];
|
|
2964
3104
|
authoringDefaults: AuthoringDefaults;
|
|
2965
3105
|
onAuthoringDefaultsChange: (next: AuthoringDefaults) => void;
|
|
3106
|
+
/** Whether the active page has document mode on — gates the "Edit as
|
|
3107
|
+
* document" FAB this toolbar stacks alongside its own mode-toggle FAB.
|
|
3108
|
+
* Owned by EditorShell (resolvedActivePage lives above the Provider). */
|
|
3109
|
+
docEditEnabled: boolean;
|
|
3110
|
+
onDocEditClick: () => void;
|
|
3111
|
+
/** Whether to render BugReportFAB in this toolbar's FabStack — false on
|
|
3112
|
+
* the password-only (zero-Supabase) path, which has no backend for it. */
|
|
3113
|
+
showBugReport: boolean;
|
|
2966
3114
|
}) {
|
|
2967
3115
|
const { isEditMode, viewBranch, setViewBranch, toggleEditMode, historyState, setHistoryState, historyPageId, setHistoryPageId } = useEditorContext();
|
|
2968
3116
|
|
|
@@ -2983,13 +3131,13 @@ function EditorToolbar({
|
|
|
2983
3131
|
return (
|
|
2984
3132
|
<>
|
|
2985
3133
|
{isEditMode && (
|
|
2986
|
-
//
|
|
2987
|
-
//
|
|
2988
|
-
//
|
|
2989
|
-
//
|
|
2990
|
-
<
|
|
2991
|
-
|
|
2992
|
-
|
|
3134
|
+
// The docked chat sidebar slides in BELOW this bar (ChatPanel docks
|
|
3135
|
+
// at top-16 on /edit), so the toolbar never moves during the chat
|
|
3136
|
+
// open/close transition — ToolbarFrame's h-16 matches the panel's
|
|
3137
|
+
// top offset (and the body's pt-16) exactly.
|
|
3138
|
+
<ToolbarFrame
|
|
3139
|
+
left={
|
|
3140
|
+
<>
|
|
2993
3141
|
{buttonState === "saveAndPublish" && (
|
|
2994
3142
|
<SplitButton
|
|
2995
3143
|
label="Save & Publish"
|
|
@@ -3024,18 +3172,20 @@ function EditorToolbar({
|
|
|
3024
3172
|
</Button>
|
|
3025
3173
|
)}
|
|
3026
3174
|
<PresenceAvatars peers={collabPeers} selfUserId={collabSelfId} status={collabStatus} />
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3175
|
+
</>
|
|
3176
|
+
}
|
|
3177
|
+
center={
|
|
3178
|
+
<StatusText
|
|
3179
|
+
publishAction={publishAction}
|
|
3180
|
+
publishFeedback={publishFeedback}
|
|
3181
|
+
saveProgress={saveProgress}
|
|
3182
|
+
buildState={buildState}
|
|
3183
|
+
buildElapsed={buildElapsed}
|
|
3184
|
+
onDismiss={onBuildDismiss}
|
|
3185
|
+
/>
|
|
3186
|
+
}
|
|
3187
|
+
right={
|
|
3188
|
+
<>
|
|
3039
3189
|
<ProcessingIndicator items={processingItems} />
|
|
3040
3190
|
<Tooltip side="bottom" content="Authoring defaults">
|
|
3041
3191
|
<AuthoringDefaultsMenu defaults={authoringDefaults} audiences={audiences} onChange={onAuthoringDefaultsChange} />
|
|
@@ -3057,15 +3207,15 @@ function EditorToolbar({
|
|
|
3057
3207
|
<ShowAllChromeToggle />
|
|
3058
3208
|
</Tooltip>
|
|
3059
3209
|
<Tooltip side="bottom-clamp" content="Site settings">
|
|
3060
|
-
<IconButton icon={<
|
|
3210
|
+
<IconButton icon={<Settings size={16} />} label="Site settings" size="md" onClick={onSettingsClick} className="border border-base-200 bg-base-accent" />
|
|
3061
3211
|
</Tooltip>
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3212
|
+
</>
|
|
3213
|
+
}
|
|
3214
|
+
/>
|
|
3065
3215
|
)}
|
|
3066
3216
|
{!isEditMode && (
|
|
3067
|
-
<
|
|
3068
|
-
|
|
3217
|
+
<ToolbarFrame
|
|
3218
|
+
center={
|
|
3069
3219
|
<SegmentedControl
|
|
3070
3220
|
options={[
|
|
3071
3221
|
{ value: "live", label: "Published" },
|
|
@@ -3074,27 +3224,37 @@ function EditorToolbar({
|
|
|
3074
3224
|
value={viewBranch}
|
|
3075
3225
|
onChange={(v) => setViewBranch(v as "saved" | "live")}
|
|
3076
3226
|
/>
|
|
3077
|
-
|
|
3078
|
-
|
|
3227
|
+
}
|
|
3228
|
+
/>
|
|
3079
3229
|
)}
|
|
3080
3230
|
{!historyState && (
|
|
3081
|
-
<
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
|
3095
|
-
</svg>
|
|
3231
|
+
<FabStack>
|
|
3232
|
+
{/* Bug reports persist via Supabase; hidden on the password-only
|
|
3233
|
+
path (showBugReport, threaded from EditorShell's capabilities).
|
|
3234
|
+
History is presentation only, never editing (branch-preview
|
|
3235
|
+
already routes to DocumentPresentOverlay; !historyState covers
|
|
3236
|
+
the separate historySelectEvent commit view) — FabStack
|
|
3237
|
+
unmounts with the rest of this block whenever history is
|
|
3238
|
+
active, which hides all three FABs together. */}
|
|
3239
|
+
{showBugReport && <BugReportFAB stacked />}
|
|
3240
|
+
{docEditEnabled && (
|
|
3241
|
+
<Fab stacked label="Edit as document" column="editor" onClick={onDocEditClick}>
|
|
3242
|
+
<FileText size={20} aria-hidden="true" />
|
|
3243
|
+
</Fab>
|
|
3096
3244
|
)}
|
|
3097
|
-
|
|
3245
|
+
<Fab
|
|
3246
|
+
stacked
|
|
3247
|
+
label={isEditMode ? "Switch to view mode" : "Switch to edit mode"}
|
|
3248
|
+
column="editor"
|
|
3249
|
+
onClick={toggleEditMode}
|
|
3250
|
+
>
|
|
3251
|
+
{isEditMode ? (
|
|
3252
|
+
<Eye className="h-5 w-5" aria-hidden="true" />
|
|
3253
|
+
) : (
|
|
3254
|
+
<Pencil className="h-5 w-5" aria-hidden="true" />
|
|
3255
|
+
)}
|
|
3256
|
+
</Fab>
|
|
3257
|
+
</FabStack>
|
|
3098
3258
|
)}
|
|
3099
3259
|
</>
|
|
3100
3260
|
);
|