@ecsplico/editor 1.0.0 → 2.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 +37 -0
- package/README.md +7 -7
- package/dist/adapters/anchors.d.ts +80 -0
- package/dist/adapters/anchors.js +270 -0
- package/dist/adapters/anchors.js.map +1 -0
- package/dist/adapters/comments/client.d.ts +64 -0
- package/dist/adapters/comments/client.js +536 -0
- package/dist/adapters/comments/client.js.map +1 -0
- package/dist/adapters/comments/drizzle.d.ts +430 -0
- package/dist/adapters/comments/drizzle.js +236 -0
- package/dist/adapters/comments/drizzle.js.map +1 -0
- package/dist/adapters/comments/errors.d.ts +9 -0
- package/dist/adapters/comments/errors.js +19 -0
- package/dist/adapters/comments/errors.js.map +1 -0
- package/dist/adapters/comments/json.d.ts +19 -0
- package/dist/adapters/comments/json.js +94 -0
- package/dist/adapters/comments/json.js.map +1 -0
- package/dist/adapters/comments/record.js +78 -0
- package/dist/adapters/comments/record.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +23 -0
- package/dist/adapters/comments/server.js +102 -0
- package/dist/adapters/comments/server.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +109 -0
- package/dist/adapters/documents.js +108 -0
- package/dist/adapters/documents.js.map +1 -0
- package/dist/adapters/drizzle.d.ts +293 -0
- package/dist/adapters/drizzle.js +157 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/files.js +30 -0
- package/dist/adapters/files.js.map +1 -0
- package/dist/adapters/forge.d.ts +58 -0
- package/dist/adapters/forge.js +91 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +0 -0
- package/dist/adapters/json.d.ts +24 -0
- package/dist/adapters/json.js +128 -0
- package/dist/adapters/json.js.map +1 -0
- package/dist/adapters/nitro.d.ts +61 -0
- package/dist/adapters/nitro.js +212 -0
- package/dist/adapters/nitro.js.map +1 -0
- package/dist/adapters/routes.js +16 -0
- package/dist/adapters/routes.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -0
- package/dist/adapters/shared.js +32 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/vue.d.ts +67 -0
- package/dist/adapters/vue.js +187 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/doc-anchors.d.ts +5 -0
- package/dist/core/doc-anchors.js +4 -3
- package/dist/core/doc-anchors.js.map +1 -1
- package/dist/core/editor-ui.js +57 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/notify.d.ts +1 -1
- package/dist/core/notify.js.map +1 -1
- package/dist/core/surface-session.js +139 -0
- package/dist/core/surface-session.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js.map +1 -1
- package/dist/features/ai/agent-ops.d.ts +1 -1
- package/dist/features/ai/agent-ops.js +9 -6
- package/dist/features/ai/agent-ops.js.map +1 -1
- package/dist/features/ai/ai-apply.d.ts +1 -1
- package/dist/features/ai/ai-review.js +10 -1
- package/dist/features/ai/ai-review.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +78 -252
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.js +1 -0
- package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat.d.ts +3 -2
- package/dist/features/ai/document-chat.js +16 -19
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/index.d.ts +5 -3
- package/dist/features/ai/index.js +4 -2
- package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
- package/dist/features/ai/protocol/agent-ops.js +65 -39
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +89 -0
- package/dist/features/ai/protocol/block-lines.js +177 -0
- package/dist/features/ai/protocol/block-lines.js.map +1 -0
- package/dist/features/ai/protocol/chat.js +10 -10
- 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/index.js +3 -2
- package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
- package/dist/features/ai/react/AiSelectionMenu.js +64 -255
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -0
- package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
- package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
- package/dist/features/ai/selection-flow.d.ts +133 -0
- package/dist/features/ai/selection-flow.js +283 -0
- package/dist/features/ai/selection-flow.js.map +1 -0
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-document-chat.d.ts +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/feature.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
- package/dist/features/comments/components/CommentBubble.js +1 -1
- package/dist/features/comments/components/CommentComposer.js +1 -1
- package/dist/features/comments/components/CommentThreadView.js +5 -2
- package/dist/features/comments/components/CommentThreadView.js.map +1 -1
- package/dist/features/comments/components/CommentsMargin.js +108 -230
- package/dist/features/comments/components/CommentsMargin.js.map +1 -1
- package/dist/features/comments/components/CommentsOverlay.js +1 -1
- package/dist/features/comments/feature.js +1 -1
- package/dist/features/comments/index.d.ts +4 -2
- package/dist/features/comments/index.js +4 -2
- package/dist/features/comments/margin-controller.d.ts +44 -0
- package/dist/features/comments/margin-controller.js +132 -0
- package/dist/features/comments/margin-controller.js.map +1 -0
- package/dist/features/comments/margin-layout.d.ts +55 -0
- package/dist/features/comments/margin-layout.js +79 -0
- package/dist/features/comments/margin-layout.js.map +1 -0
- package/dist/features/comments/model.d.ts +8 -0
- package/dist/features/comments/model.js +60 -34
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentBubble.js +1 -1
- package/dist/features/comments/react/CommentComposer.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +2 -2
- package/dist/features/comments/react/CommentThreadView.js.map +1 -1
- package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
- package/dist/features/comments/react/CommentsMargin.js +85 -251
- package/dist/features/comments/react/CommentsMargin.js.map +1 -1
- package/dist/features/comments/react/CommentsOverlay.js +1 -1
- package/dist/features/comments/react/use-comments.js +1 -1
- package/dist/features/comments/react/use-margin-layout.js +45 -0
- package/dist/features/comments/react/use-margin-layout.js.map +1 -0
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +23 -0
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js +40 -0
- package/dist/features/comments/snapshot-equality.js.map +1 -0
- package/dist/features/comments/thread-anchor.js +33 -0
- package/dist/features/comments/thread-anchor.js.map +1 -0
- package/dist/features/comments/use-comments.d.ts +3 -1
- package/dist/features/comments/use-comments.js +1 -0
- package/dist/features/comments/use-comments.js.map +1 -1
- package/dist/features/comments/use-margin-layout.js +29 -0
- package/dist/features/comments/use-margin-layout.js.map +1 -0
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +21 -0
- package/dist/features/composites/accepted-children.js +126 -0
- package/dist/features/composites/accepted-children.js.map +1 -0
- package/dist/features/composites/components/CompositeDetails.js +1 -1
- package/dist/features/composites/components/CompositeInspector.js +38 -164
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/feature.d.ts +2 -1
- package/dist/features/composites/feature.js +8 -2
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/index.d.ts +5 -3
- package/dist/features/composites/index.js +8 -6
- package/dist/features/composites/inspector-model.d.ts +97 -0
- package/dist/features/composites/inspector-model.js +255 -0
- package/dist/features/composites/inspector-model.js.map +1 -0
- package/dist/features/composites/kinds.d.ts +11 -0
- package/dist/features/composites/kinds.js +17 -2
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/outline.d.ts +18 -5
- package/dist/features/composites/outline.js +44 -12
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
- package/dist/features/composites/react/CompositeInspector.js +21 -180
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
- package/dist/features/composites/react/CompositeView.js +1 -1
- package/dist/features/composites/react/use-composite-inspector.js +29 -0
- package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/schema.d.ts +2 -2
- package/dist/features/composites/schema.js +2 -2
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/use-composite-anchors.d.ts +1 -1
- package/dist/features/composites/use-composite-inspector.js +41 -0
- package/dist/features/composites/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/use-composite-rows.d.ts +1 -1
- package/dist/features/media/index.d.ts +1 -1
- package/dist/features/media/vue.d.ts +1 -1
- package/dist/features/mentions/index.d.ts +3 -3
- package/dist/features/mentions/index.js +3 -3
- package/dist/features/mentions/schema.d.ts +1 -1
- package/dist/features/mentions/schema.js +1 -1
- package/dist/features/themes/index.d.ts +1 -1
- package/dist/features/themes/index.js +1 -1
- package/dist/features/transcript/feature.js +1 -1
- package/dist/features/transcript/index.js +2 -2
- package/dist/i18n/de/ai.js +2 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/common.js +1 -2
- package/dist/i18n/de/common.js.map +1 -1
- package/dist/i18n/de/composites.js +8 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/tree.js +14 -1
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +22 -1
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/common.js +1 -2
- package/dist/i18n/en/common.js.map +1 -1
- package/dist/i18n/en/composites.js +8 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/tree.js +14 -1
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +2 -2
- package/dist/integrations/composite-ai/index.js +2 -2
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.d.ts +1 -1
- package/dist/markdown/MarkdownSurface.js +2 -2
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/markdown/index.d.ts +2 -2
- package/dist/nuxt/module.d.ts +0 -6
- package/dist/nuxt/module.js +3 -5
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/install-context.d.ts +2 -3
- package/dist/nuxt/runtime/install-context.js +2 -3
- package/dist/nuxt/runtime/install-context.js.map +1 -1
- package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +1 -1
- package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
- package/dist/react/EditorSurface.d.ts +1 -1
- package/dist/react/EditorSurface.js +43 -149
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.js +3 -40
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/react/styles/ui-vars.css +2 -2
- package/dist/schema/advanced.js +2 -2
- package/dist/server/adapter.d.ts +2 -2
- package/dist/server/collab.js +1 -1
- package/dist/server/handlers/ai.d.ts +4 -2
- package/dist/server/handlers/ai.js +11 -9
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +1 -1
- package/dist/server/handlers/editor.d.ts +1 -1
- package/dist/server/handlers/editor.js +2 -2
- package/dist/server/host.d.ts +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/styles/ai.css +1 -1
- package/dist/styles/authors.css +1 -1
- package/dist/styles/block-ui.css +1 -1
- package/dist/styles/collab.css +1 -1
- package/dist/styles/comments.css +1 -1
- package/dist/styles/core.css +2 -2
- package/dist/styles/media.css +1 -1
- package/dist/styles/mentions.css +1 -1
- package/dist/styles/themes.css +1 -1
- package/dist/styles/tokens.css +1 -1
- package/dist/styles/transcript.css +1 -1
- package/dist/tree/DocumentTree.js +159 -387
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +37 -0
- package/dist/tree/drop-rules.js.map +1 -0
- package/dist/tree/react/DocumentTree.d.ts +2 -2
- package/dist/tree/react/DocumentTree.js +135 -397
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/react/use-tree-model.js +129 -0
- package/dist/tree/react/use-tree-model.js.map +1 -0
- package/dist/tree/react.js +1 -1
- package/dist/tree/tree-host.js +100 -0
- package/dist/tree/tree-host.js.map +1 -0
- package/dist/tree/tree-items.js +22 -43
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +580 -0
- package/dist/tree/tree-model.js.map +1 -0
- package/dist/tree/tree-rows.js +77 -0
- package/dist/tree/tree-rows.js.map +1 -0
- package/dist/tree/use-tree-model.js +94 -0
- package/dist/tree/use-tree-model.js.map +1 -0
- package/dist/tree/vue.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +2 -2
- package/dist/vue/components/EditorSurface.js +27 -87
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/components/EditorToolbar.js +7 -44
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/context.d.ts +4 -4
- package/dist/vue/context.js +3 -2
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/kit/types.d.ts +1 -1
- package/dist/vue/kit/use-ui-kit.d.ts +2 -2
- package/dist/vue/kit/use-ui-kit.js +3 -3
- package/dist/vue/kit/use-ui-kit.js.map +1 -1
- package/dist/vue/use-editor-ui.d.ts +1 -1
- package/dist/vue/use-notifier.d.ts +1 -1
- package/docs/integration.md +98 -5
- package/docs/llms.md +10 -2
- package/docs/usage.md +10 -5
- package/package.json +70 -23
- package/dist/nuxt/runtime/plugins/editor-context-primevue.client.ts +0 -8
- package/dist/vue/kit/primevue/Alert.js +0 -49
- package/dist/vue/kit/primevue/Alert.js.map +0 -1
- package/dist/vue/kit/primevue/Avatar.js +0 -54
- package/dist/vue/kit/primevue/Avatar.js.map +0 -1
- package/dist/vue/kit/primevue/Badge.js +0 -41
- package/dist/vue/kit/primevue/Badge.js.map +0 -1
- package/dist/vue/kit/primevue/Button.js +0 -112
- package/dist/vue/kit/primevue/Button.js.map +0 -1
- package/dist/vue/kit/primevue/Chip.js +0 -38
- package/dist/vue/kit/primevue/Chip.js.map +0 -1
- package/dist/vue/kit/primevue/DropdownMenu.js +0 -98
- package/dist/vue/kit/primevue/DropdownMenu.js.map +0 -1
- package/dist/vue/kit/primevue/FieldGroup.js +0 -22
- package/dist/vue/kit/primevue/FieldGroup.js.map +0 -1
- package/dist/vue/kit/primevue/FormField.js +0 -56
- package/dist/vue/kit/primevue/FormField.js.map +0 -1
- package/dist/vue/kit/primevue/Icon.js +0 -23
- package/dist/vue/kit/primevue/Icon.js.map +0 -1
- package/dist/vue/kit/primevue/Input.js +0 -96
- package/dist/vue/kit/primevue/Input.js.map +0 -1
- package/dist/vue/kit/primevue/InputMenu.js +0 -122
- package/dist/vue/kit/primevue/InputMenu.js.map +0 -1
- package/dist/vue/kit/primevue/Modal.js +0 -59
- package/dist/vue/kit/primevue/Modal.js.map +0 -1
- package/dist/vue/kit/primevue/Popover.js +0 -135
- package/dist/vue/kit/primevue/Popover.js.map +0 -1
- package/dist/vue/kit/primevue/RadioGroup.js +0 -58
- package/dist/vue/kit/primevue/RadioGroup.js.map +0 -1
- package/dist/vue/kit/primevue/Select.js +0 -68
- package/dist/vue/kit/primevue/Select.js.map +0 -1
- package/dist/vue/kit/primevue/SelectMenu.js +0 -94
- package/dist/vue/kit/primevue/SelectMenu.js.map +0 -1
- package/dist/vue/kit/primevue/Separator.js +0 -16
- package/dist/vue/kit/primevue/Separator.js.map +0 -1
- package/dist/vue/kit/primevue/Switch.js +0 -46
- package/dist/vue/kit/primevue/Switch.js.map +0 -1
- package/dist/vue/kit/primevue/Table.js +0 -54
- package/dist/vue/kit/primevue/Table.js.map +0 -1
- package/dist/vue/kit/primevue/Tabs.js +0 -49
- package/dist/vue/kit/primevue/Tabs.js.map +0 -1
- package/dist/vue/kit/primevue/Textarea.js +0 -55
- package/dist/vue/kit/primevue/Textarea.js.map +0 -1
- package/dist/vue/kit/primevue/Tooltip.js +0 -43
- package/dist/vue/kit/primevue/Tooltip.js.map +0 -1
- package/dist/vue/kit/primevue/Tree.js +0 -75
- package/dist/vue/kit/primevue/Tree.js.map +0 -1
- package/dist/vue/kit/primevue/select-options.js +0 -55
- package/dist/vue/kit/primevue/select-options.js.map +0 -1
- package/dist/vue/kit/primevue/shared.js +0 -49
- package/dist/vue/kit/primevue/shared.js.map +0 -1
- package/dist/vue/kit/primevue/trigger.js +0 -25
- package/dist/vue/kit/primevue/trigger.js.map +0 -1
- package/dist/vue/kit/primevue.css +0 -86
- package/dist/vue/kit/primevue.d.ts +0 -33
- package/dist/vue/kit/primevue.js +0 -96
- package/dist/vue/kit/primevue.js.map +0 -1
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createMetadataRegistry } from "../core/metadata.js";
|
|
3
|
-
import { SuggestionPopup } from "../core/suggestion-popup.js";
|
|
4
|
-
import { contributions, settingsTabsOf } from "../core/define-editor.js";
|
|
5
|
-
import { registerEditor } from "../core/editor-ui.js";
|
|
6
|
-
import { blockActionsOf, createFeatureContext } from "../core/feature-context.js";
|
|
1
|
+
import { settingsTabsOf } from "../core/define-editor.js";
|
|
7
2
|
import { useEditorContext } from "./context.js";
|
|
8
|
-
import {
|
|
3
|
+
import { createSurfaceSession } from "../core/surface-session.js";
|
|
9
4
|
import { EditorSettingsModal } from "./EditorSettingsModal.js";
|
|
10
5
|
import { EditorUiContext } from "./editor-ui.js";
|
|
11
6
|
import { reactNotifier } from "./notify.js";
|
|
12
7
|
import { reactSuggestionRenderer } from "./suggestion-renderer.js";
|
|
13
8
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
14
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
import { getSchema } from "@tiptap/core";
|
|
16
10
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
17
11
|
//#region src/react/EditorSurface.tsx
|
|
18
12
|
/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */
|
|
@@ -42,10 +36,12 @@ function useOpenState(value, onChange) {
|
|
|
42
36
|
latest
|
|
43
37
|
};
|
|
44
38
|
}
|
|
45
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Everything fixed at mount: the chrome handle and the framework-free session (preset, context, extensions, content
|
|
41
|
+
* check, setup lifecycle); callbacks are read from the surface's latest props.
|
|
42
|
+
*/
|
|
46
43
|
function createSurface(props, app, parts) {
|
|
47
|
-
const { preset, collaboration } = props.current;
|
|
48
|
-
if (collaboration && !preset.features.some((feature) => feature.id === "collab")) throw new Error("@ecsplico/editor: `collaboration` needs a preset with the collaboration feature");
|
|
44
|
+
const { preset, collaboration, value, editable, metadata, documentId, upload, abilities, slashCommands, extensions, blockActions } = props.current;
|
|
49
45
|
const ui = {
|
|
50
46
|
openSettings: parts.openSettings,
|
|
51
47
|
toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),
|
|
@@ -54,76 +50,31 @@ function createSurface(props, app, parts) {
|
|
|
54
50
|
return parts.comments.latest.current.open;
|
|
55
51
|
}
|
|
56
52
|
};
|
|
57
|
-
const registry = createMetadataRegistry({ onCreate: (list, item) => props.current.onMetadataCreate?.(list, item) });
|
|
58
|
-
registry.replace({ ...props.current.metadata });
|
|
59
|
-
const ctx = createFeatureContext({
|
|
60
|
-
app,
|
|
61
|
-
preset,
|
|
62
|
-
ui,
|
|
63
|
-
metadata: registry,
|
|
64
|
-
notify: reactNotifier,
|
|
65
|
-
getEditor: () => parts.editor.current ?? void 0,
|
|
66
|
-
...props.current.documentId !== void 0 && { documentId: props.current.documentId },
|
|
67
|
-
...props.current.upload && { upload: props.current.upload },
|
|
68
|
-
...collaboration && { collaboration },
|
|
69
|
-
...props.current.abilities && { abilities: props.current.abilities },
|
|
70
|
-
...props.current.slashCommands && { slashCommands: props.current.slashCommands }
|
|
71
|
-
});
|
|
72
|
-
const extensions = [
|
|
73
|
-
presetExtension(preset),
|
|
74
|
-
SuggestionPopup.configure({ renderer: reactSuggestionRenderer }),
|
|
75
|
-
...contributions(preset, "extensions").flatMap((factory) => factory(ctx)),
|
|
76
|
-
...props.current.extensions ?? []
|
|
77
|
-
];
|
|
78
|
-
const schema = getSchema(extensions);
|
|
79
|
-
const overlays = preset.features.flatMap((feature) => [...feature.overlays ?? []]);
|
|
80
|
-
const blockActions = blockActionsOf(preset, ctx, props.current.blockActions);
|
|
81
|
-
/** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */
|
|
82
|
-
const validated = (json) => {
|
|
83
|
-
try {
|
|
84
|
-
const migrated = preset.pipeline.migrateContent(json);
|
|
85
|
-
schema.nodeFromJSON(migrated).check();
|
|
86
|
-
return { json: migrated };
|
|
87
|
-
} catch (error) {
|
|
88
|
-
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
let lastEmitted;
|
|
92
|
-
let content;
|
|
93
|
-
let initialError;
|
|
94
|
-
if (!collaboration) {
|
|
95
|
-
const initial = props.current.value ? validated(props.current.value) : { json: EMPTY_EDITOR_CONTENT };
|
|
96
|
-
if ("error" in initial) initialError = initial.error;
|
|
97
|
-
content = "json" in initial ? initial.json : EMPTY_EDITOR_CONTENT;
|
|
98
|
-
}
|
|
99
53
|
return {
|
|
100
54
|
preset,
|
|
101
|
-
collaboration,
|
|
102
55
|
ui,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
errorReported: false,
|
|
126
|
-
announced: null
|
|
56
|
+
session: createSurfaceSession({
|
|
57
|
+
app,
|
|
58
|
+
preset,
|
|
59
|
+
ui,
|
|
60
|
+
notify: reactNotifier,
|
|
61
|
+
suggestionRenderer: reactSuggestionRenderer,
|
|
62
|
+
getEditor: () => parts.editor.current ?? void 0,
|
|
63
|
+
collaboration,
|
|
64
|
+
value,
|
|
65
|
+
editable,
|
|
66
|
+
metadata,
|
|
67
|
+
documentId,
|
|
68
|
+
upload,
|
|
69
|
+
abilities,
|
|
70
|
+
slashCommands,
|
|
71
|
+
extensions,
|
|
72
|
+
blockActions,
|
|
73
|
+
onChange: (content) => props.current.onChange?.(content),
|
|
74
|
+
onMetadataCreate: (list, item) => props.current.onMetadataCreate?.(list, item),
|
|
75
|
+
onContentError: (error) => props.current.onContentError?.(error),
|
|
76
|
+
onReady: (editor) => props.current.onReady?.(editor)
|
|
77
|
+
})
|
|
127
78
|
};
|
|
128
79
|
}
|
|
129
80
|
const EditorSurface = forwardRef(function EditorSurface(props, ref) {
|
|
@@ -149,91 +100,34 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
|
|
|
149
100
|
tab: tab ?? state.tab
|
|
150
101
|
}));
|
|
151
102
|
}, []);
|
|
152
|
-
const [
|
|
103
|
+
const [{ preset, ui, session }] = useState(() => createSurface(latest, app, {
|
|
153
104
|
editor: editorRef,
|
|
154
105
|
sidebar,
|
|
155
106
|
comments,
|
|
156
107
|
openSettings
|
|
157
108
|
}));
|
|
158
|
-
const { preset, collaboration } = surface;
|
|
159
109
|
const editor = useEditor({
|
|
160
|
-
|
|
161
|
-
...!collaboration && {
|
|
162
|
-
content: surface.content,
|
|
163
|
-
onUpdate: ({ editor: instance }) => {
|
|
164
|
-
const json = instance.getJSON();
|
|
165
|
-
surface.lastEmitted = json;
|
|
166
|
-
latest.current.onChange?.(json);
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
editable,
|
|
110
|
+
...session.editorOptions,
|
|
170
111
|
immediatelyRender: true,
|
|
171
|
-
shouldRerenderOnTransaction: false
|
|
172
|
-
editorProps: { attributes: { class: "tiptap" } }
|
|
112
|
+
shouldRerenderOnTransaction: false
|
|
173
113
|
});
|
|
174
114
|
editorRef.current = editor;
|
|
175
115
|
useEffect(() => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const teardown of surface.teardowns.splice(0)) teardown();
|
|
182
|
-
};
|
|
183
|
-
}, [surface, preset]);
|
|
184
|
-
useEffect(() => {
|
|
185
|
-
if (surface.initialError && !surface.errorReported) {
|
|
186
|
-
surface.errorReported = true;
|
|
187
|
-
latest.current.onContentError?.(surface.initialError);
|
|
188
|
-
}
|
|
189
|
-
const unregister = registerEditor(editor, {
|
|
190
|
-
ui: surface.ui,
|
|
191
|
-
preset,
|
|
192
|
-
ctx: surface.ctx,
|
|
193
|
-
blockActions: surface.blockActions
|
|
194
|
-
});
|
|
195
|
-
if (surface.announced !== editor) {
|
|
196
|
-
surface.announced = editor;
|
|
197
|
-
latest.current.onReady?.(editor);
|
|
198
|
-
}
|
|
199
|
-
return unregister;
|
|
200
|
-
}, [
|
|
201
|
-
editor,
|
|
202
|
-
surface,
|
|
203
|
-
preset
|
|
204
|
-
]);
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
if (value === surface.applied.value) return;
|
|
207
|
-
surface.applied.value = value;
|
|
208
|
-
if (collaboration || !value || editor.isDestroyed || value === surface.lastEmitted) return;
|
|
209
|
-
const checked = surface.validated(value);
|
|
210
|
-
if ("error" in checked) {
|
|
211
|
-
latest.current.onContentError?.(checked.error);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
editor.commands.setContent(checked.json, { emitUpdate: false });
|
|
215
|
-
}, [
|
|
116
|
+
session.activate();
|
|
117
|
+
return () => session.deactivate();
|
|
118
|
+
}, [session]);
|
|
119
|
+
useEffect(() => session.register(editor), [editor, session]);
|
|
120
|
+
useEffect(() => session.setValue(value), [
|
|
216
121
|
value,
|
|
217
122
|
editor,
|
|
218
|
-
|
|
219
|
-
collaboration
|
|
123
|
+
session
|
|
220
124
|
]);
|
|
221
|
-
useEffect(() =>
|
|
222
|
-
if (editable === surface.applied.editable) return;
|
|
223
|
-
surface.applied.editable = editable;
|
|
224
|
-
if (editor.isDestroyed) return;
|
|
225
|
-
editor.setEditable(editable, false);
|
|
226
|
-
editor.view.dispatch(editor.state.tr.setMeta("addToHistory", false));
|
|
227
|
-
}, [
|
|
125
|
+
useEffect(() => session.setEditable(editable), [
|
|
228
126
|
editable,
|
|
229
127
|
editor,
|
|
230
|
-
|
|
128
|
+
session
|
|
231
129
|
]);
|
|
232
|
-
useEffect(() =>
|
|
233
|
-
if (metadata === surface.applied.metadata) return;
|
|
234
|
-
surface.applied.metadata = metadata;
|
|
235
|
-
surface.registry.replace({ ...metadata });
|
|
236
|
-
}, [metadata, surface]);
|
|
130
|
+
useEffect(() => session.setMetadata(metadata), [metadata, session]);
|
|
237
131
|
useEffect(() => {
|
|
238
132
|
if (sidebar.open) comments.set(false);
|
|
239
133
|
}, [sidebar.open, comments.set]);
|
|
@@ -245,7 +139,7 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
|
|
|
245
139
|
openSettings
|
|
246
140
|
}), [editor, openSettings]);
|
|
247
141
|
return /* @__PURE__ */ jsxs(EditorUiContext.Provider, {
|
|
248
|
-
value:
|
|
142
|
+
value: ui,
|
|
249
143
|
children: [
|
|
250
144
|
/* @__PURE__ */ jsx("div", {
|
|
251
145
|
className: "relative",
|
|
@@ -254,7 +148,7 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
|
|
|
254
148
|
className: "relative"
|
|
255
149
|
})
|
|
256
150
|
}),
|
|
257
|
-
|
|
151
|
+
session.overlays.map((Overlay, index) => /* @__PURE__ */ jsx(Overlay, { editor }, index)),
|
|
258
152
|
(props.settingsUi ?? "internal") === "internal" && /* @__PURE__ */ jsx(EditorSettingsModal, {
|
|
259
153
|
preset,
|
|
260
154
|
open: settingsState.open,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import { type AnyExtension, type Editor, getSchema, type JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { contributions, settingsTabsOf } from '../core/define-editor'\nimport { type EditorUi, registerEditor } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, UploadFn } from '../core/feature'\nimport { blockActionsOf, createFeatureContext } from '../core/feature-context'\nimport { createMetadataRegistry } from '../core/metadata'\nimport type { EditorMetadata, MetadataItem } from '../core/metadata-types'\nimport { presetExtension } from '../core/pipeline'\nimport { EMPTY_EDITOR_CONTENT } from '../core/schema'\nimport { SuggestionPopup } from '../core/suggestion-popup'\nimport { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/** Everything fixed at mount: preset, context, extensions, content check; callbacks are read from the surface's props. */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration } = props.current\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n if (collaboration && !preset.features.some(feature => feature.id === 'collab')) {\n throw new Error('@ecsplico/editor: `collaboration` needs a preset with the collaboration feature')\n }\n\n const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n const registry = createMetadataRegistry({ onCreate: (list, item) => props.current.onMetadataCreate?.(list, item) })\n registry.replace({ ...props.current.metadata })\n\n const ctx = createFeatureContext({\n app,\n preset,\n ui,\n metadata: registry,\n notify: reactNotifier,\n getEditor: () => parts.editor.current ?? undefined,\n ...(props.current.documentId !== undefined && { documentId: props.current.documentId }),\n ...(props.current.upload && { upload: props.current.upload }),\n ...(collaboration && { collaboration }),\n ...(props.current.abilities && { abilities: props.current.abilities }),\n ...(props.current.slashCommands && { slashCommands: props.current.slashCommands }),\n })\n\n const extensions: AnyExtension[] = [\n presetExtension(preset),\n SuggestionPopup.configure({ renderer: reactSuggestionRenderer }),\n ...contributions(preset, 'extensions').flatMap(factory => factory(ctx)),\n ...(props.current.extensions ?? []),\n ]\n const schema = getSchema(extensions)\n const overlays = preset.features.flatMap(feature => [...(feature.overlays ?? [])])\n const blockActions = blockActionsOf(preset, ctx, props.current.blockActions)\n\n /** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */\n const validated = (json: JSONContent): { json: JSONContent } | { error: Error } => {\n try {\n const migrated = preset.pipeline.migrateContent(json)\n schema.nodeFromJSON(migrated).check()\n return { json: migrated }\n }\n catch (error) {\n return { error: error instanceof Error ? error : new Error(String(error)) }\n }\n }\n\n // Standalone: the JSON last sent through `onChange`; the host handing it back is not an external change.\n let lastEmitted: JSONContent | undefined\n let content: JSONContent | undefined\n let initialError: Error | undefined\n if (!collaboration) {\n const initial = props.current.value ? validated(props.current.value) : { json: EMPTY_EDITOR_CONTENT }\n if ('error' in initial) initialError = initial.error\n content = 'json' in initial ? initial.json : EMPTY_EDITOR_CONTENT\n }\n\n return {\n preset,\n collaboration,\n ui,\n registry,\n ctx,\n extensions,\n overlays,\n blockActions,\n validated,\n content,\n initialError,\n get lastEmitted() {\n return lastEmitted\n },\n set lastEmitted(json: JSONContent | undefined) {\n lastEmitted = json\n },\n /** What the effects below already applied; they only react to a change. */\n applied: {\n value: props.current.value,\n editable: props.current.editable ?? true,\n metadata: props.current.metadata,\n },\n /** Teardowns of the `setup` contributions that are currently active. */\n teardowns: [] as (() => void)[],\n errorReported: false,\n announced: null as Editor | null,\n }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [surface] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n const { preset, collaboration } = surface\n\n const editor = useEditor({\n extensions: surface.extensions,\n ...(!collaboration && {\n content: surface.content,\n onUpdate: ({ editor: instance }: { editor: Editor }) => {\n const json = instance.getJSON()\n surface.lastEmitted = json\n latest.current.onChange?.(json)\n },\n }),\n editable,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n editorProps: { attributes: { class: 'tiptap' } },\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Run and teardown are paired, so StrictMode's mount, cleanup, mount\n // leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n for (const run of contributions(preset, 'setup')) {\n const teardown = run(surface.ctx)\n if (teardown) surface.teardowns.push(teardown)\n }\n return () => {\n for (const teardown of surface.teardowns.splice(0)) teardown()\n }\n }, [surface, preset])\n\n // The editor exists from the first render; announce it once its chrome is registered first, so a host toolbar that\n // receives it through `onReady` finds the chrome.\n useEffect(() => {\n if (surface.initialError && !surface.errorReported) {\n surface.errorReported = true\n latest.current.onContentError?.(surface.initialError)\n }\n const unregister = registerEditor(editor, { ui: surface.ui, preset, ctx: surface.ctx, blockActions: surface.blockActions })\n if (surface.announced !== editor) {\n surface.announced = editor\n latest.current.onReady?.(editor)\n }\n return unregister\n }, [editor, surface, preset])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => {\n if (value === surface.applied.value) return\n surface.applied.value = value\n if (collaboration || !value || editor.isDestroyed || value === surface.lastEmitted) return\n const checked = surface.validated(value)\n if ('error' in checked) {\n latest.current.onContentError?.(checked.error)\n return\n }\n editor.commands.setContent(checked.json, { emitUpdate: false })\n }, [value, editor, surface, collaboration])\n\n // `setEditable(…, false)` changes the flag without any event; an empty, history-less transaction lets components that\n // follow `isEditable` on `transaction` (block UI, the composite inspector) notice. It has no steps, so nothing syncs.\n useEffect(() => {\n if (editable === surface.applied.editable) return\n surface.applied.editable = editable\n if (editor.isDestroyed) return\n editor.setEditable(editable, false)\n editor.view.dispatch(editor.state.tr.setMeta('addToHistory', false))\n }, [editable, editor, surface])\n\n useEffect(() => {\n if (metadata === surface.applied.metadata) return\n surface.applied.metadata = metadata\n surface.registry.replace({ ...metadata })\n }, [metadata, surface])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={surface.ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {surface.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import type { AnyExtension, Editor, JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { settingsTabsOf } from '../core/define-editor'\nimport type { EditorUi } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, UploadFn } from '../core/feature'\nimport type { EditorMetadata, MetadataItem } from '../core/metadata-types'\nimport { createSurfaceSession } from '../core/surface-session'\nimport { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/**\n * Everything fixed at mount: the chrome handle and the framework-free session (preset, context, extensions, content\n * check, setup lifecycle); callbacks are read from the surface's latest props.\n */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration, value, editable, metadata, documentId, upload, abilities, slashCommands, extensions, blockActions } = props.current\n\n const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n const session = createSurfaceSession({\n app,\n preset,\n ui,\n notify: reactNotifier,\n suggestionRenderer: reactSuggestionRenderer,\n getEditor: () => parts.editor.current ?? undefined,\n collaboration,\n value,\n editable,\n metadata,\n documentId,\n upload,\n abilities,\n slashCommands,\n extensions,\n blockActions,\n onChange: content => props.current.onChange?.(content),\n onMetadataCreate: (list, item) => props.current.onMetadataCreate?.(list, item),\n onContentError: error => props.current.onContentError?.(error),\n onReady: editor => props.current.onReady?.(editor),\n })\n return { preset, ui, session }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [{ preset, ui, session }] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n\n const editor = useEditor({\n ...session.editorOptions,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Activation and deactivation are paired, so StrictMode's mount,\n // cleanup, mount leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n session.activate()\n return () => session.deactivate()\n }, [session])\n\n // The editor exists from the first render; its chrome is registered before the session announces it through\n // `onReady`, so a host toolbar that receives it finds the chrome.\n useEffect(() => session.register(editor), [editor, session])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => session.setValue(value), [value, editor, session])\n\n useEffect(() => session.setEditable(editable), [editable, editor, session])\n\n useEffect(() => session.setMetadata(metadata), [metadata, session])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {session.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":"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"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { abilitiesAtSelection } from "../core/abilities.js";
|
|
2
|
-
import { createMetadataRegistry } from "../core/metadata.js";
|
|
3
2
|
import { mergeToolbarGroups } from "../core/toolbar.js";
|
|
4
3
|
import { contributions } from "../core/define-editor.js";
|
|
5
|
-
import {
|
|
4
|
+
import { borrowedFeatureContext, editorEntryOf } from "../core/editor-ui.js";
|
|
6
5
|
import { useEditorContext } from "./context.js";
|
|
7
6
|
import { useEditorI18n } from "./use-editor-i18n.js";
|
|
8
7
|
import { cn } from "./ui/utils.js";
|
|
@@ -48,48 +47,12 @@ function EditorToolbar({ editor: given, preset }) {
|
|
|
48
47
|
const { t } = useEditorI18n();
|
|
49
48
|
const [state] = useState(() => {
|
|
50
49
|
const ref = { current: editor };
|
|
51
|
-
const fallbackMetadata = createMetadataRegistry();
|
|
52
|
-
const entry = () => editorEntryOf(ref.current);
|
|
53
|
-
const ui = () => entry()?.ui;
|
|
54
50
|
return {
|
|
55
51
|
ref,
|
|
56
|
-
ctx: {
|
|
52
|
+
ctx: borrowedFeatureContext({
|
|
57
53
|
app,
|
|
58
|
-
locale: app.locale,
|
|
59
|
-
t: app.t,
|
|
60
|
-
settings: app.settings,
|
|
61
|
-
notify: notifierOf(() => ref.current, app),
|
|
62
|
-
fetch: app.fetch,
|
|
63
|
-
ai: app.ai,
|
|
64
|
-
get metadata() {
|
|
65
|
-
return entry()?.ctx.metadata ?? fallbackMetadata;
|
|
66
|
-
},
|
|
67
|
-
ui: {
|
|
68
|
-
openSettings: (tab) => ui()?.openSettings(tab),
|
|
69
|
-
toggleSidebar: () => ui()?.toggleSidebar(),
|
|
70
|
-
toggleComments: () => ui()?.toggleComments(),
|
|
71
|
-
get commentsOpen() {
|
|
72
|
-
return ui()?.commentsOpen ?? false;
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
get documentId() {
|
|
76
|
-
return entry()?.ctx.documentId;
|
|
77
|
-
},
|
|
78
|
-
get upload() {
|
|
79
|
-
return entry()?.ctx.upload;
|
|
80
|
-
},
|
|
81
|
-
get collaboration() {
|
|
82
|
-
return entry()?.ctx.collaboration;
|
|
83
|
-
},
|
|
84
|
-
get anchorResolver() {
|
|
85
|
-
return entry()?.ctx.anchorResolver ?? null;
|
|
86
|
-
},
|
|
87
|
-
get abilities() {
|
|
88
|
-
return entry()?.ctx.abilities ?? {};
|
|
89
|
-
},
|
|
90
|
-
slashCommands: () => entry()?.ctx.slashCommands() ?? [],
|
|
91
54
|
getEditor: () => ref.current
|
|
92
|
-
}
|
|
55
|
+
})
|
|
93
56
|
};
|
|
94
57
|
});
|
|
95
58
|
state.ref.current = editor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { editorEntryOf, notifierOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport { createMetadataRegistry } from '../core/metadata'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n const fallbackMetadata = createMetadataRegistry()\n const entry = () => editorEntryOf(ref.current)\n const ui = () => entry()?.ui\n const ctx: FeatureContext = {\n app,\n locale: app.locale,\n t: app.t,\n settings: app.settings,\n notify: notifierOf(() => ref.current, app),\n fetch: app.fetch,\n ai: app.ai,\n get metadata() { return entry()?.ctx.metadata ?? fallbackMetadata },\n ui: {\n openSettings: tab => ui()?.openSettings(tab),\n toggleSidebar: () => ui()?.toggleSidebar(),\n toggleComments: () => ui()?.toggleComments(),\n get commentsOpen() { return ui()?.commentsOpen ?? false },\n },\n get documentId() { return entry()?.ctx.documentId },\n get upload() { return entry()?.ctx.upload },\n get collaboration() { return entry()?.ctx.collaboration },\n get anchorResolver() { return entry()?.ctx.anchorResolver ?? null },\n get abilities() { return entry()?.ctx.abilities ?? {} },\n slashCommands: () => entry()?.ctx.slashCommands() ?? [],\n getEditor: () => ref.current,\n }\n return { ref, ctx }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { borrowedFeatureContext, editorEntryOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n return { ref, ctx: borrowedFeatureContext({ app, getEditor: () => ref.current }) }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* The React surface's token map: the `--sk-*` variables the feature stylesheets (`styles/*.css`) read resolve to the
|
|
2
|
-
shadcn/ui theme tokens (the counterpart of `vue/kit/nuxt-ui.css`
|
|
2
|
+
shadcn/ui theme tokens (the counterpart of `vue/kit/nuxt-ui.css`). */
|
|
3
3
|
:root,
|
|
4
4
|
.dark {
|
|
5
5
|
--sk-bg: var(--background);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
--sk-border-accented: var(--border);
|
|
13
13
|
--sk-primary: var(--primary);
|
|
14
14
|
--sk-primary-fg: var(--primary-foreground);
|
|
15
|
-
--sk-success: oklch(0.72 0.19 150);
|
|
15
|
+
--sk-success: var(--color-success, oklch(0.72 0.19 150));
|
|
16
16
|
--sk-danger: var(--destructive);
|
|
17
17
|
--sk-radius: var(--radius);
|
|
18
18
|
--sk-bg-elevated: var(--card);
|
package/dist/schema/advanced.js
CHANGED
|
@@ -2,11 +2,11 @@ import { BlockId } from "../core/schema/block-ids.js";
|
|
|
2
2
|
import { NodeFlags } from "../core/schema/node-flags.js";
|
|
3
3
|
import { OpaqueFence } from "../core/schema/opaque-fence.js";
|
|
4
4
|
import { StrikeX } from "../core/schema/strike.js";
|
|
5
|
-
import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
|
|
6
5
|
import { compositeSchemaExtensions } from "../features/composites/schema.js";
|
|
7
|
-
import { mediaSchemaExtensions } from "../features/media/schema.js";
|
|
8
6
|
import { authorsSchemaExtensions } from "../features/authors/schema.js";
|
|
9
7
|
import { commentsSchemaExtensions } from "../features/comments/schema.js";
|
|
8
|
+
import { mediaSchemaExtensions } from "../features/media/schema.js";
|
|
9
|
+
import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
|
|
10
10
|
import { tablesSchemaExtensions } from "../features/tables/schema.js";
|
|
11
11
|
import { transcriptSchemaExtensions } from "../features/transcript/schema.js";
|
|
12
12
|
import StarterKit from "@tiptap/starter-kit";
|
package/dist/server/adapter.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EditorSettings } from "../settings/index.js";
|
|
2
|
-
import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
|
|
3
|
-
import "../features/ai/protocol/index.js";
|
|
4
2
|
import { AssetRecord } from "../features/media/assets.js";
|
|
5
3
|
import "../features/media/schema.js";
|
|
6
4
|
import { MentionUser } from "../features/mentions/mention.js";
|
|
7
5
|
import "../features/mentions/schema.js";
|
|
6
|
+
import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
|
|
7
|
+
import "../features/ai/protocol/index.js";
|
|
8
8
|
import { ThemeRecord } from "../features/themes/schema.js";
|
|
9
9
|
import { AnyExtension, JSONContent } from "@tiptap/core";
|
|
10
10
|
import { LanguageModel } from "ai";
|
package/dist/server/collab.js
CHANGED
|
@@ -4,9 +4,9 @@ import { migrateLegacyContent } from "../features/composites/legacy.js";
|
|
|
4
4
|
import { advancedSchemaExtensions } from "../schema/advanced.js";
|
|
5
5
|
import { decodeCollabState, encodeCollabState, isStateStale } from "./collab-state.js";
|
|
6
6
|
import { getSchema } from "@tiptap/core";
|
|
7
|
+
import { Hocuspocus } from "@hocuspocus/server";
|
|
7
8
|
import { prosemirrorToYDoc, prosemirrorToYXmlFragment, yDocToProsemirrorJSON, yXmlFragmentToProsemirror } from "@tiptap/y-tiptap";
|
|
8
9
|
import * as Y from "yjs";
|
|
9
|
-
import { Hocuspocus } from "@hocuspocus/server";
|
|
10
10
|
//#region src/server/collab.ts
|
|
11
11
|
/** Y.XmlFragment the TipTap Collaboration extension binds to (its default `field`). */
|
|
12
12
|
const COLLAB_FIELD = "default";
|
|
@@ -22,6 +22,7 @@ export declare const chatBodySchema: z.ZodObject<{
|
|
|
22
22
|
pending: "pending";
|
|
23
23
|
unchanged: "unchanged";
|
|
24
24
|
missing: "missing";
|
|
25
|
+
stale: "stale";
|
|
25
26
|
overlap: "overlap";
|
|
26
27
|
empty: "empty";
|
|
27
28
|
blocked: "blocked";
|
|
@@ -97,7 +98,8 @@ export declare const chatBodySchema: z.ZodObject<{
|
|
|
97
98
|
}, z.core.$strip>;
|
|
98
99
|
/**
|
|
99
100
|
* Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
|
|
100
|
-
* `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`,
|
|
101
|
+
* `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
|
|
102
|
+
* operation inside a block that another operation replaces or deletes, or converting into a type not in
|
|
101
103
|
* `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
|
|
102
104
|
* corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
|
|
103
105
|
* `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
|
|
@@ -120,8 +122,8 @@ export declare function completePost(ctx: AiHandlerContext, request: HandlerRequ
|
|
|
120
122
|
/** Body schema of `generatePost`. */
|
|
121
123
|
export declare const generateBodySchema: z.ZodObject<{
|
|
122
124
|
task: z.ZodEnum<{
|
|
123
|
-
generate: "generate";
|
|
124
125
|
custom: "custom";
|
|
126
|
+
generate: "generate";
|
|
125
127
|
continue: "continue";
|
|
126
128
|
reformulate: "reformulate";
|
|
127
129
|
"transcript-summary": "transcript-summary";
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { AI_LANGUAGES, STRUCTURE_MODES } from "../../settings/values.js";
|
|
2
|
-
import {
|
|
2
|
+
import { HttpError } from "../errors.js";
|
|
3
|
+
import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
|
|
4
|
+
import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
|
|
5
|
+
import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
|
|
6
|
+
import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
|
|
3
7
|
import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
|
|
4
8
|
import { AI_COMPLETE_LIMITS, cleanCompletion, completeMessages, completionReady } from "../../features/ai/protocol/complete.js";
|
|
5
9
|
import { modelRefSchema } from "../../features/ai/protocol/models.js";
|
|
6
10
|
import { AI_LIMITS, AI_TASKS, AI_TASK_IDS } from "../../features/ai/protocol/tasks.js";
|
|
7
11
|
import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
|
|
8
|
-
import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
|
|
9
12
|
import { TRANSCRIPT_CLEANUP_LIMITS, parseTranscriptCleanup, transcriptCleanupChunks, transcriptCleanupMessages, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
|
|
10
|
-
import { HttpError } from "../errors.js";
|
|
11
|
-
import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
import { jsonSchema, tool } from "ai";
|
|
14
15
|
//#region src/server/handlers/ai.ts
|
|
@@ -117,7 +118,7 @@ const agentTools = { [AGENT_TOOL]: tool({
|
|
|
117
118
|
},
|
|
118
119
|
afterId: {
|
|
119
120
|
type: ["string", "null"],
|
|
120
|
-
description: "add: ID des Blocks, nach dem eingefügt wird;
|
|
121
|
+
description: "add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."
|
|
121
122
|
},
|
|
122
123
|
markdown: {
|
|
123
124
|
type: "string",
|
|
@@ -136,7 +137,8 @@ const agentTools = { [AGENT_TOOL]: tool({
|
|
|
136
137
|
}) };
|
|
137
138
|
/**
|
|
138
139
|
* Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
|
|
139
|
-
* `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`,
|
|
140
|
+
* `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
|
|
141
|
+
* operation inside a block that another operation replaces or deletes, or converting into a type not in
|
|
140
142
|
* `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
|
|
141
143
|
* corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
|
|
142
144
|
* `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
|
|
@@ -147,11 +149,11 @@ async function chatPost(ctx, request) {
|
|
|
147
149
|
const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema);
|
|
148
150
|
const languageModel = await ctx.ai.resolveModel(model, user);
|
|
149
151
|
if (!messages.at(-1).content.trim()) throw new HttpError(400, "Nachricht fehlt");
|
|
150
|
-
const
|
|
151
|
-
const allowed = scope ? new Set(scope.filter((id) =>
|
|
152
|
+
const lines = readBlockLines(document);
|
|
153
|
+
const allowed = scope ? new Set(scope.filter((id) => lines.ids.has(id))) : null;
|
|
152
154
|
if (allowed && !allowed.size) throw new HttpError(400, "Die Auswahl enthält keine Blöcke des Dokuments");
|
|
153
155
|
const context = {
|
|
154
|
-
|
|
156
|
+
lines,
|
|
155
157
|
allowed,
|
|
156
158
|
targets: new Set(targets)
|
|
157
159
|
};
|