@handlewithcare/react-prosemirror 2.4.12 → 2.5.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/cjs/AbstractEditorView.js +4 -0
- package/dist/cjs/ReactEditorView.js +156 -0
- package/dist/cjs/StaticEditorView.js +86 -0
- package/dist/cjs/components/ChildNodeViews.js +59 -30
- package/dist/cjs/components/CustomNodeView.js +9 -25
- package/dist/cjs/components/DocNodeView.js +6 -15
- package/dist/cjs/components/MarkView.js +1 -2
- package/dist/cjs/components/NativeWidgetView.js +2 -3
- package/dist/cjs/components/NodeView.js +1 -1
- package/dist/cjs/components/ProseMirror.js +11 -14
- package/dist/cjs/components/ReactNodeView.js +3 -4
- package/dist/cjs/components/SeparatorHackView.js +1 -2
- package/dist/cjs/components/TextNodeView.js +4 -5
- package/dist/cjs/components/TrailingHackView.js +1 -2
- package/dist/cjs/components/WidgetView.js +2 -4
- package/dist/cjs/constants.js +33 -0
- package/dist/cjs/hooks/useEditor.js +32 -228
- package/dist/cjs/hooks/useEditorEffect.js +2 -2
- package/dist/cjs/hooks/useEditorEventCallback.js +8 -5
- package/dist/cjs/hooks/useNodeViewDescriptor.js +10 -10
- package/dist/cjs/hooks/useReactKeys.js +1 -1
- package/dist/cjs/testing/editorViewTestHelpers.js +0 -2
- package/dist/cjs/viewdesc.js +10 -9
- package/dist/esm/AbstractEditorView.js +1 -0
- package/dist/esm/ReactEditorView.js +156 -0
- package/dist/esm/StaticEditorView.js +76 -0
- package/dist/esm/components/ChildNodeViews.js +60 -32
- package/dist/esm/components/CustomNodeView.js +9 -25
- package/dist/esm/components/DocNodeView.js +6 -15
- package/dist/esm/components/MarkView.js +1 -2
- package/dist/esm/components/NativeWidgetView.js +2 -3
- package/dist/esm/components/NodeView.js +1 -1
- package/dist/esm/components/ProseMirror.js +11 -14
- package/dist/esm/components/ReactNodeView.js +3 -4
- package/dist/esm/components/SeparatorHackView.js +1 -2
- package/dist/esm/components/TextNodeView.js +4 -5
- package/dist/esm/components/TrailingHackView.js +1 -2
- package/dist/esm/components/WidgetView.js +2 -4
- package/dist/esm/constants.js +15 -0
- package/dist/esm/hooks/useEditor.js +28 -217
- package/dist/esm/hooks/useEditorEffect.js +2 -2
- package/dist/esm/hooks/useEditorEventCallback.js +8 -5
- package/dist/esm/hooks/useNodeViewDescriptor.js +10 -10
- package/dist/esm/hooks/useReactKeys.js +1 -1
- package/dist/esm/testing/editorViewTestHelpers.js +0 -2
- package/dist/esm/viewdesc.js +3 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/AbstractEditorView.d.ts +27 -0
- package/dist/types/ReactEditorView.d.ts +79 -0
- package/dist/types/StaticEditorView.d.ts +24 -0
- package/dist/types/components/ChildNodeViews.d.ts +2 -2
- package/dist/types/components/CustomNodeView.d.ts +2 -2
- package/dist/types/components/DocNodeView.d.ts +2 -5
- package/dist/types/components/MarkView.d.ts +2 -2
- package/dist/types/components/NativeWidgetView.d.ts +2 -2
- package/dist/types/components/NodeView.d.ts +2 -2
- package/dist/types/components/ReactNodeView.d.ts +2 -2
- package/dist/types/components/SeparatorHackView.d.ts +2 -2
- package/dist/types/components/TextNodeView.d.ts +4 -3
- package/dist/types/components/TrailingHackView.d.ts +2 -2
- package/dist/types/components/WidgetView.d.ts +2 -2
- package/dist/types/constants.d.ts +4 -0
- package/dist/types/contexts/EditorContext.d.ts +6 -4
- package/dist/types/decorations/computeDocDeco.d.ts +3 -2
- package/dist/types/decorations/viewDecorations.d.ts +3 -2
- package/dist/types/hooks/useEditor.d.ts +5 -46
- package/dist/types/hooks/useNodeViewDescriptor.d.ts +1 -1
- package/dist/types/hooks/useReactKeys.d.ts +1 -1
- package/dist/types/props.d.ts +3 -3
- package/dist/types/viewdesc.d.ts +6 -5
- package/package.json +6 -2
- package/dist/cjs/components/Editor.js +0 -28
- package/dist/cjs/components/NodeViews.js +0 -73
- package/dist/cjs/components/__tests__/LayoutGroup.test.js +0 -141
- package/dist/cjs/components/__tests__/ProseMirror.test.js +0 -255
- package/dist/cjs/contexts/NodeViewsContext.js +0 -10
- package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -107
- package/dist/cjs/hooks/__tests__/useNodeViews.test.js +0 -159
- package/dist/cjs/hooks/useEditorView.js +0 -100
- package/dist/cjs/hooks/useNodePos.js +0 -69
- package/dist/cjs/hooks/useNodeViews.js +0 -100
- package/dist/cjs/nodeViews/createReactNodeViewConstructor.js +0 -244
- package/dist/cjs/nodeViews/phrasingContentTags.js +0 -57
- package/dist/cjs/plugins/__tests__/react.test.js +0 -139
- package/dist/cjs/plugins/react.js +0 -71
- package/dist/cjs/selection/SelectionDOMObserver.js +0 -171
- package/dist/cjs/selection/hasFocusAndSelection.js +0 -35
- package/dist/cjs/selection/selectionFromDOM.js +0 -77
- package/dist/cjs/selection/selectionToDOM.js +0 -226
- package/dist/cjs/ssr.js +0 -85
- package/dist/esm/components/Editor.js +0 -15
- package/dist/esm/components/NodeViews.js +0 -26
- package/dist/esm/components/__tests__/LayoutGroup.test.js +0 -98
- package/dist/esm/components/__tests__/ProseMirror.test.js +0 -207
- package/dist/esm/contexts/NodeViewsContext.js +0 -9
- package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -98
- package/dist/esm/hooks/__tests__/useNodeViews.test.js +0 -116
- package/dist/esm/hooks/useEditorView.js +0 -99
- package/dist/esm/hooks/useNodePos.js +0 -16
- package/dist/esm/hooks/useNodeViews.js +0 -53
- package/dist/esm/nodeViews/createReactNodeViewConstructor.js +0 -214
- package/dist/esm/nodeViews/phrasingContentTags.js +0 -49
- package/dist/esm/plugins/__tests__/react.test.js +0 -135
- package/dist/esm/plugins/react.js +0 -64
- package/dist/esm/selection/SelectionDOMObserver.js +0 -161
- package/dist/esm/selection/hasFocusAndSelection.js +0 -17
- package/dist/esm/selection/selectionFromDOM.js +0 -59
- package/dist/esm/selection/selectionToDOM.js +0 -196
- package/dist/esm/ssr.js +0 -82
- package/dist/types/components/Editor.d.ts +0 -7
- package/dist/types/components/NodeViews.d.ts +0 -6
- package/dist/types/components/__tests__/LayoutGroup.test.d.ts +0 -1
- package/dist/types/contexts/NodeViewsContext.d.ts +0 -19
- package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +0 -1
- package/dist/types/hooks/__tests__/useNodeViews.test.d.ts +0 -1
- package/dist/types/hooks/useEditorView.d.ts +0 -23
- package/dist/types/hooks/useNodePos.d.ts +0 -9
- package/dist/types/hooks/useNodeViews.d.ts +0 -5
- package/dist/types/nodeViews/createReactNodeViewConstructor.d.ts +0 -48
- package/dist/types/nodeViews/phrasingContentTags.d.ts +0 -1
- package/dist/types/plugins/__tests__/react.test.d.ts +0 -1
- package/dist/types/plugins/react.d.ts +0 -21
- package/dist/types/selection/SelectionDOMObserver.d.ts +0 -33
- package/dist/types/selection/hasFocusAndSelection.d.ts +0 -3
- package/dist/types/selection/selectionFromDOM.d.ts +0 -4
- package/dist/types/selection/selectionToDOM.d.ts +0 -9
- package/dist/types/ssr.d.ts +0 -19
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-empty-function */ import { render } from "@testing-library/react";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { LayoutGroup } from "../../components/LayoutGroup.js";
|
|
4
|
-
import { EditorContext } from "../../contexts/EditorContext.js";
|
|
5
|
-
import { useEditorEffect } from "../useEditorEffect.js";
|
|
6
|
-
function TestComponent(param) {
|
|
7
|
-
let { effect , dependencies =[] } = param;
|
|
8
|
-
useEditorEffect(effect, [
|
|
9
|
-
effect,
|
|
10
|
-
...dependencies
|
|
11
|
-
]);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
describe("useEditorViewLayoutEffect", ()=>{
|
|
15
|
-
it("should run the effect", ()=>{
|
|
16
|
-
const effect = jest.fn();
|
|
17
|
-
const editorView = {};
|
|
18
|
-
const editorState = {};
|
|
19
|
-
const registerEventListener = ()=>{};
|
|
20
|
-
const unregisterEventListener = ()=>{};
|
|
21
|
-
render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
22
|
-
value: {
|
|
23
|
-
editorView,
|
|
24
|
-
editorState,
|
|
25
|
-
registerEventListener,
|
|
26
|
-
unregisterEventListener
|
|
27
|
-
}
|
|
28
|
-
}, /*#__PURE__*/ React.createElement(TestComponent, {
|
|
29
|
-
effect: effect
|
|
30
|
-
}))));
|
|
31
|
-
expect(effect).toHaveBeenCalled();
|
|
32
|
-
expect(effect).toHaveBeenCalledWith(editorView);
|
|
33
|
-
});
|
|
34
|
-
it("should not re-run the effect if no dependencies change", ()=>{
|
|
35
|
-
const effect = jest.fn();
|
|
36
|
-
const editorView = {};
|
|
37
|
-
const editorState = {};
|
|
38
|
-
const registerEventListener = ()=>{};
|
|
39
|
-
const unregisterEventListener = ()=>{};
|
|
40
|
-
const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
41
|
-
value: {
|
|
42
|
-
editorView,
|
|
43
|
-
editorState,
|
|
44
|
-
registerEventListener,
|
|
45
|
-
unregisterEventListener
|
|
46
|
-
}
|
|
47
|
-
}, /*#__PURE__*/ React.createElement(TestComponent, {
|
|
48
|
-
effect: effect,
|
|
49
|
-
dependencies: []
|
|
50
|
-
}))));
|
|
51
|
-
rerender(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
52
|
-
value: {
|
|
53
|
-
editorView,
|
|
54
|
-
editorState,
|
|
55
|
-
registerEventListener,
|
|
56
|
-
unregisterEventListener
|
|
57
|
-
}
|
|
58
|
-
}, /*#__PURE__*/ React.createElement(TestComponent, {
|
|
59
|
-
effect: effect,
|
|
60
|
-
dependencies: []
|
|
61
|
-
}))));
|
|
62
|
-
expect(effect).toHaveBeenCalledTimes(1);
|
|
63
|
-
});
|
|
64
|
-
it("should re-run the effect if dependencies change", ()=>{
|
|
65
|
-
const effect = jest.fn();
|
|
66
|
-
const editorView = {};
|
|
67
|
-
const editorState = {};
|
|
68
|
-
const registerEventListener = ()=>{};
|
|
69
|
-
const unregisterEventListener = ()=>{};
|
|
70
|
-
const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
71
|
-
value: {
|
|
72
|
-
editorView,
|
|
73
|
-
editorState,
|
|
74
|
-
registerEventListener,
|
|
75
|
-
unregisterEventListener
|
|
76
|
-
}
|
|
77
|
-
}, /*#__PURE__*/ React.createElement(TestComponent, {
|
|
78
|
-
effect: effect,
|
|
79
|
-
dependencies: [
|
|
80
|
-
"one"
|
|
81
|
-
]
|
|
82
|
-
}))));
|
|
83
|
-
rerender(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
84
|
-
value: {
|
|
85
|
-
editorView,
|
|
86
|
-
editorState,
|
|
87
|
-
registerEventListener,
|
|
88
|
-
unregisterEventListener
|
|
89
|
-
}
|
|
90
|
-
}, /*#__PURE__*/ React.createElement(TestComponent, {
|
|
91
|
-
effect: effect,
|
|
92
|
-
dependencies: [
|
|
93
|
-
"two"
|
|
94
|
-
]
|
|
95
|
-
}))));
|
|
96
|
-
expect(effect).toHaveBeenCalledTimes(2);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { act, render, screen } from "@testing-library/react";
|
|
2
|
-
import { Schema } from "prosemirror-model";
|
|
3
|
-
import { EditorState } from "prosemirror-state";
|
|
4
|
-
import React, { createContext, useContext, useState } from "react";
|
|
5
|
-
import { ProseMirror } from "../../components/ProseMirror.js";
|
|
6
|
-
import { react } from "../../plugins/react.js";
|
|
7
|
-
// Mock `ReactDOM.flushSync` to call `act` to flush updates from DOM mutations.
|
|
8
|
-
jest.mock("react-dom", ()=>({
|
|
9
|
-
...jest.requireActual("react-dom"),
|
|
10
|
-
flushSync: (fn)=>act(fn)
|
|
11
|
-
}));
|
|
12
|
-
const schema = new Schema({
|
|
13
|
-
nodes: {
|
|
14
|
-
doc: {
|
|
15
|
-
content: "block+"
|
|
16
|
-
},
|
|
17
|
-
list: {
|
|
18
|
-
group: "block",
|
|
19
|
-
content: "list_item+"
|
|
20
|
-
},
|
|
21
|
-
list_item: {
|
|
22
|
-
content: "inline*"
|
|
23
|
-
},
|
|
24
|
-
text: {
|
|
25
|
-
group: "inline"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
const state = EditorState.create({
|
|
30
|
-
doc: schema.topNodeType.create(null, schema.nodes.list.createAndFill()),
|
|
31
|
-
schema,
|
|
32
|
-
plugins: [
|
|
33
|
-
react()
|
|
34
|
-
]
|
|
35
|
-
});
|
|
36
|
-
describe("useNodeViews", ()=>{
|
|
37
|
-
it("should render node views", ()=>{
|
|
38
|
-
function List(param) {
|
|
39
|
-
let { children } = param;
|
|
40
|
-
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
|
|
41
|
-
contentEditable: false
|
|
42
|
-
}, "list"), /*#__PURE__*/ React.createElement("ul", null, children));
|
|
43
|
-
}
|
|
44
|
-
function ListItem(param) {
|
|
45
|
-
let { children } = param;
|
|
46
|
-
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
|
|
47
|
-
contentEditable: false
|
|
48
|
-
}, "list item"), /*#__PURE__*/ React.createElement("li", null, children));
|
|
49
|
-
}
|
|
50
|
-
const nodeViews = {
|
|
51
|
-
list: ()=>({
|
|
52
|
-
component: List,
|
|
53
|
-
dom: document.createElement("div"),
|
|
54
|
-
contentDOM: document.createElement("div")
|
|
55
|
-
}),
|
|
56
|
-
list_item: ()=>({
|
|
57
|
-
component: ListItem,
|
|
58
|
-
dom: document.createElement("div"),
|
|
59
|
-
contentDOM: document.createElement("div")
|
|
60
|
-
})
|
|
61
|
-
};
|
|
62
|
-
function TestEditor() {
|
|
63
|
-
const [mount, setMount] = useState(null);
|
|
64
|
-
return /*#__PURE__*/ React.createElement(ProseMirror, {
|
|
65
|
-
mount: mount,
|
|
66
|
-
nodeViews: nodeViews,
|
|
67
|
-
defaultState: state
|
|
68
|
-
}, /*#__PURE__*/ React.createElement("div", {
|
|
69
|
-
ref: setMount
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
render(/*#__PURE__*/ React.createElement(TestEditor, null));
|
|
73
|
-
expect(screen.getByText("list")).toBeTruthy();
|
|
74
|
-
expect(screen.getByText("list item")).toBeTruthy();
|
|
75
|
-
});
|
|
76
|
-
it("should render child node views as children of their parents", ()=>{
|
|
77
|
-
const TestContext = /*#__PURE__*/ createContext("default");
|
|
78
|
-
function List(param) {
|
|
79
|
-
let { children } = param;
|
|
80
|
-
return /*#__PURE__*/ React.createElement(TestContext.Provider, {
|
|
81
|
-
value: "overriden"
|
|
82
|
-
}, /*#__PURE__*/ React.createElement("ul", null, children));
|
|
83
|
-
}
|
|
84
|
-
function ListItem(param) {
|
|
85
|
-
let { children } = param;
|
|
86
|
-
const testContextValue = useContext(TestContext);
|
|
87
|
-
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
|
|
88
|
-
contentEditable: false
|
|
89
|
-
}, testContextValue), /*#__PURE__*/ React.createElement("li", null, children));
|
|
90
|
-
}
|
|
91
|
-
const nodeViews = {
|
|
92
|
-
list: ()=>({
|
|
93
|
-
component: List,
|
|
94
|
-
dom: document.createElement("div"),
|
|
95
|
-
contentDOM: document.createElement("div")
|
|
96
|
-
}),
|
|
97
|
-
list_item: ()=>({
|
|
98
|
-
component: ListItem,
|
|
99
|
-
dom: document.createElement("div"),
|
|
100
|
-
contentDOM: document.createElement("div")
|
|
101
|
-
})
|
|
102
|
-
};
|
|
103
|
-
function TestEditor() {
|
|
104
|
-
const [mount, setMount] = useState(null);
|
|
105
|
-
return /*#__PURE__*/ React.createElement(ProseMirror, {
|
|
106
|
-
mount: mount,
|
|
107
|
-
nodeViews: nodeViews,
|
|
108
|
-
defaultState: state
|
|
109
|
-
}, /*#__PURE__*/ React.createElement("div", {
|
|
110
|
-
ref: setMount
|
|
111
|
-
}));
|
|
112
|
-
}
|
|
113
|
-
render(/*#__PURE__*/ React.createElement(TestEditor, null));
|
|
114
|
-
expect(screen.getByText("overriden")).toBeTruthy();
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { Schema } from "prosemirror-model";
|
|
2
|
-
import { EditorState } from "prosemirror-state";
|
|
3
|
-
import { EditorView } from "prosemirror-view";
|
|
4
|
-
import { useLayoutEffect, useMemo, useState } from "react";
|
|
5
|
-
import { flushSync } from "react-dom";
|
|
6
|
-
import { useComponentEventListeners } from "./useComponentEventListeners.js";
|
|
7
|
-
const EMPTY_SCHEMA = new Schema({
|
|
8
|
-
nodes: {
|
|
9
|
-
doc: {
|
|
10
|
-
content: "text*"
|
|
11
|
-
},
|
|
12
|
-
text: {
|
|
13
|
-
inline: true
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
const EMPTY_STATE = EditorState.create({
|
|
18
|
-
schema: EMPTY_SCHEMA
|
|
19
|
-
});
|
|
20
|
-
let didWarnValueDefaultValue = false;
|
|
21
|
-
/**
|
|
22
|
-
* Creates, mounts, and manages a ProseMirror `EditorView`.
|
|
23
|
-
*
|
|
24
|
-
* All state and props updates are executed in a layout effect.
|
|
25
|
-
* To ensure that the EditorState and EditorView are never out of
|
|
26
|
-
* sync, it's important that the EditorView produced by this hook
|
|
27
|
-
* is only accessed through the provided hooks.
|
|
28
|
-
*/ export function useEditorView(mount, options) {
|
|
29
|
-
if (process.env.NODE_ENV !== "production") {
|
|
30
|
-
if (options.defaultState !== undefined && options.state !== undefined && !didWarnValueDefaultValue) {
|
|
31
|
-
console.error("A component contains a ProseMirror editor with both value and defaultValue props. " + "ProseMirror editors must be either controlled or uncontrolled " + "(specify either the state prop, or the defaultState prop, but not both). " + "Decide between using a controlled or uncontrolled ProseMirror editor " + "and remove one of these props. More info: " + "https://reactjs.org/link/controlled-components");
|
|
32
|
-
didWarnValueDefaultValue = true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const defaultState = options.defaultState ?? EMPTY_STATE;
|
|
36
|
-
const [_state, setState] = useState(defaultState);
|
|
37
|
-
const state = options.state ?? _state;
|
|
38
|
-
const { componentEventListenersPlugin , registerEventListener , unregisterEventListener } = useComponentEventListeners();
|
|
39
|
-
const plugins = useMemo(()=>[
|
|
40
|
-
...options.plugins ?? [],
|
|
41
|
-
componentEventListenersPlugin
|
|
42
|
-
], [
|
|
43
|
-
options.plugins,
|
|
44
|
-
componentEventListenersPlugin
|
|
45
|
-
]);
|
|
46
|
-
function dispatchTransaction(tr) {
|
|
47
|
-
flushSync(()=>{
|
|
48
|
-
if (!options.state) {
|
|
49
|
-
setState((s)=>s.apply(tr));
|
|
50
|
-
}
|
|
51
|
-
if (options.dispatchTransaction) {
|
|
52
|
-
options.dispatchTransaction.call(this, tr);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
const directEditorProps = {
|
|
57
|
-
...options,
|
|
58
|
-
state,
|
|
59
|
-
plugins,
|
|
60
|
-
dispatchTransaction
|
|
61
|
-
};
|
|
62
|
-
const [_view, setView] = useState(null);
|
|
63
|
-
const view = options.view ?? _view;
|
|
64
|
-
useLayoutEffect(()=>{
|
|
65
|
-
return ()=>{
|
|
66
|
-
if (_view) {
|
|
67
|
-
_view.destroy();
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}, [
|
|
71
|
-
_view
|
|
72
|
-
]);
|
|
73
|
-
// This effect runs on every render and handles the view lifecycle.
|
|
74
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
|
-
useLayoutEffect(()=>{
|
|
76
|
-
if (_view) {
|
|
77
|
-
if (_view.dom === mount) {
|
|
78
|
-
_view.setProps(directEditorProps);
|
|
79
|
-
} else {
|
|
80
|
-
setView(null);
|
|
81
|
-
}
|
|
82
|
-
} else if (mount) {
|
|
83
|
-
setView(new EditorView({
|
|
84
|
-
mount
|
|
85
|
-
}, directEditorProps));
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
return useMemo(()=>({
|
|
89
|
-
editorState: state,
|
|
90
|
-
editorView: view,
|
|
91
|
-
registerEventListener,
|
|
92
|
-
unregisterEventListener
|
|
93
|
-
}), [
|
|
94
|
-
state,
|
|
95
|
-
_view,
|
|
96
|
-
registerEventListener,
|
|
97
|
-
unregisterEventListener
|
|
98
|
-
]);
|
|
99
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext } from "react";
|
|
2
|
-
import { reactPluginKey } from "../plugins/react.js";
|
|
3
|
-
import { useEditorState } from "./useEditorState.js";
|
|
4
|
-
const NodePosContext = /*#__PURE__*/ createContext(null);
|
|
5
|
-
export function NodePosProvider(param) {
|
|
6
|
-
let { nodeKey , children } = param;
|
|
7
|
-
const editorState = useEditorState();
|
|
8
|
-
const pluginState = reactPluginKey.getState(editorState);
|
|
9
|
-
if (!pluginState) return /*#__PURE__*/ React.createElement(React.Fragment, null, children);
|
|
10
|
-
return /*#__PURE__*/ React.createElement(NodePosContext.Provider, {
|
|
11
|
-
value: pluginState.keyToPos.get(nodeKey) ?? 0
|
|
12
|
-
}, children);
|
|
13
|
-
}
|
|
14
|
-
export function useNodePos() {
|
|
15
|
-
return useContext(NodePosContext);
|
|
16
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useState } from "react";
|
|
2
|
-
import { NodeViews } from "../components/NodeViews.js";
|
|
3
|
-
import { createReactNodeViewConstructor, findNodeKeyUp } from "../nodeViews/createReactNodeViewConstructor.js";
|
|
4
|
-
export function useNodeViews(nodeViews) {
|
|
5
|
-
const [portals, setPortals] = useState({});
|
|
6
|
-
const registerPortal = useCallback((view, getPos, portal)=>{
|
|
7
|
-
const nearestAncestorKey = findNodeKeyUp(view, getPos());
|
|
8
|
-
setPortals((oldPortals)=>{
|
|
9
|
-
const oldChildPortals = oldPortals[nearestAncestorKey] ?? [];
|
|
10
|
-
const newChildPortals = oldChildPortals.concat({
|
|
11
|
-
getPos,
|
|
12
|
-
portal
|
|
13
|
-
});
|
|
14
|
-
return {
|
|
15
|
-
...oldPortals,
|
|
16
|
-
[nearestAncestorKey]: newChildPortals
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
return ()=>{
|
|
20
|
-
setPortals((oldPortals)=>{
|
|
21
|
-
const oldChildPortals = oldPortals[nearestAncestorKey] ?? [];
|
|
22
|
-
const newChildPortals = oldChildPortals.filter((param)=>{
|
|
23
|
-
let { portal: p } = param;
|
|
24
|
-
return p !== portal;
|
|
25
|
-
});
|
|
26
|
-
return {
|
|
27
|
-
...oldPortals,
|
|
28
|
-
[nearestAncestorKey]: newChildPortals
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
|
-
}, []);
|
|
33
|
-
const wrappedNodeViews = useMemo(()=>{
|
|
34
|
-
const nodeViewEntries = Object.entries(nodeViews ?? {});
|
|
35
|
-
const wrappedNodeViewEntries = nodeViewEntries.map((param)=>{
|
|
36
|
-
let [name, constructor] = param;
|
|
37
|
-
return [
|
|
38
|
-
name,
|
|
39
|
-
createReactNodeViewConstructor(constructor, registerPortal)
|
|
40
|
-
];
|
|
41
|
-
});
|
|
42
|
-
return Object.fromEntries(wrappedNodeViewEntries);
|
|
43
|
-
}, [
|
|
44
|
-
nodeViews,
|
|
45
|
-
registerPortal
|
|
46
|
-
]);
|
|
47
|
-
return {
|
|
48
|
-
nodeViews: wrappedNodeViews,
|
|
49
|
-
nodeViewsComponent: /*#__PURE__*/ React.createElement(NodeViews, {
|
|
50
|
-
portals: portals
|
|
51
|
-
})
|
|
52
|
-
};
|
|
53
|
-
}
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import React, { forwardRef, useContext, useImperativeHandle, useState } from "react";
|
|
2
|
-
import { createPortal } from "react-dom";
|
|
3
|
-
import { NodeViewsContext } from "../contexts/NodeViewsContext.js";
|
|
4
|
-
import { useEditorEffect } from "../hooks/useEditorEffect.js";
|
|
5
|
-
import { NodePosProvider } from "../hooks/useNodePos.js";
|
|
6
|
-
import { ROOT_NODE_KEY, createNodeKey, reactPluginKey } from "../plugins/react.js";
|
|
7
|
-
import { phrasingContentTags } from "./phrasingContentTags.js";
|
|
8
|
-
/**
|
|
9
|
-
* Identifies a node view constructor as having been created
|
|
10
|
-
* by @nytimes/react-prosemirror
|
|
11
|
-
*/ export const REACT_NODE_VIEW = Symbol("react node view");
|
|
12
|
-
let didWarnReactPlugin = false;
|
|
13
|
-
/**
|
|
14
|
-
* Searches upward for the nearest node with a node key,
|
|
15
|
-
* returning the first node key it finds associated with
|
|
16
|
-
* a React node view.
|
|
17
|
-
*
|
|
18
|
-
* Returns the root key if no ancestor nodes have node keys.
|
|
19
|
-
*/ export function findNodeKeyUp(editorView, pos) {
|
|
20
|
-
const pluginState = reactPluginKey.getState(editorView.state);
|
|
21
|
-
if (!pluginState) return ROOT_NODE_KEY;
|
|
22
|
-
const $pos = editorView.state.doc.resolve(pos);
|
|
23
|
-
for(let d = $pos.depth; d > 0; d--){
|
|
24
|
-
const ancestorNodeTypeName = $pos.node(d).type.name;
|
|
25
|
-
const ancestorNodeView = editorView.props.nodeViews?.[ancestorNodeTypeName];
|
|
26
|
-
if (!ancestorNodeView?.[REACT_NODE_VIEW]) continue;
|
|
27
|
-
const ancestorPos = $pos.before(d);
|
|
28
|
-
const ancestorKey = pluginState.posToKey.get(ancestorPos);
|
|
29
|
-
if (ancestorKey) return ancestorKey;
|
|
30
|
-
}
|
|
31
|
-
return ROOT_NODE_KEY;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Factory function for creating nodeViewConstructors that
|
|
35
|
-
* render as React components.
|
|
36
|
-
*
|
|
37
|
-
* `NodeView` can be any React component that takes
|
|
38
|
-
* `NodeViewComponentProps`. It will be passed all of the
|
|
39
|
-
* arguments to the `nodeViewConstructor` except for
|
|
40
|
-
* `editorView`. NodeView components that need access
|
|
41
|
-
* directly to the EditorView should use the
|
|
42
|
-
* `useEditorViewEvent` and `useEditorViewLayoutEffect`
|
|
43
|
-
* hooks to ensure safe access.
|
|
44
|
-
*
|
|
45
|
-
* For contentful Nodes, the NodeView component will also
|
|
46
|
-
* be passed a `children` prop containing an empty element.
|
|
47
|
-
* ProseMirror will render content nodes into this element.
|
|
48
|
-
*/ export function createReactNodeViewConstructor(nodeViewConstructor, registerPortal) {
|
|
49
|
-
function nodeViewConstructorWrapper(node, editorView, getPos, decorations, innerDecorations) {
|
|
50
|
-
const nodeView = nodeViewConstructor(node, editorView, getPos, decorations, innerDecorations);
|
|
51
|
-
const { component: NodeView } = nodeView;
|
|
52
|
-
if (!NodeView) {
|
|
53
|
-
return nodeView;
|
|
54
|
-
}
|
|
55
|
-
const reactPluginState = reactPluginKey.getState(editorView.state);
|
|
56
|
-
if (!reactPluginState) {
|
|
57
|
-
if (!didWarnReactPlugin) {
|
|
58
|
-
console.error("The React ProseMirror plugin is required to use React node views. " + "Make sure to add it to the ProseMirror editor state.");
|
|
59
|
-
didWarnReactPlugin = true;
|
|
60
|
-
}
|
|
61
|
-
return nodeView;
|
|
62
|
-
}
|
|
63
|
-
const { dom , contentDOM } = nodeView;
|
|
64
|
-
// Use a span if the provided contentDOM is in the "phrasing" content
|
|
65
|
-
// category. Otherwise use a div. This is our best attempt at not
|
|
66
|
-
// breaking the intended content model, for now.
|
|
67
|
-
//
|
|
68
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories#phrasing_content
|
|
69
|
-
const ContentDOMWrapper = contentDOM && (phrasingContentTags.includes(contentDOM.tagName.toLocaleLowerCase()) ? "span" : "div");
|
|
70
|
-
const nodeKey = reactPluginState.posToKey.get(getPos()) ?? createNodeKey();
|
|
71
|
-
/**
|
|
72
|
-
* Wrapper component to provide some imperative handles for updating
|
|
73
|
-
* and re-rendering its child. Takes and renders an arbitrary ElementType
|
|
74
|
-
* that expects NodeViewComponentProps as props.
|
|
75
|
-
*/ const NodeViewWrapper = /*#__PURE__*/ forwardRef(function NodeViewWrapper(param, ref) {
|
|
76
|
-
let { initialState } = param;
|
|
77
|
-
const [node, setNode] = useState(initialState.node);
|
|
78
|
-
const [decorations, setDecorations] = useState(initialState.decorations);
|
|
79
|
-
const [isSelected, setIsSelected] = useState(initialState.isSelected);
|
|
80
|
-
const nodeViews = useContext(NodeViewsContext);
|
|
81
|
-
const childNodeViews = nodeViews[nodeKey];
|
|
82
|
-
const [childNodeViewPortals, setChildNodeViewPortals] = useState(childNodeViews?.map((param)=>{
|
|
83
|
-
let { portal } = param;
|
|
84
|
-
return portal;
|
|
85
|
-
}));
|
|
86
|
-
// `getPos` is technically derived from the EditorView
|
|
87
|
-
// state, so it's not safe to call until after the EditorView
|
|
88
|
-
// has been updated
|
|
89
|
-
useEditorEffect(()=>{
|
|
90
|
-
setChildNodeViewPortals(childNodeViews?.sort((a, b)=>a.getPos() - b.getPos()).map((param)=>{
|
|
91
|
-
let { portal } = param;
|
|
92
|
-
return portal;
|
|
93
|
-
}));
|
|
94
|
-
}, [
|
|
95
|
-
childNodeViews
|
|
96
|
-
]);
|
|
97
|
-
const [contentDOMWrapper, setContentDOMWrapper] = useState(null);
|
|
98
|
-
const [contentDOMParent, setContentDOMParent] = useState(null);
|
|
99
|
-
useImperativeHandle(ref, ()=>({
|
|
100
|
-
node,
|
|
101
|
-
contentDOMWrapper: contentDOMWrapper,
|
|
102
|
-
contentDOMParent: contentDOMParent,
|
|
103
|
-
setNode,
|
|
104
|
-
setDecorations,
|
|
105
|
-
setIsSelected
|
|
106
|
-
}), [
|
|
107
|
-
node,
|
|
108
|
-
contentDOMWrapper,
|
|
109
|
-
contentDOMParent
|
|
110
|
-
]);
|
|
111
|
-
return /*#__PURE__*/ React.createElement(NodePosProvider, {
|
|
112
|
-
nodeKey: nodeKey
|
|
113
|
-
}, /*#__PURE__*/ React.createElement(NodeView, {
|
|
114
|
-
node: node,
|
|
115
|
-
decorations: decorations,
|
|
116
|
-
isSelected: isSelected
|
|
117
|
-
}, childNodeViewPortals, ContentDOMWrapper && /*#__PURE__*/ React.createElement(ContentDOMWrapper, {
|
|
118
|
-
style: {
|
|
119
|
-
display: "contents"
|
|
120
|
-
},
|
|
121
|
-
ref: (nextContentDOMWrapper)=>{
|
|
122
|
-
setContentDOMWrapper(nextContentDOMWrapper);
|
|
123
|
-
// we preserve a reference to the contentDOMWrapper'
|
|
124
|
-
// parent so that later we can reassemble the DOM hierarchy
|
|
125
|
-
// React expects when cleaning up the ContentDOMWrapper element
|
|
126
|
-
if (nextContentDOMWrapper?.parentNode) {
|
|
127
|
-
setContentDOMParent(nextContentDOMWrapper.parentNode);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
})));
|
|
131
|
-
});
|
|
132
|
-
NodeViewWrapper.displayName = `NodeView(${NodeView.displayName ?? NodeView.name})`;
|
|
133
|
-
let componentRef = null;
|
|
134
|
-
const element = /*#__PURE__*/ React.createElement(NodeViewWrapper, {
|
|
135
|
-
initialState: {
|
|
136
|
-
node,
|
|
137
|
-
decorations,
|
|
138
|
-
isSelected: false
|
|
139
|
-
},
|
|
140
|
-
ref: (c)=>{
|
|
141
|
-
componentRef = c;
|
|
142
|
-
if (!componentRef || componentRef.node.isLeaf) return;
|
|
143
|
-
const contentDOMWrapper = componentRef.contentDOMWrapper;
|
|
144
|
-
if (!contentDOMWrapper || !(contentDOMWrapper instanceof HTMLElement)) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// We always set contentDOM when !node.isLeaf, which is checked above
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
149
|
-
contentDOMWrapper.appendChild(contentDOM);
|
|
150
|
-
// Synchronize the ProseMirror selection to the DOM, because mounting the
|
|
151
|
-
// component changes the DOM outside of a ProseMirror update.
|
|
152
|
-
const { node } = componentRef;
|
|
153
|
-
const pos = getPos();
|
|
154
|
-
const end = pos + node.nodeSize;
|
|
155
|
-
const { from , to } = editorView.state.selection;
|
|
156
|
-
if (editorView.hasFocus() && pos < from && to < end) {
|
|
157
|
-
// This call seems like it should be a no-op, given the editor already has
|
|
158
|
-
// focus, but it causes ProseMirror to synchronize the DOM selection with
|
|
159
|
-
// its state again, placing the DOM selection in a reasonable place within
|
|
160
|
-
// the node.
|
|
161
|
-
editorView.focus();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
const portal = /*#__PURE__*/ createPortal(element, dom, nodeKey);
|
|
166
|
-
const unregisterPortal = registerPortal(editorView, getPos, portal);
|
|
167
|
-
return {
|
|
168
|
-
ignoreMutation (record) {
|
|
169
|
-
return !contentDOM?.contains(record.target);
|
|
170
|
-
},
|
|
171
|
-
...nodeView,
|
|
172
|
-
selectNode () {
|
|
173
|
-
componentRef?.setIsSelected(true);
|
|
174
|
-
nodeView.selectNode?.();
|
|
175
|
-
},
|
|
176
|
-
deselectNode () {
|
|
177
|
-
componentRef?.setIsSelected(false);
|
|
178
|
-
nodeView.deselectNode?.();
|
|
179
|
-
},
|
|
180
|
-
update (node, decorations, innerDecorations) {
|
|
181
|
-
// If this node view's parent has been removed from the registry, we
|
|
182
|
-
// need to rebuild it and its children with new registry keys
|
|
183
|
-
const positionRegistry = reactPluginKey.getState(editorView.state);
|
|
184
|
-
if (positionRegistry && nodeKey !== positionRegistry.posToKey.get(getPos())) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
if (nodeView.update?.(node, decorations, innerDecorations) === false) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
if (node.type === componentRef?.node.type) {
|
|
191
|
-
componentRef?.setNode(node);
|
|
192
|
-
componentRef?.setDecorations(decorations);
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
196
|
-
},
|
|
197
|
-
destroy () {
|
|
198
|
-
// React expects the contentDOMParent to be a child of the
|
|
199
|
-
// DOM element where the portal was mounted, but in some situations
|
|
200
|
-
// contenteditable may have already detached the contentDOMParent
|
|
201
|
-
// from the DOM. Here we attempt to reassemble the DOM that React
|
|
202
|
-
// expects when cleaning up the portal.
|
|
203
|
-
if (componentRef?.contentDOMParent) {
|
|
204
|
-
this.dom.appendChild(componentRef.contentDOMParent);
|
|
205
|
-
}
|
|
206
|
-
unregisterPortal();
|
|
207
|
-
nodeView.destroy?.();
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
return Object.assign(nodeViewConstructorWrapper, {
|
|
212
|
-
[REACT_NODE_VIEW]: true
|
|
213
|
-
});
|
|
214
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export const phrasingContentTags = [
|
|
2
|
-
"abbr",
|
|
3
|
-
"audio",
|
|
4
|
-
"b",
|
|
5
|
-
"bdo",
|
|
6
|
-
"br",
|
|
7
|
-
"button",
|
|
8
|
-
"canvas",
|
|
9
|
-
"cite",
|
|
10
|
-
"code",
|
|
11
|
-
"data",
|
|
12
|
-
"datalist",
|
|
13
|
-
"dfn",
|
|
14
|
-
"em",
|
|
15
|
-
"embed",
|
|
16
|
-
"i",
|
|
17
|
-
"iframe",
|
|
18
|
-
"img",
|
|
19
|
-
"input",
|
|
20
|
-
"kbd",
|
|
21
|
-
"keygen",
|
|
22
|
-
"label",
|
|
23
|
-
"mark",
|
|
24
|
-
"math",
|
|
25
|
-
"meter",
|
|
26
|
-
"noscript",
|
|
27
|
-
"object",
|
|
28
|
-
"output",
|
|
29
|
-
"picture",
|
|
30
|
-
"progress",
|
|
31
|
-
"q",
|
|
32
|
-
"ruby",
|
|
33
|
-
"s",
|
|
34
|
-
"samp",
|
|
35
|
-
"script",
|
|
36
|
-
"select",
|
|
37
|
-
"small",
|
|
38
|
-
"span",
|
|
39
|
-
"strong",
|
|
40
|
-
"sub",
|
|
41
|
-
"sup",
|
|
42
|
-
"svg",
|
|
43
|
-
"textarea",
|
|
44
|
-
"time",
|
|
45
|
-
"u",
|
|
46
|
-
"var",
|
|
47
|
-
"video",
|
|
48
|
-
"wbr"
|
|
49
|
-
];
|