canopycms 0.0.68-int.98 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2131 -0
- package/README.md +47 -63
- package/dist/ai/generate.d.ts +5 -0
- package/dist/ai/generate.js +244 -115
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/json-to-markdown.d.ts +21 -1
- package/dist/ai/json-to-markdown.js +120 -57
- package/dist/ai/references.d.ts +72 -0
- package/dist/ai/references.js +223 -0
- package/dist/ai/resolve-branch.js +9 -2
- package/dist/ai/to-plain-text.d.ts +17 -8
- package/dist/ai/to-plain-text.js +18 -15
- package/dist/ai/types.d.ts +84 -3
- package/dist/api/admin-branch-health.d.ts +24 -1
- package/dist/api/admin-branch-health.js +52 -20
- package/dist/api/admin.d.ts +23 -5
- package/dist/api/admin.js +36 -1
- package/dist/api/assets.js +88 -78
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +1 -1
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +16 -7
- package/dist/api/branch-status.d.ts +16 -6
- package/dist/api/branch-status.js +57 -12
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +8 -3
- package/dist/api/branch.d.ts +25 -12
- package/dist/api/branch.js +164 -103
- package/dist/api/client.d.ts +78 -58
- package/dist/api/client.js +150 -117
- package/dist/api/comments.d.ts +12 -3
- package/dist/api/comments.js +118 -9
- package/dist/api/content.d.ts +9 -9
- package/dist/api/content.js +131 -22
- package/dist/api/entries-constants.d.ts +14 -3
- package/dist/api/entries-constants.js +14 -3
- package/dist/api/entries.d.ts +40 -2
- package/dist/api/entries.js +102 -6
- package/dist/api/github-sync.d.ts +19 -0
- package/dist/api/github-sync.js +50 -3
- package/dist/api/groups.d.ts +4 -4
- package/dist/api/groups.js +2 -1
- package/dist/api/guards.js +23 -3
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +1 -0
- package/dist/api/permissions.d.ts +21 -2
- package/dist/api/permissions.js +50 -3
- package/dist/api/request-deadline.d.ts +59 -0
- package/dist/api/request-deadline.js +73 -0
- package/dist/api/resolve-references.js +8 -7
- package/dist/api/route-builder.d.ts +14 -0
- package/dist/api/route-builder.js +14 -1
- package/dist/api/schema.d.ts +11 -9
- package/dist/api/schema.js +2 -0
- package/dist/api/settings-helpers.d.ts +5 -1
- package/dist/api/settings-helpers.js +10 -2
- package/dist/api/types.d.ts +18 -0
- package/dist/api/users-constants.d.ts +6 -0
- package/dist/api/users-constants.js +6 -0
- package/dist/assets/asset-url.d.ts +28 -16
- package/dist/assets/asset-url.js +51 -25
- package/dist/assets/finalize.js +3 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +153 -0
- package/dist/assets/materialize.js +402 -0
- package/dist/assets/store-local.d.ts +19 -5
- package/dist/assets/store-local.js +69 -10
- package/dist/assets/store-s3.d.ts +38 -7
- package/dist/assets/store-s3.js +128 -22
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +31 -4
- package/dist/auth/cache.d.ts +1 -1
- package/dist/auth/cache.js +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +2 -0
- package/dist/auth/caching-auth-plugin.js +12 -2
- package/dist/auth/file-based-auth-cache.d.ts +8 -0
- package/dist/auth/file-based-auth-cache.js +18 -8
- package/dist/auth/person-name.d.ts +5 -0
- package/dist/auth/person-name.js +8 -0
- package/dist/auth/plugin.d.ts +10 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/auth/user-metadata-lookup.d.ts +21 -0
- package/dist/auth/user-metadata-lookup.js +116 -0
- package/dist/authorization/branch.d.ts +1 -1
- package/dist/authorization/branch.js +12 -13
- package/dist/authorization/groups/schema.d.ts +4 -4
- package/dist/authorization/helpers.d.ts +8 -0
- package/dist/authorization/helpers.js +8 -0
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/protected-branch.d.ts +5 -4
- package/dist/authorization/protected-branch.js +7 -5
- package/dist/branch-health.d.ts +35 -8
- package/dist/branch-health.js +54 -18
- package/dist/branch-metadata-error.d.ts +27 -0
- package/dist/branch-metadata-error.js +23 -0
- package/dist/branch-metadata-file.d.ts +205 -24
- package/dist/branch-metadata-file.js +126 -25
- package/dist/branch-metadata.d.ts +45 -6
- package/dist/branch-metadata.js +142 -50
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-registry.d.ts +8 -0
- package/dist/branch-registry.js +61 -9
- package/dist/branch-schema-cache.d.ts +29 -12
- package/dist/branch-schema-cache.js +64 -21
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +239 -100
- package/dist/build/asset-refs.d.ts +94 -0
- package/dist/build/asset-refs.js +267 -0
- package/dist/build/generate-ai-content.js +5 -0
- package/dist/build/inline-flight.d.ts +37 -0
- package/dist/build/inline-flight.js +142 -0
- package/dist/cli/asset-refs.d.ts +52 -0
- package/dist/cli/asset-refs.js +194 -0
- package/dist/cli/cli.d.ts +15 -1
- package/dist/cli/cli.js +8543 -3193
- package/dist/cli/configured-asset-store.d.ts +12 -0
- package/dist/cli/configured-asset-store.js +46 -0
- package/dist/cli/generate-ai-content.js +4244 -1072
- package/dist/cli/init.js +217 -126
- package/dist/cli/project-detect.d.ts +2 -0
- package/dist/cli/project-detect.js +2 -0
- package/dist/cli/template-files/Dockerfile.cms.template +13 -5
- package/dist/cli/template-files/cms-stack.ts.template +16 -13
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/comment-store.d.ts +25 -0
- package/dist/comment-store.js +43 -8
- package/dist/config/flatten.js +2 -0
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schemas/config.d.ts +12 -29
- package/dist/config/schemas/config.js +9 -12
- package/dist/config/schemas/field.d.ts +3 -0
- package/dist/config/schemas/field.js +58 -4
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +2 -20
- package/dist/config/schemas/url.js +2 -25
- package/dist/config/types.d.ts +116 -22
- package/dist/config/types.js +3 -9
- package/dist/config/validation.d.ts +25 -4
- package/dist/config/validation.js +116 -3
- package/dist/content-id-index.js +2 -2
- package/dist/content-index-generation.d.ts +1 -1
- package/dist/content-index-generation.js +1 -1
- package/dist/content-listing.d.ts +10 -1
- package/dist/content-listing.js +22 -5
- package/dist/content-reader.d.ts +9 -0
- package/dist/content-reader.js +16 -11
- package/dist/content-store.d.ts +75 -12
- package/dist/content-store.js +249 -86
- package/dist/content-tree.js +2 -2
- package/dist/context.js +5 -2
- package/dist/dangling-reference-log.d.ts +17 -0
- package/dist/dangling-reference-log.js +29 -0
- package/dist/dev-content-watcher.d.ts +1 -1
- package/dist/dev-content-watcher.js +1 -1
- package/dist/editor/BranchManager.d.ts +23 -7
- package/dist/editor/BranchManager.js +119 -54
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +1 -2
- package/dist/editor/CommentsPanel.d.ts +14 -0
- package/dist/editor/CommentsPanel.js +68 -43
- package/dist/editor/Editor.d.ts +11 -5
- package/dist/editor/Editor.js +891 -218
- package/dist/editor/EditorAuthGate.d.ts +7 -1
- package/dist/editor/EditorAuthGate.js +41 -9
- package/dist/editor/EntryNavigator.d.ts +22 -0
- package/dist/editor/EntryNavigator.js +353 -105
- package/dist/editor/FormHeader.d.ts +25 -0
- package/dist/editor/FormHeader.js +82 -0
- package/dist/editor/FormRenderer.d.ts +27 -3
- package/dist/editor/FormRenderer.js +171 -76
- package/dist/editor/PreviewFrame.d.ts +40 -0
- package/dist/editor/PreviewFrame.js +215 -0
- package/dist/editor/PreviewPane.d.ts +47 -0
- package/dist/editor/PreviewPane.js +147 -0
- package/dist/editor/PreviewToolbarTools.d.ts +22 -0
- package/dist/editor/PreviewToolbarTools.js +73 -0
- package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
- package/dist/editor/admin/SystemHealthPanel.js +163 -33
- package/dist/editor/admin/useSystemHealth.d.ts +18 -0
- package/dist/editor/admin/useSystemHealth.js +70 -1
- package/dist/editor/branch-status.d.ts +17 -0
- package/dist/editor/branch-status.js +29 -0
- package/dist/editor/canopy-path.d.ts +9 -6
- package/dist/editor/canopy-path.js +8 -11
- package/dist/editor/client-reference-resolver.d.ts +43 -16
- package/dist/editor/client-reference-resolver.js +155 -64
- package/dist/editor/comments/BranchComments.d.ts +1 -1
- package/dist/editor/comments/BranchComments.js +2 -2
- package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
- package/dist/editor/comments/CloseRefusedNotice.js +7 -0
- package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
- package/dist/editor/comments/CommentThreadsPopover.js +274 -0
- package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
- package/dist/editor/comments/CommentWriteAlert.js +13 -0
- package/dist/editor/comments/EntryComments.d.ts +10 -4
- package/dist/editor/comments/EntryComments.js +14 -17
- package/dist/editor/comments/FieldWrapper.d.ts +18 -4
- package/dist/editor/comments/FieldWrapper.js +62 -28
- package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
- package/dist/editor/comments/InlineCommentThread.js +11 -47
- package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
- package/dist/editor/comments/ThreadCarousel.js +18 -21
- package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
- package/dist/editor/comments/ThreadReplyControls.js +92 -0
- package/dist/editor/comments/field-comment-context.d.ts +14 -0
- package/dist/editor/comments/field-comment-context.js +14 -0
- package/dist/editor/comments/form-comments.d.ts +48 -0
- package/dist/editor/comments/form-comments.js +178 -0
- package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
- package/dist/editor/comments/item-anchor-copy.js +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
- package/dist/editor/comments/popover-placement.d.ts +18 -0
- package/dist/editor/comments/popover-placement.js +58 -0
- package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
- package/dist/editor/comments/use-tracked-writes.js +53 -0
- package/dist/editor/components/BranchAccessList.d.ts +15 -0
- package/dist/editor/components/BranchAccessList.js +52 -0
- package/dist/editor/components/BranchLockBanner.d.ts +22 -0
- package/dist/editor/components/BranchLockBanner.js +39 -0
- package/dist/editor/components/BranchPill.d.ts +32 -0
- package/dist/editor/components/BranchPill.js +33 -0
- package/dist/editor/components/BranchesDrawer.d.ts +11 -0
- package/dist/editor/components/BranchesDrawer.js +5 -0
- package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
- package/dist/editor/components/ConfirmDeleteModal.js +3 -2
- package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
- package/dist/editor/components/DestructiveMenuItem.js +8 -0
- package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
- package/dist/editor/components/EditorCrashScreen.js +22 -0
- package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
- package/dist/editor/components/EditorErrorBoundary.js +40 -0
- package/dist/editor/components/EditorHeader.d.ts +56 -106
- package/dist/editor/components/EditorHeader.js +130 -131
- package/dist/editor/components/EditorRail.d.ts +49 -0
- package/dist/editor/components/EditorRail.js +65 -0
- package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
- package/dist/editor/components/EntryBreadcrumb.js +56 -0
- package/dist/editor/components/EntryCreateModal.d.ts +12 -7
- package/dist/editor/components/EntryCreateModal.js +51 -57
- package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
- package/dist/editor/components/NoEditPermissionNotice.js +5 -4
- package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
- package/dist/editor/components/QuickOpenPalette.js +66 -0
- package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
- package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
- package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
- package/dist/editor/components/ReasonedMenuItem.js +15 -0
- package/dist/editor/components/ReferencedByList.d.ts +11 -0
- package/dist/editor/components/ReferencedByList.js +28 -0
- package/dist/editor/components/RelativeTime.d.ts +10 -0
- package/dist/editor/components/RelativeTime.js +10 -0
- package/dist/editor/components/RenameEntryModal.d.ts +9 -1
- package/dist/editor/components/RenameEntryModal.js +19 -31
- package/dist/editor/components/RetryToastMessage.d.ts +9 -0
- package/dist/editor/components/RetryToastMessage.js +6 -0
- package/dist/editor/components/SaveAllResult.d.ts +25 -0
- package/dist/editor/components/SaveAllResult.js +86 -0
- package/dist/editor/components/SaveSplitButton.d.ts +30 -0
- package/dist/editor/components/SaveSplitButton.js +108 -0
- package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
- package/dist/editor/components/StagedChangesDrawer.js +92 -0
- package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
- package/dist/editor/components/UnavailableEntryNotice.js +9 -0
- package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
- package/dist/editor/components/UnavailableTypeMessage.js +9 -0
- package/dist/editor/components/UndoToastMessage.d.ts +18 -0
- package/dist/editor/components/UndoToastMessage.js +10 -0
- package/dist/editor/components/UserBadge.d.ts +2 -5
- package/dist/editor/components/UserBadge.js +62 -33
- package/dist/editor/components/ValidationSummary.d.ts +14 -0
- package/dist/editor/components/ValidationSummary.js +24 -0
- package/dist/editor/components/index.d.ts +2 -2
- package/dist/editor/components/index.js +2 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -2
- package/dist/editor/context/ApiClientContext.js +19 -8
- package/dist/editor/context/AssetContext.d.ts +13 -35
- package/dist/editor/context/AssetContext.js +12 -31
- package/dist/editor/context/EditorStateContext.d.ts +4 -4
- package/dist/editor/context/EditorStateContext.js +4 -4
- package/dist/editor/context/SWRProvider.d.ts +3 -3
- package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
- package/dist/editor/context/SiteMdxAllowContext.js +7 -0
- package/dist/editor/context/index.d.ts +2 -1
- package/dist/editor/context/index.js +2 -1
- package/dist/editor/contrast.d.ts +19 -0
- package/dist/editor/contrast.js +41 -0
- package/dist/editor/copy.d.ts +18 -0
- package/dist/editor/copy.js +18 -0
- package/dist/editor/editor-config.js +16 -0
- package/dist/editor/editor-utils.d.ts +34 -8
- package/dist/editor/editor-utils.js +42 -20
- package/dist/editor/entry-search.d.ts +20 -0
- package/dist/editor/entry-search.js +34 -0
- package/dist/editor/field-breadcrumb.d.ts +12 -0
- package/dist/editor/field-breadcrumb.js +85 -0
- package/dist/editor/field-props.d.ts +86 -0
- package/dist/editor/field-props.js +27 -0
- package/dist/editor/field-target.d.ts +30 -0
- package/dist/editor/field-target.js +77 -0
- package/dist/editor/fields/BlockField.d.ts +12 -0
- package/dist/editor/fields/BlockField.js +89 -73
- package/dist/editor/fields/CodeField.d.ts +2 -0
- package/dist/editor/fields/CodeField.js +6 -2
- package/dist/editor/fields/DateTimeField.d.ts +4 -29
- package/dist/editor/fields/DateTimeField.js +79 -41
- package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
- package/dist/editor/fields/ExpandAllButton.js +4 -0
- package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
- package/dist/editor/fields/FieldCrashFallback.js +30 -0
- package/dist/editor/fields/FieldLabel.d.ts +37 -0
- package/dist/editor/fields/FieldLabel.js +19 -0
- package/dist/editor/fields/ImageField.d.ts +4 -0
- package/dist/editor/fields/ImageField.js +29 -14
- package/dist/editor/fields/InlineGroupField.js +4 -3
- package/dist/editor/fields/ItemList.d.ts +42 -0
- package/dist/editor/fields/ItemList.js +130 -0
- package/dist/editor/fields/MarkdownField.d.ts +32 -0
- package/dist/editor/fields/MarkdownField.js +223 -46
- package/dist/editor/fields/MdxImageDialog.js +1 -1
- package/dist/editor/fields/NumberField.d.ts +2 -0
- package/dist/editor/fields/NumberField.js +8 -5
- package/dist/editor/fields/NumberListField.d.ts +2 -0
- package/dist/editor/fields/NumberListField.js +19 -16
- package/dist/editor/fields/ObjectField.d.ts +2 -7
- package/dist/editor/fields/ObjectField.js +15 -5
- package/dist/editor/fields/ObjectListField.d.ts +29 -0
- package/dist/editor/fields/ObjectListField.js +57 -0
- package/dist/editor/fields/ReferenceField.d.ts +2 -0
- package/dist/editor/fields/ReferenceField.js +34 -9
- package/dist/editor/fields/SelectField.d.ts +2 -0
- package/dist/editor/fields/SelectField.js +11 -2
- package/dist/editor/fields/StringListField.d.ts +4 -1
- package/dist/editor/fields/StringListField.js +22 -8
- package/dist/editor/fields/TextField.d.ts +2 -0
- package/dist/editor/fields/TextField.js +6 -2
- package/dist/editor/fields/ToggleField.d.ts +2 -0
- package/dist/editor/fields/ToggleField.js +8 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
- package/dist/editor/fields/form-expansion.d.ts +9 -0
- package/dist/editor/fields/form-expansion.js +2 -0
- package/dist/editor/fields/item-expansion.d.ts +15 -0
- package/dist/editor/fields/item-expansion.js +60 -0
- package/dist/editor/fields/list-item-keys.d.ts +8 -0
- package/dist/editor/fields/list-item-keys.js +50 -0
- package/dist/editor/fields/list-item-title.d.ts +20 -0
- package/dist/editor/fields/list-item-title.js +85 -0
- package/dist/editor/fields/markdown-export-options.d.ts +2 -0
- package/dist/editor/fields/markdown-export-options.js +8 -0
- package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
- package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
- package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
- package/dist/editor/fields/mdx-jsx-support.js +157 -69
- package/dist/editor/fields/rich-text-failures.d.ts +5 -0
- package/dist/editor/fields/rich-text-failures.js +26 -0
- package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
- package/dist/editor/fields/toolbar-wrap.js +35 -0
- package/dist/editor/form-outline.d.ts +13 -0
- package/dist/editor/form-outline.js +70 -0
- package/dist/editor/group-manager/GroupCard.d.ts +4 -1
- package/dist/editor/group-manager/GroupCard.js +12 -3
- package/dist/editor/group-manager/GroupForm.js +1 -1
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
- package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
- package/dist/editor/group-manager/MemberList.d.ts +2 -1
- package/dist/editor/group-manager/MemberList.js +15 -4
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
- package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
- package/dist/editor/group-manager/index.js +34 -5
- package/dist/editor/group-manager/types.d.ts +5 -2
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +54 -0
- package/dist/editor/hooks/save-all.d.ts +68 -0
- package/dist/editor/hooks/save-all.js +61 -0
- package/dist/editor/hooks/useBranchActions.d.ts +11 -1
- package/dist/editor/hooks/useBranchActions.js +42 -19
- package/dist/editor/hooks/useBranchManager.d.ts +21 -0
- package/dist/editor/hooks/useBranchManager.js +321 -125
- package/dist/editor/hooks/useBranchesData.d.ts +8 -4
- package/dist/editor/hooks/useBranchesData.js +9 -5
- package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
- package/dist/editor/hooks/useCommentSystem.js +79 -55
- package/dist/editor/hooks/useCommentsData.d.ts +11 -7
- package/dist/editor/hooks/useCommentsData.js +20 -19
- package/dist/editor/hooks/useDraftManager.d.ts +77 -3
- package/dist/editor/hooks/useDraftManager.js +887 -94
- package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
- package/dist/editor/hooks/useEditorLayout.js +98 -20
- package/dist/editor/hooks/useEntriesData.d.ts +2 -1
- package/dist/editor/hooks/useEntriesData.js +18 -4
- package/dist/editor/hooks/useEntryManager.d.ts +41 -2
- package/dist/editor/hooks/useEntryManager.js +227 -41
- package/dist/editor/hooks/useGroupManager.js +13 -16
- package/dist/editor/hooks/usePermissionManager.js +14 -6
- package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
- package/dist/editor/hooks/usePreviewMarks.js +33 -0
- package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
- package/dist/editor/hooks/useReferenceResolution.js +34 -177
- package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
- package/dist/editor/hooks/useSchemaManager.js +97 -39
- package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
- package/dist/editor/hooks/useUserMetadata.js +76 -47
- package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
- package/dist/editor/hooks/user-metadata-batcher.js +33 -0
- package/dist/editor/media/AssetCard.d.ts +6 -7
- package/dist/editor/media/AssetCard.js +24 -31
- package/dist/editor/media/AssetListRow.d.ts +16 -0
- package/dist/editor/media/AssetListRow.js +41 -0
- package/dist/editor/media/CropStep.js +6 -1
- package/dist/editor/media/MediaLibrary.d.ts +4 -3
- package/dist/editor/media/MediaLibrary.js +11 -4
- package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
- package/dist/editor/media/MediaLibraryBody.js +195 -80
- package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
- package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +8 -0
- package/dist/editor/media/editor-image-src.js +10 -0
- package/dist/editor/media/format-bytes.d.ts +2 -0
- package/dist/editor/media/format-bytes.js +23 -0
- package/dist/editor/media/media-library-prefs.d.ts +23 -0
- package/dist/editor/media/media-library-prefs.js +49 -0
- package/dist/editor/media/media-library-view.d.ts +34 -0
- package/dist/editor/media/media-library-view.js +40 -0
- package/dist/editor/media/useAssetLibrary.d.ts +30 -0
- package/dist/editor/media/useAssetLibrary.js +126 -0
- package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
- package/dist/editor/media/useStableAssetOrder.js +37 -0
- package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
- package/dist/editor/media/useThumbnailFailure.js +16 -0
- package/dist/editor/permission-manager/GroupSelector.js +2 -2
- package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
- package/dist/editor/permission-manager/PermissionEditor.js +15 -4
- package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
- package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.js +3 -3
- package/dist/editor/permission-manager/index.js +12 -3
- package/dist/editor/permission-manager/types.d.ts +5 -2
- package/dist/editor/platform.d.ts +3 -0
- package/dist/editor/platform.js +9 -0
- package/dist/editor/preview-asset-base.d.ts +9 -0
- package/dist/editor/preview-asset-base.js +17 -0
- package/dist/editor/preview-bridge.d.ts +51 -27
- package/dist/editor/preview-bridge.js +103 -144
- package/dist/editor/preview-marks.d.ts +15 -0
- package/dist/editor/preview-marks.js +74 -0
- package/dist/editor/preview-path.d.ts +12 -0
- package/dist/editor/preview-path.js +45 -1
- package/dist/editor/preview-scale.d.ts +27 -0
- package/dist/editor/preview-scale.js +34 -0
- package/dist/editor/raw-asset-base.d.ts +5 -0
- package/dist/editor/raw-asset-base.js +14 -0
- package/dist/editor/relative-time.d.ts +4 -4
- package/dist/editor/relative-time.js +16 -8
- package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
- package/dist/editor/schema-editor/CollectionEditor.js +361 -62
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
- package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
- package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
- package/dist/editor/schema-editor/collection-draft.js +40 -0
- package/dist/editor/schema-editor/index.d.ts +1 -1
- package/dist/editor/schema-label.d.ts +10 -0
- package/dist/editor/schema-label.js +17 -0
- package/dist/editor/settings-conflict.d.ts +5 -0
- package/dist/editor/settings-conflict.js +8 -0
- package/dist/editor/shell/ActivityRail.d.ts +37 -0
- package/dist/editor/shell/ActivityRail.js +91 -0
- package/dist/editor/shell/EditorShell.d.ts +17 -0
- package/dist/editor/shell/EditorShell.js +31 -0
- package/dist/editor/shell/PaneLayout.d.ts +28 -0
- package/dist/editor/shell/PaneLayout.js +116 -0
- package/dist/editor/shell/SidePanelHost.d.ts +35 -0
- package/dist/editor/shell/SidePanelHost.js +184 -0
- package/dist/editor/shell/TopBar.d.ts +12 -0
- package/dist/editor/shell/TopBar.js +14 -0
- package/dist/editor/shell/host-chrome.d.ts +16 -0
- package/dist/editor/shell/host-chrome.js +69 -0
- package/dist/editor/slug.d.ts +16 -0
- package/dist/editor/slug.js +43 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.js +314 -13
- package/dist/editor/unanswered.d.ts +17 -0
- package/dist/editor/unanswered.js +26 -0
- package/dist/editor/unavailable-entry-type.d.ts +20 -0
- package/dist/editor/unavailable-entry-type.js +28 -0
- package/dist/editor/undo-removal.d.ts +57 -0
- package/dist/editor/undo-removal.js +163 -0
- package/dist/editor/user-display.d.ts +4 -0
- package/dist/editor/user-display.js +5 -0
- package/dist/editor/utils/confirm-modal.d.ts +8 -0
- package/dist/editor/utils/confirm-modal.js +12 -0
- package/dist/editor/utils/editor-errors.d.ts +19 -0
- package/dist/editor/utils/editor-errors.js +32 -0
- package/dist/editor/utils/focus-return.d.ts +56 -0
- package/dist/editor/utils/focus-return.js +106 -0
- package/dist/entry-link-resolver.d.ts +11 -1
- package/dist/entry-link-resolver.js +65 -5
- package/dist/entry-schema-registry.d.ts +3 -3
- package/dist/entry-schema-registry.js +7 -4
- package/dist/entry-schema.d.ts +49 -3
- package/dist/entry-schema.js +18 -0
- package/dist/git-manager.d.ts +172 -10
- package/dist/git-manager.js +449 -107
- package/dist/github-service.d.ts +39 -2
- package/dist/github-service.js +101 -7
- package/dist/http/handler.js +74 -9
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +9 -4
- package/dist/http/worker-not-ready.js +59 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/item-keys.d.ts +46 -0
- package/dist/item-keys.js +235 -0
- package/dist/operating-mode/editor-mode-check.d.ts +15 -0
- package/dist/operating-mode/editor-mode-check.js +24 -0
- package/dist/operating-mode/mode-env.d.ts +13 -14
- package/dist/operating-mode/mode-env.js +13 -14
- package/dist/operating-mode/types.d.ts +2 -2
- package/dist/paths/branch.d.ts +0 -1
- package/dist/paths/branch.js +1 -8
- package/dist/paths/index.d.ts +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/validation.js +0 -2
- package/dist/preview.d.ts +8 -0
- package/dist/preview.js +7 -0
- package/dist/reference-resolver.js +1 -0
- package/dist/resolve-canopy-user.d.ts +1 -1
- package/dist/resolve-canopy-user.js +1 -1
- package/dist/schema/meta-loader.d.ts +18 -8
- package/dist/schema/meta-loader.js +42 -9
- package/dist/schema/registry-fingerprint.d.ts +14 -0
- package/dist/schema/registry-fingerprint.js +63 -0
- package/dist/schema/resolver.d.ts +7 -4
- package/dist/schema/resolver.js +7 -6
- package/dist/schema/schema-store.d.ts +7 -10
- package/dist/schema/schema-store.js +13 -11
- package/dist/schema/schema-unavailable-error.d.ts +11 -0
- package/dist/schema/schema-unavailable-error.js +15 -0
- package/dist/schema/types.d.ts +22 -0
- package/dist/schema-registry-record.d.ts +35 -0
- package/dist/schema-registry-record.js +134 -0
- package/dist/server.d.ts +79 -9
- package/dist/server.js +62 -8
- package/dist/services.d.ts +33 -5
- package/dist/services.js +155 -37
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +159 -45
- package/dist/stamp-on-publish.d.ts +36 -0
- package/dist/stamp-on-publish.js +71 -0
- package/dist/static/index.d.ts +58 -5
- package/dist/static/index.js +133 -3
- package/dist/static/seo.d.ts +5 -5
- package/dist/static/seo.js +5 -5
- package/dist/submission-attribution.d.ts +8 -0
- package/dist/submission-attribution.js +22 -0
- package/dist/system-users.d.ts +12 -0
- package/dist/system-users.js +12 -0
- package/dist/task-queue/cms-task-queue.d.ts +1 -1
- package/dist/task-queue/cms-task-queue.js +1 -1
- package/dist/task-queue/index.d.ts +1 -1
- package/dist/task-queue/index.js +1 -1
- package/dist/task-queue/task-queue.d.ts +6 -0
- package/dist/task-queue/task-queue.js +29 -0
- package/dist/task-queue/worker-status.d.ts +35 -9
- package/dist/task-queue/worker-status.js +84 -17
- package/dist/types.d.ts +90 -9
- package/dist/url-collision.js +3 -3
- package/dist/url-exclusivity-fixtures.d.ts +8 -1
- package/dist/url-exclusivity-fixtures.js +11 -1
- package/dist/user.js +2 -1
- package/dist/utils/atomic-write.js +2 -1
- package/dist/utils/base-branch.d.ts +13 -0
- package/dist/utils/base-branch.js +23 -0
- package/dist/utils/content-serialize.d.ts +15 -7
- package/dist/utils/content-serialize.js +353 -51
- package/dist/utils/content-write-lock.d.ts +6 -1
- package/dist/utils/content-write-lock.js +21 -6
- package/dist/utils/debug.d.ts +1 -1
- package/dist/utils/debug.js +5 -3
- package/dist/utils/error.js +13 -2
- package/dist/utils/escape-text.d.ts +7 -0
- package/dist/utils/escape-text.js +27 -0
- package/dist/utils/fs.d.ts +12 -4
- package/dist/utils/fs.js +18 -5
- package/dist/utils/git.d.ts +21 -1
- package/dist/utils/git.js +63 -8
- package/dist/utils/json-source-splice.d.ts +14 -0
- package/dist/utils/json-source-splice.js +120 -0
- package/dist/utils/markdown-body-splice.d.ts +39 -0
- package/dist/utils/markdown-body-splice.js +421 -0
- package/dist/utils/occ-json-write.js +18 -12
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +32 -1
- package/dist/utils/provisioning-lock.js +78 -32
- package/dist/utils/sanitize-href.d.ts +9 -23
- package/dist/utils/sanitize-href.js +14 -28
- package/dist/utils/title-field.d.ts +10 -0
- package/dist/utils/title-field.js +47 -0
- package/dist/utils/yaml-source-splice.d.ts +58 -0
- package/dist/utils/yaml-source-splice.js +515 -0
- package/dist/validation/block-structural-keys.d.ts +1 -1
- package/dist/validation/block-structural-keys.js +1 -1
- package/dist/validation/deletion-checker.d.ts +21 -29
- package/dist/validation/deletion-checker.js +41 -117
- package/dist/validation/entry-validator.d.ts +6 -10
- package/dist/validation/entry-validator.js +19 -26
- package/dist/validation/field-traversal.d.ts +22 -0
- package/dist/validation/field-traversal.js +39 -8
- package/dist/validation/markdown-safety.d.ts +68 -0
- package/dist/validation/markdown-safety.js +596 -0
- package/dist/validation/mdx-allowlist.d.ts +55 -0
- package/dist/validation/mdx-allowlist.js +183 -0
- package/dist/validation/reference-validator.d.ts +2 -0
- package/dist/validation/reference-validator.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-policy.d.ts +124 -0
- package/dist/worker/branch-policy.js +243 -0
- package/dist/worker/bundle-header.d.ts +117 -0
- package/dist/worker/bundle-header.js +253 -0
- package/dist/worker/cms-worker.d.ts +236 -142
- package/dist/worker/cms-worker.js +772 -285
- package/dist/worker/gateway-egress.d.ts +43 -0
- package/dist/worker/gateway-egress.js +209 -0
- package/dist/worker/gateway-instance.d.ts +42 -0
- package/dist/worker/gateway-instance.js +129 -0
- package/dist/worker/git-process.d.ts +81 -0
- package/dist/worker/git-process.js +234 -0
- package/dist/worker/git-sync.d.ts +34 -17
- package/dist/worker/git-sync.js +394 -131
- package/dist/worker/github-auth.d.ts +9 -9
- package/dist/worker/github-auth.js +13 -15
- package/dist/worker/github-gateway-http.d.ts +62 -0
- package/dist/worker/github-gateway-http.js +977 -0
- package/dist/worker/github-gateway-wire.d.ts +590 -0
- package/dist/worker/github-gateway-wire.js +488 -0
- package/dist/worker/github-gateway.d.ts +238 -0
- package/dist/worker/github-gateway.js +726 -0
- package/dist/worker/github-mirror-upkeep.d.ts +75 -0
- package/dist/worker/github-mirror-upkeep.js +266 -0
- package/dist/worker/github-mirror.d.ts +364 -0
- package/dist/worker/github-mirror.js +1030 -0
- package/dist/worker/github-transfer.d.ts +56 -0
- package/dist/worker/github-transfer.js +444 -0
- package/dist/worker/history-rewrite.d.ts +4 -5
- package/dist/worker/history-rewrite.js +12 -17
- package/dist/worker/provisioned-workspace.d.ts +5 -5
- package/dist/worker/provisioned-workspace.js +5 -5
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +58 -54
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +12 -0
- package/dist/worker/schema-gate.d.ts +49 -0
- package/dist/worker/schema-gate.js +226 -0
- package/dist/worker/self-checks.d.ts +107 -0
- package/dist/worker/self-checks.js +358 -0
- package/dist/worker/shared-repo-git.d.ts +96 -0
- package/dist/worker/shared-repo-git.js +459 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +136 -0
- package/dist/worker/stale-mirror.d.ts +82 -0
- package/dist/worker/stale-mirror.js +130 -0
- package/dist/worker/task-runner.d.ts +5 -3
- package/dist/worker/task-runner.js +287 -186
- package/dist/worker/worker-context.d.ts +36 -37
- package/package.json +35 -7
- package/dist/editor/EditorPanes.d.ts +0 -10
- package/dist/editor/EditorPanes.js +0 -98
- package/dist/editor/components/EditorFooter.d.ts +0 -11
- package/dist/editor/components/EditorFooter.js +0 -9
- package/dist/editor/components/EditorSidebar.d.ts +0 -67
- package/dist/editor/components/EditorSidebar.js +0 -22
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { createElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import useSWR, { useSWRConfig } from 'swr';
|
|
3
3
|
import { notifications } from '@mantine/notifications';
|
|
4
4
|
import { buildWritePayload, normalizeContentPayload } from '../editor-utils.js';
|
|
5
5
|
import { isDataOnlyFormat } from '../../utils/format.js';
|
|
6
|
+
import { withTitleAtPath } from '../../utils/title-field.js';
|
|
6
7
|
import { getErrorMessage } from '../../utils/error.js';
|
|
7
8
|
import { useApiClient } from '../context/index.js';
|
|
9
|
+
import { SCHEMA_UNAVAILABLE_CODE } from '../unavailable-entry-type.js';
|
|
8
10
|
import { entriesKey, fetchEntriesAndSchema } from './useEntriesData.js';
|
|
11
|
+
import { RetryToastMessage } from '../components/RetryToastMessage.js';
|
|
12
|
+
import { isGatewayFailure, isUnanswered, MayHaveLandedError, UnansweredError } from '../unanswered.js';
|
|
13
|
+
const ENTRIES_LOAD_FAILED_TOAST_ID = 'canopy-entries-load-failed';
|
|
9
14
|
// Re-exported so existing imports of `listAllEntries` from this module keep
|
|
10
15
|
// working -- the implementation lives in useEntriesData.ts alongside the
|
|
11
16
|
// other fetch/key/type pieces useEntryManager shares with the SWR layer.
|
|
@@ -18,13 +23,27 @@ export { listAllEntries } from './useEntriesData.js';
|
|
|
18
23
|
* errors (when present) so the form can surface them next to the fields.
|
|
19
24
|
*/
|
|
20
25
|
export class SaveApiError extends Error {
|
|
21
|
-
constructor(status,
|
|
26
|
+
constructor(status,
|
|
27
|
+
/** The server's (or saveEntry's own) explanation, if any; `message` falls back to the status. */
|
|
28
|
+
serverMessage, fieldErrors, code) {
|
|
22
29
|
super(serverMessage || `Save failed: ${status}`);
|
|
23
30
|
this.status = status;
|
|
31
|
+
this.serverMessage = serverMessage;
|
|
24
32
|
this.fieldErrors = fieldErrors;
|
|
33
|
+
this.code = code;
|
|
25
34
|
this.name = 'SaveApiError';
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown by a read for editing when the API answers `SCHEMA_UNAVAILABLE`: the entry's type
|
|
39
|
+
* names a schema the running code lacks, so the entry cannot be opened until that changes.
|
|
40
|
+
*/
|
|
41
|
+
export class EntrySchemaUnavailableError extends Error {
|
|
42
|
+
constructor(serverMessage) {
|
|
43
|
+
super(serverMessage || 'Entry type unavailable');
|
|
44
|
+
this.name = 'EntrySchemaUnavailableError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
28
47
|
// Module-level singletons for the derived-empty fallbacks. These must be
|
|
29
48
|
// referentially stable: `collectionByPath` and `currentEntry` memoize on them,
|
|
30
49
|
// and a fresh `[]` each render would invalidate both on every render while a
|
|
@@ -32,6 +51,11 @@ export class SaveApiError extends Error {
|
|
|
32
51
|
const EMPTY_ENTRIES = [];
|
|
33
52
|
const EMPTY_COLLECTIONS = [];
|
|
34
53
|
const EMPTY_SCHEMAS = [];
|
|
54
|
+
/**
|
|
55
|
+
* The URL's `entry`: a path; `''` for no entry and the navigator open (the crash screen's escape
|
|
56
|
+
* from an entry that crashes the editor); null opens the first entry.
|
|
57
|
+
*/
|
|
58
|
+
const readUrlEntry = () => typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get('entry');
|
|
35
59
|
/**
|
|
36
60
|
* Custom hook for managing editor entries (CRUD operations).
|
|
37
61
|
*/
|
|
@@ -74,7 +98,11 @@ export function useEntryManager(options) {
|
|
|
74
98
|
};
|
|
75
99
|
// Initialize with prop value or empty (URL sync happens in effect after mount)
|
|
76
100
|
const [selectedPath, setSelectedPath] = useState(options.initialSelectedId ?? '');
|
|
77
|
-
const
|
|
101
|
+
const selectedPathRef = useRef(selectedPath);
|
|
102
|
+
selectedPathRef.current = selectedPath;
|
|
103
|
+
// Counts selections through the returned setter, not this hook's own fallbacks.
|
|
104
|
+
const selectionsByCallerRef = useRef(0);
|
|
105
|
+
const [navigatorOpen, setNavigatorOpen] = useState(() => readUrlEntry() === '');
|
|
78
106
|
const isInitialMount = useRef(true);
|
|
79
107
|
const hasSyncedFromUrl = useRef(false);
|
|
80
108
|
// OCC version tokens — captured on load, sent on save. Keyed by
|
|
@@ -148,11 +176,22 @@ export function useEntryManager(options) {
|
|
|
148
176
|
const [createModalError, setCreateModalError] = useState(null);
|
|
149
177
|
const [createModalCreating, setCreateModalCreating] = useState(false);
|
|
150
178
|
// Store the URL entry param on mount (before any effects change the URL)
|
|
151
|
-
const initialUrlEntry = useRef(
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
initialUrlEntry.current = params.get('entry');
|
|
179
|
+
const initialUrlEntry = useRef(undefined);
|
|
180
|
+
if (initialUrlEntry.current === undefined) {
|
|
181
|
+
initialUrlEntry.current = readUrlEntry();
|
|
155
182
|
}
|
|
183
|
+
// Set by the URL's empty `entry` or by `closeEntry`; honoured until an entry is selected or the
|
|
184
|
+
// branch changes, after which a lost selection opens the first again.
|
|
185
|
+
const noEntryRequested = useRef(initialUrlEntry.current === '');
|
|
186
|
+
const selectEntryPath = useCallback((path) => {
|
|
187
|
+
selectionsByCallerRef.current += 1;
|
|
188
|
+
setSelectedPath(path);
|
|
189
|
+
}, []);
|
|
190
|
+
// No entry open, as the URL's empty `entry` asks, where a lost selection opens the first.
|
|
191
|
+
const closeEntry = useCallback(() => {
|
|
192
|
+
noEntryRequested.current = true;
|
|
193
|
+
selectEntryPath('');
|
|
194
|
+
}, [selectEntryPath]);
|
|
156
195
|
const collectionByPath = useMemo(() => {
|
|
157
196
|
const map = new Map();
|
|
158
197
|
const walk = (collections) => {
|
|
@@ -178,32 +217,46 @@ export function useEntryManager(options) {
|
|
|
178
217
|
.filter((e) => e.collectionPath === createModalCollection.path && e.slug)
|
|
179
218
|
.map((e) => e.slug));
|
|
180
219
|
}, [entriesState, createModalCollection]);
|
|
181
|
-
|
|
220
|
+
// The read both entry paths share; returns the raw payload so `loadEntry` can take its version.
|
|
221
|
+
const readEntryPayload = async (entry, requestBranch) => {
|
|
182
222
|
if (!entry.collectionPath) {
|
|
183
223
|
throw new Error('Entry missing collectionPath');
|
|
184
224
|
}
|
|
185
|
-
// Pin the branch this request targets: if the user switches branches
|
|
186
|
-
// while the read is in flight, the token must be recorded under the
|
|
187
|
-
// branch that actually served it, not the current one.
|
|
188
|
-
const requestBranch = options.branchName;
|
|
189
225
|
// Build path from collectionPath and slug (if it's a collection entry)
|
|
190
226
|
const path = entry.slug ? `${entry.collectionPath}/${entry.slug}` : entry.collectionPath;
|
|
191
227
|
const result = await apiClient.content.read({
|
|
192
228
|
branch: requestBranch,
|
|
193
229
|
path,
|
|
194
230
|
});
|
|
195
|
-
if (!result.ok)
|
|
231
|
+
if (!result.ok) {
|
|
232
|
+
if (result.code === SCHEMA_UNAVAILABLE_CODE)
|
|
233
|
+
throw new EntrySchemaUnavailableError(result.error);
|
|
196
234
|
throw new Error(`Load failed: ${result.status}${result.error ? ` — ${result.error}` : ''}`);
|
|
235
|
+
}
|
|
236
|
+
return result.data;
|
|
237
|
+
};
|
|
238
|
+
const loadEntry = async (entry) => {
|
|
239
|
+
// Pin the branch this request targets: if the user switches branches
|
|
240
|
+
// while the read is in flight, the token must be recorded under the
|
|
241
|
+
// branch that actually served it, not the current one.
|
|
242
|
+
const requestBranch = options.branchName;
|
|
243
|
+
const data = await readEntryPayload(entry, requestBranch);
|
|
197
244
|
// Capture OCC version token for next save
|
|
198
|
-
if (entry.contentId && typeof
|
|
199
|
-
entryVersionsRef.current.set(versionKey(requestBranch, entry.contentId),
|
|
245
|
+
if (entry.contentId && typeof data?.version === 'number') {
|
|
246
|
+
entryVersionsRef.current.set(versionKey(requestBranch, entry.contentId), data.version);
|
|
200
247
|
}
|
|
201
|
-
return normalizeContentPayload(
|
|
248
|
+
return normalizeContentPayload(data);
|
|
202
249
|
};
|
|
250
|
+
// The same read as `loadEntry`, minus the token capture. A value read this way can be
|
|
251
|
+
// compared against but never saved against: only `loadEntry` makes an entry saveable.
|
|
252
|
+
const readEntryValue = async (entry) => normalizeContentPayload(await readEntryPayload(entry, options.branchName));
|
|
203
253
|
// Read side of the same branch-qualified token map `saveEntry` writes/reads.
|
|
204
254
|
// Deliberately keyed off the CURRENT branch (not a pinned one): callers use
|
|
205
255
|
// it to reason about the entry they are looking at right now.
|
|
206
256
|
const getEntryVersion = (contentId) => entryVersionsRef.current.get(versionKey(options.branchName, contentId));
|
|
257
|
+
const adoptEntryVersion = (contentId, version) => {
|
|
258
|
+
entryVersionsRef.current.set(versionKey(options.branchName, contentId), version);
|
|
259
|
+
};
|
|
207
260
|
const saveEntry = async (entry, value) => {
|
|
208
261
|
if (!entry.collectionPath) {
|
|
209
262
|
throw new Error('Entry missing collectionPath');
|
|
@@ -226,16 +279,17 @@ export function useEntryManager(options) {
|
|
|
226
279
|
// no token means this entry was never successfully read on this branch and nothing could check the save
|
|
227
280
|
// against other editors' work. Refused as a conflict; the server refuses it too.
|
|
228
281
|
if (expectedVersion === undefined) {
|
|
229
|
-
throw new SaveApiError(409,
|
|
230
|
-
"against other editors' changes. Reload it and try again.");
|
|
282
|
+
throw new SaveApiError(409, "This entry hasn't finished loading, so it can't be saved yet. Reload it and try again (your unsaved edits will be lost).");
|
|
231
283
|
}
|
|
232
284
|
const writeBody = {
|
|
233
285
|
...payload,
|
|
234
286
|
expectedVersion,
|
|
235
287
|
};
|
|
236
288
|
const result = await apiClient.content.write(writeParams, writeBody);
|
|
289
|
+
if (isGatewayFailure(result))
|
|
290
|
+
throw new UnansweredError();
|
|
237
291
|
if (!result.ok)
|
|
238
|
-
throw new SaveApiError(result.status, result.error, result.fieldErrors);
|
|
292
|
+
throw new SaveApiError(result.status, result.error, result.fieldErrors, result.code);
|
|
239
293
|
// Update stored version token from write response
|
|
240
294
|
if (typeof result.data?.version === 'number') {
|
|
241
295
|
entryVersionsRef.current.set(versionKey(requestBranch, entry.contentId), result.data.version);
|
|
@@ -265,6 +319,76 @@ export function useEntryManager(options) {
|
|
|
265
319
|
}
|
|
266
320
|
return normalizeContentPayload(result.data);
|
|
267
321
|
};
|
|
322
|
+
// The branch's entries as the server lists them now, without replacing the list on screen.
|
|
323
|
+
const listEntriesUncommitted = async (branch) => (await fetchEntriesAndSchema(apiClient, branch, {
|
|
324
|
+
resolvePreviewSrc: options.resolvePreviewSrc,
|
|
325
|
+
})).entries;
|
|
326
|
+
// Commits a fresh list and opens what `pick` finds (returned either way), unless the user was
|
|
327
|
+
// off `fromPath` or chose another entry or branch since. Not judged after the refresh, which
|
|
328
|
+
// moves the selection off a vanished `fromPath` on its own.
|
|
329
|
+
const refreshAndOpen = async (branch, fromPath, pick) => {
|
|
330
|
+
const stillOnIt = selectedPathRef.current === fromPath;
|
|
331
|
+
const selections = selectionsByCallerRef.current;
|
|
332
|
+
const found = (await refreshEntries(branch)).find(pick);
|
|
333
|
+
if (!found || branch !== currentBranchRef.current)
|
|
334
|
+
return undefined;
|
|
335
|
+
if (stillOnIt && selections === selectionsByCallerRef.current)
|
|
336
|
+
setSelectedPath(found.path);
|
|
337
|
+
return found;
|
|
338
|
+
};
|
|
339
|
+
const followMovedEntry = async (entry) => {
|
|
340
|
+
const branch = options.branchName;
|
|
341
|
+
const movedHere = (e) => e.contentId === entry.contentId && e.path !== entry.path;
|
|
342
|
+
if (!(await listEntriesUncommitted(branch)).some(movedHere))
|
|
343
|
+
return undefined;
|
|
344
|
+
return refreshAndOpen(branch, entry.path, movedHere);
|
|
345
|
+
};
|
|
346
|
+
// After a create that landed, a failed refresh is "not listed yet", never a failed create.
|
|
347
|
+
const openCreated = (branch, fromPath, pick) => refreshAndOpen(branch, fromPath, pick).catch((err) => {
|
|
348
|
+
console.error(err);
|
|
349
|
+
return undefined;
|
|
350
|
+
});
|
|
351
|
+
const recreateEntry = async (entry, value) => {
|
|
352
|
+
if (!entry.collectionPath) {
|
|
353
|
+
throw new Error('Entry missing collectionPath');
|
|
354
|
+
}
|
|
355
|
+
const branch = options.branchName;
|
|
356
|
+
const path = entry.slug ? `${entry.collectionPath}/${entry.slug}` : entry.collectionPath;
|
|
357
|
+
const writeParams = { branch, path };
|
|
358
|
+
if (entry.entryType)
|
|
359
|
+
writeParams.entryType = entry.entryType;
|
|
360
|
+
// The new entry, not the gone one a lagging list may still show at that path.
|
|
361
|
+
const createdHere = (e) => e.path === entry.path && e.contentId !== entry.contentId;
|
|
362
|
+
let result;
|
|
363
|
+
try {
|
|
364
|
+
result = await apiClient.content.write(writeParams, {
|
|
365
|
+
...buildWritePayload(entry, value),
|
|
366
|
+
expectedVersion: null,
|
|
367
|
+
});
|
|
368
|
+
if (isGatewayFailure(result))
|
|
369
|
+
throw new UnansweredError();
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
// An unanswered create may have landed: if the list now has a new entry at the path, it did.
|
|
373
|
+
if (!isUnanswered(err))
|
|
374
|
+
throw err;
|
|
375
|
+
const listed = await listEntriesUncommitted(branch).catch(() => []);
|
|
376
|
+
if (!listed.some(createdHere))
|
|
377
|
+
throw err;
|
|
378
|
+
const landed = await openCreated(branch, entry.path, createdHere);
|
|
379
|
+
// No answer, so no version: read it, as a draft moved onto the entry needs it as its base.
|
|
380
|
+
if (landed)
|
|
381
|
+
await loadEntry(landed).catch(console.error);
|
|
382
|
+
return landed;
|
|
383
|
+
}
|
|
384
|
+
if (!result.ok)
|
|
385
|
+
throw new SaveApiError(result.status, result.error, result.fieldErrors, result.code);
|
|
386
|
+
const created = await openCreated(branch, entry.path, createdHere);
|
|
387
|
+
if (created && typeof result.data?.version === 'number') {
|
|
388
|
+
entryVersionsRef.current.set(versionKey(branch, created.contentId), result.data.version);
|
|
389
|
+
}
|
|
390
|
+
return created;
|
|
391
|
+
};
|
|
268
392
|
// Explicit reload: always issues a fresh, independent fetch (never
|
|
269
393
|
// deduped/coalesced against an in-flight automatic load) so callers that
|
|
270
394
|
// just mutated content (save, create, rename, schema change) are
|
|
@@ -272,9 +396,9 @@ export function useEntryManager(options) {
|
|
|
272
396
|
// refreshSeqRef doc comment above; also warms the SWR cache for `branch`
|
|
273
397
|
// so a later automatic re-fetch of the same key (e.g. switching away and
|
|
274
398
|
// back) can reuse it instead of refetching.
|
|
275
|
-
const
|
|
399
|
+
const refreshContent = async (branch = options.branchName) => {
|
|
276
400
|
if (!branch)
|
|
277
|
-
return [];
|
|
401
|
+
return { entries: [], collections: [] };
|
|
278
402
|
const seq = claimRefreshSeq(branch);
|
|
279
403
|
const fetched = await fetchEntriesAndSchema(apiClient, branch, {
|
|
280
404
|
resolvePreviewSrc: options.resolvePreviewSrc,
|
|
@@ -292,8 +416,9 @@ export function useEntryManager(options) {
|
|
|
292
416
|
setView({ branch, ...fetched });
|
|
293
417
|
}
|
|
294
418
|
}
|
|
295
|
-
return fetched
|
|
419
|
+
return fetched;
|
|
296
420
|
};
|
|
421
|
+
const refreshEntries = async (branch = options.branchName) => (await refreshContent(branch)).entries;
|
|
297
422
|
const handleCreateEntry = async (collectionPath, _) => {
|
|
298
423
|
const col = collectionByPath.get(collectionPath);
|
|
299
424
|
if (!col || col.type === 'entry') {
|
|
@@ -303,7 +428,7 @@ export function useEntryManager(options) {
|
|
|
303
428
|
setCreateModalError(null);
|
|
304
429
|
setCreateModalOpen(true);
|
|
305
430
|
};
|
|
306
|
-
const handleCreateModalSubmit = async (slug, entryTypeName) => {
|
|
431
|
+
const handleCreateModalSubmit = async (slug, entryTypeName, title) => {
|
|
307
432
|
if (!createModalCollection)
|
|
308
433
|
return;
|
|
309
434
|
setCreateModalCreating(true);
|
|
@@ -317,9 +442,10 @@ export function useEntryManager(options) {
|
|
|
317
442
|
// already exist yet". The client always sends create intent here,
|
|
318
443
|
// never a blind update, so a same-slug create cannot silently
|
|
319
444
|
// overwrite existing content.
|
|
445
|
+
const data = title && selectedType?.titleField ? withTitleAtPath(selectedType.titleField, title) : {};
|
|
320
446
|
const payload = isDataOnlyFormat(format)
|
|
321
|
-
? { format: format, data
|
|
322
|
-
: { format, data
|
|
447
|
+
? { format: format, data, expectedVersion: null }
|
|
448
|
+
: { format, data, body: '', expectedVersion: null };
|
|
323
449
|
// Use collection path (e.g., "content/posts") not name (e.g., "posts")
|
|
324
450
|
const path = `${createModalCollection.path}/${slug}`;
|
|
325
451
|
const result = await apiClient.content.write({
|
|
@@ -327,6 +453,8 @@ export function useEntryManager(options) {
|
|
|
327
453
|
path,
|
|
328
454
|
entryType: entryTypeName,
|
|
329
455
|
}, payload);
|
|
456
|
+
if (isGatewayFailure(result))
|
|
457
|
+
throw new UnansweredError();
|
|
330
458
|
if (!result.ok) {
|
|
331
459
|
const errorMsg = 'error' in result ? result.error : `Create failed: ${result.status}`;
|
|
332
460
|
throw new Error(errorMsg);
|
|
@@ -352,6 +480,9 @@ export function useEntryManager(options) {
|
|
|
352
480
|
console.error(err);
|
|
353
481
|
const errorMessage = err instanceof Error ? err.message : 'Create failed';
|
|
354
482
|
setCreateModalError(errorMessage);
|
|
483
|
+
// An unanswered create may have landed; the content tree then shows it.
|
|
484
|
+
if (isUnanswered(err))
|
|
485
|
+
void refreshEntries().catch(console.error);
|
|
355
486
|
}
|
|
356
487
|
finally {
|
|
357
488
|
setCreateModalCreating(false);
|
|
@@ -364,32 +495,57 @@ export function useEntryManager(options) {
|
|
|
364
495
|
setCreateModalCreating(false);
|
|
365
496
|
};
|
|
366
497
|
const renameEntry = async (path, newSlug) => {
|
|
498
|
+
const renaming = entriesState.find((e) => e.path === path);
|
|
499
|
+
const contentId = renaming?.contentId;
|
|
500
|
+
const label = renaming?.label ?? path;
|
|
501
|
+
// As `refreshAndOpen`: the new path opens only if the user is still on the entry and branch.
|
|
502
|
+
const branch = options.branchName;
|
|
503
|
+
const stillOnIt = selectedPathRef.current === path;
|
|
504
|
+
const selections = selectionsByCallerRef.current;
|
|
505
|
+
// The rename landed whatever the refresh does. The new path is selected only once the list
|
|
506
|
+
// holds it: before that, the selection check would find it missing and open the first entry.
|
|
507
|
+
const landed = async (newPath) => {
|
|
508
|
+
const listed = await refreshEntries().catch((err) => {
|
|
509
|
+
console.error(err);
|
|
510
|
+
return null;
|
|
511
|
+
});
|
|
512
|
+
if (newPath &&
|
|
513
|
+
listed?.some((e) => e.path === newPath) &&
|
|
514
|
+
stillOnIt &&
|
|
515
|
+
selections === selectionsByCallerRef.current &&
|
|
516
|
+
branch === currentBranchRef.current) {
|
|
517
|
+
setSelectedPath(newPath);
|
|
518
|
+
}
|
|
519
|
+
notifications.show({ message: 'Entry renamed', color: 'green' });
|
|
520
|
+
};
|
|
367
521
|
options.setBusy(true);
|
|
368
522
|
try {
|
|
369
523
|
const result = await apiClient.content.renameEntry({
|
|
370
524
|
branch: options.branchName,
|
|
371
525
|
path,
|
|
372
526
|
}, { newSlug });
|
|
527
|
+
if (isGatewayFailure(result))
|
|
528
|
+
throw new UnansweredError();
|
|
373
529
|
if (!result.ok) {
|
|
374
530
|
const errorMsg = 'error' in result ? result.error : `Rename failed: ${result.status}`;
|
|
375
531
|
throw new Error(errorMsg);
|
|
376
532
|
}
|
|
377
|
-
|
|
378
|
-
if (selectedPath === path && result.data) {
|
|
379
|
-
setSelectedPath(result.data.newPath);
|
|
380
|
-
}
|
|
381
|
-
// Refresh entries to get updated paths
|
|
382
|
-
await refreshEntries();
|
|
383
|
-
notifications.show({
|
|
384
|
-
message: 'Entry renamed successfully',
|
|
385
|
-
color: 'green',
|
|
386
|
-
});
|
|
533
|
+
await landed(result.data?.newPath);
|
|
387
534
|
}
|
|
388
535
|
catch (err) {
|
|
389
536
|
console.error(err);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
537
|
+
if (!isUnanswered(err))
|
|
538
|
+
throw err;
|
|
539
|
+
// Re-listed by content ID, under the new slug it landed. Still at the old path proves
|
|
540
|
+
// nothing: past the client's deadline the server may still be working.
|
|
541
|
+
const listed = await listEntriesUncommitted(options.branchName).catch(() => null);
|
|
542
|
+
const now = contentId ? listed?.find((e) => e.contentId === contentId) : undefined;
|
|
543
|
+
if (now && now.path !== path && now.slug === newSlug) {
|
|
544
|
+
await landed(now.path);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
void refreshEntries().catch(console.error);
|
|
548
|
+
throw new MayHaveLandedError(`Renaming "${label}" may have gone through even though the server didn't answer. Check the "Content" panel before trying again.`);
|
|
393
549
|
}
|
|
394
550
|
finally {
|
|
395
551
|
options.setBusy(false);
|
|
@@ -407,6 +563,7 @@ export function useEntryManager(options) {
|
|
|
407
563
|
isInitialMount.current = false;
|
|
408
564
|
}
|
|
409
565
|
else {
|
|
566
|
+
noEntryRequested.current = false;
|
|
410
567
|
setSelectedPath('');
|
|
411
568
|
// Bound version-map growth across switches. Correctness no longer
|
|
412
569
|
// depends on this clear: tokens are keyed by `${branch}:${contentId}`
|
|
@@ -500,21 +657,37 @@ export function useEntryManager(options) {
|
|
|
500
657
|
const notifiedErrorBranchRef = useRef(null);
|
|
501
658
|
useEffect(() => {
|
|
502
659
|
if (!entriesError || !options.branchName) {
|
|
660
|
+
if (notifiedErrorBranchRef.current !== null)
|
|
661
|
+
notifications.hide(ENTRIES_LOAD_FAILED_TOAST_ID);
|
|
503
662
|
notifiedErrorBranchRef.current = null;
|
|
504
663
|
return;
|
|
505
664
|
}
|
|
506
665
|
if (notifiedErrorBranchRef.current === options.branchName)
|
|
507
666
|
return;
|
|
508
|
-
|
|
667
|
+
const branch = options.branchName;
|
|
668
|
+
notifiedErrorBranchRef.current = branch;
|
|
509
669
|
console.error(entriesError);
|
|
670
|
+
// A shown toast with this id ignores `show`, so another branch's would keep its Retry.
|
|
671
|
+
notifications.hide(ENTRIES_LOAD_FAILED_TOAST_ID);
|
|
510
672
|
notifications.show({
|
|
673
|
+
id: ENTRIES_LOAD_FAILED_TOAST_ID,
|
|
511
674
|
title: 'Could not load content',
|
|
512
|
-
message:
|
|
675
|
+
message: createElement(RetryToastMessage, {
|
|
676
|
+
message: getErrorMessage(entriesError),
|
|
677
|
+
onRetry: () => {
|
|
678
|
+
notifications.hide(ENTRIES_LOAD_FAILED_TOAST_ID);
|
|
679
|
+
notifiedErrorBranchRef.current = null;
|
|
680
|
+
void globalMutate(entriesKey(branch));
|
|
681
|
+
},
|
|
682
|
+
}),
|
|
513
683
|
color: 'red',
|
|
684
|
+
autoClose: false,
|
|
514
685
|
});
|
|
515
686
|
}, [entriesError, options.branchName]);
|
|
516
687
|
// Validate selected entry when entries change
|
|
517
688
|
useEffect(() => {
|
|
689
|
+
if (selectedPath)
|
|
690
|
+
noEntryRequested.current = false;
|
|
518
691
|
// Skip validation if entries haven't loaded yet
|
|
519
692
|
if (entriesState.length === 0)
|
|
520
693
|
return;
|
|
@@ -530,6 +703,8 @@ export function useEntryManager(options) {
|
|
|
530
703
|
// If the selected entry exists, keep it
|
|
531
704
|
if (entriesState.find((e) => e.path === selectedPath))
|
|
532
705
|
return;
|
|
706
|
+
if (selectedPath === '' && noEntryRequested.current)
|
|
707
|
+
return;
|
|
533
708
|
// Fall back to first entry
|
|
534
709
|
setSelectedPath(entriesState[0]?.path ?? '');
|
|
535
710
|
}, [entriesState, selectedPath]);
|
|
@@ -544,6 +719,10 @@ export function useEntryManager(options) {
|
|
|
544
719
|
if (selectedPath) {
|
|
545
720
|
url.searchParams.set('entry', selectedPath);
|
|
546
721
|
}
|
|
722
|
+
else if (noEntryRequested.current) {
|
|
723
|
+
// Kept, so a reload still opens no entry.
|
|
724
|
+
url.searchParams.set('entry', '');
|
|
725
|
+
}
|
|
547
726
|
else {
|
|
548
727
|
url.searchParams.delete('entry');
|
|
549
728
|
}
|
|
@@ -551,21 +730,28 @@ export function useEntryManager(options) {
|
|
|
551
730
|
}, [selectedPath]);
|
|
552
731
|
return {
|
|
553
732
|
selectedPath,
|
|
554
|
-
setSelectedPath,
|
|
733
|
+
setSelectedPath: selectEntryPath,
|
|
734
|
+
closeEntry,
|
|
555
735
|
entries: entriesState,
|
|
556
736
|
setEntries,
|
|
557
737
|
collections: collectionsState,
|
|
558
738
|
currentEntry,
|
|
559
739
|
availableSchemas,
|
|
560
740
|
entriesInitializing,
|
|
741
|
+
entriesLoadFailed: Boolean(options.branchName) && Boolean(entriesError),
|
|
561
742
|
navigatorOpen,
|
|
562
743
|
setNavigatorOpen,
|
|
563
744
|
refreshEntries,
|
|
745
|
+
refreshContent,
|
|
564
746
|
handleCreateEntry,
|
|
565
747
|
renameEntry,
|
|
566
748
|
loadEntry,
|
|
749
|
+
readEntryValue,
|
|
567
750
|
saveEntry,
|
|
568
751
|
getEntryVersion,
|
|
752
|
+
adoptEntryVersion,
|
|
753
|
+
followMovedEntry,
|
|
754
|
+
recreateEntry,
|
|
569
755
|
collectionByPath: collectionByPath,
|
|
570
756
|
createModalOpen,
|
|
571
757
|
createModalCollection,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { notifications } from '@mantine/notifications';
|
|
3
3
|
import { useApiClient } from '../context/index.js';
|
|
4
|
+
import { SETTINGS_CHANGED_CODE, SettingsChangedError } from '../settings-conflict.js';
|
|
5
|
+
import { createUserMetadataBatcher } from './user-metadata-batcher.js';
|
|
4
6
|
/**
|
|
5
7
|
* Custom hook for managing internal groups (CRUD operations).
|
|
6
8
|
*/
|
|
@@ -39,10 +41,13 @@ export function useGroupManager(options) {
|
|
|
39
41
|
expectedContentVersion: versionRef.current,
|
|
40
42
|
});
|
|
41
43
|
if (!result.ok) {
|
|
42
|
-
|
|
44
|
+
const message = result.error || 'Failed to save groups';
|
|
45
|
+
throw result.code === SETTINGS_CHANGED_CODE
|
|
46
|
+
? new SettingsChangedError(message)
|
|
47
|
+
: new Error(message);
|
|
43
48
|
}
|
|
44
49
|
notifications.show({
|
|
45
|
-
title: 'Groups
|
|
50
|
+
title: 'Groups saved',
|
|
46
51
|
message: 'Internal groups have been updated',
|
|
47
52
|
color: 'green',
|
|
48
53
|
});
|
|
@@ -50,7 +55,10 @@ export function useGroupManager(options) {
|
|
|
50
55
|
}
|
|
51
56
|
catch (err) {
|
|
52
57
|
const message = err instanceof Error ? err.message : 'Failed to save groups';
|
|
53
|
-
notifications.show({
|
|
58
|
+
notifications.show({
|
|
59
|
+
message,
|
|
60
|
+
color: err instanceof SettingsChangedError ? 'yellow' : 'red',
|
|
61
|
+
});
|
|
54
62
|
throw err;
|
|
55
63
|
}
|
|
56
64
|
}, []);
|
|
@@ -70,18 +78,7 @@ export function useGroupManager(options) {
|
|
|
70
78
|
return [];
|
|
71
79
|
}
|
|
72
80
|
}, []);
|
|
73
|
-
const handleGetUserMetadata =
|
|
74
|
-
try {
|
|
75
|
-
const result = await apiClient.permissions.getUserMetadata({ userId });
|
|
76
|
-
if (!result.ok)
|
|
77
|
-
return null;
|
|
78
|
-
return result.data?.user ?? null;
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
console.error('Get user metadata failed:', err);
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}, []);
|
|
81
|
+
const handleGetUserMetadata = useMemo(() => createUserMetadataBatcher(apiClient), [apiClient]);
|
|
85
82
|
const handleSearchExternalGroups = useCallback(async (query) => {
|
|
86
83
|
try {
|
|
87
84
|
const result = await apiClient.groups.searchExternal({ q: query });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { notifications } from '@mantine/notifications';
|
|
3
3
|
import { useApiClient } from '../context/index.js';
|
|
4
|
+
import { SETTINGS_CHANGED_CODE, SettingsChangedError } from '../settings-conflict.js';
|
|
4
5
|
/**
|
|
5
6
|
* Custom hook for managing path permissions (CRUD operations).
|
|
6
7
|
*/
|
|
@@ -42,10 +43,13 @@ export function usePermissionManager(options) {
|
|
|
42
43
|
expectedContentVersion: versionRef.current,
|
|
43
44
|
});
|
|
44
45
|
if (!result.ok) {
|
|
45
|
-
|
|
46
|
+
const message = result.error || 'Failed to save permissions';
|
|
47
|
+
throw result.code === SETTINGS_CHANGED_CODE
|
|
48
|
+
? new SettingsChangedError(message)
|
|
49
|
+
: new Error(message);
|
|
46
50
|
}
|
|
47
51
|
notifications.show({
|
|
48
|
-
title: 'Permissions
|
|
52
|
+
title: 'Permissions saved',
|
|
49
53
|
message: 'Permissions have been updated',
|
|
50
54
|
color: 'green',
|
|
51
55
|
});
|
|
@@ -53,7 +57,10 @@ export function usePermissionManager(options) {
|
|
|
53
57
|
}
|
|
54
58
|
catch (err) {
|
|
55
59
|
const message = err instanceof Error ? err.message : 'Failed to save permissions';
|
|
56
|
-
notifications.show({
|
|
60
|
+
notifications.show({
|
|
61
|
+
message,
|
|
62
|
+
color: err instanceof SettingsChangedError ? 'yellow' : 'red',
|
|
63
|
+
});
|
|
57
64
|
throw err;
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -64,13 +71,14 @@ export function usePermissionManager(options) {
|
|
|
64
71
|
// an unreadable groups.json rendered as a silently empty picker with no
|
|
65
72
|
// warning, which is the same failure class listGroupsHandler's deliberate
|
|
66
73
|
// 500 exists to prevent.
|
|
67
|
-
|
|
74
|
+
// Stable identity: useGroupsAndUsers reloads groups whenever this function changes.
|
|
75
|
+
const handleListGroups = useCallback(async () => {
|
|
68
76
|
const result = await apiClient.permissions.listGroups();
|
|
69
77
|
if (!result.ok) {
|
|
70
78
|
throw new Error(result.error || 'Failed to load groups');
|
|
71
79
|
}
|
|
72
80
|
return result.data?.groups ?? [];
|
|
73
|
-
};
|
|
81
|
+
}, [apiClient]);
|
|
74
82
|
useEffect(() => {
|
|
75
83
|
if (options.isOpen) {
|
|
76
84
|
loadPermissions();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FieldConfig } from '../../config/index.js';
|
|
2
|
+
import type { PreviewMarks } from '../PreviewFrame.js';
|
|
3
|
+
import { type InexactMark } from '../preview-marks.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the framed page reports of its marks while highlighting is on, kept for the `src` that
|
|
6
|
+
* said so: whether it marks nothing, and which marks name no field of the entry. Each such mark
|
|
7
|
+
* is a console warning once per page and path, since the toggle's note only counts them. A report
|
|
8
|
+
* is checked against the draft as it stood when the report arrived, not the draft since, and the
|
|
9
|
+
* page reports after each draft (`usePreviewHighlight`), so the two stay in step.
|
|
10
|
+
*/
|
|
11
|
+
export declare function usePreviewMarks({ src, highlightEnabled, fields, data, }: {
|
|
12
|
+
src: string | undefined;
|
|
13
|
+
highlightEnabled: boolean;
|
|
14
|
+
fields: readonly FieldConfig[];
|
|
15
|
+
data: unknown;
|
|
16
|
+
}): {
|
|
17
|
+
onMarks: (reported: PreviewMarks) => void;
|
|
18
|
+
clearMarks: () => void;
|
|
19
|
+
marksNothing: boolean;
|
|
20
|
+
inexactMarks: InexactMark[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { findInexactMarks } from '../preview-marks.js';
|
|
3
|
+
const NO_MARKS = [];
|
|
4
|
+
/**
|
|
5
|
+
* What the framed page reports of its marks while highlighting is on, kept for the `src` that
|
|
6
|
+
* said so: whether it marks nothing, and which marks name no field of the entry. Each such mark
|
|
7
|
+
* is a console warning once per page and path, since the toggle's note only counts them. A report
|
|
8
|
+
* is checked against the draft as it stood when the report arrived, not the draft since, and the
|
|
9
|
+
* page reports after each draft (`usePreviewHighlight`), so the two stay in step.
|
|
10
|
+
*/
|
|
11
|
+
export function usePreviewMarks({ src, highlightEnabled, fields, data, }) {
|
|
12
|
+
const [marks, setMarks] = useState(null);
|
|
13
|
+
const current = highlightEnabled && marks !== null && marks.src === src ? marks : null;
|
|
14
|
+
const latestData = useRef(data);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
latestData.current = data;
|
|
17
|
+
});
|
|
18
|
+
const inexactMarks = useMemo(() => (current?.paths ? findInexactMarks(fields, current.data, current.paths) : NO_MARKS), [current?.paths, current?.data, fields]);
|
|
19
|
+
const warned = useRef(new Set());
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
for (const { path, nearest } of inexactMarks) {
|
|
22
|
+
const key = JSON.stringify([src, path]);
|
|
23
|
+
if (warned.current.has(key))
|
|
24
|
+
continue;
|
|
25
|
+
warned.current.add(key);
|
|
26
|
+
console.warn(`[canopycms] The preview marks "${path}", which names no field of this entry` +
|
|
27
|
+
(nearest ? `; its nearest field is "${nearest}".` : '.'));
|
|
28
|
+
}
|
|
29
|
+
}, [inexactMarks, src]);
|
|
30
|
+
const onMarks = useCallback((reported) => setMarks({ ...reported, src: src ?? '', data: latestData.current }), [src]);
|
|
31
|
+
const clearMarks = useCallback(() => setMarks(null), []);
|
|
32
|
+
return { onMarks, clearMarks, marksNothing: current?.count === 0, inexactMarks };
|
|
33
|
+
}
|