@ecsplico/editor 2.1.2 → 3.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 +78 -1
- package/README.md +39 -156
- package/dist/adapters/comments/drizzle.d.ts +1 -1
- package/dist/adapters/comments/record.d.ts +15 -0
- package/dist/adapters/comments/{client.js → rest-model.js} +3 -3
- package/dist/adapters/comments/rest-model.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +2 -1
- package/dist/adapters/comments/server.js +2 -1
- package/dist/adapters/comments/server.js.map +1 -1
- package/dist/adapters/comments/types.d.ts +2 -2
- package/dist/adapters/document-state.d.ts +23 -0
- package/dist/adapters/document-state.js +66 -0
- package/dist/adapters/document-state.js.map +1 -0
- package/dist/adapters/documents.js +14 -11
- package/dist/adapters/documents.js.map +1 -1
- package/dist/adapters/drizzle.d.ts +2 -2
- package/dist/adapters/drizzle.js +97 -88
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/adapters/forge.d.ts +12 -7
- package/dist/adapters/forge.js +38 -45
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/http.d.ts +20 -0
- package/dist/adapters/http.js +68 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/json.d.ts +3 -3
- package/dist/adapters/json.js +58 -49
- package/dist/adapters/json.js.map +1 -1
- package/dist/adapters/memory.d.ts +21 -0
- package/dist/adapters/memory.js +53 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/nitro.d.ts +27 -20
- package/dist/adapters/nitro.js +59 -163
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/react.d.ts +55 -0
- package/dist/adapters/react.js +209 -0
- package/dist/adapters/react.js.map +1 -0
- package/dist/adapters/shared.d.ts +15 -21
- package/dist/adapters/shared.js +10 -8
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/vue.d.ts +15 -30
- package/dist/adapters/vue.js +36 -87
- package/dist/adapters/vue.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.js +95 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/{adapters/routes.js → core/api-routes.js} +5 -3
- package/dist/core/api-routes.js.map +1 -0
- package/dist/core/backend-capability.d.ts +7 -0
- package/dist/core/backend-capability.js +17 -0
- package/dist/core/backend-capability.js.map +1 -0
- package/dist/core/backend-types.d.ts +245 -0
- package/dist/core/backend-types.js +17 -0
- package/dist/core/backend-types.js.map +1 -0
- package/dist/core/backend.d.ts +108 -0
- package/dist/core/backend.js +14 -0
- package/dist/core/backend.js.map +1 -0
- package/dist/core/block-convert.d.ts +7 -5
- package/dist/core/block-convert.js +28 -5
- package/dist/core/block-convert.js.map +1 -1
- package/dist/core/command-types.d.ts +96 -0
- package/dist/core/commands.d.ts +40 -0
- package/dist/core/commands.js +151 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/context.d.ts +17 -20
- package/dist/core/context.js +24 -21
- package/dist/core/context.js.map +1 -1
- package/dist/core/core-feature.d.ts +22 -7
- package/dist/core/core-feature.js +51 -18
- package/dist/core/core-feature.js.map +1 -1
- package/dist/core/define-editor.d.ts +4 -3
- package/dist/core/define-editor.js +14 -7
- package/dist/core/define-editor.js.map +1 -1
- package/dist/core/document-markdown.js +12 -3
- package/dist/core/document-markdown.js.map +1 -1
- package/dist/core/editor-ui.d.ts +8 -1
- package/dist/core/editor-ui.js +9 -8
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/extension-points.d.ts +3 -15
- package/dist/core/feature-context.d.ts +8 -7
- package/dist/core/feature-context.js +12 -11
- package/dist/core/feature-context.js.map +1 -1
- package/dist/core/feature.d.ts +41 -19
- package/dist/core/feature.js.map +1 -1
- package/dist/core/fences.d.ts +2 -1
- package/dist/core/fences.js +3 -2
- package/dist/core/fences.js.map +1 -1
- package/dist/core/fetch-error.d.ts +1 -1
- package/dist/core/fetch-error.js +3 -1
- package/dist/core/fetch-error.js.map +1 -1
- package/dist/core/hash.d.ts +5 -0
- package/dist/core/hash.js +11 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/http-backend.d.ts +11 -0
- package/dist/core/http-backend.js +195 -0
- package/dist/core/http-backend.js.map +1 -0
- package/dist/core/index.d.ts +15 -9
- package/dist/core/index.js +13 -9
- package/dist/core/palette.d.ts +48 -0
- package/dist/core/palette.js +95 -0
- package/dist/core/palette.js.map +1 -0
- package/dist/core/schema/abilities.d.ts +2 -1
- package/dist/core/schema/abilities.js +5 -1
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/core/schema/index.d.ts +11 -5
- package/dist/core/schema/index.js +13 -8
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/schema/mark-flags.d.ts +8 -1
- package/dist/core/schema/of-features.d.ts +13 -0
- package/dist/core/schema/of-features.js +14 -0
- package/dist/core/schema/of-features.js.map +1 -0
- package/dist/core/slash.d.ts +8 -6
- package/dist/core/slash.js +24 -16
- package/dist/core/slash.js.map +1 -1
- package/dist/core/surface-session.js +15 -5
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/toolbar-items.d.ts +10 -4
- package/dist/core/toolbar-items.js +269 -195
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/core/transaction-guards.d.ts +21 -0
- package/dist/core/transaction-guards.js +49 -0
- package/dist/core/transaction-guards.js.map +1 -0
- package/dist/core/util.js +11 -0
- package/dist/core/util.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/agent-op-review.d.ts +1 -1
- package/dist/features/ai/agent-op-review.js +1 -1
- package/dist/features/ai/agent-ops.js +1 -1
- package/dist/features/ai/ai-apply.d.ts +2 -2
- package/dist/features/ai/ai-apply.js +1 -1
- package/dist/features/ai/ai-review.js +1 -1
- package/dist/features/ai/ai-sse.d.ts +6 -6
- package/dist/features/ai/ai-sse.js +20 -63
- package/dist/features/ai/ai-sse.js.map +1 -1
- package/dist/features/ai/assistant.d.ts +8 -7
- package/dist/features/ai/assistant.js +3 -3
- package/dist/features/ai/assistant.js.map +1 -1
- package/dist/features/ai/block-actions.d.ts +3 -2
- package/dist/features/ai/block-actions.js +3 -2
- package/dist/features/ai/block-actions.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.vue.d.ts +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +8 -8
- package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/document-chat-model.d.ts +8 -7
- package/dist/features/ai/document-chat-model.js +5 -5
- package/dist/features/ai/document-chat-model.js.map +1 -1
- package/dist/features/ai/document-chat.js +1 -1
- package/dist/features/ai/feature.js +27 -14
- package/dist/features/ai/feature.js.map +1 -1
- package/dist/features/ai/ghost-autocomplete.d.ts +7 -6
- package/dist/features/ai/ghost-autocomplete.js +6 -9
- package/dist/features/ai/ghost-autocomplete.js.map +1 -1
- package/dist/features/ai/index.d.ts +4 -3
- package/dist/features/ai/index.js +10 -10
- package/dist/features/ai/model-profiles.d.ts +5 -4
- package/dist/features/ai/model-profiles.js +2 -2
- package/dist/features/ai/model-profiles.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.js +1 -1
- package/dist/features/ai/protocol/block-lines.js.map +1 -1
- package/dist/features/ai/protocol/chat.js +6 -2
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/models.d.ts +2 -16
- package/dist/features/ai/protocol/models.js.map +1 -1
- package/dist/features/ai/rate-limit.d.ts +1 -1
- package/dist/features/ai/rate-limit.js +3 -1
- package/dist/features/ai/rate-limit.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js +2 -2
- package/dist/features/ai/react/AiSidebar.js +4 -4
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.d.ts +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +8 -10
- package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/react/use-ai-assistant.js +3 -3
- package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/react/use-document-chat.js +2 -2
- package/dist/features/ai/react/use-document-chat.js.map +1 -1
- package/dist/features/ai/react/use-model-profiles.d.ts +2 -1
- package/dist/features/ai/selection-flow.d.ts +1 -1
- package/dist/features/ai/selection-flow.js +1 -1
- package/dist/features/ai/sidebar-flow.d.ts +1 -1
- package/dist/features/ai/sidebar-flow.js +1 -1
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-ai-assistant.js +3 -3
- package/dist/features/ai/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/use-document-chat.js +2 -2
- package/dist/features/ai/use-document-chat.js.map +1 -1
- package/dist/features/ai/use-model-profiles.d.ts +2 -1
- package/dist/features/authors/attribution.d.ts +1 -1
- package/dist/features/authors/feature.js +12 -11
- package/dist/features/authors/feature.js.map +1 -1
- package/dist/features/block-ui/block-actions.d.ts +1 -1
- package/dist/features/block-ui/block-actions.js +1 -1
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.js +1 -1
- package/dist/features/block-ui/feature.js +1 -0
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.js +1 -1
- package/dist/features/collab/anchor-resolver.js +1 -1
- package/dist/features/collab/feature.js +2 -0
- package/dist/features/collab/feature.js.map +1 -1
- package/dist/features/comments/components/CommentThreadView.vue.d.ts +2 -1
- package/dist/features/comments/feature.js +12 -11
- package/dist/features/comments/feature.js.map +1 -1
- package/dist/features/comments/index.d.ts +3 -2
- package/dist/features/comments/index.js +1 -1
- package/dist/features/comments/margin-layout.d.ts +3 -2
- package/dist/features/comments/model.d.ts +3 -67
- package/dist/features/comments/model.js +2 -2
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentThreadView.d.ts +3 -2
- package/dist/features/comments/react/use-comments.d.ts +2 -1
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +2 -52
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js.map +1 -1
- package/dist/features/comments/use-comments.d.ts +3 -2
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +1 -0
- package/dist/features/composites/accepted-children.js +16 -1
- package/dist/features/composites/accepted-children.js.map +1 -1
- package/dist/features/composites/ai-brief.js +39 -0
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/bibliography-command.d.ts +11 -0
- package/dist/features/composites/bibliography-form.js +90 -0
- package/dist/features/composites/bibliography-form.js.map +1 -0
- package/dist/features/composites/bibliography.d.ts +33 -0
- package/dist/features/composites/bibliography.js +120 -0
- package/dist/features/composites/bibliography.js.map +1 -0
- package/dist/features/composites/components/BibliographyEditor.js +261 -0
- package/dist/features/composites/components/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/components/CompositeChrome.js +78 -29
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeDetails.js +46 -11
- package/dist/features/composites/components/CompositeDetails.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.vue.d.ts +2 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +64 -5
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.js +20 -7
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/components/CompositeTable.js +13 -4
- package/dist/features/composites/components/CompositeTable.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/components/CompositeView.js +11 -1
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +103 -0
- package/dist/features/composites/components/DerivationBadge.js.map +1 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js +60 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.js +122 -0
- package/dist/features/composites/components/ReferenceView.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.vue.d.ts +96 -0
- package/dist/features/composites/components/SettingsEditingTab.js +1 -1
- package/dist/features/composites/convert.js +2 -2
- package/dist/features/composites/convert.js.map +1 -1
- package/dist/features/composites/derivation.d.ts +61 -0
- package/dist/features/composites/derivation.js +98 -0
- package/dist/features/composites/derivation.js.map +1 -0
- package/dist/features/composites/derived-chrome.js +69 -0
- package/dist/features/composites/derived-chrome.js.map +1 -0
- package/dist/features/composites/feature.js +159 -109
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/fences.d.ts +4 -3
- package/dist/features/composites/fences.js +51 -7
- package/dist/features/composites/fences.js.map +1 -1
- package/dist/features/composites/index.d.ts +12 -3
- package/dist/features/composites/index.js +15 -6
- package/dist/features/composites/inspector-model.d.ts +4 -1
- package/dist/features/composites/inspector-model.js +8 -4
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +16 -2
- package/dist/features/composites/kinds.js +145 -6
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/legacy.d.ts +4 -1
- package/dist/features/composites/legacy.js +56 -1
- package/dist/features/composites/legacy.js.map +1 -1
- package/dist/features/composites/meta-display.js +45 -2
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/meta-keys.d.ts +7 -0
- package/dist/features/composites/meta-keys.js +21 -0
- package/dist/features/composites/meta-keys.js.map +1 -0
- package/dist/features/composites/node-views.d.ts +6 -3
- package/dist/features/composites/node-views.js +7 -3
- package/dist/features/composites/node-views.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +6 -1
- package/dist/features/composites/nodes.js +33 -7
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/react/BibliographyEditor.js +229 -0
- package/dist/features/composites/react/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/react/CompositeChrome.js +67 -19
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.d.ts +2 -1
- package/dist/features/composites/react/CompositeDetails.js +45 -6
- package/dist/features/composites/react/CompositeDetails.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.js +43 -1
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.js +12 -6
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.js +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/react/CompositeTable.js +16 -2
- package/dist/features/composites/react/CompositeTable.js.map +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js +2 -2
- package/dist/features/composites/react/CompositeView.js +6 -1
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +116 -0
- package/dist/features/composites/react/DerivationBadge.js.map +1 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js +52 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/react/ReferenceView.d.ts +11 -0
- package/dist/features/composites/react/ReferenceView.js +115 -0
- package/dist/features/composites/react/ReferenceView.js.map +1 -0
- package/dist/features/composites/react/SettingsEditingTab.js +2 -2
- package/dist/features/composites/react.d.ts +2 -1
- package/dist/features/composites/react.js +7 -3
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/reference-ai.js +16 -0
- package/dist/features/composites/reference-ai.js.map +1 -0
- package/dist/features/composites/reference-label.d.ts +19 -0
- package/dist/features/composites/reference-label.js +58 -0
- package/dist/features/composites/reference-label.js.map +1 -0
- package/dist/features/composites/reference-suggestion.d.ts +10 -0
- package/dist/features/composites/reference-suggestion.js +41 -0
- package/dist/features/composites/reference-suggestion.js.map +1 -0
- package/dist/features/composites/reference-text.d.ts +18 -0
- package/dist/features/composites/reference-text.js +89 -0
- package/dist/features/composites/reference-text.js.map +1 -0
- package/dist/features/composites/reference.d.ts +44 -0
- package/dist/features/composites/reference.js +148 -0
- package/dist/features/composites/reference.js.map +1 -0
- package/dist/features/composites/reveal-source.d.ts +32 -0
- package/dist/features/composites/reveal-source.js +89 -0
- package/dist/features/composites/reveal-source.js.map +1 -0
- package/dist/features/composites/rows.d.ts +25 -3
- package/dist/features/composites/rows.js +72 -10
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/schema.d.ts +9 -3
- package/dist/features/composites/schema.js +11 -4
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/table-model.js +12 -21
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/composites/vue.d.ts +3 -2
- package/dist/features/composites/vue.js +8 -4
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/media/assets.d.ts +2 -0
- package/dist/features/media/assets.js +3 -2
- package/dist/features/media/assets.js.map +1 -1
- package/dist/features/media/components/FileView.js +13 -4
- package/dist/features/media/components/FileView.js.map +1 -1
- package/dist/features/media/feature.d.ts +3 -3
- package/dist/features/media/feature.js +21 -28
- package/dist/features/media/feature.js.map +1 -1
- package/dist/features/media/file-import.js +2 -2
- package/dist/features/media/file-import.js.map +1 -1
- package/dist/features/media/index.d.ts +2 -2
- package/dist/features/media/index.js +3 -3
- package/dist/features/media/react/AudioRecorderModal.js +1 -1
- package/dist/features/media/react/FileView.js +19 -2
- package/dist/features/media/react/FileView.js.map +1 -1
- package/dist/features/media/react.js +1 -1
- package/dist/features/media/schema.d.ts +8 -0
- package/dist/features/media/schema.js +126 -11
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/media/upload.d.ts +16 -14
- package/dist/features/media/upload.js +13 -19
- package/dist/features/media/upload.js.map +1 -1
- package/dist/features/mentions/index.d.ts +4 -3
- package/dist/features/mentions/index.js +4 -1
- package/dist/features/mentions/index.js.map +1 -1
- package/dist/features/mentions/mention-text.d.ts +1 -1
- package/dist/features/mentions/mention-text.js.map +1 -1
- package/dist/features/mentions/mention.d.ts +0 -6
- package/dist/features/mentions/mention.js.map +1 -1
- package/dist/features/mentions/metadata-defaults.d.ts +2 -2
- package/dist/features/mentions/metadata-defaults.js +3 -9
- package/dist/features/mentions/metadata-defaults.js.map +1 -1
- package/dist/features/mentions/schema.d.ts +2 -1
- package/dist/features/mentions/schema.js.map +1 -1
- package/dist/features/tables/feature.d.ts +2 -1
- package/dist/features/tables/feature.js +90 -70
- package/dist/features/tables/feature.js.map +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js +6 -9
- package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/feature.js +2 -0
- package/dist/features/themes/feature.js.map +1 -1
- package/dist/features/themes/index.d.ts +2 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js +7 -10
- package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/schema.d.ts +2 -6
- package/dist/features/themes/schema.js.map +1 -1
- package/dist/features/themes/theme-runtime.d.ts +1 -1
- package/dist/features/themes/theme-runtime.js +4 -2
- package/dist/features/themes/theme-runtime.js.map +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js +48 -28
- package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/feature.js +21 -19
- package/dist/features/transcript/feature.js.map +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js +55 -36
- package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/segment-node.d.ts +2 -0
- package/dist/features/transcript/segment-node.js +35 -7
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/composites.js +91 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/integrations.js +49 -8
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/media.js +0 -1
- package/dist/i18n/de/media.js.map +1 -1
- package/dist/i18n/de/palette.js +22 -0
- package/dist/i18n/de/palette.js.map +1 -0
- package/dist/i18n/de/tables.js +0 -1
- package/dist/i18n/de/tables.js.map +1 -1
- package/dist/i18n/de/transcript.js +2 -1
- package/dist/i18n/de/transcript.js.map +1 -1
- package/dist/i18n/de/tree.js +3 -0
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +157 -11
- package/dist/i18n/de.js +3 -1
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/composites.js +89 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +49 -8
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/media.js +0 -1
- package/dist/i18n/en/media.js.map +1 -1
- package/dist/i18n/en/palette.js +22 -0
- package/dist/i18n/en/palette.js.map +1 -0
- package/dist/i18n/en/tables.js +0 -1
- package/dist/i18n/en/tables.js.map +1 -1
- package/dist/i18n/en/transcript.js +2 -1
- package/dist/i18n/en/transcript.js.map +1 -1
- package/dist/i18n/en/tree.js +3 -0
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/integrations/composite-ai/bibliography-fetch.d.ts +31 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js +159 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -0
- package/dist/integrations/composite-ai/composite-fill.d.ts +30 -8
- package/dist/integrations/composite-ai/composite-fill.js +97 -56
- package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
- package/dist/integrations/composite-ai/composite-summary.d.ts +4 -12
- package/dist/integrations/composite-ai/composite-summary.js +7 -26
- package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +29 -0
- package/dist/integrations/composite-ai/derive.js +173 -0
- package/dist/integrations/composite-ai/derive.js.map +1 -0
- package/dist/integrations/composite-ai/index.d.ts +11 -7
- package/dist/integrations/composite-ai/index.js +72 -14
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/rendition.d.ts +45 -0
- package/dist/integrations/composite-ai/rendition.js +314 -0
- package/dist/integrations/composite-ai/rendition.js.map +1 -0
- package/dist/integrations/composite-media/index.d.ts +7 -5
- package/dist/integrations/composite-media/index.js +86 -13
- package/dist/integrations/composite-media/index.js.map +1 -1
- package/dist/integrations/transcript-ai/index.d.ts +1 -1
- package/dist/integrations/transcript-ai/index.js +5 -2
- package/dist/integrations/transcript-ai/index.js.map +1 -1
- package/dist/integrations/transcript-ai/protocol.d.ts +2 -12
- package/dist/integrations/transcript-ai/protocol.js +0 -2
- package/dist/integrations/transcript-ai/protocol.js.map +1 -1
- package/dist/integrations/transcript-ai/transcript-cleanup.d.ts +5 -5
- package/dist/integrations/transcript-ai/transcript-cleanup.js +7 -10
- package/dist/integrations/transcript-ai/transcript-cleanup.js.map +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +12 -11
- package/dist/markdown/MarkdownSurface2.js +3 -6
- package/dist/markdown/MarkdownSurface2.js.map +1 -1
- package/dist/markdown/document.js +1 -1
- package/dist/markdown/source-mode.d.ts +1 -1
- package/dist/nuxt/module.d.ts +1402 -54
- package/dist/nuxt/module.js +6 -49
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/install-context.d.ts +7 -3
- package/dist/nuxt/runtime/install-context.js +14 -8
- package/dist/nuxt/runtime/install-context.js.map +1 -1
- package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +2 -2
- package/dist/nuxt/runtime/server/plugins/editor-server.js +20 -12
- package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
- package/dist/presets/compose.d.ts +94 -0
- package/dist/presets/compose.js +164 -0
- package/dist/presets/compose.js.map +1 -0
- package/dist/react/AdvancedEditor.d.ts +15 -15
- package/dist/react/AdvancedEditor.js +15 -13
- package/dist/react/AdvancedEditor.js.map +1 -1
- package/dist/react/EditorCommandPalette.d.ts +13 -0
- package/dist/react/EditorCommandPalette.js +118 -0
- package/dist/react/EditorCommandPalette.js.map +1 -0
- package/dist/react/EditorSettingsModal.d.ts +5 -5
- package/dist/react/EditorSettingsModal.js +7 -4
- package/dist/react/EditorSettingsModal.js.map +1 -1
- package/dist/react/EditorSettingsPanel.d.ts +7 -7
- package/dist/react/EditorSettingsPanel.js +8 -5
- package/dist/react/EditorSettingsPanel.js.map +1 -1
- package/dist/react/EditorSurface.d.ts +7 -7
- package/dist/react/EditorSurface.js +10 -6
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.d.ts +4 -6
- package/dist/react/EditorToolbar.js +29 -37
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/react/SimpleEditor.d.ts +22 -23
- package/dist/react/SimpleEditor.js +21 -7
- package/dist/react/SimpleEditor.js.map +1 -1
- package/dist/react/advanced.d.ts +4 -3
- package/dist/react/advanced.js +2 -2
- package/dist/react/compose.d.ts +20 -0
- package/dist/react/compose.js +49 -0
- package/dist/react/compose.js.map +1 -0
- package/dist/react/composed-surface.d.ts +39 -0
- package/dist/react/composed-surface.js +71 -0
- package/dist/react/composed-surface.js.map +1 -0
- package/dist/react/context.js +6 -2
- package/dist/react/context.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/key-labels.js +15 -0
- package/dist/react/key-labels.js.map +1 -0
- package/dist/react/settings.d.ts +1 -1
- package/dist/react/simple.d.ts +2 -2
- package/dist/react/ui/command.js +36 -1
- package/dist/react/ui/command.js.map +1 -1
- package/dist/schema/advanced.d.ts +6 -6
- package/dist/schema/advanced.js +26 -29
- package/dist/schema/advanced.js.map +1 -1
- package/dist/server/ai.d.ts +5 -16
- package/dist/server/ai.js.map +1 -1
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/collab-state.d.ts +1 -1
- package/dist/server/collab-state.js.map +1 -1
- package/dist/server/collab.d.ts +13 -13
- package/dist/server/collab.js +34 -18
- package/dist/server/collab.js.map +1 -1
- package/dist/server/crossref.js +78 -0
- package/dist/server/crossref.js.map +1 -0
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/errors.js +1 -1
- package/dist/server/errors.js.map +1 -1
- package/dist/server/extract.js +308 -0
- package/dist/server/extract.js.map +1 -0
- package/dist/server/h3.d.ts +8 -0
- package/dist/{nuxt/runtime/server/utils/editor-route.js → server/h3.js} +8 -16
- package/dist/server/h3.js.map +1 -0
- package/dist/server/handlers/ai.d.ts +32 -10
- package/dist/server/handlers/ai.js +172 -37
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +12 -5
- package/dist/server/handlers/context.js +10 -4
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +19 -13
- package/dist/server/handlers/editor.js +49 -38
- package/dist/server/handlers/editor.js.map +1 -1
- package/dist/server/handlers/index.d.ts +4 -4
- package/dist/server/handlers/index.js +4 -4
- package/dist/server/host.d.ts +16 -25
- package/dist/server/host.js +58 -41
- package/dist/server/host.js.map +1 -1
- package/dist/server/index.d.ts +6 -4
- package/dist/server/index.js +3 -2
- package/dist/server/pdf.js +97 -0
- package/dist/server/pdf.js.map +1 -0
- package/dist/server/{adapter.d.ts → ports.d.ts} +81 -30
- package/dist/server/ports.js +29 -0
- package/dist/server/ports.js.map +1 -0
- package/dist/server/routes.d.ts +22 -0
- package/dist/server/routes.js +144 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js.map +1 -1
- package/dist/styles/composites.css +25 -0
- package/dist/tree/DocumentTree.js +56 -29
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +23 -0
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +2 -2
- package/dist/tree/tree-items.js +1 -1
- package/dist/vue/AdvancedEditor.js +30 -9
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +32 -12
- package/dist/vue/SimpleEditor.js +62 -16
- package/dist/vue/SimpleEditor.js.map +1 -1
- package/dist/vue/SimpleEditor.vue.d.ts +39 -9
- package/dist/vue/advanced.d.ts +3 -2
- package/dist/vue/advanced.js +2 -2
- package/dist/vue/components/EditorCommandPalette.js +91 -0
- package/dist/vue/components/EditorCommandPalette.js.map +1 -0
- package/dist/vue/components/EditorCommandPalette.vue.d.ts +15 -0
- package/dist/vue/components/EditorSettingsModal.js +10 -6
- package/dist/vue/components/EditorSettingsModal.js.map +1 -1
- package/dist/vue/components/EditorSettingsModal.vue.d.ts +4 -4
- package/dist/vue/components/EditorSettingsPanel.js +10 -6
- package/dist/vue/components/EditorSettingsPanel.js.map +1 -1
- package/dist/vue/components/EditorSettingsPanel.vue.d.ts +6 -6
- package/dist/vue/components/EditorSurface.js +11 -6
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +11 -11
- package/dist/vue/components/EditorToolbar.js +41 -49
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/components/EditorToolbar.vue.d.ts +2 -4
- package/dist/vue/compose.d.ts +17 -0
- package/dist/vue/compose.js +51 -0
- package/dist/vue/compose.js.map +1 -0
- package/dist/vue/context-key.d.ts +6 -0
- package/dist/vue/context-key.js +6 -0
- package/dist/vue/context-key.js.map +1 -0
- package/dist/vue/context.d.ts +8 -6
- package/dist/vue/context.js +6 -5
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/index.d.ts +10 -8
- package/dist/vue/index.js +10 -8
- package/dist/vue/kit/index.d.ts +2 -2
- package/dist/vue/kit/nuxt-ui.js +23 -0
- package/dist/vue/kit/nuxt-ui.js.map +1 -1
- package/dist/vue/kit/types.d.ts +40 -1
- package/package.json +30 -16
- package/dist/adapters/comments/client.d.ts +0 -64
- package/dist/adapters/comments/client.js.map +0 -1
- package/dist/adapters/routes.js.map +0 -1
- package/dist/core/toolbar.d.ts +0 -74
- package/dist/core/toolbar.js +0 -45
- package/dist/core/toolbar.js.map +0 -1
- package/dist/integrations/composite-ai/document-transcribe.d.ts +0 -14
- package/dist/integrations/composite-ai/document-transcribe.js +0 -89
- package/dist/integrations/composite-ai/document-transcribe.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/chat.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/chat.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/chat.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/complete.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/complete.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/complete.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/generate.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/generate.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/generate.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/models.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/models.get.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/models.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.put.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.put.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.put.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/tags.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/tags.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/tags.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/users.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/users.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/users.get.js.map +0 -1
- package/dist/nuxt/runtime/server/plugins/collab.d.ts +0 -5
- package/dist/nuxt/runtime/server/plugins/collab.js +0 -10
- package/dist/nuxt/runtime/server/plugins/collab.js.map +0 -1
- package/dist/nuxt/runtime/server/routes/collab/[id].d.ts +0 -5
- package/dist/nuxt/runtime/server/routes/collab/[id].js +0 -35
- package/dist/nuxt/runtime/server/routes/collab/[id].js.map +0 -1
- package/dist/nuxt/runtime/server/utils/editor-route.d.ts +0 -11
- package/dist/nuxt/runtime/server/utils/editor-route.js.map +0 -1
- package/dist/presets/advanced.d.ts +0 -33
- package/dist/presets/advanced.js +0 -50
- package/dist/presets/advanced.js.map +0 -1
- package/dist/presets/simple.d.ts +0 -9
- package/dist/presets/simple.js +0 -12
- package/dist/presets/simple.js.map +0 -1
- package/dist/react/advanced-preset.d.ts +0 -11
- package/dist/react/advanced-preset.js +0 -31
- package/dist/react/advanced-preset.js.map +0 -1
- package/dist/vue/advanced-preset.d.ts +0 -13
- package/dist/vue/advanced-preset.js +0 -33
- package/dist/vue/advanced-preset.js.map +0 -1
- package/docs/extending.md +0 -538
- package/docs/integration.md +0 -632
- package/docs/llms.md +0 -383
- package/docs/usage.md +0 -1076
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","names":[],"sources":["../../../src/features/comments/model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport * as Y from 'yjs'\nimport type { AuthorIdentity, FeatureContext } from '../../core'\nimport { anchorResolverOf, editorEntryOf, releaseRanges, resolveRange } from '../../core'\nimport { newBlockId } from '../../core/schema'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { cancelCommentDraft, commentsDoc, commentsKey, type CommentsMeta, commentsUser, setActiveComment, startCommentDraft } from './comments-plugin'\nimport { COMMENT_LIMITS, COMMENTS_FIELD, type CommentAnchor, type CommentReply, type CommentThread } from './schema'\nimport { EMPTY_SNAPSHOT, NO_POSITIONS, NO_RANGES, NO_THREADS, sameOrphanAt, sameRange, sameRanges, sameUser } from './snapshot-equality'\nimport { captureThreadAnchor, resolveThreadAnchor } from './thread-anchor'\n\nexport type DocRange = { from: number, to: number }\n\n/** What a comments UI renders: replaced (a new object) whenever any part of it changes, so a store subscriber re-renders only then. */\nexport interface CommentsSnapshot {\n /** Comments need a collaborative editor whose `Comments` extension has a user (a host model decides for itself). */\n available: boolean\n /**\n * Whether the user may compose a comment now: the default model says `available` and the editor is editable, a host\n * model may allow readers of a read-only document to comment.\n */\n canComment: boolean\n user: AuthorIdentity | null\n /** Plain snapshots of all threads in the Y.Doc, replaced on every change. */\n threads: readonly CommentThread[]\n /** Current text range per thread id (threads without one are orphans). */\n ranges: ReadonlyMap<string, DocRange>\n /**\n * Where the text of an orphaned open thread was: the document position of the deletion point, resolved from the\n * thread's stored anchor (so it is the same place for every collaborator and follows edits). Absent for orphans\n * without a usable anchor (threads from before anchors, or whose block is gone for good).\n */\n orphanAt: ReadonlyMap<string, number>\n active: string | null\n /** Current range of the draft a new comment is being written for. */\n draft: DocRange | null\n /** The draft's text as the new thread will keep it (`quote`). */\n draftQuote: string\n /** Unresolved threads in document order, orphans last; resolved ones newest first. */\n open: readonly CommentThread[]\n resolved: readonly CommentThread[]\n}\n\n/** The mutations of the comments of one editor; no-ops without collaboration or a user. */\nexport interface CommentsActions {\n /** Creates a thread on the draft range; its id, or null without a (non-empty) draft or text. */\n add: (text: string) => string | null\n reply: (threadId: string, text: string) => void\n /** Changes the text of the user's own thread (`replyId` null) or reply. */\n edit: (threadId: string, replyId: string | null, text: string) => void\n /** Resolves or reopens a thread (anyone may). */\n setResolved: (threadId: string, resolved: boolean) => void\n /** Deletes the user's own thread with its marks / the user's own reply. */\n remove: (threadId: string) => void\n removeReply: (threadId: string, replyId: string) => void\n /** Activates a thread (`null`: none); `reveal` selects and scrolls to its text. */\n activate: (id: string | null, options?: { reveal?: boolean }) => void\n /** Starts a comment on the current selection (the layout shows the composer while `draft` is set). */\n startDraft: () => void\n cancelDraft: () => void\n}\n\n/**\n * The comments of one editor, framework-free: `state` is the observable snapshot, `refresh()` brings it up to date\n * (the framework hook calls it when the editor it watches changes; the model itself listens to that editor's\n * transactions from then on), `dispose()` detaches the editor and Y.Doc listeners and may be followed by `refresh()` again\n * (StrictMode mounts twice).\n */\nexport interface CommentsModel extends CommentsActions {\n readonly state: ReadonlyStore<CommentsSnapshot>\n refresh: () => void\n dispose: () => void\n}\n\n/** A thread's `Y.Map` as plain data; `replies` converted explicitly so it is always a plain array. */\nfunction snapshotOf(thread: Y.Map<unknown>): CommentThread {\n const replies = thread.get('replies')\n return { ...(thread.toJSON() as CommentThread), replies: replies instanceof Y.Array ? replies.toArray() as CommentReply[] : [] }\n}\n\n/**\n * The comment threads of a collaborative editor, stored in its Y.Doc (`COMMENTS_FIELD`, one nested `Y.Map` per thread,\n * replies in a nested `Y.Array`), so they sync live with the document and persist with its Yjs state. Keeps the\n * `Comments` plugin's `known`/`hidden` thread ids in step with the store (highlights), exposes the plugin's ranges,\n * active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and\n * deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).\n * `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).\n * Each thread also keeps `anchor` (a Yjs relative position of its text's start, see `CommentAnchor`): `orphanAt` resolves\n * it for the threads whose text is gone. Threads from before anchors get theirs written once they are seen with a range.\n */\nexport function createCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY_SNAPSHOT)\n\n const current = () => {\n const instance = getEditor()\n return instance && !instance.isDestroyed ? instance : undefined\n }\n\n // The editor whose transactions are followed, and the Y.Map of threads observed.\n let bound: Editor | undefined\n let observed: { doc: Y.Doc, map: Y.Map<unknown> } | undefined\n let threads: readonly CommentThread[] = NO_THREADS\n let syncQueued = false\n // The thread list the plugin was last told about, for the deactivation rule.\n let told: readonly CommentThread[] = NO_THREADS\n // Threads already tried for an anchor (written, or not capturable), so a failed capture is not repeated on every change.\n const anchorTried = new Set<string>()\n let healQueued = false\n\n function unobserve() {\n observed?.map.unobserveDeep(onThreadsChanged)\n observed = undefined\n }\n\n function onThreadsChanged() {\n if (!observed) return\n threads = [...observed.map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n recompute()\n queueSync()\n }\n\n function onTransaction() {\n recompute()\n }\n\n function unbind() {\n bound?.off('transaction', onTransaction)\n // `setEditable` emits `update` only, and `canComment` follows `isEditable`.\n bound?.off('update', onTransaction)\n bound = undefined\n }\n\n /** The snapshot of the editor's current state; the previous object when nothing changed. */\n function recompute() {\n const instance = current()\n if (!instance) {\n store.set(EMPTY_SNAPSHOT)\n return\n }\n const previous = store.get()\n const ydoc = commentsDoc(instance)\n const user = commentsUser(instance)\n const plugin = commentsKey.getState(instance.state)\n const ranges = plugin?.ranges ?? NO_RANGES\n const orphanAt = orphanPositions(instance, ranges)\n const active = plugin?.active ?? null\n const anchor = plugin?.draft\n const draft = anchor ? resolveRange(instance.state, anchor) : null\n const draftQuote = draft ? instance.state.doc.textBetween(draft.from, draft.to, ' ').slice(0, COMMENT_LIMITS.quote) : ''\n const available = !!ydoc && !!user\n const canComment = available && instance.isEditable\n\n const sameThreads = threads === previous.threads\n const sameRangeMap = sameRanges(ranges, previous.ranges)\n const sameOrphanMap = sameOrphanAt(orphanAt, previous.orphanAt)\n if (\n available === previous.available && canComment === previous.canComment && sameUser(user, previous.user) && sameThreads && sameRangeMap && sameOrphanMap\n && active === previous.active && sameRange(draft, previous.draft) && draftQuote === previous.draftQuote\n ) return\n\n const kept = sameThreads && sameRangeMap\n const open = kept\n ? previous.open\n : threads\n .filter(t => !t.resolved)\n .sort((a, b) => (ranges.get(a.id)?.from ?? Infinity) - (ranges.get(b.id)?.from ?? Infinity) || a.createdAt.localeCompare(b.createdAt))\n const resolved = sameThreads\n ? previous.resolved\n : threads.filter(t => t.resolved).sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n store.set({\n available,\n canComment,\n user,\n threads,\n ranges: sameRangeMap ? previous.ranges : ranges,\n orphanAt: sameOrphanMap ? previous.orphanAt : orphanAt,\n active,\n draft: sameRange(draft, previous.draft) ? previous.draft : draft,\n draftQuote,\n open,\n resolved,\n })\n if (available) queueHeal()\n }\n\n /** Where each unresolved thread without a text range was, from its anchor; empty without a Yjs session. */\n function orphanPositions(instance: Editor, ranges: ReadonlyMap<string, DocRange>): ReadonlyMap<string, number> {\n const resolver = anchorResolverOf(instance)\n if (!resolver) return NO_POSITIONS\n const size = instance.state.doc.content.size\n let found: Map<string, number> | undefined\n for (const thread of threads) {\n if (thread.resolved || ranges.has(thread.id) || !thread.anchor) continue\n const position = resolveThreadAnchor(instance.state, resolver, thread.anchor)\n if (position !== null && position <= size) (found ??= new Map()).set(thread.id, position)\n }\n return found ?? NO_POSITIONS\n }\n\n // Threads from before anchors get theirs once they are seen with their text in the document (any client with a user\n // writes it; the positions are equivalent, so concurrent writes agree). Queued: not written from inside a Yjs\n // observer or a transaction listener.\n function queueHeal() {\n if (healQueued || !threads.some(t => !t.anchor && !anchorTried.has(t.id))) return\n healQueued = true\n queueMicrotask(() => {\n healQueued = false\n healAnchors()\n })\n }\n\n function healAnchors() {\n const ctx = writable()\n const resolver = ctx && anchorResolverOf(ctx.editor)\n if (!ctx || !resolver || ctx.editor !== bound) return\n const { ranges } = store.get()\n const written: [Y.Map<unknown>, CommentAnchor][] = []\n for (const { id, anchor } of threads) {\n const range = ranges.get(id)\n if (anchor || anchorTried.has(id) || !range) continue\n anchorTried.add(id)\n const thread = threadMap(ctx.map, id)\n const captured = thread && captureThreadAnchor(ctx.editor.state, resolver, range)\n if (thread && captured) written.push([thread, captured])\n }\n if (written.length) ctx.ydoc.transact(() => written.forEach(([thread, captured]) => thread.set('anchor', captured)))\n }\n\n // Highlights appear and disappear on every client as threads arrive, get resolved or deleted. The active thread\n // stays highlighted even when resolved (a resolved thread activated from its card), so a thread that turns resolved\n // or disappears while active is deactivated here, on every client, not only on the one that resolved or deleted it.\n // Queued (not dispatched from inside the Yjs observer or a transaction listener).\n function queueSync() {\n if (syncQueued) return\n syncQueued = true\n queueMicrotask(() => {\n syncQueued = false\n syncPlugin()\n })\n }\n\n function syncPlugin() {\n const instance = current()\n if (!instance || instance !== bound) return\n const value = commentsKey.getState(instance.state)\n if (!value) return\n const known = new Set(threads.map(t => t.id))\n const hidden = new Set(threads.filter(t => t.resolved).map(t => t.id))\n const meta: CommentsMeta = { known, hidden }\n const before = value.active ? told.find(t => t.id === value.active) : undefined\n if (value.active && before && !before.resolved && (!known.has(value.active) || hidden.has(value.active))) meta.active = null\n told = threads\n instance.view.dispatch(instance.state.tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n }\n\n /** Follows the editor `getEditor` names now, and the Y.Doc of its collaboration session. */\n function refresh() {\n const instance = current()\n if (instance !== bound) {\n unbind()\n unobserve()\n bound = instance\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n instance?.on('transaction', onTransaction)\n instance?.on('update', onTransaction)\n // The new editor's plugin knows no threads yet.\n queueSync()\n }\n const ydoc = instance ? commentsDoc(instance) : null\n if ((observed?.doc ?? null) !== ydoc) {\n unobserve()\n if (ydoc) {\n const map = ydoc.getMap<unknown>(COMMENTS_FIELD)\n observed = { doc: ydoc, map }\n map.observeDeep(onThreadsChanged)\n threads = [...map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n }\n else {\n threads = NO_THREADS\n }\n queueSync()\n }\n recompute()\n }\n\n function dispose() {\n unbind()\n unobserve()\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n store.set(EMPTY_SNAPSHOT)\n }\n\n /** Editor, thread map and user while comments are available; null otherwise. */\n function writable() {\n const instance = current()\n const ydoc = instance && commentsDoc(instance)\n const author = instance && commentsUser(instance)\n if (!instance || !ydoc || !author) return null\n return { editor: instance, ydoc, map: ydoc.getMap<unknown>(COMMENTS_FIELD), user: { id: author.id, name: author.name, color: author.color } }\n }\n\n /** Trimmed and cut to the limit; '' when nothing is left. */\n const clean = (text: string) => text.trim().slice(0, COMMENT_LIMITS.text)\n\n function threadMap(map: Y.Map<unknown>, threadId: string): Y.Map<unknown> | null {\n const thread = map.get(threadId)\n return thread instanceof Y.Map ? thread : null\n }\n\n function repliesOf(thread: Y.Map<unknown>): Y.Array<CommentReply> | null {\n const replies = thread.get('replies')\n return replies instanceof Y.Array ? replies as Y.Array<CommentReply> : null\n }\n\n function add(text: string): string | null {\n const ctx = writable()\n const body = clean(text)\n const anchor = ctx && commentsKey.getState(ctx.editor.state)?.draft\n const range = ctx && anchor ? resolveRange(ctx.editor.state, anchor) : null\n const type = ctx?.editor.schema.marks.comment\n if (!ctx || !anchor || !range || range.from >= range.to || !body || !type) return null\n const id = newBlockId()\n const { state } = ctx.editor\n const resolver = anchorResolverOf(ctx.editor)\n const threadAnchor = resolver ? captureThreadAnchor(state, resolver, range) : null\n const quote = state.doc.textBetween(range.from, range.to, ' ').slice(0, COMMENT_LIMITS.quote)\n const tr = state.tr.addMark(range.from, range.to, type.create({ id }))\n // Text that takes no marks (code block, inline code): no thread that would start out without a range.\n if (!tr.docChanged) return null\n // Not undoable: the mark is the thread's anchor, not an edit of the text.\n const meta: CommentsMeta = { draft: null, active: id }\n ctx.editor.view.dispatch(tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n releaseRanges(ctx.editor, [anchor])\n const thread = new Y.Map<unknown>()\n thread.set('id', id)\n thread.set('author', ctx.user)\n thread.set('text', body)\n thread.set('createdAt', new Date().toISOString())\n thread.set('quote', quote)\n thread.set('resolved', false)\n if (threadAnchor) thread.set('anchor', threadAnchor)\n thread.set('replies', new Y.Array<CommentReply>())\n ctx.map.set(id, thread)\n return id\n }\n\n function reply(threadId: string, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies || !body) return\n replies.push([{ id: newBlockId(), author: ctx.user, text: body, createdAt: new Date().toISOString() }])\n }\n\n function edit(threadId: string, replyId: string | null, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || !body) return\n const now = new Date().toISOString()\n if (replyId === null) {\n if ((thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id || thread.get('text') === body) return\n ctx.ydoc.transact(() => {\n thread.set('text', body)\n thread.set('editedAt', now)\n })\n return\n }\n const replies = repliesOf(thread)\n const index = replies ? replies.toArray().findIndex(r => r.id === replyId) : -1\n const previous = replies && index >= 0 ? replies.get(index) : undefined\n if (!replies || !previous || previous.author.id !== ctx.user.id || previous.text === body) return\n ctx.ydoc.transact(() => {\n replies.delete(index, 1)\n replies.insert(index, [{ ...previous, text: body, editedAt: now }])\n })\n }\n\n function setResolved(threadId: string, value: boolean): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread) return\n thread.set('resolved', value)\n if (value && commentsKey.getState(ctx.editor.state)?.active === threadId) setActiveComment(ctx.editor, null)\n }\n\n function remove(threadId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || (thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id) return\n const { state } = ctx.editor\n const tr = state.tr\n state.doc.descendants((node, pos) => {\n if (!node.isText) return\n const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.id === threadId)\n if (mark) tr.removeMark(pos, pos + node.nodeSize, mark)\n })\n if (commentsKey.getState(state)?.active === threadId) tr.setMeta(commentsKey, { active: null } satisfies CommentsMeta)\n if (tr.docChanged || tr.getMeta(commentsKey)) ctx.editor.view.dispatch(tr.setMeta('addToHistory', false))\n ctx.map.delete(threadId)\n }\n\n function removeReply(threadId: string, replyId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies) return\n const index = replies.toArray().findIndex(r => r.id === replyId)\n if (index >= 0 && replies.get(index).author.id === ctx.user.id) replies.delete(index, 1)\n }\n\n function activate(id: string | null, options: { reveal?: boolean } = {}): void {\n const instance = current()\n if (instance) setActiveComment(instance, id, options)\n }\n\n function startDraft(): void {\n const ctx = writable()\n if (ctx) startCommentDraft(ctx.editor)\n }\n\n function cancelDraft(): void {\n const instance = current()\n if (instance) cancelCommentDraft(instance)\n }\n\n return { state: store, refresh, dispose, add, reply, edit, setResolved, remove, removeReply, activate, startDraft, cancelDraft }\n}\n\n/** Creates the comments model of one editor, as a host supplies it to `commentsFeatureCore`. */\nexport type CommentsModelFactory = (getEditor: () => Editor | undefined) => CommentsModel\n\n// The host's model factory per surface context, set by the comments feature while the surface builds its extensions.\nconst modelFactories = new WeakMap<FeatureContext, CommentsModelFactory>()\n\n/** Registers the host model factory of `ctx` (the comments feature does this; hosts pass `model` to the feature). */\nexport function provideCommentsModel(ctx: FeatureContext, factory: CommentsModelFactory): void {\n modelFactories.set(ctx, factory)\n}\n\n/** The host model factory the comments feature of `ctx` was configured with; undefined for the default Y.Doc model. */\nexport function commentsModelOf(ctx: FeatureContext): CommentsModelFactory | undefined {\n return modelFactories.get(ctx)\n}\n\n/**\n * The comments model the framework hooks (`useComments`) use: the model of the surface `getEditor` belongs to\n * (`commentsModelOf` of its feature context: the host's `model` factory) and `createCommentsModel` for every other\n * editor. It follows the editor over time: when the editor changes to one of a surface with another model, the inner\n * model is replaced (disposed, created, refreshed) and `state` carries on with the new one. The factory runs once per\n * consumer of this function, so a host model that must share state across them keeps it outside the factory.\n */\nexport function createEditorCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY_SNAPSHOT)\n let inner: { model: CommentsModel, factory: CommentsModelFactory, stop: () => void } | undefined\n\n function release() {\n inner?.stop()\n inner?.model.dispose()\n inner = undefined\n }\n\n /** The inner model for the editor now; a replacement when its surface brings another factory. */\n function current(): CommentsModel {\n const entry = editorEntryOf(getEditor())\n const factory = (entry && commentsModelOf(entry.ctx)) ?? createCommentsModel\n if (inner?.factory !== factory) {\n release()\n const model = factory(getEditor)\n inner = { model, factory, stop: model.state.subscribe(snapshot => store.set(snapshot)) }\n store.set(model.state.get())\n }\n return inner!.model\n }\n\n return {\n state: store,\n refresh: () => current().refresh(),\n dispose: () => {\n release()\n store.set(EMPTY_SNAPSHOT)\n },\n add: text => current().add(text),\n reply: (threadId, text) => current().reply(threadId, text),\n edit: (threadId, replyId, text) => current().edit(threadId, replyId, text),\n setResolved: (threadId, resolved) => current().setResolved(threadId, resolved),\n remove: threadId => current().remove(threadId),\n removeReply: (threadId, replyId) => current().removeReply(threadId, replyId),\n activate: (id, options) => current().activate(id, options),\n startDraft: () => current().startDraft(),\n cancelDraft: () => current().cancelDraft(),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"model.js","names":[],"sources":["../../../src/features/comments/model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport * as Y from 'yjs'\nimport type { FeatureContext } from '../../core'\nimport type { CommentsModel, CommentsSnapshot, DocRange } from '../../core/backend-types'\nimport { anchorResolverOf, editorEntryOf, releaseRanges, resolveRange } from '../../core'\nimport { newBlockId } from '../../core/schema'\nimport { createStore } from '../../core/store'\nimport { cancelCommentDraft, commentsDoc, commentsKey, type CommentsMeta, commentsUser, setActiveComment, startCommentDraft } from './comments-plugin'\nimport { COMMENT_LIMITS, COMMENTS_FIELD, type CommentAnchor, type CommentReply, type CommentThread } from './schema'\nimport { EMPTY_SNAPSHOT, NO_POSITIONS, NO_RANGES, NO_THREADS, sameOrphanAt, sameRange, sameRanges, sameUser } from './snapshot-equality'\nimport { captureThreadAnchor, resolveThreadAnchor } from './thread-anchor'\n\nexport type { CommentsActions, CommentsModel, CommentsSnapshot, DocRange } from '../../core/backend-types'\n\n/** A thread's `Y.Map` as plain data; `replies` converted explicitly so it is always a plain array. */\nfunction snapshotOf(thread: Y.Map<unknown>): CommentThread {\n const replies = thread.get('replies')\n return { ...(thread.toJSON() as CommentThread), replies: replies instanceof Y.Array ? replies.toArray() as CommentReply[] : [] }\n}\n\n/**\n * The comment threads of a collaborative editor, stored in its Y.Doc (`COMMENTS_FIELD`, one nested `Y.Map` per thread,\n * replies in a nested `Y.Array`), so they sync live with the document and persist with its Yjs state. Keeps the\n * `Comments` plugin's `known`/`hidden` thread ids in step with the store (highlights), exposes the plugin's ranges,\n * active thread and draft, and the mutations. Mutations are no-ops without collaboration or user; editing and\n * deleting are limited to the user's own comments and replies (a client-side rule: the Y.Doc itself has no ACL).\n * `getEditor` names the editor to work on (it may change over time and may return a destroyed or no editor).\n * Each thread also keeps `anchor` (a Yjs relative position of its text's start, see `CommentAnchor`): `orphanAt` resolves\n * it for the threads whose text is gone. Threads from before anchors get theirs written once they are seen with a range.\n */\nexport function createCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY_SNAPSHOT)\n\n const current = () => {\n const instance = getEditor()\n return instance && !instance.isDestroyed ? instance : undefined\n }\n\n // The editor whose transactions are followed, and the Y.Map of threads observed.\n let bound: Editor | undefined\n let observed: { doc: Y.Doc, map: Y.Map<unknown> } | undefined\n let threads: readonly CommentThread[] = NO_THREADS\n let syncQueued = false\n // The thread list the plugin was last told about, for the deactivation rule.\n let told: readonly CommentThread[] = NO_THREADS\n // Threads already tried for an anchor (written, or not capturable), so a failed capture is not repeated on every change.\n const anchorTried = new Set<string>()\n let healQueued = false\n\n function unobserve() {\n observed?.map.unobserveDeep(onThreadsChanged)\n observed = undefined\n }\n\n function onThreadsChanged() {\n if (!observed) return\n threads = [...observed.map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n recompute()\n queueSync()\n }\n\n function onTransaction() {\n recompute()\n }\n\n function unbind() {\n bound?.off('transaction', onTransaction)\n // `setEditable` emits `update` only, and `canComment` follows `isEditable`.\n bound?.off('update', onTransaction)\n bound = undefined\n }\n\n /** The snapshot of the editor's current state; the previous object when nothing changed. */\n function recompute() {\n const instance = current()\n if (!instance) {\n store.set(EMPTY_SNAPSHOT)\n return\n }\n const previous = store.get()\n const ydoc = commentsDoc(instance)\n const user = commentsUser(instance)\n const plugin = commentsKey.getState(instance.state)\n const ranges = plugin?.ranges ?? NO_RANGES\n const orphanAt = orphanPositions(instance, ranges)\n const active = plugin?.active ?? null\n const anchor = plugin?.draft\n const draft = anchor ? resolveRange(instance.state, anchor) : null\n const draftQuote = draft ? instance.state.doc.textBetween(draft.from, draft.to, ' ').slice(0, COMMENT_LIMITS.quote) : ''\n const available = !!ydoc && !!user\n const canComment = available && instance.isEditable\n\n const sameThreads = threads === previous.threads\n const sameRangeMap = sameRanges(ranges, previous.ranges)\n const sameOrphanMap = sameOrphanAt(orphanAt, previous.orphanAt)\n if (\n available === previous.available && canComment === previous.canComment && sameUser(user, previous.user) && sameThreads && sameRangeMap && sameOrphanMap\n && active === previous.active && sameRange(draft, previous.draft) && draftQuote === previous.draftQuote\n ) return\n\n const kept = sameThreads && sameRangeMap\n const open = kept\n ? previous.open\n : threads\n .filter(t => !t.resolved)\n .sort((a, b) => (ranges.get(a.id)?.from ?? Infinity) - (ranges.get(b.id)?.from ?? Infinity) || a.createdAt.localeCompare(b.createdAt))\n const resolved = sameThreads\n ? previous.resolved\n : threads.filter(t => t.resolved).sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n store.set({\n available,\n canComment,\n user,\n threads,\n ranges: sameRangeMap ? previous.ranges : ranges,\n orphanAt: sameOrphanMap ? previous.orphanAt : orphanAt,\n active,\n draft: sameRange(draft, previous.draft) ? previous.draft : draft,\n draftQuote,\n open,\n resolved,\n })\n if (available) queueHeal()\n }\n\n /** Where each unresolved thread without a text range was, from its anchor; empty without a Yjs session. */\n function orphanPositions(instance: Editor, ranges: ReadonlyMap<string, DocRange>): ReadonlyMap<string, number> {\n const resolver = anchorResolverOf(instance)\n if (!resolver) return NO_POSITIONS\n const size = instance.state.doc.content.size\n let found: Map<string, number> | undefined\n for (const thread of threads) {\n if (thread.resolved || ranges.has(thread.id) || !thread.anchor) continue\n const position = resolveThreadAnchor(instance.state, resolver, thread.anchor)\n if (position !== null && position <= size) (found ??= new Map()).set(thread.id, position)\n }\n return found ?? NO_POSITIONS\n }\n\n // Threads from before anchors get theirs once they are seen with their text in the document (any client with a user\n // writes it; the positions are equivalent, so concurrent writes agree). Queued: not written from inside a Yjs\n // observer or a transaction listener.\n function queueHeal() {\n if (healQueued || !threads.some(t => !t.anchor && !anchorTried.has(t.id))) return\n healQueued = true\n queueMicrotask(() => {\n healQueued = false\n healAnchors()\n })\n }\n\n function healAnchors() {\n const ctx = writable()\n const resolver = ctx && anchorResolverOf(ctx.editor)\n if (!ctx || !resolver || ctx.editor !== bound) return\n const { ranges } = store.get()\n const written: [Y.Map<unknown>, CommentAnchor][] = []\n for (const { id, anchor } of threads) {\n const range = ranges.get(id)\n if (anchor || anchorTried.has(id) || !range) continue\n anchorTried.add(id)\n const thread = threadMap(ctx.map, id)\n const captured = thread && captureThreadAnchor(ctx.editor.state, resolver, range)\n if (thread && captured) written.push([thread, captured])\n }\n if (written.length) ctx.ydoc.transact(() => written.forEach(([thread, captured]) => thread.set('anchor', captured)))\n }\n\n // Highlights appear and disappear on every client as threads arrive, get resolved or deleted. The active thread\n // stays highlighted even when resolved (a resolved thread activated from its card), so a thread that turns resolved\n // or disappears while active is deactivated here, on every client, not only on the one that resolved or deleted it.\n // Queued (not dispatched from inside the Yjs observer or a transaction listener).\n function queueSync() {\n if (syncQueued) return\n syncQueued = true\n queueMicrotask(() => {\n syncQueued = false\n syncPlugin()\n })\n }\n\n function syncPlugin() {\n const instance = current()\n if (!instance || instance !== bound) return\n const value = commentsKey.getState(instance.state)\n if (!value) return\n const known = new Set(threads.map(t => t.id))\n const hidden = new Set(threads.filter(t => t.resolved).map(t => t.id))\n const meta: CommentsMeta = { known, hidden }\n const before = value.active ? told.find(t => t.id === value.active) : undefined\n if (value.active && before && !before.resolved && (!known.has(value.active) || hidden.has(value.active))) meta.active = null\n told = threads\n instance.view.dispatch(instance.state.tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n }\n\n /** Follows the editor `getEditor` names now, and the Y.Doc of its collaboration session. */\n function refresh() {\n const instance = current()\n if (instance !== bound) {\n unbind()\n unobserve()\n bound = instance\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n instance?.on('transaction', onTransaction)\n instance?.on('update', onTransaction)\n // The new editor's plugin knows no threads yet.\n queueSync()\n }\n const ydoc = instance ? commentsDoc(instance) : null\n if ((observed?.doc ?? null) !== ydoc) {\n unobserve()\n if (ydoc) {\n const map = ydoc.getMap<unknown>(COMMENTS_FIELD)\n observed = { doc: ydoc, map }\n map.observeDeep(onThreadsChanged)\n threads = [...map.values()].filter((t): t is Y.Map<unknown> => t instanceof Y.Map).map(snapshotOf)\n }\n else {\n threads = NO_THREADS\n }\n queueSync()\n }\n recompute()\n }\n\n function dispose() {\n unbind()\n unobserve()\n threads = NO_THREADS\n told = NO_THREADS\n anchorTried.clear()\n store.set(EMPTY_SNAPSHOT)\n }\n\n /** Editor, thread map and user while comments are available; null otherwise. */\n function writable() {\n const instance = current()\n const ydoc = instance && commentsDoc(instance)\n const author = instance && commentsUser(instance)\n if (!instance || !ydoc || !author) return null\n return { editor: instance, ydoc, map: ydoc.getMap<unknown>(COMMENTS_FIELD), user: { id: author.id, name: author.name, color: author.color } }\n }\n\n /** Trimmed and cut to the limit; '' when nothing is left. */\n const clean = (text: string) => text.trim().slice(0, COMMENT_LIMITS.text)\n\n function threadMap(map: Y.Map<unknown>, threadId: string): Y.Map<unknown> | null {\n const thread = map.get(threadId)\n return thread instanceof Y.Map ? thread : null\n }\n\n function repliesOf(thread: Y.Map<unknown>): Y.Array<CommentReply> | null {\n const replies = thread.get('replies')\n return replies instanceof Y.Array ? replies as Y.Array<CommentReply> : null\n }\n\n function add(text: string): string | null {\n const ctx = writable()\n const body = clean(text)\n const anchor = ctx && commentsKey.getState(ctx.editor.state)?.draft\n const range = ctx && anchor ? resolveRange(ctx.editor.state, anchor) : null\n const type = ctx?.editor.schema.marks.comment\n if (!ctx || !anchor || !range || range.from >= range.to || !body || !type) return null\n const id = newBlockId()\n const { state } = ctx.editor\n const resolver = anchorResolverOf(ctx.editor)\n const threadAnchor = resolver ? captureThreadAnchor(state, resolver, range) : null\n const quote = state.doc.textBetween(range.from, range.to, ' ').slice(0, COMMENT_LIMITS.quote)\n const tr = state.tr.addMark(range.from, range.to, type.create({ id }))\n // Text that takes no marks (code block, inline code): no thread that would start out without a range.\n if (!tr.docChanged) return null\n // Not undoable: the mark is the thread's anchor, not an edit of the text.\n const meta: CommentsMeta = { draft: null, active: id }\n ctx.editor.view.dispatch(tr.setMeta(commentsKey, meta).setMeta('addToHistory', false))\n releaseRanges(ctx.editor, [anchor])\n const thread = new Y.Map<unknown>()\n thread.set('id', id)\n thread.set('author', ctx.user)\n thread.set('text', body)\n thread.set('createdAt', new Date().toISOString())\n thread.set('quote', quote)\n thread.set('resolved', false)\n if (threadAnchor) thread.set('anchor', threadAnchor)\n thread.set('replies', new Y.Array<CommentReply>())\n ctx.map.set(id, thread)\n return id\n }\n\n function reply(threadId: string, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies || !body) return\n replies.push([{ id: newBlockId(), author: ctx.user, text: body, createdAt: new Date().toISOString() }])\n }\n\n function edit(threadId: string, replyId: string | null, text: string): void {\n const ctx = writable()\n const body = clean(text)\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || !body) return\n const now = new Date().toISOString()\n if (replyId === null) {\n if ((thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id || thread.get('text') === body) return\n ctx.ydoc.transact(() => {\n thread.set('text', body)\n thread.set('editedAt', now)\n })\n return\n }\n const replies = repliesOf(thread)\n const index = replies ? replies.toArray().findIndex(r => r.id === replyId) : -1\n const previous = replies && index >= 0 ? replies.get(index) : undefined\n if (!replies || !previous || previous.author.id !== ctx.user.id || previous.text === body) return\n ctx.ydoc.transact(() => {\n replies.delete(index, 1)\n replies.insert(index, [{ ...previous, text: body, editedAt: now }])\n })\n }\n\n function setResolved(threadId: string, value: boolean): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread) return\n thread.set('resolved', value)\n if (value && commentsKey.getState(ctx.editor.state)?.active === threadId) setActiveComment(ctx.editor, null)\n }\n\n function remove(threadId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n if (!ctx || !thread || (thread.get('author') as CommentThread['author'] | undefined)?.id !== ctx.user.id) return\n const { state } = ctx.editor\n const tr = state.tr\n state.doc.descendants((node, pos) => {\n if (!node.isText) return\n const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.id === threadId)\n if (mark) tr.removeMark(pos, pos + node.nodeSize, mark)\n })\n if (commentsKey.getState(state)?.active === threadId) tr.setMeta(commentsKey, { active: null } satisfies CommentsMeta)\n if (tr.docChanged || tr.getMeta(commentsKey)) ctx.editor.view.dispatch(tr.setMeta('addToHistory', false))\n ctx.map.delete(threadId)\n }\n\n function removeReply(threadId: string, replyId: string): void {\n const ctx = writable()\n const thread = ctx && threadMap(ctx.map, threadId)\n const replies = thread && repliesOf(thread)\n if (!ctx || !replies) return\n const index = replies.toArray().findIndex(r => r.id === replyId)\n if (index >= 0 && replies.get(index).author.id === ctx.user.id) replies.delete(index, 1)\n }\n\n function activate(id: string | null, options: { reveal?: boolean } = {}): void {\n const instance = current()\n if (instance) setActiveComment(instance, id, options)\n }\n\n function startDraft(): void {\n const ctx = writable()\n if (ctx) startCommentDraft(ctx.editor)\n }\n\n function cancelDraft(): void {\n const instance = current()\n if (instance) cancelCommentDraft(instance)\n }\n\n return { state: store, refresh, dispose, add, reply, edit, setResolved, remove, removeReply, activate, startDraft, cancelDraft }\n}\n\n/** Creates the comments model of one editor, as a host supplies it to `commentsFeatureCore`. */\nexport type CommentsModelFactory = (getEditor: () => Editor | undefined) => CommentsModel\n\n// The host's model factory per surface context, set by the comments feature while the surface builds its extensions.\nconst modelFactories = new WeakMap<FeatureContext, CommentsModelFactory>()\n\n/** Registers the host model factory of `ctx` (the comments feature does this; hosts pass `model` to the feature). */\nexport function provideCommentsModel(ctx: FeatureContext, factory: CommentsModelFactory): void {\n modelFactories.set(ctx, factory)\n}\n\n/** The host model factory the comments feature of `ctx` was configured with; undefined for the default Y.Doc model. */\nexport function commentsModelOf(ctx: FeatureContext): CommentsModelFactory | undefined {\n return modelFactories.get(ctx)\n}\n\n/**\n * The comments model the framework hooks (`useComments`) use: the model of the surface `getEditor` belongs to\n * (`commentsModelOf` of its feature context: the host's `model` factory) and `createCommentsModel` for every other\n * editor. It follows the editor over time: when the editor changes to one of a surface with another model, the inner\n * model is replaced (disposed, created, refreshed) and `state` carries on with the new one. The factory runs once per\n * consumer of this function, so a host model that must share state across them keeps it outside the factory.\n */\nexport function createEditorCommentsModel(getEditor: () => Editor | undefined): CommentsModel {\n const store = createStore<CommentsSnapshot>(EMPTY_SNAPSHOT)\n let inner: { model: CommentsModel, factory: CommentsModelFactory, stop: () => void } | undefined\n\n function release() {\n inner?.stop()\n inner?.model.dispose()\n inner = undefined\n }\n\n /** The inner model for the editor now; a replacement when its surface brings another factory. */\n function current(): CommentsModel {\n const entry = editorEntryOf(getEditor())\n const factory = (entry && commentsModelOf(entry.ctx)) ?? createCommentsModel\n if (inner?.factory !== factory) {\n release()\n const model = factory(getEditor)\n inner = { model, factory, stop: model.state.subscribe(snapshot => store.set(snapshot)) }\n store.set(model.state.get())\n }\n return inner!.model\n }\n\n return {\n state: store,\n refresh: () => current().refresh(),\n dispose: () => {\n release()\n store.set(EMPTY_SNAPSHOT)\n },\n add: text => current().add(text),\n reply: (threadId, text) => current().reply(threadId, text),\n edit: (threadId, replyId, text) => current().edit(threadId, replyId, text),\n setResolved: (threadId, resolved) => current().setResolved(threadId, resolved),\n remove: threadId => current().remove(threadId),\n removeReply: (threadId, replyId) => current().removeReply(threadId, replyId),\n activate: (id, options) => current().activate(id, options),\n startDraft: () => current().startDraft(),\n cancelDraft: () => current().cancelDraft(),\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AuthorIdentity } from "../../../core/feature.js";
|
|
2
|
+
import { CommentThread, CommentsActions } from "../../../core/backend-types.js";
|
|
2
3
|
import "../../../core/index.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import "../model.js";
|
|
5
|
+
import "../schema.js";
|
|
5
6
|
//#region src/features/comments/react/CommentThreadView.d.ts
|
|
6
7
|
/**
|
|
7
8
|
* One comment thread as a card: root comment (with the commented text, or „Textstelle gelöscht“ for an orphan),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CommentsActions, CommentsSnapshot } from "
|
|
1
|
+
import { CommentsActions, CommentsSnapshot } from "../../../core/backend-types.js";
|
|
2
|
+
import "../model.js";
|
|
2
3
|
import { Editor } from "@tiptap/core";
|
|
3
4
|
//#region src/features/comments/react/use-comments.d.ts
|
|
4
5
|
/** What `useComments` returns; layouts pass it on to `CommentThreadView`. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CommentsFeatureOptions } from "./feature.js";
|
|
2
1
|
import { ReactFeature } from "../../react/EditorSurface.js";
|
|
3
2
|
import "../../react/index.js";
|
|
3
|
+
import { CommentsFeatureOptions } from "./feature.js";
|
|
4
4
|
import { CommentBubble } from "./react/CommentBubble.js";
|
|
5
5
|
import { CommentComposer } from "./react/CommentComposer.js";
|
|
6
6
|
import { CommentsListMenu } from "./react/CommentsListMenu.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COMMENTS_LAYOUTS, CommentsLayout } from "../../settings/values.js";
|
|
2
|
-
import {
|
|
2
|
+
import { CommentAnchor, CommentAuthor, CommentReply, CommentThread } from "../../core/backend-types.js";
|
|
3
3
|
import "../../core/schema/mark-flags.js";
|
|
4
4
|
import { AnyExtension, Mark } from "@tiptap/core";
|
|
5
5
|
//#region src/features/comments/schema.d.ts
|
|
@@ -10,56 +10,6 @@ export declare const COMMENT_LIMITS: {
|
|
|
10
10
|
readonly text: 5000;
|
|
11
11
|
readonly quote: 200;
|
|
12
12
|
};
|
|
13
|
-
/** Who wrote a comment or reply: the writing user's identity, copied in when writing. */
|
|
14
|
-
export type CommentAuthor = AuthorIdentity;
|
|
15
|
-
export interface CommentReply {
|
|
16
|
-
id: string;
|
|
17
|
-
author: CommentAuthor;
|
|
18
|
-
text: string;
|
|
19
|
-
/** ISO timestamps; `editedAt` once the author changed the text. */
|
|
20
|
-
createdAt: string;
|
|
21
|
-
editedAt?: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Where a thread's text starts, as the JSON form of a Yjs relative position (`Y.relativePositionToJSON`): bound to the
|
|
25
|
-
* first commented character, so it keeps pointing at the place after that text was deleted (and moves with the text
|
|
26
|
-
* around it). Stored in the thread's `Y.Map` under `anchor`, so every collaborator resolves the same place.
|
|
27
|
-
*/
|
|
28
|
-
export interface CommentAnchor {
|
|
29
|
-
type?: {
|
|
30
|
-
client: number;
|
|
31
|
-
clock: number;
|
|
32
|
-
};
|
|
33
|
-
tname?: string;
|
|
34
|
-
item?: {
|
|
35
|
-
client: number;
|
|
36
|
-
clock: number;
|
|
37
|
-
};
|
|
38
|
-
assoc?: number;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* A comment thread: the root comment plus its replies. Stored as a nested `Y.Map` in `ydoc.getMap(COMMENTS_FIELD)`
|
|
42
|
-
* (replies as a nested `Y.Array`), so concurrent replies and edits of different users merge. Its text range is the
|
|
43
|
-
* `comment` mark with the thread's `id`.
|
|
44
|
-
*/
|
|
45
|
-
export interface CommentThread {
|
|
46
|
-
id: string;
|
|
47
|
-
author: CommentAuthor;
|
|
48
|
-
text: string;
|
|
49
|
-
createdAt: string;
|
|
50
|
-
editedAt?: string;
|
|
51
|
-
/** The commented text when the thread was created, cut to `COMMENT_LIMITS.quote`. */
|
|
52
|
-
quote: string;
|
|
53
|
-
/** Resolved threads are kept, without highlight, until reopened. */
|
|
54
|
-
resolved: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* Start of the commented text as a Yjs relative position, written when the thread is created (older threads get it
|
|
57
|
-
* once they are seen with their text still in the document). Tells where an orphaned thread's text was. Absent on threads
|
|
58
|
-
* not yet seen with a range, and when the position could not be captured.
|
|
59
|
-
*/
|
|
60
|
-
anchor?: CommentAnchor;
|
|
61
|
-
replies: CommentReply[];
|
|
62
|
-
}
|
|
63
13
|
/**
|
|
64
14
|
* Anchors a comment thread to text (attribute `id` = the thread's id). Storage only: the visible highlight is a
|
|
65
15
|
* client-side decoration driven by the thread's state (`Comments` extension). `excludes: ''` lets the marks of
|
|
@@ -70,5 +20,5 @@ export declare const CommentMark: Mark<any, any>;
|
|
|
70
20
|
/** The comment mark of the document schema. */
|
|
71
21
|
export declare const commentsSchemaExtensions: () => AnyExtension[];
|
|
72
22
|
//#endregion
|
|
73
|
-
export { COMMENTS_LAYOUTS, type CommentsLayout };
|
|
23
|
+
export { COMMENTS_LAYOUTS, type CommentAnchor, type CommentAuthor, type CommentReply, type CommentThread, type CommentsLayout };
|
|
74
24
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/comments/schema.ts"],"sourcesContent":["import { type AnyExtension, Mark, mergeAttributes } from '@tiptap/core'\nimport
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/comments/schema.ts"],"sourcesContent":["import { type AnyExtension, Mark, mergeAttributes } from '@tiptap/core'\nimport '../../core/schema/mark-flags'\n\n// Vue-free and server-importable (`@ecsplico/editor/comments/schema`): the comment mark that anchors a thread to\n// text, the thread data model and the limits. The Y.Map store, the highlights and the UI live in the feature.\n\nexport { COMMENTS_LAYOUTS, type CommentsLayout } from '../../settings/values'\n\n/** Name of the `Y.Map` holding the comment threads in the Y.Doc, beside the document's 'default' XmlFragment. */\nexport const COMMENTS_FIELD = 'comments'\n\n/** Maximum lengths of a comment's or reply's text and of the quoted text a thread keeps of its range. */\nexport const COMMENT_LIMITS = { text: 5000, quote: 200 } as const\n\n// The thread data model (`CommentThread`, its replies, author and anchor) is core's, because `EditorBackend.comments`\n// returns a model of them; this entry exports the types as before.\nexport type { CommentAnchor, CommentAuthor, CommentReply, CommentThread } from '../../core/backend-types'\n\n/**\n * Anchors a comment thread to text (attribute `id` = the thread's id). Storage only: the visible highlight is a\n * client-side decoration driven by the thread's state (`Comments` extension). `excludes: ''` lets the marks of\n * overlapping threads coexist on the same text; `inclusive: false` keeps text typed at the end of a range outside it.\n * No Markdown form (`markdownInvisible`).\n */\nexport const CommentMark = Mark.create({\n name: 'comment',\n excludes: '',\n inclusive: false,\n markdownInvisible: true,\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: el => el.getAttribute('data-comment-id'),\n renderHTML: attrs => ({ 'data-comment-id': attrs.id }),\n },\n }\n },\n parseHTML: () => [{ tag: 'span[data-comment-id]' }],\n renderHTML: ({ HTMLAttributes }) => ['span', mergeAttributes(HTMLAttributes, { class: 'comment' }), 0],\n})\n\n/** The comment mark of the document schema. */\nexport const commentsSchemaExtensions = (): AnyExtension[] => [CommentMark]\n"],"mappings":";;;;AASA,MAAa,iBAAiB;;AAG9B,MAAa,iBAAiB;CAAE,MAAM;CAAM,OAAO;AAAI;;;;;;;AAYvD,MAAa,cAAc,KAAK,OAAO;CACrC,MAAM;CACN,UAAU;CACV,WAAW;CACX,mBAAmB;CACnB,gBAAgB;EACd,OAAO,EACL,IAAI;GACF,SAAS;GACT,YAAW,OAAM,GAAG,aAAa,iBAAiB;GAClD,aAAY,WAAU,EAAE,mBAAmB,MAAM,GAAG;EACtD,EACF;CACF;CACA,iBAAiB,CAAC,EAAE,KAAK,wBAAwB,CAAC;CAClD,aAAa,EAAE,qBAAqB;EAAC;EAAQ,gBAAgB,gBAAgB,EAAE,OAAO,UAAU,CAAC;EAAG;CAAC;AACvG,CAAC;;AAGD,MAAa,iCAAiD,CAAC,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-equality.js","names":[],"sources":["../../../src/features/comments/snapshot-equality.ts"],"sourcesContent":["import type { AuthorIdentity } from '../../core'\nimport type { CommentsSnapshot, DocRange } from './model'\nimport type { CommentThread } from './schema'\n\n// What both comments models (the Y.Doc one in `./model` and the REST one in `adapters/comments/
|
|
1
|
+
{"version":3,"file":"snapshot-equality.js","names":[],"sources":["../../../src/features/comments/snapshot-equality.ts"],"sourcesContent":["import type { AuthorIdentity } from '../../core'\nimport type { CommentsSnapshot, DocRange } from './model'\nimport type { CommentThread } from './schema'\n\n// What both comments models (the Y.Doc one in `./model` and the REST one in `adapters/comments/rest-model`) need to hand\n// out a snapshot that keeps its identity while nothing in it changed. Type-only imports: this module stays free of `yjs`.\n\nexport const NO_RANGES: ReadonlyMap<string, DocRange> = new Map()\nexport const NO_THREADS: readonly CommentThread[] = []\nexport const NO_POSITIONS: ReadonlyMap<string, number> = new Map()\n\n/** The snapshot of an editor that has no comments (yet). */\nexport const EMPTY_SNAPSHOT: CommentsSnapshot = {\n available: false,\n canComment: false,\n user: null,\n threads: NO_THREADS,\n ranges: NO_RANGES,\n orphanAt: NO_POSITIONS,\n active: null,\n draft: null,\n draftQuote: '',\n open: NO_THREADS,\n resolved: NO_THREADS,\n}\n\nexport function sameRange(a: DocRange | null, b: DocRange | null): boolean {\n return a === b || (!!a && !!b && a.from === b.from && a.to === b.to)\n}\n\nexport function sameRanges(a: ReadonlyMap<string, DocRange>, b: ReadonlyMap<string, DocRange>): boolean {\n if (a === b) return true\n if (a.size !== b.size) return false\n for (const [id, range] of a) {\n if (!sameRange(range, b.get(id) ?? null)) return false\n }\n return true\n}\n\nexport function sameOrphanAt(a: ReadonlyMap<string, number>, b: ReadonlyMap<string, number>): boolean {\n if (a === b) return true\n if (a.size !== b.size) return false\n for (const [id, position] of a) {\n if (b.get(id) !== position) return false\n }\n return true\n}\n\nexport function sameUser(a: AuthorIdentity | null, b: AuthorIdentity | null): boolean {\n return a === b || (!!a && !!b && a.id === b.id && a.name === b.name && a.color === b.color)\n}\n"],"mappings":";AAOA,MAAa,4BAA2C,IAAI,IAAI;AAChE,MAAa,aAAuC,CAAC;AACrD,MAAa,+BAA4C,IAAI,IAAI;;AAGjE,MAAa,iBAAmC;CAC9C,WAAW;CACX,YAAY;CACZ,MAAM;CACN,SAAS;CACT,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,OAAO;CACP,YAAY;CACZ,MAAM;CACN,UAAU;AACZ;AAEA,SAAgB,UAAU,GAAoB,GAA6B;CACzE,OAAO,MAAM,KAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE;AACnE;AAEA,SAAgB,WAAW,GAAkC,GAA2C;CACtG,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,SAAS,EAAE,MAAM,OAAO;CAC9B,KAAK,MAAM,CAAC,IAAI,UAAU,GACxB,IAAI,CAAC,UAAU,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,OAAO;CAEnD,OAAO;AACT;AAEA,SAAgB,aAAa,GAAgC,GAAyC;CACpG,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,SAAS,EAAE,MAAM,OAAO;CAC9B,KAAK,MAAM,CAAC,IAAI,aAAa,GAC3B,IAAI,EAAE,IAAI,EAAE,MAAM,UAAU,OAAO;CAErC,OAAO;AACT;AAEA,SAAgB,SAAS,GAA0B,GAAmC;CACpF,OAAO,MAAM,KAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE;AACvF"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AuthorIdentity } from "../../core/feature.js";
|
|
2
|
+
import { CommentThread, CommentsActions, DocRange } from "../../core/backend-types.js";
|
|
2
3
|
import "../../core/index.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import "./model.js";
|
|
5
|
+
import "./schema.js";
|
|
5
6
|
import { Editor } from "@tiptap/core";
|
|
6
7
|
import { ComputedRef, MaybeRefOrGetter, Ref } from "vue";
|
|
7
8
|
//#region src/features/comments/use-comments.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CommentsFeatureOptions } from "./feature.js";
|
|
2
1
|
import { VueFeature } from "../../vue/index.js";
|
|
2
|
+
import { CommentsFeatureOptions } from "./feature.js";
|
|
3
3
|
import _default from "./components/CommentBubble.vue.js";
|
|
4
4
|
import _default$1 from "./components/CommentComposer.vue.js";
|
|
5
5
|
import _default$3 from "./components/CommentsListMenu.vue.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Translate } from "../../i18n/index.js";
|
|
2
2
|
import { Notifier } from "../../core/notify.js";
|
|
3
|
+
import "../../core/command-types.js";
|
|
3
4
|
import { Extension } from "@tiptap/core";
|
|
4
5
|
import { Node as Node$1 } from "@tiptap/pm/model";
|
|
5
6
|
//#region src/features/composites/accepted-children.d.ts
|
|
@@ -50,6 +50,21 @@ function nestingViolations(doc, kinds) {
|
|
|
50
50
|
walk(doc, null);
|
|
51
51
|
return found;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The insert guard of the composites feature: a Baustein of kind `creates.attrs.kind` may be created only where the
|
|
55
|
+
* nearest Baustein around the insertion point (`$parent` and outward; quotes and lists in between do not count)
|
|
56
|
+
* accepts that kind, or where there is none. Uses `kindAccepts`, the rule `AcceptedChildren` enforces on transactions
|
|
57
|
+
* through `nestingViolations`, so the `/` menu and toolbar offer exactly what an edit would not be refused for.
|
|
58
|
+
*/
|
|
59
|
+
const compositeInsertGuard = (editor, $parent, creates) => {
|
|
60
|
+
const child = creates.attrs?.kind;
|
|
61
|
+
if (creates.type !== "composite" || typeof child !== "string") return true;
|
|
62
|
+
for (let depth = $parent.depth; depth >= 0; depth--) {
|
|
63
|
+
const node = $parent.node(depth);
|
|
64
|
+
if (node.type.name === "composite") return kindAccepts(compositeKindsOf(editor), String(node.attrs.kind), child);
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
53
68
|
/** A step that cannot change which Baustein sits in which: marks, text typed or deleted inside one textblock, attributes other than `kind`. */
|
|
54
69
|
function keepsNesting(step, before) {
|
|
55
70
|
if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) return true;
|
|
@@ -121,6 +136,6 @@ const AcceptedChildren = Extension.create({
|
|
|
121
136
|
}
|
|
122
137
|
});
|
|
123
138
|
//#endregion
|
|
124
|
-
export { AcceptedChildren, nestingReason, nestingViolations, outermostComposites };
|
|
139
|
+
export { AcceptedChildren, compositeInsertGuard, nestingReason, nestingViolations, outermostComposites };
|
|
125
140
|
|
|
126
141
|
//# sourceMappingURL=accepted-children.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accepted-children.js","names":[],"sources":["../../../src/features/composites/accepted-children.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey, type Transaction } from '@tiptap/pm/state'\nimport { AddMarkStep, AttrStep, RemoveMarkStep, ReplaceStep, type Step } from '@tiptap/pm/transform'\nimport type { Notifier } from '../../core/notify'\nimport { createTranslator, type Translate } from '../../i18n'\nimport { type CompositeKind, compositeKind, kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein nested in a Baustein that does not accept its kind (both kind ids). */\nexport interface NestingViolation {\n parent: string\n child: string\n}\n\n/** The short sentence for a refused nesting, naming both kinds by label (German: „Notiz nimmt keine Aufgabe auf.“). */\nexport function nestingReason(kinds: readonly CompositeKind[], parent: string, child: string, t: Translate): string {\n return t('composites.nesting.refused', { parent: compositeKind(kinds, parent).label, child: compositeKind(kinds, child).label })\n}\n\n/**\n * The Bausteine of `node`'s subtree, outermost first: `node` itself when it is a Baustein, otherwise the Bausteine\n * directly inside it (not those nested in another Baustein).\n */\nexport function outermostComposites(node: PMNode): PMNode[] {\n if (node.type.name === 'composite') return [node]\n const found: PMNode[] = []\n node.descendants((child) => {\n if (child.type.name === 'composite') {\n found.push(child)\n return false\n }\n return !child.isTextblock\n })\n return found\n}\n\n/**\n * Every Baustein in `doc` whose nearest Baustein ancestor (quotes and lists in between do not count) does not accept\n * its kind, in document order. Top-level Bausteine have no parent and never violate.\n */\nexport function nestingViolations(doc: PMNode, kinds: readonly CompositeKind[]): NestingViolation[] {\n const found: NestingViolation[] = []\n function walk(node: PMNode, parentKind: string | null) {\n node.forEach((child) => {\n if (child.isTextblock || child.isLeaf) return\n if (child.type.name === 'composite') {\n const kind = String(child.attrs.kind)\n if (parentKind !== null && !kindAccepts(kinds, parentKind, kind)) found.push({ parent: parentKind, child: kind })\n walk(child, kind)\n }\n else walk(child, parentKind)\n })\n }\n walk(doc, null)\n return found\n}\n\n/** A step that cannot change which Baustein sits in which: marks, text typed or deleted inside one textblock, attributes other than `kind`. */\nfunction keepsNesting(step: Step, before: PMNode): boolean {\n if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) return true\n if (step instanceof AttrStep) return step.attr !== 'kind'\n if (step instanceof ReplaceStep) {\n const { slice } = step\n if (slice.openStart !== 0 || slice.openEnd !== 0 || !slice.content.content.every(child => child.isInline)) return false\n const $from = before.resolve(step.from)\n return $from.parent.isTextblock && $from.sameParent(before.resolve(step.to))\n }\n return false\n}\n\n/**\n * Transactions that were not made by this user's edit: collaboration (y-sync marks remote changes and Yjs undo/redo\n * with `isChangeOrigin`) and prosemirror-history's undo/redo. They replay what was allowed, or what others wrote.\n */\nfunction isReplay(tr: Transaction): boolean {\n const sync = tr.getMeta('y-sync$') as { isChangeOrigin?: boolean } | undefined\n return sync?.isChangeOrigin === true || tr.getMeta('history$') !== undefined\n}\n\n/** The pair of kinds that occurs more often in `after` than in `before`, if any. */\nfunction addedViolation(before: readonly NestingViolation[], after: readonly NestingViolation[]): NestingViolation | undefined {\n const counts = new Map<string, number>()\n const key = (v: NestingViolation) => JSON.stringify([v.parent, v.child])\n for (const v of before) counts.set(key(v), (counts.get(key(v)) ?? 0) + 1)\n for (const v of after) {\n const left = counts.get(key(v)) ?? 0\n if (left === 0) return v\n counts.set(key(v), left - 1)\n }\n return after[0]\n}\n\nexport interface AcceptedChildrenOptions {\n /** Shows the notice of a refused edit (default: dropped). The composites feature passes the surface's `ctx.notify`. */\n notify: Notifier\n /** The translator of the notice. The composites feature passes the surface's `ctx.t`. */\n t: Translate\n}\n\n/**\n * Enforces the kinds' accepted children (`CompositeKind.accepts`) for every local edit at one place: a transaction that\n * leaves the document with more Bausteine nested in a Baustein that does not accept them than before is refused — block\n * drag, „In Baustein umwandeln“, paste, AI inserts and review acceptance, `moveBlockInto` — and the user is told why.\n * Existing violations stay: transactions that add none (typing, undoing) pass, and so do collaboration replays, undo/redo\n * and loading a whole document. Client-only, no schema: kinds are read from the editor (`compositeKindsOf`).\n */\nexport const AcceptedChildren = Extension.create<AcceptedChildrenOptions>({\n name: 'acceptedChildren',\n addOptions: () => ({ notify: () => {}, t: createTranslator() }),\n addProseMirrorPlugins() {\n const { editor } = this\n const { notify, t } = this.options\n return [new Plugin({\n key: new PluginKey('acceptedChildren'),\n filterTransaction(tr, state) {\n if (!tr.docChanged || isReplay(tr)) return true\n // `setContent` loads a whole new document; that is not an edit of the existing one.\n if (tr.steps.some((step, index) => step instanceof ReplaceStep && step.from === 0 && step.to === tr.docs[index]!.content.size)) return true\n if (tr.steps.every((step, index) => keepsNesting(step, tr.docs[index]!))) return true\n const kinds = compositeKindsOf(editor)\n const before = nestingViolations(state.doc, kinds)\n const after = nestingViolations(tr.doc, kinds)\n if (after.length <= before.length) return true\n const added = addedViolation(before, after)\n notify({\n id: 'composites-accepted-children',\n title: t('composites.nesting.title'),\n ...(added && { description: nestingReason(kinds, added.parent, added.child, t) }),\n kind: 'warning',\n icon: 'i-lucide-ban',\n })\n return false\n },\n })]\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accepted-children.js","names":[],"sources":["../../../src/features/composites/accepted-children.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey, type Transaction } from '@tiptap/pm/state'\nimport { AddMarkStep, AttrStep, RemoveMarkStep, ReplaceStep, type Step } from '@tiptap/pm/transform'\nimport type { InsertGuard } from '../../core/command-types'\nimport type { Notifier } from '../../core/notify'\nimport { createTranslator, type Translate } from '../../i18n'\nimport { type CompositeKind, compositeKind, kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein nested in a Baustein that does not accept its kind (both kind ids). */\nexport interface NestingViolation {\n parent: string\n child: string\n}\n\n/** The short sentence for a refused nesting, naming both kinds by label (German: „Notiz nimmt keine Aufgabe auf.“). */\nexport function nestingReason(kinds: readonly CompositeKind[], parent: string, child: string, t: Translate): string {\n return t('composites.nesting.refused', { parent: compositeKind(kinds, parent).label, child: compositeKind(kinds, child).label })\n}\n\n/**\n * The Bausteine of `node`'s subtree, outermost first: `node` itself when it is a Baustein, otherwise the Bausteine\n * directly inside it (not those nested in another Baustein).\n */\nexport function outermostComposites(node: PMNode): PMNode[] {\n if (node.type.name === 'composite') return [node]\n const found: PMNode[] = []\n node.descendants((child) => {\n if (child.type.name === 'composite') {\n found.push(child)\n return false\n }\n return !child.isTextblock\n })\n return found\n}\n\n/**\n * Every Baustein in `doc` whose nearest Baustein ancestor (quotes and lists in between do not count) does not accept\n * its kind, in document order. Top-level Bausteine have no parent and never violate.\n */\nexport function nestingViolations(doc: PMNode, kinds: readonly CompositeKind[]): NestingViolation[] {\n const found: NestingViolation[] = []\n function walk(node: PMNode, parentKind: string | null) {\n node.forEach((child) => {\n if (child.isTextblock || child.isLeaf) return\n if (child.type.name === 'composite') {\n const kind = String(child.attrs.kind)\n if (parentKind !== null && !kindAccepts(kinds, parentKind, kind)) found.push({ parent: parentKind, child: kind })\n walk(child, kind)\n }\n else walk(child, parentKind)\n })\n }\n walk(doc, null)\n return found\n}\n\n/**\n * The insert guard of the composites feature: a Baustein of kind `creates.attrs.kind` may be created only where the\n * nearest Baustein around the insertion point (`$parent` and outward; quotes and lists in between do not count)\n * accepts that kind, or where there is none. Uses `kindAccepts`, the rule `AcceptedChildren` enforces on transactions\n * through `nestingViolations`, so the `/` menu and toolbar offer exactly what an edit would not be refused for.\n */\nexport const compositeInsertGuard: InsertGuard = (editor, $parent, creates) => {\n const child = creates.attrs?.kind\n if (creates.type !== 'composite' || typeof child !== 'string') return true\n for (let depth = $parent.depth; depth >= 0; depth--) {\n const node = $parent.node(depth)\n if (node.type.name === 'composite') return kindAccepts(compositeKindsOf(editor), String(node.attrs.kind), child)\n }\n return true\n}\n\n/** A step that cannot change which Baustein sits in which: marks, text typed or deleted inside one textblock, attributes other than `kind`. */\nfunction keepsNesting(step: Step, before: PMNode): boolean {\n if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) return true\n if (step instanceof AttrStep) return step.attr !== 'kind'\n if (step instanceof ReplaceStep) {\n const { slice } = step\n if (slice.openStart !== 0 || slice.openEnd !== 0 || !slice.content.content.every(child => child.isInline)) return false\n const $from = before.resolve(step.from)\n return $from.parent.isTextblock && $from.sameParent(before.resolve(step.to))\n }\n return false\n}\n\n/**\n * Transactions that were not made by this user's edit: collaboration (y-sync marks remote changes and Yjs undo/redo\n * with `isChangeOrigin`) and prosemirror-history's undo/redo. They replay what was allowed, or what others wrote.\n */\nfunction isReplay(tr: Transaction): boolean {\n const sync = tr.getMeta('y-sync$') as { isChangeOrigin?: boolean } | undefined\n return sync?.isChangeOrigin === true || tr.getMeta('history$') !== undefined\n}\n\n/** The pair of kinds that occurs more often in `after` than in `before`, if any. */\nfunction addedViolation(before: readonly NestingViolation[], after: readonly NestingViolation[]): NestingViolation | undefined {\n const counts = new Map<string, number>()\n const key = (v: NestingViolation) => JSON.stringify([v.parent, v.child])\n for (const v of before) counts.set(key(v), (counts.get(key(v)) ?? 0) + 1)\n for (const v of after) {\n const left = counts.get(key(v)) ?? 0\n if (left === 0) return v\n counts.set(key(v), left - 1)\n }\n return after[0]\n}\n\nexport interface AcceptedChildrenOptions {\n /** Shows the notice of a refused edit (default: dropped). The composites feature passes the surface's `ctx.notify`. */\n notify: Notifier\n /** The translator of the notice. The composites feature passes the surface's `ctx.t`. */\n t: Translate\n}\n\n/**\n * Enforces the kinds' accepted children (`CompositeKind.accepts`) for every local edit at one place: a transaction that\n * leaves the document with more Bausteine nested in a Baustein that does not accept them than before is refused — block\n * drag, „In Baustein umwandeln“, paste, AI inserts and review acceptance, `moveBlockInto` — and the user is told why.\n * Existing violations stay: transactions that add none (typing, undoing) pass, and so do collaboration replays, undo/redo\n * and loading a whole document. Client-only, no schema: kinds are read from the editor (`compositeKindsOf`).\n */\nexport const AcceptedChildren = Extension.create<AcceptedChildrenOptions>({\n name: 'acceptedChildren',\n addOptions: () => ({ notify: () => {}, t: createTranslator() }),\n addProseMirrorPlugins() {\n const { editor } = this\n const { notify, t } = this.options\n return [new Plugin({\n key: new PluginKey('acceptedChildren'),\n filterTransaction(tr, state) {\n if (!tr.docChanged || isReplay(tr)) return true\n // `setContent` loads a whole new document; that is not an edit of the existing one.\n if (tr.steps.some((step, index) => step instanceof ReplaceStep && step.from === 0 && step.to === tr.docs[index]!.content.size)) return true\n if (tr.steps.every((step, index) => keepsNesting(step, tr.docs[index]!))) return true\n const kinds = compositeKindsOf(editor)\n const before = nestingViolations(state.doc, kinds)\n const after = nestingViolations(tr.doc, kinds)\n if (after.length <= before.length) return true\n const added = addedViolation(before, after)\n notify({\n id: 'composites-accepted-children',\n title: t('composites.nesting.title'),\n ...(added && { description: nestingReason(kinds, added.parent, added.child, t) }),\n kind: 'warning',\n icon: 'i-lucide-ban',\n })\n return false\n },\n })]\n },\n})\n"],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { findBlockById } from "../../core/schema/block-ids.js";
|
|
1
2
|
import { compositeKind } from "./kinds.js";
|
|
3
|
+
import { derivationOf } from "./derivation.js";
|
|
2
4
|
import { compositeKindsOf } from "./nodes.js";
|
|
5
|
+
import { citeFull } from "./bibliography.js";
|
|
3
6
|
//#region src/features/composites/ai-brief.ts
|
|
4
7
|
const PLACEHOLDER = /\{([\p{L}\p{N}_-]+)\}/gu;
|
|
5
8
|
const isFilled = (value) => typeof value === "string" && value !== "" || typeof value === "number";
|
|
@@ -21,6 +24,42 @@ function compositeAiBrief(editor, pos) {
|
|
|
21
24
|
const meta = node.attrs.meta ?? {};
|
|
22
25
|
const fields = kind.fields.flatMap((field) => {
|
|
23
26
|
const raw = meta[field.key];
|
|
27
|
+
if (field.type === "bibliography") {
|
|
28
|
+
const bib = raw;
|
|
29
|
+
return bib && typeof bib === "object" && typeof bib.type === "string" ? [{
|
|
30
|
+
key: field.key,
|
|
31
|
+
label: field.label,
|
|
32
|
+
value: citeFull({
|
|
33
|
+
...bib,
|
|
34
|
+
title: node.child(0).textContent
|
|
35
|
+
})
|
|
36
|
+
}] : [];
|
|
37
|
+
}
|
|
38
|
+
if (field.type === "reference") {
|
|
39
|
+
const target = typeof raw === "string" && raw ? findBlockById(editor.state.doc, raw)?.node : void 0;
|
|
40
|
+
const title = target?.type.name === "composite" ? target.child(0).textContent.trim() : "";
|
|
41
|
+
return title ? [{
|
|
42
|
+
key: field.key,
|
|
43
|
+
label: field.label,
|
|
44
|
+
value: title
|
|
45
|
+
}] : [];
|
|
46
|
+
}
|
|
47
|
+
if (field.type === "derivation") {
|
|
48
|
+
const derivation = derivationOf(node);
|
|
49
|
+
return derivation ? [{
|
|
50
|
+
key: field.key,
|
|
51
|
+
label: field.label,
|
|
52
|
+
value: `${derivation.method} from ${"asset" in derivation.from ? derivation.from.asset : derivation.from.block}`
|
|
53
|
+
}] : [];
|
|
54
|
+
}
|
|
55
|
+
if (field.type === "multiselect") {
|
|
56
|
+
const value = (Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : []).map((item) => field.options?.find((option) => option.value === item)?.label ?? item).join(", ");
|
|
57
|
+
return value ? [{
|
|
58
|
+
key: field.key,
|
|
59
|
+
label: field.label,
|
|
60
|
+
value
|
|
61
|
+
}] : [];
|
|
62
|
+
}
|
|
24
63
|
if (!isFilled(raw)) return [];
|
|
25
64
|
const value = String(raw);
|
|
26
65
|
return [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-brief.js","names":[],"sources":["../../../src/features/composites/ai-brief.ts"],"sourcesContent":["// Vue-free: what one Baustein tells the model (read by „Baustein füllen (KI)“, see `integrations/composite-ai`).\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport { compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT, compositeKindsOf } from './nodes'\n\n/** What one Baustein tells the model: kind brief, place in the hierarchy, chrome, field values, resolved instruction and skeleton, body. */\nexport interface CompositeAiBrief {\n kind: { id: string, label: string, purpose?: string }\n /** Titles of the enclosing composites, outermost first (empty at document level). */\n path: string[]\n title: string\n description: string\n tags: string[]\n /** Declared fields with a non-empty value, `select` values as their option label. */\n fields: { key: string, label: string, value: string }[]\n /** `meta[ai.instructionsField]` when non-empty, else `ai.instructions`, else ''. */\n instructions: string\n /** `ai.skeleton` with `{key}` replaced by the string value of `meta[key]` (declared fields only); '' without a skeleton. */\n skeleton: string\n /** Body blocks as Markdown ('' for a body without text). */\n body: string\n}\n\nconst PLACEHOLDER = /\\{([\\p{L}\\p{N}_-]+)\\}/gu\n\nconst isFilled = (value: unknown): value is string | number => (typeof value === 'string' && value !== '') || typeof value === 'number'\n\n/** The brief of the composite at `pos`; null unless the node there is a composite whose kind has `ai`. */\nexport function compositeAiBrief(editor: Editor, pos: number): CompositeAiBrief | null {\n const node = editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'composite' || node.childCount < COMPOSITE_CHROME_COUNT) return null\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n const ai = kind.ai\n if (!ai) return null\n\n const $pos = editor.state.doc.resolve(pos)\n const path: string[] = []\n for (let depth = 1; depth <= $pos.depth; depth++) {\n const ancestor = $pos.node(depth)\n if (ancestor.type.name === 'composite') path.push(ancestor.child(0).textContent.trim())\n }\n\n const tags: unknown = node.child(2).attrs.tags\n const body = Array.from({ length: node.childCount - COMPOSITE_CHROME_COUNT }, (_, i) => node.child(COMPOSITE_CHROME_COUNT + i))\n const meta: Record<string, unknown> = node.attrs.meta ?? {}\n\n const fields = kind.fields.flatMap((field) => {\n const raw = meta[field.key]\n if (!isFilled(raw)) return []\n const value = String(raw)\n return [{ key: field.key, label: field.label, value: field.type === 'select' ? (field.options?.find(option => option.value === value)?.label ?? value) : value }]\n })\n const keys = new Set(kind.fields.map(field => field.key))\n const own = ai.instructionsField ? String(meta[ai.instructionsField] ?? '').trim() : ''\n\n return {\n kind: { id: kind.id, label: kind.label, ...(ai.purpose && { purpose: ai.purpose }) },\n path,\n title: node.child(0).textContent,\n description: node.child(1).textContent,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n fields,\n instructions: own || ai.instructions?.trim() || '',\n skeleton: ai.skeleton?.replace(PLACEHOLDER, (match, key: string) => keys.has(key) && isFilled(meta[key]) ? String(meta[key]) : match) ?? '',\n body: body.some(child => child.textContent.trim())\n ? editor.markdown!.serialize({ type: 'doc', content: body.map(child => child.toJSON() as JSONContent) }).trim()\n : '',\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ai-brief.js","names":[],"sources":["../../../src/features/composites/ai-brief.ts"],"sourcesContent":["// Vue-free: what one Baustein tells the model (read by „Baustein füllen (KI)“, see `integrations/composite-ai`).\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport { type CslItem, citeFull } from './bibliography'\nimport { derivationOf } from './derivation'\nimport { compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT, compositeKindsOf } from './nodes'\n\n/** What one Baustein tells the model: kind brief, place in the hierarchy, chrome, field values, resolved instruction and skeleton, body. */\nexport interface CompositeAiBrief {\n kind: { id: string, label: string, purpose?: string }\n /** Titles of the enclosing composites, outermost first (empty at document level). */\n path: string[]\n title: string\n description: string\n tags: string[]\n /** Declared fields with a non-empty value, `select` values as their option label. */\n fields: { key: string, label: string, value: string }[]\n /** `meta[ai.instructionsField]` when non-empty, else `ai.instructions`, else ''. */\n instructions: string\n /** `ai.skeleton` with `{key}` replaced by the string value of `meta[key]` (declared fields only); '' without a skeleton. */\n skeleton: string\n /** Body blocks as Markdown ('' for a body without text). */\n body: string\n}\n\nconst PLACEHOLDER = /\\{([\\p{L}\\p{N}_-]+)\\}/gu\n\nconst isFilled = (value: unknown): value is string | number => (typeof value === 'string' && value !== '') || typeof value === 'number'\n\n/** The brief of the composite at `pos`; null unless the node there is a composite whose kind has `ai`. */\nexport function compositeAiBrief(editor: Editor, pos: number): CompositeAiBrief | null {\n const node = editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'composite' || node.childCount < COMPOSITE_CHROME_COUNT) return null\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n const ai = kind.ai\n if (!ai) return null\n\n const $pos = editor.state.doc.resolve(pos)\n const path: string[] = []\n for (let depth = 1; depth <= $pos.depth; depth++) {\n const ancestor = $pos.node(depth)\n if (ancestor.type.name === 'composite') path.push(ancestor.child(0).textContent.trim())\n }\n\n const tags: unknown = node.child(2).attrs.tags\n const body = Array.from({ length: node.childCount - COMPOSITE_CHROME_COUNT }, (_, i) => node.child(COMPOSITE_CHROME_COUNT + i))\n const meta: Record<string, unknown> = node.attrs.meta ?? {}\n\n const fields = kind.fields.flatMap((field) => {\n const raw = meta[field.key]\n if (field.type === 'bibliography') {\n const bib = raw as Partial<CslItem> | null | undefined\n return bib && typeof bib === 'object' && typeof bib.type === 'string' ? [{ key: field.key, label: field.label, value: citeFull({ ...(bib as CslItem), title: node.child(0).textContent }) }] : []\n }\n if (field.type === 'reference') {\n const target = typeof raw === 'string' && raw ? findBlockById(editor.state.doc, raw)?.node : undefined\n const title = target?.type.name === 'composite' ? target.child(0).textContent.trim() : ''\n return title ? [{ key: field.key, label: field.label, value: title }] : []\n }\n if (field.type === 'derivation') {\n const derivation = derivationOf(node)\n return derivation ? [{ key: field.key, label: field.label, value: `${derivation.method} from ${'asset' in derivation.from ? derivation.from.asset : derivation.from.block}` }] : []\n }\n if (field.type === 'multiselect') {\n const chosen = Array.isArray(raw) ? raw.filter((item): item is string => typeof item === 'string') : []\n const value = chosen.map(item => field.options?.find(option => option.value === item)?.label ?? item).join(', ')\n return value ? [{ key: field.key, label: field.label, value }] : []\n }\n if (!isFilled(raw)) return []\n const value = String(raw)\n return [{ key: field.key, label: field.label, value: field.type === 'select' ? (field.options?.find(option => option.value === value)?.label ?? value) : value }]\n })\n const keys = new Set(kind.fields.map(field => field.key))\n const own = ai.instructionsField ? String(meta[ai.instructionsField] ?? '').trim() : ''\n\n return {\n kind: { id: kind.id, label: kind.label, ...(ai.purpose && { purpose: ai.purpose }) },\n path,\n title: node.child(0).textContent,\n description: node.child(1).textContent,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n fields,\n instructions: own || ai.instructions?.trim() || '',\n skeleton: ai.skeleton?.replace(PLACEHOLDER, (match, key: string) => keys.has(key) && isFilled(meta[key]) ? String(meta[key]) : match) ?? '',\n body: body.some(child => child.textContent.trim())\n ? editor.markdown!.serialize({ type: 'doc', content: body.map(child => child.toJSON() as JSONContent) }).trim()\n : '',\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "@tiptap/core";
|
|
2
|
+
//#region src/features/composites/bibliography-command.d.ts
|
|
3
|
+
declare module '@tiptap/core' {
|
|
4
|
+
interface Commands<ReturnType> {
|
|
5
|
+
compositeBibliographyFetch: {
|
|
6
|
+
/** Fetches the bibliographic data of the Baustein at `pos` into its `meta.bib` (`ai.extract`, method `bibliography`). Registered by `composite-ai`. */
|
|
7
|
+
fetchCompositeBibliography: (pos: number) => ReturnType;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=bibliography-command.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { yearOf } from "./bibliography.js";
|
|
2
|
+
//#region src/features/composites/bibliography-form.ts
|
|
3
|
+
/** Keys of the CSL item the form edits; every other stored key is kept as it is. */
|
|
4
|
+
const MANAGED_KEYS = {
|
|
5
|
+
"type": true,
|
|
6
|
+
"author": true,
|
|
7
|
+
"issued": true,
|
|
8
|
+
"container-title": true,
|
|
9
|
+
"volume": true,
|
|
10
|
+
"issue": true,
|
|
11
|
+
"page": true,
|
|
12
|
+
"publisher": true,
|
|
13
|
+
"DOI": true,
|
|
14
|
+
"ISBN": true,
|
|
15
|
+
"URL": true,
|
|
16
|
+
"abstract": true
|
|
17
|
+
};
|
|
18
|
+
/** Never stored in `meta.bib`: the block id and the title belong to the Baustein. */
|
|
19
|
+
const FOREIGN_KEYS = {
|
|
20
|
+
id: true,
|
|
21
|
+
title: true
|
|
22
|
+
};
|
|
23
|
+
/** The names of `authors`, one per non-empty line. */
|
|
24
|
+
function parseAuthors(text) {
|
|
25
|
+
return text.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
26
|
+
const comma = line.indexOf(",");
|
|
27
|
+
if (comma < 0) return { literal: line };
|
|
28
|
+
const family = line.slice(0, comma).trim();
|
|
29
|
+
const given = line.slice(comma + 1).trim();
|
|
30
|
+
return family ? {
|
|
31
|
+
family,
|
|
32
|
+
...given && { given }
|
|
33
|
+
} : { literal: line };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** `names` as the text `parseAuthors` reads back: `Family, Given` per line. */
|
|
37
|
+
function authorsText(names) {
|
|
38
|
+
return (names ?? []).map((name) => name.family ? name.given ? `${name.family}, ${name.given}` : `${name.family},` : name.literal ?? "").filter(Boolean).join("\n");
|
|
39
|
+
}
|
|
40
|
+
/** The form values of `item`; without an item the defaults of an empty form. */
|
|
41
|
+
function bibFormOf(item) {
|
|
42
|
+
const year = item ? yearOf(item) : null;
|
|
43
|
+
return {
|
|
44
|
+
type: item?.type ?? "article-journal",
|
|
45
|
+
authors: authorsText(item?.author),
|
|
46
|
+
year: year === null ? "" : String(year),
|
|
47
|
+
containerTitle: item?.["container-title"] ?? "",
|
|
48
|
+
volume: item?.volume ?? "",
|
|
49
|
+
issue: item?.issue ?? "",
|
|
50
|
+
page: item?.page ?? "",
|
|
51
|
+
publisher: item?.publisher ?? "",
|
|
52
|
+
doi: item?.DOI ?? "",
|
|
53
|
+
isbn: item?.ISBN ?? "",
|
|
54
|
+
url: item?.URL ?? "",
|
|
55
|
+
abstract: item?.abstract ?? ""
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The complete CSL item for `form`: no `title` and no empty strings. Keys the form does not edit (`editor`,
|
|
60
|
+
* `publisher-place`, `language`, …) are taken over from `base`; an unchanged year keeps the stored date parts (month, day).
|
|
61
|
+
*/
|
|
62
|
+
function bibItemOf(form, base) {
|
|
63
|
+
const text = (value) => value.trim() || void 0;
|
|
64
|
+
const author = parseAuthors(form.authors);
|
|
65
|
+
const year = Number.parseInt(form.year, 10);
|
|
66
|
+
const keptDate = base?.issued && yearOf(base) === year ? base.issued : void 0;
|
|
67
|
+
const fields = {
|
|
68
|
+
author: author.length ? author : void 0,
|
|
69
|
+
issued: Number.isFinite(year) ? keptDate ?? { "date-parts": [[year]] } : void 0,
|
|
70
|
+
"container-title": text(form.containerTitle),
|
|
71
|
+
volume: text(form.volume),
|
|
72
|
+
issue: text(form.issue),
|
|
73
|
+
page: text(form.page),
|
|
74
|
+
publisher: text(form.publisher),
|
|
75
|
+
DOI: text(form.doi),
|
|
76
|
+
ISBN: text(form.isbn),
|
|
77
|
+
URL: text(form.url),
|
|
78
|
+
abstract: text(form.abstract)
|
|
79
|
+
};
|
|
80
|
+
const kept = Object.entries(base ?? {}).filter(([key, value]) => !MANAGED_KEYS[key] && !FOREIGN_KEYS[key] && value !== "" && value !== void 0 && value !== null);
|
|
81
|
+
const entries = [...Object.entries(fields).filter(([, value]) => value !== void 0), ...kept];
|
|
82
|
+
return {
|
|
83
|
+
type: form.type,
|
|
84
|
+
...Object.fromEntries(entries)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { authorsText, bibFormOf, bibItemOf, parseAuthors };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=bibliography-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bibliography-form.js","names":[],"sources":["../../../src/features/composites/bibliography-form.ts"],"sourcesContent":["// Vue-free: the form model of the bibliography editor (`BibliographyEditor` on Vue and React) — a CSL item as flat strings and back.\nimport { type CslItem, type CslName, type CslType, yearOf } from './bibliography'\n\n/** The CSL item as the editor's inputs hold it: one string per input. */\nexport interface BibForm {\n type: CslType\n /** One person per line: `Family, Given`; a line without a comma is an institution or an unparsed name. */\n authors: string\n year: string\n containerTitle: string\n volume: string\n issue: string\n page: string\n publisher: string\n doi: string\n isbn: string\n url: string\n abstract: string\n}\n\n/** Keys of the CSL item the form edits; every other stored key is kept as it is. */\nconst MANAGED_KEYS: Record<string, true> = { 'type': true, 'author': true, 'issued': true, 'container-title': true, 'volume': true, 'issue': true, 'page': true, 'publisher': true, 'DOI': true, 'ISBN': true, 'URL': true, 'abstract': true }\n/** Never stored in `meta.bib`: the block id and the title belong to the Baustein. */\nconst FOREIGN_KEYS: Record<string, true> = { id: true, title: true }\n\n/** The names of `authors`, one per non-empty line. */\nexport function parseAuthors(text: string): CslName[] {\n return text.split('\\n').map(line => line.trim()).filter(Boolean).map((line) => {\n const comma = line.indexOf(',')\n if (comma < 0) return { literal: line }\n const family = line.slice(0, comma).trim()\n const given = line.slice(comma + 1).trim()\n return family ? { family, ...(given && { given }) } : { literal: line }\n })\n}\n\n/** `names` as the text `parseAuthors` reads back: `Family, Given` per line. */\nexport function authorsText(names: readonly CslName[] | undefined): string {\n return (names ?? []).map(name => name.family ? (name.given ? `${name.family}, ${name.given}` : `${name.family},`) : (name.literal ?? '')).filter(Boolean).join('\\n')\n}\n\n/** The form values of `item`; without an item the defaults of an empty form. */\nexport function bibFormOf(item: CslItem | null | undefined): BibForm {\n const year = item ? yearOf(item) : null\n return {\n type: item?.type ?? 'article-journal',\n authors: authorsText(item?.author),\n year: year === null ? '' : String(year),\n containerTitle: item?.['container-title'] ?? '',\n volume: item?.volume ?? '',\n issue: item?.issue ?? '',\n page: item?.page ?? '',\n publisher: item?.publisher ?? '',\n doi: item?.DOI ?? '',\n isbn: item?.ISBN ?? '',\n url: item?.URL ?? '',\n abstract: item?.abstract ?? '',\n }\n}\n\n/**\n * The complete CSL item for `form`: no `title` and no empty strings. Keys the form does not edit (`editor`,\n * `publisher-place`, `language`, …) are taken over from `base`; an unchanged year keeps the stored date parts (month, day).\n */\nexport function bibItemOf(form: BibForm, base?: CslItem | null): CslItem {\n const text = (value: string) => value.trim() || undefined\n const author = parseAuthors(form.authors)\n const year = Number.parseInt(form.year, 10)\n const keptDate = base?.issued && yearOf(base) === year ? base.issued : undefined\n const fields: Record<string, unknown> = {\n author: author.length ? author : undefined,\n issued: Number.isFinite(year) ? (keptDate ?? { 'date-parts': [[year]] }) : undefined,\n 'container-title': text(form.containerTitle),\n volume: text(form.volume),\n issue: text(form.issue),\n page: text(form.page),\n publisher: text(form.publisher),\n DOI: text(form.doi),\n ISBN: text(form.isbn),\n URL: text(form.url),\n abstract: text(form.abstract),\n }\n const kept = Object.entries(base ?? {}).filter(([key, value]) => !MANAGED_KEYS[key] && !FOREIGN_KEYS[key] && value !== '' && value !== undefined && value !== null)\n const entries = [...Object.entries(fields).filter(([, value]) => value !== undefined), ...kept]\n return { type: form.type, ...Object.fromEntries(entries) }\n}\n"],"mappings":";;;AAqBA,MAAM,eAAqC;CAAE,QAAQ;CAAM,UAAU;CAAM,UAAU;CAAM,mBAAmB;CAAM,UAAU;CAAM,SAAS;CAAM,QAAQ;CAAM,aAAa;CAAM,OAAO;CAAM,QAAQ;CAAM,OAAO;CAAM,YAAY;AAAK;;AAE7O,MAAM,eAAqC;CAAE,IAAI;CAAM,OAAO;AAAK;;AAGnE,SAAgB,aAAa,MAAyB;CACpD,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,SAAS;EAC7E,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,IAAI,QAAQ,GAAG,OAAO,EAAE,SAAS,KAAK;EACtC,MAAM,SAAS,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,KAAK;EACzC,MAAM,QAAQ,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;EACzC,OAAO,SAAS;GAAE;GAAQ,GAAI,SAAS,EAAE,MAAM;EAAG,IAAI,EAAE,SAAS,KAAK;CACxE,CAAC;AACH;;AAGA,SAAgB,YAAY,OAA+C;CACzE,QAAQ,SAAS,CAAC,EAAA,CAAG,KAAI,SAAQ,KAAK,SAAU,KAAK,QAAQ,GAAG,KAAK,OAAO,IAAI,KAAK,UAAU,GAAG,KAAK,OAAO,KAAO,KAAK,WAAW,EAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;AACrK;;AAGA,SAAgB,UAAU,MAA2C;CACnE,MAAM,OAAO,OAAO,OAAO,IAAI,IAAI;CACnC,OAAO;EACL,MAAM,MAAM,QAAQ;EACpB,SAAS,YAAY,MAAM,MAAM;EACjC,MAAM,SAAS,OAAO,KAAK,OAAO,IAAI;EACtC,gBAAgB,OAAO,sBAAsB;EAC7C,QAAQ,MAAM,UAAU;EACxB,OAAO,MAAM,SAAS;EACtB,MAAM,MAAM,QAAQ;EACpB,WAAW,MAAM,aAAa;EAC9B,KAAK,MAAM,OAAO;EAClB,MAAM,MAAM,QAAQ;EACpB,KAAK,MAAM,OAAO;EAClB,UAAU,MAAM,YAAY;CAC9B;AACF;;;;;AAMA,SAAgB,UAAU,MAAe,MAAgC;CACvE,MAAM,QAAQ,UAAkB,MAAM,KAAK,KAAK,KAAA;CAChD,MAAM,SAAS,aAAa,KAAK,OAAO;CACxC,MAAM,OAAO,OAAO,SAAS,KAAK,MAAM,EAAE;CAC1C,MAAM,WAAW,MAAM,UAAU,OAAO,IAAI,MAAM,OAAO,KAAK,SAAS,KAAA;CACvE,MAAM,SAAkC;EACtC,QAAQ,OAAO,SAAS,SAAS,KAAA;EACjC,QAAQ,OAAO,SAAS,IAAI,IAAK,YAAY,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,IAAK,KAAA;EAC3E,mBAAmB,KAAK,KAAK,cAAc;EAC3C,QAAQ,KAAK,KAAK,MAAM;EACxB,OAAO,KAAK,KAAK,KAAK;EACtB,MAAM,KAAK,KAAK,IAAI;EACpB,WAAW,KAAK,KAAK,SAAS;EAC9B,KAAK,KAAK,KAAK,GAAG;EAClB,MAAM,KAAK,KAAK,IAAI;EACpB,KAAK,KAAK,KAAK,GAAG;EAClB,UAAU,KAAK,KAAK,QAAQ;CAC9B;CACA,MAAM,OAAO,OAAO,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,WAAW,CAAC,aAAa,QAAQ,CAAC,aAAa,QAAQ,UAAU,MAAM,UAAU,KAAA,KAAa,UAAU,IAAI;CAClK,MAAM,UAAU,CAAC,GAAG,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,GAAG,GAAG,IAAI;CAC9F,OAAO;EAAE,MAAM,KAAK;EAAM,GAAG,OAAO,YAAY,OAAO;CAAE;AAC3D"}
|