@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,12 +1,14 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef, useEffect } from "react";
|
|
1
|
+
import { useCallback, useMemo, useRef, useEffect, useState } from "react";
|
|
2
2
|
import type { SectionContent } from "../schemas/sections";
|
|
3
3
|
import type { SiteIndex, SiteConfig } from "../schemas/site-config";
|
|
4
|
-
import { persistAll,
|
|
4
|
+
import { persistAll, deleteSectionRows } from "../lib/dexie";
|
|
5
5
|
|
|
6
6
|
interface PersistenceState {
|
|
7
7
|
pendingSections: Map<string, SectionContent>;
|
|
8
8
|
indexDirty: boolean;
|
|
9
9
|
configDirty: boolean;
|
|
10
|
+
pendingConfig: SiteConfig | null;
|
|
11
|
+
pendingDeletedRows: Set<string>;
|
|
10
12
|
// Durable "unsaved index structure exists" signal. Unlike indexDirty — which is
|
|
11
13
|
// flush-PENDING state and self-clears ~500ms after an edit — this mirrors the
|
|
12
14
|
// lifetime of the Dexie siteIndex row: set on every structural edit, NOT
|
|
@@ -43,6 +45,22 @@ interface PersistenceOptions {
|
|
|
43
45
|
deletedSectionsRef?: React.RefObject<Set<string>>;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
const PERSIST_FAILED_COPY = "Your latest edits couldn't be stored in this browser. Keep this tab open and try saving again.";
|
|
49
|
+
// How long Save/Discard wait on an in-flight IndexedDB write before giving up.
|
|
50
|
+
// A hung transaction (Safari private mode, a quota prompt) never settles.
|
|
51
|
+
const FLUSH_WAIT_MS = 10_000;
|
|
52
|
+
|
|
53
|
+
/** Resolves true once `flush` settles (either way), false if it is still running after FLUSH_WAIT_MS. */
|
|
54
|
+
async function settlesInTime(flush: Promise<void>): Promise<boolean> {
|
|
55
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
56
|
+
const timedOut = new Promise<false>((resolve) => { timer = setTimeout(() => resolve(false), FLUSH_WAIT_MS); });
|
|
57
|
+
try {
|
|
58
|
+
return await Promise.race([flush.then(() => true, () => true), timedOut]);
|
|
59
|
+
} finally {
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
export function useEditorPersistence(
|
|
47
65
|
siteIndexRef: React.RefObject<SiteIndex>,
|
|
48
66
|
options?: PersistenceOptions,
|
|
@@ -51,67 +69,130 @@ export function useEditorPersistence(
|
|
|
51
69
|
pendingSections: new Map(),
|
|
52
70
|
indexDirty: false,
|
|
53
71
|
configDirty: false,
|
|
72
|
+
pendingConfig: null,
|
|
73
|
+
pendingDeletedRows: new Set(),
|
|
54
74
|
unsavedIndex: false,
|
|
55
75
|
generation: 0,
|
|
56
76
|
indexGeneration: 0,
|
|
57
77
|
sectionGenerations: new Map(),
|
|
58
78
|
});
|
|
79
|
+
const [persistenceError, setPersistenceError] = useState<string | null>(null);
|
|
80
|
+
const activeFlush = useRef<Promise<void> | null>(null);
|
|
81
|
+
// Writes given up on after FLUSH_WAIT_MS; their late outcome must not touch state.
|
|
82
|
+
const abandonedFlushes = useRef(new WeakSet<Promise<void>>());
|
|
83
|
+
// Bumped by discardPending so an in-flight flush can't restore discarded edits.
|
|
84
|
+
const discardEpoch = useRef(0);
|
|
59
85
|
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
60
86
|
const onFlushRef = useRef(options?.onFlush);
|
|
61
87
|
useEffect(() => { onFlushRef.current = options?.onFlush; });
|
|
62
88
|
const deletedSectionsRef = useRef(options?.deletedSectionsRef);
|
|
63
89
|
useEffect(() => { deletedSectionsRef.current = options?.deletedSectionsRef; });
|
|
64
90
|
|
|
65
|
-
const flushToDexie = useCallback(async () => {
|
|
91
|
+
const flushToDexie = useCallback(async (opts?: { exit?: boolean }) => {
|
|
92
|
+
// Saves wait for an already-running flush before reading Dexie; its failure
|
|
93
|
+
// restored its entries into state, so this flush retries them. A write that
|
|
94
|
+
// never settles is abandoned so the editor can recover without a reload.
|
|
95
|
+
// A tab-close flush can't wait: the unloading page may never resume it, and
|
|
96
|
+
// IndexedDB applies its later transaction after the in-flight one anyway.
|
|
97
|
+
while (!opts?.exit && activeFlush.current) {
|
|
98
|
+
const inFlight = activeFlush.current;
|
|
99
|
+
if (!(await settlesInTime(inFlight))) abandonedFlushes.current.add(inFlight);
|
|
100
|
+
if (activeFlush.current === inFlight) activeFlush.current = null;
|
|
101
|
+
}
|
|
66
102
|
const s = state.current;
|
|
67
|
-
if (s.pendingSections.size === 0 && !s.indexDirty) return;
|
|
103
|
+
if (s.pendingSections.size === 0 && !s.indexDirty && !s.pendingConfig && !s.pendingDeletedRows.size) return;
|
|
68
104
|
|
|
69
105
|
const entries = Array.from(s.pendingSections.entries()).map(([sectionId, content]) => ({
|
|
70
106
|
sectionId,
|
|
71
107
|
content,
|
|
72
108
|
}));
|
|
109
|
+
const entryGenerations = new Map(s.sectionGenerations);
|
|
73
110
|
const wasIndexDirty = s.indexDirty;
|
|
111
|
+
const config = s.pendingConfig;
|
|
112
|
+
const deletedRows = s.pendingDeletedRows;
|
|
113
|
+
const epoch = discardEpoch.current;
|
|
74
114
|
|
|
75
115
|
// Clear optimistically so new edits during the await go into a fresh set
|
|
76
116
|
s.pendingSections = new Map();
|
|
77
117
|
s.indexDirty = false;
|
|
118
|
+
s.pendingConfig = null;
|
|
119
|
+
s.pendingDeletedRows = new Set();
|
|
78
120
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const pendingDeletions = deletedSectionsRef.current?.current;
|
|
84
|
-
await persistAll(
|
|
85
|
-
entries,
|
|
86
|
-
wasIndexDirty ? siteIndexRef.current : undefined,
|
|
87
|
-
pendingDeletions && pendingDeletions.size > 0 ? [...pendingDeletions] : undefined,
|
|
88
|
-
);
|
|
89
|
-
// Fire only after a successful persist — broadcasting an edit that failed
|
|
90
|
-
// to persist would let peers diverge from what this editor can recover.
|
|
91
|
-
onFlushRef.current?.(entries, wasIndexDirty);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
// Restore: merge back any entries that weren't re-dirtied during the await
|
|
121
|
+
// Merge back what this flush took, unless Discard ran since (it would
|
|
122
|
+
// resurrect discarded edits) or a newer edit superseded it.
|
|
123
|
+
const restore = () => {
|
|
124
|
+
if (discardEpoch.current !== epoch) return;
|
|
94
125
|
for (const { sectionId, content } of entries) {
|
|
95
|
-
if (!s.pendingSections.has(sectionId)) {
|
|
126
|
+
if (!s.pendingSections.has(sectionId) && s.sectionGenerations.get(sectionId) === entryGenerations.get(sectionId)) {
|
|
96
127
|
s.pendingSections.set(sectionId, content);
|
|
97
128
|
}
|
|
98
129
|
}
|
|
99
130
|
if (wasIndexDirty) s.indexDirty = true;
|
|
100
|
-
|
|
131
|
+
if (config && !s.pendingConfig) s.pendingConfig = config;
|
|
132
|
+
for (const id of deletedRows) s.pendingDeletedRows.add(id);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let pending: Promise<void>;
|
|
136
|
+
const write = async () => {
|
|
137
|
+
try {
|
|
138
|
+
if (deletedRows.size) await deleteSectionRows([...deletedRows]);
|
|
139
|
+
// The deletion set is cumulative in shell state (cleared only on save or
|
|
140
|
+
// discard), so every flush rewrites the whole current set — a later flush
|
|
141
|
+
// can never narrow an earlier record.
|
|
142
|
+
const pendingDeletions = deletedSectionsRef.current?.current;
|
|
143
|
+
await persistAll(
|
|
144
|
+
entries,
|
|
145
|
+
wasIndexDirty ? siteIndexRef.current : undefined,
|
|
146
|
+
pendingDeletions && pendingDeletions.size > 0 ? [...pendingDeletions] : undefined,
|
|
147
|
+
config ?? undefined,
|
|
148
|
+
);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error("Failed to flush to Dexie:", err);
|
|
151
|
+
// An abandoned write was already restored when it was given up on.
|
|
152
|
+
if (!abandonedFlushes.current.has(pending)) {
|
|
153
|
+
restore();
|
|
154
|
+
setPersistenceError(PERSIST_FAILED_COPY);
|
|
155
|
+
}
|
|
156
|
+
throw new Error(PERSIST_FAILED_COPY, { cause: err });
|
|
157
|
+
}
|
|
158
|
+
if (abandonedFlushes.current.has(pending)) return;
|
|
159
|
+
setPersistenceError(null);
|
|
160
|
+
// Fire only after a successful persist — broadcasting an edit that failed
|
|
161
|
+
// to persist would let peers diverge from what this editor can recover.
|
|
162
|
+
// Config-only and row-delete-only flushes have nothing to broadcast.
|
|
163
|
+
if (entries.length > 0 || wasIndexDirty) onFlushRef.current?.(entries, wasIndexDirty);
|
|
164
|
+
};
|
|
165
|
+
pending = write();
|
|
166
|
+
activeFlush.current = pending;
|
|
167
|
+
try {
|
|
168
|
+
if (!(await settlesInTime(pending))) {
|
|
169
|
+
abandonedFlushes.current.add(pending);
|
|
170
|
+
restore();
|
|
171
|
+
setPersistenceError(PERSIST_FAILED_COPY);
|
|
172
|
+
throw new Error(PERSIST_FAILED_COPY);
|
|
173
|
+
}
|
|
174
|
+
await pending;
|
|
175
|
+
} finally {
|
|
176
|
+
if (activeFlush.current === pending) activeFlush.current = null;
|
|
101
177
|
}
|
|
102
178
|
}, [siteIndexRef]);
|
|
103
179
|
|
|
180
|
+
// Background flushes surface their error in the banner; explicit saves reject.
|
|
181
|
+
const flushInBackground = useCallback(() => {
|
|
182
|
+
void flushToDexie().catch(() => {});
|
|
183
|
+
}, [flushToDexie]);
|
|
184
|
+
|
|
104
185
|
const scheduleFlush = useCallback(() => {
|
|
105
186
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
106
|
-
flushTimerRef.current = setTimeout(
|
|
107
|
-
}, [
|
|
187
|
+
flushTimerRef.current = setTimeout(flushInBackground, 500);
|
|
188
|
+
}, [flushInBackground]);
|
|
108
189
|
|
|
109
190
|
useEffect(() => {
|
|
110
191
|
return () => {
|
|
111
192
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
112
|
-
|
|
193
|
+
flushInBackground();
|
|
113
194
|
};
|
|
114
|
-
}, [
|
|
195
|
+
}, [flushInBackground]);
|
|
115
196
|
|
|
116
197
|
// Flush pending edits when the tab is hidden or unloaded. The unmount cleanup
|
|
117
198
|
// above is fire-and-forget and does not reliably complete during real document
|
|
@@ -126,7 +207,7 @@ export function useEditorPersistence(
|
|
|
126
207
|
clearTimeout(flushTimerRef.current);
|
|
127
208
|
flushTimerRef.current = null;
|
|
128
209
|
}
|
|
129
|
-
void flushToDexie();
|
|
210
|
+
void flushToDexie({ exit: true }).catch(() => {});
|
|
130
211
|
};
|
|
131
212
|
const onVisibilityChange = () => {
|
|
132
213
|
if (document.visibilityState === "hidden") flushOnExit();
|
|
@@ -165,9 +246,7 @@ export function useEditorPersistence(
|
|
|
165
246
|
state.current.unsavedIndex = true;
|
|
166
247
|
state.current.indexGeneration = ++state.current.generation;
|
|
167
248
|
state.current.sectionGenerations.delete(sectionId);
|
|
168
|
-
|
|
169
|
-
console.error("Failed to delete section row from Dexie:", err);
|
|
170
|
-
});
|
|
249
|
+
state.current.pendingDeletedRows.add(sectionId);
|
|
171
250
|
scheduleFlush();
|
|
172
251
|
}, [scheduleFlush]);
|
|
173
252
|
|
|
@@ -190,8 +269,10 @@ export function useEditorPersistence(
|
|
|
190
269
|
|
|
191
270
|
const persistConfig = useCallback(async (config: SiteConfig) => {
|
|
192
271
|
state.current.configDirty = true;
|
|
193
|
-
|
|
194
|
-
|
|
272
|
+
state.current.pendingConfig = config;
|
|
273
|
+
// Resolves once written; a failure surfaces in the persistence banner.
|
|
274
|
+
await flushToDexie().catch(() => {});
|
|
275
|
+
}, [flushToDexie]);
|
|
195
276
|
|
|
196
277
|
const cancelPendingFlush = useCallback(() => {
|
|
197
278
|
if (flushTimerRef.current) {
|
|
@@ -200,6 +281,28 @@ export function useEditorPersistence(
|
|
|
200
281
|
}
|
|
201
282
|
}, []);
|
|
202
283
|
|
|
284
|
+
const discardPending = useCallback(async () => {
|
|
285
|
+
cancelPendingFlush();
|
|
286
|
+
discardEpoch.current++;
|
|
287
|
+
// Finish an in-flight write before the caller clears Dexie, or it could
|
|
288
|
+
// recreate the rows after Discard has removed them. A write that never
|
|
289
|
+
// settles is abandoned rather than hanging Discard; if it lands later it can
|
|
290
|
+
// still write discarded rows, which no browser API lets us cancel.
|
|
291
|
+
while (activeFlush.current) {
|
|
292
|
+
const inFlight = activeFlush.current;
|
|
293
|
+
if (!(await settlesInTime(inFlight))) abandonedFlushes.current.add(inFlight);
|
|
294
|
+
if (activeFlush.current === inFlight) activeFlush.current = null;
|
|
295
|
+
}
|
|
296
|
+
state.current.pendingSections.clear();
|
|
297
|
+
state.current.pendingConfig = null;
|
|
298
|
+
state.current.pendingDeletedRows.clear();
|
|
299
|
+
state.current.indexDirty = false;
|
|
300
|
+
state.current.configDirty = false;
|
|
301
|
+
state.current.unsavedIndex = false;
|
|
302
|
+
state.current.sectionGenerations.clear();
|
|
303
|
+
setPersistenceError(null);
|
|
304
|
+
}, [cancelPendingFlush]);
|
|
305
|
+
|
|
203
306
|
const isConfigDirty = useCallback(() => state.current.configDirty, []);
|
|
204
307
|
const clearConfigDirty = useCallback(() => { state.current.configDirty = false; }, []);
|
|
205
308
|
// Reflects whether an index change is pending flush to Dexie. NOTE: this is
|
|
@@ -236,14 +339,16 @@ export function useEditorPersistence(
|
|
|
236
339
|
[],
|
|
237
340
|
);
|
|
238
341
|
|
|
239
|
-
// Memoized, and every member above is
|
|
240
|
-
//
|
|
342
|
+
// Memoized, and every member above is identity-stable except persistenceError,
|
|
343
|
+
// which changes only when a flush fails or recovers — so this object is stable
|
|
344
|
+
// across ordinary edits. A fresh literal per render changed `persistence`'s identity on every
|
|
241
345
|
// EditorShell render — which churned onBlockMove, getEditIntent,
|
|
242
346
|
// onSectionChange, onAddSection, performDeleteSection, onReorderSections and
|
|
243
347
|
// all twelve page/nav handlers, and made EditorContent tear down and
|
|
244
348
|
// re-register its atlaskit drag monitors on every keystroke.
|
|
245
349
|
return useMemo(
|
|
246
350
|
() => ({
|
|
351
|
+
persistenceError,
|
|
247
352
|
markSectionDirty,
|
|
248
353
|
markIndexDirty,
|
|
249
354
|
removeSection,
|
|
@@ -252,6 +357,7 @@ export function useEditorPersistence(
|
|
|
252
357
|
persistConfig,
|
|
253
358
|
flushNow: flushToDexie,
|
|
254
359
|
cancelPendingFlush,
|
|
360
|
+
discardPending,
|
|
255
361
|
isConfigDirty,
|
|
256
362
|
clearConfigDirty,
|
|
257
363
|
isIndexDirty,
|
|
@@ -262,8 +368,8 @@ export function useEditorPersistence(
|
|
|
262
368
|
sectionDirtiedAfter,
|
|
263
369
|
}),
|
|
264
370
|
[
|
|
265
|
-
markSectionDirty, markIndexDirty, removeSection, dropPendingSection, hasPendingSection,
|
|
266
|
-
persistConfig, flushToDexie, cancelPendingFlush, isConfigDirty, clearConfigDirty,
|
|
371
|
+
persistenceError, markSectionDirty, markIndexDirty, removeSection, dropPendingSection, hasPendingSection,
|
|
372
|
+
persistConfig, flushToDexie, cancelPendingFlush, discardPending, isConfigDirty, clearConfigDirty,
|
|
267
373
|
isIndexDirty, hasUnsavedIndex, clearUnsavedIndex, noteUnsavedIndex, currentGeneration,
|
|
268
374
|
sectionDirtiedAfter,
|
|
269
375
|
],
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { useState, useCallback, useRef
|
|
1
|
+
import { useState, useCallback, useRef } from "react";
|
|
2
|
+
import { useNotice } from "./useNotice";
|
|
3
|
+
import { COPY } from "../components/shared/copy";
|
|
4
|
+
import type { NoticeTone } from "../components/shared/Notice";
|
|
2
5
|
import type { SiteIndex, SiteConfig } from "../schemas/site-config";
|
|
3
6
|
import type { SectionContent } from "../schemas/sections";
|
|
4
7
|
import type { LoadedSection } from "../lib/loader";
|
|
@@ -7,6 +10,7 @@ import { getDirtySectionRows, hasLocalChanges, discardSavedChanges, cacheContent
|
|
|
7
10
|
import { packUploads, packDocumentUploads, type MediaUploadPayload, type DocumentUploadPayload } from "../lib/save-chunks";
|
|
8
11
|
import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
|
|
9
12
|
import { assembleSavedManifest } from "../lib/media-folders";
|
|
13
|
+
import { imageVariantPath } from "../lib/image-variant-plan";
|
|
10
14
|
import { buildCommittedBaseline } from "../collab/recovery";
|
|
11
15
|
|
|
12
16
|
function blobToBase64(blob: Blob): Promise<string> {
|
|
@@ -60,6 +64,9 @@ interface PublishDeps {
|
|
|
60
64
|
clearConfigDirty: () => void;
|
|
61
65
|
siteIndexRef: React.RefObject<SiteIndex>;
|
|
62
66
|
siteConfig: SiteConfig | null;
|
|
67
|
+
/** This session's tags read was degraded; the cache re-primes record tags
|
|
68
|
+
* as not loaded (see cacheContent). */
|
|
69
|
+
tagsDegraded: boolean;
|
|
63
70
|
sections: LoadedSection[];
|
|
64
71
|
deletedSectionIds?: string[];
|
|
65
72
|
/** Receives the ids that were actually committed this save (so the caller can
|
|
@@ -81,7 +88,7 @@ interface PublishDeps {
|
|
|
81
88
|
pendingMediaDeletions: string[];
|
|
82
89
|
onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
|
|
83
90
|
onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
|
|
84
|
-
onPublishComplete?: () => void;
|
|
91
|
+
onPublishComplete?: (sha: string) => void;
|
|
85
92
|
// The draft version this editor is based on (saved-branch head, or null when
|
|
86
93
|
// no draft exists yet). Sent with saves so the server can 409 on a concurrent
|
|
87
94
|
// edit instead of silently overwriting it. Read at save time (latest value).
|
|
@@ -145,6 +152,7 @@ export function useEditorPublish({
|
|
|
145
152
|
clearConfigDirty,
|
|
146
153
|
siteIndexRef,
|
|
147
154
|
siteConfig,
|
|
155
|
+
tagsDegraded,
|
|
148
156
|
sections,
|
|
149
157
|
deletedSectionIds,
|
|
150
158
|
onSuccess,
|
|
@@ -171,23 +179,10 @@ export function useEditorPublish({
|
|
|
171
179
|
onDocumentsDeleted,
|
|
172
180
|
}: PublishDeps) {
|
|
173
181
|
const [publishAction, setPublishAction] = useState<PublishAction>("idle");
|
|
174
|
-
const
|
|
182
|
+
const { notice: publishFeedback, show: showFeedback, clear: clearPublishFeedback } = useNotice();
|
|
175
183
|
const [saveProgress, setSaveProgress] = useState<string | null>(null);
|
|
176
|
-
const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
177
184
|
const inFlightRef = useRef(false);
|
|
178
185
|
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
return () => {
|
|
181
|
-
if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
|
|
182
|
-
};
|
|
183
|
-
}, []);
|
|
184
|
-
|
|
185
|
-
const showFeedback = useCallback((message: string, duration: number) => {
|
|
186
|
-
setPublishFeedback(message);
|
|
187
|
-
if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
|
|
188
|
-
feedbackTimerRef.current = setTimeout(() => setPublishFeedback(null), duration);
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
186
|
async function gatherMediaPayload(
|
|
192
187
|
pendingItems: MediaItem[],
|
|
193
188
|
pendingDeletions: string[],
|
|
@@ -218,7 +213,7 @@ export function useEditorPublish({
|
|
|
218
213
|
const ext = mimeExt[item.mimeType] ?? "bin";
|
|
219
214
|
blobPayloads.push({ path: `assets/images/${item.folder}/original.${ext}`, base64 });
|
|
220
215
|
} else {
|
|
221
|
-
blobPayloads.push({ path:
|
|
216
|
+
blobPayloads.push({ path: imageVariantPath(item.folder, key), base64 });
|
|
222
217
|
}
|
|
223
218
|
}
|
|
224
219
|
|
|
@@ -591,7 +586,7 @@ export function useEditorPublish({
|
|
|
591
586
|
recoveredRef.current = false;
|
|
592
587
|
editTokenRef.current = getEditGeneration?.() ?? Number.MAX_SAFE_INTEGER;
|
|
593
588
|
setPublishAction("saving");
|
|
594
|
-
|
|
589
|
+
clearPublishFeedback();
|
|
595
590
|
return true;
|
|
596
591
|
}
|
|
597
592
|
|
|
@@ -675,12 +670,13 @@ export function useEditorPublish({
|
|
|
675
670
|
// M1 honesty: only claim the view was updated when recovery actually
|
|
676
671
|
// rebased it this invocation (i.e. the RETRY 409'd). A failed/absent/
|
|
677
672
|
// skipped recovery left the view untouched — say so.
|
|
673
|
+
const tone: NoticeTone = isConflict && recoveredRef.current ? "warning" : "error";
|
|
678
674
|
const message = isConflict
|
|
679
675
|
? recoveredRef.current
|
|
680
676
|
? "Someone else saved while you were editing — your view has been updated; try saving again."
|
|
681
677
|
: "This site was changed elsewhere — reload to get the latest, then re-apply your edits."
|
|
682
678
|
: error instanceof Error && error.message ? error.message : `${failLabel} failed`;
|
|
683
|
-
showFeedback(message, isConflict ? 8000 : 6000);
|
|
679
|
+
showFeedback(tone, message, { ttlMs: isConflict ? 8000 : 6000 });
|
|
684
680
|
}
|
|
685
681
|
|
|
686
682
|
const handleSave = useCallback(async () => {
|
|
@@ -726,11 +722,11 @@ export function useEditorPublish({
|
|
|
726
722
|
if (committedDocumentDeletions.length > 0) onDocumentsDeleted?.(committedDocumentDeletions);
|
|
727
723
|
} catch (cleanupError) {
|
|
728
724
|
console.error("Save succeeded but local cleanup failed:", cleanupError);
|
|
729
|
-
showFeedback("Saved, but the local cache couldn't be updated — reload the editor.", 8000);
|
|
725
|
+
showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
|
|
730
726
|
return;
|
|
731
727
|
}
|
|
732
728
|
|
|
733
|
-
showFeedback("
|
|
729
|
+
showFeedback("success", COPY.publish.saved, { ttlMs: 3000 });
|
|
734
730
|
} catch (error) {
|
|
735
731
|
reportSaveError(error, "Save");
|
|
736
732
|
} finally {
|
|
@@ -748,7 +744,7 @@ export function useEditorPublish({
|
|
|
748
744
|
if (inFlightRef.current) return;
|
|
749
745
|
inFlightRef.current = true;
|
|
750
746
|
setPublishAction("publishing");
|
|
751
|
-
|
|
747
|
+
clearPublishFeedback();
|
|
752
748
|
|
|
753
749
|
try {
|
|
754
750
|
const response = await fetch("/api/publish", {
|
|
@@ -772,10 +768,10 @@ export function useEditorPublish({
|
|
|
772
768
|
// restored on a later cache hit. (Save & Publish already re-caches instead.)
|
|
773
769
|
await clearContentCache();
|
|
774
770
|
onShasUpdated(null, sha);
|
|
775
|
-
onPublishComplete?.();
|
|
771
|
+
onPublishComplete?.(sha);
|
|
776
772
|
} catch (error) {
|
|
777
773
|
console.error("Publish failed:", error);
|
|
778
|
-
showFeedback("
|
|
774
|
+
showFeedback("error", COPY.publish.publishFailed, { ttlMs: 5000 });
|
|
779
775
|
} finally {
|
|
780
776
|
inFlightRef.current = false;
|
|
781
777
|
setPublishAction("idle");
|
|
@@ -824,7 +820,7 @@ export function useEditorPublish({
|
|
|
824
820
|
await cacheContent(saved.sha, baselineSections, saved.sentIndex, siteConfig!, {
|
|
825
821
|
media: saved.committedManifest,
|
|
826
822
|
documents: saved.committedDocumentManifest,
|
|
827
|
-
});
|
|
823
|
+
}, tagsDegraded);
|
|
828
824
|
finishSave(saved.uploadedItems, saved.savedSections.map((r) => r.sectionId));
|
|
829
825
|
if (saved.committedDocumentIds.length > 0) onDocumentsPublished?.(saved.committedDocumentIds);
|
|
830
826
|
if (saved.committedDocumentDeletions.length > 0) onDocumentsDeleted?.(saved.committedDocumentDeletions);
|
|
@@ -833,7 +829,7 @@ export function useEditorPublish({
|
|
|
833
829
|
// top of unknown local state would compound it — stop and tell the
|
|
834
830
|
// editor to reload.
|
|
835
831
|
console.error("Save succeeded but local cleanup failed:", cleanupError);
|
|
836
|
-
showFeedback("Saved, but the local cache couldn't be updated — reload the editor before publishing.", 8000);
|
|
832
|
+
showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor before publishing.", { ttlMs: 8000 });
|
|
837
833
|
return;
|
|
838
834
|
}
|
|
839
835
|
}
|
|
@@ -866,18 +862,18 @@ export function useEditorPublish({
|
|
|
866
862
|
await cacheContent(sha, baselineSections, sentIndex, siteConfig!, {
|
|
867
863
|
media: committedManifest!,
|
|
868
864
|
documents: committedDocumentManifest!,
|
|
869
|
-
});
|
|
865
|
+
}, tagsDegraded);
|
|
870
866
|
} catch (cleanupError) {
|
|
871
867
|
console.error("Publish succeeded but the cache re-key failed:", cleanupError);
|
|
872
868
|
onShasUpdated(null, sha);
|
|
873
|
-
onPublishComplete?.();
|
|
874
|
-
showFeedback("Published, but the local cache couldn't be updated — reload the editor.", 8000);
|
|
869
|
+
onPublishComplete?.(sha);
|
|
870
|
+
showFeedback("warning", "Published, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
|
|
875
871
|
return;
|
|
876
872
|
}
|
|
877
873
|
}
|
|
878
874
|
|
|
879
875
|
onShasUpdated(null, sha);
|
|
880
|
-
onPublishComplete?.();
|
|
876
|
+
onPublishComplete?.(sha);
|
|
881
877
|
} catch (error) {
|
|
882
878
|
reportSaveError(error, "Publish");
|
|
883
879
|
} finally {
|
|
@@ -886,7 +882,7 @@ export function useEditorPublish({
|
|
|
886
882
|
setPublishAction("idle");
|
|
887
883
|
setSaveProgress(null);
|
|
888
884
|
}
|
|
889
|
-
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions]);
|
|
885
|
+
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions, tagsDegraded]);
|
|
890
886
|
|
|
891
887
|
return { publishAction, publishFeedback, saveProgress, handleSave, handlePublish, handleSaveAndPublish };
|
|
892
888
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
2
|
-
import { hashFileBuffer
|
|
2
|
+
import { hashFileBuffer } from "../media/utils";
|
|
3
|
+
import { mediaIdentity, imageManifestEntry } from "../lib/image-variant-plan";
|
|
3
4
|
import { ProcessingQueue, type QueueItem, type QueueEvent } from "../media/queue";
|
|
4
5
|
import type { MediaManifest, MediaItem as LibraryMediaItem } from "../media/types";
|
|
5
6
|
import type { SiteConfig } from "../schemas/site-config";
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
markPendingMediaDeleted,
|
|
13
14
|
removePendingMediaDeletion,
|
|
14
15
|
} from "../lib/dexie";
|
|
15
|
-
import { generateVideoPoster } from "../media/
|
|
16
|
+
import { generateVideoPoster } from "../media/video-poster";
|
|
16
17
|
import { ASSET_CACHE_CONTROL } from "../storage/types";
|
|
17
18
|
import { tallyImageIds, collectImageIds, clearImageIds, applyLogoDeletion } from "../lib/image-refs";
|
|
18
19
|
import { clearDocumentRefs, tallyDocumentIds } from "../lib/document-refs";
|
|
@@ -235,7 +236,16 @@ export function useMediaPipeline({
|
|
|
235
236
|
if (event.type === "complete" && event.item.result) {
|
|
236
237
|
const result = event.item.result;
|
|
237
238
|
const kind = event.item.kind;
|
|
238
|
-
|
|
239
|
+
// A still image's manifest entry comes only from the plan the worker
|
|
240
|
+
// rendered. One without a plan (a stale cached worker after a deploy)
|
|
241
|
+
// is a failed upload, never a quiet entry with no variants.
|
|
242
|
+
if (kind === "image" && !result.plan) {
|
|
243
|
+
uploadTargetsRef.current.delete(event.item.hash);
|
|
244
|
+
hiddenUploadsRef.current.delete(event.item.hash);
|
|
245
|
+
uploadCallbacksRef.current.delete(event.item.hash);
|
|
246
|
+
onUploadErrorRef.current?.(`"${event.item.originalName}" couldn't be processed — reload the editor and try again.`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
239
249
|
|
|
240
250
|
const finalize = async (
|
|
241
251
|
localUrls: Record<string, string>,
|
|
@@ -245,23 +255,23 @@ export function useMediaPipeline({
|
|
|
245
255
|
) => {
|
|
246
256
|
if (destroyedRef.current) return;
|
|
247
257
|
const targetFolderId = uploadTargetsRef.current.get(event.item.hash) ?? null;
|
|
258
|
+
// A still image's entry comes from the plan the worker rendered;
|
|
259
|
+
// animated and video items have no variants, only the identity.
|
|
260
|
+
const base: LibraryMediaItem = result.plan
|
|
261
|
+
? imageManifestEntry(result.plan, result.variants, result.primaryBlob.size)
|
|
262
|
+
: {
|
|
263
|
+
...mediaIdentity(event.item.hash, event.item.originalName),
|
|
264
|
+
kind,
|
|
265
|
+
width,
|
|
266
|
+
height,
|
|
267
|
+
mimeType: event.item.mimeType,
|
|
268
|
+
size: result.primaryBlob.size,
|
|
269
|
+
variants: [],
|
|
270
|
+
alt: "",
|
|
271
|
+
storage: "github",
|
|
272
|
+
};
|
|
248
273
|
const item: LibraryMediaItem = {
|
|
249
|
-
|
|
250
|
-
hash: event.item.hash,
|
|
251
|
-
kind,
|
|
252
|
-
originalName: sanitizedName,
|
|
253
|
-
width,
|
|
254
|
-
height,
|
|
255
|
-
mimeType: kind === "image" ? "image/webp" : event.item.mimeType,
|
|
256
|
-
size: result.primaryBlob.size,
|
|
257
|
-
folder: `${sanitizedName}-${event.item.hash}`,
|
|
258
|
-
variants: result.variants.map((v) => ({
|
|
259
|
-
width: v.width,
|
|
260
|
-
height: v.height,
|
|
261
|
-
size: v.size,
|
|
262
|
-
})),
|
|
263
|
-
alt: "",
|
|
264
|
-
storage: "github",
|
|
274
|
+
...base,
|
|
265
275
|
...(targetFolderId ? { folderId: targetFolderId } : {}),
|
|
266
276
|
...(hiddenUploadsRef.current.get(event.item.hash) ? { hidden: true } : {}),
|
|
267
277
|
};
|
|
@@ -408,17 +418,13 @@ export function useMediaPipeline({
|
|
|
408
418
|
|
|
409
419
|
try {
|
|
410
420
|
const { posterBlob, width, height, duration } = await generateVideoPoster(file, mediaConfig.quality);
|
|
411
|
-
const sanitizedName = sanitizeMediaName(file.name);
|
|
412
421
|
const item: LibraryMediaItem = {
|
|
413
|
-
|
|
414
|
-
hash,
|
|
422
|
+
...mediaIdentity(hash, file.name),
|
|
415
423
|
kind: "video",
|
|
416
|
-
originalName: sanitizedName,
|
|
417
424
|
width,
|
|
418
425
|
height,
|
|
419
426
|
mimeType: file.type,
|
|
420
427
|
size: file.size,
|
|
421
|
-
folder: `${sanitizedName}-${hash}`,
|
|
422
428
|
variants: [],
|
|
423
429
|
alt: "",
|
|
424
430
|
storage: "assets",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { NoticeTone } from "../components/shared/Notice";
|
|
3
|
+
|
|
4
|
+
export interface NoticeState {
|
|
5
|
+
tone: NoticeTone;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TTL_MS = 5000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One notice, one timer. `error` is sticky by default (no ttl) since it
|
|
13
|
+
* usually needs a deliberate dismiss; every other tone auto-clears.
|
|
14
|
+
* Call a second instance for a notice that must stay visible independently
|
|
15
|
+
* of another (e.g. toolbar feedback vs. a collab toast).
|
|
16
|
+
*/
|
|
17
|
+
export function useNotice() {
|
|
18
|
+
const [notice, setNotice] = useState<NoticeState | null>(null);
|
|
19
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
return () => {
|
|
23
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const clear = useCallback(() => {
|
|
28
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
29
|
+
setNotice(null);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const show = useCallback(
|
|
33
|
+
(tone: NoticeTone, message: string, options?: { ttlMs?: number }) => {
|
|
34
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
35
|
+
setNotice({ tone, message });
|
|
36
|
+
const ttlMs = options?.ttlMs ?? (tone === "error" ? undefined : DEFAULT_TTL_MS);
|
|
37
|
+
if (ttlMs !== undefined) {
|
|
38
|
+
timerRef.current = setTimeout(() => setNotice(null), ttlMs);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return { notice, show, clear };
|
|
45
|
+
}
|
|
@@ -15,6 +15,17 @@ interface Options {
|
|
|
15
15
|
const DRAG_TYPE = "nav-tree-row";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
+
* One of three sibling drag-and-drop position hooks, split by location type
|
|
19
|
+
* rather than unified behind a generic `useSortable({ axis })`: this one
|
|
20
|
+
* (NavLocation — the Pages modal's group-aware nav tree), `useSortableRow`
|
|
21
|
+
* (flat index — section-ordering lists), and `useBlockDnd` (block/container id
|
|
22
|
+
* + index — the section canvas, root and nested). A fourth location type
|
|
23
|
+
* earns its own hook on the same shape (rowRef/handleRef, lazy atlaskit
|
|
24
|
+
* import, closest-edge state, a ref for the latest callback) rather than a
|
|
25
|
+
* parameter bag bolted onto an existing one — see docs/superpowers/specs/
|
|
26
|
+
* 2026-09-04-editor-ui-primitives-consolidation.md § D6 for why a shared
|
|
27
|
+
* `useSortable` was deferred.
|
|
28
|
+
*
|
|
18
29
|
* Closest-edge drag wiring for one Pages-modal nav row. A sibling of
|
|
19
30
|
* useSortableRow that speaks NavLocation instead of a flat index; the section
|
|
20
31
|
* modal keeps using the original, so this cannot regress it.
|