@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
package/src/lib/registry.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { ZodType, z } from "zod";
|
|
|
2
2
|
import type { ComponentType, ReactNode } from "react";
|
|
3
3
|
import type { Audience } from "../schemas/auth";
|
|
4
4
|
import type { LinkValue } from "../schemas/link";
|
|
5
|
+
import type { ImageBackground } from "../schemas/shared";
|
|
5
6
|
import type { SectionContent } from "../schemas/sections";
|
|
7
|
+
import type { TagEntry } from "../schemas/tags";
|
|
6
8
|
import type { VideoSource } from "../components/sections/Video/VideoBlock";
|
|
7
9
|
|
|
8
10
|
// --- Settings field types ---
|
|
@@ -71,6 +73,16 @@ export type SettingsFieldDef =
|
|
|
71
73
|
label: string;
|
|
72
74
|
default: { documentId: string };
|
|
73
75
|
target?: "content" | "options";
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
/** Per-placement backdrop behind an image (ImageBackgroundField). Leave
|
|
79
|
+
* `default` unset: an absent value is "None" and must stay absent on save. */
|
|
80
|
+
type: "image-background";
|
|
81
|
+
label: string;
|
|
82
|
+
default?: ImageBackground;
|
|
83
|
+
target?: "content" | "options";
|
|
84
|
+
/** Hide the field for the current values (e.g. when the ref is a video). */
|
|
85
|
+
hidden?: (values: Record<string, unknown>) => boolean;
|
|
74
86
|
};
|
|
75
87
|
|
|
76
88
|
export type SettingsSchema = Record<string, SettingsFieldDef>;
|
|
@@ -105,6 +117,20 @@ export interface SectionProps<T = unknown> {
|
|
|
105
117
|
onDelete?: () => void;
|
|
106
118
|
}
|
|
107
119
|
|
|
120
|
+
/** The site's tag registry and its edits, as the editor shell hands them to
|
|
121
|
+
* the canvas and Site settings. */
|
|
122
|
+
export interface SiteTagging {
|
|
123
|
+
/** Already reconciled (siteTags). */
|
|
124
|
+
tags: TagEntry[];
|
|
125
|
+
/** False when the registry was never loaded or loaded degraded: a save would
|
|
126
|
+
* wholesale-replace tags.json with what this session couldn't fully read. */
|
|
127
|
+
editable: boolean;
|
|
128
|
+
onCreate: (label: string, sectionId?: string) => void;
|
|
129
|
+
onRename: (tagId: string, label: string) => void;
|
|
130
|
+
onDelete: (tagId: string) => void;
|
|
131
|
+
onToggle: (sectionId: string, tagId: string, tagged: boolean) => void;
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
export interface WrapperProps {
|
|
109
135
|
sectionId: string;
|
|
110
136
|
sectionType: string;
|
|
@@ -112,6 +138,8 @@ export interface WrapperProps {
|
|
|
112
138
|
dirty?: boolean;
|
|
113
139
|
index: number;
|
|
114
140
|
isLast: boolean;
|
|
141
|
+
/** Total sections on the page — Move up/down's "N of M" and end-disabling. */
|
|
142
|
+
sectionCount?: number;
|
|
115
143
|
containerId: string;
|
|
116
144
|
isContainerBlock: boolean;
|
|
117
145
|
definition: SectionDefinition;
|
|
@@ -119,6 +147,15 @@ export interface WrapperProps {
|
|
|
119
147
|
audiences: Audience[];
|
|
120
148
|
access: string[];
|
|
121
149
|
onAccessChange?: (access: string[]) => void;
|
|
150
|
+
/** Section tags (the tags.json sidecar). Only root sections get a
|
|
151
|
+
* SectionWrapper — container children are not index sections and cannot be
|
|
152
|
+
* tagged. Absent handlers make the Tags pill read-only. */
|
|
153
|
+
tagging: {
|
|
154
|
+
tags: TagEntry[];
|
|
155
|
+
assignedIds: string[];
|
|
156
|
+
onToggle?: (tagId: string, tagged: boolean) => void;
|
|
157
|
+
onCreate?: (label: string) => void;
|
|
158
|
+
};
|
|
122
159
|
onStatusChange?: (status: "draft" | "live" | "archived") => void;
|
|
123
160
|
onSectionChange?: (options: Record<string, unknown>) => void;
|
|
124
161
|
onReorder?: (fromIndex: number, toIndex: number) => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Sidecar reconcile changed. Reconcile repairs silently on every read, so
|
|
3
|
+
* these are only ever warnings: a validator reports them, nothing rejects on them.
|
|
4
|
+
* - dangling: ids of pages/sections the index does not have, dropped
|
|
5
|
+
* - duplicate-id: a group/tag id used again, later uses dropped
|
|
6
|
+
* - duplicate-member: a page placed twice / a section tagged twice, later placements dropped
|
|
7
|
+
* - unlisted: pages missing from the nav, appended to its end (nav only)
|
|
8
|
+
*
|
|
9
|
+
* Its own leaf so nav-tree and tags can report without importing the Sidecar
|
|
10
|
+
* module that imports them.
|
|
11
|
+
*/
|
|
12
|
+
export type SidecarDiagnostic = {
|
|
13
|
+
code: "dangling" | "duplicate-id" | "duplicate-member" | "unlisted";
|
|
14
|
+
ids: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function diagnosticsCollector() {
|
|
18
|
+
const byCode = new Map<SidecarDiagnostic["code"], Set<string>>();
|
|
19
|
+
return {
|
|
20
|
+
add(code: SidecarDiagnostic["code"], id: string) {
|
|
21
|
+
const ids = byCode.get(code) ?? new Set<string>();
|
|
22
|
+
ids.add(id);
|
|
23
|
+
byCode.set(code, ids);
|
|
24
|
+
},
|
|
25
|
+
list(): SidecarDiagnostic[] {
|
|
26
|
+
return [...byCode].map(([code, ids]) => ({ code, ids: [...ids] }));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { ZodType } from "zod";
|
|
2
|
+
import { NavEntrySchema, NavFileSchema, type NavEntry } from "../schemas/nav";
|
|
3
|
+
import { TagEntrySchema, TagsFileSchema, type TagEntry } from "../schemas/tags";
|
|
4
|
+
import type { SiteIndex } from "../schemas/site-config";
|
|
5
|
+
import { reconcileNavReport } from "./nav-tree";
|
|
6
|
+
import { mergeTags, reconcileTagsReport } from "./tags";
|
|
7
|
+
import type { SidecarDiagnostic } from "./sidecar-diagnostics";
|
|
8
|
+
|
|
9
|
+
export type { SidecarDiagnostic };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One definition per Sidecar — a content file kept beside index.json rather than
|
|
13
|
+
* on it, so a writer that predates it cannot drop it (CONTEXT.md). Every rule a
|
|
14
|
+
* caller would otherwise re-implement per file lives behind these: lenient
|
|
15
|
+
* parse, lifting the wire key off an index before IndexSchema strips it, and the
|
|
16
|
+
* two places the Sidecars genuinely differ — how a list is repaired against the
|
|
17
|
+
* index, and how concurrent edits rebase. Pure and browser-safe: the editor uses
|
|
18
|
+
* it during hydration, and callers do their own I/O.
|
|
19
|
+
*/
|
|
20
|
+
export interface SidecarDefinition<E> {
|
|
21
|
+
/** Also the key the Sidecar travels under on the wire (`index.nav`, `index.tags`). */
|
|
22
|
+
name: "nav" | "tags";
|
|
23
|
+
path: string;
|
|
24
|
+
/** The key holding the entry list inside the file. */
|
|
25
|
+
fileKey: "entries" | "tags";
|
|
26
|
+
entrySchema: ZodType<E>;
|
|
27
|
+
fileSchema: ZodType<unknown>;
|
|
28
|
+
/** Total, pure and idempotent; also reports what it repaired. */
|
|
29
|
+
reconcile(entries: E[], index: SiteIndex): { entries: E[]; diagnostics: SidecarDiagnostic[] };
|
|
30
|
+
/** The list after a collab rebase; `undefined` when no side carries one. */
|
|
31
|
+
rebase(args: { base: E[] | undefined; local: E[] | undefined; remote: E[] | undefined }): E[] | undefined;
|
|
32
|
+
/** Words for a declined write's warning. */
|
|
33
|
+
words: {
|
|
34
|
+
/** What the file holds: "navigation entries". */
|
|
35
|
+
entries: string;
|
|
36
|
+
/** What did not land: "Page grouping changes". */
|
|
37
|
+
changes: string;
|
|
38
|
+
/** What a valid payload is: "a valid navigation tree". */
|
|
39
|
+
valid: string;
|
|
40
|
+
/** The expected wire shape, for an agent that sent the wrong one. */
|
|
41
|
+
shape: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const NAV: SidecarDefinition<NavEntry> = {
|
|
46
|
+
name: "nav",
|
|
47
|
+
path: "src/content/nav.json",
|
|
48
|
+
fileKey: "entries",
|
|
49
|
+
entrySchema: NavEntrySchema,
|
|
50
|
+
fileSchema: NavFileSchema,
|
|
51
|
+
reconcile: (entries, index) => reconcileNavReport(entries, index.pages),
|
|
52
|
+
// Grouping is edited rarely and has no dirty tracking, so persisted remote
|
|
53
|
+
// state beats unsaved local edits. A remote without the Sidecar (an older
|
|
54
|
+
// server) keeps ours rather than flattening it.
|
|
55
|
+
rebase: ({ local, remote }) => (remote !== undefined ? remote : local),
|
|
56
|
+
words: {
|
|
57
|
+
entries: "navigation entries",
|
|
58
|
+
changes: "Page grouping changes",
|
|
59
|
+
valid: "a valid navigation tree",
|
|
60
|
+
shape: 'an array of {kind:"page",pageId} / {kind:"group",id,label,startCollapsed,pageIds} entries',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const TAGS: SidecarDefinition<TagEntry> = {
|
|
65
|
+
name: "tags",
|
|
66
|
+
path: "src/content/tags.json",
|
|
67
|
+
fileKey: "tags",
|
|
68
|
+
entrySchema: TagEntrySchema,
|
|
69
|
+
fileSchema: TagsFileSchema,
|
|
70
|
+
reconcile: (entries, index) => reconcileTagsReport(entries, index.sections),
|
|
71
|
+
// Tagging is edited from every section's chrome, so remote-wins would drop
|
|
72
|
+
// unsaved tagging whenever a peer saved anything. See mergeTags.
|
|
73
|
+
rebase: mergeTags,
|
|
74
|
+
words: {
|
|
75
|
+
entries: "tag entries",
|
|
76
|
+
changes: "Tag changes",
|
|
77
|
+
valid: "a valid tag list",
|
|
78
|
+
shape: "an array of {id,label,sectionIds} entries",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Widened once here: the definitions are invariant in their entry type, so a
|
|
83
|
+
// loop over both needs the common type.
|
|
84
|
+
export const SIDECARS: readonly SidecarDefinition<unknown>[] = [NAV, TAGS] as readonly SidecarDefinition<unknown>[];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A Sidecar as read. `degraded` means the file was present but held something
|
|
88
|
+
* this package version could not represent, so `entries` is missing it: the read
|
|
89
|
+
* is a Degraded read, never to be cached, posted back, or written over the file.
|
|
90
|
+
*/
|
|
91
|
+
export type SidecarRead<E> = { entries: E[]; degraded: boolean };
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Tolerance is per entry: one unknown entry kind (a newer package's addition, an
|
|
95
|
+
* authoring typo) drops that entry alone, not every group or tag on the site.
|
|
96
|
+
*/
|
|
97
|
+
function readEntries<E>(def: SidecarDefinition<E>, raw: unknown): SidecarRead<E> {
|
|
98
|
+
if (!Array.isArray(raw)) {
|
|
99
|
+
console.warn(`[sidecars] ${def.name}: entry list is not a list; reading it as empty and degraded`);
|
|
100
|
+
return { entries: [], degraded: true };
|
|
101
|
+
}
|
|
102
|
+
const entries: E[] = [];
|
|
103
|
+
let dropped = 0;
|
|
104
|
+
for (const item of raw) {
|
|
105
|
+
const parsed = def.entrySchema.safeParse(item);
|
|
106
|
+
if (parsed.success) entries.push(parsed.data);
|
|
107
|
+
else dropped++;
|
|
108
|
+
}
|
|
109
|
+
if (dropped > 0) {
|
|
110
|
+
console.warn(`[sidecars] ${def.name}: dropped ${dropped} entr${dropped === 1 ? "y" : "ies"} this package version cannot represent; the read is degraded`);
|
|
111
|
+
}
|
|
112
|
+
return { entries, degraded: dropped > 0 };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Read a parsed Sidecar file. Absent (null/undefined) is an empty list, not degraded. */
|
|
116
|
+
export function readSidecar<E>(def: SidecarDefinition<E>, file: unknown): SidecarRead<E> {
|
|
117
|
+
if (file === undefined || file === null) return { entries: [], degraded: false };
|
|
118
|
+
return readEntries(def, (file as Record<string, unknown>)[def.fileKey]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Read a Sidecar file's raw text. Present-but-unparseable JSON is degraded, not
|
|
123
|
+
* absent: a site whose nav.json is corrupt still has groups, and treating it as
|
|
124
|
+
* "never had any" would let a writer replace them.
|
|
125
|
+
*/
|
|
126
|
+
export function readSidecarText<E>(def: SidecarDefinition<E>, text: string | undefined): SidecarRead<E> {
|
|
127
|
+
if (text === undefined) return { entries: [], degraded: false };
|
|
128
|
+
let file: unknown;
|
|
129
|
+
try {
|
|
130
|
+
file = JSON.parse(text);
|
|
131
|
+
} catch {
|
|
132
|
+
console.warn(`[sidecars] ${def.name}: ${def.path} is not valid JSON; the read is degraded`);
|
|
133
|
+
return { entries: [], degraded: true };
|
|
134
|
+
}
|
|
135
|
+
return readSidecar(def, file);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Lift the Sidecars off an index as it arrives on the wire, BEFORE anything
|
|
140
|
+
* parses it with IndexSchema — the parse strips unknown keys, and an index
|
|
141
|
+
* round-tripped through it has lost its Sidecars. A key the index does not carry
|
|
142
|
+
* is `undefined` (omitted: writers leave the file alone); a carried key is read
|
|
143
|
+
* with the file's per-entry leniency, into fresh objects that never alias the
|
|
144
|
+
* caller's.
|
|
145
|
+
*/
|
|
146
|
+
export function liftSidecars(rawIndex: unknown): { nav?: SidecarRead<NavEntry>; tags?: SidecarRead<TagEntry> } {
|
|
147
|
+
const raw = rawIndex !== null && typeof rawIndex === "object" ? (rawIndex as Record<string, unknown>) : {};
|
|
148
|
+
return {
|
|
149
|
+
...(raw.nav !== undefined ? { nav: readEntries(NAV, raw.nav) } : {}),
|
|
150
|
+
...(raw.tags !== undefined ? { tags: readEntries(TAGS, raw.tags) } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The Sidecar file on the branch a write lands on, as the writer read it. */
|
|
155
|
+
export type SidecarFileState =
|
|
156
|
+
| { kind: "absent" }
|
|
157
|
+
| { kind: "present"; text: string }
|
|
158
|
+
/** The read itself failed, so nothing is known about the file. */
|
|
159
|
+
| { kind: "unreadable" };
|
|
160
|
+
|
|
161
|
+
export type SidecarWritePlan =
|
|
162
|
+
| { action: "untouched" }
|
|
163
|
+
| { action: "write"; path: string; content: string }
|
|
164
|
+
/** Declined. The rest of the write still lands, so the warning is what tells the caller. */
|
|
165
|
+
| { action: "skip"; warning: string };
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* What a write does to one Sidecar file, given what was posted (from
|
|
169
|
+
* liftSidecars) and the file on the branch. Every Sidecar write REPLACES the
|
|
170
|
+
* file, and the posted list was built from what this package version could
|
|
171
|
+
* READ — so a file holding anything this version cannot fully parse (an entry
|
|
172
|
+
* from a newer package, or a corrupt file) is read-only: replacing it would
|
|
173
|
+
* destroy those entries with a clean write. The same holds for a posted
|
|
174
|
+
* Degraded read.
|
|
175
|
+
*
|
|
176
|
+
* Emptiness is judged on the POSTED list, before reconcile: an empty list never
|
|
177
|
+
* creates a file (every editor save carries the Sidecars, and nav's reconcile
|
|
178
|
+
* would turn [] into the page list), while an empty list over an existing file
|
|
179
|
+
* replaces it wholesale. A written list is reconciled against the index being
|
|
180
|
+
* written, so it never names a page or section that write removed.
|
|
181
|
+
*/
|
|
182
|
+
export function planSidecarWrite<E>(
|
|
183
|
+
def: SidecarDefinition<E>,
|
|
184
|
+
posted: SidecarRead<E> | undefined,
|
|
185
|
+
branch: SidecarFileState,
|
|
186
|
+
index: SiteIndex,
|
|
187
|
+
): SidecarWritePlan {
|
|
188
|
+
if (posted === undefined) return { action: "untouched" };
|
|
189
|
+
const skip = (reason: string): SidecarWritePlan => ({
|
|
190
|
+
action: "skip",
|
|
191
|
+
warning: `${def.path} was left unchanged: ${reason} ${def.words.changes} from this save did not land. Upgrade the site's @drawnagency packages (or repair ${def.path} directly in the repo), then retry.`,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (posted.degraded) {
|
|
195
|
+
// Leaving the file alone is the right action; saying nothing is not — an
|
|
196
|
+
// agent that posted a malformed list would otherwise believe it landed.
|
|
197
|
+
return {
|
|
198
|
+
action: "skip",
|
|
199
|
+
warning: `${def.name} was not ${def.words.valid}, so ${def.path} was left unchanged (it held entries this package version cannot read). Expected ${def.words.shape}.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (branch.kind === "unreadable") {
|
|
203
|
+
return skip("the current file could not be read to confirm the replacement loses nothing.");
|
|
204
|
+
}
|
|
205
|
+
if (branch.kind === "present") {
|
|
206
|
+
let existing: unknown;
|
|
207
|
+
try {
|
|
208
|
+
existing = JSON.parse(branch.text);
|
|
209
|
+
} catch {
|
|
210
|
+
return skip("the current file is not valid JSON, so this version cannot prove a replacement keeps its entries.");
|
|
211
|
+
}
|
|
212
|
+
if (!def.fileSchema.safeParse(existing).success) {
|
|
213
|
+
return skip(`the current file holds ${def.words.entries} this package version cannot represent (likely written by a newer version).`);
|
|
214
|
+
}
|
|
215
|
+
} else if (posted.entries.length === 0) {
|
|
216
|
+
return { action: "untouched" };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { entries } = def.reconcile(posted.entries, index);
|
|
220
|
+
return { action: "write", path: def.path, content: JSON.stringify({ [def.fileKey]: entries }, null, 2) };
|
|
221
|
+
}
|
package/src/lib/slug.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The one slugifier for page slugs and media names. Its own leaf, with no
|
|
2
|
+
// imports, so the media worker can use it without bundling the schemas
|
|
3
|
+
// lib/pages pulls in.
|
|
4
|
+
export function slugifyPageSlug(input: string): string {
|
|
5
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES } from "../schemas/documents";
|
|
2
|
+
import { MEDIA_GIT_MAX_BYTES, MEDIA_BUCKET_MAX_BYTES, type MediaConfig } from "../schemas/media";
|
|
3
|
+
|
|
4
|
+
// Where a file's bytes live, decided by its size: committed to the site repo
|
|
5
|
+
// (git), stored in the optional bucket, or refused. Every upload path and the
|
|
6
|
+
// write path's backstop decide this the same way, and every message quotes the
|
|
7
|
+
// limits through formatBytes, so a limit changes in one place.
|
|
8
|
+
|
|
9
|
+
export { DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES, MEDIA_GIT_MAX_BYTES, MEDIA_BUCKET_MAX_BYTES };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The MCP connector's own document ceiling, half the bucket cap. The binding
|
|
13
|
+
* constraint is memory, not time: a presigned PUT cannot stream, so the whole
|
|
14
|
+
* body is buffered (peak about 2x the file). The browser path has no such
|
|
15
|
+
* limit because those bytes never pass through a function.
|
|
16
|
+
*/
|
|
17
|
+
export const DOCUMENT_CONNECTOR_MAX_BYTES = 104_857_600;
|
|
18
|
+
|
|
19
|
+
export type StorageTier = "git" | "bucket" | "too_large";
|
|
20
|
+
|
|
21
|
+
type MediaLimits = Pick<MediaConfig, "maxFileSize" | "maxAssetSize">;
|
|
22
|
+
|
|
23
|
+
/** Media limits come from the site's `media` config; documents' are fixed. */
|
|
24
|
+
export function tierFor(
|
|
25
|
+
kind: "document" | "media",
|
|
26
|
+
bytes: number,
|
|
27
|
+
media: MediaLimits = { maxFileSize: MEDIA_GIT_MAX_BYTES, maxAssetSize: MEDIA_BUCKET_MAX_BYTES },
|
|
28
|
+
): StorageTier {
|
|
29
|
+
const [gitMax, bucketMax] =
|
|
30
|
+
kind === "document" ? [DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES] : [media.maxFileSize, media.maxAssetSize];
|
|
31
|
+
if (bytes <= gitMax) return "git";
|
|
32
|
+
return bytes <= bucketMax ? "bucket" : "too_large";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** "2.5 MB", "100 MB" — one decimal at most, in binary megabytes. */
|
|
36
|
+
export function formatBytes(bytes: number): string {
|
|
37
|
+
return `${Math.round((bytes / 1024 / 1024) * 10) / 10} MB`;
|
|
38
|
+
}
|
package/src/lib/tags.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { TagEntry } from "../schemas/tags";
|
|
2
|
+
import type { SiteIndex } from "../schemas/site-config";
|
|
3
|
+
import { diagnosticsCollector, type SidecarDiagnostic } from "./sidecar-diagnostics";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Repair the tag sidecar against the current section set.
|
|
7
|
+
*
|
|
8
|
+
* Total, pure, and idempotent — the same contract as reconcileNav, for the same
|
|
9
|
+
* reason: tags.json survives writers that do not know it exists, so it can name
|
|
10
|
+
* sections an older writer (or an agent) has since deleted, and the repair has
|
|
11
|
+
* to happen on read rather than by rejecting content. It is deliberately NOT a
|
|
12
|
+
* schema refinement: parse-rejecting content would leave the editor unable to
|
|
13
|
+
* load the site it needs to repair it.
|
|
14
|
+
*
|
|
15
|
+
* Drops assignments to sections absent from `sections`, duplicate section ids
|
|
16
|
+
* within a tag, duplicate tag ids (first wins), and tags with no usable id. A
|
|
17
|
+
* tag with no assignments left is KEPT — the registry outlives its sections.
|
|
18
|
+
* Every entry is rebuilt key by key, so the output always parses as a
|
|
19
|
+
* TagsFile and never shares structure with the input (the editor holds this in
|
|
20
|
+
* React state, where a shared array would let a later edit mutate prior state).
|
|
21
|
+
*/
|
|
22
|
+
export function reconcileTags(entries: TagEntry[], sections: SiteIndex["sections"]): TagEntry[] {
|
|
23
|
+
return reconcileTagsReport(entries, sections).entries;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** reconcileTags plus what the repair changed — what a validator reports. */
|
|
27
|
+
export function reconcileTagsReport(
|
|
28
|
+
entries: TagEntry[],
|
|
29
|
+
sections: SiteIndex["sections"],
|
|
30
|
+
): { entries: TagEntry[]; diagnostics: SidecarDiagnostic[] } {
|
|
31
|
+
const report = diagnosticsCollector();
|
|
32
|
+
const entryList = Array.isArray(entries) ? entries : [];
|
|
33
|
+
const validIds = new Set(sections && typeof sections === "object" ? Object.keys(sections) : []);
|
|
34
|
+
|
|
35
|
+
const seenTagIds = new Set<string>();
|
|
36
|
+
const result: TagEntry[] = [];
|
|
37
|
+
for (const entry of entryList) {
|
|
38
|
+
if (!entry || typeof entry !== "object") continue;
|
|
39
|
+
if (typeof entry.id !== "string" || entry.id === "") continue;
|
|
40
|
+
if (seenTagIds.has(entry.id)) {
|
|
41
|
+
report.add("duplicate-id", entry.id);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
seenTagIds.add(entry.id);
|
|
45
|
+
|
|
46
|
+
const claimed = Array.isArray(entry.sectionIds) ? entry.sectionIds : [];
|
|
47
|
+
const sectionIds: string[] = [];
|
|
48
|
+
for (const id of claimed) {
|
|
49
|
+
if (typeof id !== "string") continue;
|
|
50
|
+
if (!validIds.has(id)) report.add("dangling", id);
|
|
51
|
+
else if (sectionIds.includes(id)) report.add("duplicate-member", id);
|
|
52
|
+
else sectionIds.push(id);
|
|
53
|
+
}
|
|
54
|
+
result.push({ id: entry.id, label: typeof entry.label === "string" ? entry.label : "", sectionIds });
|
|
55
|
+
}
|
|
56
|
+
return { entries: result, diagnostics: report.list() };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The ONLY sanctioned way to read the tags. Always reconciles, so callers never
|
|
61
|
+
* reason about whether index.tags is present, stale, or names deleted sections.
|
|
62
|
+
*/
|
|
63
|
+
export function siteTags(index: SiteIndex): TagEntry[] {
|
|
64
|
+
return reconcileTags(index.tags ?? [], index.sections);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Display fallback for blank tag labels — mirrors groupDisplayLabel. */
|
|
68
|
+
export function tagDisplayLabel(label: string): string {
|
|
69
|
+
return label || "Untitled tag";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The tag already using this name (case- and whitespace-insensitive) — what
|
|
73
|
+
* an inline create reuses, and what the registry refuses to duplicate. */
|
|
74
|
+
export function findTagByLabel(tags: TagEntry[], label: string): TagEntry | null {
|
|
75
|
+
const wanted = label.trim().toLowerCase();
|
|
76
|
+
if (!wanted) return null;
|
|
77
|
+
return tags.find((t) => t.label.trim().toLowerCase() === wanted) ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function addTag(index: SiteIndex, id: string, label: string, sectionId?: string): SiteIndex {
|
|
81
|
+
const tags = siteTags(index);
|
|
82
|
+
if (tags.some((t) => t.id === id)) return { ...index, tags };
|
|
83
|
+
return { ...index, tags: reconcileTags([...tags, { id, label, sectionIds: sectionId ? [sectionId] : [] }], index.sections) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function renameTag(index: SiteIndex, id: string, label: string): SiteIndex {
|
|
87
|
+
return { ...index, tags: siteTags(index).map((t) => (t.id === id ? { ...t, label } : t)) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function deleteTag(index: SiteIndex, id: string): SiteIndex {
|
|
91
|
+
return { ...index, tags: siteTags(index).filter((t) => t.id !== id) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function setSectionTagged(index: SiteIndex, sectionId: string, tagId: string, tagged: boolean): SiteIndex {
|
|
95
|
+
return {
|
|
96
|
+
...index,
|
|
97
|
+
tags: siteTags(index).map((t) => {
|
|
98
|
+
if (t.id !== tagId) return t;
|
|
99
|
+
const without = t.sectionIds.filter((id) => id !== sectionId);
|
|
100
|
+
return { ...t, sectionIds: tagged ? [...without, sectionId] : without };
|
|
101
|
+
}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Three-way merge of the tag sidecar for the collab rebase.
|
|
107
|
+
*
|
|
108
|
+
* Nav's rebase rule is "the remote's persisted tree wins", which is safe there
|
|
109
|
+
* because grouping is edited rarely. Tags are edited from every section's
|
|
110
|
+
* chrome, so the same rule would silently drop unsaved tagging whenever a peer
|
|
111
|
+
* saved anything at all. Instead, local edits are diffed against the clean
|
|
112
|
+
* baseline (`base`) and replayed onto the remote:
|
|
113
|
+
* - a tag created locally is appended; one deleted locally is dropped;
|
|
114
|
+
* - a local rename wins, otherwise the remote label stands;
|
|
115
|
+
* - per (tag, section) pair, a local add or remove is replayed;
|
|
116
|
+
* - a tag the REMOTE deleted stays deleted, local assignments included —
|
|
117
|
+
* persisted state beats unsaved edits to a tag that no longer exists.
|
|
118
|
+
* With no baseline to diff against, the remote wins (nav's rule). A remote with
|
|
119
|
+
* no tags at all (an older server that does not attach the sidecar) leaves the
|
|
120
|
+
* local tags intact rather than wiping them. Inputs are never mutated; the
|
|
121
|
+
* result is not reconciled — readers do that through siteTags.
|
|
122
|
+
*/
|
|
123
|
+
export function mergeTags(args: {
|
|
124
|
+
base: TagEntry[] | undefined;
|
|
125
|
+
local: TagEntry[] | undefined;
|
|
126
|
+
remote: TagEntry[] | undefined;
|
|
127
|
+
}): TagEntry[] | undefined {
|
|
128
|
+
const { base, local, remote } = args;
|
|
129
|
+
if (remote === undefined) return local?.map((t) => ({ ...t, sectionIds: [...t.sectionIds] }));
|
|
130
|
+
if (base === undefined || local === undefined) return remote.map((t) => ({ ...t, sectionIds: [...t.sectionIds] }));
|
|
131
|
+
|
|
132
|
+
// First occurrence wins, matching reconcileTags — `base` is an unreconciled
|
|
133
|
+
// server read, and a last-wins map would invent a diff against a duplicate.
|
|
134
|
+
const firstById = (tags: TagEntry[]) => {
|
|
135
|
+
const byId = new Map<string, TagEntry>();
|
|
136
|
+
for (const t of tags) if (!byId.has(t.id)) byId.set(t.id, t);
|
|
137
|
+
return byId;
|
|
138
|
+
};
|
|
139
|
+
const baseById = firstById(base);
|
|
140
|
+
const localById = firstById(local);
|
|
141
|
+
const remoteIds = new Set(remote.map((t) => t.id));
|
|
142
|
+
|
|
143
|
+
const result: TagEntry[] = [];
|
|
144
|
+
for (const r of remote) {
|
|
145
|
+
const b = baseById.get(r.id);
|
|
146
|
+
const l = localById.get(r.id);
|
|
147
|
+
if (b && !l) continue; // deleted locally
|
|
148
|
+
const members = [...r.sectionIds];
|
|
149
|
+
let label = r.label;
|
|
150
|
+
if (b && l) {
|
|
151
|
+
if (l.label !== b.label) label = l.label;
|
|
152
|
+
for (const id of l.sectionIds) if (!b.sectionIds.includes(id) && !members.includes(id)) members.push(id);
|
|
153
|
+
for (const id of b.sectionIds) {
|
|
154
|
+
if (l.sectionIds.includes(id)) continue;
|
|
155
|
+
const at = members.indexOf(id);
|
|
156
|
+
if (at !== -1) members.splice(at, 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
result.push({ id: r.id, label, sectionIds: members });
|
|
160
|
+
}
|
|
161
|
+
for (const l of local) {
|
|
162
|
+
if (!remoteIds.has(l.id) && !baseById.has(l.id)) result.push({ ...l, sectionIds: [...l.sectionIds] });
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
package/src/lib/theme-vars.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SiteConfig } from "../schemas/site-config";
|
|
2
2
|
import { suggestAccessibleOn } from "./contrast";
|
|
3
|
+
import { cornerRadiusValue } from "./corner-radius";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Computes the CSS value for the brand `--color-primary` / `--color-primary-contrast`
|
|
@@ -50,3 +51,25 @@ export function onPrimaryVarValue(c?: Partial<ThemeColorConfig> | null): string
|
|
|
50
51
|
const dark = c?.primaryColorDark ? suggestAccessibleOn(c.primaryColorDark) : null;
|
|
51
52
|
return lightDark(light, dark, c?.darkMode === "optional");
|
|
52
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The full inline `<html style>` string both `Layout.astro` and
|
|
57
|
+
* `LoginLayout.astro` set at SSR time — one assembly point for the 9 custom
|
|
58
|
+
* properties instead of the same template literal copied into both files.
|
|
59
|
+
* Byte-for-byte identical to what those files built inline before this
|
|
60
|
+
* existed; changing the property order or defaults here is a client-facing
|
|
61
|
+
* change to every site's `<html>` tag, not a refactor.
|
|
62
|
+
*/
|
|
63
|
+
export function themeStyle(siteConfig?: SiteConfig | null): string {
|
|
64
|
+
return (
|
|
65
|
+
`--color-primary: ${primaryVarValue(siteConfig)}; ` +
|
|
66
|
+
`--color-primary-contrast: ${accentVarValue(siteConfig)}; ` +
|
|
67
|
+
`--color-on-primary: ${onPrimaryVarValue(siteConfig)}; ` +
|
|
68
|
+
`--font-heading: ${siteConfig?.headingFont ?? "system-ui"}, system-ui, sans-serif; ` +
|
|
69
|
+
`--font-body: ${siteConfig?.bodyFont ?? "system-ui"}, system-ui, sans-serif; ` +
|
|
70
|
+
`--heading-text-transform: ${siteConfig?.uppercaseHeadings !== false ? "uppercase" : "none"}; ` +
|
|
71
|
+
`--subheading-text-transform: ${siteConfig?.uppercaseSubheadings !== false ? "uppercase" : "none"}; ` +
|
|
72
|
+
`--nav-text-transform: ${siteConfig?.uppercaseNavHeadings !== false ? "uppercase" : "none"}; ` +
|
|
73
|
+
`--radius-outer: ${cornerRadiusValue(siteConfig?.cornerRadius)};`
|
|
74
|
+
);
|
|
75
|
+
}
|
package/src/media/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export * from "./provider";
|
|
|
5
5
|
export * from "./utils";
|
|
6
6
|
export * from "./queue";
|
|
7
7
|
export * from "./resolve";
|
|
8
|
-
export { generateVideoPoster } from "./
|
|
8
|
+
export { generateVideoPoster } from "./video-poster";
|
|
9
9
|
export { composePreviewImage, type PreviewImageInputs } from "./compose-preview";
|
|
10
10
|
|
|
11
11
|
const MEDIA_KEY = Symbol.for("@drawnagency/primitives/media");
|
package/src/media/queue.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MediaKind } from "./types";
|
|
2
|
+
import type { ImageVariantPlan } from "../lib/image-variant-plan";
|
|
2
3
|
|
|
3
4
|
export interface QueueInput {
|
|
4
5
|
buffer: ArrayBuffer;
|
|
@@ -23,6 +24,7 @@ export interface QueueItem {
|
|
|
23
24
|
posterBlob?: Blob;
|
|
24
25
|
width: number;
|
|
25
26
|
height: number;
|
|
27
|
+
plan?: ImageVariantPlan;
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -116,6 +118,7 @@ export class ProcessingQueue {
|
|
|
116
118
|
posterBlob: msg.posterBlob,
|
|
117
119
|
width: msg.width,
|
|
118
120
|
height: msg.height,
|
|
121
|
+
plan: msg.plan,
|
|
119
122
|
};
|
|
120
123
|
this.options.onEvent({ type: "complete", item: { ...item } });
|
|
121
124
|
// The event above carries the only reference anyone reads. Holding ours
|
package/src/media/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugifyPageSlug } from "../lib/
|
|
1
|
+
import { slugifyPageSlug } from "../lib/slug";
|
|
2
2
|
import type { MediaItem } from "./types";
|
|
3
3
|
|
|
4
4
|
export async function hashFileBuffer(buffer: ArrayBuffer): Promise<string> {
|
|
@@ -48,6 +48,24 @@ export function mimeToExt(mime: string): string {
|
|
|
48
48
|
return MIME_TO_EXT[mime] ?? "bin";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Editor preview URL for a library item, matching what /api/media/[id] serves:
|
|
52
|
+
// SVGs carry kind "image" but have no webp variants, only original.svg; rasters
|
|
53
|
+
// get their smallest variant; animated and video items get their playable
|
|
54
|
+
// original (a <video> src, not a still). Null for a raster with no variants.
|
|
55
|
+
export function mediaThumbnailUrl(item: MediaItem): string | null {
|
|
56
|
+
if (item.mimeType === "image/svg+xml" || item.kind !== "image") {
|
|
57
|
+
return `/api/media/${item.id}/original.${mimeToExt(item.mimeType)}`;
|
|
58
|
+
}
|
|
59
|
+
if (item.variants.length === 0) return null;
|
|
60
|
+
const smallest = item.variants.reduce((a, b) => (a.width < b.width ? a : b));
|
|
61
|
+
return `/api/media/${item.id}/${smallest.width}.webp`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// For <img>-only previews (cover, logo): null unless the item is a still image.
|
|
65
|
+
export function imageThumbnailUrl(item: MediaItem | undefined): string | null {
|
|
66
|
+
return item?.kind === "image" ? mediaThumbnailUrl(item) : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
export function displayFilenameExt(mime: string): string {
|
|
52
70
|
const ext = MIME_TO_EXT[mime];
|
|
53
71
|
return ext ? `.${ext}` : "";
|
|
@@ -26,7 +26,7 @@ export function generateVideoPoster(
|
|
|
26
26
|
}, 10_000);
|
|
27
27
|
|
|
28
28
|
video.addEventListener("error", () => {
|
|
29
|
-
safeReject(new Error("
|
|
29
|
+
safeReject(new Error("Couldn't load video for poster generation"));
|
|
30
30
|
}, { once: true });
|
|
31
31
|
|
|
32
32
|
video.muted = true;
|
|
@@ -56,7 +56,7 @@ export function generateVideoPoster(
|
|
|
56
56
|
settled = true;
|
|
57
57
|
cleanup();
|
|
58
58
|
if (!posterBlob) {
|
|
59
|
-
reject(new Error("
|
|
59
|
+
reject(new Error("Couldn't create poster blob"));
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
resolve({ posterBlob, width: w, height: h, duration: video.duration });
|