@drawnagency/primitives 0.1.113 → 0.1.115
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-4WQ36ZP7.js +83 -0
- package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
- package/dist/chunk-5GFACGSN.js +6 -0
- package/dist/{chunk-AHALSCQH.js → chunk-67P34WNW.js} +76 -131
- package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
- package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
- package/dist/chunk-APIJPTFG.js +64 -0
- package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
- package/dist/chunk-CDBURDPX.js +90 -0
- package/dist/chunk-DFKTF3CH.js +0 -0
- package/dist/chunk-DIMDRBFK.js +11 -0
- package/dist/chunk-DJABADOO.js +131 -0
- package/dist/{chunk-NHN2LSIU.js → chunk-DJOGPSRU.js} +31 -68
- package/dist/{chunk-KR2MHTNJ.js → chunk-FHTKSELX.js} +610 -62
- package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
- package/dist/chunk-GNF6KFQ7.js +18 -0
- package/dist/{chunk-FRE2CCLV.js → chunk-HHNPSKPT.js} +5 -79
- package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
- package/dist/chunk-JR24U7FJ.js +120 -0
- package/dist/{chunk-BDFTMGCL.js → chunk-JXATNKLV.js} +11 -10
- package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
- package/dist/{chunk-6BZ2KNTP.js → chunk-NX7TFHA4.js} +195 -65
- package/dist/{chunk-VWWY5TL5.js → chunk-O37AV27P.js} +14 -75
- package/dist/chunk-RIITGTV3.js +275 -0
- package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
- package/dist/chunk-S7QRHUWO.js +8 -0
- package/dist/{chunk-Z7W3DQTR.js → chunk-T26HAKW3.js} +424 -42
- package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
- package/dist/{chunk-GPPVYLEI.js → chunk-UUUSJDIG.js} +1 -1
- package/dist/chunk-VY3IFGE3.js +70 -0
- package/dist/chunk-WYOFQHKI.js +176 -0
- package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
- package/dist/{chunk-DS4GCYV2.js → chunk-YV3YSZEB.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 +24 -9
- 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 +150 -79
- 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 +22 -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 +17 -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 +25 -18
- package/dist/components/sections/register.js +25 -18
- 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 +10 -17
- 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 +6 -3
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.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 +95 -48
- 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 +7 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +12 -3
- 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/image-variant-plan.d.ts +42 -0
- package/dist/lib/image-variant-plan.d.ts.map +1 -0
- package/dist/lib/image-variant-plan.js +53 -0
- package/dist/lib/index-ops.d.ts.map +1 -1
- package/dist/lib/index-ops.js +21 -17
- package/dist/lib/index.d.ts +7 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +55 -26
- package/dist/lib/loader.d.ts +25 -12
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/media-ops.d.ts +73 -0
- package/dist/lib/media-ops.d.ts.map +1 -0
- package/dist/lib/media-ops.js +60 -0
- package/dist/lib/nav-tree.d.ts +7 -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/pages.d.ts +2 -1
- package/dist/lib/pages.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/sidecar-diagnostics.d.ts +20 -0
- package/dist/lib/sidecar-diagnostics.d.ts.map +1 -0
- package/dist/lib/sidecars.d.ts +118 -0
- package/dist/lib/sidecars.d.ts.map +1 -0
- package/dist/lib/sidecars.js +23 -0
- package/dist/lib/slug.d.ts +2 -0
- package/dist/lib/slug.d.ts.map +1 -0
- package/dist/lib/storage-tier.d.ts +17 -0
- package/dist/lib/storage-tier.d.ts.map +1 -0
- package/dist/lib/storage-tier.js +29 -0
- package/dist/lib/tags.d.ts +64 -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 +18 -14
- package/dist/media/queue.d.ts +2 -0
- package/dist/media/queue.d.ts.map +1 -1
- 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/media/worker.d.ts +1 -35
- package/dist/media/worker.d.ts.map +1 -1
- 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 +25 -13
- package/dist/schemas/media.d.ts +4 -0
- package/dist/schemas/media.d.ts.map +1 -1
- 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 +2 -1
- package/dist/storage/memory.d.ts +21 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +129 -0
- package/dist/storage/types.d.ts +9 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +26 -1
- package/src/auth/cookies.ts +111 -0
- package/src/collab/rebase.ts +29 -9
- 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 +501 -338
- 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 +27 -31
- package/src/hooks/useMediaPipeline.ts +30 -24
- 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 +24 -2
- 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/image-variant-plan.ts +92 -0
- package/src/lib/index-ops.ts +9 -18
- package/src/lib/index.ts +20 -4
- package/src/lib/loader.ts +28 -88
- package/src/lib/media-ops.ts +112 -0
- package/src/lib/nav-tree.ts +104 -4
- package/src/lib/nav.ts +196 -36
- package/src/lib/pages.ts +2 -3
- 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/sidecar-diagnostics.ts +29 -0
- package/src/lib/sidecars.ts +221 -0
- package/src/lib/slug.ts +6 -0
- package/src/lib/storage-tier.ts +38 -0
- package/src/lib/tags.ts +165 -0
- package/src/lib/theme-vars.ts +23 -0
- package/src/media/index.ts +1 -1
- package/src/media/queue.ts +3 -0
- package/src/media/utils.ts +19 -1
- package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
- package/src/media/worker.ts +17 -8
- package/src/schemas/index.ts +1 -0
- package/src/schemas/media.ts +7 -2
- 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/memory.ts +178 -0
- package/src/storage/types.ts +16 -3
- 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
|
|
|
@@ -646,6 +689,7 @@ export default function EditorShell({
|
|
|
646
689
|
media: remote.mediaManifest ?? mediaManifestRef.current,
|
|
647
690
|
documents: remote.documentManifest ?? documentManifestRef.current ?? {},
|
|
648
691
|
},
|
|
692
|
+
remote.tagsDegraded,
|
|
649
693
|
{
|
|
650
694
|
savedBranchSha: remote.savedBranchSha,
|
|
651
695
|
changedSectionIds: remote.changedSectionIds,
|
|
@@ -732,6 +776,7 @@ export default function EditorShell({
|
|
|
732
776
|
}
|
|
733
777
|
setSections(result.sections);
|
|
734
778
|
setSiteIndex(result.index);
|
|
779
|
+
setTagsDegraded(remote.tagsDegraded);
|
|
735
780
|
setSavedSha(remote.savedBranchSha);
|
|
736
781
|
setChangedSectionIds(new Set(remote.changedSectionIds));
|
|
737
782
|
setMainIndex(remote.mainIndex);
|
|
@@ -757,6 +802,7 @@ export default function EditorShell({
|
|
|
757
802
|
media: remote.mediaManifest ?? mediaManifestRef.current,
|
|
758
803
|
documents: remote.documentManifest ?? documentManifestRef.current ?? {},
|
|
759
804
|
},
|
|
805
|
+
remote.tagsDegraded,
|
|
760
806
|
{
|
|
761
807
|
savedBranchSha: remote.savedBranchSha,
|
|
762
808
|
changedSectionIds: remote.changedSectionIds,
|
|
@@ -914,6 +960,7 @@ export default function EditorShell({
|
|
|
914
960
|
clearConfigDirty: persistence.clearConfigDirty,
|
|
915
961
|
siteIndexRef,
|
|
916
962
|
siteConfig,
|
|
963
|
+
tagsDegraded,
|
|
917
964
|
sections,
|
|
918
965
|
deletedSectionIds: Array.from(deletedSections),
|
|
919
966
|
onSaveWarnings: setSaveWarnings,
|
|
@@ -1026,6 +1073,7 @@ export default function EditorShell({
|
|
|
1026
1073
|
// onMediaPublished / onDocumentsPublished callbacks only refresh through a
|
|
1027
1074
|
// state effect and can still lag by a render at this point.
|
|
1028
1075
|
{ media: committedManifest, documents: committedDocumentManifest },
|
|
1076
|
+
tagsDegraded,
|
|
1029
1077
|
{
|
|
1030
1078
|
savedBranchSha: sha,
|
|
1031
1079
|
changedSectionIds: [...changed],
|
|
@@ -1085,7 +1133,7 @@ export default function EditorShell({
|
|
|
1085
1133
|
useEffect(() => {
|
|
1086
1134
|
if (!resolvedActivePage) return;
|
|
1087
1135
|
const nav = generateSiteNav({
|
|
1088
|
-
index: siteIndex, activeSections,
|
|
1136
|
+
index: siteIndex, activeSections: navSections,
|
|
1089
1137
|
// The Audience Details surface is not a page: clearing the active id drops
|
|
1090
1138
|
// the previous page's highlight (and its heading tree), so the sidebar
|
|
1091
1139
|
// never shows you in two places at once.
|
|
@@ -1094,7 +1142,7 @@ export default function EditorShell({
|
|
|
1094
1142
|
documents: siteDocuments ?? [],
|
|
1095
1143
|
});
|
|
1096
1144
|
siteNavChangeEvent.dispatch(nav);
|
|
1097
|
-
}, [siteIndex,
|
|
1145
|
+
}, [siteIndex, navSections, resolvedActivePage, siteDocuments, showAudienceDetails]);
|
|
1098
1146
|
|
|
1099
1147
|
const goToPage = useCallback((pageId: string) => {
|
|
1100
1148
|
const page = pageById(siteIndexRef.current, pageId);
|
|
@@ -1112,6 +1160,30 @@ export default function EditorShell({
|
|
|
1112
1160
|
|
|
1113
1161
|
// Nav island → shell page selection.
|
|
1114
1162
|
useEffect(() => pageSelectEvent.listen((id) => { setShowAudienceDetails(false); goToPage(id); }), [goToPage]);
|
|
1163
|
+
|
|
1164
|
+
// A Reference's "Edit source": open the source's page, then scroll to it once
|
|
1165
|
+
// that page's sections have rendered.
|
|
1166
|
+
const [scrollToSectionId, setScrollToSectionId] = useState<string | null>(null);
|
|
1167
|
+
const jumpToSection = useCallback((sectionId: string) => {
|
|
1168
|
+
const page = siteIndexRef.current.pages.find((p) => p.order.includes(sectionId));
|
|
1169
|
+
if (!page) return;
|
|
1170
|
+
pageSelectEvent.dispatch(page.id);
|
|
1171
|
+
setScrollToSectionId(sectionId);
|
|
1172
|
+
}, []);
|
|
1173
|
+
// One attempt, cleared either way: a section the canvas isn't showing (a
|
|
1174
|
+
// draft in the published preview) must not leave a scroll armed for later.
|
|
1175
|
+
useEffect(() => {
|
|
1176
|
+
if (!scrollToSectionId) return;
|
|
1177
|
+
document.querySelector(`[data-section-id="${CSS.escape(scrollToSectionId)}"]`)
|
|
1178
|
+
?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1179
|
+
setScrollToSectionId(null);
|
|
1180
|
+
}, [scrollToSectionId, activeSections]);
|
|
1181
|
+
|
|
1182
|
+
const referenceSources = useMemo<ReferenceSources>(() => publishedReferenceSources({
|
|
1183
|
+
viewSections, viewIndex, mainIndex, activePageId: resolvedActivePage?.id ?? null, audiences, mediaManifest, jumpToSection,
|
|
1184
|
+
}) ?? { index: siteIndex, sections, audiences, mediaManifest, copies: referenceCopies, jumpToSection },
|
|
1185
|
+
[viewSections, viewIndex, mainIndex, resolvedActivePage, siteIndex, sections, audiences, mediaManifest, referenceCopies, jumpToSection]);
|
|
1186
|
+
|
|
1115
1187
|
useEffect(() => audienceDetailsEvent.listen(() => setShowAudienceDetails(true)), []);
|
|
1116
1188
|
|
|
1117
1189
|
// Fetched on open rather than at mount: most editing sessions never look at
|
|
@@ -1254,6 +1326,7 @@ export default function EditorShell({
|
|
|
1254
1326
|
await cacheContent(
|
|
1255
1327
|
data.sha, data.sections, data.index, data.siteConfig,
|
|
1256
1328
|
{ media: loadedManifest, documents: data.documentManifest ?? {} },
|
|
1329
|
+
data.tagsDegraded,
|
|
1257
1330
|
{
|
|
1258
1331
|
savedBranchSha: data.savedBranchSha,
|
|
1259
1332
|
changedSectionIds: data.changedSectionIds,
|
|
@@ -1269,6 +1342,7 @@ export default function EditorShell({
|
|
|
1269
1342
|
setChangedSectionIds(new Set(data.changedSectionIds));
|
|
1270
1343
|
setMainIndex(data.mainIndex);
|
|
1271
1344
|
setUnparseableSectionIds(data.unparseableSectionIds);
|
|
1345
|
+
setTagsDegraded(data.tagsDegraded);
|
|
1272
1346
|
}
|
|
1273
1347
|
|
|
1274
1348
|
if (cancelled) return;
|
|
@@ -1321,7 +1395,7 @@ export default function EditorShell({
|
|
|
1321
1395
|
|
|
1322
1396
|
loadContent().catch((err) => {
|
|
1323
1397
|
console.error("Failed to load editor content:", err);
|
|
1324
|
-
setShellState({ phase: "error", message: err instanceof Error ? err.message : "
|
|
1398
|
+
setShellState({ phase: "error", message: err instanceof Error ? err.message : "Couldn't load content" });
|
|
1325
1399
|
});
|
|
1326
1400
|
return () => { cancelled = true; };
|
|
1327
1401
|
}, [headSha, draftHeadSha, siteId, applySiteConfigPreview, fetchSavedContent]);
|
|
@@ -1354,7 +1428,12 @@ export default function EditorShell({
|
|
|
1354
1428
|
return null;
|
|
1355
1429
|
}).filter((s): s is LoadedSection => s !== null),
|
|
1356
1430
|
);
|
|
1357
|
-
|
|
1431
|
+
// A row written before tags shipped carries none, and that build could
|
|
1432
|
+
// not have edited them — keep the freshly fetched registry rather than
|
|
1433
|
+
// installing an index that reads as "tags never loaded".
|
|
1434
|
+
setSiteIndex(restoredIndex.tags === undefined && siteIndexRef.current.tags !== undefined
|
|
1435
|
+
? { ...restoredIndex, tags: siteIndexRef.current.tags }
|
|
1436
|
+
: restoredIndex);
|
|
1358
1437
|
// Pending deletions come from the snapshot's own persisted intent, and are
|
|
1359
1438
|
// intersected with what the branch still has so an already-applied deletion
|
|
1360
1439
|
// can't be replayed.
|
|
@@ -1470,6 +1549,11 @@ export default function EditorShell({
|
|
|
1470
1549
|
);
|
|
1471
1550
|
|
|
1472
1551
|
const handleDiscard = useCallback(async () => {
|
|
1552
|
+
// Drop Site Settings' debounced config write too, or its trailing edge
|
|
1553
|
+
// would write the discarded config back into Dexie.
|
|
1554
|
+
if (configFlushTimerRef.current) { clearTimeout(configFlushTimerRef.current); configFlushTimerRef.current = null; }
|
|
1555
|
+
pendingConfigRef.current = null;
|
|
1556
|
+
await persistence.discardPending();
|
|
1473
1557
|
await discardLocalChanges();
|
|
1474
1558
|
setLocalChangesExist(false);
|
|
1475
1559
|
setDirtySectionIds(new Set());
|
|
@@ -1614,6 +1698,9 @@ export default function EditorShell({
|
|
|
1614
1698
|
setSiteIndex((prev) => reorderVisibleSectionInPage(prev, resolvedActivePage.id, visibleIds, fromIndex, toIndex));
|
|
1615
1699
|
persistence.markIndexDirty();
|
|
1616
1700
|
setLocalChangesExist(true);
|
|
1701
|
+
const movedType = activeSections[fromIndex]?.section.type;
|
|
1702
|
+
const label = (movedType && getSection(movedType)?.label) || "Section";
|
|
1703
|
+
setReorderAnnouncement(`Moved ${label} to position ${toIndex + 1} of ${visibleIds.length}`);
|
|
1617
1704
|
},
|
|
1618
1705
|
[persistence, resolvedActivePage, activeSections],
|
|
1619
1706
|
);
|
|
@@ -1655,8 +1742,16 @@ export default function EditorShell({
|
|
|
1655
1742
|
}, [persistence]);
|
|
1656
1743
|
|
|
1657
1744
|
const handleMoveNavEntry = useCallback((from: NavLocation, to: NavLocation) => {
|
|
1658
|
-
|
|
1745
|
+
// Read the pre-move index from the ref (mirrors onBlockMove's getState
|
|
1746
|
+
// pattern) rather than the updater's `prev`, so describeNavMove can
|
|
1747
|
+
// resolve `from`'s label/kind against the SAME snapshot moveNavEntry acts
|
|
1748
|
+
// on, and the two calls can't observe different states.
|
|
1749
|
+
const prevIndex = siteIndexRef.current;
|
|
1750
|
+
const nextIndex = moveNavEntry(prevIndex, from, to);
|
|
1751
|
+
setSiteIndex(() => nextIndex);
|
|
1659
1752
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1753
|
+
const message = describeNavMove(prevIndex, nextIndex, from);
|
|
1754
|
+
if (message) setReorderAnnouncement(message);
|
|
1660
1755
|
}, [persistence]);
|
|
1661
1756
|
|
|
1662
1757
|
const handleAddGroup = useCallback(() => {
|
|
@@ -1682,6 +1777,48 @@ export default function EditorShell({
|
|
|
1682
1777
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1683
1778
|
}, [persistence]);
|
|
1684
1779
|
|
|
1780
|
+
// Section tags: the same index-state edit + markIndexDirty as the nav group
|
|
1781
|
+
// handlers above. Every save then carries the runtime `tags`, which
|
|
1782
|
+
// content-ops writes to the tags.json sidecar.
|
|
1783
|
+
const handleCreateTag = useCallback((label: string, sectionId?: string) => {
|
|
1784
|
+
const id = typeof crypto !== "undefined" && crypto.randomUUID ? `tag_${crypto.randomUUID()}` : `tag_${Date.now()}`;
|
|
1785
|
+
setSiteIndex((prev) => {
|
|
1786
|
+
// Typing an existing tag's name reuses it rather than minting a twin.
|
|
1787
|
+
const existing = findTagByLabel(siteTags(prev), label);
|
|
1788
|
+
if (existing) return sectionId ? setSectionTagged(prev, sectionId, existing.id, true) : prev;
|
|
1789
|
+
return addTag(prev, id, label, sectionId);
|
|
1790
|
+
});
|
|
1791
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1792
|
+
}, [persistence]);
|
|
1793
|
+
|
|
1794
|
+
const handleRenameTag = useCallback((tagId: string, label: string) => {
|
|
1795
|
+
setSiteIndex((prev) => renameTag(prev, tagId, label));
|
|
1796
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1797
|
+
}, [persistence]);
|
|
1798
|
+
|
|
1799
|
+
const handleDeleteTag = useCallback((tagId: string) => {
|
|
1800
|
+
setSiteIndex((prev) => deleteTag(prev, tagId));
|
|
1801
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1802
|
+
}, [persistence]);
|
|
1803
|
+
|
|
1804
|
+
const handleSectionTagToggle = useCallback((sectionId: string, tagId: string, tagged: boolean) => {
|
|
1805
|
+
setSiteIndex((prev) => setSectionTagged(prev, sectionId, tagId, tagged));
|
|
1806
|
+
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
1807
|
+
}, [persistence]);
|
|
1808
|
+
|
|
1809
|
+
const tags = useMemo(() => siteTags(siteIndex), [siteIndex]);
|
|
1810
|
+
// Absent `tags` means the registry was never loaded (a Dexie row or cache
|
|
1811
|
+
// entry from a build that predates tags) — NOT an empty registry. Editing it
|
|
1812
|
+
// would post `tags: [new]` and wholesale-replace the real tags.json, so
|
|
1813
|
+
// every tag edit is off until a load that read the sidecar. A degraded read
|
|
1814
|
+
// (tags.json held entries this version couldn't parse) is the same hazard:
|
|
1815
|
+
// saving would drop the entries it skipped.
|
|
1816
|
+
const tagsEditable = siteIndex.tags !== undefined && !tagsDegraded;
|
|
1817
|
+
const tagging = useMemo<SiteTagging>(() => ({
|
|
1818
|
+
tags, editable: tagsEditable,
|
|
1819
|
+
onCreate: handleCreateTag, onRename: handleRenameTag, onDelete: handleDeleteTag, onToggle: handleSectionTagToggle,
|
|
1820
|
+
}), [tags, tagsEditable, handleCreateTag, handleRenameTag, handleDeleteTag, handleSectionTagToggle]);
|
|
1821
|
+
|
|
1685
1822
|
const handleSetHome = useCallback((id: string) => {
|
|
1686
1823
|
setSiteIndex((prev) => setHomePage(prev, id));
|
|
1687
1824
|
persistence.markIndexDirty(); setLocalChangesExist(true);
|
|
@@ -1902,7 +2039,7 @@ export default function EditorShell({
|
|
|
1902
2039
|
return historyRestoreEvent.listen(async ({ sha }) => {
|
|
1903
2040
|
try {
|
|
1904
2041
|
const res = await fetch(`/api/content/history?sha=${sha}`);
|
|
1905
|
-
if (!res.ok) throw new Error(`
|
|
2042
|
+
if (!res.ok) throw new Error(`Couldn't load version: ${res.status}`);
|
|
1906
2043
|
const data = await res.json();
|
|
1907
2044
|
const ok = await handleHistoryRestore({
|
|
1908
2045
|
sections: data.sections,
|
|
@@ -1933,7 +2070,7 @@ export default function EditorShell({
|
|
|
1933
2070
|
return (
|
|
1934
2071
|
<div className="flex min-h-[50vh] items-center justify-center">
|
|
1935
2072
|
<div className="text-center">
|
|
1936
|
-
<p className="text-sm text-
|
|
2073
|
+
<p className="text-sm text-danger">{shellState.message}</p>
|
|
1937
2074
|
<Button variant="secondary" size="md" className="mt-3" onClick={() => window.location.reload()}>
|
|
1938
2075
|
Retry
|
|
1939
2076
|
</Button>
|
|
@@ -1963,15 +2100,21 @@ export default function EditorShell({
|
|
|
1963
2100
|
|
|
1964
2101
|
return (
|
|
1965
2102
|
<PagesContext.Provider value={pagesContextValue}>
|
|
2103
|
+
<ReferenceSourcesContext.Provider value={referenceSources}>
|
|
2104
|
+
<BrandSwatchesContext.Provider value={brandSwatches}>
|
|
1966
2105
|
<EditorProvider>
|
|
1967
2106
|
<ViewBranchWatcher
|
|
1968
|
-
|
|
1969
|
-
onError={
|
|
2107
|
+
setView={setView}
|
|
2108
|
+
onError={showRemoteNotice}
|
|
1970
2109
|
/>
|
|
1971
|
-
<HistoryWatcher
|
|
2110
|
+
<HistoryWatcher />
|
|
1972
2111
|
<EditorModalProvider>
|
|
1973
2112
|
<MediaLibraryContext.Provider value={mediaLibraryValue}>
|
|
1974
2113
|
<div className="editor-shell relative">
|
|
2114
|
+
{/* Announces every section/page/group reorder — from drag or the
|
|
2115
|
+
Move up/down buttons — since neither leaves a visible confirmation
|
|
2116
|
+
a screen reader user would otherwise get. */}
|
|
2117
|
+
<p aria-live="polite" className="sr-only">{reorderAnnouncement}</p>
|
|
1975
2118
|
<EditorToolbar
|
|
1976
2119
|
buttonState={buttonState}
|
|
1977
2120
|
localChangesExist={localChangesExist}
|
|
@@ -2000,42 +2143,22 @@ export default function EditorShell({
|
|
|
2000
2143
|
audiences={audiences}
|
|
2001
2144
|
authoringDefaults={authoringDefaults}
|
|
2002
2145
|
onAuthoringDefaultsChange={onAuthoringDefaultsChange}
|
|
2146
|
+
docEditEnabled={Boolean(resolvedActivePage?.document?.enabled)}
|
|
2147
|
+
onDocEditClick={() => setShowDocumentEditor(true)}
|
|
2148
|
+
// Bug reports persist via Supabase. On the password-only
|
|
2149
|
+
// (zero-Supabase) path there's no backend and the editor session
|
|
2150
|
+
// has userId: null, so the button would always 401 — hide it there.
|
|
2151
|
+
showBugReport={!capabilities.passwordOnly}
|
|
2003
2152
|
/>
|
|
2004
2153
|
|
|
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
2154
|
{/* Deck Builder (#34). Editors are super-admins over decks and are
|
|
2029
2155
|
never gated by deckBuilder.audiences, so this is unconditional.
|
|
2030
2156
|
An <a>, not a click handler: builder mode is its own route.
|
|
2031
|
-
|
|
2157
|
+
Standalone (not in EditorToolbar's FabStack): bottom-60 clears
|
|
2158
|
+
the stack's three members with room to spare. Taking over the
|
|
2032
2159
|
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>
|
|
2160
|
+
<Fab href="/decks?next=%2Fedit" label="Build a deck" column="editor" className="bottom-60">
|
|
2161
|
+
<Presentation size={20} aria-hidden="true" />
|
|
2039
2162
|
</Fab>
|
|
2040
2163
|
{showDocumentEditor && resolvedActivePage?.document?.enabled && (
|
|
2041
2164
|
<Suspense fallback={<DocumentOverlayFallback />}>
|
|
@@ -2047,8 +2170,9 @@ export default function EditorShell({
|
|
|
2047
2170
|
editor. NOTE: viewSections drives the "Published" branch preview
|
|
2048
2171
|
only; the separate historySelectEvent commit view
|
|
2049
2172
|
(EditorContext.historyState) is the OTHER read-only case — the
|
|
2050
|
-
|
|
2051
|
-
ever being opened onto it in the
|
|
2173
|
+
document FAB in EditorToolbar is gated on !historyState, which
|
|
2174
|
+
keeps this overlay from ever being opened onto it in the
|
|
2175
|
+
first place. */}
|
|
2052
2176
|
{viewSections !== null ? (
|
|
2053
2177
|
<DocumentPresentOverlay
|
|
2054
2178
|
page={resolvedActivePage}
|
|
@@ -2086,7 +2210,7 @@ export default function EditorShell({
|
|
|
2086
2210
|
|
|
2087
2211
|
{rejectedUploads.length > 0 && (
|
|
2088
2212
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2089
|
-
<
|
|
2213
|
+
<InlineAlert
|
|
2090
2214
|
message={formatRejections(rejectedUploads, siteConfig?.media.maxFileSize)}
|
|
2091
2215
|
onDismiss={() => setRejectedUploads([])}
|
|
2092
2216
|
/>
|
|
@@ -2100,7 +2224,7 @@ export default function EditorShell({
|
|
|
2100
2224
|
never show the same message twice. */}
|
|
2101
2225
|
{uploadErrorMessage && !showMediaLibrary && (
|
|
2102
2226
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2103
|
-
<
|
|
2227
|
+
<InlineAlert
|
|
2104
2228
|
message={uploadErrorMessage}
|
|
2105
2229
|
onDismiss={() => setUploadErrorMessage(null)}
|
|
2106
2230
|
/>
|
|
@@ -2109,29 +2233,34 @@ export default function EditorShell({
|
|
|
2109
2233
|
|
|
2110
2234
|
{unparseableSectionIds.length > 0 && (
|
|
2111
2235
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
2112
|
-
<
|
|
2236
|
+
<InlineAlert
|
|
2113
2237
|
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
2238
|
onDismiss={() => setUnparseableSectionIds([])}
|
|
2115
2239
|
/>
|
|
2116
2240
|
</div>
|
|
2117
2241
|
)}
|
|
2118
2242
|
|
|
2119
|
-
{
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2243
|
+
{/* One sticky container so the two banners stack instead of overlapping. */}
|
|
2244
|
+
{(persistence.persistenceError || saveWarnings.length > 0) && (
|
|
2245
|
+
<div className="sticky top-2 z-30 mx-auto flex w-full max-w-3xl flex-col gap-2 px-4">
|
|
2246
|
+
{persistence.persistenceError && (
|
|
2247
|
+
<Notice tone="error" placement="inline">
|
|
2248
|
+
{persistence.persistenceError}
|
|
2249
|
+
</Notice>
|
|
2250
|
+
)}
|
|
2251
|
+
{saveWarnings.length > 0 && (
|
|
2252
|
+
<InlineAlert
|
|
2253
|
+
message={saveWarnings.join(" ")}
|
|
2254
|
+
onDismiss={() => setSaveWarnings([])}
|
|
2255
|
+
/>
|
|
2256
|
+
)}
|
|
2125
2257
|
</div>
|
|
2126
2258
|
)}
|
|
2127
2259
|
|
|
2128
2260
|
{remoteNotice && (
|
|
2129
|
-
<
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
>
|
|
2133
|
-
{remoteNotice}
|
|
2134
|
-
</div>
|
|
2261
|
+
<Notice tone={remoteNotice.tone} placement="toast">
|
|
2262
|
+
{remoteNotice.message}
|
|
2263
|
+
</Notice>
|
|
2135
2264
|
)}
|
|
2136
2265
|
|
|
2137
2266
|
{/* bp-band-shift: rides the chat dock's FLIP glide (base.css). On
|
|
@@ -2164,7 +2293,7 @@ export default function EditorShell({
|
|
|
2164
2293
|
) : (
|
|
2165
2294
|
<HistoryOrEditorContent
|
|
2166
2295
|
index={siteIndex}
|
|
2167
|
-
activeSections={
|
|
2296
|
+
activeSections={navSections}
|
|
2168
2297
|
activePageId={resolvedActivePage?.id ?? null}
|
|
2169
2298
|
>
|
|
2170
2299
|
<EditorContent
|
|
@@ -2181,6 +2310,7 @@ export default function EditorShell({
|
|
|
2181
2310
|
onBlockMove={onBlockMove}
|
|
2182
2311
|
onMoveSection={siteIndex.pages.length > 1 ? setMovingSectionId : undefined}
|
|
2183
2312
|
onAccessChange={onAccessChange}
|
|
2313
|
+
tagging={tagging}
|
|
2184
2314
|
onStatusChange={onStatusChange}
|
|
2185
2315
|
mainIndex={mainIndex}
|
|
2186
2316
|
changedSectionIds={changedSectionIds}
|
|
@@ -2203,41 +2333,34 @@ export default function EditorShell({
|
|
|
2203
2333
|
chatbotPlatformEnabled={chatbotPlatformEnabled}
|
|
2204
2334
|
capabilities={capabilities}
|
|
2205
2335
|
currentUser={currentUser}
|
|
2336
|
+
tagging={tagging}
|
|
2206
2337
|
/>
|
|
2207
|
-
<
|
|
2338
|
+
<ConfirmDialog
|
|
2208
2339
|
isOpen={pendingDeleteSectionId !== null}
|
|
2209
|
-
onClose={() => setPendingDeleteSectionId(null)}
|
|
2210
2340
|
title="Delete Section"
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
</div>
|
|
2223
|
-
</EditorModal>
|
|
2224
|
-
<EditorModal
|
|
2341
|
+
body={
|
|
2342
|
+
<p className="text-sm text-base-contrast-light">
|
|
2343
|
+
Delete this section? It will be removed from the page now and deleted permanently when you save or publish.
|
|
2344
|
+
</p>
|
|
2345
|
+
}
|
|
2346
|
+
confirmLabel="Delete"
|
|
2347
|
+
tone="destructive"
|
|
2348
|
+
onConfirm={confirmDeleteSection}
|
|
2349
|
+
onCancel={() => setPendingDeleteSectionId(null)}
|
|
2350
|
+
/>
|
|
2351
|
+
<ConfirmDialog
|
|
2225
2352
|
isOpen={showDiscardConfirm}
|
|
2226
|
-
onClose={() => setShowDiscardConfirm(false)}
|
|
2227
2353
|
title="Discard Changes"
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
</Button>
|
|
2239
|
-
</div>
|
|
2240
|
-
</EditorModal>
|
|
2354
|
+
body={
|
|
2355
|
+
<p className="text-sm text-base-contrast-light">
|
|
2356
|
+
Discard all unsaved changes? This will reload with the last published content.
|
|
2357
|
+
</p>
|
|
2358
|
+
}
|
|
2359
|
+
confirmLabel="Discard"
|
|
2360
|
+
tone="destructive"
|
|
2361
|
+
onConfirm={handleToolbarDiscard}
|
|
2362
|
+
onCancel={() => setShowDiscardConfirm(false)}
|
|
2363
|
+
/>
|
|
2241
2364
|
<EditorModal
|
|
2242
2365
|
isOpen={showMediaLibrary}
|
|
2243
2366
|
onClose={() => {
|
|
@@ -2357,16 +2480,18 @@ export default function EditorShell({
|
|
|
2357
2480
|
</MediaLibraryContext.Provider>
|
|
2358
2481
|
</EditorModalProvider>
|
|
2359
2482
|
</EditorProvider>
|
|
2483
|
+
</BrandSwatchesContext.Provider>
|
|
2484
|
+
</ReferenceSourcesContext.Provider>
|
|
2360
2485
|
</PagesContext.Provider>
|
|
2361
2486
|
);
|
|
2362
2487
|
}
|
|
2363
2488
|
|
|
2364
2489
|
function ViewBranchWatcher({
|
|
2365
|
-
|
|
2490
|
+
setView,
|
|
2366
2491
|
onError,
|
|
2367
2492
|
}: {
|
|
2368
|
-
|
|
2369
|
-
onError: (message: string) => void;
|
|
2493
|
+
setView: (view: { sections: LoadedSection[]; index: SiteIndex | null } | null) => void;
|
|
2494
|
+
onError: (message: string, tone?: NoticeState["tone"]) => void;
|
|
2370
2495
|
}) {
|
|
2371
2496
|
const { isEditMode, viewBranch } = useEditorContext();
|
|
2372
2497
|
|
|
@@ -2376,7 +2501,7 @@ function ViewBranchWatcher({
|
|
|
2376
2501
|
// fetch (fetching `saved` would 404 after a publish and fall back to main,
|
|
2377
2502
|
// clobbering unsaved edits). Null viewSections routes to the in-memory path.
|
|
2378
2503
|
if (isEditMode || viewBranch !== "live") {
|
|
2379
|
-
|
|
2504
|
+
setView(null);
|
|
2380
2505
|
return;
|
|
2381
2506
|
}
|
|
2382
2507
|
|
|
@@ -2387,42 +2512,34 @@ function ViewBranchWatcher({
|
|
|
2387
2512
|
.then((res) => {
|
|
2388
2513
|
// A non-JSON 5xx used to parse to `undefined` and render an empty
|
|
2389
2514
|
// "Published" view with nothing explaining it.
|
|
2390
|
-
if (!res.ok) throw new Error(`
|
|
2515
|
+
if (!res.ok) throw new Error(`Couldn't load the published view: ${res.status}`);
|
|
2391
2516
|
return res.json();
|
|
2392
2517
|
})
|
|
2393
2518
|
.then((data) => {
|
|
2394
2519
|
if (!Array.isArray(data?.sections)) throw new Error("Malformed published-view response");
|
|
2395
|
-
|
|
2520
|
+
setView({ sections: data.sections, index: data.index ?? null });
|
|
2396
2521
|
})
|
|
2397
2522
|
.catch((err) => {
|
|
2398
2523
|
if (err?.name === "AbortError") return;
|
|
2399
2524
|
console.error(err);
|
|
2400
2525
|
// 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.");
|
|
2526
|
+
setView(null);
|
|
2527
|
+
onError("Couldn't load the published view — showing your current edits.", "error");
|
|
2403
2528
|
});
|
|
2404
2529
|
return () => controller.abort();
|
|
2405
|
-
}, [viewBranch, isEditMode,
|
|
2530
|
+
}, [viewBranch, isEditMode, setView, onError]);
|
|
2406
2531
|
|
|
2407
2532
|
return null;
|
|
2408
2533
|
}
|
|
2409
2534
|
|
|
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();
|
|
2535
|
+
function HistoryWatcher() {
|
|
2536
|
+
const { setHistoryState } = useEditorContext();
|
|
2420
2537
|
|
|
2421
2538
|
useEffect(() => {
|
|
2422
2539
|
const unlisten = historySelectEvent.listen(async ({ sha, date }) => {
|
|
2423
2540
|
try {
|
|
2424
2541
|
const res = await fetch(`/api/content/history?sha=${sha}`);
|
|
2425
|
-
if (!res.ok) throw new Error(`
|
|
2542
|
+
if (!res.ok) throw new Error(`Couldn't load history: ${res.status}`);
|
|
2426
2543
|
const data = await res.json();
|
|
2427
2544
|
setHistoryState({
|
|
2428
2545
|
sha,
|
|
@@ -2442,10 +2559,6 @@ function HistoryWatcher({
|
|
|
2442
2559
|
return unlisten;
|
|
2443
2560
|
}, [setHistoryState]);
|
|
2444
2561
|
|
|
2445
|
-
useEffect(() => {
|
|
2446
|
-
onHistoryActiveChange?.(historyState !== null);
|
|
2447
|
-
}, [historyState, onHistoryActiveChange]);
|
|
2448
|
-
|
|
2449
2562
|
return null;
|
|
2450
2563
|
}
|
|
2451
2564
|
|
|
@@ -2556,6 +2669,7 @@ function EditorContent({
|
|
|
2556
2669
|
onBlockMove,
|
|
2557
2670
|
onMoveSection,
|
|
2558
2671
|
onAccessChange,
|
|
2672
|
+
tagging,
|
|
2559
2673
|
onStatusChange,
|
|
2560
2674
|
mainIndex,
|
|
2561
2675
|
changedSectionIds,
|
|
@@ -2577,6 +2691,7 @@ function EditorContent({
|
|
|
2577
2691
|
onBlockMove: (move: BlockMove) => void;
|
|
2578
2692
|
onMoveSection?: (sectionId: string) => void;
|
|
2579
2693
|
onAccessChange: (sectionId: string, access: string[]) => void;
|
|
2694
|
+
tagging: SiteTagging;
|
|
2580
2695
|
onStatusChange: (sectionId: string, status: "draft" | "live" | "archived") => void;
|
|
2581
2696
|
mainIndex: SiteIndex | null;
|
|
2582
2697
|
changedSectionIds: Set<string>;
|
|
@@ -2589,6 +2704,7 @@ function EditorContent({
|
|
|
2589
2704
|
}) {
|
|
2590
2705
|
const { isEditMode, viewBranch, previewAudience } = useEditorContext();
|
|
2591
2706
|
const { openModal, closeModal } = useEditorModal();
|
|
2707
|
+
const referenceSources = useContext(ReferenceSourcesContext);
|
|
2592
2708
|
const [pendingInsertIndex, setPendingInsertIndex] = useState<number | null>(null);
|
|
2593
2709
|
const dismissPendingInsert = useCallback(() => setPendingInsertIndex(null), []);
|
|
2594
2710
|
|
|
@@ -2606,6 +2722,10 @@ function EditorContent({
|
|
|
2606
2722
|
viewSections,
|
|
2607
2723
|
activePageSectionIds,
|
|
2608
2724
|
});
|
|
2725
|
+
// TL;DR / Summary plates render their nav anchor from this. Derived from the
|
|
2726
|
+
// same list the sidebar nav is built from (not displaySections, which preview
|
|
2727
|
+
// modes filter), so a plate's id always matches the href its nav entry links to.
|
|
2728
|
+
const summaryAnchors = useMemo(() => summaryNavAnchors(sections), [sections]);
|
|
2609
2729
|
|
|
2610
2730
|
useEffect(() => {
|
|
2611
2731
|
return combine(
|
|
@@ -2651,157 +2771,174 @@ function EditorContent({
|
|
|
2651
2771
|
// `.section-wrapper:first-child` zeroes the 2.25rem that every other section
|
|
2652
2772
|
// reserves above itself for its hover chrome, so the first section's controls
|
|
2653
2773
|
// would sit under the fixed toolbar. Re-reserve it on the list instead.
|
|
2654
|
-
<
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2774
|
+
<SummaryAnchorsContext.Provider value={summaryAnchors}>
|
|
2775
|
+
<div className="pt-9">
|
|
2776
|
+
{displaySections.map(({ section, meta }, index) => {
|
|
2777
|
+
const definition = getSection(section.type);
|
|
2778
|
+
if (!definition) {
|
|
2779
|
+
return (
|
|
2780
|
+
<div
|
|
2781
|
+
key={section.id}
|
|
2782
|
+
className="border border-warning bg-warning-bg px-4 py-3 text-sm text-warning"
|
|
2783
|
+
role="alert"
|
|
2784
|
+
>
|
|
2785
|
+
Unknown section type: <code className="font-mono font-semibold">{section.type}</code>
|
|
2786
|
+
</div>
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
const Component = definition.component;
|
|
2790
|
+
// The viewer renders no wrapper for a Reference with nothing to show,
|
|
2791
|
+
// so neither does the preview — its spacing would otherwise remain.
|
|
2792
|
+
if (
|
|
2793
|
+
!isEditMode && section.type === REFERENCE_TYPE && referenceSources &&
|
|
2794
|
+
!referenceShowsInPreview(referenceSources, section.id, section.content as unknown as ReferenceContent, viewBranch)
|
|
2795
|
+
) {
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2658
2799
|
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
|
-
)}
|
|
2800
|
+
<Fragment key={section.id}>
|
|
2801
|
+
{editingEnabled && pendingInsertIndex === index && (
|
|
2802
|
+
<SectionSkeleton
|
|
2803
|
+
types={typeOptions}
|
|
2804
|
+
onSelect={(type) => {
|
|
2805
|
+
onAddSection(index, type);
|
|
2806
|
+
setPendingInsertIndex(null);
|
|
2807
|
+
}}
|
|
2808
|
+
onDismiss={dismissPendingInsert}
|
|
2809
|
+
/>
|
|
2810
|
+
)}
|
|
2682
2811
|
|
|
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}
|
|
2812
|
+
<ErrorBoundary label={`${definition.label} (${section.type})`}>
|
|
2813
|
+
<SectionLayout
|
|
2814
|
+
type={section.type}
|
|
2693
2815
|
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
|
-
}
|
|
2816
|
+
dimNonPublished={!isEditMode}
|
|
2817
|
+
dimmed={previewAudience !== null && !audiencePasses(meta.access, previewAudience)}
|
|
2771
2818
|
>
|
|
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
|
-
|
|
2819
|
+
<SectionWrapper
|
|
2820
|
+
sectionId={section.id}
|
|
2821
|
+
sectionType={section.type}
|
|
2822
|
+
status={meta.status}
|
|
2823
|
+
dirty={dirtySectionIds.has(section.id)}
|
|
2824
|
+
index={index}
|
|
2825
|
+
isLast={index === displaySections.length - 1}
|
|
2826
|
+
sectionCount={displaySections.length}
|
|
2827
|
+
containerId={ROOT_CONTAINER_ID}
|
|
2828
|
+
isContainerBlock={section.type === "container"}
|
|
2829
|
+
definition={definition}
|
|
2830
|
+
mainStatus={mainIndex?.sections[section.id]?.status ?? null}
|
|
2831
|
+
contentDiffersFromMain={changedSectionIds.has(section.id) || dirtySectionIds.has(section.id)}
|
|
2832
|
+
isLocalOnly={dirtySectionIds.has(section.id) && !changedSectionIds.has(section.id)}
|
|
2833
|
+
lockedBy={locks.get(section.id) ? { email: locks.get(section.id)!.email } : null}
|
|
2834
|
+
onTakeover={() => onTakeover(section.id)}
|
|
2835
|
+
onEditIntent={getEditIntent(section.id)}
|
|
2836
|
+
options={{
|
|
2837
|
+
...(section.content as Record<string, unknown>),
|
|
2838
|
+
...("options" in section ? (section.options as Record<string, unknown>) : {}),
|
|
2839
|
+
}}
|
|
2840
|
+
audiences={audiences}
|
|
2841
|
+
access={meta.access}
|
|
2842
|
+
onAccessChange={editingEnabled ? (a) => onAccessChange(section.id, a) : undefined}
|
|
2843
|
+
tagging={{
|
|
2844
|
+
tags: tagging.tags,
|
|
2845
|
+
assignedIds: tagging.tags.filter((t) => t.sectionIds.includes(section.id)).map((t) => t.id),
|
|
2846
|
+
onToggle: editingEnabled && tagging.editable ? (tagId, tagged) => tagging.onToggle(section.id, tagId, tagged) : undefined,
|
|
2847
|
+
onCreate: editingEnabled && tagging.editable ? (label) => tagging.onCreate(label, section.id) : undefined,
|
|
2848
|
+
}}
|
|
2849
|
+
onStatusChange={editingEnabled ? (s) => onStatusChange(section.id, s) : undefined}
|
|
2850
|
+
onSectionChange={editingEnabled ? (settingsResult) => {
|
|
2851
|
+
const result = settingsResult as Record<string, unknown>;
|
|
2852
|
+
if (result && typeof result === "object" && "content" in result && "options" in result) {
|
|
2853
|
+
const contentPatch = result.content as Record<string, unknown>;
|
|
2854
|
+
const optionsPatch = result.options as Record<string, unknown>;
|
|
2855
|
+
onSectionChange(section.id, {
|
|
2856
|
+
...section,
|
|
2857
|
+
content: { ...section.content, ...contentPatch },
|
|
2858
|
+
options: { ...("options" in section ? section.options : {}), ...optionsPatch },
|
|
2859
|
+
} as SectionContent);
|
|
2860
|
+
} else {
|
|
2861
|
+
onSectionChange(section.id, {
|
|
2862
|
+
...section,
|
|
2863
|
+
...result,
|
|
2864
|
+
} as SectionContent);
|
|
2865
|
+
}
|
|
2866
|
+
} : undefined}
|
|
2867
|
+
onReorder={editingEnabled ? onReorderSections : undefined}
|
|
2868
|
+
onMoveSection={editingEnabled && onMoveSection ? () => onMoveSection(section.id) : undefined}
|
|
2869
|
+
onRequestInsert={editingEnabled ? (i) => setPendingInsertIndex(i) : undefined}
|
|
2870
|
+
onDelete={editingEnabled ? () => onDeleteSection(section.id) : undefined}
|
|
2871
|
+
onAddChild={
|
|
2872
|
+
editingEnabled && section.type === "container"
|
|
2873
|
+
? () => {
|
|
2874
|
+
openModal(
|
|
2875
|
+
"Add block",
|
|
2876
|
+
<SectionTypePicker
|
|
2877
|
+
types={childInsertableTypes()}
|
|
2878
|
+
onClose={closeModal}
|
|
2879
|
+
onSelect={(type) => {
|
|
2880
|
+
const def = getSection(type);
|
|
2881
|
+
if (def) {
|
|
2882
|
+
const id =
|
|
2883
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
2884
|
+
? crypto.randomUUID()
|
|
2885
|
+
: `child-${Date.now()}`;
|
|
2886
|
+
const child = { id, ...(def.defaults() as object) } as Section;
|
|
2887
|
+
const containerSection: Section = {
|
|
2888
|
+
id: section.id,
|
|
2889
|
+
type: "container",
|
|
2890
|
+
content: section.content as Record<string, unknown>,
|
|
2891
|
+
};
|
|
2892
|
+
const childCount =
|
|
2893
|
+
(section.content as { children?: unknown[] }).children?.length ?? 0;
|
|
2894
|
+
const next = insertChildAt(containerSection, child, childCount);
|
|
2895
|
+
onSectionChange(section.id, {
|
|
2896
|
+
...section,
|
|
2897
|
+
content: next.content,
|
|
2898
|
+
} as SectionContent);
|
|
2899
|
+
}
|
|
2900
|
+
closeModal();
|
|
2901
|
+
}}
|
|
2902
|
+
/>,
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
: undefined
|
|
2906
|
+
}
|
|
2907
|
+
>
|
|
2908
|
+
<Component
|
|
2909
|
+
content={section}
|
|
2910
|
+
options={"options" in section ? (section.options as Record<string, unknown>) : undefined}
|
|
2911
|
+
onChange={editingEnabled ? (newContent) => onSectionChange(section.id, newContent as SectionContent) : undefined}
|
|
2912
|
+
isEditMode={isEditMode}
|
|
2913
|
+
openModal={editingEnabled ? openModal : undefined}
|
|
2914
|
+
// Immediate (no confirm modal): used when a colors section's
|
|
2915
|
+
// last swatch is dragged out, auto-removing the empty section.
|
|
2916
|
+
onDelete={editingEnabled ? () => onDeleteSectionImmediate(section.id) : undefined}
|
|
2917
|
+
/>
|
|
2918
|
+
</SectionWrapper>
|
|
2919
|
+
</SectionLayout>
|
|
2920
|
+
</ErrorBoundary>
|
|
2921
|
+
</Fragment>
|
|
2922
|
+
);
|
|
2923
|
+
})}
|
|
2924
|
+
|
|
2925
|
+
{/* Trailing selector: shown when an insert was requested after the last
|
|
2926
|
+
section, or always on an empty page — an empty page has no section
|
|
2927
|
+
chrome to insert from, so the selector IS its default state (not
|
|
2928
|
+
dismissible; there is nothing behind it). */}
|
|
2929
|
+
{editingEnabled && (displaySections.length === 0 || pendingInsertIndex === displaySections.length) && (
|
|
2930
|
+
<SectionSkeleton
|
|
2931
|
+
types={typeOptions}
|
|
2932
|
+
onSelect={(type) => {
|
|
2933
|
+
onAddSection(displaySections.length, type);
|
|
2934
|
+
setPendingInsertIndex(null);
|
|
2935
|
+
}}
|
|
2936
|
+
onDismiss={dismissPendingInsert}
|
|
2937
|
+
/>
|
|
2938
|
+
)}
|
|
2803
2939
|
|
|
2804
|
-
|
|
2940
|
+
</div>
|
|
2941
|
+
</SummaryAnchorsContext.Provider>
|
|
2805
2942
|
);
|
|
2806
2943
|
}
|
|
2807
2944
|
|
|
@@ -2847,7 +2984,7 @@ function formatElapsed(seconds: number): string {
|
|
|
2847
2984
|
function DismissButton({ onClick }: { onClick: () => void }) {
|
|
2848
2985
|
return (
|
|
2849
2986
|
<button type="button" onClick={onClick} className="cursor-pointer" aria-label="Dismiss">
|
|
2850
|
-
<X size={12} className="text-base-
|
|
2987
|
+
<X size={12} className="text-base-contrast/30 hover:text-base-contrast/60" />
|
|
2851
2988
|
</button>
|
|
2852
2989
|
);
|
|
2853
2990
|
}
|
|
@@ -2861,7 +2998,7 @@ function StatusText({
|
|
|
2861
2998
|
onDismiss,
|
|
2862
2999
|
}: {
|
|
2863
3000
|
publishAction: "idle" | "saving" | "publishing";
|
|
2864
|
-
publishFeedback:
|
|
3001
|
+
publishFeedback: NoticeState | null;
|
|
2865
3002
|
saveProgress: string | null;
|
|
2866
3003
|
buildState: "idle" | "building" | "ready" | "error" | "fading";
|
|
2867
3004
|
buildElapsed: number;
|
|
@@ -2869,14 +3006,14 @@ function StatusText({
|
|
|
2869
3006
|
}) {
|
|
2870
3007
|
if (publishAction === "saving") {
|
|
2871
3008
|
return (
|
|
2872
|
-
<span className="relative text-xs font-medium text-base-
|
|
3009
|
+
<span className="relative text-xs font-medium text-base-contrast/60">
|
|
2873
3010
|
{saveProgress ?? <>Saving changes<LoadingEllipsis /></>}
|
|
2874
3011
|
</span>
|
|
2875
3012
|
);
|
|
2876
3013
|
}
|
|
2877
3014
|
if (publishAction === "publishing" || buildState === "building") {
|
|
2878
3015
|
return (
|
|
2879
|
-
<span className="text-xs font-medium text-
|
|
3016
|
+
<span className="text-xs font-medium text-warning">
|
|
2880
3017
|
Publishing ({formatElapsed(buildElapsed)})
|
|
2881
3018
|
</span>
|
|
2882
3019
|
);
|
|
@@ -2884,7 +3021,7 @@ function StatusText({
|
|
|
2884
3021
|
if (buildState === "ready" || buildState === "fading") {
|
|
2885
3022
|
return (
|
|
2886
3023
|
<span className={cn(
|
|
2887
|
-
"inline-flex items-center gap-1.5 text-xs font-medium text-
|
|
3024
|
+
"inline-flex items-center gap-1.5 text-xs font-medium text-success transition-opacity duration-1000",
|
|
2888
3025
|
buildState === "fading" ? "opacity-0" : "opacity-100",
|
|
2889
3026
|
)}>
|
|
2890
3027
|
Published in {formatElapsed(buildElapsed)}
|
|
@@ -2894,19 +3031,22 @@ function StatusText({
|
|
|
2894
3031
|
}
|
|
2895
3032
|
if (buildState === "error") {
|
|
2896
3033
|
return (
|
|
2897
|
-
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-
|
|
3034
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-danger">
|
|
2898
3035
|
Publish failed
|
|
2899
3036
|
<DismissButton onClick={onDismiss} />
|
|
2900
3037
|
</span>
|
|
2901
3038
|
);
|
|
2902
3039
|
}
|
|
2903
3040
|
if (publishFeedback) {
|
|
3041
|
+
const toneClass: Record<NoticeState["tone"], string> = {
|
|
3042
|
+
success: "text-success",
|
|
3043
|
+
info: "text-base-contrast",
|
|
3044
|
+
warning: "text-warning",
|
|
3045
|
+
error: "text-danger",
|
|
3046
|
+
};
|
|
2904
3047
|
return (
|
|
2905
|
-
<span className={cn(
|
|
2906
|
-
|
|
2907
|
-
publishFeedback === "Saved" ? "text-green-600" : "text-red-600",
|
|
2908
|
-
)}>
|
|
2909
|
-
{publishFeedback}
|
|
3048
|
+
<span className={cn("text-xs font-medium", toneClass[publishFeedback.tone])}>
|
|
3049
|
+
{publishFeedback.message}
|
|
2910
3050
|
</span>
|
|
2911
3051
|
);
|
|
2912
3052
|
}
|
|
@@ -2938,11 +3078,14 @@ function EditorToolbar({
|
|
|
2938
3078
|
audiences,
|
|
2939
3079
|
authoringDefaults,
|
|
2940
3080
|
onAuthoringDefaultsChange,
|
|
3081
|
+
docEditEnabled,
|
|
3082
|
+
onDocEditClick,
|
|
3083
|
+
showBugReport,
|
|
2941
3084
|
}: {
|
|
2942
3085
|
buttonState: "synced" | "publish" | "saveAndPublish";
|
|
2943
3086
|
localChangesExist: boolean;
|
|
2944
3087
|
publishAction: "idle" | "saving" | "publishing";
|
|
2945
|
-
publishFeedback:
|
|
3088
|
+
publishFeedback: NoticeState | null;
|
|
2946
3089
|
saveProgress: string | null;
|
|
2947
3090
|
onSave: () => void;
|
|
2948
3091
|
onPublish: () => void;
|
|
@@ -2963,6 +3106,14 @@ function EditorToolbar({
|
|
|
2963
3106
|
audiences: Audience[];
|
|
2964
3107
|
authoringDefaults: AuthoringDefaults;
|
|
2965
3108
|
onAuthoringDefaultsChange: (next: AuthoringDefaults) => void;
|
|
3109
|
+
/** Whether the active page has document mode on — gates the "Edit as
|
|
3110
|
+
* document" FAB this toolbar stacks alongside its own mode-toggle FAB.
|
|
3111
|
+
* Owned by EditorShell (resolvedActivePage lives above the Provider). */
|
|
3112
|
+
docEditEnabled: boolean;
|
|
3113
|
+
onDocEditClick: () => void;
|
|
3114
|
+
/** Whether to render BugReportFAB in this toolbar's FabStack — false on
|
|
3115
|
+
* the password-only (zero-Supabase) path, which has no backend for it. */
|
|
3116
|
+
showBugReport: boolean;
|
|
2966
3117
|
}) {
|
|
2967
3118
|
const { isEditMode, viewBranch, setViewBranch, toggleEditMode, historyState, setHistoryState, historyPageId, setHistoryPageId } = useEditorContext();
|
|
2968
3119
|
|
|
@@ -2983,13 +3134,13 @@ function EditorToolbar({
|
|
|
2983
3134
|
return (
|
|
2984
3135
|
<>
|
|
2985
3136
|
{isEditMode && (
|
|
2986
|
-
//
|
|
2987
|
-
//
|
|
2988
|
-
//
|
|
2989
|
-
//
|
|
2990
|
-
<
|
|
2991
|
-
|
|
2992
|
-
|
|
3137
|
+
// The docked chat sidebar slides in BELOW this bar (ChatPanel docks
|
|
3138
|
+
// at top-16 on /edit), so the toolbar never moves during the chat
|
|
3139
|
+
// open/close transition — ToolbarFrame's h-16 matches the panel's
|
|
3140
|
+
// top offset (and the body's pt-16) exactly.
|
|
3141
|
+
<ToolbarFrame
|
|
3142
|
+
left={
|
|
3143
|
+
<>
|
|
2993
3144
|
{buttonState === "saveAndPublish" && (
|
|
2994
3145
|
<SplitButton
|
|
2995
3146
|
label="Save & Publish"
|
|
@@ -3024,18 +3175,20 @@ function EditorToolbar({
|
|
|
3024
3175
|
</Button>
|
|
3025
3176
|
)}
|
|
3026
3177
|
<PresenceAvatars peers={collabPeers} selfUserId={collabSelfId} status={collabStatus} />
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3178
|
+
</>
|
|
3179
|
+
}
|
|
3180
|
+
center={
|
|
3181
|
+
<StatusText
|
|
3182
|
+
publishAction={publishAction}
|
|
3183
|
+
publishFeedback={publishFeedback}
|
|
3184
|
+
saveProgress={saveProgress}
|
|
3185
|
+
buildState={buildState}
|
|
3186
|
+
buildElapsed={buildElapsed}
|
|
3187
|
+
onDismiss={onBuildDismiss}
|
|
3188
|
+
/>
|
|
3189
|
+
}
|
|
3190
|
+
right={
|
|
3191
|
+
<>
|
|
3039
3192
|
<ProcessingIndicator items={processingItems} />
|
|
3040
3193
|
<Tooltip side="bottom" content="Authoring defaults">
|
|
3041
3194
|
<AuthoringDefaultsMenu defaults={authoringDefaults} audiences={audiences} onChange={onAuthoringDefaultsChange} />
|
|
@@ -3057,15 +3210,15 @@ function EditorToolbar({
|
|
|
3057
3210
|
<ShowAllChromeToggle />
|
|
3058
3211
|
</Tooltip>
|
|
3059
3212
|
<Tooltip side="bottom-clamp" content="Site settings">
|
|
3060
|
-
<IconButton icon={<
|
|
3213
|
+
<IconButton icon={<Settings size={16} />} label="Site settings" size="md" onClick={onSettingsClick} className="border border-base-200 bg-base-accent" />
|
|
3061
3214
|
</Tooltip>
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3215
|
+
</>
|
|
3216
|
+
}
|
|
3217
|
+
/>
|
|
3065
3218
|
)}
|
|
3066
3219
|
{!isEditMode && (
|
|
3067
|
-
<
|
|
3068
|
-
|
|
3220
|
+
<ToolbarFrame
|
|
3221
|
+
center={
|
|
3069
3222
|
<SegmentedControl
|
|
3070
3223
|
options={[
|
|
3071
3224
|
{ value: "live", label: "Published" },
|
|
@@ -3074,27 +3227,37 @@ function EditorToolbar({
|
|
|
3074
3227
|
value={viewBranch}
|
|
3075
3228
|
onChange={(v) => setViewBranch(v as "saved" | "live")}
|
|
3076
3229
|
/>
|
|
3077
|
-
|
|
3078
|
-
|
|
3230
|
+
}
|
|
3231
|
+
/>
|
|
3079
3232
|
)}
|
|
3080
3233
|
{!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>
|
|
3234
|
+
<FabStack>
|
|
3235
|
+
{/* Bug reports persist via Supabase; hidden on the password-only
|
|
3236
|
+
path (showBugReport, threaded from EditorShell's capabilities).
|
|
3237
|
+
History is presentation only, never editing (branch-preview
|
|
3238
|
+
already routes to DocumentPresentOverlay; !historyState covers
|
|
3239
|
+
the separate historySelectEvent commit view) — FabStack
|
|
3240
|
+
unmounts with the rest of this block whenever history is
|
|
3241
|
+
active, which hides all three FABs together. */}
|
|
3242
|
+
{showBugReport && <BugReportFAB stacked />}
|
|
3243
|
+
{docEditEnabled && (
|
|
3244
|
+
<Fab stacked label="Edit as document" column="editor" onClick={onDocEditClick}>
|
|
3245
|
+
<FileText size={20} aria-hidden="true" />
|
|
3246
|
+
</Fab>
|
|
3096
3247
|
)}
|
|
3097
|
-
|
|
3248
|
+
<Fab
|
|
3249
|
+
stacked
|
|
3250
|
+
label={isEditMode ? "Switch to view mode" : "Switch to edit mode"}
|
|
3251
|
+
column="editor"
|
|
3252
|
+
onClick={toggleEditMode}
|
|
3253
|
+
>
|
|
3254
|
+
{isEditMode ? (
|
|
3255
|
+
<Eye className="h-5 w-5" aria-hidden="true" />
|
|
3256
|
+
) : (
|
|
3257
|
+
<Pencil className="h-5 w-5" aria-hidden="true" />
|
|
3258
|
+
)}
|
|
3259
|
+
</Fab>
|
|
3260
|
+
</FabStack>
|
|
3098
3261
|
)}
|
|
3099
3262
|
</>
|
|
3100
3263
|
);
|