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
|
@@ -5,17 +5,41 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
*
|
|
6
6
|
* A collection has a name (machine-readable id), label (display name), and
|
|
7
7
|
* entries (entry types defining what content can be created). Create mode
|
|
8
|
-
*
|
|
9
|
-
* label
|
|
8
|
+
* stages everything locally and sends it with "Create collection"; Cancel
|
|
9
|
+
* discards it. Edit mode stages name, label and slug until "Save collection
|
|
10
|
+
* details", but every entry-type add, update and remove saves to the branch
|
|
11
|
+
* at once. The footer's dismiss button says what leaving does: "Discard
|
|
12
|
+
* changes" while details are staged, "Close" once an entry-type change has
|
|
13
|
+
* saved or may have, else "Cancel" (which would promise a revert it cannot do
|
|
14
|
+
* after one).
|
|
10
15
|
*/
|
|
11
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
12
|
-
import { Modal, Stack, TextInput, Group, Button, Alert, Text, Paper, ActionIcon, Menu,
|
|
13
|
-
import { IconAlertCircle, IconPlus, IconDotsVertical, IconTrash, IconEdit, IconStar, } from '@tabler/icons-react';
|
|
16
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
17
|
+
import { Modal, Stack, TextInput, Group, Button, Alert, Text, Paper, ActionIcon, Menu, Title, Badge, Tooltip, } from '@mantine/core';
|
|
18
|
+
import { IconAlertCircle, IconAlertTriangle, IconRestore, IconCheck, IconPlus, IconDotsVertical, IconTrash, IconEdit, IconStar, } from '@tabler/icons-react';
|
|
14
19
|
import { EntryTypeEditor } from './EntryTypeEditor.js';
|
|
15
20
|
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal.js';
|
|
21
|
+
import { DestructiveMenuItem } from '../components/DestructiveMenuItem.js';
|
|
16
22
|
import { getErrorMessage } from '../../utils/error.js';
|
|
17
|
-
|
|
23
|
+
import { slugOf } from './collection-draft.js';
|
|
24
|
+
/** Whether a listed entry type holds every field an add or update sent. */
|
|
25
|
+
function showsFields(entryType, fields) {
|
|
26
|
+
return Object.entries(fields).every(([key, value]) => key === 'default'
|
|
27
|
+
? !!entryType.default === !!value
|
|
28
|
+
: entryType[key] === value);
|
|
29
|
+
}
|
|
30
|
+
function usageOf(entryType) {
|
|
31
|
+
return 'usageCount' in entryType ? (entryType.usageCount ?? 0) : 0;
|
|
32
|
+
}
|
|
33
|
+
export function CollectionEditor({ isOpen, editingCollection, parentPath, availableSchemas, onSave, onAddEntryType, onUpdateEntryType, onRemoveEntryType, onReloadEntryTypes, onClose, isSaving = false, error = null, restored = null, onDiscardRestored, }) {
|
|
18
34
|
const isEditMode = editingCollection !== null;
|
|
35
|
+
// Read when the dialog opens only: dropping it later must not reset what the user has typed.
|
|
36
|
+
const restoredRef = useRef(restored);
|
|
37
|
+
restoredRef.current = restored;
|
|
38
|
+
const [showingRestored, setShowingRestored] = useState(false);
|
|
39
|
+
const nameInputRef = useRef(null);
|
|
40
|
+
// Staged removals, latest last; Undo stays in the dialog, as a toast's is outside the focus trap.
|
|
41
|
+
const [stagedRemovals, setStagedRemovals] = useState([]);
|
|
42
|
+
const stagedRemoval = stagedRemovals.at(-1);
|
|
19
43
|
const [formData, setFormData] = useState({
|
|
20
44
|
name: '',
|
|
21
45
|
label: '',
|
|
@@ -29,33 +53,95 @@ export function CollectionEditor({ isOpen, editingCollection, parentPath, availa
|
|
|
29
53
|
const [editingEntryTypeIndex, setEditingEntryTypeIndex] = useState(null);
|
|
30
54
|
const [entryTypeSaving, setEntryTypeSaving] = useState(false);
|
|
31
55
|
const [entryTypeError, setEntryTypeError] = useState(null);
|
|
56
|
+
const [errorDismissed, setErrorDismissed] = useState(false);
|
|
57
|
+
// Edit mode renders this list rather than `editingCollection.entries`, a snapshot taken when
|
|
58
|
+
// the dialog opened that the parent never refreshes. Each immediate action patches it.
|
|
59
|
+
const [entryTypes, setEntryTypes] = useState(() => editingCollection?.entries ?? []);
|
|
60
|
+
// True once an immediate entry-type change has saved, or may have, in this opening of the dialog.
|
|
61
|
+
const [entryTypeChanged, setEntryTypeChanged] = useState(false);
|
|
62
|
+
const [entryTypeStatus, setEntryTypeStatus] = useState('');
|
|
63
|
+
// An unanswered remove the re-read did not settle, shown above the list.
|
|
64
|
+
const [entryTypeUnanswered, setEntryTypeUnanswered] = useState(null);
|
|
65
|
+
// An unanswered add or update the re-read did not settle, shown in the entry type dialog,
|
|
66
|
+
// which stays open with the user's input.
|
|
67
|
+
const [entryTypeWarning, setEntryTypeWarning] = useState(null);
|
|
68
|
+
const [removeFailure, setRemoveFailure] = useState(null);
|
|
69
|
+
const [removing, setRemoving] = useState(false);
|
|
70
|
+
const [menuOpenFor, setMenuOpenFor] = useState(null);
|
|
71
|
+
// Closing an overlay returns focus to its opener, but a row's menu item is gone once it acts,
|
|
72
|
+
// so Mantine's own return lands nowhere. `row` names the trigger to focus; null is the heading.
|
|
73
|
+
const [focusRequest, setFocusRequest] = useState(null);
|
|
74
|
+
const triggerRefs = useRef(new Map());
|
|
75
|
+
const entryTypesHeadingRef = useRef(null);
|
|
32
76
|
const [deleteEntryTypeModalOpen, setDeleteEntryTypeModalOpen] = useState(false);
|
|
33
77
|
const [deletingEntryType, setDeletingEntryType] = useState(null);
|
|
34
78
|
useEffect(() => {
|
|
35
79
|
if (isOpen) {
|
|
80
|
+
const prefill = restoredRef.current;
|
|
36
81
|
if (editingCollection) {
|
|
37
82
|
setFormData({
|
|
38
|
-
name: editingCollection.name,
|
|
39
|
-
label: editingCollection.label || '',
|
|
40
|
-
entries: [], //
|
|
83
|
+
name: prefill?.name ?? editingCollection.name,
|
|
84
|
+
label: prefill?.label ?? (editingCollection.label || ''),
|
|
85
|
+
entries: [], // Edit mode keeps its entry types in `entryTypes`
|
|
41
86
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const lastPart = pathParts[pathParts.length - 1];
|
|
45
|
-
const slugPart = lastPart?.split('.')[0] || '';
|
|
46
|
-
setSlug(slugPart);
|
|
87
|
+
setEntryTypes(editingCollection.entries);
|
|
88
|
+
setSlug(prefill?.slug ?? slugOf(editingCollection.logicalPath));
|
|
47
89
|
}
|
|
48
90
|
else {
|
|
49
91
|
setFormData({
|
|
50
|
-
name: '',
|
|
51
|
-
label: '',
|
|
52
|
-
entries: [],
|
|
92
|
+
name: prefill?.name ?? '',
|
|
93
|
+
label: prefill?.label ?? '',
|
|
94
|
+
entries: prefill?.entries ?? [],
|
|
53
95
|
});
|
|
54
96
|
setSlug('');
|
|
97
|
+
setEntryTypes([]);
|
|
55
98
|
}
|
|
99
|
+
setShowingRestored(!!prefill);
|
|
100
|
+
setStagedRemovals([]);
|
|
56
101
|
setValidationError(null);
|
|
102
|
+
setEntryTypeChanged(false);
|
|
103
|
+
setEntryTypeStatus('');
|
|
104
|
+
setEntryTypeUnanswered(null);
|
|
105
|
+
setRemoveFailure(null);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// The parent closes this dialog (and nulls `editingCollection`) when details save, so a
|
|
109
|
+
// sub-dialog left open would stage into a closed dialog in create mode.
|
|
110
|
+
setEntryTypeEditorOpen(false);
|
|
111
|
+
setEditingEntryType(null);
|
|
112
|
+
setEditingEntryTypeIndex(null);
|
|
113
|
+
setEntryTypeError(null);
|
|
114
|
+
setDeleteEntryTypeModalOpen(false);
|
|
57
115
|
}
|
|
58
116
|
}, [isOpen, editingCollection]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setErrorDismissed(false);
|
|
119
|
+
}, [error, isOpen, editingCollection]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!focusRequest)
|
|
122
|
+
return;
|
|
123
|
+
// Runs after the overlay's own focus return, which Mantine's useFocusReturn delays by 10ms.
|
|
124
|
+
const timer = window.setTimeout(() => {
|
|
125
|
+
const target = focusRequest.row === null
|
|
126
|
+
? entryTypesHeadingRef.current
|
|
127
|
+
: triggerRefs.current.get(focusRequest.row);
|
|
128
|
+
target?.focus();
|
|
129
|
+
setFocusRequest(null);
|
|
130
|
+
}, 50);
|
|
131
|
+
return () => window.clearTimeout(timer);
|
|
132
|
+
}, [focusRequest]);
|
|
133
|
+
const discardRestored = useCallback(() => {
|
|
134
|
+
setFormData({
|
|
135
|
+
name: editingCollection?.name ?? '',
|
|
136
|
+
label: editingCollection?.label ?? '',
|
|
137
|
+
entries: [],
|
|
138
|
+
});
|
|
139
|
+
setSlug(editingCollection ? slugOf(editingCollection.logicalPath) : '');
|
|
140
|
+
setShowingRestored(false);
|
|
141
|
+
setStagedRemovals([]);
|
|
142
|
+
onDiscardRestored?.();
|
|
143
|
+
nameInputRef.current?.focus();
|
|
144
|
+
}, [editingCollection, onDiscardRestored]);
|
|
59
145
|
const updateField = useCallback((field, value) => {
|
|
60
146
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
61
147
|
setValidationError(null);
|
|
@@ -92,9 +178,7 @@ export function CollectionEditor({ isOpen, editingCollection, parentPath, availa
|
|
|
92
178
|
if (formData.label !== (editingCollection?.label || '')) {
|
|
93
179
|
updates.label = formData.label || undefined;
|
|
94
180
|
}
|
|
95
|
-
const
|
|
96
|
-
const lastPart = pathParts[pathParts.length - 1];
|
|
97
|
-
const currentSlug = lastPart?.split('.')[0] || '';
|
|
181
|
+
const currentSlug = editingCollection ? slugOf(editingCollection.logicalPath) : '';
|
|
98
182
|
if (slug && slug !== currentSlug) {
|
|
99
183
|
updates.slug = slug;
|
|
100
184
|
}
|
|
@@ -126,18 +210,101 @@ export function CollectionEditor({ isOpen, editingCollection, parentPath, availa
|
|
|
126
210
|
setEntryTypeError(null);
|
|
127
211
|
setEntryTypeEditorOpen(true);
|
|
128
212
|
}, []);
|
|
213
|
+
const announceEntryTypeChange = useCallback((message) => {
|
|
214
|
+
setEntryTypeStatus(message);
|
|
215
|
+
setEntryTypeChanged(true);
|
|
216
|
+
}, []);
|
|
217
|
+
/**
|
|
218
|
+
* After an entry-type action the server never answered, which may have landed: re-reads the
|
|
219
|
+
* list from the branch and shows it, and counts the action as a possible change (the footer
|
|
220
|
+
* reads "Close"). Resolves to the re-read list, or null when it could not be read.
|
|
221
|
+
*/
|
|
222
|
+
const reloadEntryTypes = useCallback(async (collectionPath) => {
|
|
223
|
+
setEntryTypeChanged(true);
|
|
224
|
+
const reloaded = await (onReloadEntryTypes?.(collectionPath) ?? Promise.resolve(null)).catch((err) => {
|
|
225
|
+
console.error(err);
|
|
226
|
+
return null;
|
|
227
|
+
});
|
|
228
|
+
if (reloaded)
|
|
229
|
+
setEntryTypes(reloaded);
|
|
230
|
+
return reloaded;
|
|
231
|
+
}, [onReloadEntryTypes]);
|
|
232
|
+
const closeEntryTypeEditor = useCallback(() => {
|
|
233
|
+
setEntryTypeEditorOpen(false);
|
|
234
|
+
setEditingEntryType(null);
|
|
235
|
+
setEditingEntryTypeIndex(null);
|
|
236
|
+
setEntryTypeError(null);
|
|
237
|
+
setEntryTypeWarning(null);
|
|
238
|
+
}, []);
|
|
239
|
+
// Esc, the close button and a click outside all land here; closing mid-request would hide
|
|
240
|
+
// the request's result.
|
|
241
|
+
const dismissEntryTypeEditor = useCallback(() => {
|
|
242
|
+
if (entryTypeSaving)
|
|
243
|
+
return;
|
|
244
|
+
const editedName = editingEntryType?.name;
|
|
245
|
+
closeEntryTypeEditor();
|
|
246
|
+
if (editedName)
|
|
247
|
+
setFocusRequest({ row: editedName });
|
|
248
|
+
}, [entryTypeSaving, editingEntryType, closeEntryTypeEditor]);
|
|
129
249
|
const handleEntryTypeSave = useCallback(async (data, isNew) => {
|
|
250
|
+
const editedName = isNew ? undefined : editingEntryType?.name;
|
|
251
|
+
const finish = () => {
|
|
252
|
+
closeEntryTypeEditor();
|
|
253
|
+
if (editedName)
|
|
254
|
+
setFocusRequest({ row: editedName });
|
|
255
|
+
};
|
|
130
256
|
if (isEditMode && editingCollection) {
|
|
257
|
+
// The server skips undefined fields, so a cleared label or max items keeps its stored
|
|
258
|
+
// value: the list shows what was stored, and an update with none saves nothing.
|
|
259
|
+
const stored = Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined));
|
|
260
|
+
if (!isNew && Object.keys(stored).length === 0) {
|
|
261
|
+
finish();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
131
264
|
setEntryTypeSaving(true);
|
|
132
265
|
setEntryTypeError(null);
|
|
266
|
+
setEntryTypeWarning(null);
|
|
267
|
+
setEntryTypeStatus('');
|
|
268
|
+
setEntryTypeUnanswered(null);
|
|
269
|
+
setRemoveFailure(null);
|
|
133
270
|
try {
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
271
|
+
// An absent callback leaves `result` undefined: nothing was saved, so nothing is
|
|
272
|
+
// applied locally or announced.
|
|
273
|
+
let result;
|
|
274
|
+
if (isNew) {
|
|
275
|
+
const entryType = data;
|
|
276
|
+
result = await onAddEntryType?.(editingCollection.logicalPath, entryType);
|
|
277
|
+
if (result?.ok) {
|
|
278
|
+
setEntryTypes((prev) => [...prev, { ...entryType, usageCount: 0 }]);
|
|
279
|
+
announceEntryTypeChange(`Entry type "${entryType.name}" added`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (editingEntryType) {
|
|
283
|
+
const name = editingEntryType.name;
|
|
284
|
+
result = await onUpdateEntryType?.(editingCollection.logicalPath, name, data);
|
|
285
|
+
if (result?.ok) {
|
|
286
|
+
setEntryTypes((prev) => prev.map((et) => (et.name === name ? { ...et, ...stored } : et)));
|
|
287
|
+
announceEntryTypeChange(`Entry type "${name}" updated`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (result && !result.ok && result.unanswered) {
|
|
291
|
+
// The re-read settles it when the branch already shows the change. Otherwise this
|
|
292
|
+
// dialog keeps the input: re-sending an update sends the same change, and re-sending
|
|
293
|
+
// an add that had landed is refused because the name exists (`schema-store.ts`).
|
|
294
|
+
const name = isNew ? data.name : editingEntryType?.name;
|
|
295
|
+
const reloaded = await reloadEntryTypes(editingCollection.logicalPath);
|
|
296
|
+
const now = reloaded?.find((et) => et.name === name);
|
|
297
|
+
if (name && now && showsFields(now, stored)) {
|
|
298
|
+
announceEntryTypeChange(`Entry type "${name}" ${isNew ? 'added' : 'updated'}`);
|
|
299
|
+
closeEntryTypeEditor();
|
|
300
|
+
setFocusRequest({ row: name });
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
setEntryTypeWarning(isNew
|
|
304
|
+
? `"${name}" may not have been added. Select "Add entry type" to try again: if it was added, you'll be told it already exists.`
|
|
305
|
+
: 'The change may not have been saved. Select "Save entry type" to send it again.');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
141
308
|
if (result && !result.ok) {
|
|
142
309
|
setEntryTypeError(result.error);
|
|
143
310
|
return;
|
|
@@ -168,9 +335,7 @@ export function CollectionEditor({ isOpen, editingCollection, parentPath, availa
|
|
|
168
335
|
}));
|
|
169
336
|
}
|
|
170
337
|
}
|
|
171
|
-
|
|
172
|
-
setEditingEntryType(null);
|
|
173
|
-
setEditingEntryTypeIndex(null);
|
|
338
|
+
finish();
|
|
174
339
|
}, [
|
|
175
340
|
isEditMode,
|
|
176
341
|
editingCollection,
|
|
@@ -178,45 +343,179 @@ export function CollectionEditor({ isOpen, editingCollection, parentPath, availa
|
|
|
178
343
|
editingEntryTypeIndex,
|
|
179
344
|
onAddEntryType,
|
|
180
345
|
onUpdateEntryType,
|
|
346
|
+
announceEntryTypeChange,
|
|
347
|
+
reloadEntryTypes,
|
|
348
|
+
closeEntryTypeEditor,
|
|
181
349
|
]);
|
|
350
|
+
const displayEntryTypes = isEditMode
|
|
351
|
+
? entryTypes
|
|
352
|
+
: formData.entries;
|
|
353
|
+
// Create mode stages entry types, so a removal goes at once with an Undo; edit mode's saves at
|
|
354
|
+
// once with no restore, so it confirms (docs/ux-guidelines.md, Destructive actions).
|
|
355
|
+
const removeStagedEntryType = useCallback((entryType, index) => {
|
|
356
|
+
const names = formData.entries.map((et) => et.name);
|
|
357
|
+
const neighbour = names[index + 1] ?? names[index - 1] ?? null;
|
|
358
|
+
setFormData((prev) => ({ ...prev, entries: prev.entries.filter((_, i) => i !== index) }));
|
|
359
|
+
setStagedRemovals((prev) => [...prev, { entryType, index }]);
|
|
360
|
+
setFocusRequest({ row: neighbour });
|
|
361
|
+
}, [formData.entries]);
|
|
362
|
+
const undoStagedRemoval = useCallback(() => {
|
|
363
|
+
if (!stagedRemoval)
|
|
364
|
+
return;
|
|
365
|
+
const { entryType, index } = stagedRemoval;
|
|
366
|
+
// Into the list as it is now, unless an entry type of that name was added since.
|
|
367
|
+
setFormData((prev) => prev.entries.some((et) => et.name === entryType.name)
|
|
368
|
+
? prev
|
|
369
|
+
: {
|
|
370
|
+
...prev,
|
|
371
|
+
entries: [...prev.entries.slice(0, index), entryType, ...prev.entries.slice(index)],
|
|
372
|
+
});
|
|
373
|
+
setStagedRemovals((prev) => prev.slice(0, -1));
|
|
374
|
+
setFocusRequest({ row: entryType.name });
|
|
375
|
+
}, [stagedRemoval]);
|
|
182
376
|
const handleRemoveEntryType = useCallback((entryType, index) => {
|
|
183
|
-
|
|
377
|
+
if (!isEditMode) {
|
|
378
|
+
removeStagedEntryType(entryType, index);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
setDeletingEntryType({ entryType });
|
|
184
382
|
setDeleteEntryTypeModalOpen(true);
|
|
383
|
+
}, [isEditMode, removeStagedEntryType]);
|
|
384
|
+
// Only closes: the target is replaced on the next open, so the message stays put while the
|
|
385
|
+
// dialog fades out.
|
|
386
|
+
const closeRemoveConfirm = useCallback(() => {
|
|
387
|
+
setDeleteEntryTypeModalOpen(false);
|
|
185
388
|
}, []);
|
|
186
|
-
const
|
|
389
|
+
const cancelRemoveConfirm = useCallback(() => {
|
|
390
|
+
closeRemoveConfirm();
|
|
391
|
+
if (deletingEntryType)
|
|
392
|
+
setFocusRequest({ row: deletingEntryType.entryType.name });
|
|
393
|
+
}, [closeRemoveConfirm, deletingEntryType]);
|
|
394
|
+
const removeEntryType = useCallback(async (name) => {
|
|
395
|
+
const names = displayEntryTypes.map((et) => et.name);
|
|
396
|
+
// Focus moves to the row that takes the removed one's place, else the one before it, else
|
|
397
|
+
// the heading.
|
|
398
|
+
const neighbour = names[names.indexOf(name) + 1] ?? names[names.indexOf(name) - 1] ?? null;
|
|
399
|
+
if (editingCollection) {
|
|
400
|
+
if (!onRemoveEntryType)
|
|
401
|
+
return;
|
|
402
|
+
setRemoving(true);
|
|
403
|
+
setEntryTypeStatus('');
|
|
404
|
+
setEntryTypeUnanswered(null);
|
|
405
|
+
try {
|
|
406
|
+
const result = await onRemoveEntryType(editingCollection.logicalPath, name);
|
|
407
|
+
if (result.ok) {
|
|
408
|
+
setEntryTypes((prev) => prev.filter((et) => et.name !== name));
|
|
409
|
+
setRemoveFailure(null);
|
|
410
|
+
announceEntryTypeChange(`Entry type "${name}" removed`);
|
|
411
|
+
setFocusRequest({ row: neighbour });
|
|
412
|
+
}
|
|
413
|
+
else if (result.unanswered) {
|
|
414
|
+
setRemoveFailure(null);
|
|
415
|
+
// The re-read settles it when the entry type is gone.
|
|
416
|
+
const reloaded = await reloadEntryTypes(editingCollection.logicalPath);
|
|
417
|
+
const listed = reloaded?.map((et) => et.name);
|
|
418
|
+
if (listed && !listed.includes(name)) {
|
|
419
|
+
announceEntryTypeChange(`Entry type "${name}" removed`);
|
|
420
|
+
setFocusRequest({ row: neighbour && listed.includes(neighbour) ? neighbour : null });
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
setEntryTypeUnanswered(listed
|
|
424
|
+
? `"${name}" is still listed, so it may not have been removed yet. Remove it again to retry, or close and reopen this collection later to check.`
|
|
425
|
+
: `"${name}" may already have been removed. Close and reopen this collection to check before trying again.`);
|
|
426
|
+
setFocusRequest({ row: name });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
setRemoveFailure({ name, message: result.error });
|
|
431
|
+
setFocusRequest({ row: name });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
setRemoveFailure({ name, message: getErrorMessage(err) });
|
|
436
|
+
setFocusRequest({ row: name });
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
setRemoving(false);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}, [
|
|
443
|
+
displayEntryTypes,
|
|
444
|
+
editingCollection,
|
|
445
|
+
onRemoveEntryType,
|
|
446
|
+
announceEntryTypeChange,
|
|
447
|
+
reloadEntryTypes,
|
|
448
|
+
]);
|
|
449
|
+
const confirmRemoveEntryType = useCallback(async () => {
|
|
187
450
|
if (!deletingEntryType)
|
|
188
451
|
return;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
452
|
+
await removeEntryType(deletingEntryType.entryType.name);
|
|
453
|
+
closeRemoveConfirm();
|
|
454
|
+
}, [deletingEntryType, removeEntryType, closeRemoveConfirm]);
|
|
455
|
+
const removeMessage = deletingEntryType
|
|
456
|
+
? `Remove "${deletingEntryType.entryType.name}"? This saves immediately and cannot be undone.`
|
|
457
|
+
: '';
|
|
458
|
+
const removeBlockedReason = (entryType) => {
|
|
459
|
+
if (displayEntryTypes.length === 1) {
|
|
460
|
+
return 'A collection needs at least one entry type. Add another before removing this one.';
|
|
192
461
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
462
|
+
// The server refuses to remove an entry type that entries use. Staged ones have no entries.
|
|
463
|
+
const used = isEditMode ? usageOf(entryType) : 0;
|
|
464
|
+
return used > 0
|
|
465
|
+
? `${used} ${used === 1 ? 'entry uses' : 'entries use'} this entry type. Move or delete ${used === 1 ? 'it' : 'them'} first.`
|
|
466
|
+
: undefined;
|
|
467
|
+
};
|
|
468
|
+
// The hook's single `isLoading` flag drives `isSaving`, so it is also true while an
|
|
469
|
+
// entry-type action runs; the footer must not spin or close over that action's result.
|
|
470
|
+
const entryTypeBusy = entryTypeSaving || removing;
|
|
471
|
+
// Any request in flight, a details save included: the parent closes this dialog when a details
|
|
472
|
+
// save succeeds, so an entry-type action started meanwhile would report into a closed dialog.
|
|
473
|
+
const requestBusy = isSaving || entryTypeBusy;
|
|
474
|
+
// Mantine's Escape handler fires on every open Modal, so one Esc would close a sub-dialog and
|
|
475
|
+
// this dialog together, losing staged details. A request in flight must not be hidden either.
|
|
476
|
+
const dismissBlocked = requestBusy || entryTypeEditorOpen || deleteEntryTypeModalOpen;
|
|
477
|
+
const propErrorVisible = !!error && !errorDismissed;
|
|
478
|
+
const displayError = propErrorVisible ? error : validationError;
|
|
479
|
+
const errorTitle = propErrorVisible
|
|
480
|
+
? isEditMode
|
|
481
|
+
? "Couldn't save collection details"
|
|
482
|
+
: "Couldn't create collection"
|
|
483
|
+
: undefined;
|
|
484
|
+
// The comparisons handleSave uses to build its update, so "Discard changes" shows exactly when
|
|
485
|
+
// that update would carry a change.
|
|
486
|
+
const detailsDirty = editingCollection !== null &&
|
|
487
|
+
(formData.name !== editingCollection.name ||
|
|
488
|
+
formData.label !== (editingCollection.label || '') ||
|
|
489
|
+
(!!slug && slug !== slugOf(editingCollection.logicalPath)));
|
|
490
|
+
const createStaged = !isEditMode && (!!formData.name || !!formData.label || formData.entries.length > 0);
|
|
491
|
+
const dismissLabel = detailsDirty
|
|
492
|
+
? 'Discard changes'
|
|
493
|
+
: isEditMode && entryTypeChanged
|
|
494
|
+
? 'Close'
|
|
495
|
+
: 'Cancel';
|
|
496
|
+
const entryTypesSection = (_jsxs(Stack, { gap: "md", children: [_jsxs("div", { children: [_jsx(Title, { order: 3, size: "h5", tabIndex: -1, ref: entryTypesHeadingRef, children: "Entry types" }), isEditMode && (_jsx(Text, { size: "sm", c: "dimmed", children: "Changes here save to the branch immediately." }))] }), entryTypeUnanswered && (_jsx(Alert, { icon: _jsx(IconAlertTriangle, { size: 16 }), color: "yellow", title: "No answer from the server", withCloseButton: true, onClose: () => {
|
|
497
|
+
setEntryTypeUnanswered(null);
|
|
498
|
+
setFocusRequest({ row: null });
|
|
499
|
+
}, children: entryTypeUnanswered })), displayEntryTypes.length === 0 ? (_jsx(Paper, { p: "md", withBorder: true, children: _jsx(Text, { c: "dimmed", ta: "center", size: "sm", children: "No entry types yet. Add at least one to choose what kinds of entries this collection can hold." }) })) : (_jsx(Stack, { gap: "xs", children: displayEntryTypes.map((entryType, index) => (_jsx(Paper, { p: "sm", withBorder: true, children: _jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [_jsx(Group, { gap: "sm", children: _jsxs("div", { children: [_jsxs(Group, { gap: "xs", children: [_jsx(Text, { fw: 500, size: "sm", children: entryType.name }), entryType.default && (_jsx(Badge, { size: "xs", color: "blue", variant: "light", leftSection: _jsx(IconStar, { size: 10 }), children: "Default" })), entryType.maxItems === 1 && (_jsx(Badge, { size: "xs", color: "gray", variant: "light", children: "Singleton" }))] }), _jsxs(Text, { size: "xs", c: "dimmed", children: [entryType.format.toUpperCase(), " \u00B7 ", entryType.schema, entryType.label && ` · "${entryType.label}"`] })] }) }), _jsxs(Menu, { position: "bottom-end", withinPortal: true, onChange: (opened) => setMenuOpenFor(opened ? entryType.name : null), children: [_jsx(Tooltip, { label: `Actions for ${entryType.name}`, disabled: menuOpenFor === entryType.name, children: _jsx(Menu.Target, { children: _jsx(ActionIcon, { variant: "subtle", "aria-label": `Actions for ${entryType.name}`, disabled: requestBusy, ref: (el) => {
|
|
500
|
+
if (el)
|
|
501
|
+
triggerRefs.current.set(entryType.name, el);
|
|
502
|
+
else
|
|
503
|
+
triggerRefs.current.delete(entryType.name);
|
|
504
|
+
}, children: _jsx(IconDotsVertical, { size: 16, "aria-hidden": true }) }) }) }), _jsxs(Menu.Dropdown, { children: [_jsx(Menu.Item, { leftSection: _jsx(IconEdit, { size: 14 }), onClick: () => handleOpenEditEntryType(entryType, index), children: "Edit\u2026" }), _jsx(Menu.Divider, {}), _jsx(DestructiveMenuItem, { leftSection: _jsx(IconTrash, { size: 14 }), onClick: () => handleRemoveEntryType(entryType, index), blockedReason: removeBlockedReason(entryType), children: "Remove" })] })] })] }) }, entryType.name))) })), _jsxs(Group, { gap: 6, wrap: "nowrap", role: "status", mih: "calc(var(--mantine-font-size-sm) * var(--mantine-line-height-sm))", children: [isEditMode && entryTypeStatus && (_jsxs(_Fragment, { children: [_jsx(IconCheck, { size: 14, color: "var(--mantine-color-green-text)", "aria-hidden": true }), _jsx(Text, { size: "sm", children: entryTypeStatus })] })), !isEditMode && stagedRemoval && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", children: `Removed entry type "${stagedRemoval.entryType.name}".` }), _jsx(Button, { size: "compact-sm", variant: "subtle", onClick: undoStagedRemoval, "aria-label": `Undo removing entry type "${stagedRemoval.entryType.name}"`, children: "Undo" })] }))] }), removeFailure && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Couldn't remove entry type", withCloseButton: true, onClose: () => {
|
|
505
|
+
setRemoveFailure(null);
|
|
506
|
+
setFocusRequest({ row: null });
|
|
507
|
+
}, children: _jsxs(Stack, { gap: "xs", align: "flex-start", children: [_jsx(Text, { size: "sm", children: removeFailure.message }), _jsx(Button, { size: "xs", variant: "light", color: "red", loading: removing, disabled: requestBusy, onClick: () => void removeEntryType(removeFailure.name), children: "Retry" })] }) })), _jsx(Button, { size: "sm", variant: "light", leftSection: _jsx(IconPlus, { size: 16 }), onClick: handleOpenAddEntryType, disabled: requestBusy, children: "Add entry type\u2026" })] }));
|
|
508
|
+
return (_jsxs(_Fragment, { children: [_jsx(Modal, { opened: isOpen, onClose: dismissBlocked ? () => { } : onClose, closeOnClickOutside: !detailsDirty && !createStaged, title: isEditMode
|
|
509
|
+
? `Edit collection: ${editingCollection?.label || editingCollection?.name}`
|
|
510
|
+
: 'Create collection', size: "lg", children: _jsxs(Stack, { gap: "md", children: [showingRestored && restored && (_jsx(Alert, { icon: _jsx(IconRestore, { size: 16 }), color: "blue", title: "Restored what you entered", children: _jsxs(Stack, { gap: "xs", align: "flex-start", children: [_jsx(Text, { size: "sm", children: isEditMode
|
|
511
|
+
? "The server didn't answer your last save, and this collection doesn't show it, so your changes are filled in again."
|
|
512
|
+
: "The server didn't answer your last create, and the collection isn't in the content tree, so what you entered is filled in again." }), _jsx(Button, { size: "xs", variant: "light", onClick: discardRestored, children: "Discard changes" })] }) })), displayError && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", title: errorTitle, withCloseButton: true, onClose: () => propErrorVisible ? setErrorDismissed(true) : setValidationError(null), children: displayError })), isEditMode && (_jsxs("div", { children: [_jsx(Title, { order: 3, size: "h5", children: "Collection details" }), _jsx(Text, { size: "sm", c: "dimmed", children: "Saved when you select \"Save collection details\"." })] })), _jsx(TextInput, { ref: nameInputRef, label: "Name", description: "Machine-readable identifier (e.g., posts, pages, articles)", placeholder: "e.g. posts", value: formData.name, onChange: (e) => updateField('name', e.target.value), required: true }), _jsx(TextInput, { label: "Label", description: "Human-readable display name", placeholder: "e.g. Blog posts", value: formData.label, onChange: (e) => updateField('label', e.target.value) }), isEditMode && (_jsx(TextInput, { label: "Slug", description: "Directory name (filesystem path). Changing this renames the directory.", placeholder: "e.g. posts", value: slug, onChange: (e) => {
|
|
207
513
|
setSlug(e.target.value);
|
|
208
514
|
setValidationError(null);
|
|
209
|
-
} })), parentPath && !isEditMode && (_jsxs(Text, { size: "sm", c: "dimmed", children: ["This collection will be created inside: ", _jsx("strong", { children: parentPath })] })),
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
'usageCount' in deletingEntryType.entryType &&
|
|
216
|
-
deletingEntryType.entryType.usageCount
|
|
217
|
-
? `This entry type is used by ${deletingEntryType.entryType.usageCount} ${deletingEntryType.entryType.usageCount === 1 ? 'entry' : 'entries'}. Removing it will prevent editing those entries. Are you sure you want to remove this entry type?`
|
|
218
|
-
: 'Are you sure you want to remove this entry type? This cannot be undone.', confirmLabel: "Remove Entry Type", onConfirm: confirmRemoveEntryType, onClose: () => {
|
|
219
|
-
setDeleteEntryTypeModalOpen(false);
|
|
220
|
-
setDeletingEntryType(null);
|
|
221
|
-
} })] }));
|
|
515
|
+
} })), parentPath && !isEditMode && (_jsxs(Text, { size: "sm", c: "dimmed", children: ["This collection will be created inside: ", _jsx("strong", { children: parentPath })] })), isEditMode ? (_jsx(Paper, { withBorder: true, p: "md", children: entryTypesSection })) : (entryTypesSection), _jsxs(Group, { justify: "flex-end", gap: "sm", mt: "md", "data-testid": "collection-editor-footer", children: [_jsx(Button, { size: "sm", variant: "subtle", onClick: () => {
|
|
516
|
+
// "Discard changes" over refilled input discards the kept input too.
|
|
517
|
+
if (detailsDirty && showingRestored && restored)
|
|
518
|
+
onDiscardRestored?.();
|
|
519
|
+
onClose();
|
|
520
|
+
}, disabled: isSaving || entryTypeBusy, children: dismissLabel }), _jsx(Button, { size: "sm", onClick: handleSave, loading: isSaving && !entryTypeBusy, disabled: entryTypeBusy, children: isEditMode ? 'Save collection details' : 'Create collection' })] })] }) }), _jsx(EntryTypeEditor, { isOpen: entryTypeEditorOpen, editingEntryType: editingEntryType, availableSchemas: availableSchemas, existingEntryTypeNames: displayEntryTypes.map((et) => et.name), staged: !isEditMode, onSave: handleEntryTypeSave, onClose: dismissEntryTypeEditor, isSaving: entryTypeSaving, error: entryTypeError, warning: entryTypeWarning }), _jsx(ConfirmDeleteModal, { isOpen: deleteEntryTypeModalOpen, title: "Remove entry type", message: removeMessage, confirmLabel: "Remove entry type", onConfirm: confirmRemoveEntryType, onClose: removing ? () => { } : cancelRemoveConfirm, loading: removing })] }));
|
|
222
521
|
}
|
|
@@ -24,9 +24,13 @@ export interface EntryTypeEditorProps {
|
|
|
24
24
|
availableSchemas: string[];
|
|
25
25
|
/** Existing entry type names in the collection (for duplicate validation) */
|
|
26
26
|
existingEntryTypeNames?: string[];
|
|
27
|
+
/** The caller holds an edit until its own save, so confirming it updates rather than saves. */
|
|
28
|
+
staged?: boolean;
|
|
27
29
|
onSave: (data: CreateEntryTypeInput | UpdateEntryTypeInput, isNew: boolean) => void;
|
|
28
30
|
onClose: () => void;
|
|
29
31
|
isSaving?: boolean;
|
|
30
32
|
error?: string | null;
|
|
33
|
+
/** A note above the kept form that the last save's outcome is unknown, and how to retry. */
|
|
34
|
+
warning?: string | null;
|
|
31
35
|
}
|
|
32
|
-
export declare function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, existingEntryTypeNames, onSave, onClose, isSaving, error, }: EntryTypeEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
export declare function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, existingEntryTypeNames, staged, onSave, onClose, isSaving, error, warning, }: EntryTypeEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,14 +9,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
*/
|
|
10
10
|
import { useState, useCallback, useEffect } from 'react';
|
|
11
11
|
import { Modal, Stack, TextInput, Select, Switch, NumberInput, Group, Button, Text, Alert, Tooltip, } from '@mantine/core';
|
|
12
|
-
import { IconAlertCircle, IconLock } from '@tabler/icons-react';
|
|
12
|
+
import { IconAlertCircle, IconAlertTriangle, IconLock } from '@tabler/icons-react';
|
|
13
13
|
const FORMAT_OPTIONS = [
|
|
14
14
|
{ value: 'json', label: 'JSON' },
|
|
15
15
|
{ value: 'yaml', label: 'YAML' },
|
|
16
16
|
{ value: 'md', label: 'Markdown' },
|
|
17
17
|
{ value: 'mdx', label: 'MDX' },
|
|
18
18
|
];
|
|
19
|
-
export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, existingEntryTypeNames = [], onSave, onClose, isSaving = false, error = null, }) {
|
|
19
|
+
export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, existingEntryTypeNames = [], staged = false, onSave, onClose, isSaving = false, error = null, warning = null, }) {
|
|
20
20
|
const isEditMode = editingEntryType !== null;
|
|
21
21
|
const usageCount = editingEntryType?.usageCount ?? 0;
|
|
22
22
|
const isLocked = isEditMode && usageCount > 0;
|
|
@@ -29,6 +29,9 @@ export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, ex
|
|
|
29
29
|
maxItems: undefined,
|
|
30
30
|
});
|
|
31
31
|
const [validationError, setValidationError] = useState(null);
|
|
32
|
+
// A refresh hands over a new `availableSchemas` array after every schema change; keying the reset
|
|
33
|
+
// on its first value rather than its identity keeps what the user is typing.
|
|
34
|
+
const defaultSchema = availableSchemas[0] || '';
|
|
32
35
|
/* eslint-disable react-hooks/set-state-in-effect -- intentional: sync form state from props on open */
|
|
33
36
|
useEffect(() => {
|
|
34
37
|
if (isOpen) {
|
|
@@ -47,14 +50,14 @@ export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, ex
|
|
|
47
50
|
name: '',
|
|
48
51
|
label: '',
|
|
49
52
|
format: 'json',
|
|
50
|
-
schema:
|
|
53
|
+
schema: defaultSchema,
|
|
51
54
|
default: false,
|
|
52
55
|
maxItems: undefined,
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
setValidationError(null);
|
|
56
59
|
}
|
|
57
|
-
}, [isOpen, editingEntryType,
|
|
60
|
+
}, [isOpen, editingEntryType, defaultSchema]);
|
|
58
61
|
/* eslint-enable react-hooks/set-state-in-effect */
|
|
59
62
|
const updateField = useCallback((field, value) => {
|
|
60
63
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
@@ -128,7 +131,9 @@ export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, ex
|
|
|
128
131
|
label: key,
|
|
129
132
|
}));
|
|
130
133
|
const displayError = error || validationError;
|
|
131
|
-
return (_jsx(Modal, { opened: isOpen, onClose: onClose, title: isEditMode
|
|
134
|
+
return (_jsx(Modal, { opened: isOpen, onClose: onClose, title: isEditMode
|
|
135
|
+
? `Edit entry type: ${editingEntryType?.label || editingEntryType?.name}`
|
|
136
|
+
: 'Add entry type', size: "md", children: _jsxs(Stack, { gap: "md", children: [warning && !displayError && (_jsx(Alert, { icon: _jsx(IconAlertTriangle, { size: 16 }), color: "yellow", title: "No answer from the server", children: warning })), displayError && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Error", withCloseButton: true, onClose: () => setValidationError(null), children: displayError })), _jsx(TextInput, { label: "Name", description: "Machine-readable identifier (e.g., post, page, article)", placeholder: "e.g. post", value: formData.name, onChange: (e) => updateField('name', e.target.value), disabled: isEditMode, required: !isEditMode }), _jsx(TextInput, { label: "Label", description: "Human-readable display name", placeholder: "e.g. Blog post", value: formData.label, onChange: (e) => updateField('label', e.target.value) }), _jsx(Tooltip, { label: isLocked
|
|
132
137
|
? `Cannot change format: ${usageCount} ${usageCount === 1 ? 'entry' : 'entries'} use this type`
|
|
133
138
|
: '', disabled: !isLocked, withinPortal: true, children: _jsx(Select, { label: "Format", description: isLocked
|
|
134
139
|
? `Locked (${usageCount} ${usageCount === 1 ? 'entry' : 'entries'} exist)`
|
|
@@ -136,5 +141,5 @@ export function EntryTypeEditor({ isOpen, editingEntryType, availableSchemas, ex
|
|
|
136
141
|
? `Cannot change schema: ${usageCount} ${usageCount === 1 ? 'entry' : 'entries'} use this type`
|
|
137
142
|
: '', disabled: !isLocked, withinPortal: true, children: _jsx(Select, { label: "Schema", description: isLocked
|
|
138
143
|
? `Locked (${usageCount} ${usageCount === 1 ? 'entry' : 'entries'} exist)`
|
|
139
|
-
: 'Field definitions for this entry type', data: schemaOptions, value: formData.schema, onChange: (value) => value && updateField('schema', value), searchable: true, required: true, placeholder: "Select a schema", disabled: isLocked, rightSection: isLocked ? _jsx(IconLock, { size: 14 }) : undefined }) }), _jsx(Switch, { label: "Default entry type", description: "Use this type when adding new items to the collection", checked: formData.default, onChange: (e) => updateField('default', e.currentTarget.checked) }), _jsx(NumberInput, { label: "Max
|
|
144
|
+
: 'Field definitions for this entry type', data: schemaOptions, value: formData.schema, onChange: (value) => value && updateField('schema', value), searchable: true, required: true, placeholder: "Select a schema", disabled: isLocked, rightSection: isLocked ? _jsx(IconLock, { size: 14 }) : undefined }) }), _jsx(Switch, { label: "Default entry type", description: "Use this type when adding new items to the collection", checked: formData.default, onChange: (e) => updateField('default', e.currentTarget.checked) }), _jsx(NumberInput, { label: "Max items", description: "Limit number of items (leave empty for unlimited, use 1 for singleton-like behavior)", placeholder: "Unlimited", value: formData.maxItems ?? '', onChange: (value) => updateField('maxItems', typeof value === 'number' ? value : undefined), min: 1, allowNegative: false, allowDecimal: false }), formData.maxItems === 1 && (_jsx(Text, { size: "xs", c: "dimmed", children: "With maxItems=1, this entry type behaves like a singleton (e.g., homepage, settings)" })), _jsxs(Group, { justify: "flex-end", gap: "sm", mt: "md", children: [_jsx(Button, { size: "sm", variant: "subtle", onClick: onClose, disabled: isSaving, children: "Cancel" }), _jsx(Button, { size: "sm", onClick: handleSave, loading: isSaving, children: isEditMode ? (staged ? 'Update entry type' : 'Save entry type') : 'Add entry type' })] })] }) }));
|
|
140
145
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LogicalPath } from '../../paths/types.js';
|
|
2
|
+
import type { CreateCollectionInput, UpdateCollectionInput } from '../../schema/schema-store-types.js';
|
|
3
|
+
import type { EditorCollection } from '../Editor.js';
|
|
4
|
+
/**
|
|
5
|
+
* What was sent by a collection create or details save that got no answer, so the dialog can fill
|
|
6
|
+
* it in again. `Editor.tsx` keeps it until the dialog's opener reads it landed, a save of the same
|
|
7
|
+
* thing succeeds, or the user discards it.
|
|
8
|
+
*/
|
|
9
|
+
export type CollectionDraft = {
|
|
10
|
+
branch: string;
|
|
11
|
+
} & ({
|
|
12
|
+
kind: 'create';
|
|
13
|
+
input: CreateCollectionInput;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'details';
|
|
16
|
+
collectionPath: LogicalPath;
|
|
17
|
+
updates: UpdateCollectionInput;
|
|
18
|
+
});
|
|
19
|
+
/** The directory name in a logical path, without its embedded id ("posts.abc123" -> "posts"). */
|
|
20
|
+
export declare function slugOf(logicalPath: LogicalPath): string;
|
|
21
|
+
/** Whether `collections`, read from the draft's branch, show the draft's write. */
|
|
22
|
+
export declare function collectionDraftLanded(collections: readonly EditorCollection[], draft: CollectionDraft): boolean;
|