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,274 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { Badge, Button, Group, Popover, ScrollArea, Stack, Text, Textarea, Tooltip, } from '@mantine/core';
|
|
6
|
+
import { useClickOutside, useDidUpdate, useId, useIsomorphicEffect, useMediaQuery, useWindowEvent, } from '@mantine/hooks';
|
|
7
|
+
import { IconMessage, IconMessagePlus, IconPlus } from '@tabler/icons-react';
|
|
8
|
+
import { InlineCommentThread } from './InlineCommentThread.js';
|
|
9
|
+
import { useTrackedWrites } from './use-tracked-writes.js';
|
|
10
|
+
import { CloseRefusedNotice } from './CloseRefusedNotice.js';
|
|
11
|
+
import { useRefocusAfterSubmit } from './ThreadReplyControls.js';
|
|
12
|
+
import { commentsAriaKeyShortcuts, commentsShortcutLabel } from './form-comments.js';
|
|
13
|
+
import { choosePopoverSide } from './popover-placement.js';
|
|
14
|
+
import { CommentWriteAlert, toCommentWriteFailure, } from './CommentWriteAlert.js';
|
|
15
|
+
const HIGHLIGHT_MS = 2000;
|
|
16
|
+
const sortThreads = (threads) => [...threads].sort((a, b) => {
|
|
17
|
+
if (a.resolved !== b.resolved)
|
|
18
|
+
return a.resolved ? 1 : -1;
|
|
19
|
+
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
20
|
+
});
|
|
21
|
+
const openThreadsLabel = (count) => count === 1 ? '1 open thread' : `${count} open threads`;
|
|
22
|
+
/**
|
|
23
|
+
* A field's or entry's comment control and the popover it opens: the threads (open first), each
|
|
24
|
+
* with reply and Resolve, and a box for a new thread. Comments stay open on a read-only form,
|
|
25
|
+
* since reviewers comment on submitted branches.
|
|
26
|
+
*/
|
|
27
|
+
export const CommentThreadsPopover = ({ threads, subject, variant, revealed = false, anchors, openRequest, currentUserId, canResolve, onAddComment, onResolveThread, onGetUserMetadata, highlightThreadId, threadFieldLabel, composeBlockedReason, testId, }) => {
|
|
28
|
+
const [opened, setOpened] = useState(false);
|
|
29
|
+
const [composing, setComposing] = useState(false);
|
|
30
|
+
// Drafts live here, not in the dropdown, which unmounts whenever the popover closes.
|
|
31
|
+
const [newThreadDraft, setNewThreadDraft] = useState('');
|
|
32
|
+
const [replyDrafts, setReplyDrafts] = useState({});
|
|
33
|
+
const headingRef = useRef(null);
|
|
34
|
+
const [flashThreadId, setFlashThreadId] = useState(undefined);
|
|
35
|
+
const threadRefs = useRef(new Map());
|
|
36
|
+
// Set by a post; the "New thread" button takes focus when it next mounts, as the composer goes.
|
|
37
|
+
const focusNewThreadRef = useRef(false);
|
|
38
|
+
// Touch screens have no hover, so the field icon stays visible there.
|
|
39
|
+
const noHover = useMediaQuery('(hover: none)');
|
|
40
|
+
const popoverId = useId();
|
|
41
|
+
const [controlNode, setControlNode] = useState(null);
|
|
42
|
+
// Read when focus returns: a popover that mounts open has no `controlNode` state as it opens.
|
|
43
|
+
const controlRef = useRef(null);
|
|
44
|
+
const setControl = useCallback((el) => {
|
|
45
|
+
controlRef.current = el;
|
|
46
|
+
setControlNode(el);
|
|
47
|
+
}, []);
|
|
48
|
+
const [dropdownNode, setDropdownNode] = useState(null);
|
|
49
|
+
// Set by a menu item's open, whose item is gone by close: its menu's trigger stands in.
|
|
50
|
+
const [returnFocusTo, setReturnFocusTo] = useState(undefined);
|
|
51
|
+
// Where focus returns on close, recorded as the popover opens, before its focus trap runs.
|
|
52
|
+
const openerRef = useRef(null);
|
|
53
|
+
const [side, setSide] = useState('below');
|
|
54
|
+
// A highlight's open always scrolls its field into place, even when it fits, and so overrides
|
|
55
|
+
// the smooth scroll `handleJumpToField` (`hooks/useCommentSystem.ts`) starts.
|
|
56
|
+
const [scrollOnOpen, setScrollOnOpen] = useState(false);
|
|
57
|
+
const placedRef = useRef(false);
|
|
58
|
+
// A click chose where focus goes, so a stand-in never takes it.
|
|
59
|
+
const pointerClosedRef = useRef(false);
|
|
60
|
+
const open = ({ scroll = false } = {}) => {
|
|
61
|
+
setReturnFocusTo(undefined);
|
|
62
|
+
setScrollOnOpen(scroll);
|
|
63
|
+
setOpened(true);
|
|
64
|
+
};
|
|
65
|
+
const sortedThreads = useMemo(() => sortThreads(threads), [threads]);
|
|
66
|
+
const openCount = useMemo(() => threads.filter((t) => !t.resolved).length, [threads]);
|
|
67
|
+
// A newly highlighted thread here opens the popover on it; adjusted during render, not in an
|
|
68
|
+
// effect, so the popover opens in the same commit.
|
|
69
|
+
const [seenHighlight, setSeenHighlight] = useState(undefined);
|
|
70
|
+
// It stays unseen until its thread arrives, so a thread list that catches up still opens it.
|
|
71
|
+
const highlightHere = !!highlightThreadId && threads.some((t) => t.id === highlightThreadId);
|
|
72
|
+
if (highlightThreadId !== seenHighlight && (!highlightThreadId || highlightHere)) {
|
|
73
|
+
setSeenHighlight(highlightThreadId);
|
|
74
|
+
if (highlightHere) {
|
|
75
|
+
open({ scroll: true });
|
|
76
|
+
setFlashThreadId(highlightThreadId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const [seenOpenRequest, setSeenOpenRequest] = useState(openRequest);
|
|
80
|
+
if (openRequest !== seenOpenRequest) {
|
|
81
|
+
setSeenOpenRequest(openRequest);
|
|
82
|
+
if (openRequest) {
|
|
83
|
+
setOpened(true);
|
|
84
|
+
setScrollOnOpen(false);
|
|
85
|
+
setReturnFocusTo(() => openRequest.returnFocusTo);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Once per open, before paint, as soon as both anchors exist: a field that mounts already
|
|
89
|
+
// highlighted gets them a commit after the popover opens.
|
|
90
|
+
useIsomorphicEffect(() => {
|
|
91
|
+
if (!opened) {
|
|
92
|
+
placedRef.current = false;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (placedRef.current || !anchors?.above || !anchors.below)
|
|
96
|
+
return;
|
|
97
|
+
placedRef.current = true;
|
|
98
|
+
setSide(choosePopoverSide(anchors, { scroll: scrollOnOpen }));
|
|
99
|
+
}, [opened, anchors]);
|
|
100
|
+
useIsomorphicEffect(() => {
|
|
101
|
+
if (!opened)
|
|
102
|
+
return;
|
|
103
|
+
pointerClosedRef.current = false;
|
|
104
|
+
const active = document.activeElement;
|
|
105
|
+
openerRef.current = returnFocusTo
|
|
106
|
+
? { target: returnFocusTo, standIn: true }
|
|
107
|
+
: {
|
|
108
|
+
// A popover that opened with nothing focused, as on a highlight, returns to its control.
|
|
109
|
+
target: () => active instanceof HTMLElement && active !== document.body && active.isConnected
|
|
110
|
+
? active
|
|
111
|
+
: controlRef.current,
|
|
112
|
+
standIn: false,
|
|
113
|
+
};
|
|
114
|
+
}, [opened]);
|
|
115
|
+
// Focus returns here rather than through Mantine, which refocuses the opener even after a click
|
|
116
|
+
// has put focus on another control.
|
|
117
|
+
useDidUpdate(() => {
|
|
118
|
+
const opener = openerRef.current;
|
|
119
|
+
if (opened || !opener)
|
|
120
|
+
return;
|
|
121
|
+
if (pointerClosedRef.current && opener.standIn)
|
|
122
|
+
return;
|
|
123
|
+
const closing = dropdownNode;
|
|
124
|
+
const timer = window.setTimeout(() => {
|
|
125
|
+
const active = document.activeElement;
|
|
126
|
+
// Focus left in the fading dropdown, on a detached or closing menu item, or nowhere, hasn't
|
|
127
|
+
// been moved on by the user.
|
|
128
|
+
const unmoved = !active ||
|
|
129
|
+
active === document.body ||
|
|
130
|
+
!active.isConnected ||
|
|
131
|
+
closing?.contains(active) ||
|
|
132
|
+
active.closest('[data-menu-dropdown]');
|
|
133
|
+
if (!unmoved)
|
|
134
|
+
return;
|
|
135
|
+
const target = opener.target();
|
|
136
|
+
target?.focus({ preventScroll: true });
|
|
137
|
+
if (opener.standIn)
|
|
138
|
+
target?.scrollIntoView?.({ block: 'nearest' });
|
|
139
|
+
}, 20);
|
|
140
|
+
return () => window.clearTimeout(timer);
|
|
141
|
+
}, [opened]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!flashThreadId)
|
|
144
|
+
return;
|
|
145
|
+
const frame = window.requestAnimationFrame(() => {
|
|
146
|
+
threadRefs.current.get(flashThreadId)?.scrollIntoView({ block: 'nearest' });
|
|
147
|
+
});
|
|
148
|
+
const timer = window.setTimeout(() => setFlashThreadId(undefined), HIGHLIGHT_MS);
|
|
149
|
+
return () => {
|
|
150
|
+
window.cancelAnimationFrame(frame);
|
|
151
|
+
window.clearTimeout(timer);
|
|
152
|
+
};
|
|
153
|
+
}, [flashThreadId]);
|
|
154
|
+
// While a post or Resolve is in flight the popover stays open, so its busy state and any error
|
|
155
|
+
// stay in view and a reopened composer can't send the same text twice.
|
|
156
|
+
const { inFlight, track, holdOpen, closeRefused } = useTrackedWrites();
|
|
157
|
+
const addComment = track(onAddComment);
|
|
158
|
+
const resolveThread = track(onResolveThread);
|
|
159
|
+
const close = () => {
|
|
160
|
+
if (holdOpen())
|
|
161
|
+
return;
|
|
162
|
+
setOpened(false);
|
|
163
|
+
setComposing(false);
|
|
164
|
+
focusNewThreadRef.current = false;
|
|
165
|
+
};
|
|
166
|
+
const controlLabel = openCount > 0
|
|
167
|
+
? `Comment on ${subject} (${openThreadsLabel(openCount)})`
|
|
168
|
+
: `Comment on ${subject}`;
|
|
169
|
+
// Open threads keep the control in view as a count; resolved ones are listed by the form's
|
|
170
|
+
// "Field comments" menu.
|
|
171
|
+
const shown = revealed || opened || noHover || openCount > 0;
|
|
172
|
+
// Idle, the control leaves the tab order: the form's menu and shortcut reach comments instead.
|
|
173
|
+
const idle = !opened && openCount === 0;
|
|
174
|
+
const anchored = variant === 'field' && anchors !== undefined;
|
|
175
|
+
const controlTooltip = openCount > 0
|
|
176
|
+
? `Comment on ${subject} (${openThreadsLabel(openCount)}, ${commentsShortcutLabel()})`
|
|
177
|
+
: `Comment on ${subject} (${commentsShortcutLabel()})`;
|
|
178
|
+
// Mantine's Escape is off in flight; on the window, as a disabled composer drops focus.
|
|
179
|
+
useWindowEvent('keydown', (event) => {
|
|
180
|
+
if (event.key === 'Escape' && opened)
|
|
181
|
+
holdOpen();
|
|
182
|
+
});
|
|
183
|
+
const closeByPointer = () => {
|
|
184
|
+
if (holdOpen())
|
|
185
|
+
return;
|
|
186
|
+
pointerClosedRef.current = true;
|
|
187
|
+
close();
|
|
188
|
+
};
|
|
189
|
+
// Anchored, the control is outside the popover's target, so clicks on it count as inside here.
|
|
190
|
+
useClickOutside(() => anchored && opened && closeByPointer(), null, [controlNode, dropdownNode]);
|
|
191
|
+
const target = variant === 'entry' ? (_jsx(Button, { variant: "subtle", color: "gray", c: "var(--mantine-color-text)", size: "compact-sm", leftSection: _jsx(IconMessage, { size: 16, "aria-hidden": true }), rightSection: openCount > 0 ? (_jsx(Badge, { size: "sm", variant: "light", color: "orange", c: "var(--mantine-color-text)", "aria-hidden": true, children: openCount })) : undefined, ref: setControl, "aria-label": controlLabel, "aria-expanded": opened, "aria-haspopup": "dialog", onClick: (event) => (!opened ? open() : event.detail > 0 ? closeByPointer() : close()), "data-testid": testId, children: "Comment on this entry" })) : (
|
|
192
|
+
// One element for every state, so a count change never remounts the control focus returns to.
|
|
193
|
+
_jsx(Button, { variant: openCount > 0 ? 'light' : 'subtle', color: openCount > 0 ? 'orange' : 'gray', c: openCount > 0 ? 'var(--mantine-color-text)' : undefined, size: "compact-xs", px: 4, ref: setControl, tabIndex: idle ? -1 : undefined, "aria-label": controlLabel, "aria-expanded": opened, "aria-haspopup": "dialog", "aria-controls": anchored && opened ? `${popoverId}-dropdown` : undefined, "aria-keyshortcuts": commentsAriaKeyShortcuts(), onClick: (event) => (!opened ? open() : event.detail > 0 ? closeByPointer() : close()), "data-testid": testId, "data-comment-control": true, "data-revealed": shown ? '' : undefined, style: {
|
|
194
|
+
// Hidden by opacity, not display, so a pointer or focus on the field shows it in place.
|
|
195
|
+
opacity: shown ? 1 : 0,
|
|
196
|
+
transition: 'opacity 120ms ease',
|
|
197
|
+
}, children: _jsxs(Group, { gap: 4, wrap: "nowrap", children: [threads.length === 0 ? (_jsx(IconMessagePlus, { size: 16, "aria-hidden": true })) : (_jsx(IconMessage, { size: 14, color: openCount > 0 ? 'var(--mantine-color-orange-text)' : undefined, "aria-hidden": true })), openCount > 0 && _jsx("span", { children: openCount })] }) }));
|
|
198
|
+
const tooltipTarget = variant === 'entry' ? (target) : (_jsx(Tooltip, { label: controlTooltip, withinPortal: true, disabled: opened, children: target }));
|
|
199
|
+
const popover = (_jsxs(Popover, { id: popoverId, opened: opened, onChange: (next) => (next ? open() : close()), onClose: close,
|
|
200
|
+
// Never over the field's input; the entry's button sits above the form.
|
|
201
|
+
position: anchored && side === 'below' ? 'bottom-start' : 'top-start',
|
|
202
|
+
// Clamped to the room on its side, scrolling inside, since it never flips over the input.
|
|
203
|
+
middlewares: anchored ? { flip: false, shift: true, size: { padding: 8 } } : undefined, width: 380, shadow: "md", withArrow: !anchored, trapFocus: true, returnFocus: false,
|
|
204
|
+
// Off while a write is in flight, when `close` refuses anyway, so Mantine's Escape handler
|
|
205
|
+
// doesn't run its focus return on a popover that stays open.
|
|
206
|
+
closeOnEscape: inFlight === 0, closeOnClickOutside: !anchored,
|
|
207
|
+
// Anchored, the target is an empty element, so the control and dropdown carry the roles.
|
|
208
|
+
withRoles: !anchored, children: [_jsx(Popover.Target, { children: anchored ? _jsx(AnchorTarget, { node: anchors?.[side] }) : tooltipTarget }), _jsx(Popover.Dropdown, { ref: setDropdownNode, "aria-label": `Comments on ${subject}`, ...(anchored ? { role: 'dialog', id: `${popoverId}-dropdown`, tabIndex: -1 } : {}), style: anchored ? { overflowY: 'auto' } : undefined, "data-testid": `${testId}-popover`, p: "sm", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { ref: headingRef, size: "sm", fw: 600, tabIndex: -1, "data-autofocus": sortedThreads.length > 0 || undefined, children: ["Comments on ", subject] }), sortedThreads.length > 0 && (_jsx(ScrollArea.Autosize, { mah: 360, offsetScrollbars: true, type: "auto", children: _jsx(Stack, { gap: "sm", children: sortedThreads.map((thread) => (_jsx("div", { ref: (el) => {
|
|
209
|
+
if (el)
|
|
210
|
+
threadRefs.current.set(thread.id, el);
|
|
211
|
+
else
|
|
212
|
+
threadRefs.current.delete(thread.id);
|
|
213
|
+
}, "data-highlighted": flashThreadId === thread.id ? '' : undefined, style: {
|
|
214
|
+
borderRadius: 4,
|
|
215
|
+
outline: flashThreadId === thread.id
|
|
216
|
+
? '2px solid var(--mantine-color-blue-5)'
|
|
217
|
+
: undefined,
|
|
218
|
+
outlineOffset: -2,
|
|
219
|
+
}, children: _jsx(InlineCommentThread, { thread: thread, onAddReply: (text) => addComment(text, thread.id), onResolve: () => resolveThread(thread.id),
|
|
220
|
+
// Resolve and the reply box go with the open state, so focus stays inside.
|
|
221
|
+
focusAfterResolve: () => headingRef.current, replyText: replyDrafts[thread.id] ?? '', onReplyTextChange: (text) => setReplyDrafts((prev) => ({ ...prev, [thread.id]: text })), currentUserId: currentUserId, canResolve: canResolve, onGetUserMetadata: onGetUserMetadata, fieldLabel: threadFieldLabel?.(thread) }) }, thread.id))) }) })), sortedThreads.length === 0 || composing ? (_jsx(NewThreadComposer, { blockedReason: composeBlockedReason, text: newThreadDraft, onTextChange: setNewThreadDraft, onSubmit: async (text) => {
|
|
222
|
+
// Set first: the first thread's arrival mounts the button before this resolves.
|
|
223
|
+
focusNewThreadRef.current = true;
|
|
224
|
+
try {
|
|
225
|
+
await addComment(text);
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
focusNewThreadRef.current = false;
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
setComposing(false);
|
|
232
|
+
}, onCancel: sortedThreads.length > 0 ? () => setComposing(false) : close })) : (_jsx(Group, { children: _jsx(Button, { ref: (el) => {
|
|
233
|
+
if (el && focusNewThreadRef.current) {
|
|
234
|
+
focusNewThreadRef.current = false;
|
|
235
|
+
el.focus();
|
|
236
|
+
}
|
|
237
|
+
}, variant: "subtle", size: "compact-sm", leftSection: _jsx(IconPlus, { size: 14, "aria-hidden": true }), onClick: () => setComposing(true), "data-testid": "new-thread-button", children: "New thread" }) })), _jsx(CloseRefusedNotice, { refused: closeRefused })] }) })] }));
|
|
238
|
+
return anchored ? (_jsxs(_Fragment, { children: [tooltipTarget, popover] })) : (popover);
|
|
239
|
+
};
|
|
240
|
+
/** An empty element placed in `node`, as the popover's position reference. */
|
|
241
|
+
const AnchorTarget = React.forwardRef(function AnchorTarget({ node }, ref) {
|
|
242
|
+
return node ? createPortal(_jsx("div", { ref: ref, "aria-hidden": true }), node) : null;
|
|
243
|
+
});
|
|
244
|
+
const NewThreadComposer = ({ text, onTextChange: setText, onSubmit, onCancel, blockedReason }) => {
|
|
245
|
+
const [submitting, setSubmitting] = useState(false);
|
|
246
|
+
const [error, setError] = useState(null);
|
|
247
|
+
const boxRef = useRef(null);
|
|
248
|
+
// Shown on request ("New thread"), so it takes focus.
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
boxRef.current?.focus();
|
|
251
|
+
}, []);
|
|
252
|
+
useRefocusAfterSubmit(submitting, boxRef);
|
|
253
|
+
const submit = async () => {
|
|
254
|
+
if (!text.trim() || blockedReason)
|
|
255
|
+
return;
|
|
256
|
+
setSubmitting(true);
|
|
257
|
+
setError(null);
|
|
258
|
+
try {
|
|
259
|
+
await onSubmit(text);
|
|
260
|
+
setText('');
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
setError(toCommentWriteFailure(err));
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
setSubmitting(false);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const button = (_jsx(Button, { size: "compact-sm", onClick: () => void submit(), loading: submitting, disabled: !text.trim(), "data-disabled": blockedReason ? true : undefined, "aria-disabled": blockedReason ? true : undefined, "data-testid": "create-thread-button", children: "Comment" }));
|
|
270
|
+
// `aria-disabled` keeps it focusable for its reason. Unblocked it isn't wrapped: around a focused
|
|
271
|
+
// button the Tooltip keeps Escape from the listener that refuses a close mid-post.
|
|
272
|
+
const commentButton = blockedReason ? (_jsx(Tooltip, { label: blockedReason, multiline: true, maw: 280, children: button })) : (button);
|
|
273
|
+
return (_jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { ref: boxRef, "aria-label": "New comment", placeholder: "Add a comment\u2026", value: text, onChange: (e) => setText(e.currentTarget.value), minRows: 2, autosize: true, disabled: submitting, description: blockedReason, error: error && !error.mayHaveLanded ? error.message : undefined, "data-autofocus": true, "data-testid": "new-thread-textarea" }), error?.mayHaveLanded && _jsx(CommentWriteAlert, { failure: error, onClose: () => setError(null) }), _jsxs(Group, { gap: "xs", children: [commentButton, _jsx(Button, { size: "compact-sm", variant: "subtle", color: "gray", onClick: onCancel, disabled: submitting, children: "Cancel" })] })] }));
|
|
274
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A comment write's failure, and whether the write may have landed anyway. */
|
|
2
|
+
export interface CommentWriteFailure {
|
|
3
|
+
message: string;
|
|
4
|
+
mayHaveLanded: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const toCommentWriteFailure: (err: unknown) => CommentWriteFailure;
|
|
7
|
+
/** A failed comment write: red, or yellow when it may have been posted. */
|
|
8
|
+
export declare function CommentWriteAlert({ failure, onClose, }: {
|
|
9
|
+
failure: CommentWriteFailure;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Alert } from '@mantine/core';
|
|
3
|
+
import { IconAlertCircle, IconAlertTriangle } from '@tabler/icons-react';
|
|
4
|
+
import { getErrorMessage } from '../../utils/error.js';
|
|
5
|
+
import { MayHaveLandedError } from '../unanswered.js';
|
|
6
|
+
export const toCommentWriteFailure = (err) => ({
|
|
7
|
+
message: getErrorMessage(err),
|
|
8
|
+
mayHaveLanded: err instanceof MayHaveLandedError,
|
|
9
|
+
});
|
|
10
|
+
/** A failed comment write: red, or yellow when it may have been posted. */
|
|
11
|
+
export function CommentWriteAlert({ failure, onClose, }) {
|
|
12
|
+
return (_jsx(Alert, { icon: failure.mayHaveLanded ? _jsx(IconAlertTriangle, { size: 16 }) : _jsx(IconAlertCircle, { size: 16 }), color: failure.mayHaveLanded ? 'yellow' : 'red', variant: "light", onClose: onClose, withCloseButton: true, children: failure.message }));
|
|
13
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CommentThread } from '../../comment-store.js';
|
|
3
|
+
import type { UserSearchResult } from '../../auth/types.js';
|
|
3
4
|
export interface EntryCommentsProps {
|
|
4
5
|
/** All comments (will be filtered for entry-level) */
|
|
5
6
|
comments: CommentThread[];
|
|
@@ -13,13 +14,18 @@ export interface EntryCommentsProps {
|
|
|
13
14
|
onAddComment: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
|
|
14
15
|
/** Handler to resolve a thread */
|
|
15
16
|
onResolveThread: (threadId: string) => Promise<void>;
|
|
16
|
-
/** Auto-focus and expand */
|
|
17
|
-
autoFocus?: boolean;
|
|
18
17
|
/** Thread ID to highlight and scroll to */
|
|
19
18
|
highlightThreadId?: string;
|
|
19
|
+
/** Resolves author ids to names; without it authors show as ids. */
|
|
20
|
+
onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
21
|
+
/** Changes with the entry or branch, which resets the popover, so a draft never posts elsewhere. */
|
|
22
|
+
resetKey: string;
|
|
23
|
+
/** Names the field of a thread on a removed item. */
|
|
24
|
+
removedItemFieldLabel?: (thread: CommentThread) => string;
|
|
20
25
|
}
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
27
|
+
* The top of the form's comment actions: the "Field comments" menu, the keyboard route to
|
|
28
|
+
* field comments, and "Comment on this entry" with the entry's threads. Those include the field
|
|
29
|
+
* threads whose list item or block was removed, which stay on the entry, marked.
|
|
24
30
|
*/
|
|
25
31
|
export declare const EntryComments: React.FC<EntryCommentsProps>;
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { Group } from '@mantine/core';
|
|
5
|
+
import { CommentThreadsPopover } from './CommentThreadsPopover.js';
|
|
6
|
+
import { FormCommentsMenu } from './form-comments.js';
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* The top of the form's comment actions: the "Field comments" menu, the keyboard route to
|
|
9
|
+
* field comments, and "Comment on this entry" with the entry's threads. Those include the field
|
|
10
|
+
* threads whose list item or block was removed, which stay on the entry, marked.
|
|
9
11
|
*/
|
|
10
|
-
export const EntryComments = ({ comments, entryPath, currentUserId, canResolve, onAddComment, onResolveThread,
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
if (!shouldShowCarousel) {
|
|
18
|
-
return (_jsx(Box, { children: _jsx(Button, { size: "xs", variant: "subtle", color: "gray", onClick: () => setShowCarousel(true), style: { fontSize: '0.75rem', height: '1.5rem' }, children: "New file comment" }) }));
|
|
19
|
-
}
|
|
20
|
-
return (_jsx(ThreadCarousel, { threads: entryThreads, label: "Entry Comments", contextType: "entry", currentUserId: currentUserId, canResolve: canResolve, onAddComment: handleAddComment, onResolveThread: onResolveThread, autoFocus: autoFocus, autoOpenNewThread: showCarousel && entryThreads.length === 0, highlightThreadId: highlightThreadId }));
|
|
12
|
+
export const EntryComments = ({ comments, entryPath, currentUserId, canResolve, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, resetKey, removedItemFieldLabel, }) => {
|
|
13
|
+
const entryThreads = useMemo(() => comments.filter((t) => t.entryPath === entryPath &&
|
|
14
|
+
(t.type === 'entry' || (t.type === 'field' && t.onRemovedItem))), [comments, entryPath]);
|
|
15
|
+
return (_jsxs(Group, { justify: "flex-end", gap: "xs", children: [_jsx(FormCommentsMenu, { comments: comments, entryPath: entryPath }), _jsx(CommentThreadsPopover, { threads: entryThreads, subject: "this entry", variant: "entry", currentUserId: currentUserId, canResolve: canResolve, onAddComment: (text, threadId) => onAddComment(text, 'entry', entryPath, undefined, threadId), onResolveThread: onResolveThread, onGetUserMetadata: onGetUserMetadata, highlightThreadId: highlightThreadId, threadFieldLabel: (thread) => thread.onRemovedItem && thread.canopyPath
|
|
16
|
+
? (removedItemFieldLabel?.(thread) ?? thread.canopyPath)
|
|
17
|
+
: undefined, testId: "entry-comment-button" }, resetKey)] }));
|
|
21
18
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CommentThread } from '../../comment-store.js';
|
|
3
|
+
import type { UserSearchResult } from '../../auth/types.js';
|
|
3
4
|
export interface FieldWrapperProps {
|
|
4
5
|
/** Child form field element */
|
|
5
6
|
children: React.ReactNode;
|
|
@@ -19,13 +20,26 @@ export interface FieldWrapperProps {
|
|
|
19
20
|
onAddComment: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
|
|
20
21
|
/** Handler to resolve a thread */
|
|
21
22
|
onResolveThread: (threadId: string) => Promise<void>;
|
|
22
|
-
/**
|
|
23
|
-
fieldLabel
|
|
23
|
+
/** The field's breadcrumb ("Links › /a › Href"), which names the control: "Comment on <breadcrumb>". */
|
|
24
|
+
fieldLabel: string;
|
|
24
25
|
/** Thread ID to highlight and scroll to */
|
|
25
26
|
highlightThreadId?: string;
|
|
27
|
+
/** Resolves author ids to names; without it authors show as ids. */
|
|
28
|
+
onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
29
|
+
/**
|
|
30
|
+
* `label`: the field puts the control in its `FieldLabel` via `useFieldCommentControl`.
|
|
31
|
+
* `corner`: the wrapper places it at the top right, for custom renderers, whose labels are
|
|
32
|
+
* their own.
|
|
33
|
+
*/
|
|
34
|
+
controlPlacement?: 'label' | 'corner';
|
|
35
|
+
/** Changes with the entry or branch, which resets the popover, so a draft never posts elsewhere. */
|
|
36
|
+
resetKey: string;
|
|
37
|
+
/** Why a new thread can't be posted on this field yet (`CommentThreadsPopover`). */
|
|
38
|
+
commentBlockedReason?: string;
|
|
26
39
|
}
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
41
|
+
* Gives a form field its comment control. The control shows while the pointer or focus is on
|
|
42
|
+
* this field and not on a field nested inside it, so a group never shows a second icon for its
|
|
43
|
+
* children; a field with open threads always shows it.
|
|
30
44
|
*/
|
|
31
45
|
export declare const FieldWrapper: React.FC<FieldWrapperProps>;
|
|
@@ -1,36 +1,70 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Box
|
|
5
|
-
import {
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Box } from '@mantine/core';
|
|
5
|
+
import { CommentThreadsPopover } from './CommentThreadsPopover.js';
|
|
6
|
+
import { FieldCommentContext } from './field-comment-context.js';
|
|
7
|
+
import { useFormComments } from './form-comments.js';
|
|
8
|
+
import { whenRevealed } from '../field-target.js';
|
|
9
|
+
const FIELD_SCOPE_ATTR = 'data-field-comments';
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
11
|
+
* Gives a form field its comment control. The control shows while the pointer or focus is on
|
|
12
|
+
* this field and not on a field nested inside it, so a group never shows a second icon for its
|
|
13
|
+
* children; a field with open threads always shows it.
|
|
9
14
|
*/
|
|
10
|
-
export const FieldWrapper = ({ children, canopyPath, entryPath, threads, autoFocus, currentUserId, canResolve, onAddComment, onResolveThread, highlightThreadId, }) => {
|
|
15
|
+
export const FieldWrapper = ({ children, canopyPath, entryPath, threads, autoFocus, currentUserId, canResolve, onAddComment, onResolveThread, fieldLabel, highlightThreadId, onGetUserMetadata, controlPlacement = 'label', resetKey, commentBlockedReason, }) => {
|
|
11
16
|
const wrapperRef = useRef(null);
|
|
12
|
-
const [
|
|
13
|
-
const
|
|
17
|
+
const [pointerOn, setPointerOn] = useState(false);
|
|
18
|
+
const [focusOn, setFocusOn] = useState(false);
|
|
19
|
+
const [above, setAbove] = useState(null);
|
|
20
|
+
const [below, setBelow] = useState(null);
|
|
21
|
+
const anchors = useMemo(() => ({ above, below }), [above, below]);
|
|
22
|
+
// A highlighted thread here opens the popover, which scrolls the field itself.
|
|
23
|
+
const highlightHere = !!highlightThreadId && threads.some((t) => t.id === highlightThreadId);
|
|
24
|
+
const [openRequest, setOpenRequest] = useState(undefined);
|
|
25
|
+
const formComments = useFormComments();
|
|
26
|
+
useEffect(() => formComments?.register(canopyPath, {
|
|
27
|
+
subject: fieldLabel,
|
|
28
|
+
blockedReason: commentBlockedReason,
|
|
29
|
+
// A new object per request, so asking again while it is open still counts.
|
|
30
|
+
open: (returnFocusTo) => {
|
|
31
|
+
const field = wrapperRef.current;
|
|
32
|
+
if (field)
|
|
33
|
+
whenRevealed(field, () => setOpenRequest({ returnFocusTo }));
|
|
34
|
+
else
|
|
35
|
+
setOpenRequest({ returnFocusTo });
|
|
36
|
+
},
|
|
37
|
+
element: () => wrapperRef.current,
|
|
38
|
+
}), [formComments, canopyPath, fieldLabel, commentBlockedReason]);
|
|
14
39
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
40
|
+
const field = wrapperRef.current;
|
|
41
|
+
if (!field)
|
|
42
|
+
return;
|
|
43
|
+
if (highlightHere)
|
|
44
|
+
whenRevealed(field, () => { });
|
|
45
|
+
else if (autoFocus) {
|
|
46
|
+
whenRevealed(field, () => field.scrollIntoView({ behavior: 'smooth', block: 'center' }));
|
|
20
47
|
}
|
|
21
|
-
}, [autoFocus]);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
}, [autoFocus, highlightHere]);
|
|
49
|
+
// Events from a nested field, or from a popover portalled out of the DOM, have another
|
|
50
|
+
// closest scope (or none), so they leave this field's icon hidden.
|
|
51
|
+
const ownsTarget = (target) => target instanceof Element &&
|
|
52
|
+
wrapperRef.current !== null &&
|
|
53
|
+
target.closest(`[${FIELD_SCOPE_ATTR}]`) === wrapperRef.current;
|
|
54
|
+
const control = (_jsx(CommentThreadsPopover, { threads: threads, subject: fieldLabel, variant: "field", revealed: pointerOn || focusOn, anchors: anchors, openRequest: openRequest, currentUserId: currentUserId, canResolve: canResolve, onAddComment: (text, threadId) => onAddComment(text, 'field', entryPath, canopyPath, threadId), onResolveThread: onResolveThread, onGetUserMetadata: onGetUserMetadata, highlightThreadId: highlightThreadId, composeBlockedReason: commentBlockedReason, testId: `field-comment-${canopyPath}` }, resetKey));
|
|
55
|
+
return (_jsxs(Box, { ref: wrapperRef, "data-canopy-field": canopyPath, [FIELD_SCOPE_ATTR]: '', pos: "relative", style: { width: '100%' }, onMouseOver: (event) => setPointerOn(ownsTarget(event.target)), onMouseLeave: () => setPointerOn(false), onFocus: (event) => {
|
|
56
|
+
const owned = ownsTarget(event.target);
|
|
57
|
+
setFocusOn(owned);
|
|
58
|
+
if (owned)
|
|
59
|
+
formComments?.noteFocus(canopyPath);
|
|
60
|
+
}, onBlur: (event) => {
|
|
61
|
+
const next = event.relatedTarget;
|
|
62
|
+
if (!(next instanceof Node && event.currentTarget.contains(next)))
|
|
63
|
+
setFocusOn(false);
|
|
64
|
+
}, children: [_jsx("div", { ref: setAbove, "data-comment-anchor": "above" }), _jsx(FieldCommentContext.Provider, { value: { canopyPath, control }, children: _jsx(Box, { style: {
|
|
65
|
+
outline: autoFocus ? '2px solid var(--mantine-color-blue-5)' : undefined,
|
|
66
|
+
outlineOffset: autoFocus ? 2 : undefined,
|
|
67
|
+
borderRadius: 4,
|
|
68
|
+
transition: 'outline 0.2s ease',
|
|
69
|
+
}, children: children }) }), _jsx("div", { ref: setBelow, "data-comment-anchor": "below" }), controlPlacement === 'corner' && (_jsx(Box, { pos: "absolute", top: 4, right: 4, style: { zIndex: 1 }, children: control }))] }));
|
|
36
70
|
};
|
|
@@ -8,15 +8,22 @@ export interface InlineCommentThreadProps {
|
|
|
8
8
|
onAddReply: (text: string) => Promise<void>;
|
|
9
9
|
/** Handler to resolve this thread */
|
|
10
10
|
onResolve: () => Promise<void>;
|
|
11
|
+
/** Where focus goes after a resolve; see `ThreadReplyControls`. */
|
|
12
|
+
focusAfterResolve?: () => HTMLElement | null | undefined;
|
|
11
13
|
/** Current user ID for permission checking */
|
|
12
14
|
currentUserId: string;
|
|
13
|
-
/** Whether user
|
|
15
|
+
/** Whether the user may resolve any thread; a thread's own author always may resolve it. */
|
|
14
16
|
canResolve: boolean;
|
|
15
17
|
/** Optional function to fetch user metadata for displaying user badges */
|
|
16
18
|
onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
|
|
19
|
+
/** The reply draft, when the caller keeps it, so it outlives this component; with `onReplyTextChange`. */
|
|
20
|
+
replyText?: string;
|
|
21
|
+
onReplyTextChange?: (text: string) => void;
|
|
22
|
+
/** The field a thread shown away from it is on, such as one on a removed item. */
|
|
23
|
+
fieldLabel?: string;
|
|
17
24
|
}
|
|
18
25
|
/**
|
|
19
|
-
*
|
|
26
|
+
* One comment thread, as the comment popover and ThreadCarousel list them.
|
|
20
27
|
* Always shows full thread view with per-thread scrolling.
|
|
21
28
|
*/
|
|
22
29
|
export declare const InlineCommentThread: React.FC<InlineCommentThreadProps>;
|
|
@@ -1,60 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import { Alert, Badge, Button, Group, Paper, Stack, Text, Textarea } from '@mantine/core';
|
|
5
|
-
import { IconAlertCircle } from '@tabler/icons-react';
|
|
3
|
+
import { Badge, Group, Paper, Stack, Text } from '@mantine/core';
|
|
6
4
|
import { UserBadge } from '../components/UserBadge.js';
|
|
7
|
-
import {
|
|
5
|
+
import { userIdLabel } from '../user-display.js';
|
|
6
|
+
import { RelativeTime } from '../components/RelativeTime.js';
|
|
7
|
+
import { ThreadReplyControls } from './ThreadReplyControls.js';
|
|
8
|
+
import { ON_REMOVED_ITEM } from './item-anchor-copy.js';
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* One comment thread, as the comment popover and ThreadCarousel list them.
|
|
10
11
|
* Always shows full thread view with per-thread scrolling.
|
|
11
12
|
*/
|
|
12
|
-
export const InlineCommentThread = ({ thread, onAddReply, onResolve, currentUserId, canResolve, onGetUserMetadata, }) => {
|
|
13
|
-
|
|
14
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
15
|
-
const [error, setError] = useState(null);
|
|
16
|
-
const handleReply = async () => {
|
|
17
|
-
if (!replyText.trim())
|
|
18
|
-
return;
|
|
19
|
-
setIsSubmitting(true);
|
|
20
|
-
setError(null);
|
|
21
|
-
try {
|
|
22
|
-
await onAddReply(replyText);
|
|
23
|
-
setReplyText('');
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
setError(err instanceof Error ? err.message : 'Failed to add reply');
|
|
27
|
-
}
|
|
28
|
-
finally {
|
|
29
|
-
setIsSubmitting(false);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
const handleResolve = async () => {
|
|
33
|
-
setIsSubmitting(true);
|
|
34
|
-
setError(null);
|
|
35
|
-
try {
|
|
36
|
-
await onResolve();
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
setError(err instanceof Error ? err.message : 'Failed to resolve thread');
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
setIsSubmitting(false);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const canUserResolve = () => {
|
|
46
|
-
if (!canResolve)
|
|
47
|
-
return false;
|
|
48
|
-
return thread.authorId === currentUserId || canResolve;
|
|
49
|
-
};
|
|
50
|
-
return (_jsx(Paper, { withBorder: true, p: "md", bg: "white", "data-testid": "inline-comment-thread", style: {
|
|
13
|
+
export const InlineCommentThread = ({ thread, onAddReply, onResolve, focusAfterResolve, currentUserId, canResolve, onGetUserMetadata, replyText, onReplyTextChange, fieldLabel, }) => {
|
|
14
|
+
return (_jsx(Paper, { withBorder: true, p: "md", bg: "var(--mantine-color-body)", "data-testid": "inline-comment-thread", style: {
|
|
51
15
|
width: '100%',
|
|
52
16
|
overflowY: 'auto',
|
|
53
17
|
display: 'flex',
|
|
54
18
|
flexDirection: 'column',
|
|
55
|
-
}, children: _jsxs(Stack, { gap: "md", children: [_jsxs(Group, { gap: "xs", children: [!thread.resolved && (_jsx(Badge, { size: "xs", color: "orange", variant: "light", children: "Unresolved" })), thread.resolved && (_jsx(Badge, { size: "xs", color: "green", variant: "light", children: "Resolved" }))] }), _jsx(Stack, { gap: "md", children: thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
|
|
56
|
-
borderTop: '1px solid var(--mantine-color-
|
|
19
|
+
}, children: _jsxs(Stack, { gap: "md", children: [_jsxs(Group, { gap: "xs", children: [!thread.resolved && (_jsx(Badge, { size: "xs", color: "orange", variant: "light", children: "Unresolved" })), 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 }))] }), fieldLabel && (_jsx(Text, { size: "xs", c: "dimmed", "data-testid": "inline-comment-thread-field", children: fieldLabel })), _jsx(Stack, { gap: "md", children: thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
|
|
20
|
+
borderTop: '1px solid var(--mantine-color-default-border)',
|
|
57
21
|
marginTop: 8,
|
|
58
22
|
marginBottom: 8,
|
|
59
|
-
} })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", justify: "space-between", 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: 600, children: comment.userId })), _jsx(
|
|
23
|
+
} })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", justify: "space-between", 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: 600, 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: onAddReply, onResolve: onResolve, focusAfterResolve: focusAfterResolve, currentUserId: currentUserId, canResolve: canResolve, replyText: replyText, onReplyTextChange: onReplyTextChange })), thread.resolved && thread.resolvedBy && (_jsxs(Group, { gap: 4, children: [_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: "Resolved by" }), onGetUserMetadata ? (_jsx(UserBadge, { userId: thread.resolvedBy, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: userIdLabel(thread.resolvedBy) })), thread.resolvedAt && (_jsx(RelativeTime, { iso: thread.resolvedAt, size: "xs", c: "dimmed", fs: "italic" }))] }))] }) }));
|
|
60
24
|
};
|