@witchcraft/editor 0.1.0 → 0.2.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/dist/module.json +1 -1
- package/dist/runtime/components/Editor.d.vue.ts +11 -1
- package/dist/runtime/components/Editor.vue.d.ts +11 -1
- package/dist/runtime/components/EditorDemoApp.vue +35 -5
- package/dist/runtime/components/EditorDemoControls.d.vue.ts +3 -0
- package/dist/runtime/components/EditorDemoControls.vue +55 -12
- package/dist/runtime/components/EditorDemoControls.vue.d.ts +3 -0
- package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.d.vue.ts +2 -2
- package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.vue.d.ts +2 -2
- package/dist/runtime/pm/features/Collaboration/Collaboration.d.ts +14 -63
- package/dist/runtime/pm/features/Collaboration/Collaboration.js +4 -164
- package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.d.ts +16 -0
- package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.js +82 -0
- package/dist/runtime/pm/features/CommandsMenus/commandBarMenuItems.js +5 -1
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.d.ts +16 -3
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.js +19 -2
- package/dist/runtime/pm/features/DocumentApi/composables/useEditorContent.js +2 -0
- package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.d.ts +4 -1
- package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.js +39 -8
- package/dist/runtime/pm/features/DocumentApi/types.d.ts +26 -48
- package/dist/runtime/pm/schema.d.ts +1 -1
- package/package.json +35 -34
- package/src/runtime/components/EditorDemoApp.vue +36 -5
- package/src/runtime/components/EditorDemoControls.vue +57 -12
- package/src/runtime/pm/features/Collaboration/Collaboration.ts +19 -286
- package/src/runtime/pm/features/Collaboration/createCollaborationPlugins.ts +129 -0
- package/src/runtime/pm/features/CommandsMenus/commandBarMenuItems.ts +5 -2
- package/src/runtime/pm/features/DocumentApi/DocumentApi.ts +35 -5
- package/src/runtime/pm/features/DocumentApi/composables/useEditorContent.ts +2 -0
- package/src/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.ts +56 -8
- package/src/runtime/pm/features/DocumentApi/types.ts +30 -52
- package/dist/runtime/demo/App.d.vue.ts +0 -3
- package/dist/runtime/demo/App.vue +0 -100
- package/dist/runtime/demo/App.vue.d.ts +0 -3
- package/src/runtime/demo/App.vue +0 -113
package/dist/module.json
CHANGED
|
@@ -26,6 +26,16 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
26
26
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
27
27
|
onLoad?: (() => any) | undefined;
|
|
28
28
|
onUnload?: (() => any) | undefined;
|
|
29
|
-
}>, {
|
|
29
|
+
}>, {
|
|
30
|
+
menus: Record<string, MenuRenderInfo>;
|
|
31
|
+
content: Content;
|
|
32
|
+
docId: string;
|
|
33
|
+
documentApi: DocumentApiInterface;
|
|
34
|
+
linkOptions: EditorLinkOptions;
|
|
35
|
+
dragScrollOptions: Pick<ScrollNearContainerEdgesOptions, "scrollMargin" | "outerScrollMargin" | "fastPixelMultiplier">;
|
|
36
|
+
cssVariables: Partial<CssVariables>;
|
|
37
|
+
editorOptions: Partial<EditorOptions>;
|
|
38
|
+
codeBlocksThemeIsDark: boolean;
|
|
39
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
40
|
declare const _default: typeof __VLS_export;
|
|
31
41
|
export default _default;
|
|
@@ -26,6 +26,16 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
26
26
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
27
27
|
onLoad?: (() => any) | undefined;
|
|
28
28
|
onUnload?: (() => any) | undefined;
|
|
29
|
-
}>, {
|
|
29
|
+
}>, {
|
|
30
|
+
menus: Record<string, MenuRenderInfo>;
|
|
31
|
+
content: Content;
|
|
32
|
+
docId: string;
|
|
33
|
+
documentApi: DocumentApiInterface;
|
|
34
|
+
linkOptions: EditorLinkOptions;
|
|
35
|
+
dragScrollOptions: Pick<ScrollNearContainerEdgesOptions, "scrollMargin" | "outerScrollMargin" | "fastPixelMultiplier">;
|
|
36
|
+
cssVariables: Partial<CssVariables>;
|
|
37
|
+
editorOptions: Partial<EditorOptions>;
|
|
38
|
+
codeBlocksThemeIsDark: boolean;
|
|
39
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
40
|
declare const _default: typeof __VLS_export;
|
|
31
41
|
export default _default;
|
|
@@ -10,8 +10,34 @@
|
|
|
10
10
|
<EditorDemoControls
|
|
11
11
|
:code-blocks-theme-list="codeBlocksThemeList"
|
|
12
12
|
v-model:code-blocks-theme="codeBlocksTheme"
|
|
13
|
+
v-model:use-two-editors="useTwoEditors"
|
|
14
|
+
/>
|
|
15
|
+
|
|
16
|
+
<Editor
|
|
17
|
+
class="
|
|
18
|
+
max-w-[700px]
|
|
19
|
+
flex-1
|
|
20
|
+
flex
|
|
21
|
+
border
|
|
22
|
+
border-neutral-300
|
|
23
|
+
dark:border-neutral-700
|
|
24
|
+
rounded-sm
|
|
25
|
+
min-h-0
|
|
26
|
+
"
|
|
27
|
+
v-bind="{
|
|
28
|
+
codeBlocksThemeIsDark,
|
|
29
|
+
cssVariables: {
|
|
30
|
+
pmCodeBlockBgColor: codeBlocksThemeBgColor
|
|
31
|
+
},
|
|
32
|
+
docId,
|
|
33
|
+
documentApi,
|
|
34
|
+
linkOptions,
|
|
35
|
+
editorOptions,
|
|
36
|
+
menus
|
|
37
|
+
}"
|
|
13
38
|
/>
|
|
14
39
|
<Editor
|
|
40
|
+
v-if="useTwoEditors"
|
|
15
41
|
class="
|
|
16
42
|
max-w-[700px]
|
|
17
43
|
flex-1
|
|
@@ -39,6 +65,7 @@
|
|
|
39
65
|
|
|
40
66
|
<script setup>
|
|
41
67
|
import WRoot from "@witchcraft/ui/components/LibRoot";
|
|
68
|
+
import { useRoute } from "nuxt/app";
|
|
42
69
|
import { reactive, ref, shallowRef } from "vue";
|
|
43
70
|
import Editor from "./Editor.vue";
|
|
44
71
|
import EditorDemoControls from "./EditorDemoControls.vue";
|
|
@@ -86,19 +113,22 @@ const menus = shallowRef({
|
|
|
86
113
|
popupOptions: {
|
|
87
114
|
pinToItemDistance: (state) => {
|
|
88
115
|
const { $from, $to } = state.selection;
|
|
89
|
-
const fromNode = $from.node(-1);
|
|
90
|
-
const toNode = $to.node(-1);
|
|
91
|
-
if (fromNode
|
|
116
|
+
const fromNode = -1 < $from.depth ? $from.node(-1) : void 0;
|
|
117
|
+
const toNode = -1 < $to.depth ? $to.node(-1) : void 0;
|
|
118
|
+
if (fromNode?.type !== toNode?.type) {
|
|
92
119
|
return 0;
|
|
93
120
|
}
|
|
94
|
-
return fromNode
|
|
121
|
+
return fromNode?.type.name.startsWith("table") ? 120 : 0;
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
}
|
|
98
125
|
});
|
|
126
|
+
const useYjs = useRoute().query.useYjs;
|
|
127
|
+
const useTwoEditors = ref(false);
|
|
99
128
|
const { documentApi } = useTestDocumentApi(
|
|
100
129
|
editorOptions,
|
|
101
|
-
testDocuments
|
|
130
|
+
testDocuments,
|
|
131
|
+
{ useCollab: useYjs === "true" }
|
|
102
132
|
);
|
|
103
133
|
const docId = ref("root");
|
|
104
134
|
</script>
|
|
@@ -3,12 +3,15 @@ type __VLS_Props = {
|
|
|
3
3
|
};
|
|
4
4
|
type __VLS_ModelProps = {
|
|
5
5
|
"codeBlocksTheme": string;
|
|
6
|
+
"useTwoEditors": boolean;
|
|
6
7
|
};
|
|
7
8
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
10
|
"update:codeBlocksTheme": (value: string) => any;
|
|
11
|
+
"update:useTwoEditors": (value: boolean) => any;
|
|
10
12
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
13
|
"onUpdate:codeBlocksTheme"?: ((value: string) => any) | undefined;
|
|
14
|
+
"onUpdate:useTwoEditors"?: ((value: boolean) => any) | undefined;
|
|
12
15
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
16
|
declare const _default: typeof __VLS_export;
|
|
14
17
|
export default _default;
|
|
@@ -1,36 +1,79 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="
|
|
4
|
+
max-w-[700px]
|
|
4
5
|
w-full
|
|
5
6
|
flex
|
|
6
|
-
|
|
7
|
+
flex-col
|
|
8
|
+
items-center
|
|
7
9
|
justify-center
|
|
8
10
|
gap-2
|
|
9
|
-
|
|
11
|
+
"
|
|
12
|
+
>
|
|
13
|
+
<div
|
|
14
|
+
class="
|
|
15
|
+
flex
|
|
16
|
+
gap-4
|
|
17
|
+
items-center
|
|
18
|
+
justify-center
|
|
19
|
+
border
|
|
20
|
+
border-neutral-300
|
|
21
|
+
dark:border-neutral-700
|
|
22
|
+
rounded-md
|
|
23
|
+
p-2
|
|
24
|
+
"
|
|
25
|
+
>
|
|
26
|
+
<!-- external is to force a reload, otherwise the editors won't work because of how they're setup for the demo (document api is created here and would need to be recreated with the route changes) -->
|
|
27
|
+
<NuxtLink
|
|
28
|
+
v-if="useYjs !== void 0"
|
|
29
|
+
:to="{ path: '/', query: {} }"
|
|
30
|
+
:external="true"
|
|
31
|
+
>Go to Non-Yjs Example</NuxtLink>
|
|
32
|
+
<NuxtLink
|
|
33
|
+
v-else
|
|
34
|
+
:to="{ path: '/', query: { useYjs: 'true' } }"
|
|
35
|
+
:external="true"
|
|
36
|
+
>Go to Yjs Example </NuxtLink>
|
|
37
|
+
<WCheckbox v-model="useTwoEditors">
|
|
38
|
+
Use Two Editors (same document)
|
|
39
|
+
</WCheckbox>
|
|
40
|
+
</div>
|
|
41
|
+
<div
|
|
42
|
+
class="
|
|
43
|
+
flex
|
|
44
|
+
justify-center
|
|
45
|
+
items-center
|
|
46
|
+
gap-2
|
|
47
|
+
[&>*]:rounded-md
|
|
10
48
|
[&>*]:p-2
|
|
11
49
|
[&>*]:border
|
|
12
50
|
[&>*]:border-neutral-300
|
|
13
51
|
[&>*]:dark:border-neutral-700
|
|
52
|
+
|
|
14
53
|
"
|
|
15
|
-
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
54
|
+
>
|
|
55
|
+
<div class="flex items-center gap-2">
|
|
56
|
+
<span>
|
|
57
|
+
Global Theme:
|
|
58
|
+
</span>
|
|
59
|
+
<WDarkModeSwitcher/>
|
|
60
|
+
</div>
|
|
61
|
+
<CodeBlockThemePicker
|
|
62
|
+
:code-blocks-theme-list="codeBlocksThemeList"
|
|
63
|
+
v-model:code-blocks-theme="codeBlocksTheme"
|
|
64
|
+
/>
|
|
21
65
|
</div>
|
|
22
|
-
<CodeBlockThemePicker
|
|
23
|
-
:code-blocks-theme-list="codeBlocksThemeList"
|
|
24
|
-
v-model:code-blocks-theme="codeBlocksTheme"
|
|
25
|
-
/>
|
|
26
66
|
</div>
|
|
27
67
|
</template>
|
|
28
68
|
|
|
29
69
|
<script setup>
|
|
30
70
|
import WDarkModeSwitcher from "@witchcraft/ui/components/LibDarkModeSwitcher";
|
|
71
|
+
import { useRoute } from "nuxt/app";
|
|
31
72
|
import CodeBlockThemePicker from "./CodeBlockThemePicker.vue";
|
|
32
73
|
defineProps({
|
|
33
74
|
codeBlocksThemeList: { type: Array, required: true }
|
|
34
75
|
});
|
|
35
76
|
const codeBlocksTheme = defineModel("codeBlocksTheme", { type: String, ...{ required: true } });
|
|
77
|
+
const useTwoEditors = defineModel("useTwoEditors", { type: Boolean, ...{ required: true } });
|
|
78
|
+
const useYjs = useRoute().query.useYjs;
|
|
36
79
|
</script>
|
|
@@ -3,12 +3,15 @@ type __VLS_Props = {
|
|
|
3
3
|
};
|
|
4
4
|
type __VLS_ModelProps = {
|
|
5
5
|
"codeBlocksTheme": string;
|
|
6
|
+
"useTwoEditors": boolean;
|
|
6
7
|
};
|
|
7
8
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
10
|
"update:codeBlocksTheme": (value: string) => any;
|
|
11
|
+
"update:useTwoEditors": (value: boolean) => any;
|
|
10
12
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
13
|
"onUpdate:codeBlocksTheme"?: ((value: string) => any) | undefined;
|
|
14
|
+
"onUpdate:useTwoEditors"?: ((value: boolean) => any) | undefined;
|
|
12
15
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
16
|
declare const _default: typeof __VLS_export;
|
|
14
17
|
export default _default;
|
|
@@ -12,6 +12,8 @@ interface Props {
|
|
|
12
12
|
passedDragThreshold: boolean;
|
|
13
13
|
hideChildren: boolean;
|
|
14
14
|
}
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
15
17
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
16
18
|
collapseIndicatorClick: (event: MouseEvent) => any;
|
|
17
19
|
grabHandlePassiveTouchStart: (event: TouchEvent) => any;
|
|
@@ -23,5 +25,3 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
23
25
|
onGrabHandlePointerDown?: ((event: PointerEvent) => any) | undefined;
|
|
24
26
|
onGrabHandleContextMenu?: ((event: PointerEvent) => any) | undefined;
|
|
25
27
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
-
declare const _default: typeof __VLS_export;
|
|
27
|
-
export default _default;
|
|
@@ -12,6 +12,8 @@ interface Props {
|
|
|
12
12
|
passedDragThreshold: boolean;
|
|
13
13
|
hideChildren: boolean;
|
|
14
14
|
}
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
15
17
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
16
18
|
collapseIndicatorClick: (event: MouseEvent) => any;
|
|
17
19
|
grabHandlePassiveTouchStart: (event: TouchEvent) => any;
|
|
@@ -23,5 +25,3 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
23
25
|
onGrabHandlePointerDown?: ((event: PointerEvent) => any) | undefined;
|
|
24
26
|
onGrabHandleContextMenu?: ((event: PointerEvent) => any) | undefined;
|
|
25
27
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
-
declare const _default: typeof __VLS_export;
|
|
27
|
-
export default _default;
|
|
@@ -1,66 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ySyncPlugin, yUndoPlugin } from "y-prosemirror";
|
|
3
|
-
import * as Y from "yjs";
|
|
4
|
-
type YSyncOpts = Parameters<typeof ySyncPlugin>[1];
|
|
5
|
-
type YUndoOpts = Parameters<typeof yUndoPlugin>[0];
|
|
6
|
-
declare module "@tiptap/core" {
|
|
7
|
-
interface Commands<ReturnType> {
|
|
8
|
-
collaboration: {
|
|
9
|
-
/** Sets the fragment to use for the collaboration extension. */
|
|
10
|
-
setFragment: (fragment: Y.XmlFragment | undefined, options?: {
|
|
11
|
-
register?: boolean;
|
|
12
|
-
unregister?: boolean;
|
|
13
|
-
}) => ReturnType;
|
|
14
|
-
/** Enables or disables collaboration. */
|
|
15
|
-
enableCollaboration: (enable: boolean) => ReturnType;
|
|
16
|
-
/**
|
|
17
|
-
* Undo recent changes
|
|
18
|
-
*
|
|
19
|
-
* @example editor.commands.undo()
|
|
20
|
-
*/
|
|
21
|
-
undo: () => ReturnType;
|
|
22
|
-
/**
|
|
23
|
-
* Reapply reverted changes
|
|
24
|
-
*
|
|
25
|
-
* @example editor.commands.redo()
|
|
26
|
-
*/
|
|
27
|
-
redo: () => ReturnType;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
export interface CollaborationStorage {
|
|
32
|
-
/**
|
|
33
|
-
* Whether collaboration is currently disabled.
|
|
34
|
-
* Disabling collaboration will prevent any changes from being synced with other users.
|
|
35
|
-
*/
|
|
36
|
-
isDisabled: boolean;
|
|
37
|
-
}
|
|
38
|
-
export interface CollaborationOptions {
|
|
39
|
-
/**
|
|
40
|
-
* A raw Y.js fragment, can be used instead of `document` and `field`.
|
|
41
|
-
*
|
|
42
|
-
* @example new Y.Doc().getXmlFragment('body')
|
|
43
|
-
*/
|
|
44
|
-
fragment?: Y.XmlFragment | null;
|
|
45
|
-
/**
|
|
46
|
-
* Fired when the content from Yjs is initially rendered to Tiptap.
|
|
47
|
-
*/
|
|
48
|
-
onFirstRender?: () => void;
|
|
49
|
-
/**
|
|
50
|
-
* Options for the Yjs sync plugin.
|
|
51
|
-
*/
|
|
52
|
-
ySyncOptions?: YSyncOpts;
|
|
53
|
-
/**
|
|
54
|
-
* Options for the Yjs undo plugin.
|
|
55
|
-
*/
|
|
56
|
-
yUndoOptions?: YUndoOpts;
|
|
57
|
-
/** @internal */
|
|
58
|
-
_destroySyncPlugin?: () => void;
|
|
59
|
-
}
|
|
1
|
+
import { type CollaborationOptions } from "@tiptap/extension-collaboration";
|
|
60
2
|
/**
|
|
61
|
-
*
|
|
3
|
+
* Extension of the base collaboration extension without prosemirror plugins or storage.
|
|
62
4
|
*
|
|
63
|
-
*
|
|
5
|
+
* We can't use tiptap's collaboration extension (or any extension that creates the sync plugin) because it expects to be configured with the document's ydoc per editor.
|
|
6
|
+
*
|
|
7
|
+
* This doesn't mesh well with how the document api works (see {@link DocumentApi}).
|
|
8
|
+
*
|
|
9
|
+
* Instead we let the extension register anything it wants (shortcuts, commands, etc) except the plugins and storage. This way it can just be added to the list of editor extensions normally.
|
|
10
|
+
*
|
|
11
|
+
* Then there is a seperate function to actually create the plugins:
|
|
12
|
+
*
|
|
13
|
+
* {@link createCollaborationPlugins} creates the plugins **per doc** and can be used from your document api without an editor instance. See {@link useTestDocumentApi} for an example.
|
|
14
|
+
*
|
|
15
|
+
* It's also where the `enableContentCheck` option should be set if you need it. It's IGNORED if passed to the extension.
|
|
64
16
|
*/
|
|
65
|
-
export declare const Collaboration: Extension<CollaborationOptions,
|
|
66
|
-
export {};
|
|
17
|
+
export declare const Collaboration: import("@tiptap/core").Extension<Omit<CollaborationOptions, "document" | "field" | "fragment">, Record<string, never>>;
|
|
@@ -1,169 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
redo,
|
|
5
|
-
undo,
|
|
6
|
-
ySyncPlugin,
|
|
7
|
-
yUndoPlugin,
|
|
8
|
-
yUndoPluginKey,
|
|
9
|
-
yXmlFragmentToProseMirrorRootNode
|
|
10
|
-
} from "y-prosemirror";
|
|
11
|
-
import * as Y from "yjs";
|
|
12
|
-
const ySyncFilterPluginKey = new PluginKey("ySyncFilter");
|
|
13
|
-
function createUndoPlugin(self) {
|
|
14
|
-
const yUndoPluginInstance = yUndoPlugin(self.options.yUndoOptions);
|
|
15
|
-
const originalUndoPluginView = yUndoPluginInstance.spec.view;
|
|
16
|
-
yUndoPluginInstance.spec.view = (view) => {
|
|
17
|
-
const { undoManager } = yUndoPluginKey.getState(view.state);
|
|
18
|
-
if (undoManager.restore) {
|
|
19
|
-
undoManager.restore();
|
|
20
|
-
undoManager.restore = () => {
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
const viewRet = originalUndoPluginView ? originalUndoPluginView(view) : void 0;
|
|
24
|
-
return {
|
|
25
|
-
destroy: () => {
|
|
26
|
-
const hasUndoManSelf = undoManager.trackedOrigins.has(undoManager);
|
|
27
|
-
const observers = undoManager._observers;
|
|
28
|
-
undoManager.restore = () => {
|
|
29
|
-
if (hasUndoManSelf) {
|
|
30
|
-
undoManager.trackedOrigins.add(undoManager);
|
|
31
|
-
}
|
|
32
|
-
undoManager.doc.on("afterTransaction", undoManager.afterTransactionHandler);
|
|
33
|
-
undoManager._observers = observers;
|
|
34
|
-
};
|
|
35
|
-
if (viewRet?.destroy) {
|
|
36
|
-
viewRet.destroy();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
return yUndoPluginInstance;
|
|
42
|
-
}
|
|
43
|
-
function createSyncPlugin(self) {
|
|
44
|
-
const fragment = self.options.fragment;
|
|
45
|
-
if (!fragment) {
|
|
46
|
-
throw new Error("Collaboration requires a fragment.");
|
|
47
|
-
}
|
|
48
|
-
const plugin = ySyncPlugin(fragment, {
|
|
49
|
-
...self.options.ySyncOptions,
|
|
50
|
-
onFirstRender: self.options.onFirstRender
|
|
51
|
-
});
|
|
52
|
-
let off;
|
|
53
|
-
function destroy() {
|
|
54
|
-
off?.();
|
|
55
|
-
}
|
|
56
|
-
if (self.editor.options.enableContentCheck) {
|
|
57
|
-
const onBeforeTransaction = () => {
|
|
58
|
-
try {
|
|
59
|
-
yXmlFragmentToProseMirrorRootNode(fragment, self.editor.schema).check();
|
|
60
|
-
} catch (error) {
|
|
61
|
-
self.editor.emit("contentError", {
|
|
62
|
-
error,
|
|
63
|
-
editor: self.editor,
|
|
64
|
-
disableCollaboration: () => {
|
|
65
|
-
fragment.doc?.destroy();
|
|
66
|
-
self.storage.isDisabled = true;
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
return void 0;
|
|
72
|
-
};
|
|
73
|
-
fragment.doc?.on("beforeTransaction", onBeforeTransaction);
|
|
74
|
-
off = () => {
|
|
75
|
-
fragment.doc?.off("beforeTransaction", onBeforeTransaction);
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return { plugin, destroy };
|
|
79
|
-
}
|
|
80
|
-
function createSyncFilterPlugin(self) {
|
|
81
|
-
if (!self.editor.options.enableContentCheck) return;
|
|
82
|
-
const fragment = self.options.fragment;
|
|
83
|
-
if (!fragment) {
|
|
84
|
-
throw new Error("Collaboration requires a fragment.");
|
|
85
|
-
}
|
|
86
|
-
return new Plugin({
|
|
87
|
-
key: ySyncFilterPluginKey,
|
|
88
|
-
filterTransaction: () => {
|
|
89
|
-
if (self.storage.isDisabled) {
|
|
90
|
-
fragment.doc?.destroy();
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function createPlugins(self) {
|
|
98
|
-
const { plugin: syncPlugin, destroy: destroySyncPlugin } = createSyncPlugin(self);
|
|
99
|
-
self.options._destroySyncPlugin = destroySyncPlugin;
|
|
100
|
-
const filterPlugin = createSyncFilterPlugin(self);
|
|
101
|
-
const plugins = [
|
|
102
|
-
syncPlugin,
|
|
103
|
-
createUndoPlugin(self)
|
|
104
|
-
];
|
|
105
|
-
if (filterPlugin) {
|
|
106
|
-
plugins.push(filterPlugin);
|
|
107
|
-
}
|
|
108
|
-
return plugins;
|
|
109
|
-
}
|
|
110
|
-
export const Collaboration = Extension.create({
|
|
111
|
-
name: "collaboration",
|
|
112
|
-
priority: 1e3,
|
|
113
|
-
addOptions() {
|
|
114
|
-
return {
|
|
115
|
-
document: null,
|
|
116
|
-
field: "default",
|
|
117
|
-
fragment: new Y.Doc().getXmlFragment("prosemirror")
|
|
118
|
-
};
|
|
119
|
-
},
|
|
1
|
+
import BaseCollaboration from "@tiptap/extension-collaboration";
|
|
2
|
+
export const Collaboration = BaseCollaboration.extend({
|
|
120
3
|
addStorage() {
|
|
121
|
-
return {
|
|
122
|
-
isDisabled: false
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
onCreate() {
|
|
126
|
-
if (this.editor.extensionManager.extensions.find((extension) => extension.name === "history")) {
|
|
127
|
-
console.warn(
|
|
128
|
-
'[tiptap warn]: "@tiptap/extension-collaboration" comes with its own history support and is not compatible with "@tiptap/extension-history".'
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
addCommands() {
|
|
133
|
-
return {
|
|
134
|
-
enableCollaboration: (enable) => () => {
|
|
135
|
-
this.storage.isDisabled = !enable;
|
|
136
|
-
return true;
|
|
137
|
-
},
|
|
138
|
-
undo: () => ({ tr, state, dispatch }) => {
|
|
139
|
-
tr.setMeta("preventDispatch", true);
|
|
140
|
-
const undoManager = yUndoPluginKey.getState(state).undoManager;
|
|
141
|
-
if (undoManager.undoStack.length === 0) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
if (!dispatch) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
return undo(state);
|
|
148
|
-
},
|
|
149
|
-
redo: () => ({ tr, state, dispatch }) => {
|
|
150
|
-
tr.setMeta("preventDispatch", true);
|
|
151
|
-
const undoManager = yUndoPluginKey.getState(state).undoManager;
|
|
152
|
-
if (undoManager.redoStack.length === 0) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
if (!dispatch) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
return redo(state);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
},
|
|
162
|
-
onDestroy() {
|
|
163
|
-
this.options._destroySyncPlugin?.();
|
|
4
|
+
return {};
|
|
164
5
|
},
|
|
165
6
|
addProseMirrorPlugins() {
|
|
166
|
-
|
|
167
|
-
return createPlugins(self);
|
|
7
|
+
return [];
|
|
168
8
|
}
|
|
169
9
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CollaborationOptions } from "@tiptap/extension-collaboration";
|
|
2
|
+
import { Plugin } from "@tiptap/pm/state";
|
|
3
|
+
/**
|
|
4
|
+
* Copied from tiptap's collaboration extension with a few minor changes to make it work with our document api.
|
|
5
|
+
*
|
|
6
|
+
* Changes:
|
|
7
|
+
*
|
|
8
|
+
* - A local object is created for the plugin instance to simulate the extension's storage.
|
|
9
|
+
* - When it needs the editor (for the `contentError` event), every editor currently using the doc is iterated over and sent the event.
|
|
10
|
+
* - The normally editor level `enableContentCheck` option should be set here, it has no effect if passed to the extension.
|
|
11
|
+
*
|
|
12
|
+
* See {@link Collaboration} for more info and {@link useTestDocumentApi} for an example of how to use it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createCollaborationPlugins(options: CollaborationOptions & {
|
|
15
|
+
enableContentCheck: boolean;
|
|
16
|
+
}, schema: any, getConnectedEditors?: () => any): Plugin[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
2
|
+
import {
|
|
3
|
+
ySyncPlugin,
|
|
4
|
+
yUndoPlugin,
|
|
5
|
+
yUndoPluginKey,
|
|
6
|
+
yXmlFragmentToProsemirrorJSON
|
|
7
|
+
} from "@tiptap/y-tiptap";
|
|
8
|
+
export function createCollaborationPlugins(options, schema, getConnectedEditors) {
|
|
9
|
+
const storage = {
|
|
10
|
+
isDisabled: false
|
|
11
|
+
};
|
|
12
|
+
const fragment = options.fragment ? options.fragment : options.document.getXmlFragment(options.field);
|
|
13
|
+
const yUndoPluginInstance = yUndoPlugin(options.yUndoOptions);
|
|
14
|
+
const originalUndoPluginView = yUndoPluginInstance.spec.view;
|
|
15
|
+
yUndoPluginInstance.spec.view = (view) => {
|
|
16
|
+
const { undoManager } = yUndoPluginKey.getState(view.state);
|
|
17
|
+
if (undoManager.restore) {
|
|
18
|
+
undoManager.restore();
|
|
19
|
+
undoManager.restore = () => {
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const viewRet = originalUndoPluginView ? originalUndoPluginView(view) : void 0;
|
|
23
|
+
return {
|
|
24
|
+
destroy: () => {
|
|
25
|
+
const hasUndoManSelf = undoManager.trackedOrigins.has(undoManager);
|
|
26
|
+
const observers = undoManager._observers;
|
|
27
|
+
undoManager.restore = () => {
|
|
28
|
+
if (hasUndoManSelf) {
|
|
29
|
+
undoManager.trackedOrigins.add(undoManager);
|
|
30
|
+
}
|
|
31
|
+
undoManager.doc.on("afterTransaction", undoManager.afterTransactionHandler);
|
|
32
|
+
undoManager._observers = observers;
|
|
33
|
+
};
|
|
34
|
+
if (viewRet?.destroy) {
|
|
35
|
+
viewRet.destroy();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const ySyncPluginOptions = {
|
|
41
|
+
...options.ySyncOptions,
|
|
42
|
+
onFirstRender: options.onFirstRender
|
|
43
|
+
};
|
|
44
|
+
const ySyncPluginInstance = ySyncPlugin(fragment, ySyncPluginOptions);
|
|
45
|
+
if (options.enableContentCheck) {
|
|
46
|
+
fragment.doc?.on("beforeTransaction", () => {
|
|
47
|
+
try {
|
|
48
|
+
const jsonContent = yXmlFragmentToProsemirrorJSON(fragment);
|
|
49
|
+
if (jsonContent.content.length === 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
schema.nodeFromJSON(jsonContent).check();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
for (const editor of getConnectedEditors?.() ?? []) {
|
|
55
|
+
editor.emit("contentError", {
|
|
56
|
+
error,
|
|
57
|
+
editor,
|
|
58
|
+
disableCollaboration: () => {
|
|
59
|
+
fragment.doc?.destroy();
|
|
60
|
+
storage.isDisabled = true;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return [
|
|
69
|
+
ySyncPluginInstance,
|
|
70
|
+
yUndoPluginInstance,
|
|
71
|
+
options.enableContentCheck && new Plugin({
|
|
72
|
+
key: new PluginKey("filterInvalidContent"),
|
|
73
|
+
filterTransaction: () => {
|
|
74
|
+
if (storage.isDisabled !== false) {
|
|
75
|
+
fragment.doc?.destroy();
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
].filter(Boolean);
|
|
82
|
+
}
|
|
@@ -64,7 +64,11 @@ export const toggleSuperscriptCommand = {
|
|
|
64
64
|
description: "Superscript the selected text.",
|
|
65
65
|
icon: { component: SuperscriptIcon }
|
|
66
66
|
};
|
|
67
|
-
export const tableCanShow = (state) =>
|
|
67
|
+
export const tableCanShow = (state) => {
|
|
68
|
+
const fromNode = -1 < state.selection.$from.depth ? state.selection.$from.node(-1) : void 0;
|
|
69
|
+
const toNode = -1 < state.selection.$to.depth ? state.selection.$to.node(-1) : void 0;
|
|
70
|
+
return fromNode?.type.name === "tableCell" && toNode?.type.name === "tableCell";
|
|
71
|
+
};
|
|
68
72
|
export const tableAddColBeforeCommand = {
|
|
69
73
|
type: "command",
|
|
70
74
|
command: "addColumnBefore",
|