canopycms 0.0.68-int.98 → 0.1.0
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/CHANGELOG.md +2131 -0
- package/README.md +47 -63
- package/dist/ai/generate.d.ts +5 -0
- package/dist/ai/generate.js +244 -115
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/json-to-markdown.d.ts +21 -1
- package/dist/ai/json-to-markdown.js +120 -57
- package/dist/ai/references.d.ts +72 -0
- package/dist/ai/references.js +223 -0
- package/dist/ai/resolve-branch.js +9 -2
- package/dist/ai/to-plain-text.d.ts +17 -8
- package/dist/ai/to-plain-text.js +18 -15
- package/dist/ai/types.d.ts +84 -3
- package/dist/api/admin-branch-health.d.ts +24 -1
- package/dist/api/admin-branch-health.js +52 -20
- package/dist/api/admin.d.ts +23 -5
- package/dist/api/admin.js +36 -1
- package/dist/api/assets.js +88 -78
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +1 -1
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +16 -7
- package/dist/api/branch-status.d.ts +16 -6
- package/dist/api/branch-status.js +57 -12
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +8 -3
- package/dist/api/branch.d.ts +25 -12
- package/dist/api/branch.js +164 -103
- package/dist/api/client.d.ts +78 -58
- package/dist/api/client.js +150 -117
- package/dist/api/comments.d.ts +12 -3
- package/dist/api/comments.js +118 -9
- package/dist/api/content.d.ts +9 -9
- package/dist/api/content.js +131 -22
- package/dist/api/entries-constants.d.ts +14 -3
- package/dist/api/entries-constants.js +14 -3
- package/dist/api/entries.d.ts +40 -2
- package/dist/api/entries.js +102 -6
- package/dist/api/github-sync.d.ts +19 -0
- package/dist/api/github-sync.js +50 -3
- package/dist/api/groups.d.ts +4 -4
- package/dist/api/groups.js +2 -1
- package/dist/api/guards.js +23 -3
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +1 -0
- package/dist/api/permissions.d.ts +21 -2
- package/dist/api/permissions.js +50 -3
- package/dist/api/request-deadline.d.ts +59 -0
- package/dist/api/request-deadline.js +73 -0
- package/dist/api/resolve-references.js +8 -7
- package/dist/api/route-builder.d.ts +14 -0
- package/dist/api/route-builder.js +14 -1
- package/dist/api/schema.d.ts +11 -9
- package/dist/api/schema.js +2 -0
- package/dist/api/settings-helpers.d.ts +5 -1
- package/dist/api/settings-helpers.js +10 -2
- package/dist/api/types.d.ts +18 -0
- package/dist/api/users-constants.d.ts +6 -0
- package/dist/api/users-constants.js +6 -0
- package/dist/assets/asset-url.d.ts +28 -16
- package/dist/assets/asset-url.js +51 -25
- package/dist/assets/finalize.js +3 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +153 -0
- package/dist/assets/materialize.js +402 -0
- package/dist/assets/store-local.d.ts +19 -5
- package/dist/assets/store-local.js +69 -10
- package/dist/assets/store-s3.d.ts +38 -7
- package/dist/assets/store-s3.js +128 -22
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +31 -4
- package/dist/auth/cache.d.ts +1 -1
- package/dist/auth/cache.js +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +2 -0
- package/dist/auth/caching-auth-plugin.js +12 -2
- package/dist/auth/file-based-auth-cache.d.ts +8 -0
- package/dist/auth/file-based-auth-cache.js +18 -8
- package/dist/auth/person-name.d.ts +5 -0
- package/dist/auth/person-name.js +8 -0
- package/dist/auth/plugin.d.ts +10 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/auth/user-metadata-lookup.d.ts +21 -0
- package/dist/auth/user-metadata-lookup.js +116 -0
- package/dist/authorization/branch.d.ts +1 -1
- package/dist/authorization/branch.js +12 -13
- package/dist/authorization/groups/schema.d.ts +4 -4
- package/dist/authorization/helpers.d.ts +8 -0
- package/dist/authorization/helpers.js +8 -0
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/protected-branch.d.ts +5 -4
- package/dist/authorization/protected-branch.js +7 -5
- package/dist/branch-health.d.ts +35 -8
- package/dist/branch-health.js +54 -18
- package/dist/branch-metadata-error.d.ts +27 -0
- package/dist/branch-metadata-error.js +23 -0
- package/dist/branch-metadata-file.d.ts +205 -24
- package/dist/branch-metadata-file.js +126 -25
- package/dist/branch-metadata.d.ts +45 -6
- package/dist/branch-metadata.js +142 -50
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-registry.d.ts +8 -0
- package/dist/branch-registry.js +61 -9
- package/dist/branch-schema-cache.d.ts +29 -12
- package/dist/branch-schema-cache.js +64 -21
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +239 -100
- package/dist/build/asset-refs.d.ts +94 -0
- package/dist/build/asset-refs.js +267 -0
- package/dist/build/generate-ai-content.js +5 -0
- package/dist/build/inline-flight.d.ts +37 -0
- package/dist/build/inline-flight.js +142 -0
- package/dist/cli/asset-refs.d.ts +52 -0
- package/dist/cli/asset-refs.js +194 -0
- package/dist/cli/cli.d.ts +15 -1
- package/dist/cli/cli.js +8543 -3193
- package/dist/cli/configured-asset-store.d.ts +12 -0
- package/dist/cli/configured-asset-store.js +46 -0
- package/dist/cli/generate-ai-content.js +4244 -1072
- package/dist/cli/init.js +217 -126
- package/dist/cli/project-detect.d.ts +2 -0
- package/dist/cli/project-detect.js +2 -0
- package/dist/cli/template-files/Dockerfile.cms.template +13 -5
- package/dist/cli/template-files/cms-stack.ts.template +16 -13
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/comment-store.d.ts +25 -0
- package/dist/comment-store.js +43 -8
- package/dist/config/flatten.js +2 -0
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schemas/config.d.ts +12 -29
- package/dist/config/schemas/config.js +9 -12
- package/dist/config/schemas/field.d.ts +3 -0
- package/dist/config/schemas/field.js +58 -4
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +2 -20
- package/dist/config/schemas/url.js +2 -25
- package/dist/config/types.d.ts +116 -22
- package/dist/config/types.js +3 -9
- package/dist/config/validation.d.ts +25 -4
- package/dist/config/validation.js +116 -3
- package/dist/content-id-index.js +2 -2
- package/dist/content-index-generation.d.ts +1 -1
- package/dist/content-index-generation.js +1 -1
- package/dist/content-listing.d.ts +10 -1
- package/dist/content-listing.js +22 -5
- package/dist/content-reader.d.ts +9 -0
- package/dist/content-reader.js +16 -11
- package/dist/content-store.d.ts +75 -12
- package/dist/content-store.js +249 -86
- package/dist/content-tree.js +2 -2
- package/dist/context.js +5 -2
- package/dist/dangling-reference-log.d.ts +17 -0
- package/dist/dangling-reference-log.js +29 -0
- package/dist/dev-content-watcher.d.ts +1 -1
- package/dist/dev-content-watcher.js +1 -1
- package/dist/editor/BranchManager.d.ts +23 -7
- package/dist/editor/BranchManager.js +119 -54
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +1 -2
- package/dist/editor/CommentsPanel.d.ts +14 -0
- package/dist/editor/CommentsPanel.js +68 -43
- package/dist/editor/Editor.d.ts +11 -5
- package/dist/editor/Editor.js +891 -218
- package/dist/editor/EditorAuthGate.d.ts +7 -1
- package/dist/editor/EditorAuthGate.js +41 -9
- package/dist/editor/EntryNavigator.d.ts +22 -0
- package/dist/editor/EntryNavigator.js +353 -105
- package/dist/editor/FormHeader.d.ts +25 -0
- package/dist/editor/FormHeader.js +82 -0
- package/dist/editor/FormRenderer.d.ts +27 -3
- package/dist/editor/FormRenderer.js +171 -76
- package/dist/editor/PreviewFrame.d.ts +40 -0
- package/dist/editor/PreviewFrame.js +215 -0
- package/dist/editor/PreviewPane.d.ts +47 -0
- package/dist/editor/PreviewPane.js +147 -0
- package/dist/editor/PreviewToolbarTools.d.ts +22 -0
- package/dist/editor/PreviewToolbarTools.js +73 -0
- package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
- package/dist/editor/admin/SystemHealthPanel.js +163 -33
- package/dist/editor/admin/useSystemHealth.d.ts +18 -0
- package/dist/editor/admin/useSystemHealth.js +70 -1
- package/dist/editor/branch-status.d.ts +17 -0
- package/dist/editor/branch-status.js +29 -0
- package/dist/editor/canopy-path.d.ts +9 -6
- package/dist/editor/canopy-path.js +8 -11
- package/dist/editor/client-reference-resolver.d.ts +43 -16
- package/dist/editor/client-reference-resolver.js +155 -64
- package/dist/editor/comments/BranchComments.d.ts +1 -1
- package/dist/editor/comments/BranchComments.js +2 -2
- package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
- package/dist/editor/comments/CloseRefusedNotice.js +7 -0
- package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
- package/dist/editor/comments/CommentThreadsPopover.js +274 -0
- package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
- package/dist/editor/comments/CommentWriteAlert.js +13 -0
- package/dist/editor/comments/EntryComments.d.ts +10 -4
- package/dist/editor/comments/EntryComments.js +14 -17
- package/dist/editor/comments/FieldWrapper.d.ts +18 -4
- package/dist/editor/comments/FieldWrapper.js +62 -28
- package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
- package/dist/editor/comments/InlineCommentThread.js +11 -47
- package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
- package/dist/editor/comments/ThreadCarousel.js +18 -21
- package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
- package/dist/editor/comments/ThreadReplyControls.js +92 -0
- package/dist/editor/comments/field-comment-context.d.ts +14 -0
- package/dist/editor/comments/field-comment-context.js +14 -0
- package/dist/editor/comments/form-comments.d.ts +48 -0
- package/dist/editor/comments/form-comments.js +178 -0
- package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
- package/dist/editor/comments/item-anchor-copy.js +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
- package/dist/editor/comments/popover-placement.d.ts +18 -0
- package/dist/editor/comments/popover-placement.js +58 -0
- package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
- package/dist/editor/comments/use-tracked-writes.js +53 -0
- package/dist/editor/components/BranchAccessList.d.ts +15 -0
- package/dist/editor/components/BranchAccessList.js +52 -0
- package/dist/editor/components/BranchLockBanner.d.ts +22 -0
- package/dist/editor/components/BranchLockBanner.js +39 -0
- package/dist/editor/components/BranchPill.d.ts +32 -0
- package/dist/editor/components/BranchPill.js +33 -0
- package/dist/editor/components/BranchesDrawer.d.ts +11 -0
- package/dist/editor/components/BranchesDrawer.js +5 -0
- package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
- package/dist/editor/components/ConfirmDeleteModal.js +3 -2
- package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
- package/dist/editor/components/DestructiveMenuItem.js +8 -0
- package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
- package/dist/editor/components/EditorCrashScreen.js +22 -0
- package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
- package/dist/editor/components/EditorErrorBoundary.js +40 -0
- package/dist/editor/components/EditorHeader.d.ts +56 -106
- package/dist/editor/components/EditorHeader.js +130 -131
- package/dist/editor/components/EditorRail.d.ts +49 -0
- package/dist/editor/components/EditorRail.js +65 -0
- package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
- package/dist/editor/components/EntryBreadcrumb.js +56 -0
- package/dist/editor/components/EntryCreateModal.d.ts +12 -7
- package/dist/editor/components/EntryCreateModal.js +51 -57
- package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
- package/dist/editor/components/NoEditPermissionNotice.js +5 -4
- package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
- package/dist/editor/components/QuickOpenPalette.js +66 -0
- package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
- package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
- package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
- package/dist/editor/components/ReasonedMenuItem.js +15 -0
- package/dist/editor/components/ReferencedByList.d.ts +11 -0
- package/dist/editor/components/ReferencedByList.js +28 -0
- package/dist/editor/components/RelativeTime.d.ts +10 -0
- package/dist/editor/components/RelativeTime.js +10 -0
- package/dist/editor/components/RenameEntryModal.d.ts +9 -1
- package/dist/editor/components/RenameEntryModal.js +19 -31
- package/dist/editor/components/RetryToastMessage.d.ts +9 -0
- package/dist/editor/components/RetryToastMessage.js +6 -0
- package/dist/editor/components/SaveAllResult.d.ts +25 -0
- package/dist/editor/components/SaveAllResult.js +86 -0
- package/dist/editor/components/SaveSplitButton.d.ts +30 -0
- package/dist/editor/components/SaveSplitButton.js +108 -0
- package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
- package/dist/editor/components/StagedChangesDrawer.js +92 -0
- package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
- package/dist/editor/components/UnavailableEntryNotice.js +9 -0
- package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
- package/dist/editor/components/UnavailableTypeMessage.js +9 -0
- package/dist/editor/components/UndoToastMessage.d.ts +18 -0
- package/dist/editor/components/UndoToastMessage.js +10 -0
- package/dist/editor/components/UserBadge.d.ts +2 -5
- package/dist/editor/components/UserBadge.js +62 -33
- package/dist/editor/components/ValidationSummary.d.ts +14 -0
- package/dist/editor/components/ValidationSummary.js +24 -0
- package/dist/editor/components/index.d.ts +2 -2
- package/dist/editor/components/index.js +2 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -2
- package/dist/editor/context/ApiClientContext.js +19 -8
- package/dist/editor/context/AssetContext.d.ts +13 -35
- package/dist/editor/context/AssetContext.js +12 -31
- package/dist/editor/context/EditorStateContext.d.ts +4 -4
- package/dist/editor/context/EditorStateContext.js +4 -4
- package/dist/editor/context/SWRProvider.d.ts +3 -3
- package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
- package/dist/editor/context/SiteMdxAllowContext.js +7 -0
- package/dist/editor/context/index.d.ts +2 -1
- package/dist/editor/context/index.js +2 -1
- package/dist/editor/contrast.d.ts +19 -0
- package/dist/editor/contrast.js +41 -0
- package/dist/editor/copy.d.ts +18 -0
- package/dist/editor/copy.js +18 -0
- package/dist/editor/editor-config.js +16 -0
- package/dist/editor/editor-utils.d.ts +34 -8
- package/dist/editor/editor-utils.js +42 -20
- package/dist/editor/entry-search.d.ts +20 -0
- package/dist/editor/entry-search.js +34 -0
- package/dist/editor/field-breadcrumb.d.ts +12 -0
- package/dist/editor/field-breadcrumb.js +85 -0
- package/dist/editor/field-props.d.ts +86 -0
- package/dist/editor/field-props.js +27 -0
- package/dist/editor/field-target.d.ts +30 -0
- package/dist/editor/field-target.js +77 -0
- package/dist/editor/fields/BlockField.d.ts +12 -0
- package/dist/editor/fields/BlockField.js +89 -73
- package/dist/editor/fields/CodeField.d.ts +2 -0
- package/dist/editor/fields/CodeField.js +6 -2
- package/dist/editor/fields/DateTimeField.d.ts +4 -29
- package/dist/editor/fields/DateTimeField.js +79 -41
- package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
- package/dist/editor/fields/ExpandAllButton.js +4 -0
- package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
- package/dist/editor/fields/FieldCrashFallback.js +30 -0
- package/dist/editor/fields/FieldLabel.d.ts +37 -0
- package/dist/editor/fields/FieldLabel.js +19 -0
- package/dist/editor/fields/ImageField.d.ts +4 -0
- package/dist/editor/fields/ImageField.js +29 -14
- package/dist/editor/fields/InlineGroupField.js +4 -3
- package/dist/editor/fields/ItemList.d.ts +42 -0
- package/dist/editor/fields/ItemList.js +130 -0
- package/dist/editor/fields/MarkdownField.d.ts +32 -0
- package/dist/editor/fields/MarkdownField.js +223 -46
- package/dist/editor/fields/MdxImageDialog.js +1 -1
- package/dist/editor/fields/NumberField.d.ts +2 -0
- package/dist/editor/fields/NumberField.js +8 -5
- package/dist/editor/fields/NumberListField.d.ts +2 -0
- package/dist/editor/fields/NumberListField.js +19 -16
- package/dist/editor/fields/ObjectField.d.ts +2 -7
- package/dist/editor/fields/ObjectField.js +15 -5
- package/dist/editor/fields/ObjectListField.d.ts +29 -0
- package/dist/editor/fields/ObjectListField.js +57 -0
- package/dist/editor/fields/ReferenceField.d.ts +2 -0
- package/dist/editor/fields/ReferenceField.js +34 -9
- package/dist/editor/fields/SelectField.d.ts +2 -0
- package/dist/editor/fields/SelectField.js +11 -2
- package/dist/editor/fields/StringListField.d.ts +4 -1
- package/dist/editor/fields/StringListField.js +22 -8
- package/dist/editor/fields/TextField.d.ts +2 -0
- package/dist/editor/fields/TextField.js +6 -2
- package/dist/editor/fields/ToggleField.d.ts +2 -0
- package/dist/editor/fields/ToggleField.js +8 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
- package/dist/editor/fields/form-expansion.d.ts +9 -0
- package/dist/editor/fields/form-expansion.js +2 -0
- package/dist/editor/fields/item-expansion.d.ts +15 -0
- package/dist/editor/fields/item-expansion.js +60 -0
- package/dist/editor/fields/list-item-keys.d.ts +8 -0
- package/dist/editor/fields/list-item-keys.js +50 -0
- package/dist/editor/fields/list-item-title.d.ts +20 -0
- package/dist/editor/fields/list-item-title.js +85 -0
- package/dist/editor/fields/markdown-export-options.d.ts +2 -0
- package/dist/editor/fields/markdown-export-options.js +8 -0
- package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
- package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
- package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
- package/dist/editor/fields/mdx-jsx-support.js +157 -69
- package/dist/editor/fields/rich-text-failures.d.ts +5 -0
- package/dist/editor/fields/rich-text-failures.js +26 -0
- package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
- package/dist/editor/fields/toolbar-wrap.js +35 -0
- package/dist/editor/form-outline.d.ts +13 -0
- package/dist/editor/form-outline.js +70 -0
- package/dist/editor/group-manager/GroupCard.d.ts +4 -1
- package/dist/editor/group-manager/GroupCard.js +12 -3
- package/dist/editor/group-manager/GroupForm.js +1 -1
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
- package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
- package/dist/editor/group-manager/MemberList.d.ts +2 -1
- package/dist/editor/group-manager/MemberList.js +15 -4
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
- package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
- package/dist/editor/group-manager/index.js +34 -5
- package/dist/editor/group-manager/types.d.ts +5 -2
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +54 -0
- package/dist/editor/hooks/save-all.d.ts +68 -0
- package/dist/editor/hooks/save-all.js +61 -0
- package/dist/editor/hooks/useBranchActions.d.ts +11 -1
- package/dist/editor/hooks/useBranchActions.js +42 -19
- package/dist/editor/hooks/useBranchManager.d.ts +21 -0
- package/dist/editor/hooks/useBranchManager.js +321 -125
- package/dist/editor/hooks/useBranchesData.d.ts +8 -4
- package/dist/editor/hooks/useBranchesData.js +9 -5
- package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
- package/dist/editor/hooks/useCommentSystem.js +79 -55
- package/dist/editor/hooks/useCommentsData.d.ts +11 -7
- package/dist/editor/hooks/useCommentsData.js +20 -19
- package/dist/editor/hooks/useDraftManager.d.ts +77 -3
- package/dist/editor/hooks/useDraftManager.js +887 -94
- package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
- package/dist/editor/hooks/useEditorLayout.js +98 -20
- package/dist/editor/hooks/useEntriesData.d.ts +2 -1
- package/dist/editor/hooks/useEntriesData.js +18 -4
- package/dist/editor/hooks/useEntryManager.d.ts +41 -2
- package/dist/editor/hooks/useEntryManager.js +227 -41
- package/dist/editor/hooks/useGroupManager.js +13 -16
- package/dist/editor/hooks/usePermissionManager.js +14 -6
- package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
- package/dist/editor/hooks/usePreviewMarks.js +33 -0
- package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
- package/dist/editor/hooks/useReferenceResolution.js +34 -177
- package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
- package/dist/editor/hooks/useSchemaManager.js +97 -39
- package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
- package/dist/editor/hooks/useUserMetadata.js +76 -47
- package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
- package/dist/editor/hooks/user-metadata-batcher.js +33 -0
- package/dist/editor/media/AssetCard.d.ts +6 -7
- package/dist/editor/media/AssetCard.js +24 -31
- package/dist/editor/media/AssetListRow.d.ts +16 -0
- package/dist/editor/media/AssetListRow.js +41 -0
- package/dist/editor/media/CropStep.js +6 -1
- package/dist/editor/media/MediaLibrary.d.ts +4 -3
- package/dist/editor/media/MediaLibrary.js +11 -4
- package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
- package/dist/editor/media/MediaLibraryBody.js +195 -80
- package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
- package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +8 -0
- package/dist/editor/media/editor-image-src.js +10 -0
- package/dist/editor/media/format-bytes.d.ts +2 -0
- package/dist/editor/media/format-bytes.js +23 -0
- package/dist/editor/media/media-library-prefs.d.ts +23 -0
- package/dist/editor/media/media-library-prefs.js +49 -0
- package/dist/editor/media/media-library-view.d.ts +34 -0
- package/dist/editor/media/media-library-view.js +40 -0
- package/dist/editor/media/useAssetLibrary.d.ts +30 -0
- package/dist/editor/media/useAssetLibrary.js +126 -0
- package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
- package/dist/editor/media/useStableAssetOrder.js +37 -0
- package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
- package/dist/editor/media/useThumbnailFailure.js +16 -0
- package/dist/editor/permission-manager/GroupSelector.js +2 -2
- package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
- package/dist/editor/permission-manager/PermissionEditor.js +15 -4
- package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
- package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.js +3 -3
- package/dist/editor/permission-manager/index.js +12 -3
- package/dist/editor/permission-manager/types.d.ts +5 -2
- package/dist/editor/platform.d.ts +3 -0
- package/dist/editor/platform.js +9 -0
- package/dist/editor/preview-asset-base.d.ts +9 -0
- package/dist/editor/preview-asset-base.js +17 -0
- package/dist/editor/preview-bridge.d.ts +51 -27
- package/dist/editor/preview-bridge.js +103 -144
- package/dist/editor/preview-marks.d.ts +15 -0
- package/dist/editor/preview-marks.js +74 -0
- package/dist/editor/preview-path.d.ts +12 -0
- package/dist/editor/preview-path.js +45 -1
- package/dist/editor/preview-scale.d.ts +27 -0
- package/dist/editor/preview-scale.js +34 -0
- package/dist/editor/raw-asset-base.d.ts +5 -0
- package/dist/editor/raw-asset-base.js +14 -0
- package/dist/editor/relative-time.d.ts +4 -4
- package/dist/editor/relative-time.js +16 -8
- package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
- package/dist/editor/schema-editor/CollectionEditor.js +361 -62
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
- package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
- package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
- package/dist/editor/schema-editor/collection-draft.js +40 -0
- package/dist/editor/schema-editor/index.d.ts +1 -1
- package/dist/editor/schema-label.d.ts +10 -0
- package/dist/editor/schema-label.js +17 -0
- package/dist/editor/settings-conflict.d.ts +5 -0
- package/dist/editor/settings-conflict.js +8 -0
- package/dist/editor/shell/ActivityRail.d.ts +37 -0
- package/dist/editor/shell/ActivityRail.js +91 -0
- package/dist/editor/shell/EditorShell.d.ts +17 -0
- package/dist/editor/shell/EditorShell.js +31 -0
- package/dist/editor/shell/PaneLayout.d.ts +28 -0
- package/dist/editor/shell/PaneLayout.js +116 -0
- package/dist/editor/shell/SidePanelHost.d.ts +35 -0
- package/dist/editor/shell/SidePanelHost.js +184 -0
- package/dist/editor/shell/TopBar.d.ts +12 -0
- package/dist/editor/shell/TopBar.js +14 -0
- package/dist/editor/shell/host-chrome.d.ts +16 -0
- package/dist/editor/shell/host-chrome.js +69 -0
- package/dist/editor/slug.d.ts +16 -0
- package/dist/editor/slug.js +43 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.js +314 -13
- package/dist/editor/unanswered.d.ts +17 -0
- package/dist/editor/unanswered.js +26 -0
- package/dist/editor/unavailable-entry-type.d.ts +20 -0
- package/dist/editor/unavailable-entry-type.js +28 -0
- package/dist/editor/undo-removal.d.ts +57 -0
- package/dist/editor/undo-removal.js +163 -0
- package/dist/editor/user-display.d.ts +4 -0
- package/dist/editor/user-display.js +5 -0
- package/dist/editor/utils/confirm-modal.d.ts +8 -0
- package/dist/editor/utils/confirm-modal.js +12 -0
- package/dist/editor/utils/editor-errors.d.ts +19 -0
- package/dist/editor/utils/editor-errors.js +32 -0
- package/dist/editor/utils/focus-return.d.ts +56 -0
- package/dist/editor/utils/focus-return.js +106 -0
- package/dist/entry-link-resolver.d.ts +11 -1
- package/dist/entry-link-resolver.js +65 -5
- package/dist/entry-schema-registry.d.ts +3 -3
- package/dist/entry-schema-registry.js +7 -4
- package/dist/entry-schema.d.ts +49 -3
- package/dist/entry-schema.js +18 -0
- package/dist/git-manager.d.ts +172 -10
- package/dist/git-manager.js +449 -107
- package/dist/github-service.d.ts +39 -2
- package/dist/github-service.js +101 -7
- package/dist/http/handler.js +74 -9
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +9 -4
- package/dist/http/worker-not-ready.js +59 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/item-keys.d.ts +46 -0
- package/dist/item-keys.js +235 -0
- package/dist/operating-mode/editor-mode-check.d.ts +15 -0
- package/dist/operating-mode/editor-mode-check.js +24 -0
- package/dist/operating-mode/mode-env.d.ts +13 -14
- package/dist/operating-mode/mode-env.js +13 -14
- package/dist/operating-mode/types.d.ts +2 -2
- package/dist/paths/branch.d.ts +0 -1
- package/dist/paths/branch.js +1 -8
- package/dist/paths/index.d.ts +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/validation.js +0 -2
- package/dist/preview.d.ts +8 -0
- package/dist/preview.js +7 -0
- package/dist/reference-resolver.js +1 -0
- package/dist/resolve-canopy-user.d.ts +1 -1
- package/dist/resolve-canopy-user.js +1 -1
- package/dist/schema/meta-loader.d.ts +18 -8
- package/dist/schema/meta-loader.js +42 -9
- package/dist/schema/registry-fingerprint.d.ts +14 -0
- package/dist/schema/registry-fingerprint.js +63 -0
- package/dist/schema/resolver.d.ts +7 -4
- package/dist/schema/resolver.js +7 -6
- package/dist/schema/schema-store.d.ts +7 -10
- package/dist/schema/schema-store.js +13 -11
- package/dist/schema/schema-unavailable-error.d.ts +11 -0
- package/dist/schema/schema-unavailable-error.js +15 -0
- package/dist/schema/types.d.ts +22 -0
- package/dist/schema-registry-record.d.ts +35 -0
- package/dist/schema-registry-record.js +134 -0
- package/dist/server.d.ts +79 -9
- package/dist/server.js +62 -8
- package/dist/services.d.ts +33 -5
- package/dist/services.js +155 -37
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +159 -45
- package/dist/stamp-on-publish.d.ts +36 -0
- package/dist/stamp-on-publish.js +71 -0
- package/dist/static/index.d.ts +58 -5
- package/dist/static/index.js +133 -3
- package/dist/static/seo.d.ts +5 -5
- package/dist/static/seo.js +5 -5
- package/dist/submission-attribution.d.ts +8 -0
- package/dist/submission-attribution.js +22 -0
- package/dist/system-users.d.ts +12 -0
- package/dist/system-users.js +12 -0
- package/dist/task-queue/cms-task-queue.d.ts +1 -1
- package/dist/task-queue/cms-task-queue.js +1 -1
- package/dist/task-queue/index.d.ts +1 -1
- package/dist/task-queue/index.js +1 -1
- package/dist/task-queue/task-queue.d.ts +6 -0
- package/dist/task-queue/task-queue.js +29 -0
- package/dist/task-queue/worker-status.d.ts +35 -9
- package/dist/task-queue/worker-status.js +84 -17
- package/dist/types.d.ts +90 -9
- package/dist/url-collision.js +3 -3
- package/dist/url-exclusivity-fixtures.d.ts +8 -1
- package/dist/url-exclusivity-fixtures.js +11 -1
- package/dist/user.js +2 -1
- package/dist/utils/atomic-write.js +2 -1
- package/dist/utils/base-branch.d.ts +13 -0
- package/dist/utils/base-branch.js +23 -0
- package/dist/utils/content-serialize.d.ts +15 -7
- package/dist/utils/content-serialize.js +353 -51
- package/dist/utils/content-write-lock.d.ts +6 -1
- package/dist/utils/content-write-lock.js +21 -6
- package/dist/utils/debug.d.ts +1 -1
- package/dist/utils/debug.js +5 -3
- package/dist/utils/error.js +13 -2
- package/dist/utils/escape-text.d.ts +7 -0
- package/dist/utils/escape-text.js +27 -0
- package/dist/utils/fs.d.ts +12 -4
- package/dist/utils/fs.js +18 -5
- package/dist/utils/git.d.ts +21 -1
- package/dist/utils/git.js +63 -8
- package/dist/utils/json-source-splice.d.ts +14 -0
- package/dist/utils/json-source-splice.js +120 -0
- package/dist/utils/markdown-body-splice.d.ts +39 -0
- package/dist/utils/markdown-body-splice.js +421 -0
- package/dist/utils/occ-json-write.js +18 -12
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +32 -1
- package/dist/utils/provisioning-lock.js +78 -32
- package/dist/utils/sanitize-href.d.ts +9 -23
- package/dist/utils/sanitize-href.js +14 -28
- package/dist/utils/title-field.d.ts +10 -0
- package/dist/utils/title-field.js +47 -0
- package/dist/utils/yaml-source-splice.d.ts +58 -0
- package/dist/utils/yaml-source-splice.js +515 -0
- package/dist/validation/block-structural-keys.d.ts +1 -1
- package/dist/validation/block-structural-keys.js +1 -1
- package/dist/validation/deletion-checker.d.ts +21 -29
- package/dist/validation/deletion-checker.js +41 -117
- package/dist/validation/entry-validator.d.ts +6 -10
- package/dist/validation/entry-validator.js +19 -26
- package/dist/validation/field-traversal.d.ts +22 -0
- package/dist/validation/field-traversal.js +39 -8
- package/dist/validation/markdown-safety.d.ts +68 -0
- package/dist/validation/markdown-safety.js +596 -0
- package/dist/validation/mdx-allowlist.d.ts +55 -0
- package/dist/validation/mdx-allowlist.js +183 -0
- package/dist/validation/reference-validator.d.ts +2 -0
- package/dist/validation/reference-validator.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-policy.d.ts +124 -0
- package/dist/worker/branch-policy.js +243 -0
- package/dist/worker/bundle-header.d.ts +117 -0
- package/dist/worker/bundle-header.js +253 -0
- package/dist/worker/cms-worker.d.ts +236 -142
- package/dist/worker/cms-worker.js +772 -285
- package/dist/worker/gateway-egress.d.ts +43 -0
- package/dist/worker/gateway-egress.js +209 -0
- package/dist/worker/gateway-instance.d.ts +42 -0
- package/dist/worker/gateway-instance.js +129 -0
- package/dist/worker/git-process.d.ts +81 -0
- package/dist/worker/git-process.js +234 -0
- package/dist/worker/git-sync.d.ts +34 -17
- package/dist/worker/git-sync.js +394 -131
- package/dist/worker/github-auth.d.ts +9 -9
- package/dist/worker/github-auth.js +13 -15
- package/dist/worker/github-gateway-http.d.ts +62 -0
- package/dist/worker/github-gateway-http.js +977 -0
- package/dist/worker/github-gateway-wire.d.ts +590 -0
- package/dist/worker/github-gateway-wire.js +488 -0
- package/dist/worker/github-gateway.d.ts +238 -0
- package/dist/worker/github-gateway.js +726 -0
- package/dist/worker/github-mirror-upkeep.d.ts +75 -0
- package/dist/worker/github-mirror-upkeep.js +266 -0
- package/dist/worker/github-mirror.d.ts +364 -0
- package/dist/worker/github-mirror.js +1030 -0
- package/dist/worker/github-transfer.d.ts +56 -0
- package/dist/worker/github-transfer.js +444 -0
- package/dist/worker/history-rewrite.d.ts +4 -5
- package/dist/worker/history-rewrite.js +12 -17
- package/dist/worker/provisioned-workspace.d.ts +5 -5
- package/dist/worker/provisioned-workspace.js +5 -5
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +58 -54
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +12 -0
- package/dist/worker/schema-gate.d.ts +49 -0
- package/dist/worker/schema-gate.js +226 -0
- package/dist/worker/self-checks.d.ts +107 -0
- package/dist/worker/self-checks.js +358 -0
- package/dist/worker/shared-repo-git.d.ts +96 -0
- package/dist/worker/shared-repo-git.js +459 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +136 -0
- package/dist/worker/stale-mirror.d.ts +82 -0
- package/dist/worker/stale-mirror.js +130 -0
- package/dist/worker/task-runner.d.ts +5 -3
- package/dist/worker/task-runner.js +287 -186
- package/dist/worker/worker-context.d.ts +36 -37
- package/package.json +35 -7
- package/dist/editor/EditorPanes.d.ts +0 -10
- package/dist/editor/EditorPanes.js +0 -98
- package/dist/editor/components/EditorFooter.d.ts +0 -11
- package/dist/editor/components/EditorFooter.js +0 -9
- package/dist/editor/components/EditorSidebar.d.ts +0 -67
- package/dist/editor/components/EditorSidebar.js +0 -22
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs as _jsxs,
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
-
import { Alert, Button,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { ActionIcon, Alert, Button, Center, Group, Progress, ScrollArea, SegmentedControl, Select, SimpleGrid, Stack, Text, TextInput, Tooltip, VisuallyHidden, } from '@mantine/core';
|
|
5
5
|
import { Dropzone } from '@mantine/dropzone';
|
|
6
|
-
import {
|
|
7
|
-
import { IconAlertCircle, IconUpload } from '@tabler/icons-react';
|
|
6
|
+
import { openConfirm } from '../utils/confirm-modal.js';
|
|
7
|
+
import { IconAlertCircle, IconGridDots, IconLayoutGrid, IconList, IconSearch, IconUpload, IconX, } from '@tabler/icons-react';
|
|
8
8
|
import { getErrorMessage } from '../../utils/error.js';
|
|
9
|
+
import { isNonApiResponse } from '../../api/client.js';
|
|
9
10
|
// Import directly from helpers (not the authorization barrel) to keep server-only
|
|
10
11
|
// authorization code out of this client bundle - matches BranchManager's convention.
|
|
11
12
|
import { isAdmin } from '../../authorization/helpers.js';
|
|
12
13
|
import { useApiClient, useAssetContext } from '../context/index.js';
|
|
13
14
|
import { useUserContext } from '../hooks/index.js';
|
|
14
15
|
import { AssetCard } from './AssetCard.js';
|
|
16
|
+
import { AssetListRow } from './AssetListRow.js';
|
|
17
|
+
import { MediaLibrarySkeleton } from './MediaLibrarySkeleton.js';
|
|
18
|
+
import { useAssetLibrary } from './useAssetLibrary.js';
|
|
19
|
+
import { useStableAssetOrder } from './useStableAssetOrder.js';
|
|
15
20
|
import { useAssetUpload } from './useAssetUpload.js';
|
|
21
|
+
import { useMediaLibraryPrefs } from './media-library-prefs.js';
|
|
22
|
+
import { GRID_COLUMNS, fileCountLabel, matchesKind, matchesText, } from './media-library-view.js';
|
|
16
23
|
import { ACCEPTED_ASSET_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES, MAX_UPLOAD_BYTES, } from './upload-constants.js';
|
|
17
|
-
|
|
24
|
+
/** How many assets render at first, and how many each reveal adds. All of them load regardless. */
|
|
25
|
+
const RENDER_WINDOW = 40;
|
|
26
|
+
const SORT_OPTIONS = [
|
|
27
|
+
{ value: 'newest', label: 'Newest first' },
|
|
28
|
+
{ value: 'name', label: 'Name (A–Z)' },
|
|
29
|
+
];
|
|
30
|
+
const KIND_OPTIONS = [
|
|
31
|
+
{ value: 'all', label: 'All' },
|
|
32
|
+
{ value: 'images', label: 'Images' },
|
|
33
|
+
{ value: 'pdfs', label: 'PDFs' },
|
|
34
|
+
];
|
|
35
|
+
/** An icon-only radio segment: the visible icon, a tooltip, and hidden text for the radio's name. */
|
|
36
|
+
const DensityLabel = ({ icon, text }) => (_jsx(Tooltip, { label: text, children: _jsxs(Center, { children: [icon, _jsx(VisuallyHidden, { children: text })] }) }));
|
|
37
|
+
const DENSITY_OPTIONS = [
|
|
38
|
+
{ value: 'grid', label: _jsx(DensityLabel, { icon: _jsx(IconLayoutGrid, { size: 16 }), text: "Grid" }) },
|
|
39
|
+
{
|
|
40
|
+
value: 'compact',
|
|
41
|
+
label: _jsx(DensityLabel, { icon: _jsx(IconGridDots, { size: 16 }), text: "Compact grid" }),
|
|
42
|
+
},
|
|
43
|
+
{ value: 'list', label: _jsx(DensityLabel, { icon: _jsx(IconList, { size: 16 }), text: "List" }) },
|
|
44
|
+
];
|
|
18
45
|
/**
|
|
19
|
-
* Shared grid/upload/
|
|
46
|
+
* Shared grid/list/upload/search/delete core for both MediaLibrary
|
|
20
47
|
* presentations (Drawer "manage" mode, Modal "picker" mode) - see
|
|
21
48
|
* MediaLibrary.tsx for the two wrappers.
|
|
22
49
|
*/
|
|
@@ -40,102 +67,190 @@ export const MediaLibraryBody = ({ opened, mode, onSelect }) => {
|
|
|
40
67
|
// (see MediaLibraryProps.mode's doc comment) - both image-only contexts.
|
|
41
68
|
// `manage` mode (the Editor's Media Library drawer) keeps accepting PDFs.
|
|
42
69
|
const imageOnly = mode === 'picker';
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const [
|
|
46
|
-
const [loadingMore, setLoadingMore] = useState(false);
|
|
47
|
-
const [listError, setListError] = useState(null);
|
|
70
|
+
const library = useAssetLibrary(opened);
|
|
71
|
+
const { sort, density, setSort, setDensity } = useMediaLibraryPrefs();
|
|
72
|
+
const [kind, setKind] = useState('all');
|
|
48
73
|
const [filterText, setFilterText] = useState('');
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
setAssets(result.data.assets);
|
|
59
|
-
setNextCursor(result.data.nextCursor);
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
setListError(getErrorMessage(err));
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
setLoading(false);
|
|
66
|
-
}
|
|
67
|
-
}, [client]);
|
|
74
|
+
const [actionError, setActionError] = useState(null);
|
|
75
|
+
// The result of the last remove, for the live region; the removed card itself is gone.
|
|
76
|
+
// `id` re-keys the region's text, so a second remove with the same words is announced too.
|
|
77
|
+
const [actionNotice, setActionNotice] = useState(null);
|
|
78
|
+
const searchRef = useRef(null);
|
|
79
|
+
// A removed card takes its focused button with it, so focus moves to the search field once the
|
|
80
|
+
// removal has rendered (Mantine returns focus to the trash button when the confirm closes).
|
|
81
|
+
const focusAfterRemove = useRef(false);
|
|
68
82
|
useEffect(() => {
|
|
69
|
-
if (!
|
|
83
|
+
if (!focusAfterRemove.current)
|
|
70
84
|
return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return;
|
|
79
|
-
setLoadingMore(true);
|
|
80
|
-
setListError(null);
|
|
81
|
-
try {
|
|
82
|
-
const result = await client.assets.list({ cursor: nextCursor, limit: String(PAGE_SIZE) });
|
|
83
|
-
if (!result.ok || !result.data) {
|
|
84
|
-
setListError(result.error || 'Failed to load more assets');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const page = result.data;
|
|
88
|
-
setAssets((prev) => [...prev, ...page.assets]);
|
|
89
|
-
setNextCursor(page.nextCursor);
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
setListError(getErrorMessage(err));
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
setLoadingMore(false);
|
|
96
|
-
}
|
|
85
|
+
focusAfterRemove.current = false;
|
|
86
|
+
searchRef.current?.focus();
|
|
87
|
+
});
|
|
88
|
+
// Clearing hides the control that did it, so focus moves to the search field.
|
|
89
|
+
const clearSearch = () => {
|
|
90
|
+
setFilterText('');
|
|
91
|
+
searchRef.current?.focus();
|
|
97
92
|
};
|
|
93
|
+
// This opening's uploads, newest first; they lead the list whatever the sort.
|
|
94
|
+
const [pinned, setPinned] = useState([]);
|
|
95
|
+
// The render window resets whenever the list it windows changes shape. Adjusting it during
|
|
96
|
+
// render, not in an effect, avoids a frame with the old count.
|
|
97
|
+
const windowKey = `${filterText}|${sort}|${kind}|${density}`;
|
|
98
|
+
const [windowState, setWindowState] = useState({ key: windowKey, count: RENDER_WINDOW });
|
|
99
|
+
if (windowState.key !== windowKey)
|
|
100
|
+
setWindowState({ key: windowKey, count: RENDER_WINDOW });
|
|
101
|
+
const visibleCount = windowState.key === windowKey ? windowState.count : RENDER_WINDOW;
|
|
102
|
+
const showMore = useCallback(() => setWindowState({ key: windowKey, count: visibleCount + RENDER_WINDOW }), [windowKey, visibleCount]);
|
|
103
|
+
const needle = filterText.trim().toLowerCase();
|
|
104
|
+
const effectiveKind = imageOnly ? 'images' : kind;
|
|
105
|
+
// Picker mode is image-only (see `imageOnly` above) - pdf assets are
|
|
106
|
+
// excluded from the pickable list entirely, not just the upload accept
|
|
107
|
+
// list, since an already-uploaded pdf could otherwise still be selected.
|
|
108
|
+
const scopedAssets = useMemo(() => (imageOnly ? library.assets.filter((asset) => asset.kind !== 'pdf') : library.assets), [imageOnly, library.assets]);
|
|
109
|
+
const kindAssets = useMemo(() => scopedAssets.filter((asset) => matchesKind(asset, effectiveKind)), [scopedAssets, effectiveKind]);
|
|
110
|
+
const orderedAssets = useStableAssetOrder(scopedAssets, sort, library.complete, pinned);
|
|
111
|
+
const matchedAssets = useMemo(() => orderedAssets.filter((asset) => matchesKind(asset, effectiveKind) && matchesText(asset, needle)), [orderedAssets, effectiveKind, needle]);
|
|
112
|
+
const visibleAssets = matchedAssets.slice(0, visibleCount);
|
|
113
|
+
const hasMore = matchedAssets.length > visibleAssets.length;
|
|
114
|
+
const [sentinel, setSentinel] = useState(null);
|
|
115
|
+
// The scroll viewport is the observer's root: a margin on the default root would only widen
|
|
116
|
+
// the document viewport, not the ScrollArea that clips the sentinel.
|
|
117
|
+
const [viewport, setViewport] = useState(null);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!sentinel || !hasMore || typeof IntersectionObserver === 'undefined')
|
|
120
|
+
return;
|
|
121
|
+
const observer = new IntersectionObserver((entries) => {
|
|
122
|
+
if (entries.some((entry) => entry.isIntersecting))
|
|
123
|
+
showMore();
|
|
124
|
+
}, { root: viewport, rootMargin: '200px' });
|
|
125
|
+
observer.observe(sentinel);
|
|
126
|
+
return () => observer.disconnect();
|
|
127
|
+
}, [sentinel, viewport, hasMore, showMore]);
|
|
98
128
|
const handleDrop = async (files) => {
|
|
99
129
|
const file = files[0];
|
|
100
130
|
if (!file)
|
|
101
131
|
return;
|
|
132
|
+
setActionError(null);
|
|
102
133
|
const asset = await upload.upload(file);
|
|
103
|
-
if (asset)
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
if (!asset)
|
|
135
|
+
return;
|
|
136
|
+
library.prepend(asset);
|
|
137
|
+
setPinned((prev) => [asset.hash32, ...prev.filter((hash) => hash !== asset.hash32)]);
|
|
138
|
+
// The upload must be visible: a search or kind filter that would hide it is cleared.
|
|
139
|
+
setFilterText((prev) => (matchesText(asset, prev.trim().toLowerCase()) ? prev : ''));
|
|
140
|
+
setKind((prev) => (matchesKind(asset, prev) ? prev : 'all'));
|
|
106
141
|
};
|
|
107
142
|
const handleReject = (rejections) => {
|
|
108
|
-
|
|
143
|
+
setActionError(rejections[0]?.errors[0]?.message ?? 'The file was rejected.');
|
|
109
144
|
};
|
|
110
145
|
const handleDelete = async (asset) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
setActionError(null);
|
|
147
|
+
const failure = (detail) => `Couldn't remove ${asset.filename} from the library. ${detail}`;
|
|
148
|
+
// The raw server message is a diagnostic, shown to admins only.
|
|
149
|
+
const generic = (serverMessage) => failure((isAdmin(userContext?.groups) && serverMessage) || 'Try again.');
|
|
150
|
+
try {
|
|
151
|
+
const result = await client.assets.delete({ key: asset.hash32 });
|
|
152
|
+
if (!result.ok) {
|
|
153
|
+
// An editor sees this button only for their own upload, and the endpoint answers a
|
|
154
|
+
// missing file with the same 403 as someone else's, so their 403 means it is gone.
|
|
155
|
+
const alreadyGone = result.status === 403 && !isNonApiResponse(result) && !isAdmin(userContext?.groups);
|
|
156
|
+
setActionError(alreadyGone ? failure('It may have been removed already.') : generic(result.error));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
library.remove(asset.hash32);
|
|
160
|
+
setActionNotice((prev) => ({
|
|
161
|
+
id: (prev?.id ?? 0) + 1,
|
|
162
|
+
text: `Removed ${asset.filename} from library`,
|
|
163
|
+
}));
|
|
164
|
+
focusAfterRemove.current = true;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
setActionError(generic(getErrorMessage(err)));
|
|
115
168
|
}
|
|
116
|
-
setAssets((prev) => prev.filter((a) => a.hash32 !== asset.hash32));
|
|
117
169
|
};
|
|
118
170
|
const handleDeleteClick = (asset) => {
|
|
119
|
-
|
|
171
|
+
openConfirm({
|
|
120
172
|
title: 'Remove from library',
|
|
121
|
-
children: (_jsxs(Text, { size: "sm", children: ["Remove \u201C", asset.filename, "\u201D from the media library?
|
|
122
|
-
labels: { confirm: 'Remove', cancel: 'Cancel' },
|
|
173
|
+
children: (_jsxs(Text, { size: "sm", children: ["Remove \u201C", asset.filename, "\u201D from the media library? Content that already uses this file keeps working, and the file itself is not deleted."] })),
|
|
174
|
+
labels: { confirm: 'Remove from library', cancel: 'Cancel' },
|
|
123
175
|
confirmProps: { color: 'red' },
|
|
124
176
|
onConfirm: () => {
|
|
125
177
|
void handleDelete(asset);
|
|
126
178
|
},
|
|
127
179
|
});
|
|
128
180
|
};
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
181
|
+
// `text` is shown; `announce` is the live-region wording, which changes only between phases so
|
|
182
|
+
// a screen reader is not told every page's count.
|
|
183
|
+
const loadStatus = (() => {
|
|
184
|
+
if (library.error || !library.firstPageLoaded)
|
|
185
|
+
return null;
|
|
186
|
+
if (!library.complete) {
|
|
187
|
+
return needle
|
|
188
|
+
? {
|
|
189
|
+
text: `Searching ${fileCountLabel(kindAssets.length)} loaded so far…`,
|
|
190
|
+
announce: 'Searching the files loaded so far…',
|
|
191
|
+
}
|
|
192
|
+
: {
|
|
193
|
+
text: `Loaded ${fileCountLabel(kindAssets.length)}, loading more…`,
|
|
194
|
+
announce: 'Loading more files…',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// With no match, the empty state below says so.
|
|
198
|
+
if (needle && matchedAssets.length > 0) {
|
|
199
|
+
const total = kindAssets.length;
|
|
200
|
+
const text = `${matchedAssets.length} of ${total} ${total === 1 ? 'file matches' : 'files match'}`;
|
|
201
|
+
return { text, announce: text };
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
})();
|
|
205
|
+
const kindNoun = effectiveKind === 'pdfs' ? 'PDFs' : effectiveKind === 'images' ? 'images' : 'files';
|
|
206
|
+
const libraryEmpty = library.complete && scopedAssets.length === 0;
|
|
207
|
+
const emptyState = (() => {
|
|
208
|
+
if (matchedAssets.length > 0)
|
|
209
|
+
return null;
|
|
210
|
+
// A later page failed: say what the loaded part holds; the alert explains the rest.
|
|
211
|
+
if (library.error && !library.complete) {
|
|
212
|
+
if (!library.firstPageLoaded || kindAssets.length === 0)
|
|
213
|
+
return null;
|
|
214
|
+
return needle
|
|
215
|
+
? `No ${kindNoun} match “${filterText.trim()}” among the ${fileCountLabel(kindAssets.length)} loaded.`
|
|
216
|
+
: null;
|
|
217
|
+
}
|
|
218
|
+
if (!library.complete)
|
|
219
|
+
return null;
|
|
220
|
+
if (libraryEmpty) {
|
|
221
|
+
return imageOnly
|
|
222
|
+
? 'No images yet. Drop an image above, or click there to choose one.'
|
|
223
|
+
: 'No files yet. Drop a file above, or click there to choose one.';
|
|
224
|
+
}
|
|
225
|
+
if (needle)
|
|
226
|
+
return `No ${kindNoun} match “${filterText.trim()}”.`;
|
|
227
|
+
return `No ${kindNoun} in the library.`;
|
|
228
|
+
})();
|
|
229
|
+
const renderList = () => {
|
|
230
|
+
if (density === 'list') {
|
|
231
|
+
return (_jsx(Stack, { gap: 4, children: visibleAssets.map((asset) => (_jsx(AssetListRow, { asset: asset, baseUrl: baseUrl, onSelect: mode === 'picker' ? onSelect : undefined, onDelete: canDeleteAsset(asset) ? () => handleDeleteClick(asset) : undefined }, asset.hash32))) }));
|
|
232
|
+
}
|
|
233
|
+
return (_jsx(SimpleGrid, { cols: GRID_COLUMNS[density], spacing: "sm", children: visibleAssets.map((asset) => (_jsx(AssetCard, { asset: asset, baseUrl: baseUrl, density: density, onSelect: mode === 'picker' ? onSelect : undefined, onDelete: canDeleteAsset(asset) ? () => handleDeleteClick(asset) : undefined }, asset.hash32))) }));
|
|
234
|
+
};
|
|
235
|
+
const listContent = (_jsxs(_Fragment, { children: [!library.firstPageLoaded && !library.error && visibleAssets.length === 0 && (_jsx(MediaLibrarySkeleton, { density: density })), visibleAssets.length > 0 && renderList(), emptyState && (_jsxs(Stack, { align: "center", gap: "xs", py: "xl", "data-testid": "media-library-empty", children: [_jsx(Text, { size: "sm", c: "dimmed", ta: "center", children: emptyState }), _jsxs(Group, { gap: "xs", justify: "center", children: [needle && (_jsx(Button, { variant: "light", size: "xs", onClick: clearSearch, children: "Clear search" })), kind !== 'all' && !libraryEmpty && (_jsx(Button, { variant: "light", size: "xs", onClick: () => setKind('all'), children: "Show all files" }))] })] })), hasMore && (_jsxs(_Fragment, { children: [_jsx("div", { ref: setSentinel, "aria-hidden": "true", style: { height: 1 } }), _jsx(Group, { justify: "center", py: "sm", children: _jsx(Button, { variant: "light", onClick: showMore, "data-testid": "media-library-show-more", children: "Show more" }) })] }))] }));
|
|
236
|
+
return (
|
|
237
|
+
// `h="100%"` bounds it in a fixed-height host (the MDX image dialog's library tab); `flex`
|
|
238
|
+
// bounds it in the drawer's flex body.
|
|
239
|
+
_jsxs(Stack, { gap: "sm", h: "100%", style: { flex: 1, minHeight: 0 }, children: [_jsx(Dropzone, { onDrop: (files) => void handleDrop(files), onReject: handleReject, maxSize: MAX_UPLOAD_BYTES, accept: imageOnly ? ACCEPTED_IMAGE_MIME_TYPES : ACCEPTED_ASSET_MIME_TYPES, loading: upload.uploading, multiple: false, "data-testid": "media-library-dropzone", children: _jsxs(Group, { justify: "center", gap: "xs", py: "sm", style: { pointerEvents: 'none' }, children: [_jsx(IconUpload, { size: 20 }), _jsx(Text, { size: "sm", children: imageOnly
|
|
138
240
|
? 'Drop an image here, or click to browse'
|
|
139
241
|
: 'Drop an image or PDF here, or click to browse' })] }) }), upload.uploading &&
|
|
140
|
-
(upload.progress !== null ? (_jsx(Progress, { value: upload.progress * 100, size: "sm", "data-testid": "upload-progress" })) : (_jsx(Progress, { value: 100, size: "sm", animated: true, "data-testid": "upload-progress-indeterminate" }))), upload.error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", "data-testid": "upload-error", children: upload.error })), _jsx(TextInput, { placeholder: "
|
|
242
|
+
(upload.progress !== null ? (_jsx(Progress, { value: upload.progress * 100, size: "sm", "data-testid": "upload-progress" })) : (_jsx(Progress, { value: 100, size: "sm", animated: true, "data-testid": "upload-progress-indeterminate" }))), upload.error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", "data-testid": "upload-error", children: upload.error })), _jsx(TextInput, { placeholder: "Search by filename", "aria-label": "Search by filename", leftSection: _jsx(IconSearch, { size: 16 }), rightSection: filterText ? (_jsx(Tooltip, { label: "Clear search", children: _jsx(ActionIcon, { variant: "subtle", color: "gray", size: "sm", "aria-label": "Clear search", onClick: clearSearch, children: _jsx(IconX, { size: 14 }) }) })) : null, ref: searchRef, value: filterText, onChange: (event) => setFilterText(event.currentTarget.value), "data-mantine-stop-propagation": filterText ? 'true' : undefined, onKeyDown: (event) => {
|
|
243
|
+
if (event.key === 'Escape' && filterText)
|
|
244
|
+
setFilterText('');
|
|
245
|
+
}, "data-testid": "media-library-filter" }), _jsxs(Group, { justify: "space-between", wrap: "wrap", gap: "xs", children: [imageOnly ? (_jsx("span", {})) : (_jsx(SegmentedControl, { "aria-label": "File type", data: KIND_OPTIONS, value: kind, onChange: (value) => setKind(KIND_OPTIONS.find((o) => o.value === value)?.value ?? 'all'), "data-testid": "media-library-kind" })), _jsxs(Group, { gap: "xs", wrap: "wrap", children: [_jsx(Select, { "aria-label": "Sort by", data: SORT_OPTIONS, value: sort, onChange: (value) => {
|
|
246
|
+
const next = SORT_OPTIONS.find((option) => option.value === value);
|
|
247
|
+
if (next)
|
|
248
|
+
setSort(next.value);
|
|
249
|
+
}, allowDeselect: false, w: 150, "data-testid": "media-library-sort" }), _jsx(SegmentedControl, { "aria-label": "Layout", data: DENSITY_OPTIONS, value: density, onChange: (value) => {
|
|
250
|
+
const next = DENSITY_OPTIONS.find((option) => option.value === value);
|
|
251
|
+
if (next)
|
|
252
|
+
setDensity(next.value);
|
|
253
|
+
}, "data-testid": "media-library-density" })] })] }), _jsx(Text, { component: "div", size: "xs", c: "dimmed", mih: 18, children: loadStatus && (_jsx("span", { "aria-hidden": "true", "data-testid": "media-library-load-status", children: loadStatus.text })) }), _jsx(VisuallyHidden, { role: "status", "aria-live": "polite", children: loadStatus?.announce ?? '' }), _jsx(VisuallyHidden, { role: "status", "aria-live": "polite", "data-testid": "media-library-action-notice", children: actionNotice && _jsx("span", { children: actionNotice.text }, actionNotice.id) }), library.error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", "data-testid": "media-library-error", children: _jsxs(Stack, { gap: 4, children: [_jsx(Text, { size: "sm", children: library.error.firstPage
|
|
254
|
+
? "Couldn't load the media library."
|
|
255
|
+
: `Couldn't load the rest of the media library. Showing the ${fileCountLabel(scopedAssets.length)} loaded so far.` }), isAdmin(userContext?.groups) && (_jsx(Text, { size: "xs", c: "dimmed", children: library.error.message })), _jsx(Button, { variant: "light", onClick: library.retry, children: "Retry" })] }) })), actionError && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", withCloseButton: true, closeButtonLabel: "Dismiss", onClose: () => setActionError(null), "data-testid": "media-library-action-error", children: actionError })), imageOnly ? (_jsx(ScrollArea.Autosize, { mah: "60vh", viewportRef: setViewport, children: listContent })) : (_jsx(ScrollArea, { style: { flex: 1, minHeight: 200 }, viewportRef: setViewport, children: listContent }))] }));
|
|
141
256
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type MediaDensity } from './media-library-view.js';
|
|
3
|
+
/** Placeholder shaped like the layout it stands in for, so nothing shifts when assets arrive. */
|
|
4
|
+
export declare const MediaLibrarySkeleton: React.FC<{
|
|
5
|
+
density: MediaDensity;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Card, SimpleGrid, Skeleton, Stack } from '@mantine/core';
|
|
4
|
+
import { CARD_THUMBNAIL_HEIGHT, GRID_COLUMNS } from './media-library-view.js';
|
|
5
|
+
const SKELETON_COUNT = 8;
|
|
6
|
+
/** Placeholder shaped like the layout it stands in for, so nothing shifts when assets arrive. */
|
|
7
|
+
export const MediaLibrarySkeleton = ({ density }) => {
|
|
8
|
+
const items = Array.from({ length: SKELETON_COUNT }, (_, index) => index);
|
|
9
|
+
if (density === 'list') {
|
|
10
|
+
return (_jsx(Stack, { gap: 4, "data-testid": "media-library-loading", "aria-busy": "true", children: items.map((index) => (_jsx(Skeleton, { height: 54, radius: "md" }, index))) }));
|
|
11
|
+
}
|
|
12
|
+
const thumbnailHeight = CARD_THUMBNAIL_HEIGHT[density];
|
|
13
|
+
return (_jsx(SimpleGrid, { cols: GRID_COLUMNS[density], spacing: "sm", "data-testid": "media-library-loading", "aria-busy": "true", children: items.map((index) => (_jsxs(Card, { withBorder: true, radius: "md", padding: "xs", children: [_jsx(Card.Section, { children: _jsx(Skeleton, { height: thumbnailHeight, radius: 0 }) }), _jsx(Skeleton, { height: 12, width: "70%", mt: 8 }), density === 'grid' && _jsx(Skeleton, { height: 10, width: "40%", mt: 6 })] }, index))) }));
|
|
14
|
+
};
|
|
@@ -16,10 +16,9 @@ export interface CropAreaPercent {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Convert react-easy-crop's `onCropComplete(croppedArea, croppedAreaPixels)`
|
|
19
|
-
* percentage area into a normalized `CropRect`,
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* space rather than reject a rect that was valid before rounding.
|
|
19
|
+
* percentage area into a normalized `CropRect`, clamped to [0,1] and then
|
|
20
|
+
* rounded by `roundCropRect` — the same rounding the transform URL's canonical
|
|
21
|
+
* form applies, so the stored crop is already canonical.
|
|
23
22
|
*
|
|
24
23
|
* Returns `null` if the input can't be coerced into a valid rect (e.g. a
|
|
25
24
|
* zero-area selection).
|
|
@@ -6,13 +6,7 @@
|
|
|
6
6
|
* (no react-easy-crop import) so it's trivially unit-testable and reusable
|
|
7
7
|
* without pulling in the cropper UI library.
|
|
8
8
|
*/
|
|
9
|
-
import { isValidCropRect } from '../../assets/transform-directives.js';
|
|
10
|
-
/** Matches transform-directives.ts's CROP_PRECISION so a crop rect round-trips through the URL directive unchanged. */
|
|
11
|
-
const CROP_PRECISION = 4;
|
|
12
|
-
function roundTo(value, decimals) {
|
|
13
|
-
const factor = 10 ** decimals;
|
|
14
|
-
return Math.round(value * factor) / factor;
|
|
15
|
-
}
|
|
9
|
+
import { isValidCropRect, roundCropRect } from '../../assets/transform-directives.js';
|
|
16
10
|
function clamp01(value) {
|
|
17
11
|
if (!Number.isFinite(value))
|
|
18
12
|
return 0;
|
|
@@ -20,26 +14,21 @@ function clamp01(value) {
|
|
|
20
14
|
}
|
|
21
15
|
/**
|
|
22
16
|
* Convert react-easy-crop's `onCropComplete(croppedArea, croppedAreaPixels)`
|
|
23
|
-
* percentage area into a normalized `CropRect`,
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* space rather than reject a rect that was valid before rounding.
|
|
17
|
+
* percentage area into a normalized `CropRect`, clamped to [0,1] and then
|
|
18
|
+
* rounded by `roundCropRect` — the same rounding the transform URL's canonical
|
|
19
|
+
* form applies, so the stored crop is already canonical.
|
|
27
20
|
*
|
|
28
21
|
* Returns `null` if the input can't be coerced into a valid rect (e.g. a
|
|
29
22
|
* zero-area selection).
|
|
30
23
|
*/
|
|
31
24
|
export function cropAreaPercentToRect(area) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
h = roundTo(1 - y, CROP_PRECISION);
|
|
40
|
-
if (!isValidCropRect(x, y, w, h))
|
|
41
|
-
return null;
|
|
42
|
-
return { x, y, w, h };
|
|
25
|
+
const rect = roundCropRect({
|
|
26
|
+
x: clamp01(area.x / 100),
|
|
27
|
+
y: clamp01(area.y / 100),
|
|
28
|
+
w: clamp01(area.width / 100),
|
|
29
|
+
h: clamp01(area.height / 100),
|
|
30
|
+
});
|
|
31
|
+
return isValidCropRect(rect.x, rect.y, rect.w, rect.h) ? rect : null;
|
|
43
32
|
}
|
|
44
33
|
/**
|
|
45
34
|
* Inverse of `cropAreaPercentToRect` - seeds react-easy-crop's
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AssetUrlOptions } from '../../assets/asset-url.js';
|
|
2
|
+
/**
|
|
3
|
+
* The URL the editor displays a content image at. An asset-store src goes behind `baseUrl` (the
|
|
4
|
+
* authenticated raw route, see `AssetContext`) with `opts` applied. Any other src keeps its own
|
|
5
|
+
* path, since the raw route serves only the asset store; `assetUrl` still neutralizes a spelling a
|
|
6
|
+
* browser would read as off-origin.
|
|
7
|
+
*/
|
|
8
|
+
export declare function editorImageSrc(src: string, baseUrl: string, opts?: Omit<AssetUrlOptions, 'baseUrl'>): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { assetUrl, isAssetStoreSrc } from '../../assets/asset-url.js';
|
|
2
|
+
/**
|
|
3
|
+
* The URL the editor displays a content image at. An asset-store src goes behind `baseUrl` (the
|
|
4
|
+
* authenticated raw route, see `AssetContext`) with `opts` applied. Any other src keeps its own
|
|
5
|
+
* path, since the raw route serves only the asset store; `assetUrl` still neutralizes a spelling a
|
|
6
|
+
* browser would read as off-origin.
|
|
7
|
+
*/
|
|
8
|
+
export function editorImageSrc(src, baseUrl, opts = {}) {
|
|
9
|
+
return isAssetStoreSrc(src) ? assetUrl({ src }, { ...opts, baseUrl }) : assetUrl({ src });
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const UNITS = ['B', 'KB', 'MB', 'GB'];
|
|
2
|
+
/** "512 B", "2.3 MB", "14 KB": 1024 base, one decimal below 10, whole numbers above. */
|
|
3
|
+
export function formatBytes(bytes) {
|
|
4
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
5
|
+
return '0 B';
|
|
6
|
+
let value = bytes;
|
|
7
|
+
let unit = 0;
|
|
8
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
9
|
+
value /= 1024;
|
|
10
|
+
unit += 1;
|
|
11
|
+
}
|
|
12
|
+
// 1023.6 KB rounds to "1024 KB"; promote it to 1.0 MB instead.
|
|
13
|
+
if (unit > 0 && unit < UNITS.length - 1 && Math.round(value) >= 1024) {
|
|
14
|
+
value /= 1024;
|
|
15
|
+
unit += 1;
|
|
16
|
+
}
|
|
17
|
+
if (unit === 0)
|
|
18
|
+
return `${Math.round(value)} B`;
|
|
19
|
+
const oneDecimal = Math.round(value * 10) / 10;
|
|
20
|
+
if (oneDecimal < 10)
|
|
21
|
+
return `${oneDecimal.toFixed(1)} ${UNITS[unit]}`;
|
|
22
|
+
return `${Math.round(value)} ${UNITS[unit]}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type MediaDensity, type MediaSort } from './media-library-view.js';
|
|
2
|
+
/** The preferences that persist per viewer; the kind filter and search text do not. */
|
|
3
|
+
interface MediaLibraryPrefs {
|
|
4
|
+
sort: MediaSort;
|
|
5
|
+
density: MediaDensity;
|
|
6
|
+
}
|
|
7
|
+
/** @internal Exported for tests and stories. */
|
|
8
|
+
export declare const MEDIA_LIBRARY_PREFS_KEY = "canopycms:media-library:prefs";
|
|
9
|
+
/**
|
|
10
|
+
* @internal Exported for tests. Validates each field on its own, so a bad one takes its default and the other is kept. Mantine
|
|
11
|
+
* does not guard `deserialize`, so this never throws, whatever is under the key.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseMediaLibraryPrefs(raw: string | undefined): MediaLibraryPrefs;
|
|
14
|
+
export interface UseMediaLibraryPrefsReturn extends MediaLibraryPrefs {
|
|
15
|
+
setSort: (sort: MediaSort) => void;
|
|
16
|
+
setDensity: (density: MediaDensity) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sort and density, read from localStorage after mount (SSR renders the defaults). A storage that
|
|
20
|
+
* throws leaves them in memory for the session.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useMediaLibraryPrefs(): UseMediaLibraryPrefsReturn;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useLocalStorage } from '@mantine/hooks';
|
|
3
|
+
import { isPlainRecord } from '../../validation/field-traversal.js';
|
|
4
|
+
import { MEDIA_DENSITIES, MEDIA_SORTS, } from './media-library-view.js';
|
|
5
|
+
/** @internal Exported for tests and stories. */
|
|
6
|
+
export const MEDIA_LIBRARY_PREFS_KEY = 'canopycms:media-library:prefs';
|
|
7
|
+
/** Module-level so its identity is stable: Mantine memoises its storage reader on the default. */
|
|
8
|
+
const DEFAULT_PREFS = { sort: 'newest', density: 'grid' };
|
|
9
|
+
const oneOf = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
|
|
10
|
+
/**
|
|
11
|
+
* @internal Exported for tests. Validates each field on its own, so a bad one takes its default and the other is kept. Mantine
|
|
12
|
+
* does not guard `deserialize`, so this never throws, whatever is under the key.
|
|
13
|
+
*/
|
|
14
|
+
export function parseMediaLibraryPrefs(raw) {
|
|
15
|
+
if (!raw)
|
|
16
|
+
return DEFAULT_PREFS;
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return DEFAULT_PREFS;
|
|
23
|
+
}
|
|
24
|
+
if (!isPlainRecord(parsed))
|
|
25
|
+
return DEFAULT_PREFS;
|
|
26
|
+
return {
|
|
27
|
+
sort: oneOf(MEDIA_SORTS, parsed.sort, DEFAULT_PREFS.sort),
|
|
28
|
+
density: oneOf(MEDIA_DENSITIES, parsed.density, DEFAULT_PREFS.density),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sort and density, read from localStorage after mount (SSR renders the defaults). A storage that
|
|
33
|
+
* throws leaves them in memory for the session.
|
|
34
|
+
*/
|
|
35
|
+
export function useMediaLibraryPrefs() {
|
|
36
|
+
const [prefs, setPrefs] = useLocalStorage({
|
|
37
|
+
key: MEDIA_LIBRARY_PREFS_KEY,
|
|
38
|
+
defaultValue: DEFAULT_PREFS,
|
|
39
|
+
deserialize: parseMediaLibraryPrefs,
|
|
40
|
+
// Each tab keeps its own choice; a change elsewhere reaches only tabs opened later.
|
|
41
|
+
sync: false,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
sort: prefs.sort,
|
|
45
|
+
density: prefs.density,
|
|
46
|
+
setSort: (sort) => setPrefs((prev) => ({ ...prev, sort })),
|
|
47
|
+
setDensity: (density) => setPrefs((prev) => ({ ...prev, density })),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AssetRecord } from '../../api/index.js';
|
|
2
|
+
export type MediaSort = 'newest' | 'name';
|
|
3
|
+
export type MediaDensity = 'grid' | 'compact' | 'list';
|
|
4
|
+
export type MediaKind = 'all' | 'images' | 'pdfs';
|
|
5
|
+
export declare const MEDIA_SORTS: readonly MediaSort[];
|
|
6
|
+
export declare const MEDIA_DENSITIES: readonly MediaDensity[];
|
|
7
|
+
/**
|
|
8
|
+
* Orders every loaded asset. The list API pages in storage order, so the order is only
|
|
9
|
+
* meaningful here, across everything loaded.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sortAssets(assets: readonly AssetRecord[], sort: MediaSort): AssetRecord[];
|
|
12
|
+
export declare function matchesKind(asset: AssetRecord, kind: MediaKind): boolean;
|
|
13
|
+
/** `needle` is already trimmed and lower-cased; an empty needle matches everything. */
|
|
14
|
+
export declare function matchesText(asset: AssetRecord, needle: string): boolean;
|
|
15
|
+
/** "1 file", "2 files". */
|
|
16
|
+
export declare const fileCountLabel: (count: number) => string;
|
|
17
|
+
/** Grid columns per layout; the loading skeleton uses the same ones so nothing shifts. */
|
|
18
|
+
export declare const GRID_COLUMNS: {
|
|
19
|
+
readonly grid: {
|
|
20
|
+
readonly base: 2;
|
|
21
|
+
readonly sm: 3;
|
|
22
|
+
readonly md: 4;
|
|
23
|
+
};
|
|
24
|
+
readonly compact: {
|
|
25
|
+
readonly base: 3;
|
|
26
|
+
readonly sm: 5;
|
|
27
|
+
readonly md: 6;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Card thumbnail heights in px, shared with the skeleton that stands in for them. */
|
|
31
|
+
export declare const CARD_THUMBNAIL_HEIGHT: {
|
|
32
|
+
readonly grid: 100;
|
|
33
|
+
readonly compact: 64;
|
|
34
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const MEDIA_SORTS = ['newest', 'name'];
|
|
2
|
+
export const MEDIA_DENSITIES = ['grid', 'compact', 'list'];
|
|
3
|
+
const uploadedAtMs = (asset) => {
|
|
4
|
+
const ms = Date.parse(asset.uploadedAt);
|
|
5
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
6
|
+
};
|
|
7
|
+
const byNewest = (a, b) => uploadedAtMs(b) - uploadedAtMs(a);
|
|
8
|
+
const byFilename = (a, b) => a.filename.localeCompare(b.filename, undefined, { sensitivity: 'base', numeric: true });
|
|
9
|
+
/**
|
|
10
|
+
* Orders every loaded asset. The list API pages in storage order, so the order is only
|
|
11
|
+
* meaningful here, across everything loaded.
|
|
12
|
+
*/
|
|
13
|
+
export function sortAssets(assets, sort) {
|
|
14
|
+
const compare = sort === 'name'
|
|
15
|
+
? (a, b) => byFilename(a, b) || byNewest(a, b)
|
|
16
|
+
: (a, b) => byNewest(a, b) || byFilename(a, b);
|
|
17
|
+
return [...assets].sort(compare);
|
|
18
|
+
}
|
|
19
|
+
export function matchesKind(asset, kind) {
|
|
20
|
+
if (kind === 'pdfs')
|
|
21
|
+
return asset.kind === 'pdf';
|
|
22
|
+
if (kind === 'images')
|
|
23
|
+
return asset.kind !== 'pdf';
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/** `needle` is already trimmed and lower-cased; an empty needle matches everything. */
|
|
27
|
+
export function matchesText(asset, needle) {
|
|
28
|
+
if (!needle)
|
|
29
|
+
return true;
|
|
30
|
+
return asset.filename.toLowerCase().includes(needle) || asset.slug.toLowerCase().includes(needle);
|
|
31
|
+
}
|
|
32
|
+
/** "1 file", "2 files". */
|
|
33
|
+
export const fileCountLabel = (count) => `${count} ${count === 1 ? 'file' : 'files'}`;
|
|
34
|
+
/** Grid columns per layout; the loading skeleton uses the same ones so nothing shifts. */
|
|
35
|
+
export const GRID_COLUMNS = {
|
|
36
|
+
grid: { base: 2, sm: 3, md: 4 },
|
|
37
|
+
compact: { base: 3, sm: 5, md: 6 },
|
|
38
|
+
};
|
|
39
|
+
/** Card thumbnail heights in px, shared with the skeleton that stands in for them. */
|
|
40
|
+
export const CARD_THUMBNAIL_HEIGHT = { grid: 100, compact: 64 };
|