@handlewithcare/react-prosemirror 2.8.0 → 2.9.0-tiptap.24
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/cjs/StaticEditorView.js +3 -0
- package/dist/cjs/commands/reorderSiblings.js +60 -45
- package/dist/cjs/components/CustomNodeView.js +132 -0
- package/dist/cjs/components/DefaultNodeView.js +67 -0
- package/dist/cjs/components/DocNodeView.js +96 -0
- package/dist/cjs/components/MarkView.js +119 -0
- package/dist/cjs/components/NodeView.js +86 -0
- package/dist/cjs/components/NodeViewComponentProps.js +4 -0
- package/dist/cjs/components/ReactNodeView.js +174 -0
- package/dist/cjs/components/nodes/ReactNodeView.js +4 -4
- package/dist/cjs/hooks/useEditor.js +4 -0
- package/dist/cjs/hooks/useEditorEventCallback.js +1 -1
- package/dist/cjs/tiptap/ReactProseMirrorNodeView.js +26 -0
- package/dist/cjs/tiptap/TiptapEditor.js +34 -0
- package/dist/cjs/tiptap/TiptapEditorContent.js +142 -0
- package/dist/cjs/tiptap/TiptapEditorView.js +118 -0
- package/dist/cjs/tiptap/TiptapNodeView.js +26 -0
- package/dist/cjs/tiptap/contexts/TiptapEditorContext.js +12 -0
- package/dist/cjs/tiptap/extensions/ReactProseMirror.js +40 -0
- package/dist/cjs/tiptap/hooks/useIsInReactProseMirror.js +15 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditor.js +43 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditorEffect.js +35 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditorEventCallback.js +35 -0
- package/dist/cjs/tiptap/index.js +48 -0
- package/dist/cjs/tiptap/tiptapNodeView.js +237 -0
- package/dist/cjs/viewdesc.js +5 -5
- package/dist/esm/StaticEditorView.js +3 -0
- package/dist/esm/commands/reorderSiblings.js +49 -42
- package/dist/esm/components/CustomNodeView.js +81 -0
- package/dist/esm/components/DefaultNodeView.js +16 -0
- package/dist/esm/components/DocNodeView.js +45 -0
- package/dist/esm/components/MarkView.js +68 -0
- package/dist/esm/components/NodeView.js +35 -0
- package/dist/esm/components/NodeViewComponentProps.js +1 -0
- package/dist/esm/components/ReactNodeView.js +123 -0
- package/dist/esm/components/nodes/ReactNodeView.js +4 -4
- package/dist/esm/hooks/useEditor.js +4 -0
- package/dist/esm/hooks/useEditorEffect.js +4 -0
- package/dist/esm/hooks/useEditorEventCallback.js +4 -6
- package/dist/esm/tiptap/ReactProseMirrorNodeView.js +22 -0
- package/dist/esm/tiptap/TiptapEditor.js +24 -0
- package/dist/esm/tiptap/TiptapEditorContent.js +91 -0
- package/dist/esm/tiptap/TiptapEditorView.js +69 -0
- package/dist/esm/tiptap/TiptapNodeView.js +22 -0
- package/dist/esm/tiptap/contexts/TiptapEditorContext.js +2 -0
- package/dist/esm/tiptap/extensions/ReactProseMirror.js +30 -0
- package/dist/esm/tiptap/hooks/useIsInReactProseMirror.js +5 -0
- package/dist/esm/tiptap/hooks/useTiptapEditor.js +33 -0
- package/dist/esm/tiptap/hooks/useTiptapEditorEffect.js +42 -0
- package/dist/esm/tiptap/hooks/useTiptapEditorEventCallback.js +35 -0
- package/dist/esm/tiptap/index.js +9 -0
- package/dist/esm/tiptap/tiptapNodeView.js +205 -0
- package/dist/esm/viewdesc.js +5 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/AbstractEditorView.d.ts +1 -0
- package/dist/types/StaticEditorView.d.ts +1 -0
- package/dist/types/commands/__tests__/reorderSiblings.test.d.ts +1 -0
- package/dist/types/commands/reorderSiblings.d.ts +3 -1
- package/dist/types/components/CustomNodeView.d.ts +12 -0
- package/dist/types/components/DefaultNodeView.d.ts +3 -0
- package/dist/types/components/DocNodeView.d.ts +12 -0
- package/dist/types/components/MarkView.d.ts +9 -0
- package/dist/types/components/NodeView.d.ts +11 -0
- package/dist/types/components/NodeViewComponentProps.d.ts +12 -0
- package/dist/types/components/ReactNodeView.d.ts +13 -0
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/hooks/useEditorEffect.d.ts +4 -0
- package/dist/types/hooks/useEditorEventCallback.d.ts +4 -6
- package/dist/types/props.d.ts +26 -26
- package/dist/types/tiptap/ReactProseMirrorNodeView.d.ts +15 -0
- package/dist/types/tiptap/TiptapEditor.d.ts +6 -0
- package/dist/types/tiptap/TiptapEditorContent.d.ts +19 -0
- package/dist/types/tiptap/TiptapEditorView.d.ts +16 -0
- package/dist/types/tiptap/TiptapNodeView.d.ts +15 -0
- package/dist/types/tiptap/contexts/TiptapEditorContext.d.ts +6 -0
- package/dist/types/tiptap/extensions/ReactProseMirror.d.ts +9 -0
- package/dist/types/tiptap/hooks/useIsInReactProseMirror.d.ts +1 -0
- package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +4 -0
- package/dist/types/tiptap/hooks/useTiptapEditorEffect.d.ts +21 -0
- package/dist/types/tiptap/hooks/useTiptapEditorEventCallback.d.ts +13 -0
- package/dist/types/tiptap/index.d.ts +9 -0
- package/dist/types/tiptap/tiptapNodeView.d.ts +50 -0
- package/package.json +19 -11
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { cloneElement, createElement, forwardRef, memo, useImperativeHandle, useMemo, useRef } from "react";
|
|
2
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
|
+
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
4
|
+
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
5
|
+
export const DocNodeView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function DocNodeView(param, ref) {
|
|
6
|
+
let { as, node, getPos, decorations, innerDecorations, setMount, ...elementProps } = param;
|
|
7
|
+
const innerRef = useRef(null);
|
|
8
|
+
useImperativeHandle(ref, ()=>innerRef.current);
|
|
9
|
+
useImperativeHandle(setMount, ()=>innerRef.current);
|
|
10
|
+
const nodeProps = useMemo(()=>({
|
|
11
|
+
node,
|
|
12
|
+
getPos,
|
|
13
|
+
decorations,
|
|
14
|
+
innerDecorations
|
|
15
|
+
}), [
|
|
16
|
+
node,
|
|
17
|
+
getPos,
|
|
18
|
+
decorations,
|
|
19
|
+
innerDecorations
|
|
20
|
+
]);
|
|
21
|
+
const { childContextValue } = useNodeViewDescriptor(innerRef, ()=>{
|
|
22
|
+
const dom = innerRef.current;
|
|
23
|
+
return {
|
|
24
|
+
dom,
|
|
25
|
+
contentDOM: dom,
|
|
26
|
+
update () {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}, nodeProps);
|
|
31
|
+
const children = /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
32
|
+
value: childContextValue
|
|
33
|
+
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
34
|
+
getPos: getPos,
|
|
35
|
+
node: node,
|
|
36
|
+
innerDecorations: innerDecorations
|
|
37
|
+
}));
|
|
38
|
+
const props = {
|
|
39
|
+
...elementProps,
|
|
40
|
+
suppressContentEditableWarning: true,
|
|
41
|
+
ref: innerRef
|
|
42
|
+
};
|
|
43
|
+
const element = as ? /*#__PURE__*/ cloneElement(as, props, children) : /*#__PURE__*/ createElement("div", props, children);
|
|
44
|
+
return nodeProps.decorations.reduce(wrapInDeco, element);
|
|
45
|
+
}));
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { forwardRef, memo, useContext, useImperativeHandle, useMemo, useRef } from "react";
|
|
2
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
|
+
import { useClientLayoutEffect } from "../hooks/useClientLayoutEffect.js";
|
|
4
|
+
import { MarkViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
5
|
+
import { OutputSpec } from "./OutputSpec.js";
|
|
6
|
+
export const MarkView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function MarkView(param, ref) {
|
|
7
|
+
let { mark, getPos, children } = param;
|
|
8
|
+
const { siblingsRef, parentRef } = useContext(ChildDescriptorsContext);
|
|
9
|
+
const viewDescRef = useRef(undefined);
|
|
10
|
+
const childDescriptors = useRef([]);
|
|
11
|
+
const domRef = useRef(null);
|
|
12
|
+
useImperativeHandle(ref, ()=>{
|
|
13
|
+
return domRef.current;
|
|
14
|
+
}, []);
|
|
15
|
+
const outputSpec = useMemo(()=>mark.type.spec.toDOM?.(mark, true), [
|
|
16
|
+
mark
|
|
17
|
+
]);
|
|
18
|
+
if (!outputSpec) throw new Error(`Mark spec for ${mark.type.name} is missing toDOM`);
|
|
19
|
+
useClientLayoutEffect(()=>{
|
|
20
|
+
const siblings = siblingsRef.current;
|
|
21
|
+
return ()=>{
|
|
22
|
+
if (!viewDescRef.current) return;
|
|
23
|
+
if (siblings.includes(viewDescRef.current)) {
|
|
24
|
+
const index = siblings.indexOf(viewDescRef.current);
|
|
25
|
+
siblings.splice(index, 1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}, [
|
|
29
|
+
siblingsRef
|
|
30
|
+
]);
|
|
31
|
+
useClientLayoutEffect(()=>{
|
|
32
|
+
if (!domRef.current) return;
|
|
33
|
+
const firstChildDesc = childDescriptors.current[0];
|
|
34
|
+
if (!viewDescRef.current) {
|
|
35
|
+
viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, getPos, mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current, {
|
|
36
|
+
dom: domRef.current,
|
|
37
|
+
contentDOM: firstChildDesc?.dom.parentElement ?? domRef.current
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
viewDescRef.current.parent = parentRef.current;
|
|
41
|
+
viewDescRef.current.spec.dom = viewDescRef.current.dom = domRef.current;
|
|
42
|
+
viewDescRef.current.children = childDescriptors.current;
|
|
43
|
+
viewDescRef.current.spec.contentDOM = viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
|
|
44
|
+
viewDescRef.current.mark = mark;
|
|
45
|
+
}
|
|
46
|
+
if (!siblingsRef.current.includes(viewDescRef.current)) {
|
|
47
|
+
siblingsRef.current.push(viewDescRef.current);
|
|
48
|
+
}
|
|
49
|
+
siblingsRef.current.sort(sortViewDescs);
|
|
50
|
+
for (const childDesc of childDescriptors.current){
|
|
51
|
+
childDesc.parent = viewDescRef.current;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const childContextValue = useMemo(()=>({
|
|
55
|
+
parentRef: viewDescRef,
|
|
56
|
+
siblingsRef: childDescriptors
|
|
57
|
+
}), [
|
|
58
|
+
childDescriptors,
|
|
59
|
+
viewDescRef
|
|
60
|
+
]);
|
|
61
|
+
return /*#__PURE__*/ React.createElement(OutputSpec, {
|
|
62
|
+
ref: domRef,
|
|
63
|
+
outputSpec: outputSpec,
|
|
64
|
+
isMark: true
|
|
65
|
+
}, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
66
|
+
value: childContextValue
|
|
67
|
+
}, children));
|
|
68
|
+
}));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { memo, useContext, useMemo } from "react";
|
|
2
|
+
import { NodeViewContext } from "../contexts/NodeViewContext.js";
|
|
3
|
+
import { CustomNodeView } from "./CustomNodeView.js";
|
|
4
|
+
import { DefaultNodeView } from "./DefaultNodeView.js";
|
|
5
|
+
import { ReactNodeView } from "./ReactNodeView.js";
|
|
6
|
+
export const NodeView = /*#__PURE__*/ memo(function NodeView(props) {
|
|
7
|
+
const { components, constructors } = useContext(NodeViewContext);
|
|
8
|
+
const component = components[props.node.type.name] ?? DefaultNodeView;
|
|
9
|
+
const constructor = constructors[props.node.type.name];
|
|
10
|
+
// Construct a wrapper component so that the node view remounts when either
|
|
11
|
+
// its component or constructor changes. A React node view would remount if
|
|
12
|
+
// its underlying component changed without this wrapper, but a custom node
|
|
13
|
+
// view otherwise uses the same React components for all custom node views.
|
|
14
|
+
const Component = useMemo(()=>{
|
|
15
|
+
if (constructor) {
|
|
16
|
+
return function NodeView(props) {
|
|
17
|
+
return /*#__PURE__*/ React.createElement(CustomNodeView, {
|
|
18
|
+
constructor: constructor,
|
|
19
|
+
...props
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
} else {
|
|
23
|
+
return function NodeView(props) {
|
|
24
|
+
return /*#__PURE__*/ React.createElement(ReactNodeView, {
|
|
25
|
+
component: component,
|
|
26
|
+
...props
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}, [
|
|
31
|
+
constructor,
|
|
32
|
+
component
|
|
33
|
+
]);
|
|
34
|
+
return /*#__PURE__*/ React.createElement(Component, props);
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { cloneElement, memo, useCallback, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
|
+
import { IgnoreMutationContext } from "../contexts/IgnoreMutationContext.js";
|
|
4
|
+
import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
|
|
5
|
+
import { StopEventContext } from "../contexts/StopEventContext.js";
|
|
6
|
+
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
7
|
+
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
8
|
+
export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
9
|
+
let { component: Component, outerDeco, getPos, node, innerDeco } = param;
|
|
10
|
+
const [hasCustomSelectNode, setHasCustomSelectNode] = useState(false);
|
|
11
|
+
const [selected, setSelected] = useState(false);
|
|
12
|
+
const ref = useRef(null);
|
|
13
|
+
const innerRef = useRef(null);
|
|
14
|
+
const selectNodeRef = useRef(null);
|
|
15
|
+
const deselectNodeRef = useRef(null);
|
|
16
|
+
const stopEventRef = useRef(null);
|
|
17
|
+
const ignoreMutationRef = useRef(null);
|
|
18
|
+
const setSelectNode = useCallback((selectHandler, deselectHandler)=>{
|
|
19
|
+
selectNodeRef.current = selectHandler;
|
|
20
|
+
deselectNodeRef.current = deselectHandler;
|
|
21
|
+
setHasCustomSelectNode(true);
|
|
22
|
+
return ()=>{
|
|
23
|
+
selectNodeRef.current = null;
|
|
24
|
+
deselectNodeRef.current = null;
|
|
25
|
+
setHasCustomSelectNode(false);
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
const setStopEvent = useCallback((handler)=>{
|
|
29
|
+
stopEventRef.current = handler;
|
|
30
|
+
return ()=>{
|
|
31
|
+
stopEventRef.current = null;
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
const setIgnoreMutation = useCallback((handler)=>{
|
|
35
|
+
ignoreMutationRef.current = handler;
|
|
36
|
+
return ()=>{
|
|
37
|
+
ignoreMutationRef.current = null;
|
|
38
|
+
return ()=>{
|
|
39
|
+
ignoreMutationRef.current = null;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
const nodeProps = useMemo(()=>({
|
|
44
|
+
node: node,
|
|
45
|
+
getPos: getPos,
|
|
46
|
+
decorations: outerDeco,
|
|
47
|
+
innerDecorations: innerDeco
|
|
48
|
+
}), [
|
|
49
|
+
getPos,
|
|
50
|
+
innerDeco,
|
|
51
|
+
node,
|
|
52
|
+
outerDeco
|
|
53
|
+
]);
|
|
54
|
+
const { childContextValue, contentDOM, nodeDOM } = useNodeViewDescriptor(ref, ()=>{
|
|
55
|
+
setSelected(false);
|
|
56
|
+
return {
|
|
57
|
+
dom: innerRef.current ?? ref.current,
|
|
58
|
+
update () {
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
multiType: true,
|
|
62
|
+
selectNode () {
|
|
63
|
+
const selectNode = selectNodeRef.current;
|
|
64
|
+
if (selectNode) {
|
|
65
|
+
selectNode.call(this);
|
|
66
|
+
}
|
|
67
|
+
setSelected(true);
|
|
68
|
+
},
|
|
69
|
+
deselectNode () {
|
|
70
|
+
const deselectNode = deselectNodeRef.current;
|
|
71
|
+
if (deselectNode) {
|
|
72
|
+
deselectNode.call(this);
|
|
73
|
+
}
|
|
74
|
+
setSelected(false);
|
|
75
|
+
},
|
|
76
|
+
stopEvent (event) {
|
|
77
|
+
const stopEvent = stopEventRef.current;
|
|
78
|
+
if (stopEvent) {
|
|
79
|
+
return stopEvent.call(this, event);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
},
|
|
83
|
+
ignoreMutation (mutation) {
|
|
84
|
+
const ignoreMutation = ignoreMutationRef.current;
|
|
85
|
+
if (ignoreMutation) {
|
|
86
|
+
return ignoreMutation.call(this, mutation);
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}, nodeProps);
|
|
92
|
+
const props = {
|
|
93
|
+
nodeProps,
|
|
94
|
+
...!contentDOM && !nodeProps.node.isText && nodeDOM?.nodeName !== "BR" ? {
|
|
95
|
+
contentEditable: false,
|
|
96
|
+
suppressContentEditableWarning: true
|
|
97
|
+
} : null,
|
|
98
|
+
...!hasCustomSelectNode && selected ? {
|
|
99
|
+
className: "ProseMirror-selectednode"
|
|
100
|
+
} : null,
|
|
101
|
+
...!hasCustomSelectNode && selected || node.type.spec.draggable ? {
|
|
102
|
+
draggable: true
|
|
103
|
+
} : null,
|
|
104
|
+
ref: innerRef
|
|
105
|
+
};
|
|
106
|
+
const children = !node.isLeaf ? /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
107
|
+
getPos: getPos,
|
|
108
|
+
node: node,
|
|
109
|
+
innerDecorations: innerDeco
|
|
110
|
+
}) : null;
|
|
111
|
+
const element = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, /*#__PURE__*/ React.createElement(Component, props, children)), {
|
|
112
|
+
ref
|
|
113
|
+
});
|
|
114
|
+
return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
|
|
115
|
+
value: setSelectNode
|
|
116
|
+
}, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
|
|
117
|
+
value: setStopEvent
|
|
118
|
+
}, /*#__PURE__*/ React.createElement(IgnoreMutationContext.Provider, {
|
|
119
|
+
value: setIgnoreMutation
|
|
120
|
+
}, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
121
|
+
value: childContextValue
|
|
122
|
+
}, element))));
|
|
123
|
+
});
|
|
@@ -62,28 +62,28 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
|
62
62
|
selectNode () {
|
|
63
63
|
const selectNode = selectNodeRef.current;
|
|
64
64
|
if (selectNode) {
|
|
65
|
-
selectNode();
|
|
65
|
+
selectNode.call(this);
|
|
66
66
|
}
|
|
67
67
|
setSelected(true);
|
|
68
68
|
},
|
|
69
69
|
deselectNode () {
|
|
70
70
|
const deselectNode = deselectNodeRef.current;
|
|
71
71
|
if (deselectNode) {
|
|
72
|
-
deselectNode();
|
|
72
|
+
deselectNode.call(this);
|
|
73
73
|
}
|
|
74
74
|
setSelected(false);
|
|
75
75
|
},
|
|
76
76
|
stopEvent (event) {
|
|
77
77
|
const stopEvent = stopEventRef.current;
|
|
78
78
|
if (stopEvent) {
|
|
79
|
-
return stopEvent(event);
|
|
79
|
+
return stopEvent.call(this, event);
|
|
80
80
|
}
|
|
81
81
|
return false;
|
|
82
82
|
},
|
|
83
83
|
ignoreMutation (mutation) {
|
|
84
84
|
const ignoreMutation = ignoreMutationRef.current;
|
|
85
85
|
if (ignoreMutation) {
|
|
86
|
-
return ignoreMutation(mutation);
|
|
86
|
+
return ignoreMutation.call(this, mutation);
|
|
87
87
|
}
|
|
88
88
|
return false;
|
|
89
89
|
}
|
|
@@ -101,7 +101,11 @@ let didWarnValueDefaultValue = false;
|
|
|
101
101
|
// running effects. Running effects will reattach selection
|
|
102
102
|
// change listeners if the EditorView has been destroyed.
|
|
103
103
|
if (view instanceof ReactEditorView && !view.isDestroyed) {
|
|
104
|
+
// Plugins might dispatch transactions from their
|
|
105
|
+
// view update lifecycle hooks
|
|
106
|
+
flushSyncRef.current = false;
|
|
104
107
|
view.commitPendingEffects();
|
|
108
|
+
flushSyncRef.current = true;
|
|
105
109
|
}
|
|
106
110
|
});
|
|
107
111
|
view.update(directEditorProps);
|
|
@@ -15,6 +15,10 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
|
|
|
15
15
|
* synchronously after all DOM mutations, but they do so
|
|
16
16
|
* _after_ the EditorView has been updated, even when the
|
|
17
17
|
* EditorView lives in an ancestor component.
|
|
18
|
+
*
|
|
19
|
+
* This hook can only be used in a component that is mounted
|
|
20
|
+
* as a child of the TiptapEditorView component, including
|
|
21
|
+
* React node view components.
|
|
18
22
|
*/ export function useEditorEffect(effect, dependencies) {
|
|
19
23
|
const { view, flushSyncRef } = useContext(EditorContext);
|
|
20
24
|
// The rules of hooks want `effect` to be included in the
|
|
@@ -15,11 +15,9 @@ function assertIsReactEditorView(view) {
|
|
|
15
15
|
* The callback will be called with the EditorView instance
|
|
16
16
|
* as its first argument.
|
|
17
17
|
*
|
|
18
|
-
* This hook
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* component that is mounted as a child of both of these
|
|
22
|
-
* providers.
|
|
18
|
+
* This hook can only be used in a component that is mounted
|
|
19
|
+
* as a child of the TiptapEditorView component, including
|
|
20
|
+
* React node view components.
|
|
23
21
|
*/ export function useEditorEventCallback(callback) {
|
|
24
22
|
const ref = useRef(callback);
|
|
25
23
|
const { view } = useContext(EditorContext);
|
|
@@ -33,7 +31,7 @@ function assertIsReactEditorView(view) {
|
|
|
33
31
|
args[_key] = arguments[_key];
|
|
34
32
|
}
|
|
35
33
|
assertIsReactEditorView(view);
|
|
36
|
-
return ref.current(view, ...args);
|
|
34
|
+
return ref.current.call(this, view, ...args);
|
|
37
35
|
}, [
|
|
38
36
|
view
|
|
39
37
|
]);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NodeView } from "@tiptap/core";
|
|
2
|
+
/**
|
|
3
|
+
* Subclass of Tiptap's NodeView to be used in tiptapNodeView.
|
|
4
|
+
*
|
|
5
|
+
* Allows us to pass in an existing dom and contentODM from React ProseMirror's
|
|
6
|
+
* ViewDesc, so that we can call Tiptap's default stopEvent and ignoreMutation
|
|
7
|
+
* methods
|
|
8
|
+
*/ export class ReactProseMirrorNodeView extends NodeView {
|
|
9
|
+
_dom;
|
|
10
|
+
_contentDOM;
|
|
11
|
+
constructor(component, props, dom, contentDOM, options){
|
|
12
|
+
super(component, props, options);
|
|
13
|
+
this._dom = dom;
|
|
14
|
+
this._contentDOM = contentDOM;
|
|
15
|
+
}
|
|
16
|
+
get dom() {
|
|
17
|
+
return this._dom;
|
|
18
|
+
}
|
|
19
|
+
get contentDOM() {
|
|
20
|
+
return this._contentDOM;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Editor } from "@tiptap/core";
|
|
2
|
+
import { EditorState } from "prosemirror-state";
|
|
3
|
+
import { StaticEditorView } from "../StaticEditorView.js";
|
|
4
|
+
export class TiptapEditor extends Editor {
|
|
5
|
+
constructor(options = {}){
|
|
6
|
+
super({
|
|
7
|
+
...options,
|
|
8
|
+
element: null
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
get view() {
|
|
12
|
+
return(// @ts-expect-error private property
|
|
13
|
+
this.editorView ?? new StaticEditorView({
|
|
14
|
+
state: EditorState.create({
|
|
15
|
+
schema: this.extensionManager.schema
|
|
16
|
+
}),
|
|
17
|
+
...this.options.editorProps,
|
|
18
|
+
attributes: {
|
|
19
|
+
role: "textbox",
|
|
20
|
+
...this.options.editorProps.attributes
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React, { useContext, useSyncExternalStore } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { ProseMirrorDoc } from "../components/ProseMirrorDoc.js";
|
|
4
|
+
import { useEditorEffect } from "../hooks/useEditorEffect.js";
|
|
5
|
+
import { TiptapEditorContext } from "./contexts/TiptapEditorContext.js";
|
|
6
|
+
/**
|
|
7
|
+
* This component renders all of the editor's registered "React renderers".
|
|
8
|
+
*/ const Portals = (param)=>{
|
|
9
|
+
let { contentComponent } = param;
|
|
10
|
+
const renderers = useSyncExternalStore(contentComponent.subscribe, contentComponent.getSnapshot, contentComponent.getServerSnapshot);
|
|
11
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, Object.values(renderers));
|
|
12
|
+
};
|
|
13
|
+
function getInstance() {
|
|
14
|
+
const subscribers = new Set();
|
|
15
|
+
let renderers = {};
|
|
16
|
+
return {
|
|
17
|
+
/**
|
|
18
|
+
* Subscribe to the editor instance's changes.
|
|
19
|
+
*/ subscribe (callback) {
|
|
20
|
+
subscribers.add(callback);
|
|
21
|
+
return ()=>{
|
|
22
|
+
subscribers.delete(callback);
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
getSnapshot () {
|
|
26
|
+
return renderers;
|
|
27
|
+
},
|
|
28
|
+
getServerSnapshot () {
|
|
29
|
+
return renderers;
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Adds a new React Renderer to the editor.
|
|
33
|
+
*/ setRenderer (id, renderer) {
|
|
34
|
+
renderers = {
|
|
35
|
+
...renderers,
|
|
36
|
+
[id]: /*#__PURE__*/ createPortal(renderer.reactElement, renderer.element, id)
|
|
37
|
+
};
|
|
38
|
+
subscribers.forEach((subscriber)=>subscriber());
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Removes a React Renderer from the editor.
|
|
42
|
+
*/ removeRenderer (id) {
|
|
43
|
+
const nextRenderers = {
|
|
44
|
+
...renderers
|
|
45
|
+
};
|
|
46
|
+
delete nextRenderers[id];
|
|
47
|
+
renderers = nextRenderers;
|
|
48
|
+
subscribers.forEach((subscriber)=>subscriber());
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function TiptapEditorContent(param) {
|
|
53
|
+
let { editor: editorProp, ...props } = param;
|
|
54
|
+
const editor = editorProp;
|
|
55
|
+
const { onEditorInitialize, onEditorDeinitialize } = useContext(TiptapEditorContext);
|
|
56
|
+
useEditorEffect((view)=>{
|
|
57
|
+
if (editor.view === view) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// @ts-expect-error private property
|
|
61
|
+
editor.editorView = view;
|
|
62
|
+
editor.contentComponent = getInstance();
|
|
63
|
+
// @ts-expect-error private method
|
|
64
|
+
editor.injectCSS();
|
|
65
|
+
const dom = view.dom;
|
|
66
|
+
dom.editor = editor;
|
|
67
|
+
setTimeout(()=>{
|
|
68
|
+
if (editor.isDestroyed) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
editor.commands.focus(editor.options.autofocus);
|
|
72
|
+
editor.emit("create", {
|
|
73
|
+
editor
|
|
74
|
+
});
|
|
75
|
+
editor.isInitialized = true;
|
|
76
|
+
onEditorInitialize();
|
|
77
|
+
});
|
|
78
|
+
return ()=>{
|
|
79
|
+
editor.isInitialized = false;
|
|
80
|
+
editor.contentComponent = null;
|
|
81
|
+
onEditorDeinitialize();
|
|
82
|
+
};
|
|
83
|
+
}, [
|
|
84
|
+
editor,
|
|
85
|
+
onEditorDeinitialize,
|
|
86
|
+
onEditorInitialize
|
|
87
|
+
]);
|
|
88
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(ProseMirrorDoc, props), editor?.contentComponent && /*#__PURE__*/ React.createElement(Portals, {
|
|
89
|
+
contentComponent: editor.contentComponent
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EditorContext } from "@tiptap/react";
|
|
2
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { ProseMirror } from "../components/ProseMirror.js";
|
|
4
|
+
import { useForceUpdate } from "../hooks/useForceUpdate.js";
|
|
5
|
+
import { TiptapEditorContext } from "./contexts/TiptapEditorContext.js";
|
|
6
|
+
/**
|
|
7
|
+
* Render a Tiptap-compatible React ProseMirror editor.
|
|
8
|
+
*/ export function TiptapEditorView(param) {
|
|
9
|
+
let { editor, nodeViews, markViews, children, static: isStatic = false } = param;
|
|
10
|
+
const [isEditorInitialized, setIsEditorInitialized] = useState(editor.isInitialized);
|
|
11
|
+
const forceUpdate = useForceUpdate();
|
|
12
|
+
const dispatchTransaction = useCallback((tr)=>{
|
|
13
|
+
// @ts-expect-error calling private method
|
|
14
|
+
editor.dispatchTransaction(tr);
|
|
15
|
+
// Tiptap's dispatchTransaction doesn't trigger
|
|
16
|
+
// a re-render, so we need to manually force
|
|
17
|
+
// one to ensure that React stays in sync.
|
|
18
|
+
forceUpdate();
|
|
19
|
+
}, [
|
|
20
|
+
editor,
|
|
21
|
+
forceUpdate
|
|
22
|
+
]);
|
|
23
|
+
const initialEditorProps = {
|
|
24
|
+
...editor.options.editorProps,
|
|
25
|
+
attributes: {
|
|
26
|
+
role: "textbox",
|
|
27
|
+
...editor.options.editorProps?.attributes
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const { nodeViews: customNodeViews, markViews: customMarkViews } = editor.isDestroyed ? {
|
|
31
|
+
nodeViews: undefined,
|
|
32
|
+
markViews: undefined
|
|
33
|
+
} : editor.view.props;
|
|
34
|
+
const contextValue = useMemo(()=>({
|
|
35
|
+
editor
|
|
36
|
+
}), [
|
|
37
|
+
editor
|
|
38
|
+
]);
|
|
39
|
+
const onEditorInitialize = useCallback(()=>{
|
|
40
|
+
setIsEditorInitialized(true);
|
|
41
|
+
}, []);
|
|
42
|
+
const onEditorDeinitialize = useCallback(()=>{
|
|
43
|
+
setIsEditorInitialized(false);
|
|
44
|
+
}, []);
|
|
45
|
+
const tiptapEditorContextValue = useMemo(()=>({
|
|
46
|
+
isEditorInitialized,
|
|
47
|
+
onEditorInitialize,
|
|
48
|
+
onEditorDeinitialize
|
|
49
|
+
}), [
|
|
50
|
+
isEditorInitialized,
|
|
51
|
+
onEditorDeinitialize,
|
|
52
|
+
onEditorInitialize
|
|
53
|
+
]);
|
|
54
|
+
return /*#__PURE__*/ React.createElement(ProseMirror, {
|
|
55
|
+
static: isStatic,
|
|
56
|
+
className: "tiptap",
|
|
57
|
+
...initialEditorProps,
|
|
58
|
+
markViews: markViews,
|
|
59
|
+
customMarkViews: customMarkViews,
|
|
60
|
+
nodeViews: nodeViews,
|
|
61
|
+
customNodeViews: customNodeViews,
|
|
62
|
+
state: editor.state,
|
|
63
|
+
dispatchTransaction: dispatchTransaction
|
|
64
|
+
}, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
65
|
+
value: contextValue
|
|
66
|
+
}, /*#__PURE__*/ React.createElement(TiptapEditorContext.Provider, {
|
|
67
|
+
value: tiptapEditorContextValue
|
|
68
|
+
}, children)));
|
|
69
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NodeView } from "@tiptap/core";
|
|
2
|
+
/**
|
|
3
|
+
* Subclass of Tiptap's NodeView to be used in tiptapNodeView.
|
|
4
|
+
*
|
|
5
|
+
* Allows us to pass in an existing dom and contentODM from React ProseMirror's
|
|
6
|
+
* ViewDesc, so that we can call Tiptap's default stopEvent and ignoreMutation
|
|
7
|
+
* methods
|
|
8
|
+
*/ export class ReactProseMirrorNodeView extends NodeView {
|
|
9
|
+
_dom;
|
|
10
|
+
_contentDOM;
|
|
11
|
+
constructor(component, props, dom, contentDOM, options){
|
|
12
|
+
super(component, props, options);
|
|
13
|
+
this._dom = dom;
|
|
14
|
+
this._contentDOM = contentDOM;
|
|
15
|
+
}
|
|
16
|
+
get dom() {
|
|
17
|
+
return this._dom;
|
|
18
|
+
}
|
|
19
|
+
get contentDOM() {
|
|
20
|
+
return this._contentDOM;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { reorderSiblingsOnTransaction } from "../../commands/reorderSiblings.js";
|
|
3
|
+
import { reactKeys } from "../../plugins/reactKeys.js";
|
|
4
|
+
export const ReactProseMirror = Extension.create({
|
|
5
|
+
name: "@handlewithcare/react-prosemirror/reactKeys",
|
|
6
|
+
addProseMirrorPlugins () {
|
|
7
|
+
return [
|
|
8
|
+
reactKeys()
|
|
9
|
+
];
|
|
10
|
+
},
|
|
11
|
+
addCommands () {
|
|
12
|
+
return {
|
|
13
|
+
/**
|
|
14
|
+
* Command that reorders the adjacent nodes starting
|
|
15
|
+
* at the provided position.
|
|
16
|
+
*
|
|
17
|
+
* @param pos - The `start` position of the parent of the nodes being reordered
|
|
18
|
+
* @param order - The new order for the nodes, expressed as an array of indices. For
|
|
19
|
+
* example, to swap the first two nodes in a set of three, `order`
|
|
20
|
+
* would be set to `[1, 0, 2]`. To move the first node to the end,
|
|
21
|
+
* and keep the other two in relative order, set `order` to `[1, 2, 0]`.
|
|
22
|
+
*/ reorderSiblings (initialPos, order) {
|
|
23
|
+
return function reorderSiblingsCommand(param) {
|
|
24
|
+
let { tr, state, dispatch } = param;
|
|
25
|
+
return reorderSiblingsOnTransaction(initialPos, order, tr, state, dispatch);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEditor } from "@tiptap/react";
|
|
2
|
+
import { StaticEditorView } from "../../StaticEditorView.js";
|
|
3
|
+
export function useTiptapEditor(options, deps) {
|
|
4
|
+
const editor = useEditor({
|
|
5
|
+
...options,
|
|
6
|
+
element: null
|
|
7
|
+
}, deps);
|
|
8
|
+
// @ts-expect-error private property
|
|
9
|
+
editor.editorView ??= new StaticEditorView({
|
|
10
|
+
// @ts-expect-error private property
|
|
11
|
+
state: editor.editorState,
|
|
12
|
+
...editor.options.editorProps,
|
|
13
|
+
attributes: {
|
|
14
|
+
role: "textbox",
|
|
15
|
+
...editor.options.editorProps.attributes
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
// @ts-expect-error private property
|
|
19
|
+
const stateHasPlugins = !!editor.editorState.plugins.length;
|
|
20
|
+
const stateNeedsReconfigure = !stateHasPlugins && !editor.isDestroyed;
|
|
21
|
+
if (stateNeedsReconfigure) {
|
|
22
|
+
const managerPlugins = editor.extensionManager.plugins;
|
|
23
|
+
if (managerPlugins.length) {
|
|
24
|
+
// @ts-expect-error private property
|
|
25
|
+
editor.editorState = editor.editorState.reconfigure({
|
|
26
|
+
plugins: editor.extensionManager.plugins
|
|
27
|
+
});
|
|
28
|
+
// @ts-expect-error private property
|
|
29
|
+
editor.editorView.updateState(editor.editorState);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return editor;
|
|
33
|
+
}
|