@milkdown/vue 6.1.4 → 6.3.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/lib/Editor.d.ts +8 -25
- package/lib/Editor.d.ts.map +1 -1
- package/lib/EditorComponent.d.ts +14 -0
- package/lib/EditorComponent.d.ts.map +1 -0
- package/lib/VueNode.d.ts +9 -7
- package/lib/VueNode.d.ts.map +1 -1
- package/lib/VueNodeView.d.ts +14 -16
- package/lib/VueNodeView.d.ts.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +206 -226
- package/lib/index.es.js.map +1 -1
- package/lib/types.d.ts +26 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/useEditor.d.ts +3 -0
- package/lib/useEditor.d.ts.map +1 -0
- package/lib/useGetEditor.d.ts +7 -0
- package/lib/useGetEditor.d.ts.map +1 -0
- package/package.json +7 -6
- package/src/Editor.tsx +57 -71
- package/src/EditorComponent.tsx +25 -0
- package/src/VueNode.tsx +8 -5
- package/src/VueNodeView.tsx +79 -51
- package/src/index.ts +3 -1
- package/src/types.ts +50 -0
- package/src/useEditor.ts +25 -0
- package/src/useGetEditor.ts +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milkdown/vue",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.es.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
"vue"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@milkdown/utils": "6.
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"@milkdown/utils": "6.3.0",
|
|
20
|
+
"@milkdown/exception": "6.3.0",
|
|
21
|
+
"nanoid": "^4.0.0",
|
|
22
|
+
"tslib": "^2.4.0"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"@milkdown/core": "^6.0.1",
|
|
@@ -26,8 +27,8 @@
|
|
|
26
27
|
"vue": "^3.0.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@milkdown/core": "6.
|
|
30
|
-
"@milkdown/prose": "6.
|
|
30
|
+
"@milkdown/core": "6.3.0",
|
|
31
|
+
"@milkdown/prose": "6.3.0",
|
|
31
32
|
"vue": "^3.0.0"
|
|
32
33
|
},
|
|
33
34
|
"nx": {
|
package/src/Editor.tsx
CHANGED
|
@@ -1,83 +1,23 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { Ctx, Editor, editorViewCtx, rootCtx } from '@milkdown/core';
|
|
3
|
-
import { ViewFactory } from '@milkdown/prose';
|
|
4
2
|
import {
|
|
5
3
|
ComponentInternalInstance,
|
|
6
4
|
DefineComponent,
|
|
7
5
|
defineComponent,
|
|
6
|
+
effect,
|
|
8
7
|
getCurrentInstance,
|
|
9
8
|
h,
|
|
10
|
-
inject,
|
|
11
9
|
InjectionKey,
|
|
12
10
|
markRaw,
|
|
13
11
|
onBeforeMount,
|
|
14
|
-
onMounted,
|
|
15
12
|
onUnmounted,
|
|
16
13
|
provide,
|
|
17
|
-
Ref,
|
|
18
|
-
ref,
|
|
19
14
|
shallowReactive,
|
|
20
15
|
} from 'vue';
|
|
21
16
|
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Symbol();
|
|
27
|
-
|
|
28
|
-
type GetEditor = (
|
|
29
|
-
container: HTMLDivElement,
|
|
30
|
-
renderVue: (Component: AnyVueComponent, options?: RenderOptions) => (ctx: Ctx) => ViewFactory,
|
|
31
|
-
) => Editor;
|
|
32
|
-
|
|
33
|
-
const useGetEditor = (getEditor: GetEditor) => {
|
|
34
|
-
const divRef = ref<HTMLDivElement | null>(null);
|
|
35
|
-
const renderVue = inject<(Component: DefineComponent, options?: RenderOptions) => (ctx: Ctx) => ViewFactory>(
|
|
36
|
-
rendererKey,
|
|
37
|
-
() => {
|
|
38
|
-
throw new Error();
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
const editorRef = markRaw<{ editor?: Editor }>({});
|
|
42
|
-
onMounted(() => {
|
|
43
|
-
if (!divRef.value) return;
|
|
44
|
-
|
|
45
|
-
getEditor(divRef.value, renderVue)
|
|
46
|
-
.create()
|
|
47
|
-
.then((editor) => {
|
|
48
|
-
editorRef.editor = editor;
|
|
49
|
-
return;
|
|
50
|
-
})
|
|
51
|
-
.catch((e) => console.error(e));
|
|
52
|
-
});
|
|
53
|
-
onUnmounted(() => {
|
|
54
|
-
const view = editorRef.editor?.action((ctx) => ctx.get(editorViewCtx));
|
|
55
|
-
const root = editorRef.editor?.action((ctx) => ctx.get(rootCtx)) as HTMLElement;
|
|
56
|
-
|
|
57
|
-
root?.firstChild?.remove();
|
|
58
|
-
view?.destroy();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return { divRef, editorRef };
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const EditorComponent = defineComponent<{ editor: GetEditor; editorRef?: Ref<EditorRef> }>({
|
|
65
|
-
name: 'milkdown-dom-root',
|
|
66
|
-
setup: (props, { slots }) => {
|
|
67
|
-
const refs = useGetEditor(props.editor);
|
|
68
|
-
if (props.editorRef) {
|
|
69
|
-
props.editorRef.value = {
|
|
70
|
-
get: () => refs.editorRef.editor,
|
|
71
|
-
dom: () => refs.divRef.value,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return () => <div ref={refs.divRef}>{slots['default']?.()}</div>;
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
EditorComponent['props'] = ['editor', 'editorRef'];
|
|
79
|
-
|
|
80
|
-
export type EditorRef = { get: () => Editor | undefined; dom: () => HTMLDivElement | null };
|
|
17
|
+
import { EditorComponent, EditorRef } from './EditorComponent';
|
|
18
|
+
import { EditorInfo, EditorInfoCtx } from './types';
|
|
19
|
+
import { rendererKey } from './useGetEditor';
|
|
20
|
+
import { createVueView } from './VueNodeView';
|
|
81
21
|
|
|
82
22
|
const rootInstance: {
|
|
83
23
|
instance: null | ComponentInternalInstance;
|
|
@@ -88,8 +28,35 @@ export const getRootInstance = () => {
|
|
|
88
28
|
return rootInstance.instance;
|
|
89
29
|
};
|
|
90
30
|
|
|
31
|
+
export const editorInfoCtxKey: InjectionKey<EditorInfoCtx> = Symbol();
|
|
32
|
+
|
|
33
|
+
const refDeprecatedInfo = `
|
|
34
|
+
@milkdown/vue:
|
|
35
|
+
Passing ref to VueEditor will soon be deprecated, please use:
|
|
36
|
+
|
|
37
|
+
const { editor, getInstance, getDom, loading } = useEditor(/* creator */);
|
|
38
|
+
|
|
39
|
+
effect(() => {
|
|
40
|
+
if (!loading) {
|
|
41
|
+
const editor = getInstance();
|
|
42
|
+
const rootDOM = getDom();
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
<VueEditor editor={editor} />
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const compositionDeprecatedInfo = `
|
|
50
|
+
@milkdown/vue:
|
|
51
|
+
Passing editor directly to VueEditor will soon be deprecated, please use:
|
|
52
|
+
|
|
53
|
+
const { editor } = useEditor(/* creator */);
|
|
54
|
+
|
|
55
|
+
<VueEditor editor={editor} />
|
|
56
|
+
`;
|
|
57
|
+
|
|
91
58
|
type PortalPair = [key: string, component: DefineComponent];
|
|
92
|
-
export const VueEditor = defineComponent<{ editor:
|
|
59
|
+
export const VueEditor = defineComponent<{ editor: EditorInfo; editorRef?: EditorRef }>({
|
|
93
60
|
name: 'milkdown-vue-root',
|
|
94
61
|
setup: (props) => {
|
|
95
62
|
const portals = shallowReactive<PortalPair[]>([]);
|
|
@@ -113,12 +80,35 @@ export const VueEditor = defineComponent<{ editor: GetEditor; editorRef?: Ref<Ed
|
|
|
113
80
|
portals.splice(index, 1);
|
|
114
81
|
});
|
|
115
82
|
const renderVue = createVueView(addPortal, removePortalByKey);
|
|
83
|
+
|
|
116
84
|
provide(rendererKey, renderVue);
|
|
117
85
|
|
|
86
|
+
const usingDeprecatedCompositionAPI = Object.hasOwnProperty.call(props.editor, 'getInstance');
|
|
87
|
+
|
|
88
|
+
const { getEditorCallback, dom, editor, loading } = usingDeprecatedCompositionAPI
|
|
89
|
+
? // @ts-expect-error deprecated old composition API
|
|
90
|
+
(props.editor.editor as EditorInfo)
|
|
91
|
+
: props.editor;
|
|
92
|
+
|
|
93
|
+
effect(() => {
|
|
94
|
+
if (usingDeprecatedCompositionAPI) {
|
|
95
|
+
console.warn(compositionDeprecatedInfo);
|
|
96
|
+
}
|
|
97
|
+
if (props.editorRef) {
|
|
98
|
+
console.warn(refDeprecatedInfo);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
provide(editorInfoCtxKey, {
|
|
103
|
+
dom,
|
|
104
|
+
editor,
|
|
105
|
+
loading,
|
|
106
|
+
});
|
|
107
|
+
|
|
118
108
|
return () => {
|
|
119
109
|
const portalElements = portals.map(([id, P]) => <P key={id} />);
|
|
120
110
|
return (
|
|
121
|
-
<EditorComponent editorRef={props.editorRef} editor={
|
|
111
|
+
<EditorComponent editorRef={props.editorRef} editor={getEditorCallback.value}>
|
|
122
112
|
{portalElements}
|
|
123
113
|
</EditorComponent>
|
|
124
114
|
);
|
|
@@ -126,7 +116,3 @@ export const VueEditor = defineComponent<{ editor: GetEditor; editorRef?: Ref<Ed
|
|
|
126
116
|
},
|
|
127
117
|
});
|
|
128
118
|
VueEditor['props'] = ['editor', 'editorRef'];
|
|
129
|
-
|
|
130
|
-
export const useEditor = (getEditor: GetEditor) => {
|
|
131
|
-
return (...args: Parameters<GetEditor>) => getEditor(...args);
|
|
132
|
-
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
import { Editor } from '@milkdown/core';
|
|
3
|
+
import { defineComponent, h, inject } from 'vue';
|
|
4
|
+
|
|
5
|
+
import { editorInfoCtxKey } from './Editor';
|
|
6
|
+
import { EditorInfoCtx, GetEditor } from './types';
|
|
7
|
+
import { useGetEditor } from './useGetEditor';
|
|
8
|
+
|
|
9
|
+
export const EditorComponent = defineComponent<{ editor: GetEditor; editorRef?: EditorRef }>({
|
|
10
|
+
name: 'milkdown-dom-root',
|
|
11
|
+
setup: (props, { slots }) => {
|
|
12
|
+
useGetEditor(props.editor);
|
|
13
|
+
const ctx = inject(editorInfoCtxKey, {} as EditorInfoCtx);
|
|
14
|
+
|
|
15
|
+
if (props.editorRef) {
|
|
16
|
+
props.editorRef.get = () => ctx.editor.value;
|
|
17
|
+
props.editorRef.dom = () => ctx.dom.value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return () => <div ref={ctx.dom}>{slots['default']?.()}</div>;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
EditorComponent['props'] = ['editor', 'editorRef'];
|
|
24
|
+
|
|
25
|
+
export type EditorRef = { get: () => Editor | undefined; dom: () => HTMLDivElement | null };
|
package/src/VueNode.tsx
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
import { Ctx } from '@milkdown/core';
|
|
3
3
|
import { Mark, Node } from '@milkdown/prose/model';
|
|
4
4
|
import { Decoration, EditorView } from '@milkdown/prose/view';
|
|
5
|
-
import { defineComponent, h, InjectionKey, provide } from 'vue';
|
|
5
|
+
import { defineComponent, h, inject, InjectionKey, provide, Ref } from 'vue';
|
|
6
6
|
|
|
7
|
-
export type NodeContext = {
|
|
7
|
+
export type NodeContext<T extends Node | Mark = Node | Mark> = {
|
|
8
8
|
ctx: Ctx;
|
|
9
|
-
node:
|
|
9
|
+
node: Ref<T>;
|
|
10
10
|
view: EditorView;
|
|
11
|
-
getPos: boolean | (() => number);
|
|
12
|
-
decorations: Decoration[]
|
|
11
|
+
getPos: T extends Mark ? boolean : T extends Node ? () => number : boolean | (() => number);
|
|
12
|
+
decorations: Ref<readonly Decoration[]>;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const nodeMetadata: InjectionKey<NodeContext> = Symbol();
|
|
16
16
|
|
|
17
|
+
export type UseNodeCtx = <T extends Node | Mark = Node | Mark>() => NodeContext<T>;
|
|
18
|
+
export const useNodeCtx: UseNodeCtx = () => inject(nodeMetadata) as NodeContext<never>;
|
|
19
|
+
|
|
17
20
|
export const VueNodeContainer = defineComponent<NodeContext & { as: string }>({
|
|
18
21
|
name: 'milkdown-node-container',
|
|
19
22
|
setup: ({ node, view, getPos, decorations, ctx, as }, context) => {
|
package/src/VueNodeView.tsx
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
import { Ctx } from '@milkdown/core';
|
|
3
|
-
import type { ViewFactory } from '@milkdown/prose';
|
|
4
3
|
import { Mark, Node } from '@milkdown/prose/model';
|
|
5
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Decoration,
|
|
6
|
+
DecorationSource,
|
|
7
|
+
EditorView,
|
|
8
|
+
MarkViewConstructor,
|
|
9
|
+
NodeView,
|
|
10
|
+
NodeViewConstructor,
|
|
11
|
+
} from '@milkdown/prose/view';
|
|
6
12
|
import { customAlphabet } from 'nanoid';
|
|
7
|
-
import { DefineComponent, defineComponent, h, markRaw, Teleport } from 'vue';
|
|
13
|
+
import { DefineComponent, defineComponent, h, markRaw, Ref, ref, Teleport } from 'vue';
|
|
8
14
|
|
|
9
15
|
import { getRootInstance } from '.';
|
|
10
16
|
import { Content, VueNodeContainer } from './VueNode';
|
|
@@ -14,12 +20,16 @@ const nanoid = customAlphabet('abcedfghicklmn', 10);
|
|
|
14
20
|
export type RenderOptions = Partial<
|
|
15
21
|
{
|
|
16
22
|
as: string;
|
|
17
|
-
|
|
23
|
+
update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
|
|
24
|
+
} & Pick<NodeView, 'ignoreMutation' | 'deselectNode' | 'selectNode' | 'destroy'>
|
|
18
25
|
>;
|
|
19
26
|
|
|
20
27
|
export const createVueView =
|
|
21
28
|
(addPortal: (portal: DefineComponent, key: string) => void, removePortalByKey: (key: string) => void) =>
|
|
22
|
-
(
|
|
29
|
+
(
|
|
30
|
+
component: DefineComponent,
|
|
31
|
+
options: RenderOptions = {},
|
|
32
|
+
): ((ctx: Ctx) => NodeViewConstructor | MarkViewConstructor) =>
|
|
23
33
|
(ctx) =>
|
|
24
34
|
(node, view, getPos, decorations) =>
|
|
25
35
|
new VueNodeView(ctx, component, addPortal, removePortalByKey, options, node, view, getPos, decorations);
|
|
@@ -29,65 +39,76 @@ export class VueNodeView implements NodeView {
|
|
|
29
39
|
key: string;
|
|
30
40
|
|
|
31
41
|
get isInlineOrMark() {
|
|
32
|
-
return this.node instanceof Mark || this.node.isInline;
|
|
42
|
+
return this.node.value instanceof Mark || this.node.value.isInline;
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
private node: Ref<Node | Mark>;
|
|
46
|
+
private decorations: Ref<readonly Decoration[]>;
|
|
47
|
+
|
|
35
48
|
constructor(
|
|
36
49
|
private ctx: Ctx,
|
|
37
50
|
private component: DefineComponent,
|
|
38
51
|
private addPortal: (portal: DefineComponent, key: string) => void,
|
|
39
52
|
private removePortalByKey: (key: string) => void,
|
|
40
53
|
private options: RenderOptions,
|
|
41
|
-
|
|
54
|
+
node: Node | Mark,
|
|
42
55
|
private view: EditorView,
|
|
43
56
|
private getPos: boolean | (() => number),
|
|
44
|
-
|
|
57
|
+
decorations: readonly Decoration[],
|
|
45
58
|
) {
|
|
46
59
|
this.key = nanoid();
|
|
60
|
+
this.node = ref(node);
|
|
61
|
+
this.decorations = ref(decorations);
|
|
47
62
|
const elementName = options.as ? options.as : this.isInlineOrMark ? 'span' : 'div';
|
|
48
63
|
this.teleportDOM = document.createElement(elementName);
|
|
49
64
|
this.renderPortal();
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
get dom() {
|
|
53
|
-
return this.teleportDOM.firstElementChild || this.teleportDOM;
|
|
68
|
+
return (this.teleportDOM.firstElementChild || this.teleportDOM) as HTMLElement;
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
get contentDOM() {
|
|
57
72
|
if (this.node instanceof Node && this.node.isLeaf) {
|
|
58
|
-
return
|
|
73
|
+
return undefined;
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
return this.teleportDOM.querySelector('[data-view-content]') || this.dom;
|
|
76
|
+
return this.teleportDOM.querySelector<HTMLElement>('[data-view-content]') || this.dom;
|
|
62
77
|
}
|
|
63
78
|
|
|
79
|
+
getPortal = (): DefineComponent => {
|
|
80
|
+
const CustomComponent = this.component;
|
|
81
|
+
const elementName = this.options.as ? this.options.as : this.isInlineOrMark ? 'span' : 'div';
|
|
82
|
+
return markRaw(
|
|
83
|
+
defineComponent({
|
|
84
|
+
name: 'milkdown-portal',
|
|
85
|
+
setup: () => {
|
|
86
|
+
return () => (
|
|
87
|
+
<Teleport key={this.key} to={this.teleportDOM}>
|
|
88
|
+
<VueNodeContainer
|
|
89
|
+
as={elementName}
|
|
90
|
+
ctx={this.ctx}
|
|
91
|
+
node={this.node}
|
|
92
|
+
view={this.view}
|
|
93
|
+
getPos={this.getPos}
|
|
94
|
+
decorations={this.decorations}
|
|
95
|
+
>
|
|
96
|
+
<CustomComponent>
|
|
97
|
+
<Content isInline={this.isInlineOrMark} />
|
|
98
|
+
</CustomComponent>
|
|
99
|
+
</VueNodeContainer>
|
|
100
|
+
</Teleport>
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
}) as DefineComponent,
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
64
107
|
renderPortal() {
|
|
65
108
|
if (!this.teleportDOM) return;
|
|
66
109
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
const Portal = defineComponent({
|
|
70
|
-
name: 'milkdown-portal',
|
|
71
|
-
setup: () => {
|
|
72
|
-
return () => (
|
|
73
|
-
<Teleport key={this.key} to={this.teleportDOM}>
|
|
74
|
-
<VueNodeContainer
|
|
75
|
-
as={elementName}
|
|
76
|
-
ctx={this.ctx}
|
|
77
|
-
node={this.node}
|
|
78
|
-
view={this.view}
|
|
79
|
-
getPos={this.getPos}
|
|
80
|
-
decorations={this.decorations}
|
|
81
|
-
>
|
|
82
|
-
<CustomComponent>
|
|
83
|
-
<Content isInline={this.isInlineOrMark} />
|
|
84
|
-
</CustomComponent>
|
|
85
|
-
</VueNodeContainer>
|
|
86
|
-
</Teleport>
|
|
87
|
-
);
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
this.addPortal(markRaw(Portal) as DefineComponent, this.key);
|
|
110
|
+
const Portal = this.getPortal();
|
|
111
|
+
this.addPortal(Portal, this.key);
|
|
91
112
|
const instance = getRootInstance();
|
|
92
113
|
if (instance) {
|
|
93
114
|
instance.update();
|
|
@@ -96,10 +117,11 @@ export class VueNodeView implements NodeView {
|
|
|
96
117
|
|
|
97
118
|
destroy() {
|
|
98
119
|
this.options.destroy?.();
|
|
120
|
+
this.teleportDOM.remove();
|
|
99
121
|
this.removePortalByKey(this.key);
|
|
100
122
|
}
|
|
101
123
|
|
|
102
|
-
ignoreMutation(mutation: MutationRecord
|
|
124
|
+
ignoreMutation(mutation: MutationRecord) {
|
|
103
125
|
if (this.options.ignoreMutation) {
|
|
104
126
|
return this.options.ignoreMutation(mutation);
|
|
105
127
|
}
|
|
@@ -113,7 +135,7 @@ export class VueNodeView implements NodeView {
|
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
137
|
|
|
116
|
-
if (mutation.type === 'selection') {
|
|
138
|
+
if ((mutation as unknown as { type: string }).type === 'selection') {
|
|
117
139
|
return false;
|
|
118
140
|
}
|
|
119
141
|
|
|
@@ -128,24 +150,30 @@ export class VueNodeView implements NodeView {
|
|
|
128
150
|
return true;
|
|
129
151
|
}
|
|
130
152
|
|
|
131
|
-
update(node: Node, decorations: Decoration[], innerDecorations:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
update(node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) {
|
|
154
|
+
const innerUpdate = () => {
|
|
155
|
+
if (this.options.update) {
|
|
156
|
+
const result = this.options.update?.(node, decorations, innerDecorations);
|
|
157
|
+
if (result != null) {
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (this.node.value.type !== node.type) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (node === this.node.value && this.decorations.value === decorations) {
|
|
166
|
+
return true;
|
|
136
167
|
}
|
|
137
|
-
}
|
|
138
|
-
if (this.node.type !== node.type) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
this.node.value = node;
|
|
170
|
+
this.decorations.value = decorations;
|
|
143
171
|
return true;
|
|
144
|
-
}
|
|
172
|
+
};
|
|
145
173
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return
|
|
174
|
+
const shouldUpdate = innerUpdate();
|
|
175
|
+
|
|
176
|
+
return shouldUpdate;
|
|
149
177
|
}
|
|
150
178
|
|
|
151
179
|
selectNode = this.options?.selectNode;
|
package/src/index.ts
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
import { Ctx, Editor } from '@milkdown/core';
|
|
3
|
+
import type { Mark, Node } from '@milkdown/prose/model';
|
|
4
|
+
import { Decoration, DecorationSource, MarkViewConstructor, NodeView, NodeViewConstructor } from '@milkdown/prose/view';
|
|
5
|
+
import { Ref } from 'vue';
|
|
6
|
+
|
|
7
|
+
import { AnyVueComponent } from './utils';
|
|
8
|
+
|
|
9
|
+
export type RenderOptions = Partial<
|
|
10
|
+
{
|
|
11
|
+
as: string;
|
|
12
|
+
update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
|
|
13
|
+
} & Pick<NodeView, 'ignoreMutation' | 'deselectNode' | 'selectNode' | 'destroy'>
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
export type RenderVue<U = never> = <T extends Node | Mark = Node | Mark>(
|
|
17
|
+
Component: AnyVueComponent,
|
|
18
|
+
options?: RenderOptions,
|
|
19
|
+
) => (
|
|
20
|
+
ctx: Ctx,
|
|
21
|
+
) => U extends never
|
|
22
|
+
? T extends Node
|
|
23
|
+
? NodeViewConstructor
|
|
24
|
+
: T extends Mark
|
|
25
|
+
? MarkViewConstructor
|
|
26
|
+
: NodeViewConstructor & MarkViewConstructor
|
|
27
|
+
: U extends Node
|
|
28
|
+
? NodeViewConstructor
|
|
29
|
+
: U extends Mark
|
|
30
|
+
? MarkViewConstructor
|
|
31
|
+
: NodeViewConstructor & MarkViewConstructor;
|
|
32
|
+
|
|
33
|
+
export type GetEditor = (container: HTMLDivElement, renderVue: RenderVue) => Editor;
|
|
34
|
+
|
|
35
|
+
export type EditorInfoCtx = {
|
|
36
|
+
dom: Ref<HTMLDivElement | null>;
|
|
37
|
+
editor: Ref<Editor | undefined>;
|
|
38
|
+
loading: Ref<boolean>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type EditorInfo = {
|
|
42
|
+
getEditorCallback: Ref<GetEditor>;
|
|
43
|
+
} & EditorInfoCtx;
|
|
44
|
+
|
|
45
|
+
export type UseEditorReturn = {
|
|
46
|
+
loading: Ref<boolean>;
|
|
47
|
+
getInstance: () => Editor | undefined;
|
|
48
|
+
getDom: () => HTMLDivElement | null;
|
|
49
|
+
editor: EditorInfo;
|
|
50
|
+
};
|
package/src/useEditor.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
|
|
3
|
+
import { Editor } from '@milkdown/core';
|
|
4
|
+
import { ref } from 'vue';
|
|
5
|
+
|
|
6
|
+
import { GetEditor, UseEditorReturn } from './types';
|
|
7
|
+
|
|
8
|
+
export const useEditor = (getEditor: GetEditor): UseEditorReturn => {
|
|
9
|
+
const dom = ref<HTMLDivElement | null>(null);
|
|
10
|
+
const editor = ref<Editor>();
|
|
11
|
+
const loading = ref(true);
|
|
12
|
+
const getEditorCallback = ref<GetEditor>((...args) => getEditor(...args));
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
loading,
|
|
16
|
+
getInstance: () => editor.value,
|
|
17
|
+
getDom: () => dom.value,
|
|
18
|
+
editor: {
|
|
19
|
+
getEditorCallback,
|
|
20
|
+
dom,
|
|
21
|
+
editor,
|
|
22
|
+
loading,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
+
import { Ctx, editorViewCtx, rootCtx } from '@milkdown/core';
|
|
3
|
+
import { vueRendererCallOutOfScope } from '@milkdown/exception';
|
|
4
|
+
import { MarkViewConstructor, NodeViewConstructor } from '@milkdown/prose/view';
|
|
5
|
+
import { DefineComponent, inject, InjectionKey, onMounted, onUnmounted, ref } from 'vue';
|
|
6
|
+
|
|
7
|
+
import { editorInfoCtxKey } from '.';
|
|
8
|
+
import { EditorInfoCtx, GetEditor, RenderOptions, RenderVue } from './types';
|
|
9
|
+
|
|
10
|
+
export const rendererKey: InjectionKey<
|
|
11
|
+
(component: DefineComponent, options?: RenderOptions) => (ctx: Ctx) => NodeViewConstructor | MarkViewConstructor
|
|
12
|
+
> = Symbol();
|
|
13
|
+
|
|
14
|
+
export const useGetEditor = (getEditor: GetEditor) => {
|
|
15
|
+
const renderVue = inject<RenderVue>(rendererKey, () => {
|
|
16
|
+
throw vueRendererCallOutOfScope();
|
|
17
|
+
});
|
|
18
|
+
const { dom, loading, editor: editorRef } = inject(editorInfoCtxKey, {} as EditorInfoCtx);
|
|
19
|
+
const lock = ref(false);
|
|
20
|
+
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
if (!dom.value) return;
|
|
23
|
+
|
|
24
|
+
const editor = getEditor(dom.value, renderVue);
|
|
25
|
+
if (!editor) return;
|
|
26
|
+
|
|
27
|
+
if (lock.value) return;
|
|
28
|
+
|
|
29
|
+
loading.value = true;
|
|
30
|
+
lock.value = true;
|
|
31
|
+
|
|
32
|
+
editor
|
|
33
|
+
.create()
|
|
34
|
+
.then((editor) => {
|
|
35
|
+
editorRef.value = editor;
|
|
36
|
+
return;
|
|
37
|
+
})
|
|
38
|
+
.finally(() => {
|
|
39
|
+
loading.value = false;
|
|
40
|
+
lock.value = false;
|
|
41
|
+
})
|
|
42
|
+
.catch((e) => console.error(e));
|
|
43
|
+
});
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
const view = editorRef.value?.action((ctx) => ctx.get(editorViewCtx));
|
|
46
|
+
const root = editorRef.value?.action((ctx) => ctx.get(rootCtx)) as HTMLElement;
|
|
47
|
+
|
|
48
|
+
root?.firstChild?.remove();
|
|
49
|
+
view?.destroy();
|
|
50
|
+
});
|
|
51
|
+
};
|