@handlewithcare/react-prosemirror 2.0.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/LICENSE.txt +12 -0
- package/README.md +705 -0
- package/dist/cjs/browser.js +53 -0
- package/dist/cjs/components/ChildNodeViews.js +376 -0
- package/dist/cjs/components/CursorWrapper.js +91 -0
- package/dist/cjs/components/CustomNodeView.js +79 -0
- package/dist/cjs/components/DocNodeView.js +104 -0
- package/dist/cjs/components/LayoutGroup.js +111 -0
- package/dist/cjs/components/MarkView.js +115 -0
- package/dist/cjs/components/NativeWidgetView.js +109 -0
- package/dist/cjs/components/NodeView.js +196 -0
- package/dist/cjs/components/NodeViewComponentProps.js +4 -0
- package/dist/cjs/components/OutputSpec.js +88 -0
- package/dist/cjs/components/ProseMirror.js +103 -0
- package/dist/cjs/components/ProseMirrorDoc.js +92 -0
- package/dist/cjs/components/SeparatorHackView.js +100 -0
- package/dist/cjs/components/TextNodeView.js +112 -0
- package/dist/cjs/components/TrailingHackView.js +90 -0
- package/dist/cjs/components/WidgetView.js +95 -0
- package/dist/cjs/components/WidgetViewComponentProps.js +4 -0
- package/dist/cjs/components/__tests__/ProseMirror.composition.test.js +398 -0
- package/dist/cjs/components/__tests__/ProseMirror.domchange.test.js +270 -0
- package/dist/cjs/components/__tests__/ProseMirror.draw-decoration.test.js +1010 -0
- package/dist/cjs/components/__tests__/ProseMirror.draw.test.js +337 -0
- package/dist/cjs/components/__tests__/ProseMirror.node-view.test.js +315 -0
- package/dist/cjs/components/__tests__/ProseMirror.selection.test.js +444 -0
- package/dist/cjs/components/__tests__/ProseMirror.test.js +382 -0
- package/dist/cjs/contexts/ChildDescriptorsContext.js +19 -0
- package/dist/cjs/contexts/EditorContext.js +12 -0
- package/dist/cjs/contexts/EditorStateContext.js +12 -0
- package/dist/cjs/contexts/LayoutGroupContext.js +12 -0
- package/dist/cjs/contexts/NodeViewContext.js +12 -0
- package/dist/cjs/contexts/SelectNodeContext.js +12 -0
- package/dist/cjs/contexts/StopEventContext.js +12 -0
- package/dist/cjs/contexts/__tests__/DeferredLayoutEffects.test.js +141 -0
- package/dist/cjs/decorations/ReactWidgetType.js +58 -0
- package/dist/cjs/decorations/computeDocDeco.js +44 -0
- package/dist/cjs/decorations/internalTypes.js +4 -0
- package/dist/cjs/decorations/iterDeco.js +79 -0
- package/dist/cjs/decorations/viewDecorations.js +163 -0
- package/dist/cjs/dom.js +142 -0
- package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +108 -0
- package/dist/cjs/hooks/useClientOnly.js +18 -0
- package/dist/cjs/hooks/useComponentEventListeners.js +39 -0
- package/dist/cjs/hooks/useEditor.js +287 -0
- package/dist/cjs/hooks/useEditorEffect.js +35 -0
- package/dist/cjs/hooks/useEditorEventCallback.js +33 -0
- package/dist/cjs/hooks/useEditorEventListener.js +34 -0
- package/dist/cjs/hooks/useEditorState.js +16 -0
- package/dist/cjs/hooks/useForceUpdate.js +15 -0
- package/dist/cjs/hooks/useLayoutGroupEffect.js +19 -0
- package/dist/cjs/hooks/useNodeViewDescriptor.js +115 -0
- package/dist/cjs/hooks/useReactKeys.js +17 -0
- package/dist/cjs/hooks/useSelectNode.js +28 -0
- package/dist/cjs/hooks/useStopEvent.js +24 -0
- package/dist/cjs/index.js +53 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/plugins/__tests__/reactKeys.test.js +81 -0
- package/dist/cjs/plugins/beforeInputPlugin.js +143 -0
- package/dist/cjs/plugins/componentEventListeners.js +35 -0
- package/dist/cjs/plugins/componentEventListenersPlugin.js +35 -0
- package/dist/cjs/plugins/reactKeys.js +96 -0
- package/dist/cjs/props.js +269 -0
- package/dist/cjs/selection/SelectionDOMObserver.js +174 -0
- package/dist/cjs/selection/hasFocusAndSelection.js +35 -0
- package/dist/cjs/selection/selectionFromDOM.js +77 -0
- package/dist/cjs/selection/selectionToDOM.js +226 -0
- package/dist/cjs/ssr.js +85 -0
- package/dist/cjs/testing/editorViewTestHelpers.js +111 -0
- package/dist/cjs/testing/setupProseMirrorView.js +94 -0
- package/dist/cjs/viewdesc.js +664 -0
- package/dist/esm/browser.js +43 -0
- package/dist/esm/components/ChildNodeViews.js +318 -0
- package/dist/esm/components/CursorWrapper.js +40 -0
- package/dist/esm/components/CustomNodeView.js +28 -0
- package/dist/esm/components/DocNodeView.js +53 -0
- package/dist/esm/components/LayoutGroup.js +66 -0
- package/dist/esm/components/MarkView.js +64 -0
- package/dist/esm/components/NativeWidgetView.js +58 -0
- package/dist/esm/components/NodeView.js +145 -0
- package/dist/esm/components/NodeViewComponentProps.js +1 -0
- package/dist/esm/components/OutputSpec.js +38 -0
- package/dist/esm/components/ProseMirror.js +52 -0
- package/dist/esm/components/ProseMirrorDoc.js +34 -0
- package/dist/esm/components/SeparatorHackView.js +49 -0
- package/dist/esm/components/TextNodeView.js +102 -0
- package/dist/esm/components/TrailingHackView.js +39 -0
- package/dist/esm/components/WidgetView.js +44 -0
- package/dist/esm/components/WidgetViewComponentProps.js +1 -0
- package/dist/esm/components/__tests__/ProseMirror.composition.test.js +395 -0
- package/dist/esm/components/__tests__/ProseMirror.domchange.test.js +266 -0
- package/dist/esm/components/__tests__/ProseMirror.draw-decoration.test.js +967 -0
- package/dist/esm/components/__tests__/ProseMirror.draw.test.js +294 -0
- package/dist/esm/components/__tests__/ProseMirror.node-view.test.js +272 -0
- package/dist/esm/components/__tests__/ProseMirror.selection.test.js +440 -0
- package/dist/esm/components/__tests__/ProseMirror.test.js +339 -0
- package/dist/esm/contexts/ChildDescriptorsContext.js +9 -0
- package/dist/esm/contexts/EditorContext.js +7 -0
- package/dist/esm/contexts/EditorStateContext.js +2 -0
- package/dist/esm/contexts/LayoutGroupContext.js +2 -0
- package/dist/esm/contexts/NodeViewContext.js +2 -0
- package/dist/esm/contexts/SelectNodeContext.js +2 -0
- package/dist/esm/contexts/StopEventContext.js +2 -0
- package/dist/esm/contexts/__tests__/DeferredLayoutEffects.test.js +98 -0
- package/dist/esm/decorations/ReactWidgetType.js +40 -0
- package/dist/esm/decorations/computeDocDeco.js +44 -0
- package/dist/esm/decorations/internalTypes.js +1 -0
- package/dist/esm/decorations/iterDeco.js +73 -0
- package/dist/esm/decorations/viewDecorations.js +163 -0
- package/dist/esm/dom.js +105 -0
- package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +99 -0
- package/dist/esm/hooks/useClientOnly.js +8 -0
- package/dist/esm/hooks/useComponentEventListeners.js +54 -0
- package/dist/esm/hooks/useEditor.js +278 -0
- package/dist/esm/hooks/useEditorEffect.js +38 -0
- package/dist/esm/hooks/useEditorEventCallback.js +35 -0
- package/dist/esm/hooks/useEditorEventListener.js +28 -0
- package/dist/esm/hooks/useEditorState.js +8 -0
- package/dist/esm/hooks/useForceUpdate.js +8 -0
- package/dist/esm/hooks/useLayoutGroupEffect.js +9 -0
- package/dist/esm/hooks/useNodeViewDescriptor.js +105 -0
- package/dist/esm/hooks/useReactKeys.js +7 -0
- package/dist/esm/hooks/useSelectNode.js +18 -0
- package/dist/esm/hooks/useStopEvent.js +14 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/plugins/__tests__/reactKeys.test.js +77 -0
- package/dist/esm/plugins/beforeInputPlugin.js +133 -0
- package/dist/esm/plugins/componentEventListeners.js +25 -0
- package/dist/esm/plugins/componentEventListenersPlugin.js +25 -0
- package/dist/esm/plugins/reactKeys.js +81 -0
- package/dist/esm/props.js +251 -0
- package/dist/esm/selection/SelectionDOMObserver.js +164 -0
- package/dist/esm/selection/hasFocusAndSelection.js +17 -0
- package/dist/esm/selection/selectionFromDOM.js +59 -0
- package/dist/esm/selection/selectionToDOM.js +196 -0
- package/dist/esm/ssr.js +82 -0
- package/dist/esm/testing/editorViewTestHelpers.js +88 -0
- package/dist/esm/testing/setupProseMirrorView.js +76 -0
- package/dist/esm/viewdesc.js +654 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/browser.d.ts +15 -0
- package/dist/types/components/ChildNodeViews.d.ts +9 -0
- package/dist/types/components/CursorWrapper.d.ts +5 -0
- package/dist/types/components/CustomNodeView.d.ts +21 -0
- package/dist/types/components/DocNodeView.d.ts +20 -0
- package/dist/types/components/LayoutGroup.d.ts +12 -0
- package/dist/types/components/MarkView.d.ts +9 -0
- package/dist/types/components/NativeWidgetView.d.ts +8 -0
- package/dist/types/components/NodeView.d.ts +11 -0
- package/dist/types/components/NodeViewComponentProps.d.ts +12 -0
- package/dist/types/components/OutputSpec.d.ts +8 -0
- package/dist/types/components/ProseMirror.d.ts +15 -0
- package/dist/types/components/ProseMirrorDoc.d.ts +10 -0
- package/dist/types/components/SeparatorHackView.d.ts +6 -0
- package/dist/types/components/TextNodeView.d.ts +23 -0
- package/dist/types/components/TrailingHackView.d.ts +6 -0
- package/dist/types/components/WidgetView.d.ts +8 -0
- package/dist/types/components/WidgetViewComponentProps.d.ts +6 -0
- package/dist/types/components/__tests__/ProseMirror.composition.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.domchange.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.draw-decoration.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.draw.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.node-view.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.selection.test.d.ts +1 -0
- package/dist/types/components/__tests__/ProseMirror.test.d.ts +1 -0
- package/dist/types/contexts/ChildDescriptorsContext.d.ts +6 -0
- package/dist/types/contexts/EditorContext.d.ts +14 -0
- package/dist/types/contexts/EditorStateContext.d.ts +2 -0
- package/dist/types/contexts/LayoutGroupContext.d.ts +5 -0
- package/dist/types/contexts/NodeViewContext.d.ts +6 -0
- package/dist/types/contexts/SelectNodeContext.d.ts +3 -0
- package/dist/types/contexts/StopEventContext.d.ts +3 -0
- package/dist/types/contexts/__tests__/DeferredLayoutEffects.test.d.ts +1 -0
- package/dist/types/decorations/ReactWidgetType.d.ts +39 -0
- package/dist/types/decorations/computeDocDeco.d.ts +13 -0
- package/dist/types/decorations/internalTypes.d.ts +16 -0
- package/dist/types/decorations/iterDeco.d.ts +3 -0
- package/dist/types/decorations/viewDecorations.d.ts +13 -0
- package/dist/types/dom.d.ts +22 -0
- package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +1 -0
- package/dist/types/hooks/useClientOnly.d.ts +1 -0
- package/dist/types/hooks/useComponentEventListeners.d.ts +33 -0
- package/dist/types/hooks/useEditor.d.ts +66 -0
- package/dist/types/hooks/useEditorEffect.d.ts +17 -0
- package/dist/types/hooks/useEditorEventCallback.d.ts +15 -0
- package/dist/types/hooks/useEditorEventListener.d.ts +8 -0
- package/dist/types/hooks/useEditorState.d.ts +5 -0
- package/dist/types/hooks/useForceUpdate.d.ts +5 -0
- package/dist/types/hooks/useLayoutGroupEffect.d.ts +3 -0
- package/dist/types/hooks/useNodeViewDescriptor.d.ts +11 -0
- package/dist/types/hooks/useReactKeys.d.ts +5 -0
- package/dist/types/hooks/useSelectNode.d.ts +1 -0
- package/dist/types/hooks/useStopEvent.d.ts +2 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/plugins/__tests__/reactKeys.test.d.ts +1 -0
- package/dist/types/plugins/beforeInputPlugin.d.ts +3 -0
- package/dist/types/plugins/componentEventListeners.d.ts +4 -0
- package/dist/types/plugins/componentEventListenersPlugin.d.ts +4 -0
- package/dist/types/plugins/reactKeys.d.ts +19 -0
- package/dist/types/props.d.ts +1174 -0
- package/dist/types/selection/SelectionDOMObserver.d.ts +34 -0
- package/dist/types/selection/hasFocusAndSelection.d.ts +3 -0
- package/dist/types/selection/selectionFromDOM.d.ts +4 -0
- package/dist/types/selection/selectionToDOM.d.ts +9 -0
- package/dist/types/ssr.d.ts +19 -0
- package/dist/types/testing/editorViewTestHelpers.d.ts +23 -0
- package/dist/types/testing/setupProseMirrorView.d.ts +2 -0
- package/dist/types/viewdesc.d.ts +131 -0
- package/package.json +113 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Schema } from "prosemirror-model";
|
|
2
|
+
import { EditorState } from "prosemirror-state";
|
|
3
|
+
import { DecorationSet, EditorView } from "prosemirror-view";
|
|
4
|
+
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { flushSync } from "react-dom";
|
|
6
|
+
import { beforeInputPlugin } from "../plugins/beforeInputPlugin.js";
|
|
7
|
+
import { SelectionDOMObserver } from "../selection/SelectionDOMObserver.js";
|
|
8
|
+
import { setSsrStubs } from "../ssr.js";
|
|
9
|
+
import { NodeViewDesc } from "../viewdesc.js";
|
|
10
|
+
import { useComponentEventListeners } from "./useComponentEventListeners.js";
|
|
11
|
+
import { useForceUpdate } from "./useForceUpdate.js";
|
|
12
|
+
function buildNodeViews(view) {
|
|
13
|
+
const result = Object.create(null);
|
|
14
|
+
function add(obj) {
|
|
15
|
+
for(const prop in obj)if (!Object.prototype.hasOwnProperty.call(result, prop)) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
16
|
+
result[prop] = obj[prop];
|
|
17
|
+
}
|
|
18
|
+
view.someProp("nodeViews", add);
|
|
19
|
+
view.someProp("markViews", add);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function changedNodeViews(a, b) {
|
|
23
|
+
let nA = 0, nB = 0;
|
|
24
|
+
for(const prop in a){
|
|
25
|
+
if (a[prop] != b[prop]) return true;
|
|
26
|
+
nA++;
|
|
27
|
+
}
|
|
28
|
+
for(const _ in b)nB++;
|
|
29
|
+
return nA != nB;
|
|
30
|
+
}
|
|
31
|
+
function changedProps(a, b) {
|
|
32
|
+
for (const prop of Object.keys(a)){
|
|
33
|
+
if (a[prop] !== b[prop]) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
function getEditable(view) {
|
|
38
|
+
return !view.someProp("editable", (value)=>value(view.state) === false);
|
|
39
|
+
}
|
|
40
|
+
// @ts-expect-error We're making use of knowledge of internal methods here
|
|
41
|
+
export class ReactEditorView extends EditorView {
|
|
42
|
+
shouldUpdatePluginViews = false;
|
|
43
|
+
oldProps;
|
|
44
|
+
_props;
|
|
45
|
+
constructor(place, props){
|
|
46
|
+
// Call the superclass constructor with an empty
|
|
47
|
+
// document and limited props. We'll set everything
|
|
48
|
+
// else ourselves.
|
|
49
|
+
const cleanup = setSsrStubs();
|
|
50
|
+
super(place, {
|
|
51
|
+
state: EditorState.create({
|
|
52
|
+
schema: props.state.schema,
|
|
53
|
+
plugins: props.state.plugins
|
|
54
|
+
}),
|
|
55
|
+
plugins: props.plugins
|
|
56
|
+
});
|
|
57
|
+
cleanup();
|
|
58
|
+
this.shouldUpdatePluginViews = true;
|
|
59
|
+
this._props = props;
|
|
60
|
+
this.oldProps = {
|
|
61
|
+
state: props.state
|
|
62
|
+
};
|
|
63
|
+
this.state = props.state;
|
|
64
|
+
// @ts-expect-error We're making use of knowledge of internal attributes here
|
|
65
|
+
this.domObserver.stop();
|
|
66
|
+
// @ts-expect-error We're making use of knowledge of internal attributes here
|
|
67
|
+
this.domObserver = new SelectionDOMObserver(this);
|
|
68
|
+
// @ts-expect-error We're making use of knowledge of internal attributes here
|
|
69
|
+
this.domObserver.start();
|
|
70
|
+
this.editable = getEditable(this);
|
|
71
|
+
// Destroy the DOM created by the default
|
|
72
|
+
// ProseMirror ViewDesc implementation; we
|
|
73
|
+
// have a NodeViewDesc from React instead.
|
|
74
|
+
// @ts-expect-error We're making use of knowledge of internal attributes here
|
|
75
|
+
this.docView.dom.replaceChildren();
|
|
76
|
+
// @ts-expect-error We're making use of knowledge of internal attributes here
|
|
77
|
+
this.docView = props.docView;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether the EditorView's updateStateInner method thinks that the
|
|
81
|
+
* docView needs to be blown away and redrawn.
|
|
82
|
+
*
|
|
83
|
+
* @privateremarks
|
|
84
|
+
*
|
|
85
|
+
* When ProseMirror View detects that the EditorState has been reconfigured
|
|
86
|
+
* to provide new custom node views, it calls an internal function that
|
|
87
|
+
* we can't override in order to recreate the entire editor DOM.
|
|
88
|
+
*
|
|
89
|
+
* This property mimics that check, so that we can replace the EditorView
|
|
90
|
+
* with another of our own, preventing ProseMirror View from taking over
|
|
91
|
+
* DOM management responsibility.
|
|
92
|
+
*/ get needsRedraw() {
|
|
93
|
+
if (this.oldProps.state.plugins === this._props.state.plugins && this._props.plugins === this.oldProps.plugins) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const newNodeViews = buildNodeViews(this);
|
|
97
|
+
// @ts-expect-error Internal property
|
|
98
|
+
return changedNodeViews(this.nodeViews, newNodeViews);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Like setProps, but without executing any side effects.
|
|
102
|
+
* Safe to use in a component render method.
|
|
103
|
+
*/ pureSetProps(props) {
|
|
104
|
+
// this.oldProps = this.props;
|
|
105
|
+
this._props = {
|
|
106
|
+
...this._props,
|
|
107
|
+
...props
|
|
108
|
+
};
|
|
109
|
+
this.state = this._props.state;
|
|
110
|
+
this.editable = getEditable(this);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Triggers any side effects that have been queued by previous
|
|
114
|
+
* calls to pureSetProps.
|
|
115
|
+
*/ runPendingEffects() {
|
|
116
|
+
if (changedProps(this.props, this.oldProps)) {
|
|
117
|
+
const newProps = this.props;
|
|
118
|
+
this._props = this.oldProps;
|
|
119
|
+
this.state = this._props.state;
|
|
120
|
+
this.update(newProps);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
update(props) {
|
|
124
|
+
super.update(props);
|
|
125
|
+
// Ensure that side effects aren't re-triggered until
|
|
126
|
+
// pureSetProps is called again
|
|
127
|
+
this.oldProps = props;
|
|
128
|
+
}
|
|
129
|
+
updatePluginViews(prevState) {
|
|
130
|
+
if (this.shouldUpdatePluginViews) {
|
|
131
|
+
// @ts-expect-error We're making use of knowledge of internal methods here
|
|
132
|
+
super.updatePluginViews(prevState);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// We want to trigger the default EditorView cleanup, but without
|
|
136
|
+
// the actual view.dom cleanup (which React will have already handled).
|
|
137
|
+
// So we give the EditorView a dummy DOM element and ask it to clean up
|
|
138
|
+
destroy() {
|
|
139
|
+
// @ts-expect-error we're intentionally overwriting this property
|
|
140
|
+
// to prevent side effects
|
|
141
|
+
this.dom = document.createElement("div");
|
|
142
|
+
super.destroy();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const EMPTY_SCHEMA = new Schema({
|
|
146
|
+
nodes: {
|
|
147
|
+
doc: {
|
|
148
|
+
content: "text*"
|
|
149
|
+
},
|
|
150
|
+
text: {
|
|
151
|
+
inline: true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const EMPTY_STATE = EditorState.create({
|
|
156
|
+
schema: EMPTY_SCHEMA
|
|
157
|
+
});
|
|
158
|
+
let didWarnValueDefaultValue = false;
|
|
159
|
+
/**
|
|
160
|
+
* Creates, mounts, and manages a ProseMirror `EditorView`.
|
|
161
|
+
*
|
|
162
|
+
* All state and props updates are executed in a layout effect.
|
|
163
|
+
* To ensure that the EditorState and EditorView are never out of
|
|
164
|
+
* sync, it's important that the EditorView produced by this hook
|
|
165
|
+
* is only accessed through the `useEditorViewEvent` and
|
|
166
|
+
* `useEditorViewLayoutEffect` hooks.
|
|
167
|
+
*/ export function useEditor(mount, options) {
|
|
168
|
+
if (process.env.NODE_ENV !== "production") {
|
|
169
|
+
if (options.defaultState !== undefined && options.state !== undefined && !didWarnValueDefaultValue) {
|
|
170
|
+
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");
|
|
171
|
+
didWarnValueDefaultValue = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const [cursorWrapper, _setCursorWrapper] = useState(null);
|
|
175
|
+
const forceUpdate = useForceUpdate();
|
|
176
|
+
const defaultState = options.defaultState ?? EMPTY_STATE;
|
|
177
|
+
const [_state, setState] = useState(defaultState);
|
|
178
|
+
const state = options.state ?? _state;
|
|
179
|
+
const { componentEventListenersPlugin, registerEventListener, unregisterEventListener } = useComponentEventListeners();
|
|
180
|
+
const setCursorWrapper = useCallback((deco)=>{
|
|
181
|
+
flushSync(()=>{
|
|
182
|
+
_setCursorWrapper(deco);
|
|
183
|
+
});
|
|
184
|
+
}, []);
|
|
185
|
+
const plugins = useMemo(()=>[
|
|
186
|
+
...options.plugins ?? [],
|
|
187
|
+
componentEventListenersPlugin,
|
|
188
|
+
beforeInputPlugin(setCursorWrapper)
|
|
189
|
+
], [
|
|
190
|
+
options.plugins,
|
|
191
|
+
componentEventListenersPlugin,
|
|
192
|
+
setCursorWrapper
|
|
193
|
+
]);
|
|
194
|
+
const dispatchTransaction = useCallback(function dispatchTransaction(tr) {
|
|
195
|
+
flushSync(()=>{
|
|
196
|
+
if (!options.state) {
|
|
197
|
+
setState((s)=>s.apply(tr));
|
|
198
|
+
}
|
|
199
|
+
if (options.dispatchTransaction) {
|
|
200
|
+
options.dispatchTransaction.call(this, tr);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}, [
|
|
204
|
+
options.dispatchTransaction,
|
|
205
|
+
options.state
|
|
206
|
+
]);
|
|
207
|
+
const cleanup = setSsrStubs();
|
|
208
|
+
const tempDom = document.createElement("div");
|
|
209
|
+
cleanup();
|
|
210
|
+
const docViewDescRef = useRef(new NodeViewDesc(undefined, [], ()=>-1, state.doc, [], DecorationSet.empty, tempDom, null, tempDom, ()=>false, ()=>{
|
|
211
|
+
/* The doc node can't have a node selection*/ }, ()=>{
|
|
212
|
+
/* The doc node can't have a node selection*/ }));
|
|
213
|
+
const directEditorProps = {
|
|
214
|
+
...options,
|
|
215
|
+
state,
|
|
216
|
+
plugins,
|
|
217
|
+
dispatchTransaction,
|
|
218
|
+
docView: docViewDescRef.current
|
|
219
|
+
};
|
|
220
|
+
const [view, setView] = useState(// During the initial render, we create something of a dummy
|
|
221
|
+
// EditorView. This allows us to ensure that the first render actually
|
|
222
|
+
// renders the document, which is necessary for SSR.
|
|
223
|
+
()=>new ReactEditorView(null, directEditorProps));
|
|
224
|
+
useLayoutEffect(()=>{
|
|
225
|
+
return ()=>{
|
|
226
|
+
view?.destroy();
|
|
227
|
+
};
|
|
228
|
+
}, [
|
|
229
|
+
view
|
|
230
|
+
]);
|
|
231
|
+
// This rule is concerned about infinite updates due to the
|
|
232
|
+
// call to setView. These calls are deliberately conditional,
|
|
233
|
+
// so this is not a concern.
|
|
234
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
235
|
+
useLayoutEffect(()=>{
|
|
236
|
+
if (!mount) {
|
|
237
|
+
setView(null);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!view || view.dom !== mount) {
|
|
241
|
+
const newView = new ReactEditorView({
|
|
242
|
+
mount
|
|
243
|
+
}, directEditorProps);
|
|
244
|
+
setView(newView);
|
|
245
|
+
newView.dom.addEventListener("compositionend", forceUpdate);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// TODO: We should be able to put this in previous branch,
|
|
249
|
+
// but we need to convince EditorView's constructor not to
|
|
250
|
+
// clear out the DOM when passed a mount that already has
|
|
251
|
+
// content in it, otherwise React blows up when it tries
|
|
252
|
+
// to clean up.
|
|
253
|
+
if (view.needsRedraw) {
|
|
254
|
+
setView(null);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// @ts-expect-error Internal property - domObserver
|
|
258
|
+
view?.domObserver.selectionToDOM();
|
|
259
|
+
view?.runPendingEffects();
|
|
260
|
+
});
|
|
261
|
+
view?.pureSetProps(directEditorProps);
|
|
262
|
+
const editor = useMemo(()=>({
|
|
263
|
+
view: view,
|
|
264
|
+
registerEventListener,
|
|
265
|
+
unregisterEventListener,
|
|
266
|
+
cursorWrapper,
|
|
267
|
+
docViewDescRef
|
|
268
|
+
}), [
|
|
269
|
+
view,
|
|
270
|
+
registerEventListener,
|
|
271
|
+
unregisterEventListener,
|
|
272
|
+
cursorWrapper
|
|
273
|
+
]);
|
|
274
|
+
return {
|
|
275
|
+
editor,
|
|
276
|
+
state
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useContext } from "react";
|
|
2
|
+
import { EditorContext } from "../contexts/EditorContext.js";
|
|
3
|
+
import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
|
|
4
|
+
/**
|
|
5
|
+
* Registers a layout effect to run after the EditorView has
|
|
6
|
+
* been updated with the latest EditorState and Decorations.
|
|
7
|
+
*
|
|
8
|
+
* Effects can take an EditorView instance as an argument.
|
|
9
|
+
* This hook should be used to execute layout effects that
|
|
10
|
+
* depend on the EditorView, such as for positioning DOM
|
|
11
|
+
* nodes based on ProseMirror positions.
|
|
12
|
+
*
|
|
13
|
+
* Layout effects registered with this hook still fire
|
|
14
|
+
* synchronously after all DOM mutations, but they do so
|
|
15
|
+
* _after_ the EditorView has been updated, even when the
|
|
16
|
+
* EditorView lives in an ancestor component.
|
|
17
|
+
*/ export function useEditorEffect(effect, dependencies) {
|
|
18
|
+
const { view } = useContext(EditorContext);
|
|
19
|
+
// The rules of hooks want `effect` to be included in the
|
|
20
|
+
// dependency list, but dependency issues for `effect` will
|
|
21
|
+
// be caught by the linter at the call-site for
|
|
22
|
+
// `useEditorViewLayoutEffect`.
|
|
23
|
+
// Note: we specifically don't want to re-run the effect
|
|
24
|
+
// every time it changes, because it will most likely
|
|
25
|
+
// be defined inline and run on every re-render.
|
|
26
|
+
useLayoutGroupEffect(()=>{
|
|
27
|
+
if (view) {
|
|
28
|
+
return effect(view);
|
|
29
|
+
}
|
|
30
|
+
}, // The rules of hooks want to be able to statically
|
|
31
|
+
// verify the dependencies for the effect, but this will
|
|
32
|
+
// have already happened at the call-site.
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
dependencies && [
|
|
35
|
+
view,
|
|
36
|
+
...dependencies
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useCallback, useContext, useRef } from "react";
|
|
2
|
+
import { EditorContext } from "../contexts/EditorContext.js";
|
|
3
|
+
import { useEditorEffect } from "./useEditorEffect.js";
|
|
4
|
+
/**
|
|
5
|
+
* Returns a stable function reference to be used as an
|
|
6
|
+
* event handler callback.
|
|
7
|
+
*
|
|
8
|
+
* The callback will be called with the EditorView instance
|
|
9
|
+
* as its first argument.
|
|
10
|
+
*
|
|
11
|
+
* This hook is dependent on both the
|
|
12
|
+
* `EditorViewContext.Provider` and the
|
|
13
|
+
* `DeferredLayoutEffectProvider`. It can only be used in a
|
|
14
|
+
* component that is mounted as a child of both of these
|
|
15
|
+
* providers.
|
|
16
|
+
*/ export function useEditorEventCallback(callback) {
|
|
17
|
+
const ref = useRef(callback);
|
|
18
|
+
const { view } = useContext(EditorContext);
|
|
19
|
+
useEditorEffect(()=>{
|
|
20
|
+
ref.current = callback;
|
|
21
|
+
}, [
|
|
22
|
+
callback
|
|
23
|
+
]);
|
|
24
|
+
return useCallback(function() {
|
|
25
|
+
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
26
|
+
args[_key] = arguments[_key];
|
|
27
|
+
}
|
|
28
|
+
if (view) {
|
|
29
|
+
return ref.current(view, ...args);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}, [
|
|
33
|
+
view
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useCallback, useContext, useRef } from "react";
|
|
2
|
+
import { EditorContext } from "../contexts/EditorContext.js";
|
|
3
|
+
import { useEditorEffect } from "./useEditorEffect.js";
|
|
4
|
+
/**
|
|
5
|
+
* Attaches an event listener at the `EditorView`'s DOM node. See
|
|
6
|
+
* [the ProseMirror docs](https://prosemirror.net/docs/ref/#view.EditorProps.handleDOMEvents)
|
|
7
|
+
* for more details.
|
|
8
|
+
*/ export function useEditorEventListener(eventType, handler) {
|
|
9
|
+
const { registerEventListener, unregisterEventListener } = useContext(EditorContext);
|
|
10
|
+
const ref = useRef(handler);
|
|
11
|
+
useEditorEffect(()=>{
|
|
12
|
+
ref.current = handler;
|
|
13
|
+
}, [
|
|
14
|
+
handler
|
|
15
|
+
]);
|
|
16
|
+
const eventHandler = useCallback(function(view, event) {
|
|
17
|
+
return ref.current.call(this, view, event);
|
|
18
|
+
}, []);
|
|
19
|
+
useEditorEffect(()=>{
|
|
20
|
+
registerEventListener(eventType, eventHandler);
|
|
21
|
+
return ()=>unregisterEventListener(eventType, eventHandler);
|
|
22
|
+
}, [
|
|
23
|
+
eventHandler,
|
|
24
|
+
eventType,
|
|
25
|
+
registerEventListener,
|
|
26
|
+
unregisterEventListener
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useContext } from "react";
|
|
2
|
+
import { EditorStateContext } from "../contexts/EditorStateContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* Provides access to the current EditorState value.
|
|
5
|
+
*/ export function useEditorState() {
|
|
6
|
+
const editorState = useContext(EditorStateContext);
|
|
7
|
+
return editorState;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useReducer } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Provides a function that forces an update of the
|
|
4
|
+
* component.
|
|
5
|
+
*/ export function useForceUpdate() {
|
|
6
|
+
const [, forceUpdate] = useReducer((x)=>x + 1, 0);
|
|
7
|
+
return forceUpdate;
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { useContext, useLayoutEffect } from "react";
|
|
2
|
+
import { LayoutGroupContext } from "../contexts/LayoutGroupContext.js";
|
|
3
|
+
/** Registers a layout effect to run at the nearest `LayoutGroup` boundary. */ export function useLayoutGroupEffect(effect, deps) {
|
|
4
|
+
const register = useContext(LayoutGroupContext);
|
|
5
|
+
// The rule for hooks wants to statically verify the deps,
|
|
6
|
+
// but the dependencies are up to the caller, not this implementation.
|
|
7
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
8
|
+
useLayoutEffect(()=>register(effect), deps);
|
|
9
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useCallback, useContext, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
|
+
import { EditorContext } from "../contexts/EditorContext.js";
|
|
4
|
+
import { CompositionViewDesc, NodeViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
5
|
+
export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDecorations, outerDecorations, viewDesc, contentDOMRef) {
|
|
6
|
+
const { view } = useContext(EditorContext);
|
|
7
|
+
const [hasContentDOM, setHasContentDOM] = useState(true);
|
|
8
|
+
const nodeViewDescRef = useRef(viewDesc);
|
|
9
|
+
const stopEvent = useRef(()=>false);
|
|
10
|
+
const setStopEvent = useCallback((newStopEvent)=>{
|
|
11
|
+
stopEvent.current = newStopEvent;
|
|
12
|
+
}, []);
|
|
13
|
+
const selectNode = useRef(()=>{
|
|
14
|
+
if (!nodeDomRef.current || !node) return;
|
|
15
|
+
if (nodeDomRef.current.nodeType == 1) nodeDomRef.current.classList.add("ProseMirror-selectednode");
|
|
16
|
+
if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).draggable = true;
|
|
17
|
+
});
|
|
18
|
+
const deselectNode = useRef(()=>{
|
|
19
|
+
if (!nodeDomRef.current || !node) return;
|
|
20
|
+
if (nodeDomRef.current.nodeType == 1) {
|
|
21
|
+
nodeDomRef.current.classList.remove("ProseMirror-selectednode");
|
|
22
|
+
if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).removeAttribute("draggable");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const setSelectNode = useCallback((newSelectNode, newDeselectNode)=>{
|
|
26
|
+
selectNode.current = newSelectNode;
|
|
27
|
+
deselectNode.current = newDeselectNode;
|
|
28
|
+
}, []);
|
|
29
|
+
const { siblingsRef, parentRef } = useContext(ChildDescriptorsContext);
|
|
30
|
+
const childDescriptors = useRef([]);
|
|
31
|
+
useLayoutEffect(()=>{
|
|
32
|
+
const siblings = siblingsRef.current;
|
|
33
|
+
return ()=>{
|
|
34
|
+
if (!nodeViewDescRef.current) return;
|
|
35
|
+
if (siblings.includes(nodeViewDescRef.current)) {
|
|
36
|
+
const index = siblings.indexOf(nodeViewDescRef.current);
|
|
37
|
+
siblings.splice(index, 1);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [
|
|
41
|
+
siblingsRef
|
|
42
|
+
]);
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
useLayoutEffect(()=>{
|
|
45
|
+
if (!node || !nodeDomRef.current) return;
|
|
46
|
+
const firstChildDesc = childDescriptors.current[0];
|
|
47
|
+
if (!nodeViewDescRef.current) {
|
|
48
|
+
nodeViewDescRef.current = new NodeViewDesc(parentRef.current, childDescriptors.current, getPos, node, outerDecorations, innerDecorations, domRef?.current ?? nodeDomRef.current, firstChildDesc?.dom.parentElement ?? null, nodeDomRef.current, (event)=>!!stopEvent.current(event), ()=>selectNode.current(), ()=>deselectNode.current());
|
|
49
|
+
} else {
|
|
50
|
+
nodeViewDescRef.current.parent = parentRef.current;
|
|
51
|
+
nodeViewDescRef.current.children = childDescriptors.current;
|
|
52
|
+
nodeViewDescRef.current.node = node;
|
|
53
|
+
nodeViewDescRef.current.getPos = getPos;
|
|
54
|
+
nodeViewDescRef.current.outerDeco = outerDecorations;
|
|
55
|
+
nodeViewDescRef.current.innerDeco = innerDecorations;
|
|
56
|
+
nodeViewDescRef.current.dom = domRef?.current ?? nodeDomRef.current;
|
|
57
|
+
// @ts-expect-error We have our own ViewDesc implementations
|
|
58
|
+
nodeViewDescRef.current.dom.pmViewDesc = nodeViewDescRef.current;
|
|
59
|
+
nodeViewDescRef.current.contentDOM = // If there's already a contentDOM, we can just
|
|
60
|
+
// keep it; it won't have changed. This is especially
|
|
61
|
+
// important during compositions, where the
|
|
62
|
+
// firstChildDesc might not have a correct dom node set yet.
|
|
63
|
+
contentDOMRef?.current ?? nodeViewDescRef.current.contentDOM ?? firstChildDesc?.dom.parentElement ?? null;
|
|
64
|
+
nodeViewDescRef.current.nodeDOM = nodeDomRef.current;
|
|
65
|
+
}
|
|
66
|
+
setHasContentDOM(nodeViewDescRef.current.contentDOM !== null);
|
|
67
|
+
if (!siblingsRef.current.includes(nodeViewDescRef.current)) {
|
|
68
|
+
siblingsRef.current.push(nodeViewDescRef.current);
|
|
69
|
+
}
|
|
70
|
+
siblingsRef.current.sort(sortViewDescs);
|
|
71
|
+
for (const childDesc of childDescriptors.current){
|
|
72
|
+
childDesc.parent = nodeViewDescRef.current;
|
|
73
|
+
// Because TextNodeViews can't locate the DOM nodes
|
|
74
|
+
// for compositions, we need to override them here
|
|
75
|
+
if (childDesc instanceof CompositionViewDesc) {
|
|
76
|
+
const compositionTopDOM = nodeViewDescRef.current.contentDOM?.firstChild;
|
|
77
|
+
if (!compositionTopDOM) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
78
|
+
let textDOM = compositionTopDOM;
|
|
79
|
+
while(textDOM.firstChild){
|
|
80
|
+
textDOM = textDOM.firstChild;
|
|
81
|
+
}
|
|
82
|
+
if (!textDOM || !(textDOM instanceof Text)) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
83
|
+
childDesc.dom = compositionTopDOM;
|
|
84
|
+
childDesc.textDOM = textDOM;
|
|
85
|
+
childDesc.text = textDOM.data;
|
|
86
|
+
// @ts-expect-error ???
|
|
87
|
+
childDesc.textDOM.pmViewDesc = childDesc;
|
|
88
|
+
// @ts-expect-error ???
|
|
89
|
+
view?.input.compositionNodes.push(childDesc);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return ()=>{
|
|
93
|
+
if (nodeViewDescRef.current?.children[0] instanceof CompositionViewDesc && !view?.composing) {
|
|
94
|
+
nodeViewDescRef.current?.children[0].dom.parentNode?.removeChild(nodeViewDescRef.current?.children[0].dom);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
hasContentDOM,
|
|
100
|
+
childDescriptors,
|
|
101
|
+
nodeViewDescRef,
|
|
102
|
+
setStopEvent,
|
|
103
|
+
setSelectNode
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { EditorContext } from "../contexts/EditorContext.js";
|
|
3
|
+
import { reactKeysPluginKey } from "../plugins/reactKeys.js";
|
|
4
|
+
export function useReactKeys() {
|
|
5
|
+
const { view } = useContext(EditorContext);
|
|
6
|
+
return view && reactKeysPluginKey.getState(view.state);
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
|
|
3
|
+
import { useEditorEffect } from "./useEditorEffect.js";
|
|
4
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
5
|
+
export function useSelectNode(selectNode, deselectNode) {
|
|
6
|
+
const register = useContext(SelectNodeContext);
|
|
7
|
+
const selectNodeMemo = useEditorEventCallback(selectNode);
|
|
8
|
+
const deselectNodeMemo = useEditorEventCallback(deselectNode ?? (()=>{
|
|
9
|
+
// empty
|
|
10
|
+
}));
|
|
11
|
+
return useEditorEffect(()=>{
|
|
12
|
+
register(selectNodeMemo, deselectNodeMemo);
|
|
13
|
+
}, [
|
|
14
|
+
deselectNodeMemo,
|
|
15
|
+
register,
|
|
16
|
+
selectNodeMemo
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { StopEventContext } from "../contexts/StopEventContext.js";
|
|
3
|
+
import { useEditorEffect } from "./useEditorEffect.js";
|
|
4
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
5
|
+
export function useStopEvent(stopEvent) {
|
|
6
|
+
const register = useContext(StopEventContext);
|
|
7
|
+
const stopEventMemo = useEditorEventCallback(stopEvent);
|
|
8
|
+
useEditorEffect(()=>{
|
|
9
|
+
register(stopEventMemo);
|
|
10
|
+
}, [
|
|
11
|
+
register,
|
|
12
|
+
stopEventMemo
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ "use client";
|
|
2
|
+
export { ProseMirror } from "./components/ProseMirror.js";
|
|
3
|
+
export { ProseMirrorDoc } from "./components/ProseMirrorDoc.js";
|
|
4
|
+
export { useEditorEffect } from "./hooks/useEditorEffect.js";
|
|
5
|
+
export { useEditorEventCallback } from "./hooks/useEditorEventCallback.js";
|
|
6
|
+
export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
|
|
7
|
+
export { useEditorState } from "./hooks/useEditorState.js";
|
|
8
|
+
export { useStopEvent } from "./hooks/useStopEvent.js";
|
|
9
|
+
export { useSelectNode } from "./hooks/useSelectNode.js";
|
|
10
|
+
export { reactKeys } from "./plugins/reactKeys.js";
|
|
11
|
+
export { widget } from "./decorations/ReactWidgetType.js";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Schema } from "prosemirror-model";
|
|
2
|
+
import { EditorState } from "prosemirror-state";
|
|
3
|
+
import { reactKeys, reactKeysPluginKey } from "../reactKeys.js";
|
|
4
|
+
const schema = new Schema({
|
|
5
|
+
nodes: {
|
|
6
|
+
doc: {
|
|
7
|
+
content: "block+"
|
|
8
|
+
},
|
|
9
|
+
paragraph: {
|
|
10
|
+
group: "block",
|
|
11
|
+
content: "inline*"
|
|
12
|
+
},
|
|
13
|
+
list: {
|
|
14
|
+
group: "block",
|
|
15
|
+
content: "list_item+"
|
|
16
|
+
},
|
|
17
|
+
list_item: {
|
|
18
|
+
content: "inline*"
|
|
19
|
+
},
|
|
20
|
+
text: {
|
|
21
|
+
group: "inline"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
describe("reactNodeViewPlugin", ()=>{
|
|
26
|
+
it("should create a unique key for each node", ()=>{
|
|
27
|
+
const editorState = EditorState.create({
|
|
28
|
+
doc: schema.topNodeType.create(null, [
|
|
29
|
+
schema.nodes.paragraph.create(),
|
|
30
|
+
schema.nodes.paragraph.create(),
|
|
31
|
+
schema.nodes.paragraph.create()
|
|
32
|
+
]),
|
|
33
|
+
plugins: [
|
|
34
|
+
reactKeys()
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
const pluginState = reactKeysPluginKey.getState(editorState);
|
|
38
|
+
expect(pluginState.posToKey.size).toBe(3);
|
|
39
|
+
});
|
|
40
|
+
it("should maintain key stability when possible", ()=>{
|
|
41
|
+
const initialEditorState = EditorState.create({
|
|
42
|
+
doc: schema.topNodeType.create(null, [
|
|
43
|
+
schema.nodes.paragraph.create({}, schema.text("Hello")),
|
|
44
|
+
schema.nodes.paragraph.create(),
|
|
45
|
+
schema.nodes.paragraph.create()
|
|
46
|
+
]),
|
|
47
|
+
plugins: [
|
|
48
|
+
reactKeys()
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
const initialPluginState = reactKeysPluginKey.getState(initialEditorState);
|
|
52
|
+
const nextEditorState = initialEditorState.apply(initialEditorState.tr.insertText(", world!", 6));
|
|
53
|
+
const nextPluginState = reactKeysPluginKey.getState(nextEditorState);
|
|
54
|
+
expect(Array.from(initialPluginState.keyToPos.keys())).toEqual(Array.from(nextPluginState.keyToPos.keys()));
|
|
55
|
+
});
|
|
56
|
+
it("should create unique keys for new nodes", ()=>{
|
|
57
|
+
const initialEditorState = EditorState.create({
|
|
58
|
+
doc: schema.topNodeType.create(null, [
|
|
59
|
+
schema.nodes.paragraph.create(),
|
|
60
|
+
schema.nodes.paragraph.create(),
|
|
61
|
+
schema.nodes.paragraph.create()
|
|
62
|
+
]),
|
|
63
|
+
plugins: [
|
|
64
|
+
reactKeys()
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
const initialPluginState = reactKeysPluginKey.getState(initialEditorState);
|
|
68
|
+
const nextEditorState = initialEditorState.apply(initialEditorState.tr.insert(0, schema.nodes.list.createAndFill()));
|
|
69
|
+
const nextPluginState = reactKeysPluginKey.getState(nextEditorState);
|
|
70
|
+
// Adds new keys for new nodes
|
|
71
|
+
expect(nextPluginState.keyToPos.size).toBe(5);
|
|
72
|
+
// Maintains keys for previous nodes that are still there
|
|
73
|
+
Array.from(initialPluginState.keyToPos.keys()).forEach((key)=>{
|
|
74
|
+
expect(Array.from(nextPluginState.keyToPos.keys())).toContain(key);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|