@ecsplico/editor 2.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 +18 -0
- package/README.md +4 -3
- 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/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/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 -0
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.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/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/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/tokens.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 +1 -1
- package/dist/vue/context.js +1 -1
- package/dist/vue/context.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 +97 -4
- package/docs/llms.md +9 -1
- package/docs/usage.md +10 -5
- package/package.json +67 -5
|
@@ -1,13 +1,10 @@
|
|
|
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 { editorEntryOf } from "../../core/editor-ui.js";
|
|
4
|
+
import { borrowedFeatureContext, editorEntryOf } from "../../core/editor-ui.js";
|
|
6
5
|
import { useUiKit } from "../kit/use-ui-kit.js";
|
|
7
6
|
import { useEditorContext } from "../context.js";
|
|
8
7
|
import { useEditorI18n } from "../use-editor-i18n.js";
|
|
9
|
-
import { useEditorUi } from "../use-editor-ui.js";
|
|
10
|
-
import { useNotifier } from "../use-notifier.js";
|
|
11
8
|
import { trackedStore } from "../store-ref.js";
|
|
12
9
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createVNode, defineComponent, getCurrentInstance, openBlock, renderList, resolveDynamicComponent, toRaw, unref, withCtx, withModifiers } from "vue";
|
|
13
10
|
//#region src/vue/components/EditorToolbar.vue
|
|
@@ -34,47 +31,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
34
31
|
const { Button: UButton, DropdownMenu: UDropdownMenu, FieldGroup: UFieldGroup, Tooltip: UTooltip } = useUiKit();
|
|
35
32
|
const appContext = useEditorContext();
|
|
36
33
|
const { t } = useEditorI18n();
|
|
37
|
-
const editorUi = useEditorUi(() => props.editor);
|
|
38
34
|
const instance = getCurrentInstance();
|
|
39
|
-
const
|
|
40
|
-
const entry = () => editorEntryOf(props.editor ? toRaw(props.editor) : void 0);
|
|
41
|
-
const ctx = {
|
|
35
|
+
const ctx = borrowedFeatureContext({
|
|
42
36
|
app: appContext,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
fetch: appContext.fetch,
|
|
48
|
-
ai: appContext.ai,
|
|
49
|
-
get metadata() {
|
|
50
|
-
return entry()?.ctx.metadata ?? fallbackMetadata;
|
|
51
|
-
},
|
|
52
|
-
ui: {
|
|
53
|
-
openSettings: (tab) => editorUi.value?.openSettings(tab),
|
|
54
|
-
toggleSidebar: () => editorUi.value?.toggleSidebar(),
|
|
55
|
-
toggleComments: () => editorUi.value?.toggleComments(),
|
|
56
|
-
get commentsOpen() {
|
|
57
|
-
return editorUi.value?.commentsOpen ?? false;
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
get documentId() {
|
|
61
|
-
return entry()?.ctx.documentId;
|
|
62
|
-
},
|
|
63
|
-
get upload() {
|
|
64
|
-
return entry()?.ctx.upload;
|
|
65
|
-
},
|
|
66
|
-
get collaboration() {
|
|
67
|
-
return entry()?.ctx.collaboration;
|
|
68
|
-
},
|
|
69
|
-
get anchorResolver() {
|
|
70
|
-
return entry()?.ctx.anchorResolver ?? null;
|
|
71
|
-
},
|
|
72
|
-
get abilities() {
|
|
73
|
-
return entry()?.ctx.abilities ?? {};
|
|
74
|
-
},
|
|
75
|
-
slashCommands: () => entry()?.ctx.slashCommands() ?? [],
|
|
76
|
-
getEditor: () => props.editor
|
|
77
|
-
};
|
|
37
|
+
getEditor: () => props.editor,
|
|
38
|
+
unwrap: toRaw,
|
|
39
|
+
settings: trackedStore(appContext.settings)
|
|
40
|
+
});
|
|
78
41
|
const built = /* @__PURE__ */ new WeakMap();
|
|
79
42
|
function groupsOf(preset) {
|
|
80
43
|
if (!preset) return [];
|
|
@@ -88,7 +51,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
88
51
|
groupsOf(props.preset);
|
|
89
52
|
const visibleGroups = computed(() => {
|
|
90
53
|
const editor = props.editor;
|
|
91
|
-
return groupsOf(
|
|
54
|
+
return groupsOf(editorEntryOf(editor && toRaw(editor))?.preset ?? props.preset).filter((group) => !editor || !group.visible || group.visible(editor)).map((group) => ({
|
|
92
55
|
group,
|
|
93
56
|
items: group.items.filter((item) => !editor || !item.visible || item.visible(editor))
|
|
94
57
|
})).filter(({ items }) => items.length > 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorToolbar.js","names":["_openBlock","_createElementBlock","_unref","_Fragment","_renderList","_createCommentVNode","_createVNode","_withCtx","_createBlock","_resolveDynamicComponent","_withModifiers"],"sources":["../../../src/vue/components/EditorToolbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useUiKit } from '../kit/use-ui-kit'\nimport type { Editor } from '@tiptap/core'\nimport { type Component, computed, getCurrentInstance, toRaw } from 'vue'\nimport { abilitiesAtSelection } from '../../core/abilities'\nimport { contributions } from '../../core/define-editor'\nimport { editorEntryOf } from '../../core/editor-ui'\nimport { useEditorContext } from '../context'\nimport { useEditorI18n } from '../use-editor-i18n'\nimport { useEditorUi } from '../use-editor-ui'\nimport { useNotifier } from '../use-notifier'\nimport { trackedStore } from '../store-ref'\nimport type { EditorPreset, 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'\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 * `ready` event); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. Never reads a global active editor.\n */\nconst props = defineProps<{ editor?: Editor, preset?: EditorPreset }>()\n\nconst { Button: UButton, DropdownMenu: UDropdownMenu, FieldGroup: UFieldGroup, Tooltip: UTooltip } = useUiKit()\n\nconst appContext = useEditorContext()\nconst { t } = useEditorI18n()\nconst editorUi = useEditorUi(() => props.editor)\nconst instance = getCurrentInstance()\nconst fallbackMetadata = createMetadataRegistry()\n\nconst entry = () => editorEntryOf(props.editor ? toRaw(props.editor) : undefined)\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.\nconst ctx: FeatureContext = {\n app: appContext,\n locale: appContext.locale,\n t: appContext.t,\n notify: useNotifier(() => props.editor),\n // Tracked: `active`/`visible`/`menu` read the settings during render, which must re-run when they change.\n settings: trackedStore(appContext.settings),\n fetch: appContext.fetch,\n ai: appContext.ai,\n get metadata() { return entry()?.ctx.metadata ?? fallbackMetadata },\n ui: {\n openSettings: tab => editorUi.value?.openSettings(tab),\n toggleSidebar: () => editorUi.value?.toggleSidebar(),\n toggleComments: () => editorUi.value?.toggleComments(),\n get commentsOpen() { return editorUi.value?.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: () => props.editor,\n}\n\n// Factories run during setup (they may use composables that inject); a later preset is built within the app context.\nconst built = new WeakMap<EditorPreset, ToolbarGroup[]>()\nfunction groupsOf(preset: EditorPreset | undefined): ToolbarGroup[] {\n if (!preset) return []\n const cached = built.get(preset)\n if (cached) return cached\n const build = () => mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n const groups = instance ? instance.appContext.app.runWithContext(build) : build()\n built.set(preset, groups)\n return groups\n}\ngroupsOf(props.preset)\n\nconst visibleGroups = computed(() => {\n const editor = props.editor\n const groups = groupsOf(entry()?.preset ?? props.preset)\n return groups\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\n// `active`, `can()` and the abilities read the vue-3 Editor's reactive `state`, so these re-evaluate on every transaction.\nconst abilities = computed(() => props.editor ? abilitiesAtSelection(props.editor) : new Set<Ability>())\nfunction active(item: ToolbarItem): boolean {\n return !!props.editor && !!item.active?.(props.editor)\n}\nfunction disabled(item: ToolbarItem): boolean {\n const editor = props.editor\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.value.has(item.ability)\n}\nfunction run(item: ToolbarItem) {\n if (props.editor) item.run?.(props.editor)\n}\nfunction menuOf(item: ToolbarItem): MenuItem[] {\n return props.editor && item.menu ? item.menu(props.editor) : []\n}\n/** Descriptors are framework-agnostic (`component: unknown`); in this Vue toolbar they are Vue components. */\nfunction componentOf(item: ToolbarItem): Component | undefined {\n return item.component as Component | undefined\n}\n</script>\n\n<template>\n <div class=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" :aria-label=\"t('toolbar.label')\">\n <template v-for=\"({ group, items }, gi) in visibleGroups\" :key=\"group.id\">\n <span v-if=\"gi > 0\" role=\"separator\" aria-orientation=\"vertical\" class=\"mx-2 h-6 w-[2px] shrink-0 bg-[var(--sk-border-accented)]\" />\n <UFieldGroup size=\"sm\">\n <template v-for=\"item in items\" :key=\"item.id\">\n <component :is=\"componentOf(item)\" v-if=\"componentOf(item)\" :editor=\"props.editor\" />\n <UDropdownMenu v-else-if=\"item.menu\" :items=\"menuOf(item)\">\n <UTooltip :text=\"item.label\" :kbds=\"item.kbds\">\n <UButton\n :key=\"item.icon\"\n color=\"neutral\"\n variant=\"ghost\"\n :icon=\"item.icon\"\n :aria-label=\"item.label\"\n :disabled=\"disabled(item)\"\n @mousedown.prevent\n />\n </UTooltip>\n </UDropdownMenu>\n <UTooltip v-else :text=\"item.label\" :kbds=\"item.kbds\">\n <UButton\n :key=\"item.icon\"\n color=\"neutral\"\n variant=\"ghost\"\n active-color=\"primary\"\n active-variant=\"soft\"\n :icon=\"item.icon\"\n :aria-label=\"item.label\"\n :active=\"active(item)\"\n :aria-pressed=\"item.active ? active(item) : undefined\"\n :disabled=\"disabled(item)\"\n @mousedown.prevent\n @click=\"run(item)\"\n />\n </UTooltip>\n </template>\n </UFieldGroup>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditorToolbar.js","names":["_openBlock","_createElementBlock","_unref","_Fragment","_renderList","_createCommentVNode","_createVNode","_withCtx","_createBlock","_resolveDynamicComponent","_withModifiers"],"sources":["../../../src/vue/components/EditorToolbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useUiKit } from '../kit/use-ui-kit'\nimport type { Editor } from '@tiptap/core'\nimport { type Component, computed, getCurrentInstance, toRaw } from 'vue'\nimport { abilitiesAtSelection } from '../../core/abilities'\nimport { contributions } from '../../core/define-editor'\nimport { borrowedFeatureContext, editorEntryOf } from '../../core/editor-ui'\nimport { useEditorContext } from '../context'\nimport { useEditorI18n } from '../use-editor-i18n'\nimport { trackedStore } from '../store-ref'\nimport type { EditorPreset } from '../../core/feature'\nimport type { Ability } from '../../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../../core/toolbar'\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 * `ready` event); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. Never reads a global active editor.\n */\nconst props = defineProps<{ editor?: Editor, preset?: EditorPreset }>()\n\nconst { Button: UButton, DropdownMenu: UDropdownMenu, FieldGroup: UFieldGroup, Tooltip: UTooltip } = useUiKit()\n\nconst appContext = useEditorContext()\nconst { t } = useEditorI18n()\nconst instance = getCurrentInstance()\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.\nconst ctx = borrowedFeatureContext({\n app: appContext,\n getEditor: () => props.editor,\n unwrap: toRaw,\n // Tracked: `active`/`visible`/`menu` read the settings during render, which must re-run when they change.\n settings: trackedStore(appContext.settings),\n})\n\n// Factories run during setup (they may use composables that inject); a later preset is built within the app context.\nconst built = new WeakMap<EditorPreset, ToolbarGroup[]>()\nfunction groupsOf(preset: EditorPreset | undefined): ToolbarGroup[] {\n if (!preset) return []\n const cached = built.get(preset)\n if (cached) return cached\n const build = () => mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n const groups = instance ? instance.appContext.app.runWithContext(build) : build()\n built.set(preset, groups)\n return groups\n}\ngroupsOf(props.preset)\n\nconst visibleGroups = computed(() => {\n const editor = props.editor\n const groups = groupsOf(editorEntryOf(editor && toRaw(editor))?.preset ?? props.preset)\n return groups\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\n// `active`, `can()` and the abilities read the vue-3 Editor's reactive `state`, so these re-evaluate on every transaction.\nconst abilities = computed(() => props.editor ? abilitiesAtSelection(props.editor) : new Set<Ability>())\nfunction active(item: ToolbarItem): boolean {\n return !!props.editor && !!item.active?.(props.editor)\n}\nfunction disabled(item: ToolbarItem): boolean {\n const editor = props.editor\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.value.has(item.ability)\n}\nfunction run(item: ToolbarItem) {\n if (props.editor) item.run?.(props.editor)\n}\nfunction menuOf(item: ToolbarItem): MenuItem[] {\n return props.editor && item.menu ? item.menu(props.editor) : []\n}\n/** Descriptors are framework-agnostic (`component: unknown`); in this Vue toolbar they are Vue components. */\nfunction componentOf(item: ToolbarItem): Component | undefined {\n return item.component as Component | undefined\n}\n</script>\n\n<template>\n <div class=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" :aria-label=\"t('toolbar.label')\">\n <template v-for=\"({ group, items }, gi) in visibleGroups\" :key=\"group.id\">\n <span v-if=\"gi > 0\" role=\"separator\" aria-orientation=\"vertical\" class=\"mx-2 h-6 w-[2px] shrink-0 bg-[var(--sk-border-accented)]\" />\n <UFieldGroup size=\"sm\">\n <template v-for=\"item in items\" :key=\"item.id\">\n <component :is=\"componentOf(item)\" v-if=\"componentOf(item)\" :editor=\"props.editor\" />\n <UDropdownMenu v-else-if=\"item.menu\" :items=\"menuOf(item)\">\n <UTooltip :text=\"item.label\" :kbds=\"item.kbds\">\n <UButton\n :key=\"item.icon\"\n color=\"neutral\"\n variant=\"ghost\"\n :icon=\"item.icon\"\n :aria-label=\"item.label\"\n :disabled=\"disabled(item)\"\n @mousedown.prevent\n />\n </UTooltip>\n </UDropdownMenu>\n <UTooltip v-else :text=\"item.label\" :kbds=\"item.kbds\">\n <UButton\n :key=\"item.icon\"\n color=\"neutral\"\n variant=\"ghost\"\n active-color=\"primary\"\n active-variant=\"soft\"\n :icon=\"item.icon\"\n :aria-label=\"item.label\"\n :active=\"active(item)\"\n :aria-pressed=\"item.active ? active(item) : undefined\"\n :disabled=\"disabled(item)\"\n @mousedown.prevent\n @click=\"run(item)\"\n />\n </UTooltip>\n </template>\n </UFieldGroup>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|
package/dist/vue/context.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface VueEditorOptions extends EditorContextOptions {
|
|
|
17
17
|
* and timer stop with the app. App-level notices (`options.notify`, default: queued until the first `EditorSurface`
|
|
18
18
|
* mounts and hands over the kit's `notifier()`) show as the kit's toasts.
|
|
19
19
|
*/
|
|
20
|
-
export declare function createEditor({ kit, ...options }
|
|
20
|
+
export declare function createEditor({ kit, ...options }?: VueEditorOptions): Plugin;
|
|
21
21
|
/** The app's editor context; throws a descriptive error when `createEditor()` was not installed. */
|
|
22
22
|
export declare function useEditorContext(): EditorContext;
|
|
23
23
|
//#endregion
|
package/dist/vue/context.js
CHANGED
|
@@ -15,7 +15,7 @@ const EDITOR_CONTEXT = Symbol("tiptap-editor:context");
|
|
|
15
15
|
* and timer stop with the app. App-level notices (`options.notify`, default: queued until the first `EditorSurface`
|
|
16
16
|
* mounts and hands over the kit's `notifier()`) show as the kit's toasts.
|
|
17
17
|
*/
|
|
18
|
-
function createEditor({ kit = nuxtUiKit(), ...options }) {
|
|
18
|
+
function createEditor({ kit = nuxtUiKit(), ...options } = {}) {
|
|
19
19
|
return { install(app) {
|
|
20
20
|
const ctx = createEditorContext({
|
|
21
21
|
notify: createQueuedNotifier(),
|
package/dist/vue/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../src/vue/context.ts"],"sourcesContent":["import { type InjectionKey, inject, type Plugin } from 'vue'\nimport { createEditorContext, type EditorContext, type EditorContextOptions } from '../core/context'\nimport { createQueuedNotifier } from './notify'\nimport { createSettingsBridge } from './settings'\nimport { nuxtUiKit } from './kit/nuxt-ui'\nimport { VUE_UI_KIT } from './kit/use-ui-kit'\nimport type { VueUiKit } from './kit/types'\n\nexport const EDITOR_CONTEXT: InjectionKey<EditorContext> = Symbol('tiptap-editor:context')\n\n/** What `createEditor` takes: the framework-free context options plus the UI kit that renders the chrome. */\nexport interface VueEditorOptions extends EditorContextOptions {\n /** The widgets the chrome components render. Default: `nuxtUiKit()` (`/vue/kit/nuxt-ui`), which needs `@nuxt/ui` set up in the host; pass your own `VueUiKit` to render the chrome with another library. */\n kit?: VueUiKit\n}\n\n/**\n * The editor context of a Vue app: the settings shared by all editors of the app, the HTTP client and the UI kit\n * (`useUiKit()`). Install with `app.use(createEditor())`. The settings are read from localStorage right away (client only) and every change is\n * written back. With `settings.remote`, `GET /api/editor/settings` (on the first `useEditorSettings()`) answers: `200`\n * replaces them with the user's account settings (`synced`), `204` (the host's adapter keeps no settings), `401` or a\n * network error keep the local ones; once synced every change is also sent to the server (`PUT`, debounced). Watcher\n * and timer stop with the app. App-level notices (`options.notify`, default: queued until the first `EditorSurface`\n * mounts and hands over the kit's `notifier()`) show as the kit's toasts.\n */\nexport function createEditor({ kit = nuxtUiKit(), ...options }: VueEditorOptions): Plugin {\n return {\n install(app) {\n const ctx = createEditorContext({ notify: createQueuedNotifier(), ...options })\n app.provide(VUE_UI_KIT, kit)\n app.provide(EDITOR_CONTEXT, ctx)\n const bridge = createSettingsBridge(ctx)\n app.onUnmount(() => {\n bridge.dispose()\n ctx.dispose()\n })\n },\n }\n}\n\n/** The app's editor context; throws a descriptive error when `createEditor()` was not installed. */\nexport function useEditorContext(): EditorContext {\n const context = inject(EDITOR_CONTEXT, null)\n if (!context) throw new Error('@ecsplico/editor: no editor context; install it with app.use(createEditor())')\n return context\n}\n"],"mappings":";;;;;;;AAQA,MAAa,iBAA8C,OAAO,uBAAuB;;;;;;;;;;AAiBzF,SAAgB,aAAa,EAAE,MAAM,UAAU,GAAG,GAAG,
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../src/vue/context.ts"],"sourcesContent":["import { type InjectionKey, inject, type Plugin } from 'vue'\nimport { createEditorContext, type EditorContext, type EditorContextOptions } from '../core/context'\nimport { createQueuedNotifier } from './notify'\nimport { createSettingsBridge } from './settings'\nimport { nuxtUiKit } from './kit/nuxt-ui'\nimport { VUE_UI_KIT } from './kit/use-ui-kit'\nimport type { VueUiKit } from './kit/types'\n\nexport const EDITOR_CONTEXT: InjectionKey<EditorContext> = Symbol('tiptap-editor:context')\n\n/** What `createEditor` takes: the framework-free context options plus the UI kit that renders the chrome. */\nexport interface VueEditorOptions extends EditorContextOptions {\n /** The widgets the chrome components render. Default: `nuxtUiKit()` (`/vue/kit/nuxt-ui`), which needs `@nuxt/ui` set up in the host; pass your own `VueUiKit` to render the chrome with another library. */\n kit?: VueUiKit\n}\n\n/**\n * The editor context of a Vue app: the settings shared by all editors of the app, the HTTP client and the UI kit\n * (`useUiKit()`). Install with `app.use(createEditor())`. The settings are read from localStorage right away (client only) and every change is\n * written back. With `settings.remote`, `GET /api/editor/settings` (on the first `useEditorSettings()`) answers: `200`\n * replaces them with the user's account settings (`synced`), `204` (the host's adapter keeps no settings), `401` or a\n * network error keep the local ones; once synced every change is also sent to the server (`PUT`, debounced). Watcher\n * and timer stop with the app. App-level notices (`options.notify`, default: queued until the first `EditorSurface`\n * mounts and hands over the kit's `notifier()`) show as the kit's toasts.\n */\nexport function createEditor({ kit = nuxtUiKit(), ...options }: VueEditorOptions = {}): Plugin {\n return {\n install(app) {\n const ctx = createEditorContext({ notify: createQueuedNotifier(), ...options })\n app.provide(VUE_UI_KIT, kit)\n app.provide(EDITOR_CONTEXT, ctx)\n const bridge = createSettingsBridge(ctx)\n app.onUnmount(() => {\n bridge.dispose()\n ctx.dispose()\n })\n },\n }\n}\n\n/** The app's editor context; throws a descriptive error when `createEditor()` was not installed. */\nexport function useEditorContext(): EditorContext {\n const context = inject(EDITOR_CONTEXT, null)\n if (!context) throw new Error('@ecsplico/editor: no editor context; install it with app.use(createEditor())')\n return context\n}\n"],"mappings":";;;;;;;AAQA,MAAa,iBAA8C,OAAO,uBAAuB;;;;;;;;;;AAiBzF,SAAgB,aAAa,EAAE,MAAM,UAAU,GAAG,GAAG,YAA8B,CAAC,GAAW;CAC7F,OAAO,EACL,QAAQ,KAAK;EACX,MAAM,MAAM,oBAAoB;GAAE,QAAQ,qBAAqB;GAAG,GAAG;EAAQ,CAAC;EAC9E,IAAI,QAAQ,YAAY,GAAG;EAC3B,IAAI,QAAQ,gBAAgB,GAAG;EAC/B,MAAM,SAAS,qBAAqB,GAAG;EACvC,IAAI,gBAAgB;GAClB,OAAO,QAAQ;GACf,IAAI,QAAQ;EACd,CAAC;CACH,EACF;AACF;;AAGA,SAAgB,mBAAkC;CAChD,MAAM,UAAU,OAAO,gBAAgB,IAAI;CAC3C,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,8EAA8E;CAC5G,OAAO;AACT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EditorUi } from "../core/editor-ui.js";
|
|
2
|
-
import { ComputedRef, InjectionKey, MaybeRefOrGetter } from "vue";
|
|
3
2
|
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { ComputedRef, InjectionKey, MaybeRefOrGetter } from "vue";
|
|
4
4
|
//#region src/vue/use-editor-ui.d.ts
|
|
5
5
|
/** Provided by `EditorSurface` to its subtree (e.g. `AiSelectionMenu` → `ModelQuickSwitch`). */
|
|
6
6
|
export declare const EDITOR_UI: InjectionKey<EditorUi>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Notifier } from "../core/notify.js";
|
|
2
|
-
import { MaybeRefOrGetter } from "vue";
|
|
3
2
|
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { MaybeRefOrGetter } from "vue";
|
|
4
4
|
//#region src/vue/use-notifier.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* The notifier of `editor`'s surface (its toasts), else the app context's (see `notifierOf`): what `createEditor({ notify })`
|
package/docs/integration.md
CHANGED
|
@@ -17,6 +17,7 @@ How a host composes the editor from features, where settings and view state live
|
|
|
17
17
|
11. [Settings persistence and app lifetime](#11-settings-persistence-and-app-lifetime)
|
|
18
18
|
12. [Migrating from `#tiptap-editor`](#12-migrating-from-tiptap-editor)
|
|
19
19
|
13. [Dependency rules and bundle proof](#13-dependency-rules-and-bundle-proof)
|
|
20
|
+
14. [Storage adapters, comment stores, Nitro host, `useEditorDocument`](#14-storage-adapters-comment-stores-nitro-host-useeditordocument)
|
|
20
21
|
|
|
21
22
|
## 1. Feature composition and entries
|
|
22
23
|
|
|
@@ -328,7 +329,7 @@ Two optional features build on the session's `user`; the advanced editor contain
|
|
|
328
329
|
- `authorsFeature()` marks every text run the user inserts with an `author` mark (`{ id, name, color }`; one author per run, remote changes and Yjs undo/redo keep the original author's mark). Without `collaboration.user` it attributes nothing, so standalone editors write unmarked text. Colouring is a per-user preference (`editing.authorColors`, `editing.authorColorStyle`).
|
|
329
330
|
- `commentsFeature()` (requires `collab`) stores comment threads in the Y.Doc, in a `Y.Map` named `comments`; the commented range is a `comment` mark. Threads therefore sync live and persist with the document, but they are **not** part of the JSON/Markdown adapter content. Without a Y.Doc and a `collaboration.user` the UI says „Kommentare gibt es nur in gemeinsam bearbeiteten Dokumenten.“ and nothing can be written.
|
|
330
331
|
- Both marks are in `advancedSchemaExtensions()`, so the server's Yjs↔JSON conversion keeps them. They have no Markdown form: Markdown export, imports and the AI context ignore them (`DocumentMarkdown`).
|
|
331
|
-
- Surface contract: `v-model:comments-open` (Vue `EditorSurface`/`AdvancedEditor`) or `commentsOpen`/`onCommentsOpenChange` (React) is the open state of the margin column, which the host renders with `CommentsMargin` inside the editor's scroll container; `useEditorUi().toggleComments()` and `Mod-Shift-m` toggle it, and it excludes the AI sidebar.
|
|
332
|
+
- Surface contract: `v-model:comments-open` (Vue `EditorSurface`/`AdvancedEditor`) or `commentsOpen`/`onCommentsOpenChange` (React) is the open state of the margin column, which the host renders with `CommentsMargin` inside the editor's scroll container; `useEditorUi().toggleComments()` and `Mod-Shift-m` toggle it, and it excludes the AI sidebar. The column lays its cards out with the framework-free `layoutMargin` / `createMarginController` of `@ecsplico/editor/comments`: the active thread sits exactly at its text's line, the others are one-line cards out of its way, resolved threads sit behind „Erledigt (n)“, and a thread whose text was deleted stays greyed out at the deletion point („Text gelöscht“) until undo re-attaches it ([usage guide](./usage.md#authors-and-comments)).
|
|
332
333
|
|
|
333
334
|
### Positions that survive concurrent edits
|
|
334
335
|
|
|
@@ -390,7 +391,7 @@ PORT=3200 node .output/server/index.mjs
|
|
|
390
391
|
|
|
391
392
|
## 10. The file browser and `DocumentStore`
|
|
392
393
|
|
|
393
|
-
`DocumentTree` (`@ecsplico/editor/tree/vue` or `@ecsplico/editor/tree/react`) is the file drawer: a „Dokumente“ root with folders and documents, create/rename/move/delete, drag and drop, kind filters per item, and the open document's outline (headings of a simple document, or the composites tree in the preferred structure) listed under it. It knows nothing about routing, HTTP or toasts of the host: the host passes a `DocumentStore`. The store contract, the types and the view-state model live in the framework-free `@ecsplico/editor/tree`; the two components
|
|
394
|
+
`DocumentTree` (`@ecsplico/editor/tree/vue` or `@ecsplico/editor/tree/react`) is the file drawer: a „Dokumente“ root with folders and documents, create/rename/move/delete, drag and drop, kind filters per item, a title filter („Titel filtern“), and the open document's outline (headings of a simple document, or the composites tree in the preferred structure) listed under it. It knows nothing about routing, HTTP or toasts of the host: the host passes a `DocumentStore`. The store contract, the types and the view-state model live in the framework-free `@ecsplico/editor/tree`; the two components render one internal tree model (rows, drag and drop, title filter, rename and create; not exported). The tree's own messages (delete with undo) are notices: Vue needs `<UApp>`, React needs `EditorContextProvider` and a mounted `Toaster`.
|
|
394
395
|
|
|
395
396
|
```vue
|
|
396
397
|
<script setup lang="ts">
|
|
@@ -464,7 +465,7 @@ Semantics every implementation must keep:
|
|
|
464
465
|
### Filters, virtual root, outline
|
|
465
466
|
|
|
466
467
|
- Filters hide kinds below a tree item (per item key); they persist in `localStorage` under `tip-tree:node-filters`. The view state is a framework-free model: `createTreeView()` builds one (`nodeFilters`, `expanded`, `expand`, `setKindHidden`, `clearFilter`, `clearAllFilters`, `followFolderMove`), `treeView(context)` returns the one of an `EditorContext`'s app, and `useTreeView()` (`/tree/vue`: `nodeFilters` and `expanded` as refs; `/tree/react`: as plain values that re-render the component) reads it, so several trees of one app share it (see [§11](#11-settings-persistence-and-app-lifetime)).
|
|
467
|
-
- The settings value `blocks.treeRoot` is `'folders'` (default: folders with their documents, the open document is an entry among them) or `'file'`. With `'file'`, an open document in composite structure (`blocks.structure === 'composite'`) is the *virtual root* of the tree: its composites are the nested items and dragging one moves the block (`moveBlockInto`). With no open document or the headings structure the tree shows the folders instead. Switching it in the settings panel re-roots the tree immediately.
|
|
468
|
+
- The settings value `blocks.treeRoot` is `'folders'` (default: folders with their documents, the open document is an entry among them) or `'file'`. With `'file'`, an open document in composite structure (`blocks.structure === 'composite'`) is the *virtual root* of the tree: its composites are the nested items and dragging one moves the block (`moveBlockInto`) unless the target Baustein's kind does not accept it (`CompositeKind.accepts`): the drop is then refused, outlined red with the reason of `moveIntoVerdict` under the row. With no open document or the headings structure the tree shows the folders instead. Switching it in the settings panel re-roots the tree immediately.
|
|
468
469
|
- The outline of the open document is read from `editor` (composites in composite structure, headings otherwise) and refreshed, debounced, on `update`. `DocumentTree` re-subscribes when the `editor` prop changes and removes its listener when the editor changes or the tree unmounts, so switching documents never leaves a stale listener on the previous editor.
|
|
469
470
|
|
|
470
471
|
### Not in the library
|
|
@@ -493,7 +494,7 @@ The former Nuxt module `modules/tiptap-editor` (alias `#tiptap-editor`, auto-imp
|
|
|
493
494
|
| `AiSidebar`, `AiReviewBar`, `AiSelectionMenu`, `ModelQuickSwitch`, `useAiAssistant`, `useDocumentChat`, `useModelProfiles` | `@ecsplico/editor/ai/vue` (`/ai/react` on React; the framework-free models `modelProfiles`, `createAiAssistant`, `createDocumentChat` in `@ecsplico/editor/ai`) |
|
|
494
495
|
| `useFileImport`, `AudioRecorderModal`, `useAudioRecorder`, `pickFiles` | `@ecsplico/editor/media/vue` (`/media/react` on React); `pickFiles` and the framework-free models in `@ecsplico/editor/media` |
|
|
495
496
|
| `createTranscriptCleanup`, `hasTranscriptSegments` | `@ecsplico/editor/integrations/transcript-ai` |
|
|
496
|
-
| `defaultCompositeKinds(t)`, `CompositeKind`, `headingsMarkdown`, `compositeOutline`, `moveBlockInto`, `compositeJSON`, markdown fence helpers, `CompositeTable`, `CompositeInspector`, `useCompositeRows`, `setComposite*` writers | `@ecsplico/editor/composites` (framework-free), `/composites/vue` or `/composites/react` for the components and `useCompositeRows` (schema subset: `/composites/schema`) |
|
|
497
|
+
| `defaultCompositeKinds(t)`, `CompositeKind`, `kindAccepts`, `AcceptedChildren`, `headingsMarkdown`, `compositeOutline`, `moveBlockInto`, `moveIntoVerdict`, `compositeJSON`, markdown fence helpers, `CompositeTable`, `CompositeInspector`, `useCompositeRows`, `setComposite*` writers | `@ecsplico/editor/composites` (framework-free), `/composites/vue` or `/composites/react` for the components and `useCompositeRows` (schema subset: `/composites/schema`) |
|
|
497
498
|
| `headingOutline`, `revealBlockInEditor` (framework-free), `EditorOutline` (`/outline/vue`, `/outline/react`) | `@ecsplico/editor/outline` |
|
|
498
499
|
| `EMPTY_EDITOR_CONTENT`, `ABILITY`, `Ability`, `findBlockById`, block ID helpers | `@ecsplico/editor/core/schema` |
|
|
499
500
|
| `fetchErrorMessage`, `downloadText`, abilities helpers (`abilitiesAtSelection`, `hasAbility`) | `@ecsplico/editor/core` |
|
|
@@ -535,3 +536,95 @@ bun run check:editor-composition
|
|
|
535
536
|
```
|
|
536
537
|
|
|
537
538
|
`check:editor-entries` collects the module IDs of every emitted chunk (including dynamic ones) and fails on a forbidden module, on a source module outside the fixture's allow-list, or on a missing required module. To check your own composition, add `packages/editor/checks/fixtures/<name>/` (an `index.html` plus `main.ts`/`main.tsx` that mounts the surface with its toolbar, so unused factories cannot be tree-shaken away) and one `FIXTURES` entry with `forbid`, `require` and `allowSource` patterns (and `plugins: 'react'` for a React fixture) in `packages/editor/checks/entries.ts`. Fixtures exist for `simple`, each feature (Vue, `-react` and `headless-` variants), each integration, `tree`, `tree-react`, `headless-tree`, `advanced`, `advanced-react`, the headless core (`core-headless`), the React simple editor (`react-simple`) and the Node schema.
|
|
539
|
+
|
|
540
|
+
## 14. Storage adapters, comment stores, Nitro host, `useEditorDocument`
|
|
541
|
+
|
|
542
|
+
Ready-made implementations of the `EditorAdapter` contract (§3), comment stores, a Nuxt-free Nitro host and a Vue composable ship inside this package as subpath entries (formerly the separate package `@ecsplico/editor-adapters`; from 2.1.0 `@ecsplico/editor` is the only dependency). A host picks where documents live without touching the editor. All adapters satisfy the same contract test (`test/adapters/adapter-contract.test.ts`).
|
|
543
|
+
|
|
544
|
+
```sh
|
|
545
|
+
bun add @ecsplico/editor # plus drizzle-orm only for the two drizzle entries
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
| Entry | Adapter | Storage | Needs |
|
|
549
|
+
|---|---|---|---|
|
|
550
|
+
| `@ecsplico/editor/adapters/json` | `createJsonAdapter({ dir, users? })` | files under `dir` | nothing |
|
|
551
|
+
| `@ecsplico/editor/adapters/drizzle` | `createDrizzleAdapter({ db, tables: editorTables() })` | SQLite via drizzle (`bun:sqlite`, better-sqlite3, libsql, D1) | `drizzle-orm` |
|
|
552
|
+
| `@ecsplico/editor/adapters/forge` | `createForgeAdapter({ content, state, schema, authenticate, … })` | the host's Markdown files; state, assets and settings go to a `state` adapter | nothing |
|
|
553
|
+
| `@ecsplico/editor/anchors` | `captureAnchor`, `capturePoint`, `resolveAnchor`, `mapAnchor` | pure functions over ProseMirror documents | `@tiptap/pm` |
|
|
554
|
+
| `@ecsplico/editor/comments/json` | `createJsonCommentStore({ file })` | one JSON file (forge's `concept-discussions.json` layout) | nothing |
|
|
555
|
+
| `@ecsplico/editor/comments/drizzle` | `createDrizzleCommentStore({ db, tables: commentTables() })` | SQLite via drizzle | `drizzle-orm` |
|
|
556
|
+
| `@ecsplico/editor/comments/server` | `handleCommentsRequest(store, { method, documentId, body })` | the REST handler a host routes its comments endpoint to | nothing |
|
|
557
|
+
| `@ecsplico/editor/comments/client` | `createRestCommentsModel({ fetch, documentId, user, canComment, getEditor })` | the editor's `CommentsModel` over that endpoint (framework-free) | nothing |
|
|
558
|
+
| `@ecsplico/editor/server/nitro` | `installEditorServer(nitroApp, { adapter, collab, ai?, comments? })` | mounts the editor's routes, the collaboration websocket and optional AI and comments routes on a Nitro app | `h3` |
|
|
559
|
+
| `@ecsplico/editor/adapters/vue` | `useEditorDocument(id, { fetch, collaboration?, user? })` | the document a Vue editor surface binds to: `modelValue`, `dirty`, `save()`, `collaboration`, `comments` | `vue`, `yjs`, `@hocuspocus/provider` |
|
|
560
|
+
| `@ecsplico/editor/adapters` | types only | | |
|
|
561
|
+
|
|
562
|
+
Peers: `drizzle-orm` (`^0.45.0`, optional) is needed by `./adapters/drizzle` and `./comments/drizzle` only; `h3` (optional) by `./server/nitro`; `vue` (optional), `yjs`, `@hocuspocus/provider` by `./adapters/vue`. `bun run check:editor-entries` builds all eleven adapter entries through its fixtures (`adapters-node`: `./adapters`, `./adapters/json`, `./adapters/forge`, `./anchors`, `./comments/server`, `./comments/json`; `adapters-drizzle`: both drizzle entries; `adapters-nitro`: `./server/nitro`; `adapters-vue`: `./adapters/vue`; `headless-comments-client`: `./comments/client`) and proves on the bundles that Vue is only reachable from `./adapters/vue`, Nitro only from `./server/nitro`, drizzle only from the two drizzle entries, and that no adapter entry reaches React. Entries named `./adapters/react`, `./server/nest` and `./adapters/prisma` are reserved for a later platform adoption and are not exported; the check fails if one of them appears in `exports` before it is designed.
|
|
563
|
+
|
|
564
|
+
### The tier rule
|
|
565
|
+
|
|
566
|
+
Three tiers, one direction of dependency:
|
|
567
|
+
|
|
568
|
+
1. **The editor** (`@ecsplico/editor`, everything except `adapters/`) and the `EditorAdapter` contract (`@ecsplico/editor/server`). It knows no storage and no host.
|
|
569
|
+
2. **Adapters** (`src/adapters/`, the entries above): implementations of that contract and the wiring around it. They depend on the editor; they import no application code, and no entry imports another UI or server framework than the one it is named for.
|
|
570
|
+
3. **Hosts**: an app (Nuxt, a bare Nitro server, Vite, a Fastify or tRPC server) that supplies identity (`authenticate`), a store and its own chrome. A host calls these entries; it never reaches into `dist/` internals or table names.
|
|
571
|
+
|
|
572
|
+
### Adapter matrix
|
|
573
|
+
|
|
574
|
+
| | `json` | `drizzle` | `forge` |
|
|
575
|
+
|---|---|---|---|
|
|
576
|
+
| Documents | `<id>.json` | `editor_documents` | the host's Markdown files |
|
|
577
|
+
| Collaboration state | `<id>.ystate` | `editor_states` | the `state` adapter you pass in |
|
|
578
|
+
| Settings, assets | files under `settings/`, `assets/` | `editor_settings`, `editor_assets` | the `state` adapter |
|
|
579
|
+
| Creates documents (`PUT /api/editor/documents/:id`) | yes | yes | no (the host owns its files) |
|
|
580
|
+
| Default identity | anonymous dev user outside production | same | none: the host supplies `authenticate` |
|
|
581
|
+
| Comment store | `comments/json` | `comments/drizzle` | `comments/json` or `comments/drizzle` |
|
|
582
|
+
| Nodes | one process | SQLite writers of the driver | one process |
|
|
583
|
+
|
|
584
|
+
All three pass the adapter contract test; both comment stores pass `test/adapters/comment-store.test.ts`. The JSON and forge adapters and the JSON comment store are single-node: no cross-process locking.
|
|
585
|
+
|
|
586
|
+
### Nitro host and composable
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
// server/plugins/editor.ts (bare Nitro, no Nuxt)
|
|
590
|
+
import { createJsonAdapter } from '@ecsplico/editor/adapters/json'
|
|
591
|
+
import { createJsonCommentStore } from '@ecsplico/editor/comments/json'
|
|
592
|
+
import { installEditorServer } from '@ecsplico/editor/server/nitro'
|
|
593
|
+
import { defineNitroPlugin } from 'nitropack/runtime'
|
|
594
|
+
|
|
595
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
596
|
+
installEditorServer(nitroApp, {
|
|
597
|
+
adapter: createJsonAdapter({ dir: '.data/documents' }),
|
|
598
|
+
collab: {}, // `false`: no websocket, documents still load and save over HTTP
|
|
599
|
+
comments: { store: createJsonCommentStore({ file: '.data/comments.json' }) }, // optional
|
|
600
|
+
// ai: { presets: { … } }, // optional: mounts /api/ai/*
|
|
601
|
+
})
|
|
602
|
+
})
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
`installEditorServer` is the explicit-wiring twin of the Nuxt module (§2) for hosts without Nuxt; set `experimental: { websocket: true }` in the Nitro config for the collaboration websocket. It mounts, all behind `adapter.authenticate`: the editor's client endpoints (`/api/editor/{settings,users,tags,assets/…,themes/…}`), `GET`/`PUT /api/editor/documents/:id` (a document's TipTap JSON; `PUT` needs an adapter that manages documents), `GET`/`POST /api/editor/comments/:id` (`handleCommentsRequest` over `comments.store`), `/api/ai/*` with `ai`, and `/collab/:id` (the Hocuspocus websocket) unless `collab: false`. The returned object (`adapter`, `collab`, `models`, `close()`) is for tests and shutdown; Nitro's `close` hook already calls it.
|
|
606
|
+
|
|
607
|
+
```vue
|
|
608
|
+
<script setup lang="ts">
|
|
609
|
+
import { useEditorDocument } from '@ecsplico/editor/adapters/vue'
|
|
610
|
+
import { commentsFeature } from '@ecsplico/editor/comments/vue'
|
|
611
|
+
import { coreFeature, defineEditor } from '@ecsplico/editor/core'
|
|
612
|
+
import { EditorSurface } from '@ecsplico/editor/vue'
|
|
613
|
+
import { ref } from 'vue'
|
|
614
|
+
|
|
615
|
+
const id = ref('welcome')
|
|
616
|
+
const doc = useEditorDocument(id, { fetch: (url, init) => fetch(url, init), user: { id: 'u1', name: 'Anna', color: '#e11d48' } })
|
|
617
|
+
const preset = defineEditor({ features: [coreFeature(), commentsFeature({ model: () => doc.comments.value })] })
|
|
618
|
+
</script>
|
|
619
|
+
|
|
620
|
+
<template>
|
|
621
|
+
<button :disabled="!doc.dirty.value" @click="doc.save()">Save</button>
|
|
622
|
+
<EditorSurface v-if="doc.ready.value" :key="id" :preset="preset" v-bind="doc.bind.value" />
|
|
623
|
+
</template>
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`useEditorDocument(id, options)` loads `GET /api/editor/documents/:id` (a missing document starts empty and is created by the first `save()`) and returns `modelValue`, `ready`, `dirty`, `save()`, `reload()`, `error`, `collaboration`, `comments` (the REST comments model of `/api/editor/comments/:id`), `editor` and `bind` (the surface's props and events). `dirty` ignores the block ids the editor assigns when it mounts content; they are saved with the next edit. With `collaboration: { url, token? }` the Y.Doc is the document: the server loads and stores the content, nothing is fetched or saved by the composable, and `modelValue` stays `undefined`. Mount the surface with `:key="id"` and `v-if="doc.ready.value"`.
|
|
627
|
+
|
|
628
|
+
Every adapter takes the same hooks: `authenticate({ headers, token })` (the user behind a request or collaboration socket, `null` refuses; default: an anonymous development user outside `NODE_ENV=production`, nobody in production; the forge adapter has no default), `authorize(user, documentId)` (per-document access for collaboration, `{ readOnly } | null`; omitted = every authenticated user edits every document), `schemaExtensions()` (Vue-free TipTap extensions of host-defined nodes for the collaboration schema) and `users()` (who can be `@`-mentioned).
|
|
629
|
+
|
|
630
|
+
The runnable app is [`examples/vue-nitro-json`](../../../examples/vue-nitro-json): `bun run proto:vue-nitro-json` serves it on port 3046, documents land in `examples/vue-nitro-json/.data/documents/<id>.json`, comments in `examples/vue-nitro-json/.data/comments.json`.
|
package/docs/llms.md
CHANGED
|
@@ -35,6 +35,12 @@ Every entry is an explicit subpath of `package.json#exports`; a path that is not
|
|
|
35
35
|
| ProseMirror schema for a server, validator or migration | `simpleSchemaExtensions` from `.../schema/simple`; `advancedSchemaExtensions` from `.../schema/advanced`; per-feature `.../<name>/schema`; base `coreSchemaExtensions`, `ROOT_ABILITIES`, `BlockId` helpers from `.../core/schema` | framework-free, Node-safe |
|
|
36
36
|
| Nitro side: register the adapter, forget a deleted document | `setEditorAdapter`, `EditorAdapter`, `forgetCollabDocument`, `liveContent`, `collabStats` from `.../server` | Nitro only |
|
|
37
37
|
| Server without Nitro: collab server, AI models, rate limiter, route handlers, websocket listener | `createCollabServer`, `createAiModels`, `createRateLimiter` from `.../server`; `chatPost`, `usersGet`, … and their body schemas from `.../server/handlers`; `listenCollab` from `.../server/node` | any Node process |
|
|
38
|
+
| Store documents as JSON files | `createJsonAdapter({ dir, authenticate?, authorize?, users?, schemaExtensions? })` from `@ecsplico/editor/adapters/json` | Node |
|
|
39
|
+
| Store documents in SQLite via drizzle (optional peer `drizzle-orm`) | `bootstrapEditorTables(db)`, `createDrizzleAdapter({ db, tables: editorTables() })` from `@ecsplico/editor/adapters/drizzle` | Node |
|
|
40
|
+
| Edit a host's Markdown files | `createForgeAdapter({ content, state, schema, authenticate, … })` from `@ecsplico/editor/adapters/forge` | Node |
|
|
41
|
+
| Mount the editor backend on Nitro (no Nuxt module) | `installEditorServer(nitroApp, { adapter, collab: {} \| false, ai?, rateLimit?, comments?: { store } })` from `@ecsplico/editor/server/nitro`, called from one Nitro plugin; needs `experimental: { websocket: true }` for `collab`; peer `h3` | Nitro only |
|
|
42
|
+
| Bind a Vue editor surface to a document | `useEditorDocument(id, { fetch, collaboration?, user?, canComment? })` → `{ modelValue, ready, dirty, save, reload, error, collaboration, comments, editor, bind, dispose }` from `@ecsplico/editor/adapters/vue` | browser (Vue) |
|
|
43
|
+
| Comment threads that survive edits | `captureAnchor`/`resolveAnchor`/`mapAnchor` from `.../anchors`; `createJsonCommentStore({ file })` from `.../comments/json`; `createDrizzleCommentStore({ db, tables: commentTables() })` from `.../comments/drizzle`; `handleCommentsRequest(store, { method, documentId, body })` from `.../comments/server`; `createRestCommentsModel({ fetch, documentId, user, canComment, getEditor })` from `.../comments/client` | `anchors`, `comments/client`: browser (no UI framework); the others: Node |
|
|
38
44
|
| Nuxt module (config key `tiptapEditor`) | `@ecsplico/editor/nuxt` in `modules` | build |
|
|
39
45
|
| CSS | `.../styles/core.css` (required) plus `/media.css`, `/mentions.css`, `/tables.css`, `/authors.css`, `/comments.css`, `/transcript.css`, `/composites.css`, `/ai.css`, `/collab.css`, `/block-ui.css`, `/themes.css` for the features you use | browser |
|
|
40
46
|
|
|
@@ -152,7 +158,7 @@ Advanced list in schema order: `core`, `media`, `transcript`, `composites`, `men
|
|
|
152
158
|
| `authorsFeature()` (`/authors/vue`, `/authors/react`) | none | none (author comes from the collaboration session `user`) |
|
|
153
159
|
| `collabFeature()` (`/collab/vue`, `/collab/react`) | none | `/collab/:id` websocket; active only when `collaboration` is passed to the surface |
|
|
154
160
|
| `commentsFeature({ model? })` (`/comments/vue`, `/comments/react`) | `collab`, none with `model` | comments live in the Y.Doc, or in the host's `CommentsModel` (`model: getEditor => CommentsModel`) |
|
|
155
|
-
| `compositeFeature({ kinds? })` (`/composites/vue`, `/composites/react`) | none | none (also mounts the composite overlay: status/tag chips on each title line and, on the kind icon, a popover with the type dropdown (warns before `setCompositeKind`, naming the field values that would be lost), the kind's fields and the summary; also read-only: type as text, fields disabled) |
|
|
161
|
+
| `compositeFeature({ kinds? })` (`/composites/vue`, `/composites/react`) | none | none (also mounts the composite overlay: status/tag chips on each title line and, on the kind icon, a popover with the type dropdown (warns before `setCompositeKind`, naming the field values that would be lost), the kind's fields and the summary; also read-only: type as text, fields disabled; enforces the kinds' accepted children, `CompositeKind.accepts`: an edit that nests a Baustein into one that does not accept its kind is refused with the notice `composites.nesting.title` („Verschachtelung nicht möglich“)) |
|
|
156
162
|
| `aiFeature()` (`/ai/vue`, `/ai/react`; framework-free `aiFeatureCore` in `/ai`) | none | `/api/ai/{models,generate,chat,complete}` |
|
|
157
163
|
| `blockUiFeature()` (`/block-ui/vue`, `/block-ui/react`) | none | none (drag handle, hover actions) |
|
|
158
164
|
| `themesFeature()` (`/themes/vue`, `/themes/react`) | none | adapter `listThemes`/`loadTheme` |
|
|
@@ -342,6 +348,7 @@ Complete worked example with node, node view, conversions and AI description: [e
|
|
|
342
348
|
- **Settings keys.** Only keys of `EditorSettings` persist; unknown ones are dropped on read and write.
|
|
343
349
|
- **Design tokens.** `@ecsplico/editor/styles/tokens.css` declares the shared `--sk-*` variables (`--sk-bg`, `--sk-fg`, `--sk-muted`, `--sk-border`, `--sk-primary`, `--sk-primary-fg`, `--sk-success`, `--sk-warning`, `--sk-danger`, `--sk-radius`, `--sk-font`) under `:where(:root)` (dark values under `:where(.dark)`). Later UI work in this package reads `--sk-*`; map them once in the host.
|
|
344
350
|
- **Module order.** `@nuxt/ui` before `@ecsplico/editor/nuxt` in `modules`.
|
|
351
|
+
- **Adapters tier.** Entries under `adapters/`, `anchors`, `comments/{server,json,drizzle,client}` and `server/nitro` supply storage and wiring; they import no application code. Vue is reachable only from `./adapters/vue`, Nitro (`h3`) only from `./server/nitro`, `drizzle-orm` only from the two drizzle entries, React from none; `./adapters` is types only. `./adapters/react`, `./server/nest` and `./adapters/prisma` are reserved and not exported. The Nitro host and `useEditorDocument` speak one wire contract: `GET`/`PUT /api/editor/documents/:id` (TipTap JSON, 404 = missing, `PUT` 204 and only for adapters that manage documents), `GET`/`POST /api/editor/comments/:id` (`handleCommentsRequest`, which trusts its caller), `/collab/:id` (Hocuspocus; `collaboration.url` is `ws://host/collab`). Every route authenticates through `adapter.authenticate({ headers, token })`; `null` answers 401; the default outside production is an anonymous dev user, in production nobody. The JSON and forge adapters and the JSON comment store are single-node. Mount the surface with `:key="id"` and `v-if="doc.ready.value"`, bind with `v-bind="doc.bind.value"`. Runnable reference: `examples/vue-nitro-json` (`bun run proto:vue-nitro-json`, port 3046).
|
|
345
352
|
|
|
346
353
|
## Verify
|
|
347
354
|
|
|
@@ -353,6 +360,7 @@ bun run typecheck:editor # vue-tsc --noEmit -p packages/edi
|
|
|
353
360
|
bun run build:editor # tsdown: packages/editor/dist (ESM + .d.ts per export)
|
|
354
361
|
bun run check:editor-entries # builds the editor, then one production build per fixture + module-graph isolation, then the schema snapshot
|
|
355
362
|
bun run check:editor-composition # defineEditor rejects missing `requires`; accepted compositions; one formatAiResult
|
|
363
|
+
bun run test:editor # bun test: inspector model, AI selection flow, surface session on a mounted editor (happy-dom)
|
|
356
364
|
bun run typecheck # nuxt typecheck of the demo app
|
|
357
365
|
bun run --cwd examples/vue-simple typecheck # plain Vue consumer
|
|
358
366
|
bun run --cwd examples/react-simple typecheck # plain React consumer
|
package/docs/usage.md
CHANGED
|
@@ -565,6 +565,7 @@ On React the same list comes from `/authors/react`, `/collab/react` and `/commen
|
|
|
565
565
|
- Select text and choose „Kommentar“ in the selection bubble; others reply, anyone resolves or reopens a thread („Erledigt“ threads lose their highlight), authors edit or delete their own comments. A thread whose text was deleted stays as orphan („Textstelle gelöscht“). Threads live in the Y.Doc map `comments` (`createCommentsModel(getEditor)` is the framework-free model; `useComments(editor)` is the thin Vue hook, returning the `CommentsStore`, and the React binding has the same hook), the range is the `comment` mark (the schema mark is always registered, so stored marks round-trip even without the feature's UI).
|
|
566
566
|
- `editing.commentsLayout` (`'margin'` default, `'inline'`) is a per-user setting in the „Kommentare“ settings tab:
|
|
567
567
|
- `margin` („Randspalte“): cards beside the commented text. The host places `CommentsMargin` (props `editor`, `container`, `open`) inside the editor's scroll container (`relative flex`, editor wrapper `min-w-0 flex-1`) and binds `v-model:comments-open` on the surface (React: `commentsOpen`/`onCommentsOpenChange`). The toolbar's „Kommentare“ button and `Mod-Shift-m` toggle it; opening it closes the AI sidebar and vice versa.
|
|
568
|
+
- Layout „Aktiver Faden“: the active thread (or the draft composer) is pinned exactly to the top of its text's line and shows in full; every other open thread is a one-line card (author, excerpt, reply count) that moves up or down out of its way, as close to its own line as its neighbours allow, never overlapping. Without an active thread the cards stack top-down at their lines. A click on a card activates it and reveals its text. Resolved threads are hidden behind the „Erledigt (n)“ toggle at the bottom of the column. A thread whose text was deleted stays as a greyed-out card at the deletion point, labelled „Text gelöscht“ (its position comes from the thread's anchor, a Yjs relative position kept in the Y.Map; a thread without one goes to the end); undoing the deletion attaches it to its text again. The rule is framework-free and testable on its own: `layoutMargin(entries, activeId, { gap, minTop })` maps measured cards (`MarginEntry`: id, line top or `null`, height) to tops, `marginCards(...)` builds the cards in document order, and `createMarginController(source)` does the measuring (`coordsAtPos`, card heights, one layout per animation frame); both `CommentsMargin` components only render it (all from `@ecsplico/editor/comments`).
|
|
568
569
|
- `inline`: `CommentsPopover` (mounted by the feature as an overlay) opens at the commented text; the toolbar's `CommentsListMenu` lists all threads with the open count. Esc closes the popover.
|
|
569
570
|
- Without collaboration the toolbar button is disabled with an explanation. Switching the layout closes any open thread or draft.
|
|
570
571
|
|
|
@@ -692,6 +693,8 @@ On React the feature is `compositeFeature()` from `@ecsplico/editor/composites/r
|
|
|
692
693
|
|
|
693
694
|
Kinds are data. `compositeFeature({ kinds })` replaces the built-in `defaultCompositeKinds(ctx.t)` (section, task, ...; labelled in the context's language); a kind has `id`, `label`, `icon`, `titlePlaceholder`, `descriptionPlaceholder` and `fields` (`text`, `textarea`, `date`, `number`, `select`). `compositeKindsOf(editor)` returns the editor's own list and an unknown `kind` id falls back to the `section` presentation under its own label.
|
|
694
695
|
|
|
696
|
+
**Accepted children.** `CompositeKind.accepts?: readonly string[] | 'all'` names the Baustein kinds a kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Without `accepts`, `folder` and `document` accept `'all'` and every other kind accepts none; the built-in `section` sets `'all'`, `task` and `note` accept no Baustein. Paragraphs, lists and other non-Baustein content are always allowed, and top-level Bausteine have no parent. `kindAccepts(kinds, parentKind, childKind)` answers the question for a kinds list (a parent kind the list does not know accepts nothing, `folder` and `document` aside). The rule is enforced in the editor by the `AcceptedChildren` extension, which `compositeFeature()` registers: every local edit that leaves the document with more Bausteine nested in a Baustein that does not accept their kind than before is refused — block drag, „In Baustein umwandeln“, the kind switch of the details popover, paste, AI inserts and review acceptance, `moveBlockInto` — and the user gets the notice „Verschachtelung nicht möglich“ with „<Kind> nimmt keine <Kind> auf.“ (kind labels, e.g. „Notiz nimmt keine Aufgabe auf.“). Only *new* violations are refused: a document that already nests against the rule stays as it is and can still be edited, and collaboration replays, undo/redo and loading a whole document are never blocked. The document tree applies the same rule to drops ([File browser](#file-browser)).
|
|
697
|
+
|
|
695
698
|
### Chips and details popover
|
|
696
699
|
|
|
697
700
|
The feature mounts one overlay (`CompositeChrome`) beside the editor, so every composite looks the same in every host:
|
|
@@ -746,7 +749,7 @@ It also exports the pure helpers: `defaultCompositeKinds`, `compositeKind`, `com
|
|
|
746
749
|
|
|
747
750
|
### Outline and Markdown export helpers
|
|
748
751
|
|
|
749
|
-
- `compositeOutline(doc, hiddenKinds)` reads the document as a tree of composites (`OutlineNode`: key, id, position, kind, title, summary flag, children); composites of a hidden kind drop out and their descendants move up. `canMoveBlockInto(editor, id, targetId)`
|
|
752
|
+
- `compositeOutline(doc, hiddenKinds)` reads the document as a tree of composites (`OutlineNode`: key, id, position, kind, title, summary flag, children); composites of a hidden kind drop out and their descendants move up. `moveIntoVerdict(editor, id, targetId, t)` says whether block `id` may move into the composite `targetId` (`null` = the end of the document): a `MoveVerdict`, `{ ok: true }` or `{ ok: false, reason }` with a short sentence in the language of `t` (the editor's translator) (the block or the target no longer exists, the target is no Baustein, a block into itself or into a Baustein it contains, or the target's kind does not accept the block's kind, see *Accepted children*). A block that is no Baustein may move into any Baustein unless it holds Bausteine of a refused kind; moving a block to the end of the Baustein it already sits in is never refused. `canMoveBlockInto(editor, id, targetId)` is `moveIntoVerdict(...).ok`. `moveBlockInto(editor, id, targetId)` moves the block to the end of a composite's body (or the document with `null`) in one transaction and returns whether the document changed (`false`: refused by the verdict or by the editor).
|
|
750
753
|
- `headingsMarkdown(editor, blocks, level)` exports blocks as Markdown with every composite turned into a heading (the outermost at `level`) followed by its description, tags and body; `documentCompositeMarkdown(editor, node)` does the same for one composite as a document, `markdownFileName(node)` names the file and `downloadText(filename, text)` offers it as a download.
|
|
751
754
|
|
|
752
755
|
The heading-only outline of a plain editor stays `@ecsplico/editor/outline` (`headingOutline`, `revealBlockInEditor`); its component is `EditorOutline` from `/outline/vue` or `/outline/react`.
|
|
@@ -944,16 +947,16 @@ The model proposes edits through the `edit_document` tool. The server checks the
|
|
|
944
947
|
|
|
945
948
|
| Type | Fields | Effect |
|
|
946
949
|
|---|---|---|
|
|
947
|
-
| `add` | `afterId: string \| null
|
|
950
|
+
| `add` | `afterId: string \| null` (required), `markdown` | Insert block(s) after `afterId` (in the document or its composite body). `afterId` must be present: an explicit `null` is the document start (not allowed with a selection scope, where `afterId` must be a selected block); a forgotten key is rejected, it never becomes the start. Several `add`s after the same block are fine and keep the order of the answer. |
|
|
948
951
|
| `update` | `id`, `markdown` | Replace block `id`. A single paragraph, or one block of the same type and attributes, is diffed within the block. On a composite, a `::: kind` fence (exactly one composite) replaces the whole composite; unfenced markdown replaces only its body. |
|
|
949
952
|
| `delete` | `id` | Remove the block (or the smallest list item, list or quote that would be invalid without it). |
|
|
950
953
|
| `convert` | `id`, `to` | Change the block type, keeping the content. `to` is one of the request's `targets`: the targets the installed conversion providers advertise (`paragraph`, `heading1`–`heading3`, `bulletList`, `orderedList`, `blockquote`, `codeBlock`, plus `transcriptSegment` and `composite:<kind>` with those features). |
|
|
951
954
|
|
|
952
|
-
Validation problems (`AgentOpProblemReason`): `unknown` (the id isn't in the document), `scope` (the id is outside the selection), `field` (a field is missing or invalid, e.g. empty `markdown
|
|
955
|
+
Validation problems (`AgentOpProblemReason`): `unknown` (the id isn't in the document), `scope` (the id is outside the selection, or an `add` at the document start with a selection scope), `field` (a field is missing or invalid, e.g. empty `markdown`, more than `AGENT_LIMITS.markdown` = 8000 characters, or an `add` without its `afterId` key), `duplicate` (the same block is updated, converted or deleted twice), `limit` (more than `AGENT_LIMITS.ops` = 50 operations), `target` (`to` isn't among the request's `targets`), `inside` (an operation on a block *inside* a block that another operation of the same list replaces (`update`) or deletes: the operation on the outer block wins; to change the inner block too, put the change into the outer `markdown`). Which blocks lie inside which is read from the indentation of the document text (`readBlockLines`). On problems the model is asked to retry (the retry text lists each problem and says that for `inside` the outer operation applies), up to `AGENT_LIMITS.retries` = 2 times. Whatever is still invalid after that, or left out of a corrected list, is reported in `dropped`.
|
|
953
956
|
|
|
954
|
-
**Review flow.** The client re-checks each operation against the scope the request was sent with and shows valid ones as in-document review changes the user accepts or rejects. An operation that can't be shown gets an outcome instead: `blocked
|
|
957
|
+
**Review flow.** The client re-checks each operation against the scope the request was sent with and shows valid ones as in-document review changes the user accepts or rejects. An operation that can't be shown gets an outcome instead: `blocked` (outside the scope), `stale` (its block was deleted in the browser while the model was answering; „Block gelöscht“ in the chat), `missing`, `unchanged`, `overlap` or `empty` (`AGENT_OP_OUTCOMES`). Every operation's outcome (`accepted`, `rejected`, `partial`, `invalid`, `pending`, …) is sent back as `feedback` with the next user message; the model reads it as „Entscheidungen zu deinen letzten Vorschlägen: op1 (update Block X) nicht angezeigt: Block wurde inzwischen gelöscht; …“ (`agentFeedbackText`).
|
|
955
958
|
|
|
956
|
-
**Document text** (`documentChatText(editor)`): one line per block, `[<id>] <markdown on one line>`. List and quote markers form the prefix (`- `, `1. `, `> `). A composite is a header line `[<id>] ::: kind {meta} | Title | Description | Tags: #a #b` (meta only when non-empty, empty parts omitted), followed by its body lines indented by two spaces and a closing line ` :::` without an id. The text is truncated at `CHAT_LIMITS.document` = 120000 characters.
|
|
959
|
+
**Document text** (`documentChatText(editor)`): one line per block, `[<id>] <markdown on one line>`. List and quote markers form the prefix (`- `, `1. `, `> `). A composite is a header line `[<id>] ::: kind {meta} | Title | Description | Tags: #a #b` (meta only when non-empty, empty parts omitted), followed by its body lines indented by two spaces and a closing line ` :::` without an id. A code block keeps its own lines: the first carries the id, the others start at the left edge. The text is truncated at `CHAT_LIMITS.document` = 120000 characters (the notice is `TRUNCATED_LINE`). The format has one owner, `block-lines` of `@ecsplico/editor/ai/protocol`: the writer helpers (`blockLine`, `compositeLineHeader`, …), `readBlockLines(text)` (the ids and which blocks lie inside which; lines inside fenced code are code, never block ids, so a code line like `[ghost] text` does not become one), `stripBlockLineEcho` / `stripListMarkerEcho` (remove a `[<id>] ` or list/quote marker prefix the model echoes into its Markdown) and the prompt texts (`BLOCK_LINE_INTRO`, `BLOCK_LINE_IDS_HINT`, …). The browser writes the text and the server reads it with the same module.
|
|
957
960
|
|
|
958
961
|
### Composite Markdown fence format
|
|
959
962
|
|
|
@@ -1033,6 +1036,8 @@ export function Drawer({ store, editor }: { store: DocumentStore, editor: Editor
|
|
|
1033
1036
|
- `editor` is the open page's editor (`undefined` when no document is open); take it from your page through a shared composable, as `useActiveEditor()` does in the Nuxt app.
|
|
1034
1037
|
- `v-model:selectedFolder` (React: `selectedFolder` / `onSelectedFolderChange`, uncontrolled when omitted) is where new documents, new folders and your own import actions go. Bind the same state in your import button (the Nuxt app binds it in the layout and passes it to `AppDocumentTree`). The Vue `document-actions` slot is `documentActions` on React.
|
|
1035
1038
|
- Place `DocumentTree` anywhere (a sidebar, a drawer); several can coexist, sharing one filter state per app (`useTreeView()` from `/tree/vue` returns the refs `nodeFilters` and `expanded`, from `/tree/react` the plain values, both with the framework-free `model`).
|
|
1039
|
+
- **Title filter.** The field „Titel filtern“ above the tree (clear button, `Esc` clears too) filters by title, case-insensitive substring, over folders, documents and the outline entries: only matching rows and their parents stay, the matching part is highlighted, and „Keine Treffer“ shows when nothing matches. While the filter is set the parents of matches are shown open without touching the stored open state; clearing the filter restores the rows that were open before (a row opened or closed by hand meanwhile counts only until then). The title filter belongs to one tree; the kind filters are the shared per-app state.
|
|
1040
|
+
- **Refused drops.** While a drag hovers a row, the tree asks whether that drop is allowed (`dropVerdict`, with a reason in the editor's language). An allowed target gets a primary outline and takes the drop; a refused one is outlined red, shows the reason under the row („Notiz nimmt keine Aufgabe auf.“, „Ein Ordner lässt sich nicht in sich selbst oder einen seiner Unterordner verschieben.“, …) and the browser shows „not allowed“. Moving a Baustein is judged by the open document's editor (`moveIntoVerdict`, so the kinds' accepted children apply); documents and folders follow the folder rules of the `DocumentStore` contract. Both components render one framework-free model (`createTreeModel`, internal, not exported from `./tree`); the props of `DocumentTree` are unchanged.
|
|
1036
1041
|
- The outline entries under the open document follow the settings: composites in the preferred structure (a *virtual root* when `blocks.treeRoot` is `'file'`), headings otherwise.
|
|
1037
1042
|
|
|
1038
1043
|
The full `DocumentStore` contract (loading states, mutation refresh, delete with undo, folder rules, name validation, who reports failures) and the runnable implementation are in the [integration guide](./integration.md#10-the-file-browser-and-documentstore); the host implementation is [`app/composables/useDocumentStore.ts`](../../../app/composables/useDocumentStore.ts).
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecsplico/editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vue and React editor library on TipTap v3 (Vue: Nuxt UI; React: shadcn/ui): framework-free features with Vue and React bindings, simple and advanced presets, tree-shakeable entries. Built ESM with type declarations.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/skaile-ai/editor.git",
|
|
9
|
+
"url": "git+https://github.com/skaile-ai/skaile-editor.git",
|
|
10
10
|
"directory": "packages/editor"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://github.com/skaile-ai/editor/tree/main/packages/editor#readme",
|
|
13
|
-
"bugs": "https://github.com/skaile-ai/editor/issues",
|
|
12
|
+
"homepage": "https://github.com/skaile-ai/skaile-editor/tree/main/packages/editor#readme",
|
|
13
|
+
"bugs": "https://github.com/skaile-ai/skaile-editor/issues",
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"docs",
|
|
@@ -295,6 +295,56 @@
|
|
|
295
295
|
"types": "./dist/features/comments/schema.d.ts",
|
|
296
296
|
"import": "./dist/features/comments/schema.js"
|
|
297
297
|
},
|
|
298
|
+
"./adapters": {
|
|
299
|
+
"skaile-source": "./src/adapters/index.ts",
|
|
300
|
+
"types": "./dist/adapters/index.d.ts",
|
|
301
|
+
"import": "./dist/adapters/index.js"
|
|
302
|
+
},
|
|
303
|
+
"./adapters/json": {
|
|
304
|
+
"skaile-source": "./src/adapters/json.ts",
|
|
305
|
+
"types": "./dist/adapters/json.d.ts",
|
|
306
|
+
"import": "./dist/adapters/json.js"
|
|
307
|
+
},
|
|
308
|
+
"./adapters/drizzle": {
|
|
309
|
+
"skaile-source": "./src/adapters/drizzle.ts",
|
|
310
|
+
"types": "./dist/adapters/drizzle.d.ts",
|
|
311
|
+
"import": "./dist/adapters/drizzle.js"
|
|
312
|
+
},
|
|
313
|
+
"./adapters/forge": {
|
|
314
|
+
"skaile-source": "./src/adapters/forge.ts",
|
|
315
|
+
"types": "./dist/adapters/forge.d.ts",
|
|
316
|
+
"import": "./dist/adapters/forge.js"
|
|
317
|
+
},
|
|
318
|
+
"./adapters/vue": {
|
|
319
|
+
"skaile-source": "./src/adapters/vue.ts",
|
|
320
|
+
"types": "./dist/adapters/vue.d.ts",
|
|
321
|
+
"import": "./dist/adapters/vue.js"
|
|
322
|
+
},
|
|
323
|
+
"./anchors": {
|
|
324
|
+
"skaile-source": "./src/adapters/anchors.ts",
|
|
325
|
+
"types": "./dist/adapters/anchors.d.ts",
|
|
326
|
+
"import": "./dist/adapters/anchors.js"
|
|
327
|
+
},
|
|
328
|
+
"./comments/server": {
|
|
329
|
+
"skaile-source": "./src/adapters/comments/server.ts",
|
|
330
|
+
"types": "./dist/adapters/comments/server.d.ts",
|
|
331
|
+
"import": "./dist/adapters/comments/server.js"
|
|
332
|
+
},
|
|
333
|
+
"./comments/json": {
|
|
334
|
+
"skaile-source": "./src/adapters/comments/json.ts",
|
|
335
|
+
"types": "./dist/adapters/comments/json.d.ts",
|
|
336
|
+
"import": "./dist/adapters/comments/json.js"
|
|
337
|
+
},
|
|
338
|
+
"./comments/drizzle": {
|
|
339
|
+
"skaile-source": "./src/adapters/comments/drizzle.ts",
|
|
340
|
+
"types": "./dist/adapters/comments/drizzle.d.ts",
|
|
341
|
+
"import": "./dist/adapters/comments/drizzle.js"
|
|
342
|
+
},
|
|
343
|
+
"./comments/client": {
|
|
344
|
+
"skaile-source": "./src/adapters/comments/client.ts",
|
|
345
|
+
"types": "./dist/adapters/comments/client.d.ts",
|
|
346
|
+
"import": "./dist/adapters/comments/client.js"
|
|
347
|
+
},
|
|
298
348
|
"./transcript": {
|
|
299
349
|
"skaile-source": "./src/features/transcript/index.ts",
|
|
300
350
|
"types": "./dist/features/transcript/index.d.ts",
|
|
@@ -420,6 +470,11 @@
|
|
|
420
470
|
"types": "./dist/server/node.d.ts",
|
|
421
471
|
"import": "./dist/server/node.js"
|
|
422
472
|
},
|
|
473
|
+
"./server/nitro": {
|
|
474
|
+
"skaile-source": "./src/adapters/nitro.ts",
|
|
475
|
+
"types": "./dist/adapters/nitro.d.ts",
|
|
476
|
+
"import": "./dist/adapters/nitro.js"
|
|
477
|
+
},
|
|
423
478
|
"./nuxt": {
|
|
424
479
|
"skaile-source": "./src/nuxt/module.ts",
|
|
425
480
|
"types": "./dist/nuxt/module.d.ts",
|
|
@@ -497,6 +552,7 @@
|
|
|
497
552
|
"@tiptap/pm": "^3.31.3",
|
|
498
553
|
"@tiptap/react": "^3.31.3",
|
|
499
554
|
"@tiptap/vue-3": "^3.31.3",
|
|
555
|
+
"drizzle-orm": "^0.45.0",
|
|
500
556
|
"h3": "^1.15.0",
|
|
501
557
|
"nitropack": "^2.13.0",
|
|
502
558
|
"nuxt": "^4.5.2",
|
|
@@ -554,8 +610,10 @@
|
|
|
554
610
|
"@tiptap/react": "3.31.3",
|
|
555
611
|
"@types/react": "^19.3.0",
|
|
556
612
|
"@types/react-dom": "^19.3.0",
|
|
613
|
+
"@types/ws": "^8.18.2",
|
|
557
614
|
"@vitejs/plugin-react": "^6.1.2",
|
|
558
615
|
"@vitejs/plugin-vue": "^6.0.9",
|
|
616
|
+
"drizzle-orm": "^0.45.4",
|
|
559
617
|
"h3": "^1.15.11",
|
|
560
618
|
"nitropack": "^2.13.4",
|
|
561
619
|
"react": "^19.3.0",
|
|
@@ -563,7 +621,8 @@
|
|
|
563
621
|
"tailwindcss": "^4.3.3",
|
|
564
622
|
"tsdown": "^0.23.1",
|
|
565
623
|
"unplugin-vue": "^8.0.0",
|
|
566
|
-
"vite": "^8.3.1"
|
|
624
|
+
"vite": "^8.3.1",
|
|
625
|
+
"ws": "^8.18.3"
|
|
567
626
|
},
|
|
568
627
|
"peerDependenciesMeta": {
|
|
569
628
|
"@iconify/react": {
|
|
@@ -581,6 +640,9 @@
|
|
|
581
640
|
"@tiptap/vue-3": {
|
|
582
641
|
"optional": true
|
|
583
642
|
},
|
|
643
|
+
"drizzle-orm": {
|
|
644
|
+
"optional": true
|
|
645
|
+
},
|
|
584
646
|
"h3": {
|
|
585
647
|
"optional": true
|
|
586
648
|
},
|