@drawnagency/primitives 0.1.112 → 0.1.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth/cookies.d.ts +39 -0
- package/dist/auth/cookies.d.ts.map +1 -1
- package/dist/auth/index.js +3 -3
- package/dist/auth/types.d.ts +1 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/{chunk-GPPVYLEI.js → chunk-45FA7Z55.js} +1 -1
- package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
- package/dist/chunk-5GFACGSN.js +6 -0
- package/dist/chunk-5KSAS4VY.js +275 -0
- package/dist/{chunk-5KNXTMUC.js → chunk-7SAAJVDE.js} +408 -28
- package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
- package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
- package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
- package/dist/chunk-DIMDRBFK.js +11 -0
- package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
- package/dist/{chunk-VLPELTPI.js → chunk-IP2SEBAJ.js} +5 -1
- package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
- package/dist/chunk-JR24U7FJ.js +120 -0
- package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
- package/dist/{chunk-Z2LO4PSQ.js → chunk-N3G7DOU3.js} +609 -61
- package/dist/chunk-OAGJBCSH.js +67 -0
- package/dist/{chunk-VT3PYGVW.js → chunk-PLANHXS7.js} +191 -61
- package/dist/{chunk-OGGZ5AXT.js → chunk-PNSCDBU7.js} +38 -56
- package/dist/{chunk-HSXMOS5Q.js → chunk-PTY2RG2K.js} +5 -2
- package/dist/{chunk-EPWBPIV6.js → chunk-PZPGJZRG.js} +9 -8
- package/dist/{chunk-P3ZNCHW7.js → chunk-QHMKIODQ.js} +19 -5
- package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
- package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
- package/dist/chunk-VUYCRBJK.js +174 -0
- package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
- package/dist/{chunk-3SZNUCXI.js → chunk-Y5QECJX7.js} +223 -16
- package/dist/collab/rebase.d.ts +2 -1
- package/dist/collab/rebase.d.ts.map +1 -1
- package/dist/collab/rebase.js +20 -5
- package/dist/components/brandguide/ColorsEditable.d.ts.map +1 -1
- package/dist/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/components/chat/ChatWidget.d.ts.map +1 -1
- package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
- package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
- package/dist/components/editor/ConflictDialog.d.ts.map +1 -1
- package/dist/components/editor/DragHandle.d.ts +12 -2
- package/dist/components/editor/DragHandle.d.ts.map +1 -1
- package/dist/components/editor/DropEdgeIndicator.d.ts +15 -6
- package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
- package/dist/components/editor/NavGroupRow.d.ts +4 -1
- package/dist/components/editor/NavGroupRow.d.ts.map +1 -1
- package/dist/components/editor/PagesModal.d.ts.map +1 -1
- package/dist/components/editor/SectionWrapper.d.ts +1 -1
- package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
- package/dist/components/editor/SettingsForm.d.ts.map +1 -1
- package/dist/components/editor/TagIndicator.d.ts +15 -0
- package/dist/components/editor/TagIndicator.d.ts.map +1 -0
- package/dist/components/editor/index.js +148 -77
- package/dist/components/primitives/DocumentDropZone.d.ts +1 -1
- package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
- package/dist/components/primitives/EditableList.d.ts.map +1 -1
- package/dist/components/primitives/HeadingSection.d.ts +4 -1
- package/dist/components/primitives/HeadingSection.d.ts.map +1 -1
- package/dist/components/primitives/ResolvedMedia.d.ts +5 -1
- package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
- package/dist/components/primitives/{defineHeadingSection.d.ts → define-heading-section.d.ts} +1 -1
- package/dist/components/primitives/define-heading-section.d.ts.map +1 -0
- package/dist/components/primitives/{gridDrop.d.ts → grid-drop.d.ts} +1 -1
- package/dist/components/primitives/grid-drop.d.ts.map +1 -0
- package/dist/components/primitives/index.d.ts +1 -1
- package/dist/components/primitives/index.d.ts.map +1 -1
- package/dist/components/primitives/index.js +17 -154
- package/dist/components/sections/Button/CTAButton.d.ts.map +1 -1
- package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +2 -6
- package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -1
- package/dist/components/sections/DoDontMedia/index.d.ts +8 -0
- package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
- package/dist/components/sections/IconList/IconList.d.ts +2 -1
- package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
- package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
- package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
- package/dist/components/sections/IconList/index.d.ts +1 -0
- package/dist/components/sections/IconList/index.d.ts.map +1 -1
- package/dist/components/sections/Media/MediaBlock.d.ts +2 -1
- package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
- package/dist/components/sections/Media/index.d.ts +8 -0
- package/dist/components/sections/Media/index.d.ts.map +1 -1
- package/dist/components/sections/Reference/ReferenceList.d.ts +35 -0
- package/dist/components/sections/Reference/ReferenceList.d.ts.map +1 -0
- package/dist/components/sections/Reference/ReferenceSettings.d.ts +12 -0
- package/dist/components/sections/Reference/ReferenceSettings.d.ts.map +1 -0
- package/dist/components/sections/Reference/index.d.ts +9 -0
- package/dist/components/sections/Reference/index.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts +38 -0
- package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts +20 -0
- package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts.map +1 -0
- package/dist/components/sections/SectionSummary/index.d.ts +33 -0
- package/dist/components/sections/SectionSummary/index.d.ts.map +1 -0
- package/dist/components/sections/all-sections.d.ts +53 -0
- package/dist/components/sections/all-sections.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.d.ts +41 -2
- package/dist/components/sections/brand-guide.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +12 -10
- package/dist/components/sections/core-sections.d.ts +14 -0
- package/dist/components/sections/core-sections.d.ts.map +1 -1
- package/dist/components/sections/register-schemas.js +19 -17
- package/dist/components/sections/register.js +19 -17
- package/dist/components/shared/BrandSwatchesContext.d.ts +9 -0
- package/dist/components/shared/BrandSwatchesContext.d.ts.map +1 -0
- package/dist/components/shared/Button.d.ts +11 -3
- package/dist/components/shared/Button.d.ts.map +1 -1
- package/dist/components/shared/Checkbox.d.ts +6 -0
- package/dist/components/shared/Checkbox.d.ts.map +1 -1
- package/dist/components/shared/ConfirmDialog.d.ts +25 -0
- package/dist/components/shared/ConfirmDialog.d.ts.map +1 -0
- package/dist/components/shared/ConfirmInline.d.ts +25 -0
- package/dist/components/shared/ConfirmInline.d.ts.map +1 -0
- package/dist/components/shared/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/shared/Fab.d.ts +11 -2
- package/dist/components/shared/Fab.d.ts.map +1 -1
- package/dist/components/shared/FabStack.d.ts +22 -0
- package/dist/components/shared/FabStack.d.ts.map +1 -0
- package/dist/components/shared/HistoryChangesModal.d.ts.map +1 -1
- package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
- package/dist/components/shared/ImageBackgroundField.d.ts +9 -0
- package/dist/components/shared/ImageBackgroundField.d.ts.map +1 -0
- package/dist/components/shared/{UploadRejectionAlert.d.ts → InlineAlert.d.ts} +4 -2
- package/dist/components/shared/InlineAlert.d.ts.map +1 -0
- package/dist/components/shared/Input.d.ts +10 -0
- package/dist/components/shared/Input.d.ts.map +1 -1
- package/dist/components/shared/Navigation.d.ts +1 -0
- package/dist/components/shared/Navigation.d.ts.map +1 -1
- package/dist/components/shared/Notice.d.ts +10 -0
- package/dist/components/shared/Notice.d.ts.map +1 -0
- package/dist/components/shared/Popover.d.ts.map +1 -1
- package/dist/components/shared/ReferenceSourcesContext.d.ts +25 -0
- package/dist/components/shared/ReferenceSourcesContext.d.ts.map +1 -0
- package/dist/components/shared/Select.d.ts +3 -2
- package/dist/components/shared/Select.d.ts.map +1 -1
- package/dist/components/shared/SplitButton.d.ts.map +1 -1
- package/dist/components/shared/SummaryAnchorsContext.d.ts +10 -0
- package/dist/components/shared/SummaryAnchorsContext.d.ts.map +1 -0
- package/dist/components/shared/Textarea.d.ts +7 -0
- package/dist/components/shared/Textarea.d.ts.map +1 -1
- package/dist/components/shared/ToolbarFrame.d.ts +17 -0
- package/dist/components/shared/ToolbarFrame.d.ts.map +1 -0
- package/dist/components/shared/copy.d.ts +13 -0
- package/dist/components/shared/copy.d.ts.map +1 -0
- package/dist/components/shared/icons.d.ts +0 -12
- package/dist/components/shared/icons.d.ts.map +1 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts +7 -1
- package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
- package/dist/components/shell/AudienceRow.d.ts.map +1 -1
- package/dist/components/shell/BugReportFAB.d.ts +7 -1
- package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
- package/dist/components/shell/CredentialList.d.ts.map +1 -1
- package/dist/components/shell/DocInspector.d.ts.map +1 -1
- package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -1
- package/dist/components/shell/EditorContext.d.ts +4 -0
- package/dist/components/shell/EditorContext.d.ts.map +1 -1
- package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
- package/dist/components/shell/EditorModal.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
- package/dist/components/shell/MediaFolderTile.d.ts +4 -1
- package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/RestoreModal.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsTags.d.ts +13 -0
- package/dist/components/shell/SiteSettingsTags.d.ts.map +1 -0
- package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
- package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
- package/dist/components/shell/{blockMoveDispatch.d.ts → block-move-dispatch.d.ts} +1 -1
- package/dist/components/shell/block-move-dispatch.d.ts.map +1 -0
- package/dist/components/shell/reference-sources.d.ts +22 -0
- package/dist/components/shell/reference-sources.d.ts.map +1 -0
- package/dist/deck/index.d.ts +2 -2
- package/dist/deck/index.d.ts.map +1 -1
- package/dist/deck/index.js +1 -3
- package/dist/deck-builder/index.js +11 -18
- package/dist/document/index.d.ts +2 -2
- package/dist/document/index.d.ts.map +1 -1
- package/dist/hooks/useBlockDnd.d.ts +11 -0
- package/dist/hooks/useBlockDnd.d.ts.map +1 -1
- package/dist/hooks/useBuildStatus.d.ts +1 -1
- package/dist/hooks/useBuildStatus.d.ts.map +1 -1
- package/dist/hooks/useDocumentUploads.d.ts +1 -1
- package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
- package/dist/hooks/useEditorPersistence.d.ts +5 -1
- package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +2 -2
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useNotice.d.ts +19 -0
- package/dist/hooks/useNotice.d.ts.map +1 -0
- package/dist/hooks/useSortableNavRow.d.ts +11 -0
- package/dist/hooks/useSortableNavRow.d.ts.map +1 -1
- package/dist/hooks/useSortableRow.d.ts +11 -0
- package/dist/hooks/useSortableRow.d.ts.map +1 -1
- package/dist/hooks/useStackedEscape.d.ts +32 -0
- package/dist/hooks/useStackedEscape.d.ts.map +1 -0
- package/dist/index.js +67 -49
- package/dist/lib/audience-secret.js +2 -2
- package/dist/lib/block-dnd.d.ts +5 -0
- package/dist/lib/block-dnd.d.ts.map +1 -1
- package/dist/lib/block-move.d.ts.map +1 -1
- package/dist/lib/brand-swatches.d.ts +12 -0
- package/dist/lib/brand-swatches.d.ts.map +1 -0
- package/dist/lib/container-ops.d.ts +3 -2
- package/dist/lib/container-ops.d.ts.map +1 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +9 -1
- package/dist/lib/env.d.ts +4 -0
- package/dist/lib/env.d.ts.map +1 -1
- package/dist/lib/env.js +5 -3
- package/dist/lib/events.d.ts +1 -2
- package/dist/lib/events.d.ts.map +1 -1
- package/dist/lib/image-background.d.ts +8 -0
- package/dist/lib/image-background.d.ts.map +1 -0
- package/dist/lib/index-ops.js +3 -3
- package/dist/lib/index.d.ts +6 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +29 -21
- package/dist/lib/loader.d.ts +21 -7
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/nav-tree.d.ts +1 -0
- package/dist/lib/nav-tree.d.ts.map +1 -1
- package/dist/lib/nav.d.ts +38 -0
- package/dist/lib/nav.d.ts.map +1 -1
- package/dist/lib/platform-broker.d.ts +4 -2
- package/dist/lib/platform-broker.d.ts.map +1 -1
- package/dist/lib/platform-broker.js +21 -13
- package/dist/lib/reference-type.d.ts +2 -0
- package/dist/lib/reference-type.d.ts.map +1 -0
- package/dist/lib/reference-validation.d.ts +31 -0
- package/dist/lib/reference-validation.d.ts.map +1 -0
- package/dist/lib/reference-validation.js +39 -0
- package/dist/lib/references.d.ts +74 -0
- package/dist/lib/references.d.ts.map +1 -0
- package/dist/lib/registry.d.ts +35 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/{safeRedirect.d.ts → safe-redirect.d.ts} +1 -1
- package/dist/lib/safe-redirect.d.ts.map +1 -0
- package/dist/lib/tags.d.ts +58 -0
- package/dist/lib/tags.d.ts.map +1 -0
- package/dist/lib/theme-vars.d.ts +9 -0
- package/dist/lib/theme-vars.d.ts.map +1 -1
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +8 -4
- package/dist/media/resolve.d.ts +2 -0
- package/dist/media/resolve.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/schemas/audience.js +2 -2
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +14 -8
- package/dist/schemas/shared.d.ts +26 -0
- package/dist/schemas/shared.d.ts.map +1 -1
- package/dist/schemas/site-config.d.ts +7 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/dist/schemas/tags.d.ts +27 -0
- package/dist/schemas/tags.d.ts.map +1 -0
- package/dist/storage/index.js +1 -1
- package/dist/types/database.d.ts +8 -0
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/auth/cookies.ts +111 -0
- package/src/auth/types.ts +1 -0
- package/src/collab/rebase.ts +26 -2
- package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
- package/src/components/brandguide/ColorsEditable.tsx +1 -0
- package/src/components/chat/ChatPanel.tsx +2 -3
- package/src/components/chat/ChatWidget.tsx +7 -7
- 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 +48 -35
- package/src/components/shared/Notice.tsx +59 -0
- package/src/components/shared/Popover.tsx +7 -11
- package/src/components/shared/ReferenceSourcesContext.ts +27 -0
- package/src/components/shared/Select.tsx +5 -2
- package/src/components/shared/SplitButton.tsx +11 -17
- package/src/components/shared/SummaryAnchorsContext.ts +11 -0
- package/src/components/shared/Textarea.tsx +17 -2
- package/src/components/shared/ToolbarFrame.tsx +28 -0
- package/src/components/shared/VideoSourceField.tsx +1 -1
- package/src/components/shared/copy.ts +18 -0
- package/src/components/shared/icons.tsx +1 -17
- package/src/components/shell/AudienceAddForm.tsx +2 -2
- package/src/components/shell/AudienceDetailsPanel.tsx +23 -5
- package/src/components/shell/AudienceRow.tsx +22 -15
- package/src/components/shell/BugReportFAB.tsx +50 -40
- package/src/components/shell/CredentialList.tsx +33 -13
- package/src/components/shell/DocInspector.tsx +3 -12
- package/src/components/shell/DocumentEditorOverlay.tsx +33 -23
- package/src/components/shell/EditorContext.tsx +7 -0
- package/src/components/shell/EditorLoginForm.tsx +26 -36
- package/src/components/shell/EditorModal.tsx +8 -10
- package/src/components/shell/EditorShell.tsx +499 -339
- package/src/components/shell/HistoryToolbar.tsx +24 -26
- package/src/components/shell/MediaFolderTile.tsx +8 -3
- package/src/components/shell/MediaLibraryModal.tsx +59 -60
- package/src/components/shell/ProcessingIndicator.tsx +2 -2
- package/src/components/shell/RestoreModal.tsx +17 -16
- package/src/components/shell/SetPasswordForm.tsx +3 -3
- package/src/components/shell/SiteSettingsChatbot.tsx +61 -42
- package/src/components/shell/SiteSettingsDisplay.tsx +20 -21
- package/src/components/shell/SiteSettingsModal.tsx +24 -2
- package/src/components/shell/SiteSettingsTags.tsx +143 -0
- package/src/components/shell/SiteSettingsUsers.tsx +85 -28
- package/src/components/shell/SiteSettingsViewerAccess.tsx +2 -2
- package/src/components/shell/ViewerLoginForm.tsx +5 -4
- package/src/components/shell/reference-sources.ts +36 -0
- package/src/deck/index.ts +2 -2
- package/src/document/index.ts +2 -2
- package/src/hooks/useBlockDnd.ts +11 -0
- package/src/hooks/useBuildStatus.ts +53 -6
- package/src/hooks/useDocumentUploads.ts +1 -1
- package/src/hooks/useEditorPersistence.ts +141 -35
- package/src/hooks/useEditorPublish.ts +18 -27
- package/src/hooks/useMediaPipeline.ts +1 -1
- package/src/hooks/useNotice.ts +45 -0
- package/src/hooks/useSortableNavRow.ts +11 -0
- package/src/hooks/useSortableRow.ts +22 -2
- package/src/hooks/useStackedEscape.ts +62 -0
- package/src/lib/block-dnd.ts +6 -1
- package/src/lib/block-move.ts +3 -0
- package/src/lib/brand-swatches.ts +32 -0
- package/src/lib/container-ops.ts +5 -3
- package/src/lib/dexie.ts +15 -1
- package/src/lib/env.ts +9 -0
- package/src/lib/events.ts +1 -2
- package/src/lib/image-background.ts +31 -0
- package/src/lib/index.ts +5 -4
- package/src/lib/loader.ts +54 -55
- package/src/lib/nav-tree.ts +71 -0
- package/src/lib/nav.ts +196 -36
- package/src/lib/platform-broker.ts +32 -14
- package/src/lib/reference-type.ts +3 -0
- package/src/lib/reference-validation.ts +69 -0
- package/src/lib/references.ts +130 -0
- package/src/lib/registry.ts +37 -0
- package/src/lib/tags.ts +148 -0
- package/src/lib/theme-vars.ts +23 -0
- package/src/media/index.ts +1 -1
- package/src/media/resolve.ts +3 -1
- package/src/media/utils.ts +18 -0
- package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
- package/src/schemas/index.ts +1 -0
- package/src/schemas/shared.ts +24 -1
- package/src/schemas/site-config.ts +9 -1
- package/src/schemas/tags.ts +33 -0
- package/src/storage/types.ts +1 -1
- package/src/types/database.ts +6 -0
- package/dist/chunk-BR4MIPTM.js +0 -138
- package/dist/chunk-DNY72JZN.js +0 -86
- package/dist/chunk-F2LYQPXV.js +0 -77
- package/dist/chunk-TALRVOGO.js +0 -128
- 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
|
@@ -12,7 +12,7 @@ interface BuildStatusResponse {
|
|
|
12
12
|
interface BuildStatusResult {
|
|
13
13
|
state: BuildState;
|
|
14
14
|
elapsedSeconds: number;
|
|
15
|
-
startTracking: () => void;
|
|
15
|
+
startTracking: (commitSha?: string) => void;
|
|
16
16
|
dismiss: () => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -40,6 +40,15 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
40
40
|
const fadeRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
41
41
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
42
42
|
const giveUpRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
43
|
+
const expectedCommit = useRef<string | null>(null);
|
|
44
|
+
// Commits already on the status row when tracking began (an earlier build,
|
|
45
|
+
// possibly still running) versus commits first seen building afterwards (a
|
|
46
|
+
// later publish by a peer or the MCP connector, which supersedes ours).
|
|
47
|
+
const firstTrackedPoll = useRef(false);
|
|
48
|
+
const preexistingCommits = useRef(new Set<string>());
|
|
49
|
+
const laterBuilds = useRef(new Set<string>());
|
|
50
|
+
const trackingGeneration = useRef(0);
|
|
51
|
+
const pollInFlight = useRef(false);
|
|
43
52
|
const isPolling = useRef(false);
|
|
44
53
|
const mountedRef = useRef(true);
|
|
45
54
|
|
|
@@ -80,7 +89,7 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
80
89
|
|
|
81
90
|
const fetchStatus = useCallback(async (): Promise<BuildStatusResponse | null> => {
|
|
82
91
|
try {
|
|
83
|
-
const res = await fetch("/api/build-status");
|
|
92
|
+
const res = await fetch("/api/build-status", { signal: AbortSignal.timeout(10_000) });
|
|
84
93
|
if (!res.ok) return null;
|
|
85
94
|
const data = await res.json();
|
|
86
95
|
const validStates = ["building", "ready", "error"];
|
|
@@ -107,6 +116,29 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
107
116
|
return;
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
const expected = expectedCommit.current;
|
|
120
|
+
if (expected && data.commitSha && data.commitSha !== expected) {
|
|
121
|
+
// Netlify builds serially, so a different commit is either an older
|
|
122
|
+
// build finishing late or a newer publish. No clock can tell them apart
|
|
123
|
+
// (the row is server time, this is browser time), so go by order seen:
|
|
124
|
+
// accept a different commit's result only if it began building after
|
|
125
|
+
// tracking did. A provider that reports no commit is accepted as-is.
|
|
126
|
+
const commit = data.commitSha;
|
|
127
|
+
const first = firstTrackedPoll.current;
|
|
128
|
+
firstTrackedPoll.current = false;
|
|
129
|
+
if (first || preexistingCommits.current.has(commit)) {
|
|
130
|
+
preexistingCommits.current.add(commit);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (data.state === "building") {
|
|
134
|
+
laterBuilds.current.add(commit);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!laterBuilds.current.has(commit)) return;
|
|
138
|
+
} else if (expected) {
|
|
139
|
+
firstTrackedPoll.current = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
110
142
|
setState(data.state);
|
|
111
143
|
|
|
112
144
|
if (data.state === "ready" || data.state === "error") {
|
|
@@ -133,8 +165,13 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
133
165
|
isPolling.current = true;
|
|
134
166
|
|
|
135
167
|
pollRef.current = setInterval(async () => {
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
if (pollInFlight.current) return;
|
|
169
|
+
pollInFlight.current = true;
|
|
170
|
+
const generation = trackingGeneration.current;
|
|
171
|
+
try {
|
|
172
|
+
const data = await fetchStatus();
|
|
173
|
+
if (generation === trackingGeneration.current && isPolling.current) handleStatusUpdate(data, false);
|
|
174
|
+
} finally { pollInFlight.current = false; }
|
|
138
175
|
}, POLL_INTERVAL);
|
|
139
176
|
}, [fetchStatus, handleStatusUpdate]);
|
|
140
177
|
|
|
@@ -148,9 +185,10 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
148
185
|
// real remount in production.
|
|
149
186
|
mountedRef.current = true;
|
|
150
187
|
|
|
188
|
+
const initialGeneration = trackingGeneration.current;
|
|
151
189
|
async function check() {
|
|
152
190
|
const data = await fetchStatus();
|
|
153
|
-
if (cancelled) return;
|
|
191
|
+
if (cancelled || initialGeneration !== trackingGeneration.current) return;
|
|
154
192
|
|
|
155
193
|
if (data && data.state === "building") {
|
|
156
194
|
const ageMs = Date.now() - Date.parse(data.updatedAt);
|
|
@@ -183,7 +221,13 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
183
221
|
};
|
|
184
222
|
}, [fetchStatus, handleStatusUpdate, startPolling, startTimer, stopPolling, stopTimer, armGiveUp, clearGiveUp]);
|
|
185
223
|
|
|
186
|
-
const startTracking = useCallback(() => {
|
|
224
|
+
const startTracking = useCallback((commitSha?: string) => {
|
|
225
|
+
trackingGeneration.current++;
|
|
226
|
+
expectedCommit.current = commitSha ?? null;
|
|
227
|
+
firstTrackedPoll.current = true;
|
|
228
|
+
preexistingCommits.current = new Set();
|
|
229
|
+
laterBuilds.current = new Set();
|
|
230
|
+
if (fadeRef.current) { clearTimeout(fadeRef.current); fadeRef.current = null; }
|
|
187
231
|
if (clearRef.current) { clearTimeout(clearRef.current); clearRef.current = null; }
|
|
188
232
|
setState("building");
|
|
189
233
|
startTimer();
|
|
@@ -192,6 +236,9 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
192
236
|
}, [startPolling, startTimer, armGiveUp]);
|
|
193
237
|
|
|
194
238
|
const dismiss = useCallback(() => {
|
|
239
|
+
trackingGeneration.current++;
|
|
240
|
+
expectedCommit.current = null;
|
|
241
|
+
if (fadeRef.current) { clearTimeout(fadeRef.current); fadeRef.current = null; }
|
|
195
242
|
if (clearRef.current) { clearTimeout(clearRef.current); clearRef.current = null; }
|
|
196
243
|
setState("idle");
|
|
197
244
|
stopPolling();
|
|
@@ -32,7 +32,7 @@ interface UseDocumentUploadsInput {
|
|
|
32
32
|
* invisible here, so without this the dedup below cannot tell a live
|
|
33
33
|
* document from a doomed one. No-op for an id that isn't doomed. */
|
|
34
34
|
undeleteDocument?: (id: string) => void;
|
|
35
|
-
/** Same channel the media pipeline uses — surfaces in
|
|
35
|
+
/** Same channel the media pipeline uses — surfaces in InlineAlert. */
|
|
36
36
|
onUploadError?: (message: string) => void;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -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";
|
|
@@ -81,7 +84,7 @@ interface PublishDeps {
|
|
|
81
84
|
pendingMediaDeletions: string[];
|
|
82
85
|
onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
|
|
83
86
|
onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
|
|
84
|
-
onPublishComplete?: () => void;
|
|
87
|
+
onPublishComplete?: (sha: string) => void;
|
|
85
88
|
// The draft version this editor is based on (saved-branch head, or null when
|
|
86
89
|
// no draft exists yet). Sent with saves so the server can 409 on a concurrent
|
|
87
90
|
// edit instead of silently overwriting it. Read at save time (latest value).
|
|
@@ -171,23 +174,10 @@ export function useEditorPublish({
|
|
|
171
174
|
onDocumentsDeleted,
|
|
172
175
|
}: PublishDeps) {
|
|
173
176
|
const [publishAction, setPublishAction] = useState<PublishAction>("idle");
|
|
174
|
-
const
|
|
177
|
+
const { notice: publishFeedback, show: showFeedback, clear: clearPublishFeedback } = useNotice();
|
|
175
178
|
const [saveProgress, setSaveProgress] = useState<string | null>(null);
|
|
176
|
-
const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
177
179
|
const inFlightRef = useRef(false);
|
|
178
180
|
|
|
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
181
|
async function gatherMediaPayload(
|
|
192
182
|
pendingItems: MediaItem[],
|
|
193
183
|
pendingDeletions: string[],
|
|
@@ -591,7 +581,7 @@ export function useEditorPublish({
|
|
|
591
581
|
recoveredRef.current = false;
|
|
592
582
|
editTokenRef.current = getEditGeneration?.() ?? Number.MAX_SAFE_INTEGER;
|
|
593
583
|
setPublishAction("saving");
|
|
594
|
-
|
|
584
|
+
clearPublishFeedback();
|
|
595
585
|
return true;
|
|
596
586
|
}
|
|
597
587
|
|
|
@@ -675,12 +665,13 @@ export function useEditorPublish({
|
|
|
675
665
|
// M1 honesty: only claim the view was updated when recovery actually
|
|
676
666
|
// rebased it this invocation (i.e. the RETRY 409'd). A failed/absent/
|
|
677
667
|
// skipped recovery left the view untouched — say so.
|
|
668
|
+
const tone: NoticeTone = isConflict && recoveredRef.current ? "warning" : "error";
|
|
678
669
|
const message = isConflict
|
|
679
670
|
? recoveredRef.current
|
|
680
671
|
? "Someone else saved while you were editing — your view has been updated; try saving again."
|
|
681
672
|
: "This site was changed elsewhere — reload to get the latest, then re-apply your edits."
|
|
682
673
|
: error instanceof Error && error.message ? error.message : `${failLabel} failed`;
|
|
683
|
-
showFeedback(message, isConflict ? 8000 : 6000);
|
|
674
|
+
showFeedback(tone, message, { ttlMs: isConflict ? 8000 : 6000 });
|
|
684
675
|
}
|
|
685
676
|
|
|
686
677
|
const handleSave = useCallback(async () => {
|
|
@@ -726,11 +717,11 @@ export function useEditorPublish({
|
|
|
726
717
|
if (committedDocumentDeletions.length > 0) onDocumentsDeleted?.(committedDocumentDeletions);
|
|
727
718
|
} catch (cleanupError) {
|
|
728
719
|
console.error("Save succeeded but local cleanup failed:", cleanupError);
|
|
729
|
-
showFeedback("Saved, but the local cache couldn't be updated — reload the editor.", 8000);
|
|
720
|
+
showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
|
|
730
721
|
return;
|
|
731
722
|
}
|
|
732
723
|
|
|
733
|
-
showFeedback("
|
|
724
|
+
showFeedback("success", COPY.publish.saved, { ttlMs: 3000 });
|
|
734
725
|
} catch (error) {
|
|
735
726
|
reportSaveError(error, "Save");
|
|
736
727
|
} finally {
|
|
@@ -748,7 +739,7 @@ export function useEditorPublish({
|
|
|
748
739
|
if (inFlightRef.current) return;
|
|
749
740
|
inFlightRef.current = true;
|
|
750
741
|
setPublishAction("publishing");
|
|
751
|
-
|
|
742
|
+
clearPublishFeedback();
|
|
752
743
|
|
|
753
744
|
try {
|
|
754
745
|
const response = await fetch("/api/publish", {
|
|
@@ -772,10 +763,10 @@ export function useEditorPublish({
|
|
|
772
763
|
// restored on a later cache hit. (Save & Publish already re-caches instead.)
|
|
773
764
|
await clearContentCache();
|
|
774
765
|
onShasUpdated(null, sha);
|
|
775
|
-
onPublishComplete?.();
|
|
766
|
+
onPublishComplete?.(sha);
|
|
776
767
|
} catch (error) {
|
|
777
768
|
console.error("Publish failed:", error);
|
|
778
|
-
showFeedback("
|
|
769
|
+
showFeedback("error", COPY.publish.publishFailed, { ttlMs: 5000 });
|
|
779
770
|
} finally {
|
|
780
771
|
inFlightRef.current = false;
|
|
781
772
|
setPublishAction("idle");
|
|
@@ -833,7 +824,7 @@ export function useEditorPublish({
|
|
|
833
824
|
// top of unknown local state would compound it — stop and tell the
|
|
834
825
|
// editor to reload.
|
|
835
826
|
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);
|
|
827
|
+
showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor before publishing.", { ttlMs: 8000 });
|
|
837
828
|
return;
|
|
838
829
|
}
|
|
839
830
|
}
|
|
@@ -870,14 +861,14 @@ export function useEditorPublish({
|
|
|
870
861
|
} catch (cleanupError) {
|
|
871
862
|
console.error("Publish succeeded but the cache re-key failed:", cleanupError);
|
|
872
863
|
onShasUpdated(null, sha);
|
|
873
|
-
onPublishComplete?.();
|
|
874
|
-
showFeedback("Published, but the local cache couldn't be updated — reload the editor.", 8000);
|
|
864
|
+
onPublishComplete?.(sha);
|
|
865
|
+
showFeedback("warning", "Published, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
|
|
875
866
|
return;
|
|
876
867
|
}
|
|
877
868
|
}
|
|
878
869
|
|
|
879
870
|
onShasUpdated(null, sha);
|
|
880
|
-
onPublishComplete?.();
|
|
871
|
+
onPublishComplete?.(sha);
|
|
881
872
|
} catch (error) {
|
|
882
873
|
reportSaveError(error, "Publish");
|
|
883
874
|
} finally {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
markPendingMediaDeleted,
|
|
13
13
|
removePendingMediaDeletion,
|
|
14
14
|
} from "../lib/dexie";
|
|
15
|
-
import { generateVideoPoster } from "../media/
|
|
15
|
+
import { generateVideoPoster } from "../media/video-poster";
|
|
16
16
|
import { ASSET_CACHE_CONTROL } from "../storage/types";
|
|
17
17
|
import { tallyImageIds, collectImageIds, clearImageIds, applyLogoDeletion } from "../lib/image-refs";
|
|
18
18
|
import { clearDocumentRefs, tallyDocumentIds } from "../lib/document-refs";
|
|
@@ -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.
|
|
@@ -11,6 +11,17 @@ interface UseSortableRowOptions {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
+
* One of three sibling drag-and-drop position hooks, split by location type
|
|
15
|
+
* rather than unified behind a generic `useSortable({ axis })`: this one (flat
|
|
16
|
+
* index — section-ordering lists, `EditableList`), `useSortableNavRow`
|
|
17
|
+
* (NavLocation — the Pages modal's group-aware nav tree), and `useBlockDnd`
|
|
18
|
+
* (block/container id + index — the section canvas, root and nested). A
|
|
19
|
+
* fourth location type earns its own hook on the same shape (rowRef/handleRef,
|
|
20
|
+
* lazy atlaskit import, closest-edge state, a ref for the latest callback)
|
|
21
|
+
* rather than a parameter bag bolted onto an existing one — see
|
|
22
|
+
* docs/superpowers/specs/2026-09-04-editor-ui-primitives-consolidation.md § D6
|
|
23
|
+
* for why a shared `useSortable` was deferred.
|
|
24
|
+
*
|
|
14
25
|
* Closest-edge drag-to-reorder wiring for a single list row, shared by the
|
|
15
26
|
* section- and page-ordering modals. Attach `rowRef` to the row element and
|
|
16
27
|
* `handleRef` to the drag handle, and render
|
|
@@ -30,6 +41,15 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
|
|
|
30
41
|
const [isDragging, setIsDragging] = useState(false);
|
|
31
42
|
const [closestEdge, setClosestEdge] = useState<"top" | "bottom" | null>(null);
|
|
32
43
|
|
|
44
|
+
// Read through a ref so a changed `onReorder` identity doesn't re-register
|
|
45
|
+
// the atlaskit bindings mid-drag (which cancels the drag) — the same class
|
|
46
|
+
// of bug EditableList's `onReorderRef` fixed (2026-08-03). `index` stays a
|
|
47
|
+
// real effect dependency: a stale index would report the wrong
|
|
48
|
+
// `fromIndex`/`toIndex` on drop, the hazard useSortableNavRow's `locationOf`
|
|
49
|
+
// comment warns about for its own (non-re-subscribing) case.
|
|
50
|
+
const onReorderRef = useRef(onReorder);
|
|
51
|
+
onReorderRef.current = onReorder;
|
|
52
|
+
|
|
33
53
|
useEffect(() => {
|
|
34
54
|
if (!enabled) return;
|
|
35
55
|
const row = rowRef.current;
|
|
@@ -79,7 +99,7 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
|
|
|
79
99
|
const rawEdge = extractClosestEdge(self.data);
|
|
80
100
|
const edge = rawEdge === "top" || rawEdge === "bottom" ? rawEdge : null;
|
|
81
101
|
const toIndex = computeReorderTarget(fromIndex, index, edge);
|
|
82
|
-
if (toIndex !== null)
|
|
102
|
+
if (toIndex !== null) onReorderRef.current(fromIndex, toIndex);
|
|
83
103
|
},
|
|
84
104
|
});
|
|
85
105
|
|
|
@@ -93,7 +113,7 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
|
|
|
93
113
|
cancelled = true;
|
|
94
114
|
cleanup?.();
|
|
95
115
|
};
|
|
96
|
-
}, [index, dragType,
|
|
116
|
+
}, [index, dragType, enabled]);
|
|
97
117
|
|
|
98
118
|
return { rowRef, handleRef, isDragging, closestEdge };
|
|
99
119
|
}
|