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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DanglingReferenceSite {
|
|
2
|
+
/** Workspace the entry was read from, so two branches' identical paths warn separately. */
|
|
3
|
+
root: string;
|
|
4
|
+
/** Logical path of the referring entry, when the caller knows it. */
|
|
5
|
+
entry?: string;
|
|
6
|
+
/** Field path in `traverseFields`' format, e.g. `blocks[2].author` or `reviewers[1]`. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** The id that names no readable entry. */
|
|
9
|
+
id: string;
|
|
10
|
+
}
|
|
11
|
+
/** Warn once per process that a reference names no readable entry. */
|
|
12
|
+
export declare function warnDanglingReference(site: DanglingReferenceSite): void;
|
|
13
|
+
/**
|
|
14
|
+
* Forget every reported reference.
|
|
15
|
+
* @internal Exported for tests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resetDanglingReferenceWarnings(): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { canopyLogWarn } from './utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bounds the dedupe set below. A process that has seen this many distinct dangling references
|
|
4
|
+
* stops warning about new ones rather than growing without limit; the build guard still counts
|
|
5
|
+
* every one.
|
|
6
|
+
*/
|
|
7
|
+
const MAX_WARNED = 1000;
|
|
8
|
+
/**
|
|
9
|
+
* Dangling references already reported by this process. A log dedupe only, never consulted for
|
|
10
|
+
* content, so its module lifetime carries none of the staleness hazards in docs/concurrency.md:
|
|
11
|
+
* at worst a reference repaired and broken again is not re-reported until restart.
|
|
12
|
+
*/
|
|
13
|
+
const warned = new Set();
|
|
14
|
+
/** Warn once per process that a reference names no readable entry. */
|
|
15
|
+
export function warnDanglingReference(site) {
|
|
16
|
+
const key = `${site.root}\0${site.entry ?? ''}\0${site.path}\0${site.id}`;
|
|
17
|
+
if (warned.has(key) || warned.size >= MAX_WARNED)
|
|
18
|
+
return;
|
|
19
|
+
warned.add(key);
|
|
20
|
+
canopyLogWarn(`CanopyCMS: ${site.entry ?? 'an entry'} field "${site.path}" references missing entry ${site.id}; ` +
|
|
21
|
+
'it resolves as unavailable until the reference is repointed or cleared.');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Forget every reported reference.
|
|
25
|
+
* @internal Exported for tests.
|
|
26
|
+
*/
|
|
27
|
+
export function resetDanglingReferenceWarnings() {
|
|
28
|
+
warned.clear();
|
|
29
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
|
|
16
16
|
* must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
|
|
17
17
|
* gutter block). A log cannot hold a condition; see
|
|
18
|
-
*
|
|
18
|
+
* safeinsights/canopycms#654 for the in-app surface that would
|
|
19
19
|
* replace the terminal as its primary home.
|
|
20
20
|
*
|
|
21
21
|
* All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
|
|
16
16
|
* must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
|
|
17
17
|
* gutter block). A log cannot hold a condition; see
|
|
18
|
-
*
|
|
18
|
+
* safeinsights/canopycms#654 for the in-app surface that would
|
|
19
19
|
* replace the terminal as its primary home.
|
|
20
20
|
*
|
|
21
21
|
* All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
|
|
@@ -12,6 +12,10 @@ export interface BranchSummary {
|
|
|
12
12
|
access?: {
|
|
13
13
|
users?: string[];
|
|
14
14
|
groups?: string[];
|
|
15
|
+
managerOrAdminAllowed?: boolean;
|
|
16
|
+
/** Entries the server withheld from this viewer; see `BranchListItem.withheldAccess`. */
|
|
17
|
+
withheldUsers?: number;
|
|
18
|
+
withheldGroups?: number;
|
|
15
19
|
};
|
|
16
20
|
pullRequestUrl?: string;
|
|
17
21
|
pullRequestNumber?: number;
|
|
@@ -42,15 +46,16 @@ export interface UserContext {
|
|
|
42
46
|
groups?: string[];
|
|
43
47
|
}
|
|
44
48
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* - Admins/Reviewers always have access
|
|
49
|
+
* What the current user can do to a branch, by the backend's hybrid model. Workflow access
|
|
50
|
+
* (`hasWorkflowAccess`) goes to the creator, the users and groups on the branch's access list,
|
|
51
|
+
* anyone on a system branch other than the protected base, and reviewers and admins; a
|
|
52
|
+
* `managerOrAdminAllowed` lockdown leaves only reviewers and admins. Submit also
|
|
53
|
+
* needs the server's `submitBlocked` to be false; Withdraw needs the branch submitted or approved.
|
|
51
54
|
* @internal Exported for tests.
|
|
52
55
|
*/
|
|
53
56
|
export declare const getBranchPermissions: (branch: BranchSummary, user: UserContext | undefined) => {
|
|
57
|
+
/** Creator, someone given access, a system branch, a reviewer or an admin: who may submit or withdraw. */
|
|
58
|
+
hasWorkflowAccess: boolean;
|
|
54
59
|
canSubmit: boolean;
|
|
55
60
|
canWithdraw: boolean;
|
|
56
61
|
canDelete: boolean;
|
|
@@ -61,7 +66,13 @@ export interface BranchManagerProps {
|
|
|
61
66
|
mode: OperatingMode;
|
|
62
67
|
/** Current user context for permission checks */
|
|
63
68
|
user?: UserContext;
|
|
69
|
+
/** The branch the editor has open: listed first, marked, and offered its workflow action rather than Open. */
|
|
70
|
+
currentBranchName?: string;
|
|
71
|
+
/** Why the open branch's Submit or Withdraw must wait, such as a Save all still writing it. */
|
|
72
|
+
currentWorkflowBlockedReason?: string;
|
|
64
73
|
onSelect?: (name: string) => void;
|
|
74
|
+
/** Awaited before the spinner starts, so a confirmation it shows never reads as a hung create. `false` abandons the create. */
|
|
75
|
+
onBeforeCreate?: () => Promise<boolean>;
|
|
65
76
|
/**
|
|
66
77
|
* Resolving `false` means the branch was not created, so the form stays open
|
|
67
78
|
* with the user's values; any other return (including none) closes it.
|
|
@@ -78,11 +89,16 @@ export interface BranchManagerProps {
|
|
|
78
89
|
onClose?: () => void;
|
|
79
90
|
comments?: CommentThread[];
|
|
80
91
|
currentUserId?: string;
|
|
92
|
+
/** Whether the user may resolve any comment thread; a thread's author always may resolve it. */
|
|
81
93
|
canResolve?: boolean;
|
|
82
94
|
onAddComment?: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
|
|
83
95
|
onResolveThread?: (threadId: string) => Promise<void>;
|
|
84
96
|
highlightThreadId?: string;
|
|
85
|
-
/**
|
|
97
|
+
/** Names people without `onGetOwnerMetadata`; with neither, access users are counted and owners read "Name unavailable" */
|
|
86
98
|
onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
99
|
+
/** Names branch owners and access users; the branch listing seeds owners only */
|
|
100
|
+
onGetOwnerMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
101
|
+
/** Names the people in branch comments; without it they show as ids */
|
|
102
|
+
onGetCommentUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
87
103
|
}
|
|
88
104
|
export declare const BranchManager: React.FC<BranchManagerProps>;
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from 'react';
|
|
4
|
-
import { Badge, Button, Group, Paper, ScrollArea, Stack, Text, TextInput, Textarea, Collapse, Tooltip, } from '@mantine/core';
|
|
4
|
+
import { ActionIcon, Badge, Button, Group, Menu, Paper, ScrollArea, Stack, Text, TextInput, Textarea, Collapse, Tooltip, } from '@mantine/core';
|
|
5
|
+
import { IconDots, IconExternalLink } from '@tabler/icons-react';
|
|
5
6
|
import { BranchComments } from './comments/BranchComments.js';
|
|
6
7
|
import { UserBadge } from './components/UserBadge.js';
|
|
8
|
+
import { BranchAccessList } from './components/BranchAccessList.js';
|
|
7
9
|
// Import directly from helpers to avoid server-only code in authorization barrel
|
|
8
|
-
import { isAdmin, isReviewer } from '../authorization/helpers.js';
|
|
10
|
+
import { isAdmin, isLockedOutOfBranch, isReviewer } from '../authorization/helpers.js';
|
|
9
11
|
import { clientOperatingStrategy } from '../operating-mode/client.js';
|
|
10
|
-
import {
|
|
12
|
+
import { DestructiveMenuItem } from './components/DestructiveMenuItem.js';
|
|
13
|
+
import { ReasonedMenuItem } from './components/ReasonedMenuItem.js';
|
|
14
|
+
import { RelativeTime } from './components/RelativeTime.js';
|
|
15
|
+
import { branchStatusPresentation } from './branch-status.js';
|
|
16
|
+
import { EDITOR_ACTIONS } from './copy.js';
|
|
17
|
+
import { SYSTEM_USER_ID } from '../system-users.js';
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - Admins/Reviewers always have access
|
|
19
|
+
* What the current user can do to a branch, by the backend's hybrid model. Workflow access
|
|
20
|
+
* (`hasWorkflowAccess`) goes to the creator, the users and groups on the branch's access list,
|
|
21
|
+
* anyone on a system branch other than the protected base, and reviewers and admins; a
|
|
22
|
+
* `managerOrAdminAllowed` lockdown leaves only reviewers and admins. Submit also
|
|
23
|
+
* needs the server's `submitBlocked` to be false; Withdraw needs the branch submitted or approved.
|
|
18
24
|
* @internal Exported for tests.
|
|
19
25
|
*/
|
|
20
26
|
export const getBranchPermissions = (branch, user) => {
|
|
21
27
|
if (!user) {
|
|
22
28
|
return {
|
|
29
|
+
hasWorkflowAccess: false,
|
|
23
30
|
canSubmit: false,
|
|
24
31
|
canWithdraw: false,
|
|
25
32
|
canDelete: false,
|
|
@@ -30,13 +37,14 @@ export const getBranchPermissions = (branch, user) => {
|
|
|
30
37
|
const userIsReviewer = isReviewer(user.groups);
|
|
31
38
|
const userIsCreator = branch.createdBy === user.userId;
|
|
32
39
|
// The system-branch grant is disabled on the protected base branch -- its
|
|
33
|
-
// auto-provision marker (createdBy:
|
|
40
|
+
// auto-provision marker (createdBy: SYSTEM_USER_ID) would otherwise let
|
|
34
41
|
// anyone with general access submit/withdraw/delete it.
|
|
35
|
-
const isSystemBranch = branch.createdBy ===
|
|
42
|
+
const isSystemBranch = branch.createdBy === SYSTEM_USER_ID && !branch.isProtected;
|
|
36
43
|
const userInACL = (branch.access?.users?.includes(user.userId) ||
|
|
37
44
|
user.groups?.some((g) => branch.access?.groups?.includes(g))) ??
|
|
38
45
|
false;
|
|
39
|
-
const canPerformWorkflowActions =
|
|
46
|
+
const canPerformWorkflowActions = !isLockedOutOfBranch(branch.access, user.groups) &&
|
|
47
|
+
(userIsCreator || userInACL || isSystemBranch || userIsAdmin || userIsReviewer);
|
|
40
48
|
// Submit: can perform workflow actions AND the server says submit isn't
|
|
41
49
|
// blocked. `submitBlocked` is the server's own compound answer -- status
|
|
42
50
|
// past 'editing' OR the protected base branch -- computed once in
|
|
@@ -70,13 +78,97 @@ export const getBranchPermissions = (branch, user) => {
|
|
|
70
78
|
const canRequestChanges = (userIsAdmin || userIsReviewer) &&
|
|
71
79
|
branch.status === 'submitted' &&
|
|
72
80
|
branch.pullRequestState !== 'closed';
|
|
73
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
hasWorkflowAccess: canPerformWorkflowActions,
|
|
83
|
+
canSubmit,
|
|
84
|
+
canWithdraw,
|
|
85
|
+
canDelete,
|
|
86
|
+
canRequestChanges,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/** The lookup when a card has none: an owner reads "Name unavailable" and access users are counted, not named. */
|
|
90
|
+
const noUserLookup = async () => null;
|
|
91
|
+
const CLOSED_PULL_REQUEST_REASON = 'The pull request was closed on GitHub. Withdraw the branch from review, then submit it again to open a new one.';
|
|
92
|
+
/**
|
|
93
|
+
* The workflow action a branch's status calls for, or undefined when none applies: Submit for
|
|
94
|
+
* an unprotected branch in editing, Withdraw once it is in review. 'approved' withdraws too, as
|
|
95
|
+
* its only non-destructive exit. A user without workflow access is offered neither, since it
|
|
96
|
+
* would never apply to them; a reason is given only for a block that depends on the branch's state.
|
|
97
|
+
*/
|
|
98
|
+
const workflowAction = (b, perms, onSubmit, onWithdraw) => {
|
|
99
|
+
if (!perms.hasWorkflowAccess)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (b.status === 'submitted' || b.status === 'approved') {
|
|
102
|
+
return {
|
|
103
|
+
label: EDITOR_ACTIONS.withdrawFromReview,
|
|
104
|
+
color: 'orange',
|
|
105
|
+
testId: `withdraw-branch-button-${b.name}`,
|
|
106
|
+
run: () => onWithdraw?.(b.name),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (b.status === 'editing' && !b.isProtected) {
|
|
110
|
+
return {
|
|
111
|
+
label: EDITOR_ACTIONS.submitForReview,
|
|
112
|
+
color: 'brand',
|
|
113
|
+
testId: `submit-branch-button-${b.name}`,
|
|
114
|
+
run: () => onSubmit?.(b.name),
|
|
115
|
+
blockedReason: perms.canSubmit ? undefined : "This branch can't be submitted right now.",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
74
119
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
/** Whether `user` reviews `b` rather than owns it: a reviewer or admin who neither made it nor was given access. */
|
|
121
|
+
const isReviewerOfOthersSubmission = (b, user) => !!user &&
|
|
122
|
+
isReviewer(user.groups) &&
|
|
123
|
+
b.status === 'submitted' &&
|
|
124
|
+
b.createdBy !== user.userId &&
|
|
125
|
+
!b.access?.users?.includes(user.userId) &&
|
|
126
|
+
!user.groups?.some((g) => b.access?.groups?.includes(g));
|
|
127
|
+
/**
|
|
128
|
+
* One branch: its state, and one primary action, with the rest in a "…" menu. The current branch
|
|
129
|
+
* shows its workflow action, or Request changes to a reviewer looking at someone else's
|
|
130
|
+
* submission; the others show Open, which the current one never needs. No action is shown twice.
|
|
131
|
+
*/
|
|
132
|
+
const BranchCard = ({ branch: b, isCurrent, workflowBlockedReason, user, onSelect, onSubmit, onWithdraw, onRequestChanges, onDelete, onGetUserMetadata, onGetOwnerMetadata = onGetUserMetadata, }) => {
|
|
133
|
+
const statusBadge = branchStatusPresentation(b.status, b.isProtected);
|
|
134
|
+
const perms = getBranchPermissions(b, user);
|
|
135
|
+
const action = workflowAction(b, perms, onSubmit, onWithdraw);
|
|
136
|
+
const workflow = action && workflowBlockedReason ? { ...action, blockedReason: workflowBlockedReason } : action;
|
|
137
|
+
const userIsAdmin = isAdmin(user?.groups);
|
|
138
|
+
const requestChanges = isReviewer(user?.groups) && b.status === 'submitted'
|
|
139
|
+
? {
|
|
140
|
+
label: EDITOR_ACTIONS.requestChanges,
|
|
141
|
+
color: 'orange',
|
|
142
|
+
testId: `request-changes-branch-button-${b.name}`,
|
|
143
|
+
run: () => onRequestChanges?.(b.name),
|
|
144
|
+
blockedReason: b.pullRequestState === 'closed' ? CLOSED_PULL_REQUEST_REASON : undefined,
|
|
145
|
+
}
|
|
146
|
+
: undefined;
|
|
147
|
+
const primary = !isCurrent
|
|
148
|
+
? undefined
|
|
149
|
+
: isReviewerOfOthersSubmission(b, user)
|
|
150
|
+
? requestChanges
|
|
151
|
+
: workflow;
|
|
152
|
+
const menuActions = [workflow, requestChanges].filter((a) => !!a && a !== primary);
|
|
153
|
+
const showViewPullRequest = !!b.pullRequestUrl;
|
|
154
|
+
const showDelete = !b.isProtected && (userIsAdmin || (!!user && b.createdBy === user.userId));
|
|
155
|
+
const hasMenu = menuActions.length > 0 || showViewPullRequest || showDelete;
|
|
156
|
+
const menuLabel = `More actions for ${b.name}`;
|
|
157
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
158
|
+
return (_jsx(Paper, { withBorder: true, p: "md", shadow: "xs", "aria-current": isCurrent ? 'true' : undefined, "data-current": isCurrent || undefined, "data-testid": `branch-list-item-${b.name}`, children: _jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", children: [_jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", children: [_jsx(Text, { fw: 600, children: b.name }), isCurrent && (_jsx(Badge, { color: "brand", variant: "filled", "data-testid": `branch-current-badge-${b.name}`, children: "Current" })), statusBadge && (_jsx(Badge, { color: statusBadge.color, variant: statusBadge.variant, "data-testid": `branch-status-badge-${b.name}`, "data-status": b.status, "data-protected": b.isProtected || undefined, children: statusBadge.label })), b.status === 'archived' && b.mergedAt && (_jsx(Badge, { color: "teal", variant: "light", "data-testid": `branch-merged-badge-${b.name}`, children: "Merged" })), b.pullRequestNumber && (_jsxs(Badge, { color: b.pullRequestState === 'closed'
|
|
159
|
+
? 'red'
|
|
160
|
+
: b.pullRequestState === 'merged'
|
|
161
|
+
? 'violet'
|
|
162
|
+
: 'blue', variant: "light", children: ["PR #", b.pullRequestNumber, b.pullRequestState === 'closed' ? ' (closed)' : ''] })), b.commentCount !== undefined && b.commentCount > 0 && (_jsxs(Badge, { color: "grape", variant: "light", children: [b.commentCount, " ", b.commentCount === 1 ? 'comment' : 'comments'] })), b.syncStatus === 'sync-failed' && (_jsx(Tooltip, { label: "GitHub sync failed \u2014 an admin can retry it from System health.", disabled: Boolean(b.syncFailureReason), multiline: true, maw: 320, children: _jsx(Badge, { color: "red", variant: "light", "data-testid": `sync-failed-badge-${b.name}`, children: "Sync failed" }) })), b.syncStatus === 'pending-sync' && (_jsx(Tooltip, { label: "Changes are on their way to GitHub.", children: _jsx(Badge, { color: "gray", variant: "light", "data-testid": `pending-sync-badge-${b.name}`, children: "Syncing\u2026" }) })), b.conflictStatus === 'conflicts-detected' && (_jsx(Tooltip, { label: "This branch's version was kept for conflicting entries during a rebase \u2014 review the flagged entries.", children: _jsxs(Badge, { color: "orange", variant: "light", "data-testid": `conflicts-badge-${b.name}`, children: ["Conflicts", b.conflictFiles?.length ? ` (${b.conflictFiles.length})` : ''] }) }))] }), b.syncStatus === 'sync-failed' && b.syncFailureReason && (_jsxs(Text, { size: "xs", c: "var(--canopy-danger-text)", maw: 480, "data-testid": `sync-failed-reason-${b.name}`, children: [b.syncFailureReason, b.status === 'submitted' && ' Withdraw it from review to make changes.'] })), _jsxs(Group, { gap: "xs", align: "center", children: [b.updatedAt && (_jsx(RelativeTime, { iso: b.updatedAt, prefix: "Updated ", size: "xs", c: "dimmed" })), b.createdBy && (_jsxs(Group, { gap: "xs", align: "center", wrap: "nowrap", children: [_jsx(Text, { size: "xs", c: "dimmed", children: "\u2022" }), _jsxs(Group, { gap: 4, align: "center", wrap: "nowrap", children: [_jsx(Text, { size: "xs", c: "dimmed", children: "Owner:" }), _jsx(UserBadge, { userId: b.createdBy, getUserMetadata: onGetOwnerMetadata ?? noUserLookup, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })] })] }))] }), b.access && (_jsx(BranchAccessList, { users: b.access.users, groups: b.access.groups, withheldUsers: b.access.withheldUsers, withheldGroups: b.access.withheldGroups, getUserMetadata: onGetOwnerMetadata ?? noUserLookup }))] }), _jsxs(Group, { gap: 8, wrap: "nowrap", children: [!isCurrent && (_jsx(Button, { variant: "light", onClick: () => onSelect?.(b.name), "data-testid": `switch-to-branch-button-${b.name}`, children: "Open" })), primary && (_jsx(Tooltip, { label: primary.blockedReason, disabled: !primary.blockedReason, multiline: true, maw: 280, children: _jsx(Button, { variant: "light", color: primary.color, onClick: () => {
|
|
163
|
+
if (!primary.blockedReason)
|
|
164
|
+
primary.run();
|
|
165
|
+
}, "data-disabled": primary.blockedReason ? true : undefined, "aria-disabled": primary.blockedReason ? true : undefined, "data-testid": primary.testId, children: primary.label }) })), hasMenu && (_jsxs(Menu, { withinPortal: true, shadow: "sm", position: "bottom-end", width: 280, onChange: setMenuOpen, children: [_jsx(Tooltip, { label: menuLabel, disabled: menuOpen, children: _jsx(Menu.Target, { children: _jsx(ActionIcon, { variant: "subtle", color: "gray", "aria-label": menuLabel, "data-testid": `branch-actions-menu-${b.name}`, children: _jsx(IconDots, { size: 16, "aria-hidden": true }) }) }) }), _jsxs(Menu.Dropdown, { children: [menuActions.map((action) => (_jsx(ReasonedMenuItem, { blockedReason: action.blockedReason, onClick: action.run, "data-testid": action.testId, children: action.label }, action.testId))), showViewPullRequest && (_jsx(Menu.Item, { component: "a", href: b.pullRequestUrl, target: "_blank", rel: "noopener noreferrer", leftSection: _jsx(IconExternalLink, { size: 14, "aria-hidden": true }), "data-testid": `view-pull-request-${b.name}`, children: b.pullRequestNumber
|
|
166
|
+
? `View pull request #${b.pullRequestNumber}`
|
|
167
|
+
: 'View pull request' })), showDelete && (_jsxs(_Fragment, { children: [(menuActions.length > 0 || showViewPullRequest) && _jsx(Menu.Divider, {}), _jsx(DestructiveMenuItem, { "data-testid": `delete-branch-button-${b.name}`, blockedReason: b.status === 'submitted' || b.status === 'approved'
|
|
168
|
+
? 'This branch is in review. Withdraw it from review before deleting it.'
|
|
169
|
+
: undefined, onClick: () => onDelete?.(b.name), children: "Delete branch" })] }))] })] }))] })] }) }));
|
|
78
170
|
};
|
|
79
|
-
export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, }) => {
|
|
171
|
+
export const BranchManager = ({ branches, mode, user, currentBranchName, currentWorkflowBlockedReason, onSelect, onBeforeCreate, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, onGetOwnerMetadata, onGetCommentUserMetadata, }) => {
|
|
80
172
|
const supportsBranching = clientOperatingStrategy(mode).supportsBranching();
|
|
81
173
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
82
174
|
const [newBranchName, setNewBranchName] = useState('');
|
|
@@ -89,8 +181,10 @@ export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDele
|
|
|
89
181
|
if (creatingRef.current || !newBranchName.trim())
|
|
90
182
|
return;
|
|
91
183
|
creatingRef.current = true;
|
|
92
|
-
setCreating(true);
|
|
93
184
|
try {
|
|
185
|
+
if (onBeforeCreate && !(await onBeforeCreate()))
|
|
186
|
+
return;
|
|
187
|
+
setCreating(true);
|
|
94
188
|
const created = await onCreate?.({
|
|
95
189
|
name: newBranchName.trim(),
|
|
96
190
|
title: newBranchTitle.trim() || undefined,
|
|
@@ -108,40 +202,11 @@ export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDele
|
|
|
108
202
|
setCreating(false);
|
|
109
203
|
}
|
|
110
204
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
? 'red'
|
|
119
|
-
: b.pullRequestState === 'merged'
|
|
120
|
-
? 'violet'
|
|
121
|
-
: 'blue', variant: "light", children: ["PR #", b.pullRequestNumber, b.pullRequestState === 'closed' ? ' (closed)' : ''] })), b.commentCount !== undefined && b.commentCount > 0 && (_jsxs(Badge, { color: "grape", variant: "light", children: [b.commentCount, " ", b.commentCount === 1 ? 'comment' : 'comments'] })), b.syncStatus === 'sync-failed' && (_jsx(Tooltip, { label: b.syncFailureReason
|
|
122
|
-
? `GitHub sync failed: ${b.syncFailureReason}`
|
|
123
|
-
: 'GitHub sync failed — an admin can retry it from System health.', multiline: true, maw: 320, children: _jsx(Badge, { color: "red", variant: "light", "data-testid": `sync-failed-badge-${b.name}`, children: "Sync failed" }) })), b.syncStatus === 'pending-sync' && (_jsx(Tooltip, { label: "Changes are on their way to GitHub.", children: _jsx(Badge, { color: "gray", variant: "light", "data-testid": `pending-sync-badge-${b.name}`, children: "Syncing\u2026" }) })), b.conflictStatus === 'conflicts-detected' && (_jsx(Tooltip, { label: "This branch's version was kept for conflicting entries during a rebase \u2014 review the flagged entries.", children: _jsxs(Badge, { color: "orange", variant: "light", "data-testid": `conflicts-badge-${b.name}`, children: ["Conflicts", b.conflictFiles?.length ? ` (${b.conflictFiles.length})` : ''] }) }))] }), _jsxs(Group, { gap: "xs", align: "center", children: [b.updatedAt && (_jsx(Tooltip, { label: new Date(b.updatedAt).toLocaleString(), children: _jsxs(Text, { size: "xs", c: "dimmed", children: ["Updated ", formatRelativeTime(b.updatedAt)] }) })), b.createdBy && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "xs", c: "dimmed", children: "\u2022" }), _jsx(Text, { size: "xs", c: "dimmed", children: "Owner:" }), onGetUserMetadata ? (_jsx(UserBadge, { userId: b.createdBy, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", c: "dimmed", children: b.createdBy }))] })), b.pullRequestUrl && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "xs", c: "dimmed", children: "\u2022" }), _jsx(Text, { size: "xs", c: "blue", component: "a", href: b.pullRequestUrl, target: "_blank", rel: "noopener noreferrer", style: {
|
|
124
|
-
textDecoration: 'underline',
|
|
125
|
-
cursor: 'pointer',
|
|
126
|
-
}, children: "View PR" })] }))] }), b.access && (_jsxs(Group, { gap: 6, children: [b.access.users?.map((u) => onGetUserMetadata ? (_jsx(UserBadge, { userId: u, getUserMetadata: onGetUserMetadata, variant: "avatar-only", size: "xs", showEmailTooltip: true }, u)) : (_jsx(Badge, { variant: "outline", color: "neutral", children: u }, u))), b.access.groups?.map((g) => (_jsx(Badge, { variant: "outline", color: "neutral", children: g }, g)))] }))] }), _jsxs(Group, { gap: 8, children: [_jsx(Button, { size: "xs", variant: "light", onClick: () => onSelect?.(b.name), "data-testid": `switch-to-branch-button-${b.name}`, children: "Open" }), b.status === 'submitted' || b.status === 'approved' ? (_jsx(Tooltip, { label: "Only the branch creator can withdraw", disabled: perms.canWithdraw, children: _jsx(Button, { size: "xs", variant: "light", color: "orange", onClick: () => onWithdraw?.(b.name), disabled: !perms.canWithdraw, "data-testid": `withdraw-branch-button-${b.name}`, children: "Withdraw" }) })) : (_jsx(Tooltip, { label: b.isProtected
|
|
127
|
-
? 'The base branch cannot be submitted'
|
|
128
|
-
: // A status arm: canSubmit folds in the server's status rule as
|
|
129
|
-
// well as the base-branch one, so an archived (or any
|
|
130
|
-
// non-editing) branch reads its own reason instead of the
|
|
131
|
-
// generic "Only the branch creator can submit".
|
|
132
|
-
b.status !== 'editing'
|
|
133
|
-
? `A branch with status "${b.status}" cannot be submitted`
|
|
134
|
-
: 'Only the branch creator can submit', disabled: perms.canSubmit, children: _jsx(Button, { size: "xs", variant: "light", color: "green", onClick: () => onSubmit?.(b.name), disabled: !perms.canSubmit, "data-testid": `submit-branch-button-${b.name}`, children: "Submit" }) })), _jsx(Tooltip, { label: b.pullRequestState === 'closed'
|
|
135
|
-
? 'PR was closed on GitHub without merging'
|
|
136
|
-
: 'Only Reviewers or Admins can request changes', disabled: perms.canRequestChanges, children: _jsx(Button, { size: "xs", variant: "outline", color: "neutral", onClick: () => onRequestChanges?.(b.name), disabled: !perms.canRequestChanges, "data-testid": `request-changes-branch-button-${b.name}`, children: "Request changes" }) }), _jsx(Tooltip, { label: b.isProtected
|
|
137
|
-
? 'The base branch cannot be deleted'
|
|
138
|
-
: // A status arm, mirroring the Submit tooltip above: without it,
|
|
139
|
-
// a branch blocked by its own status reads the generic "Only
|
|
140
|
-
// Admin or branch creator can delete" instead of naming the open
|
|
141
|
-
// PR. 'approved' is included alongside 'submitted' since both
|
|
142
|
-
// mean an open PR is blocking deletion.
|
|
143
|
-
b.status === 'submitted' || b.status === 'approved'
|
|
144
|
-
? `Cannot delete branch with open PR (status "${b.status}")`
|
|
145
|
-
: 'Only Admin or branch creator can delete', disabled: perms.canDelete, children: _jsx(Button, { size: "xs", variant: "outline", color: "red", onClick: () => onDelete?.(b.name), disabled: !perms.canDelete, "data-testid": `delete-branch-button-${b.name}`, children: "Delete" }) })] })] }) }, b.name));
|
|
146
|
-
}) })) })] }));
|
|
205
|
+
const orderedBranches = currentBranchName
|
|
206
|
+
? [
|
|
207
|
+
...branches.filter((b) => b.name === currentBranchName),
|
|
208
|
+
...branches.filter((b) => b.name !== currentBranchName),
|
|
209
|
+
]
|
|
210
|
+
: branches;
|
|
211
|
+
return (_jsxs(Stack, { h: "100%", style: { display: 'flex', flexDirection: 'column' }, gap: 0, "data-testid": "branch-manager", children: [currentUserId && onAddComment && onResolveThread && (_jsx(Stack, { pt: "sm", children: _jsx(BranchComments, { comments: comments, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetCommentUserMetadata }) })), !!supportsBranching && (_jsxs(Stack, { gap: "sm", pt: "sm", children: [_jsx(Button, { variant: "light", size: "sm", fullWidth: true, onClick: () => setShowCreateForm(!showCreateForm), disabled: creating, "data-testid": "create-branch-button", children: showCreateForm ? 'Cancel' : `${EDITOR_ACTIONS.newBranch}…` }), _jsx(Collapse, { in: showCreateForm, children: _jsx(Paper, { withBorder: true, p: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsx(TextInput, { label: "Branch name", placeholder: "feature/my-branch", value: newBranchName, onChange: (e) => setNewBranchName(e.target.value), required: true, disabled: creating, "data-testid": "branch-name-input" }), _jsx(TextInput, { label: "Title", placeholder: "Brief description", value: newBranchTitle, onChange: (e) => setNewBranchTitle(e.target.value), disabled: creating, "data-testid": "branch-title-input" }), _jsx(Textarea, { label: "Description", placeholder: "Detailed description of the changes", value: newBranchDescription, onChange: (e) => setNewBranchDescription(e.target.value), minRows: 2, disabled: creating, "data-testid": "branch-description-textarea" }), _jsx(Button, { size: "sm", onClick: handleCreate, disabled: !newBranchName.trim(), loading: creating, fullWidth: true, "data-testid": "create-branch-submit", children: "Create branch" })] }) }) })] })), _jsx(ScrollArea, { style: { flex: 1 }, pb: "md", children: branches.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "md", children: !supportsBranching ? 'Branch management is not available.' : 'No branches available.' })) : (_jsx(Stack, { gap: "sm", children: orderedBranches.map((b) => (_jsx(BranchCard, { branch: b, isCurrent: b.name === currentBranchName, workflowBlockedReason: b.name === currentBranchName ? currentWorkflowBlockedReason : undefined, user: user, onSelect: onSelect, onSubmit: onSubmit, onWithdraw: onWithdraw, onRequestChanges: onRequestChanges, onDelete: onDelete, onGetUserMetadata: onGetUserMetadata, onGetOwnerMetadata: onGetOwnerMetadata }, b.name))) })) })] }));
|
|
147
212
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CanopyClientConfig } from '../config/index.js';
|
|
3
3
|
import type { EditorProps } from './Editor.js';
|
|
4
|
-
export interface CanopyEditorProps extends Omit<EditorProps, 'collections' | 'previewBaseByCollection' | 'previewPrefix' | '
|
|
4
|
+
export interface CanopyEditorProps extends Omit<EditorProps, 'collections' | 'previewBaseByCollection' | 'previewPrefix' | 'mdxAllow' | 'title' | 'themeOptions' | 'entries' | 'contentRoot' | 'operatingMode'> {
|
|
5
5
|
config: CanopyClientConfig;
|
|
6
6
|
entries?: EditorProps['entries'];
|
|
7
7
|
}
|
|
@@ -11,10 +11,9 @@ export const CanopyEditor = ({ config, entries = [], initialSelectedId, initialV
|
|
|
11
11
|
// detected default branch on the first branches.list() load.
|
|
12
12
|
const resolvedBranchName = branchName ?? config.defaultActiveBranch ?? config.defaultBaseBranch ?? '';
|
|
13
13
|
const resolvedTitle = config.editor?.title ?? 'CanopyCMS Editor';
|
|
14
|
-
const resolvedSubtitle = config.editor?.subtitle;
|
|
15
14
|
const resolvedTheme = config.editor?.theme ?? undefined;
|
|
16
15
|
// The gate owns the SWRProvider (keyed by user id), so it sits between the API client and the
|
|
17
16
|
// editor: see EditorAuthGate for what a signed-out or lapsed session renders.
|
|
18
|
-
return (_jsx(ApiClientProvider, { basePath: config.basePath, children: _jsx(EditorAuthGate, { SignInComponent: config.editor?.SignInComponent, themeOptions: resolvedTheme, children: _jsx(Editor, { entries: entries, title: resolvedTitle,
|
|
17
|
+
return (_jsx(ApiClientProvider, { basePath: config.basePath, editorMode: config.mode, children: _jsx(EditorAuthGate, { SignInComponent: config.editor?.SignInComponent, themeOptions: resolvedTheme, editorMode: config.mode, children: _jsx(Editor, { entries: entries, title: resolvedTitle, branchName: resolvedBranchName, operatingMode: config.mode, initialSelectedId: initialSelectedId, initialValues: initialValues, renderPreview: renderPreview, onCreateEntry: onCreateEntry, customRenderers: customRenderers, collections: collections, contentRoot: config.contentRoot, entryLinkUrl: config.entryLinkUrl, previewBaseByCollection: config.editor?.previewBase, previewPrefix: config.editor?.previewPrefix, mdxAllow: config.mdxAllow, basePath: config.basePath, themeOptions: resolvedTheme, AccountComponent: config.editor?.AccountComponent, onAccountClick: config.editor?.onAccountClick, onLogoutClick: config.editor?.onLogoutClick }) }) }));
|
|
19
18
|
};
|
|
20
19
|
export default CanopyEditor;
|
|
@@ -2,9 +2,19 @@ import React from 'react';
|
|
|
2
2
|
import type { CommentThread } from '../comment-store.js';
|
|
3
3
|
import type { UserSearchResult } from '../auth/types.js';
|
|
4
4
|
export interface CommentsPanelProps {
|
|
5
|
+
/**
|
|
6
|
+
* Whether the drawer is open. The panel stays mounted while closed, so the composer text and
|
|
7
|
+
* reply drafts survive a close; key it by branch to reset them for another one. It refuses to
|
|
8
|
+
* close while a write is in flight, since the drawer unmounts its contents on close, and each
|
|
9
|
+
* thread's busy state and error with them.
|
|
10
|
+
*/
|
|
11
|
+
opened: boolean;
|
|
5
12
|
branchName: string;
|
|
6
13
|
comments: CommentThread[];
|
|
14
|
+
/** Whether the user may resolve any thread; a thread's own author always may resolve it. */
|
|
7
15
|
canResolve: boolean;
|
|
16
|
+
/** The user the panel is for, to tell which threads they started. */
|
|
17
|
+
currentUserId: string;
|
|
8
18
|
onAddComment: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
|
|
9
19
|
onResolveThread: (threadId: string) => Promise<void>;
|
|
10
20
|
onClose: () => void;
|
|
@@ -16,5 +26,9 @@ export interface CommentsPanelProps {
|
|
|
16
26
|
onJumpToBranch?: (threadId: string) => void;
|
|
17
27
|
/** Optional function to fetch user metadata for displaying user badges */
|
|
18
28
|
onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
29
|
+
/** An entry's title for a thread's heading; without an answer the heading shows its path. */
|
|
30
|
+
entryLabel?: (entryPath: string) => string | undefined;
|
|
31
|
+
/** A field thread's breadcrumb for its heading; without an answer the heading shows its path. */
|
|
32
|
+
fieldLabel?: (thread: CommentThread) => string | undefined;
|
|
19
33
|
}
|
|
20
34
|
export declare const CommentsPanel: React.FC<CommentsPanelProps>;
|
|
@@ -1,66 +1,91 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Badge, Button, Drawer, Group, Paper, ScrollArea, Stack, Text, Textarea, Title, Switch, } from '@mantine/core';
|
|
3
|
+
import { useId, useRef, useState } from 'react';
|
|
4
|
+
import { Badge, Button, Drawer, Group, Paper, ScrollArea, Stack, Text, Textarea, Title, Switch, Tooltip, VisuallyHidden, } from '@mantine/core';
|
|
5
5
|
import { UserBadge } from './components/UserBadge.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import { userIdLabel } from './user-display.js';
|
|
7
|
+
import { RelativeTime } from './components/RelativeTime.js';
|
|
8
|
+
import { ThreadReplyControls } from './comments/ThreadReplyControls.js';
|
|
9
|
+
import { useTrackedWrites } from './comments/use-tracked-writes.js';
|
|
10
|
+
import { CloseRefusedNotice } from './comments/CloseRefusedNotice.js';
|
|
11
|
+
import { ON_REMOVED_ITEM } from './comments/item-anchor-copy.js';
|
|
12
|
+
import { CommentWriteAlert, toCommentWriteFailure, } from './comments/CommentWriteAlert.js';
|
|
13
|
+
/**
|
|
14
|
+
* A jump button that stays focusable while a write is in flight (`aria-disabled`, not `disabled`),
|
|
15
|
+
* so its reason opens on keyboard focus and touch as well as hover.
|
|
16
|
+
*/
|
|
17
|
+
const JumpButton = ({ blocked, onClick, children }) => (_jsx(Tooltip, { label: "Your change is still saving. Try again once it finishes.", disabled: !blocked, multiline: true, maw: 280, children: _jsx(Button, { variant: "light", onClick: () => {
|
|
18
|
+
if (!blocked)
|
|
19
|
+
onClick();
|
|
20
|
+
}, "data-disabled": blocked ? true : undefined, "aria-disabled": blocked ? true : undefined, children: children }) }));
|
|
21
|
+
const THREAD_KIND_LABELS = { field: 'Field', entry: 'Entry', branch: 'Branch' };
|
|
22
|
+
export const CommentsPanel = ({ opened, branchName, comments, canResolve, currentUserId, onAddComment, onResolveThread, onClose, onJumpToField, onJumpToEntry, onJumpToBranch, onGetUserMetadata, entryLabel, fieldLabel, }) => {
|
|
8
23
|
const [newCommentText, setNewCommentText] = useState('');
|
|
9
|
-
const [
|
|
24
|
+
const [replyDrafts, setReplyDrafts] = useState({});
|
|
10
25
|
const [showResolved, setShowResolved] = useState(false);
|
|
11
26
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
27
|
+
const [error, setError] = useState(null);
|
|
28
|
+
const { inFlight, track, holdOpen, closeRefused } = useTrackedWrites();
|
|
29
|
+
const addComment = track(onAddComment);
|
|
30
|
+
const resolveThread = track(onResolveThread);
|
|
31
|
+
const close = () => {
|
|
32
|
+
if (!holdOpen())
|
|
33
|
+
onClose();
|
|
34
|
+
};
|
|
35
|
+
// A jump hands focus to its target (the field's or entry's comment popover, or the branches
|
|
36
|
+
// drawer), so the drawer returns none: its return would land after the target took focus.
|
|
37
|
+
const [jumping, setJumping] = useState(false);
|
|
38
|
+
if (opened && jumping)
|
|
39
|
+
setJumping(false);
|
|
40
|
+
const jump = (go) => {
|
|
41
|
+
setJumping(true);
|
|
42
|
+
go();
|
|
43
|
+
onClose();
|
|
44
|
+
};
|
|
45
|
+
// A resolved thread's card leaves the list with the focused button, so focus lands here.
|
|
46
|
+
const threadCountRef = useRef(null);
|
|
47
|
+
// With resolved threads shown, the card stays and its heading takes the focus instead.
|
|
48
|
+
const headingRefs = useRef(new Map());
|
|
49
|
+
const idPrefix = useId();
|
|
50
|
+
const headingId = (threadId) => `${idPrefix}-heading-${threadId}`;
|
|
12
51
|
const filteredThreads = showResolved ? comments : comments.filter((t) => !t.resolved);
|
|
13
52
|
const handleAddComment = async () => {
|
|
14
53
|
if (!newCommentText.trim())
|
|
15
54
|
return;
|
|
16
55
|
setIsSubmitting(true);
|
|
56
|
+
setError(null);
|
|
17
57
|
try {
|
|
18
|
-
|
|
19
|
-
const thread = comments.find((t) => t.id === replyTo);
|
|
20
|
-
if (thread) {
|
|
21
|
-
await onAddComment(newCommentText, thread.type, thread.entryPath, thread.canopyPath, replyTo);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
await onAddComment(newCommentText, 'branch', undefined, undefined, undefined);
|
|
26
|
-
}
|
|
58
|
+
await addComment(newCommentText, 'branch', undefined, undefined, undefined);
|
|
27
59
|
setNewCommentText('');
|
|
28
|
-
setReplyTo(null);
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
setIsSubmitting(false);
|
|
32
60
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
setIsSubmitting(true);
|
|
36
|
-
try {
|
|
37
|
-
await onResolveThread(threadId);
|
|
61
|
+
catch (err) {
|
|
62
|
+
setError(toCommentWriteFailure(err));
|
|
38
63
|
}
|
|
39
64
|
finally {
|
|
40
65
|
setIsSubmitting(false);
|
|
41
66
|
}
|
|
42
67
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
const threadHeading = (thread) => {
|
|
69
|
+
const kind = THREAD_KIND_LABELS[thread.type];
|
|
70
|
+
if (!thread.entryPath)
|
|
71
|
+
return kind;
|
|
72
|
+
const entry = entryLabel?.(thread.entryPath) ?? thread.entryPath;
|
|
73
|
+
const field = thread.canopyPath ? (fieldLabel?.(thread) ?? thread.canopyPath) : undefined;
|
|
74
|
+
return `${kind} · ${field ? `${entry} › ${field}` : entry}`;
|
|
75
|
+
};
|
|
76
|
+
return (_jsx(Drawer, { opened: opened, onClose: close, returnFocus: !jumping, position: "right", size: 420, title: _jsxs("div", { children: [_jsx(Title, { order: 4, children: "Comments" }), _jsx(Text, { size: "xs", c: "dimmed", children: branchName })] }), children: _jsxs(Stack, { gap: "md", h: "100%", style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(CloseRefusedNotice, { refused: closeRefused }), _jsx(Paper, { withBorder: true, p: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { size: "xs", c: "dimmed", children: ["Posts a branch-level comment on \"", branchName, "\". To comment on a field, use its comment icon."] }), _jsx(Textarea, { placeholder: "Add a comment\u2026", "aria-label": "New branch comment", value: newCommentText, onChange: (e) => setNewCommentText(e.target.value), minRows: 3, disabled: isSubmitting, "data-testid": "comment-textarea" }), error && _jsx(CommentWriteAlert, { failure: error, onClose: () => setError(null) }), _jsx(Button, { onClick: handleAddComment, size: "sm", loading: isSubmitting, disabled: !newCommentText.trim(), "data-testid": "comment-submit", children: "Comment" })] }) }), _jsxs(Group, { justify: "space-between", children: [_jsxs(Text, { ref: threadCountRef, size: "sm", fw: 500, tabIndex: -1, children: [filteredThreads.length, " ", filteredThreads.length === 1 ? 'thread' : 'threads'] }), _jsx(Switch, { label: "Show resolved", checked: showResolved, onChange: (e) => setShowResolved(e.currentTarget.checked), size: "sm" })] }), _jsx(ScrollArea, { style: { flex: 1 }, children: filteredThreads.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", ta: "center", py: "xl", children: showResolved ? 'No comments yet' : 'No unresolved comments' })) : (_jsx(Stack, { gap: "md", children: filteredThreads.map((thread) => (_jsx(Paper, { withBorder: true, p: "md", component: "article", "aria-labelledby": headingId(thread.id), bg: thread.resolved ? 'var(--mantine-color-default-hover)' : undefined, "data-testid": "comment-thread", children: _jsxs(Stack, { gap: "xs", children: [_jsxs(Group, { gap: "xs", children: [_jsxs(Text, { id: headingId(thread.id), ref: (node) => {
|
|
77
|
+
if (node)
|
|
78
|
+
headingRefs.current.set(thread.id, node);
|
|
79
|
+
else
|
|
80
|
+
headingRefs.current.delete(thread.id);
|
|
81
|
+
}, size: "xs", fw: 600, tabIndex: -1, "data-testid": "comment-thread-heading", children: [threadHeading(thread), thread.onRemovedItem && (_jsxs(VisuallyHidden, { children: [", ", ON_REMOVED_ITEM.toLowerCase()] }))] }), thread.resolved && (_jsx(Badge, { size: "xs", color: "green", variant: "light", children: "Resolved" })), thread.onRemovedItem && (_jsx(Badge, { size: "xs", color: "gray", variant: "light", c: "var(--mantine-color-text)", children: ON_REMOVED_ITEM }))] }), _jsxs(Group, { gap: "xs", children: [thread.type === 'field' &&
|
|
48
82
|
thread.entryPath &&
|
|
49
83
|
thread.canopyPath &&
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
onClose();
|
|
56
|
-
}, children: "Jump to entry" })), thread.type === 'branch' && onJumpToBranch && (_jsx(Button, { size: "xs", variant: "light", onClick: () => {
|
|
57
|
-
onJumpToBranch(thread.id);
|
|
58
|
-
onClose();
|
|
59
|
-
}, children: "Open branch discussion" }))] }), thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
|
|
60
|
-
borderTop: '1px solid var(--mantine-color-gray-3)',
|
|
84
|
+
!thread.onRemovedItem &&
|
|
85
|
+
onJumpToField && (_jsx(JumpButton, { blocked: inFlight > 0, onClick: () => jump(() => onJumpToField(thread.entryPath, thread.canopyPath, thread.id)), children: "Jump to field" })), (thread.type === 'entry' || thread.onRemovedItem) &&
|
|
86
|
+
thread.entryPath &&
|
|
87
|
+
onJumpToEntry && (_jsx(JumpButton, { blocked: inFlight > 0, onClick: () => jump(() => onJumpToEntry(thread.entryPath, thread.id)), children: "Jump to entry" })), thread.type === 'branch' && onJumpToBranch && (_jsx(JumpButton, { blocked: inFlight > 0, onClick: () => jump(() => onJumpToBranch(thread.id)), children: "Open branch discussion" }))] }), thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
|
|
88
|
+
borderTop: '1px solid var(--mantine-color-default-border)',
|
|
61
89
|
margin: '8px 0',
|
|
62
|
-
} })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", children: [onGetUserMetadata ? (_jsx(UserBadge, { userId: comment.userId, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", fw: 500, children: comment.userId })), _jsx(
|
|
63
|
-
setReplyTo(thread.id);
|
|
64
|
-
setNewCommentText('');
|
|
65
|
-
}, mt: "xs", children: "Reply" }))] }) }, thread.id))) })) })] }) }));
|
|
90
|
+
} })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", children: [onGetUserMetadata ? (_jsx(UserBadge, { userId: comment.userId, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", fw: 500, children: userIdLabel(comment.userId) })), _jsx(RelativeTime, { iso: comment.timestamp, size: "xs", c: "dimmed" })] }), _jsx(Text, { size: "sm", style: { whiteSpace: 'pre-wrap' }, children: comment.text })] })] }, comment.id))), !thread.resolved && (_jsx(ThreadReplyControls, { thread: thread, onAddReply: (text) => addComment(text, thread.type, thread.entryPath, thread.canopyPath, thread.id), onResolve: () => resolveThread(thread.id), focusAfterResolve: () => showResolved ? headingRefs.current.get(thread.id) : threadCountRef.current, currentUserId: currentUserId, canResolve: canResolve, replyText: replyDrafts[thread.id] ?? '', onReplyTextChange: (text) => setReplyDrafts((prev) => ({ ...prev, [thread.id]: text })) }))] }) }, thread.id))) })) })] }) }));
|
|
66
91
|
};
|