canopycms 0.0.67-int.90 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/ai/generate.js +1 -27
- package/dist/ai/handler.d.ts +0 -4
- package/dist/ai/handler.js +0 -11
- package/dist/ai/index.d.ts +1 -8
- package/dist/ai/index.js +1 -8
- package/dist/ai/json-to-markdown.js +15 -58
- package/dist/ai/resolve-branch.d.ts +2 -7
- package/dist/ai/resolve-branch.js +2 -8
- package/dist/ai/strip-mdx.d.ts +5 -13
- package/dist/ai/strip-mdx.js +5 -17
- package/dist/ai/to-plain-text.d.ts +10 -28
- package/dist/ai/to-plain-text.js +10 -28
- package/dist/ai/transform-components.d.ts +2 -4
- package/dist/ai/transform-components.js +8 -19
- package/dist/ai/types.d.ts +5 -10
- package/dist/api/admin-branch-health.d.ts +4 -12
- package/dist/api/admin-branch-health.js +26 -66
- package/dist/api/admin.d.ts +4 -12
- package/dist/api/admin.js +10 -26
- package/dist/api/assets.d.ts +0 -25
- package/dist/api/assets.js +50 -91
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +0 -2
- package/dist/api/branch-review.d.ts +0 -8
- package/dist/api/branch-review.js +0 -13
- package/dist/api/branch-status.d.ts +0 -3
- package/dist/api/branch-status.js +8 -33
- package/dist/api/branch-withdraw.d.ts +0 -1
- package/dist/api/branch-withdraw.js +6 -9
- package/dist/api/branch.d.ts +7 -12
- package/dist/api/branch.js +7 -44
- package/dist/api/client.d.ts +71 -224
- package/dist/api/client.js +82 -233
- package/dist/api/comments.d.ts +2 -4
- package/dist/api/comments.js +0 -17
- package/dist/api/content.d.ts +0 -6
- package/dist/api/content.js +66 -142
- package/dist/api/entries-constants.d.ts +5 -9
- package/dist/api/entries-constants.js +5 -9
- package/dist/api/entries.d.ts +8 -13
- package/dist/api/entries.js +20 -51
- package/dist/api/github-sync.d.ts +2 -4
- package/dist/api/github-sync.js +4 -8
- package/dist/api/groups.d.ts +3 -0
- package/dist/api/groups.js +8 -21
- package/dist/api/guards.d.ts +4 -16
- package/dist/api/guards.js +3 -22
- package/dist/api/index.d.ts +4 -4
- package/dist/api/permissions.d.ts +2 -0
- package/dist/api/permissions.js +18 -40
- package/dist/api/reference-options.d.ts +0 -1
- package/dist/api/reference-options.js +3 -11
- package/dist/api/request-body-hash.d.ts +12 -23
- package/dist/api/request-body-hash.js +12 -23
- package/dist/api/resolve-references.d.ts +0 -1
- package/dist/api/resolve-references.js +13 -28
- package/dist/api/route-builder.d.ts +3 -7
- package/dist/api/route-builder.js +1 -1
- package/dist/api/routes.d.ts +12 -0
- package/dist/api/routes.js +47 -0
- package/dist/api/schema.d.ts +23 -33
- package/dist/api/schema.js +20 -74
- package/dist/api/settings-helpers.js +4 -5
- package/dist/api/types.d.ts +0 -5
- package/dist/api/user.js +0 -5
- package/dist/api/validators.d.ts +14 -52
- package/dist/api/validators.js +14 -52
- package/dist/assets/asset-prefixes.d.ts +4 -8
- package/dist/assets/asset-prefixes.js +4 -8
- package/dist/assets/asset-src.d.ts +5 -22
- package/dist/assets/asset-src.js +5 -21
- package/dist/assets/factory.d.ts +1 -2
- package/dist/assets/factory.js +1 -2
- package/dist/assets/finalize.d.ts +3 -1
- package/dist/assets/finalize.js +3 -1
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/index.js +1 -1
- package/dist/assets/keys.d.ts +5 -3
- package/dist/assets/keys.js +6 -3
- package/dist/assets/pipeline.d.ts +3 -2
- package/dist/assets/pipeline.js +22 -56
- package/dist/assets/sharp-loader.d.ts +2 -10
- package/dist/assets/sharp-loader.js +2 -10
- package/dist/assets/store-local.d.ts +1 -2
- package/dist/assets/store-local.js +1 -2
- package/dist/assets/store-s3.d.ts +3 -4
- package/dist/assets/svg-sanitizer.d.ts +6 -14
- package/dist/assets/svg-sanitizer.js +6 -14
- package/dist/assets/transform-directives.d.ts +3 -4
- package/dist/assets/transform-directives.js +1 -1
- package/dist/assets/transform.d.ts +19 -30
- package/dist/assets/transform.js +18 -31
- package/dist/auth/cache.d.ts +2 -3
- package/dist/auth/cache.js +2 -3
- package/dist/auth/caching-auth-plugin.d.ts +11 -22
- package/dist/auth/caching-auth-plugin.js +12 -14
- package/dist/auth/context-helpers.d.ts +6 -20
- package/dist/auth/context-helpers.js +5 -17
- package/dist/auth/file-based-auth-cache.d.ts +10 -21
- package/dist/auth/file-based-auth-cache.js +23 -51
- package/dist/auth/plugin.d.ts +29 -61
- package/dist/auth/plugin.js +9 -13
- package/dist/auth/types.d.ts +11 -20
- package/dist/authorization/branch.d.ts +30 -49
- package/dist/authorization/branch.js +16 -42
- package/dist/authorization/content.d.ts +11 -40
- package/dist/authorization/content.js +13 -45
- package/dist/authorization/groups/index.d.ts +0 -3
- package/dist/authorization/groups/index.js +0 -3
- package/dist/authorization/groups/loader.d.ts +14 -28
- package/dist/authorization/groups/loader.js +14 -38
- package/dist/authorization/groups/schema.d.ts +0 -6
- package/dist/authorization/groups/schema.js +4 -7
- package/dist/authorization/helpers.d.ts +9 -28
- package/dist/authorization/helpers.js +9 -28
- package/dist/authorization/index.d.ts +3 -25
- package/dist/authorization/index.js +3 -35
- package/dist/authorization/path.d.ts +6 -13
- package/dist/authorization/path.js +8 -25
- package/dist/authorization/permissions/index.d.ts +0 -3
- package/dist/authorization/permissions/index.js +0 -3
- package/dist/authorization/permissions/loader.d.ts +14 -27
- package/dist/authorization/permissions/loader.js +14 -32
- package/dist/authorization/permissions/schema.d.ts +1 -6
- package/dist/authorization/permissions/schema.js +6 -13
- package/dist/authorization/protected-branch.d.ts +36 -65
- package/dist/authorization/protected-branch.js +29 -41
- package/dist/authorization/settings-file-store.d.ts +38 -81
- package/dist/authorization/settings-file-store.js +38 -79
- package/dist/authorization/types.d.ts +4 -26
- package/dist/authorization/types.js +0 -6
- package/dist/authorization/validation.d.ts +1 -23
- package/dist/authorization/validation.js +1 -24
- package/dist/branch-health.d.ts +39 -67
- package/dist/branch-health.js +27 -43
- package/dist/branch-metadata-file.d.ts +16 -32
- package/dist/branch-metadata-file.js +13 -26
- package/dist/branch-metadata.d.ts +49 -80
- package/dist/branch-metadata.js +57 -87
- package/dist/branch-registry.d.ts +26 -64
- package/dist/branch-registry.js +44 -93
- package/dist/branch-schema-cache.d.ts +34 -124
- package/dist/branch-schema-cache.js +49 -155
- package/dist/branch-workspace.d.ts +4 -4
- package/dist/branch-workspace.js +15 -19
- package/dist/build/generate-ai-content.d.ts +1 -0
- package/dist/build/generate-ai-content.js +12 -25
- package/dist/build-canopy.d.ts +17 -27
- package/dist/build-canopy.js +16 -23
- package/dist/build-mode.d.ts +25 -27
- package/dist/build-mode.js +25 -29
- package/dist/cli/cli.d.ts +15 -26
- package/dist/cli/cli.js +601 -978
- package/dist/cli/generate-ai-content.js +351 -628
- package/dist/cli/github-app-manifest.d.ts +84 -0
- package/dist/cli/github-app-manifest.js +161 -0
- package/dist/cli/init-github-app.d.ts +110 -444
- package/dist/cli/init-github-app.js +236 -773
- package/dist/cli/init.js +39 -80
- package/dist/cli/migrate.d.ts +13 -10
- package/dist/cli/migrate.js +22 -30
- package/dist/cli/project-detect.d.ts +17 -31
- package/dist/cli/project-detect.js +31 -42
- package/dist/cli/project-root.d.ts +4 -11
- package/dist/cli/project-root.js +4 -11
- package/dist/cli/prompt.d.ts +10 -0
- package/dist/cli/prompt.js +67 -0
- package/dist/cli/sync.d.ts +0 -8
- package/dist/cli/sync.js +8 -28
- package/dist/cli/template-files/middleware.ts.template +15 -29
- package/dist/cli/templates.d.ts +2 -12
- package/dist/cli/templates.js +4 -4
- package/dist/client.d.ts +1 -1
- package/dist/comment-store.d.ts +32 -53
- package/dist/comment-store.js +32 -52
- package/dist/config/flatten.d.ts +0 -3
- package/dist/config/flatten.js +2 -12
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -4
- package/dist/config/schemas/collection.d.ts +0 -8
- package/dist/config/schemas/collection.js +0 -10
- package/dist/config/schemas/config.d.ts +6 -78
- package/dist/config/schemas/config.js +38 -54
- package/dist/config/schemas/field.d.ts +2 -242
- package/dist/config/schemas/field.js +9 -20
- package/dist/config/schemas/media.js +1 -1
- package/dist/config/schemas/permissions.js +0 -2
- package/dist/config/schemas/url.d.ts +5 -6
- package/dist/config/schemas/url.js +5 -6
- package/dist/config/types.d.ts +45 -33
- package/dist/config/types.js +0 -1
- package/dist/config.d.ts +1 -8
- package/dist/config.js +1 -9
- package/dist/content-id-index.d.ts +76 -188
- package/dist/content-id-index.js +79 -230
- package/dist/content-index-generation.d.ts +27 -48
- package/dist/content-index-generation.js +35 -84
- package/dist/content-index-registry.d.ts +15 -24
- package/dist/content-index-registry.js +5 -9
- package/dist/content-listing.d.ts +64 -107
- package/dist/content-listing.js +68 -118
- package/dist/content-reader.d.ts +20 -29
- package/dist/content-reader.js +10 -18
- package/dist/content-store.d.ts +210 -336
- package/dist/content-store.js +413 -657
- package/dist/content-tree.d.ts +44 -86
- package/dist/content-tree.js +26 -54
- package/dist/context.d.ts +67 -94
- package/dist/context.js +53 -72
- package/dist/dev-content-watcher.d.ts +18 -18
- package/dist/dev-content-watcher.js +41 -44
- package/dist/editor/BranchManager.d.ts +3 -6
- package/dist/editor/BranchManager.js +24 -44
- package/dist/editor/CanopyEditor.js +5 -2
- package/dist/editor/CommentsPanel.js +0 -2
- package/dist/editor/Editor.js +19 -76
- package/dist/editor/EditorAuthGate.d.ts +27 -0
- package/dist/editor/EditorAuthGate.js +140 -0
- package/dist/editor/EntryNavigator.js +0 -20
- package/dist/editor/FormRenderer.d.ts +1 -2
- package/dist/editor/FormRenderer.js +8 -23
- package/dist/editor/admin/SystemHealthPanel.js +20 -37
- package/dist/editor/admin/useSystemHealth.d.ts +2 -3
- package/dist/editor/admin/useSystemHealth.js +10 -12
- package/dist/editor/client-reference-resolver.d.ts +2 -5
- package/dist/editor/client-reference-resolver.js +2 -19
- package/dist/editor/comments/BranchComments.js +0 -4
- package/dist/editor/comments/EntryComments.js +0 -4
- package/dist/editor/comments/FieldWrapper.js +0 -4
- package/dist/editor/comments/InlineCommentThread.js +0 -2
- package/dist/editor/comments/ThreadCarousel.d.ts +0 -58
- package/dist/editor/comments/ThreadCarousel.js +0 -12
- package/dist/editor/components/EditorFooter.d.ts +0 -8
- package/dist/editor/components/EditorFooter.js +0 -5
- package/dist/editor/components/EditorHeader.d.ts +0 -31
- package/dist/editor/components/EditorHeader.js +21 -71
- package/dist/editor/components/EditorSidebar.d.ts +0 -19
- package/dist/editor/components/EditorSidebar.js +0 -16
- package/dist/editor/components/EntryCreateModal.d.ts +4 -4
- package/dist/editor/components/EntryCreateModal.js +7 -22
- package/dist/editor/components/RenameEntryModal.js +0 -4
- package/dist/editor/components/UserBadge.js +0 -10
- package/dist/editor/components/index.js +0 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -7
- package/dist/editor/context/ApiClientContext.js +39 -11
- package/dist/editor/context/AssetContext.d.ts +7 -17
- package/dist/editor/context/AssetContext.js +7 -17
- package/dist/editor/context/EditorIdentityContext.d.ts +13 -0
- package/dist/editor/context/EditorIdentityContext.js +7 -0
- package/dist/editor/context/EditorStateContext.d.ts +10 -41
- package/dist/editor/context/EditorStateContext.js +7 -23
- package/dist/editor/context/SWRProvider.d.ts +3 -6
- package/dist/editor/context/index.d.ts +1 -20
- package/dist/editor/context/index.js +1 -20
- package/dist/editor/editor-utils.d.ts +8 -47
- package/dist/editor/editor-utils.js +19 -72
- package/dist/editor/fields/BlockField.d.ts +2 -2
- package/dist/editor/fields/BlockField.js +1 -2
- package/dist/editor/fields/CodeField.d.ts +0 -1
- package/dist/editor/fields/CodeField.js +0 -1
- package/dist/editor/fields/DateTimeField.d.ts +15 -22
- package/dist/editor/fields/DateTimeField.js +17 -26
- package/dist/editor/fields/ImageField.d.ts +2 -2
- package/dist/editor/fields/ImageField.js +8 -14
- package/dist/editor/fields/InlineGroupField.d.ts +0 -1
- package/dist/editor/fields/InlineGroupField.js +0 -1
- package/dist/editor/fields/MarkdownField.d.ts +0 -1
- package/dist/editor/fields/MarkdownField.js +6 -11
- package/dist/editor/fields/MdxImageDialog.d.ts +1 -1
- package/dist/editor/fields/MdxImageDialog.js +0 -1
- package/dist/editor/fields/NumberField.d.ts +9 -19
- package/dist/editor/fields/NumberField.js +11 -22
- package/dist/editor/fields/NumberListField.d.ts +7 -10
- package/dist/editor/fields/NumberListField.js +7 -10
- package/dist/editor/fields/ObjectField.d.ts +5 -8
- package/dist/editor/fields/ObjectField.js +0 -1
- package/dist/editor/fields/ReferenceField.d.ts +1 -1
- package/dist/editor/fields/ReferenceField.js +11 -16
- package/dist/editor/fields/SelectField.d.ts +2 -2
- package/dist/editor/fields/SelectField.js +0 -1
- package/dist/editor/fields/StringListField.d.ts +0 -4
- package/dist/editor/fields/StringListField.js +4 -9
- package/dist/editor/fields/TextField.d.ts +0 -1
- package/dist/editor/fields/TextField.js +0 -1
- package/dist/editor/fields/ToggleField.d.ts +0 -1
- package/dist/editor/fields/ToggleField.js +0 -1
- package/dist/editor/fields/entry-link/EntryLinkContext.js +2 -4
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +0 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.js +0 -4
- package/dist/editor/group-manager/ExternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/ExternalGroupsTab.js +0 -1
- package/dist/editor/group-manager/GroupCard.d.ts +0 -3
- package/dist/editor/group-manager/GroupForm.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.js +0 -2
- package/dist/editor/group-manager/MemberList.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.js +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.js +0 -5
- package/dist/editor/group-manager/hooks/useUserSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useUserSearch.js +0 -3
- package/dist/editor/group-manager/index.d.ts +0 -6
- package/dist/editor/group-manager/index.js +0 -10
- package/dist/editor/group-manager/types.d.ts +2 -5
- package/dist/editor/group-manager/types.js +0 -3
- package/dist/editor/hooks/index.d.ts +6 -11
- package/dist/editor/hooks/index.js +6 -12
- package/dist/editor/hooks/useBranchActions.d.ts +0 -14
- package/dist/editor/hooks/useBranchActions.js +0 -22
- package/dist/editor/hooks/useBranchManager.d.ts +2 -48
- package/dist/editor/hooks/useBranchManager.js +4 -64
- package/dist/editor/hooks/useBranchesData.d.ts +7 -6
- package/dist/editor/hooks/useBranchesData.js +7 -6
- package/dist/editor/hooks/useCommentSystem.d.ts +0 -49
- package/dist/editor/hooks/useCommentSystem.js +1 -36
- package/dist/editor/hooks/useDraftManager.d.ts +2 -35
- package/dist/editor/hooks/useDraftManager.js +23 -64
- package/dist/editor/hooks/useEditorLayout.d.ts +0 -20
- package/dist/editor/hooks/useEditorLayout.js +0 -23
- package/dist/editor/hooks/useEntriesData.d.ts +1 -0
- package/dist/editor/hooks/useEntriesData.js +2 -3
- package/dist/editor/hooks/useEntryLinkResolution.d.ts +2 -2
- package/dist/editor/hooks/useEntryLinkResolution.js +2 -2
- package/dist/editor/hooks/useEntryManager.d.ts +2 -27
- package/dist/editor/hooks/useEntryManager.js +29 -78
- package/dist/editor/hooks/useGroupManager.d.ts +0 -21
- package/dist/editor/hooks/useGroupManager.js +0 -21
- package/dist/editor/hooks/usePermissionManager.d.ts +0 -20
- package/dist/editor/hooks/usePermissionManager.js +0 -20
- package/dist/editor/hooks/useReferenceResolution.d.ts +2 -33
- package/dist/editor/hooks/useReferenceResolution.js +1 -48
- package/dist/editor/hooks/useSchemaManager.d.ts +8 -27
- package/dist/editor/hooks/useSchemaManager.js +0 -19
- package/dist/editor/hooks/useUserContext.d.ts +2 -9
- package/dist/editor/hooks/useUserContext.js +10 -10
- package/dist/editor/hooks/useUserMetadata.d.ts +0 -2
- package/dist/editor/hooks/useUserMetadata.js +0 -3
- package/dist/editor/media/AssetCard.d.ts +0 -1
- package/dist/editor/media/AssetCard.js +1 -2
- package/dist/editor/media/CropStep.d.ts +0 -1
- package/dist/editor/media/CropStep.js +0 -1
- package/dist/editor/media/MediaLibrary.d.ts +1 -2
- package/dist/editor/media/MediaLibrary.js +1 -2
- package/dist/editor/media/MediaLibraryBody.d.ts +0 -1
- package/dist/editor/media/MediaLibraryBody.js +0 -1
- package/dist/editor/media/crop-math.d.ts +1 -1
- package/dist/editor/media/crop-math.js +1 -1
- package/dist/editor/permission-manager/GroupSelector.d.ts +0 -3
- package/dist/editor/permission-manager/PermissionEditor.d.ts +0 -4
- package/dist/editor/permission-manager/PermissionEditor.js +0 -1
- package/dist/editor/permission-manager/PermissionLevelBadge.d.ts +0 -7
- package/dist/editor/permission-manager/PermissionLevelBadge.js +0 -4
- package/dist/editor/permission-manager/PermissionTree.d.ts +0 -4
- package/dist/editor/permission-manager/UserSelector.d.ts +0 -3
- package/dist/editor/permission-manager/constants.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +0 -10
- package/dist/editor/permission-manager/hooks/usePermissionTree.d.ts +0 -14
- package/dist/editor/permission-manager/hooks/usePermissionTree.js +0 -11
- package/dist/editor/permission-manager/index.d.ts +0 -7
- package/dist/editor/permission-manager/index.js +0 -16
- package/dist/editor/permission-manager/types.d.ts +2 -12
- package/dist/editor/permission-manager/types.js +0 -3
- package/dist/editor/permission-manager/utils.d.ts +0 -28
- package/dist/editor/permission-manager/utils.js +9 -23
- package/dist/editor/preview-bridge.d.ts +5 -1
- package/dist/editor/preview-bridge.js +4 -4
- package/dist/editor/schema-editor/CollectionEditor.d.ts +0 -5
- package/dist/editor/schema-editor/CollectionEditor.js +9 -31
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +0 -6
- package/dist/editor/schema-editor/EntryTypeEditor.js +3 -24
- package/dist/editor/schema-editor/index.d.ts +0 -7
- package/dist/editor/schema-editor/index.js +0 -7
- package/dist/editor/utils/env.d.ts +3 -18
- package/dist/editor/utils/env.js +6 -25
- package/dist/entry-link-resolver.d.ts +13 -35
- package/dist/entry-link-resolver.js +20 -53
- package/dist/entry-schema-registry.d.ts +20 -60
- package/dist/entry-schema-registry.js +20 -69
- package/dist/entry-schema.d.ts +118 -223
- package/dist/entry-schema.js +46 -91
- package/dist/git-manager.d.ts +101 -200
- package/dist/git-manager.js +211 -384
- package/dist/github-service.d.ts +34 -75
- package/dist/github-service.js +37 -91
- package/dist/http/handler.d.ts +6 -37
- package/dist/http/handler.js +65 -120
- package/dist/http/router.d.ts +15 -32
- package/dist/http/router.js +34 -99
- package/dist/http/types.d.ts +15 -40
- package/dist/http/types.js +3 -7
- package/dist/id.d.ts +4 -10
- package/dist/id.js +4 -10
- package/dist/index.js +6 -9
- package/dist/operating-mode/client-safe-strategy.d.ts +7 -14
- package/dist/operating-mode/client-safe-strategy.js +8 -28
- package/dist/operating-mode/client-unsafe-strategy.d.ts +6 -16
- package/dist/operating-mode/client-unsafe-strategy.js +9 -34
- package/dist/operating-mode/client.d.ts +3 -10
- package/dist/operating-mode/client.js +3 -8
- package/dist/operating-mode/deployment-name-fixtures.d.ts +14 -18
- package/dist/operating-mode/deployment-name-fixtures.js +14 -18
- package/dist/operating-mode/deployment-name.d.ts +19 -29
- package/dist/operating-mode/deployment-name.js +29 -47
- package/dist/operating-mode/index.d.ts +4 -25
- package/dist/operating-mode/index.js +9 -31
- package/dist/operating-mode/mode-env.d.ts +32 -54
- package/dist/operating-mode/mode-env.js +36 -58
- package/dist/operating-mode/types.d.ts +24 -72
- package/dist/operating-mode/types.js +1 -7
- package/dist/paths/branch-name.d.ts +30 -40
- package/dist/paths/branch-name.js +30 -40
- package/dist/paths/branch.d.ts +4 -18
- package/dist/paths/branch.js +6 -22
- package/dist/paths/index.d.ts +2 -13
- package/dist/paths/index.js +3 -23
- package/dist/paths/normalize-server.d.ts +3 -15
- package/dist/paths/normalize-server.js +3 -15
- package/dist/paths/normalize.d.ts +13 -33
- package/dist/paths/normalize.js +13 -33
- package/dist/paths/resolve.d.ts +4 -17
- package/dist/paths/resolve.js +4 -22
- package/dist/paths/types.d.ts +12 -34
- package/dist/paths/types.js +5 -7
- package/dist/paths/validation.d.ts +22 -119
- package/dist/paths/validation.js +30 -148
- package/dist/reference-resolver.d.ts +0 -13
- package/dist/reference-resolver.js +0 -18
- package/dist/resolve-canopy-user.d.ts +16 -18
- package/dist/resolve-canopy-user.js +17 -26
- package/dist/resource-generation.d.ts +16 -25
- package/dist/resource-generation.js +34 -97
- package/dist/schema/meta-loader.d.ts +2 -14
- package/dist/schema/meta-loader.js +15 -64
- package/dist/schema/resolver.d.ts +1 -4
- package/dist/schema/resolver.js +1 -6
- package/dist/schema/schema-store-types.d.ts +0 -12
- package/dist/schema/schema-store.d.ts +61 -138
- package/dist/schema/schema-store.js +85 -282
- package/dist/schema/types.d.ts +0 -4
- package/dist/server.d.ts +62 -93
- package/dist/server.js +63 -94
- package/dist/services.d.ts +23 -47
- package/dist/services.js +55 -98
- package/dist/settings-workspace.d.ts +17 -30
- package/dist/settings-workspace.js +51 -81
- package/dist/static/index.d.ts +26 -52
- package/dist/static/index.js +33 -67
- package/dist/{worker/task-queue.d.ts → task-queue/cms-task-queue.d.ts} +7 -9
- package/dist/task-queue/cms-task-queue.js +13 -0
- package/dist/task-queue/task-queue-config.d.ts +3 -0
- package/dist/{worker → task-queue}/task-queue-config.js +1 -6
- package/dist/task-queue/task-queue.d.ts +16 -22
- package/dist/task-queue/task-queue.js +20 -44
- package/dist/task-queue/types.d.ts +0 -1
- package/dist/task-queue/worker-status.d.ts +35 -0
- package/dist/task-queue/worker-status.js +40 -0
- package/dist/types.d.ts +40 -51
- package/dist/url-collision.d.ts +26 -40
- package/dist/url-collision.js +47 -63
- package/dist/url-exclusivity-fixtures.d.ts +15 -20
- package/dist/url-exclusivity-fixtures.js +14 -19
- package/dist/url-path-resolver.d.ts +8 -16
- package/dist/url-path-resolver.js +19 -29
- package/dist/user.d.ts +8 -21
- package/dist/user.js +10 -19
- package/dist/utils/async-mutex.js +4 -6
- package/dist/utils/atomic-write.d.ts +2 -11
- package/dist/utils/atomic-write.js +2 -11
- package/dist/utils/body-field.d.ts +8 -15
- package/dist/utils/body-field.js +8 -15
- package/dist/utils/content-serialize.d.ts +18 -24
- package/dist/utils/content-serialize.js +109 -160
- package/dist/utils/content-write-lock.d.ts +51 -106
- package/dist/utils/content-write-lock.js +66 -111
- package/dist/utils/debug.d.ts +7 -23
- package/dist/utils/debug.js +7 -23
- package/dist/utils/entry-url.d.ts +11 -23
- package/dist/utils/entry-url.js +11 -26
- package/dist/utils/error.d.ts +25 -90
- package/dist/utils/error.js +60 -147
- package/dist/utils/flatten-group-fields.d.ts +5 -7
- package/dist/utils/flatten-group-fields.js +5 -7
- package/dist/utils/format.d.ts +1 -5
- package/dist/utils/format.js +1 -5
- package/dist/utils/fs.d.ts +3 -4
- package/dist/utils/fs.js +3 -4
- package/dist/utils/git.d.ts +52 -83
- package/dist/utils/git.js +79 -125
- package/dist/utils/logger.d.ts +35 -50
- package/dist/utils/logger.js +35 -52
- package/dist/utils/provisioning-lock.d.ts +23 -35
- package/dist/utils/provisioning-lock.js +49 -69
- package/dist/utils/sanitize-href.d.ts +5 -10
- package/dist/utils/sanitize-href.js +8 -18
- package/dist/utils/title-field.d.ts +17 -20
- package/dist/utils/title-field.js +18 -27
- package/dist/utils/typed-filename.d.ts +20 -48
- package/dist/utils/typed-filename.js +25 -61
- package/dist/utils/url-prefix.d.ts +3 -3
- package/dist/utils/url-prefix.js +3 -3
- package/dist/validation/block-structural-keys.d.ts +16 -31
- package/dist/validation/block-structural-keys.js +19 -29
- package/dist/validation/deletion-checker.d.ts +0 -34
- package/dist/validation/deletion-checker.js +1 -43
- package/dist/validation/entry-link-validator.d.ts +6 -13
- package/dist/validation/entry-link-validator.js +4 -13
- package/dist/validation/entry-type-reference-validator.d.ts +13 -18
- package/dist/validation/entry-type-reference-validator.js +13 -18
- package/dist/validation/entry-validator.d.ts +13 -25
- package/dist/validation/entry-validator.js +15 -32
- package/dist/validation/field-traversal.d.ts +3 -30
- package/dist/validation/field-traversal.js +1 -32
- package/dist/validation/reference-validator.d.ts +5 -24
- package/dist/validation/reference-validator.js +5 -30
- package/dist/worker/cms-worker.d.ts +172 -232
- package/dist/worker/cms-worker.js +241 -333
- package/dist/worker/git-sync.d.ts +32 -133
- package/dist/worker/git-sync.js +129 -191
- package/dist/worker/github-auth.d.ts +13 -19
- package/dist/worker/github-auth.js +13 -19
- package/dist/worker/history-rewrite.d.ts +53 -66
- package/dist/worker/history-rewrite.js +54 -60
- package/dist/worker/log.d.ts +24 -41
- package/dist/worker/log.js +25 -42
- package/dist/worker/rebase.d.ts +20 -110
- package/dist/worker/rebase.js +279 -354
- package/dist/worker/task-runner.d.ts +32 -66
- package/dist/worker/task-runner.js +126 -162
- package/dist/worker/worker-context.d.ts +41 -81
- package/package.json +4 -4
- package/dist/cli/template-files/middleware-clerk.ts.template +0 -37
- package/dist/worker/task-queue-config.d.ts +0 -8
- package/dist/worker/task-queue.js +0 -19
- package/dist/worker/worker-status.d.ts +0 -47
- package/dist/worker/worker-status.js +0 -52
package/dist/editor/Editor.js
CHANGED
|
@@ -15,7 +15,6 @@ import { GroupManager } from './GroupManager.js';
|
|
|
15
15
|
import { PermissionManager } from './PermissionManager.js';
|
|
16
16
|
import { SystemHealthPanel } from './admin/SystemHealthPanel.js';
|
|
17
17
|
// Import directly from helpers to avoid server-only code in authorization barrel
|
|
18
|
-
// (same rationale as BranchManager.tsx's identical import)
|
|
19
18
|
import { isAdmin } from '../authorization/helpers.js';
|
|
20
19
|
import { buildPreviewSrc, buildCollectionLabels, buildBreadcrumbSegments } from './editor-utils.js';
|
|
21
20
|
import { useEditorLayout, useDraftManager, useEntryManager, useGroupManager, usePermissionManager, useCommentSystem, useBranchManager, useUserContext, useSchemaManager, } from './hooks/index.js';
|
|
@@ -34,7 +33,6 @@ import { MediaLibrary } from './media/MediaLibrary.js';
|
|
|
34
33
|
* saving/loading, and preview rendering using entry definitions.
|
|
35
34
|
*/
|
|
36
35
|
export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtitle = subtitle, branchName = '', collections, contentRoot, entryLinkUrl, initialSelectedId, initialValues, renderPreview, onCreateEntry, themeOptions, operatingMode, previewBaseByCollection, currentUser = 'current-user', canResolveComments = true, assetBaseUrl, basePath, AccountComponent, onAccountClick, onLogoutClick, customRenderers, }) => {
|
|
37
|
-
// Per-resource loading states
|
|
38
36
|
const [branchesLoading, setBranchesLoading] = useState(false);
|
|
39
37
|
const [entriesLoading, setEntriesLoading] = useState(false);
|
|
40
38
|
const [commentsLoading] = useState(false);
|
|
@@ -44,17 +42,14 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
44
42
|
const [branchManagerOpen, setBranchManagerOpen] = useState(false);
|
|
45
43
|
const [mediaLibraryOpen, setMediaLibraryOpen] = useState(false);
|
|
46
44
|
const [systemHealthOpen, setSystemHealthOpen] = useState(false);
|
|
47
|
-
// Schema editor state
|
|
48
45
|
const [collectionEditorOpen, setCollectionEditorOpen] = useState(false);
|
|
49
46
|
const [editingCollection, setEditingCollection] = useState(null);
|
|
50
47
|
const [collectionEditorParentPath, setCollectionEditorParentPath] = useState(undefined);
|
|
51
48
|
const [collectionEditorError, setCollectionEditorError] = useState(null);
|
|
52
|
-
// Rename entry modal state
|
|
53
49
|
const [renameModalOpen, setRenameModalOpen] = useState(false);
|
|
54
50
|
const [renamingEntry, setRenamingEntry] = useState(null);
|
|
55
51
|
const [renameModalError, setRenameModalError] = useState(null);
|
|
56
52
|
const [renameModalSaving, setRenameModalSaving] = useState(false);
|
|
57
|
-
// Delete confirmation modal state
|
|
58
53
|
const [deleteCollectionModalOpen, setDeleteCollectionModalOpen] = useState(false);
|
|
59
54
|
const [deletingCollectionPath, setDeletingCollectionPath] = useState(null);
|
|
60
55
|
const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false);
|
|
@@ -65,15 +60,12 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
65
60
|
const [previewLoadingState, setPreviewLoadingState] = useState({});
|
|
66
61
|
// Draft compile/render error reported by the preview page (null = renders cleanly)
|
|
67
62
|
const [previewError, setPreviewError] = useState(null);
|
|
68
|
-
// API client for schema operations
|
|
69
63
|
const apiClient = useApiClient();
|
|
70
|
-
// Fetch current user context for permission checks
|
|
71
64
|
const { userContext } = useUserContext();
|
|
72
|
-
// The System Health panel
|
|
65
|
+
// The System Health panel is admin-only -- gate both the sidebar
|
|
73
66
|
// menu item and the modal mount on it, same pattern BranchManager.tsx uses
|
|
74
67
|
// for its own admin-only actions.
|
|
75
68
|
const showSystemHealth = isAdmin(userContext?.groups);
|
|
76
|
-
// Use custom hooks for layout, entry, draft, group, permission, comment, and branch management
|
|
77
69
|
const { layout, setLayout, highlightEnabled, setHighlightEnabled, headerRef, headerHeight } = useEditorLayout();
|
|
78
70
|
// Comments state (shared between useCommentSystem and useBranchManager)
|
|
79
71
|
const [commentsForBranchSummaries, setCommentsForBranchSummaries] = useState([]);
|
|
@@ -90,21 +82,11 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
90
82
|
// computed server-side by the same getBranchProtection() call the writableBranch
|
|
91
83
|
// guard uses, so the UI can never drift from what the API will accept.
|
|
92
84
|
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// zero data from the server -- "no answer yet" and "not editing" both compute to
|
|
99
|
-
// locked when the fallback is itself a status comparison. Moving the decision
|
|
100
|
-
// server-side was the right call (the API guard and the UI now read one source
|
|
101
|
-
// of truth instead of two independent derivations that could drift), but an
|
|
102
|
-
// `?? false` default silently flipped what "no answer yet" means: a client that
|
|
103
|
-
// can't hear the server's answer now assumed UNLOCKED instead of LOCKED. A brief
|
|
104
|
-
// flash of locked (corrected the moment the branch list loads) is strictly safer
|
|
105
|
-
// than a flash of unlocked that invites a click the server is going to reject --
|
|
106
|
-
// and the pane is already showing a loading state during that same window, so
|
|
107
|
-
// the flash is not even visible in practice.
|
|
85
|
+
// `?? true`: with no answer from the server (branches fetch in flight or
|
|
86
|
+
// failed, or an older server without the flag) the editor must stay locked. A
|
|
87
|
+
// brief flash of locked, corrected when the list loads, is safer than a flash
|
|
88
|
+
// of unlocked that invites a click the server rejects -- and the pane shows a
|
|
89
|
+
// loading state during that window anyway.
|
|
108
90
|
const branchContentLocked = currentBranch?.writeBlocked ?? true;
|
|
109
91
|
// 2. Entry manager (depends on branchNameState, owns selectedPath)
|
|
110
92
|
const { selectedPath, setSelectedPath, entries: entriesState, collections: collectionsFromApi, currentEntry, availableSchemas, entriesInitializing, navigatorOpen, setNavigatorOpen, refreshEntries, handleCreateEntry, renameEntry, loadEntry, saveEntry, getEntryVersion, createModalOpen, createModalCollection, createModalError, createModalCreating, createModalExistingSlugs, handleCreateModalSubmit, closeCreateModal, } = useEntryManager({
|
|
@@ -121,12 +103,10 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
121
103
|
}),
|
|
122
104
|
setBusy: setEntriesLoading,
|
|
123
105
|
});
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// That effect no longer keys on the array, but a stable prop is still the
|
|
129
|
-
// right thing to pass: it also keeps Mantine's Select `data` identity steady.
|
|
106
|
+
// Keeps the entry-type list referentially stable while the create modal shows
|
|
107
|
+
// the same collection: a new array on every render would churn Mantine's
|
|
108
|
+
// Select `data` identity and give EntryCreateModal's form-seeding effect a
|
|
109
|
+
// spurious reason to reset the user's input.
|
|
130
110
|
const createModalEntryTypes = useMemo(() => createModalCollection?.entryTypes?.map((et) => ({
|
|
131
111
|
name: et.name,
|
|
132
112
|
label: et.label,
|
|
@@ -188,8 +168,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
188
168
|
});
|
|
189
169
|
const collectionLabels = useMemo(() => buildCollectionLabels(activeCollections), [activeCollections]);
|
|
190
170
|
const schema = currentEntry?.schema ?? [];
|
|
191
|
-
// Effect to load entry data when selection changes.
|
|
192
|
-
//
|
|
193
171
|
// Gated on `loadedValues[contentId]` (not `drafts[contentId]`) so a
|
|
194
172
|
// restored-from-localStorage draft never skips the load: skipping left
|
|
195
173
|
// `loadedValues[contentId]` permanently undefined, which made dirty-
|
|
@@ -252,7 +230,7 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
252
230
|
if (currentBranchRef.current !== requestBranch)
|
|
253
231
|
return;
|
|
254
232
|
setLoadedValues((prev) => ({ ...prev, [contentId]: loaded }));
|
|
255
|
-
//
|
|
233
|
+
// No draft is seeded here. `effectiveValue` is
|
|
256
234
|
// `drafts[id] ?? loadedValues[id]`, so the form renders from the line
|
|
257
235
|
// above alone; seeding `drafts[id] = loaded` only manufactured a
|
|
258
236
|
// pristine draft for every entry the user merely OPENED, which
|
|
@@ -286,17 +264,14 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
286
264
|
});
|
|
287
265
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- stable setters, run only on entry/path/branch/loadedValues change
|
|
288
266
|
}, [currentEntry, loadedValues, selectedPath, branchNameState]);
|
|
289
|
-
// Schema editor handlers
|
|
290
267
|
const handleOpenCollectionEditor = async (collection, parentPath) => {
|
|
291
268
|
if (collection) {
|
|
292
|
-
// Edit mode - fetch full collection data with usage counts
|
|
293
269
|
try {
|
|
294
270
|
const result = await apiClient.schema.getCollection({
|
|
295
271
|
branch: branchNameState,
|
|
296
272
|
collectionPath: collection.path,
|
|
297
273
|
});
|
|
298
274
|
if (result.ok && result.data && result.data.collection) {
|
|
299
|
-
// Use entry types with usage counts from API if available
|
|
300
275
|
const entries = result.data.entryTypesWithUsage
|
|
301
276
|
? result.data.entryTypesWithUsage.map((et) => ({
|
|
302
277
|
name: et.name,
|
|
@@ -324,7 +299,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
324
299
|
setEditingCollection(existingCollection);
|
|
325
300
|
}
|
|
326
301
|
else {
|
|
327
|
-
// Fallback to using EditorCollection data
|
|
328
302
|
const existingCollection = {
|
|
329
303
|
name: collection.name,
|
|
330
304
|
label: collection.label,
|
|
@@ -342,7 +316,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
342
316
|
}
|
|
343
317
|
}
|
|
344
318
|
catch {
|
|
345
|
-
// Fallback on error
|
|
346
319
|
const existingCollection = {
|
|
347
320
|
name: collection.name,
|
|
348
321
|
label: collection.label,
|
|
@@ -361,7 +334,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
361
334
|
setCollectionEditorParentPath(undefined);
|
|
362
335
|
}
|
|
363
336
|
else {
|
|
364
|
-
// Create mode
|
|
365
337
|
setEditingCollection(null);
|
|
366
338
|
setCollectionEditorParentPath(parentPath);
|
|
367
339
|
}
|
|
@@ -426,7 +398,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
426
398
|
try {
|
|
427
399
|
const result = await deleteEntry(deletingEntryPath);
|
|
428
400
|
if (result.ok && selectedPath === deletingEntryPath) {
|
|
429
|
-
// If we deleted the currently selected entry, clear selection
|
|
430
401
|
setSelectedPath('');
|
|
431
402
|
}
|
|
432
403
|
setDeleteEntryModalOpen(false);
|
|
@@ -466,7 +437,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
466
437
|
}
|
|
467
438
|
};
|
|
468
439
|
const handleReorderEntry = async (collectionPath, contentId, direction) => {
|
|
469
|
-
// Find the collection to get its current order array
|
|
470
440
|
const findCollection = (cols, path) => {
|
|
471
441
|
if (!cols)
|
|
472
442
|
return undefined;
|
|
@@ -479,21 +449,13 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
479
449
|
}
|
|
480
450
|
return undefined;
|
|
481
451
|
};
|
|
482
|
-
// Resolve against `collectionsFromApi`, NOT `activeCollections
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
// branch's collection in the stale build-time props and send ITS `order`
|
|
490
|
-
// array as a PATCH to the NEW branch. Reading `collectionsFromApi`
|
|
491
|
-
// directly makes "not yet loaded for this branch" and "loaded, has no
|
|
492
|
-
// such collection" the same (correct) not-found outcome, so the guard
|
|
493
|
-
// below is reachable again. See the correction to PR #196's "unreachable
|
|
494
|
-
// with entries empty" claim in
|
|
495
|
-
// .claude/future-tasks/resolved/program-b-final-review-followups.md for the write
|
|
496
|
-
// hazard this closes -- found by the 2026-08-12 adversarial review.
|
|
452
|
+
// Resolve against `collectionsFromApi`, NOT `activeCollections`: the latter
|
|
453
|
+
// falls back to the build-time `collections` prop whenever the fetch is
|
|
454
|
+
// still empty -- exactly the state mid-branch-switch. A write resolved
|
|
455
|
+
// against that fallback would silently PATCH the OLD branch's `order` onto
|
|
456
|
+
// the NEW branch instead of hitting the not-found guard below; reading
|
|
457
|
+
// `collectionsFromApi` directly makes "not yet loaded" and "loaded, no such
|
|
458
|
+
// collection" the same not-found outcome.
|
|
497
459
|
const collection = findCollection(collectionsFromApi, collectionPath);
|
|
498
460
|
if (!collection) {
|
|
499
461
|
// A silent no-op on a clicked menu item reads as a broken button --
|
|
@@ -507,14 +469,11 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
507
469
|
});
|
|
508
470
|
return;
|
|
509
471
|
}
|
|
510
|
-
// Use the collection's order array as the source of truth
|
|
511
|
-
// If no order array exists, build one from current entries and children
|
|
512
472
|
let currentOrder;
|
|
513
473
|
if (collection.order && collection.order.length > 0) {
|
|
514
474
|
currentOrder = [...collection.order];
|
|
515
475
|
}
|
|
516
476
|
else {
|
|
517
|
-
// Fallback: build order from entries and children
|
|
518
477
|
const collectionEntries = entriesState.filter((e) => e.collectionPath === collectionPath);
|
|
519
478
|
const entryIds = collectionEntries
|
|
520
479
|
.map((e) => e.contentId)
|
|
@@ -524,18 +483,14 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
524
483
|
.filter((id) => !!id);
|
|
525
484
|
currentOrder = [...entryIds, ...subCollectionIds];
|
|
526
485
|
}
|
|
527
|
-
// Find current position
|
|
528
486
|
const currentIndex = currentOrder.indexOf(contentId);
|
|
529
487
|
if (currentIndex === -1)
|
|
530
488
|
return;
|
|
531
|
-
// Calculate new position
|
|
532
489
|
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
533
490
|
if (newIndex < 0 || newIndex >= currentOrder.length)
|
|
534
491
|
return;
|
|
535
|
-
// Swap positions
|
|
536
492
|
const newOrder = [...currentOrder];
|
|
537
493
|
[newOrder[currentIndex], newOrder[newIndex]] = [newOrder[newIndex], newOrder[currentIndex]];
|
|
538
|
-
// Update via API
|
|
539
494
|
await updateOrder(collectionPath, newOrder);
|
|
540
495
|
};
|
|
541
496
|
// Determine if we should hide the root collection (but keep its context for ordering)
|
|
@@ -565,7 +520,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
565
520
|
const build = (node) => {
|
|
566
521
|
const entries = grouped.get(node.path) ?? [];
|
|
567
522
|
const children = node.children?.map((child) => build(child)) ?? [];
|
|
568
|
-
// Pass entries, children, and order to EntryNavigator for interleaved ordering
|
|
569
523
|
return {
|
|
570
524
|
path: node.path,
|
|
571
525
|
label: node.label ?? node.name,
|
|
@@ -597,7 +551,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
597
551
|
]);
|
|
598
552
|
// Tree expansion state - persists across drawer close/open
|
|
599
553
|
const treeExpandedStateRef = useRef({});
|
|
600
|
-
// Tree controller ref for collapse/expand all functionality
|
|
601
554
|
const treeControllerRef = useRef(null);
|
|
602
555
|
const handleTreeControllerReady = (controller) => {
|
|
603
556
|
treeControllerRef.current = controller;
|
|
@@ -607,15 +560,12 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
607
560
|
};
|
|
608
561
|
const handleCollapseAll = () => {
|
|
609
562
|
treeControllerRef.current?.collapseAllNodes();
|
|
610
|
-
// Sync the ref to empty state
|
|
611
563
|
treeExpandedStateRef.current = {};
|
|
612
564
|
};
|
|
613
565
|
const handleExpandAll = () => {
|
|
614
566
|
treeControllerRef.current?.expandAllNodes();
|
|
615
|
-
// Sync the ref with all expanded nodes from controller
|
|
616
567
|
treeExpandedStateRef.current = treeControllerRef.current?.expandedState ?? {};
|
|
617
568
|
};
|
|
618
|
-
// Resolve entry:ID links in body content for live preview
|
|
619
569
|
const { resolveEntryLinks } = useEntryLinkResolution({
|
|
620
570
|
entries: entriesState,
|
|
621
571
|
contentRoot,
|
|
@@ -637,7 +587,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
637
587
|
collectionName: e.collectionName,
|
|
638
588
|
})),
|
|
639
589
|
}), [entriesState]);
|
|
640
|
-
// Helper component for centered messages
|
|
641
590
|
const CenteredMessage = ({ children }) => (_jsx("div", { style: {
|
|
642
591
|
display: 'flex',
|
|
643
592
|
alignItems: 'center',
|
|
@@ -677,13 +626,7 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
677
626
|
// the one in-app retry for a failed branches fetch, and the fetch
|
|
678
627
|
// is terminal on its own: SWRProvider sets shouldRetryOnError and
|
|
679
628
|
// revalidateOnFocus false, and useBranchesData's refreshInterval
|
|
680
|
-
// is 0 without data.
|
|
681
|
-
// which was harmless while a failed fetch merely left the UI
|
|
682
|
-
// unlocked -- but content writes now fail CLOSED on missing branch
|
|
683
|
-
// data, so with a pinned branch (every ordinary adopter setup)
|
|
684
|
-
// that gate turned one network blip into a session-long lockout
|
|
685
|
-
// whose only escape was a page reload, behind a "Manage Branches"
|
|
686
|
-
// button that pointedly did not retry. Reloading is idempotent.
|
|
629
|
+
// is 0 without data. Reloading is idempotent.
|
|
687
630
|
loadBranches().catch(console.error);
|
|
688
631
|
}, onCommentsPanelOpen: () => setCommentsPanelOpen(true), onSave: handleSave, onSubmit: () => branchNameState && handleSubmit(branchNameState), onWithdraw: () => branchNameState && handleWithdraw(branchNameState) }), _jsx(Box, { style: {
|
|
689
632
|
paddingTop: headerHeight,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorAuthGate — decides, from the SERVER, whether the editor below it may mount.
|
|
3
|
+
*
|
|
4
|
+
* A 401 from the CMS API is the only signed-out signal trusted: the API accepts or rejects every
|
|
5
|
+
* request, and client-side auth state (Clerk's `isSignedIn`, a dev cookie) can disagree with it.
|
|
6
|
+
*
|
|
7
|
+
* - The editor mounts only once `whoami` succeeds, so it always belongs to a known identity. A
|
|
8
|
+
* 401 first shows the provider's `SignInComponent` full-screen; any other failure shows the
|
|
9
|
+
* error with Retry. The editor's first requests therefore wait one `whoami` round trip.
|
|
10
|
+
* - After mount, a 401 from ANY request overlays the sign-in UI on the still-mounted editor, so
|
|
11
|
+
* unsaved edits survive re-authentication. The request that 401'd is not retried.
|
|
12
|
+
* - Re-authenticating as the same user keeps the editor and revalidates its SWR cache; as a
|
|
13
|
+
* different user the editor and its cache remount, because the SWRProvider is keyed by user id.
|
|
14
|
+
* - A re-check that fails for a non-auth reason keeps the sign-in UI up with the error and Retry,
|
|
15
|
+
* so a provider that has already reported sign-in is never left waiting on nothing.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import type { EditorSignInProps } from '../config/index.js';
|
|
19
|
+
import { type CanopyThemeOptions } from './theme.js';
|
|
20
|
+
export interface EditorAuthGateProps {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
/** The auth provider's sign-in UI. Absent, a plain "sign in, then continue" notice is shown. */
|
|
23
|
+
SignInComponent?: React.ComponentType<EditorSignInProps>;
|
|
24
|
+
/** The editor's theme, so the gate's own screens match it. */
|
|
25
|
+
themeOptions?: CanopyThemeOptions;
|
|
26
|
+
}
|
|
27
|
+
export declare function EditorAuthGate({ children, SignInComponent, themeOptions }: EditorAuthGateProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* EditorAuthGate — decides, from the SERVER, whether the editor below it may mount.
|
|
5
|
+
*
|
|
6
|
+
* A 401 from the CMS API is the only signed-out signal trusted: the API accepts or rejects every
|
|
7
|
+
* request, and client-side auth state (Clerk's `isSignedIn`, a dev cookie) can disagree with it.
|
|
8
|
+
*
|
|
9
|
+
* - The editor mounts only once `whoami` succeeds, so it always belongs to a known identity. A
|
|
10
|
+
* 401 first shows the provider's `SignInComponent` full-screen; any other failure shows the
|
|
11
|
+
* error with Retry. The editor's first requests therefore wait one `whoami` round trip.
|
|
12
|
+
* - After mount, a 401 from ANY request overlays the sign-in UI on the still-mounted editor, so
|
|
13
|
+
* unsaved edits survive re-authentication. The request that 401'd is not retried.
|
|
14
|
+
* - Re-authenticating as the same user keeps the editor and revalidates its SWR cache; as a
|
|
15
|
+
* different user the editor and its cache remount, because the SWRProvider is keyed by user id.
|
|
16
|
+
* - A re-check that fails for a non-auth reason keeps the sign-in UI up with the error and Retry,
|
|
17
|
+
* so a provider that has already reported sign-in is never left waiting on nothing.
|
|
18
|
+
*/
|
|
19
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
|
|
20
|
+
import { Alert, Button, Center, Loader, Modal, Paper, Stack, Text, Title } from '@mantine/core';
|
|
21
|
+
import { useSWRConfig } from 'swr';
|
|
22
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
23
|
+
import { useApiClient, useOnUnauthorized } from './context/ApiClientContext.js';
|
|
24
|
+
import { EditorIdentityContext } from './context/EditorIdentityContext.js';
|
|
25
|
+
import { SWRProvider } from './context/SWRProvider.js';
|
|
26
|
+
import { CanopyCMSProvider } from './theme.js';
|
|
27
|
+
const initialGateState = {
|
|
28
|
+
phase: 'checking',
|
|
29
|
+
user: undefined,
|
|
30
|
+
sessionRejected: false,
|
|
31
|
+
error: undefined,
|
|
32
|
+
reauthCount: 0,
|
|
33
|
+
};
|
|
34
|
+
function gateReducer(state, action) {
|
|
35
|
+
switch (action.type) {
|
|
36
|
+
case 'accepted':
|
|
37
|
+
return {
|
|
38
|
+
phase: 'signed-in',
|
|
39
|
+
user: action.user,
|
|
40
|
+
sessionRejected: false,
|
|
41
|
+
error: undefined,
|
|
42
|
+
reauthCount: state.phase === 'signed-out' ? state.reauthCount + 1 : state.reauthCount,
|
|
43
|
+
};
|
|
44
|
+
case 'unauthorized': {
|
|
45
|
+
// Sticky while signed out: the mounted editor keeps making requests behind the overlay,
|
|
46
|
+
// they 401 too, and they must not clear a rejection the provider is showing.
|
|
47
|
+
const sessionRejected = (state.phase === 'signed-out' && state.sessionRejected) || action.afterSignIn;
|
|
48
|
+
// A re-check's own 401 supersedes an earlier failure; a background 401 does not.
|
|
49
|
+
const error = action.afterSignIn ? undefined : state.error;
|
|
50
|
+
const unchanged = state.phase === 'signed-out' &&
|
|
51
|
+
sessionRejected === state.sessionRejected &&
|
|
52
|
+
error === state.error;
|
|
53
|
+
return unchanged ? state : { ...state, phase: 'signed-out', sessionRejected, error };
|
|
54
|
+
}
|
|
55
|
+
case 'failed':
|
|
56
|
+
if (state.phase === 'signed-in')
|
|
57
|
+
return state;
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
phase: state.phase === 'checking' ? 'error' : state.phase,
|
|
61
|
+
error: action.error,
|
|
62
|
+
};
|
|
63
|
+
case 'retry':
|
|
64
|
+
return state.phase === 'error' ? { ...state, phase: 'checking', error: undefined } : state;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function EditorAuthGate({ children, SignInComponent, themeOptions }) {
|
|
68
|
+
const apiClient = useApiClient();
|
|
69
|
+
const [state, dispatch] = useReducer(gateReducer, initialGateState);
|
|
70
|
+
const handleUnauthorized = useCallback(() => {
|
|
71
|
+
dispatch({ type: 'unauthorized', afterSignIn: false });
|
|
72
|
+
}, []);
|
|
73
|
+
useOnUnauthorized(handleUnauthorized);
|
|
74
|
+
const check = useCallback(async (afterSignIn) => {
|
|
75
|
+
try {
|
|
76
|
+
const result = await apiClient.user.whoami();
|
|
77
|
+
if (result.ok && result.data) {
|
|
78
|
+
dispatch({
|
|
79
|
+
type: 'accepted',
|
|
80
|
+
user: { userId: result.data.userId, groups: result.data.groups },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else if (result.status === 401) {
|
|
84
|
+
dispatch({ type: 'unauthorized', afterSignIn });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
dispatch({ type: 'failed', error: result.error ?? 'Failed to load user context' });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
// Only non-auth failures land here: the client returns every 401 as a result.
|
|
92
|
+
dispatch({ type: 'failed', error: getErrorMessage(err) });
|
|
93
|
+
}
|
|
94
|
+
}, [apiClient]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
void check(false);
|
|
97
|
+
}, [check]);
|
|
98
|
+
const onSignedIn = useCallback(() => {
|
|
99
|
+
void check(true);
|
|
100
|
+
}, [check]);
|
|
101
|
+
const retryInitial = useCallback(() => {
|
|
102
|
+
dispatch({ type: 'retry' });
|
|
103
|
+
void check(false);
|
|
104
|
+
}, [check]);
|
|
105
|
+
const identity = useMemo(() => (state.user ? { user: state.user } : null), [state.user]);
|
|
106
|
+
const signIn = (_jsxs(_Fragment, { children: [state.phase === 'signed-out' && state.error && (_jsx(RetryNotice, { title: "Couldn't confirm your sign-in", error: state.error, onRetry: onSignedIn })), SignInComponent ? (_jsx(SignInComponent, { onSignedIn: onSignedIn, sessionRejected: state.sessionRejected })) : (_jsx(DefaultSignedOutNotice, { onSignedIn: onSignedIn, sessionRejected: state.sessionRejected }))] }));
|
|
107
|
+
if (!state.user || !identity) {
|
|
108
|
+
return (_jsx(CanopyCMSProvider, { ...themeOptions, withNotifications: false, children: _jsx(Center, { mih: "100vh", p: "md", children: state.phase === 'signed-out' ? (_jsx(Stack, { align: "center", gap: "md", "data-testid": "canopy-sign-in-screen", children: signIn })) : state.phase === 'error' ? (_jsx(RetryNotice, { title: "Couldn't load the editor", error: state.error ?? 'Unknown error', onRetry: retryInitial })) : (_jsxs(Stack, { align: "center", gap: "xs", "data-testid": "canopy-auth-checking", children: [_jsx(Loader, {}), _jsx(Text, { size: "sm", c: "dimmed", children: "Checking sign-in\u2026" })] })) }) }));
|
|
109
|
+
}
|
|
110
|
+
return (_jsxs(EditorIdentityContext.Provider, { value: identity, children: [_jsxs(SWRProvider, { children: [_jsx(RevalidateOnReauth, { reauthCount: state.reauthCount }), children] }, state.user.userId), state.phase === 'signed-out' && (
|
|
111
|
+
// The editor's own MantineProvider is inside <Editor>, so the overlay brings its own.
|
|
112
|
+
// Notifications stay with the editor's provider; a second container would duplicate them.
|
|
113
|
+
_jsx(CanopyCMSProvider, { ...themeOptions, withNotifications: false, children: _jsx(Modal, { opened: true, onClose: keepOpen, withCloseButton: false, closeOnClickOutside: false, closeOnEscape: false, centered: true, title: "Sign in to continue", children: _jsxs(Stack, { gap: "md", "data-testid": "canopy-sign-in-overlay", children: [_jsx(Text, { size: "sm", children: "Your session has ended. Unsaved edits stay in place if you sign back in as the same user." }), signIn] }) }) }))] }));
|
|
114
|
+
}
|
|
115
|
+
/** The overlay closes only when the server accepts a session again. */
|
|
116
|
+
function keepOpen() { }
|
|
117
|
+
/**
|
|
118
|
+
* Revalidates every SWR key when the same identity is re-accepted. `revalidateOnFocus` is off
|
|
119
|
+
* (see SWRProvider), so without this the data that 401'd behind the overlay would stay stale.
|
|
120
|
+
* A different identity remounts the SWRProvider instead, and this with it.
|
|
121
|
+
*/
|
|
122
|
+
function RevalidateOnReauth({ reauthCount }) {
|
|
123
|
+
const { mutate } = useSWRConfig();
|
|
124
|
+
const seen = useRef(reauthCount);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (seen.current === reauthCount)
|
|
127
|
+
return;
|
|
128
|
+
seen.current = reauthCount;
|
|
129
|
+
// Each data hook surfaces its own fetch error; nothing to add here.
|
|
130
|
+
mutate(() => true).catch(() => undefined);
|
|
131
|
+
}, [reauthCount, mutate]);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function RetryNotice(props) {
|
|
135
|
+
return (_jsx(Alert, { color: "red", title: props.title, maw: 480, "data-testid": "canopy-auth-error", children: _jsxs(Stack, { gap: "xs", align: "flex-start", children: [_jsx(Text, { size: "sm", children: props.error }), _jsx(Button, { size: "xs", variant: "light", onClick: props.onRetry, children: "Retry" })] }) }));
|
|
136
|
+
}
|
|
137
|
+
/** Re-checks rather than reloading: in the overlay, a reload would discard the unsaved edits. */
|
|
138
|
+
function DefaultSignedOutNotice({ onSignedIn, sessionRejected }) {
|
|
139
|
+
return (_jsx(Paper, { withBorder: true, p: "xl", radius: "md", maw: 420, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Title, { order: 3, children: "Sign in required" }), _jsx(Text, { size: "sm", children: "You are not signed in to the CMS. Sign in through this site's auth provider, then continue." }), sessionRejected && (_jsx(Text, { size: "sm", c: "red", children: "Still not signed in." })), _jsx(Button, { onClick: onSignedIn, children: "Continue" })] }) }));
|
|
140
|
+
}
|
|
@@ -36,7 +36,6 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
36
36
|
const childCollections = col.children ?? [];
|
|
37
37
|
const totalChildren = entries.length + childCollections.length;
|
|
38
38
|
const order = col.order ?? [];
|
|
39
|
-
// Build entry nodes keyed by contentId
|
|
40
39
|
const entryNodesByContentId = new Map();
|
|
41
40
|
entries.forEach((entry) => {
|
|
42
41
|
const node = {
|
|
@@ -57,7 +56,6 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
57
56
|
entryNodesByContentId.set(entry.contentId, node);
|
|
58
57
|
}
|
|
59
58
|
});
|
|
60
|
-
// Build child collection nodes keyed by contentId
|
|
61
59
|
const childNodesByContentId = new Map();
|
|
62
60
|
childCollections.forEach((child) => {
|
|
63
61
|
const childTree = toTree(child);
|
|
@@ -75,10 +73,8 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
75
73
|
childNodesByContentId.set(child.contentId, node);
|
|
76
74
|
}
|
|
77
75
|
});
|
|
78
|
-
// Interleave entries and children based on order array
|
|
79
76
|
const allChildren = [];
|
|
80
77
|
const usedContentIds = new Set();
|
|
81
|
-
// First, add items in order
|
|
82
78
|
for (const contentId of order) {
|
|
83
79
|
const entryNode = entryNodesByContentId.get(contentId);
|
|
84
80
|
if (entryNode) {
|
|
@@ -100,7 +96,6 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
100
96
|
usedContentIds.add(contentId);
|
|
101
97
|
}
|
|
102
98
|
}
|
|
103
|
-
// Add any entries not in order (alphabetically)
|
|
104
99
|
const unorderedEntries = entries
|
|
105
100
|
.filter((e) => !e.contentId || !usedContentIds.has(e.contentId))
|
|
106
101
|
.sort((a, b) => (a.label ?? '').localeCompare(b.label ?? ''));
|
|
@@ -121,7 +116,6 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
121
116
|
};
|
|
122
117
|
allChildren.push(node);
|
|
123
118
|
}
|
|
124
|
-
// Add any child collections not in order (alphabetically)
|
|
125
119
|
const unorderedChildren = childCollections
|
|
126
120
|
.filter((c) => !c.contentId || !usedContentIds.has(c.contentId))
|
|
127
121
|
.sort((a, b) => (a.label ?? '').localeCompare(b.label ?? ''));
|
|
@@ -172,34 +166,27 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
172
166
|
nodeProps: { status: item.status, isEntry: true, entryPath: item.path },
|
|
173
167
|
}));
|
|
174
168
|
}, [collections, items, hiddenRootPath]);
|
|
175
|
-
// Initialize tree controller
|
|
176
169
|
const tree = useTree({
|
|
177
170
|
initialExpandedState: expandedStateRef?.current ?? {},
|
|
178
171
|
onNodeExpand: (value) => {
|
|
179
|
-
// Update local state synchronously
|
|
180
172
|
localExpandedStateRef.current = {
|
|
181
173
|
...localExpandedStateRef.current,
|
|
182
174
|
[value]: true,
|
|
183
175
|
};
|
|
184
|
-
// Notify parent immediately
|
|
185
176
|
onExpandedStateChange?.(localExpandedStateRef.current);
|
|
186
177
|
},
|
|
187
178
|
onNodeCollapse: (value) => {
|
|
188
|
-
// Update local state synchronously
|
|
189
179
|
localExpandedStateRef.current = {
|
|
190
180
|
...localExpandedStateRef.current,
|
|
191
181
|
[value]: false,
|
|
192
182
|
};
|
|
193
|
-
// Notify parent immediately
|
|
194
183
|
onExpandedStateChange?.(localExpandedStateRef.current);
|
|
195
184
|
},
|
|
196
185
|
});
|
|
197
|
-
// Forward tree controller to parent for collapse/expand all functionality
|
|
198
186
|
useEffect(() => {
|
|
199
187
|
onTreeControllerReady?.(tree);
|
|
200
188
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- fire only when callback ref changes, not on tree state
|
|
201
189
|
}, [onTreeControllerReady]);
|
|
202
|
-
// Cleanup: save current state when component unmounts
|
|
203
190
|
// Empty dependency array ensures this only runs on mount/unmount, not on re-renders
|
|
204
191
|
useEffect(() => {
|
|
205
192
|
return () => {
|
|
@@ -207,12 +194,10 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
207
194
|
};
|
|
208
195
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
196
|
}, []);
|
|
210
|
-
// Restore state on mount and when selectedPath changes
|
|
211
197
|
useEffect(() => {
|
|
212
198
|
if (!selectedPath || !treeData)
|
|
213
199
|
return;
|
|
214
200
|
const savedState = expandedStateRef?.current ?? {};
|
|
215
|
-
// Calculate path to current entry and merge with saved state
|
|
216
201
|
const pathToEntry = calculatePathToEntry(selectedPath, treeData);
|
|
217
202
|
const baseState = { ...savedState, ...pathToEntry };
|
|
218
203
|
// Only update if state actually changed to avoid infinite loops
|
|
@@ -221,13 +206,11 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
221
206
|
if (currentStateJson !== newStateJson) {
|
|
222
207
|
tree.setExpandedState(baseState);
|
|
223
208
|
localExpandedStateRef.current = baseState;
|
|
224
|
-
// Notify parent of the merged state
|
|
225
209
|
onExpandedStateChange?.(baseState);
|
|
226
210
|
}
|
|
227
211
|
// Dependencies limited to data changes only to prevent infinite update loops
|
|
228
212
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
229
213
|
}, [selectedPath, treeData]);
|
|
230
|
-
// Auto-scroll to selected entry when drawer opens
|
|
231
214
|
useEffect(() => {
|
|
232
215
|
if (selectedPath && selectedNodeRef.current && !hasScrolledRef.current) {
|
|
233
216
|
// Small delay to ensure tree expansion completes first
|
|
@@ -242,7 +225,6 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
242
225
|
return () => clearTimeout(timeoutId);
|
|
243
226
|
}
|
|
244
227
|
}, [selectedPath, tree.expandedState]);
|
|
245
|
-
// Reset scroll flag when component mounts (drawer opens)
|
|
246
228
|
useEffect(() => {
|
|
247
229
|
hasScrolledRef.current = false;
|
|
248
230
|
}, []);
|
|
@@ -272,11 +254,9 @@ export const EntryNavigator = ({ items, collections, selectedPath, onSelect, onT
|
|
|
272
254
|
// Mantine only provides hasChildren=true if children.length > 0, but we want
|
|
273
255
|
// collections to always be expandable
|
|
274
256
|
const showChevron = hasChildren || Boolean(isCollection);
|
|
275
|
-
// Reordering is available for both entries and collections that have contentId and parent path
|
|
276
257
|
const canReorder = onReorderEntry && contentId && parentCollectionPath && typeof childIndex === 'number';
|
|
277
258
|
const canMoveUp = canReorder && childIndex > 0;
|
|
278
259
|
const canMoveDown = canReorder && typeof totalChildrenCount === 'number' && childIndex < totalChildrenCount - 1;
|
|
279
|
-
// Determine if we should show a context menu
|
|
280
260
|
// Collections show menu for add/edit/delete actions OR for reordering (subcollections)
|
|
281
261
|
const hasCollectionMenu = isCollection && (onAdd || onEdit || onAddSubCollection || onDelete || canReorder);
|
|
282
262
|
const hasEntryMenu = isEntry && entryPath && (onDeleteEntry || onRenameEntry || onReorderEntry);
|
|
@@ -32,9 +32,8 @@ export interface FormRendererProps {
|
|
|
32
32
|
* Per-field validation errors keyed by canonical canopy path (e.g.
|
|
33
33
|
* `blocks[0].title`). Rendered as a summary alert plus an inline message
|
|
34
34
|
* under each offending field. Produced by the save-path schema validation
|
|
35
|
-
* in useDraftManager
|
|
35
|
+
* in useDraftManager and by server 422 rejections.
|
|
36
36
|
*/
|
|
37
37
|
fieldErrors?: Record<string, string>;
|
|
38
38
|
}
|
|
39
39
|
export declare const FormRenderer: React.FC<FormRendererProps>;
|
|
40
|
-
export default FormRenderer;
|