@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,133 @@
|
|
|
1
|
+
import { Plugin } from "prosemirror-state";
|
|
2
|
+
import { CursorWrapper } from "../components/CursorWrapper.js";
|
|
3
|
+
import { widget } from "../decorations/ReactWidgetType.js";
|
|
4
|
+
import { reactKeysPluginKey } from "./reactKeys.js";
|
|
5
|
+
function insertText(view, eventData) {
|
|
6
|
+
let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
|
|
7
|
+
if (eventData === null) return false;
|
|
8
|
+
const from = options.from ?? view.state.selection.from;
|
|
9
|
+
const to = options.to ?? view.state.selection.to;
|
|
10
|
+
if (view.someProp("handleTextInput", (f)=>f(view, from, to, eventData))) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const { tr } = view.state;
|
|
14
|
+
if (options.marks) tr.ensureMarks(options.marks);
|
|
15
|
+
tr.insertText(eventData, from, to);
|
|
16
|
+
if (options.bust) {
|
|
17
|
+
const $from = view.state.doc.resolve(from);
|
|
18
|
+
const sharedAncestorDepth = $from.sharedDepth(to);
|
|
19
|
+
const sharedAncestorPos = $from.start(sharedAncestorDepth);
|
|
20
|
+
const parentKey = reactKeysPluginKey.getState(view.state)?.posToKey.get(sharedAncestorPos - 1);
|
|
21
|
+
tr.setMeta(reactKeysPluginKey, {
|
|
22
|
+
type: "bustKey",
|
|
23
|
+
payload: {
|
|
24
|
+
key: parentKey
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
view.dispatch(tr);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
export function beforeInputPlugin(setCursorWrapper) {
|
|
32
|
+
let compositionText = null;
|
|
33
|
+
let compositionMarks = null;
|
|
34
|
+
return new Plugin({
|
|
35
|
+
props: {
|
|
36
|
+
handleDOMEvents: {
|
|
37
|
+
compositionstart (view) {
|
|
38
|
+
const { state } = view;
|
|
39
|
+
view.dispatch(state.tr.deleteSelection());
|
|
40
|
+
const $pos = state.selection.$from;
|
|
41
|
+
if (state.selection.empty && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore?.marks.some((m)=>m.type.spec.inclusive === false))) {
|
|
42
|
+
setCursorWrapper(widget(state.selection.from, CursorWrapper, {
|
|
43
|
+
key: "cursor-wrapper",
|
|
44
|
+
marks: state.storedMarks ?? $pos.marks()
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
compositionMarks = state.storedMarks ?? $pos.marks();
|
|
48
|
+
// @ts-expect-error Internal property - input
|
|
49
|
+
view.input.composing = true;
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
compositionupdate () {
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
compositionend (view) {
|
|
56
|
+
// @ts-expect-error Internal property - input
|
|
57
|
+
view.input.composing = false;
|
|
58
|
+
if (compositionText === null) return;
|
|
59
|
+
insertText(view, compositionText, {
|
|
60
|
+
// TODO: Rather than busting the reactKey cache here,
|
|
61
|
+
// which is pretty blunt and doesn't work for
|
|
62
|
+
// multi-node replacements, we should attempt to
|
|
63
|
+
// snapshot the selected DOM during compositionstart
|
|
64
|
+
// and restore it before we end the composition.
|
|
65
|
+
// This should allow React to successfully clean up
|
|
66
|
+
// and insert the newly composed text, without requiring
|
|
67
|
+
// any remounts
|
|
68
|
+
bust: true,
|
|
69
|
+
marks: compositionMarks
|
|
70
|
+
});
|
|
71
|
+
compositionText = null;
|
|
72
|
+
compositionMarks = null;
|
|
73
|
+
setCursorWrapper(null);
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
beforeinput (view, event) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
switch(event.inputType){
|
|
79
|
+
case "insertCompositionText":
|
|
80
|
+
{
|
|
81
|
+
if (event.data === null) break;
|
|
82
|
+
compositionText = event.data;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "insertReplacementText":
|
|
86
|
+
{
|
|
87
|
+
const ranges = event.getTargetRanges();
|
|
88
|
+
event.dataTransfer?.items[0]?.getAsString((data)=>{
|
|
89
|
+
for (const range of ranges){
|
|
90
|
+
const from = view.posAtDOM(range.startContainer, range.startOffset, 1);
|
|
91
|
+
const to = view.posAtDOM(range.endContainer, range.endOffset, 1);
|
|
92
|
+
insertText(view, data, {
|
|
93
|
+
from,
|
|
94
|
+
to
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "insertText":
|
|
101
|
+
{
|
|
102
|
+
insertText(view, event.data);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "deleteWordBackward":
|
|
106
|
+
case "deleteContentBackward":
|
|
107
|
+
case "deleteWordForward":
|
|
108
|
+
case "deleteContentForward":
|
|
109
|
+
case "deleteContent":
|
|
110
|
+
{
|
|
111
|
+
const targetRanges = event.getTargetRanges();
|
|
112
|
+
const { tr } = view.state;
|
|
113
|
+
for (const range of targetRanges){
|
|
114
|
+
const start = view.posAtDOM(range.startContainer, range.startOffset);
|
|
115
|
+
const end = view.posAtDOM(range.endContainer, range.endOffset);
|
|
116
|
+
const { doc } = view.state;
|
|
117
|
+
const storedMarks = doc.resolve(start).marksAcross(doc.resolve(end));
|
|
118
|
+
tr.delete(start, end).setStoredMarks(storedMarks);
|
|
119
|
+
}
|
|
120
|
+
view.dispatch(tr);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
default:
|
|
124
|
+
{
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { unstable_batchedUpdates as batch } from "react-dom";
|
|
3
|
+
export function componentEventListeners(eventHandlerRegistry) {
|
|
4
|
+
const domEventHandlers = {};
|
|
5
|
+
for (const [eventType, handlers] of eventHandlerRegistry.entries()){
|
|
6
|
+
function handleEvent(view, event) {
|
|
7
|
+
for (const handler of handlers){
|
|
8
|
+
let handled = false;
|
|
9
|
+
batch(()=>{
|
|
10
|
+
handled = !!handler.call(this, view, event);
|
|
11
|
+
});
|
|
12
|
+
if (handled || event.defaultPrevented) return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
domEventHandlers[eventType] = handleEvent;
|
|
17
|
+
}
|
|
18
|
+
const plugin = new Plugin({
|
|
19
|
+
key: new PluginKey("@nytimes/react-prosemirror/componentEventListeners"),
|
|
20
|
+
props: {
|
|
21
|
+
handleDOMEvents: domEventHandlers
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return plugin;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Copyright (c) The New York Times Company */ import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { unstable_batchedUpdates as batch } from "react-dom";
|
|
3
|
+
export function createComponentEventListenersPlugin(eventHandlerRegistry) {
|
|
4
|
+
const domEventHandlers = {};
|
|
5
|
+
for (const [eventType, handlers] of eventHandlerRegistry.entries()){
|
|
6
|
+
function handleEvent(view, event) {
|
|
7
|
+
for (const handler of handlers){
|
|
8
|
+
let handled = false;
|
|
9
|
+
batch(()=>{
|
|
10
|
+
handled = !!handler.call(this, view, event);
|
|
11
|
+
});
|
|
12
|
+
if (handled || event.defaultPrevented) return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
domEventHandlers[eventType] = handleEvent;
|
|
17
|
+
}
|
|
18
|
+
const plugin = new Plugin({
|
|
19
|
+
key: new PluginKey("componentEventListeners"),
|
|
20
|
+
props: {
|
|
21
|
+
handleDOMEvents: domEventHandlers
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return plugin;
|
|
25
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
export function createNodeKey() {
|
|
3
|
+
const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
|
|
4
|
+
return key;
|
|
5
|
+
}
|
|
6
|
+
export const reactKeysPluginKey = new PluginKey("@nytimes/react-prosemirror/reactKeys");
|
|
7
|
+
/**
|
|
8
|
+
* Tracks a unique key for each (non-text) node in the
|
|
9
|
+
* document, identified by its current position. Keys are
|
|
10
|
+
* (mostly) stable across transaction applications. The
|
|
11
|
+
* key for a given node can be accessed by that node's
|
|
12
|
+
* current position in the document, and vice versa.
|
|
13
|
+
*/ export function reactKeys() {
|
|
14
|
+
let composing = false;
|
|
15
|
+
return new Plugin({
|
|
16
|
+
key: reactKeysPluginKey,
|
|
17
|
+
state: {
|
|
18
|
+
init (_, state) {
|
|
19
|
+
const next = {
|
|
20
|
+
posToKey: new Map(),
|
|
21
|
+
keyToPos: new Map()
|
|
22
|
+
};
|
|
23
|
+
state.doc.descendants((_, pos)=>{
|
|
24
|
+
const key = createNodeKey();
|
|
25
|
+
next.posToKey.set(pos, key);
|
|
26
|
+
next.keyToPos.set(key, pos);
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
return next;
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Keeps node keys stable across transactions.
|
|
33
|
+
*
|
|
34
|
+
* To accomplish this, we map each node position forwards
|
|
35
|
+
* through the transaction to identify its current position,
|
|
36
|
+
* and assign its key to that new position, dropping it if the
|
|
37
|
+
* node was deleted.
|
|
38
|
+
*/ apply (tr, value, _, newState) {
|
|
39
|
+
if (!tr.docChanged || composing) return value;
|
|
40
|
+
const meta = tr.getMeta(reactKeysPluginKey);
|
|
41
|
+
const keyToBust = meta?.type === "bustKey" && meta.payload.key;
|
|
42
|
+
const next = {
|
|
43
|
+
posToKey: new Map(),
|
|
44
|
+
keyToPos: new Map()
|
|
45
|
+
};
|
|
46
|
+
const posToKeyEntries = Array.from(value.posToKey.entries()).sort((param, param1)=>{
|
|
47
|
+
let [a] = param, [b] = param1;
|
|
48
|
+
return a - b;
|
|
49
|
+
});
|
|
50
|
+
for (const [pos, key] of posToKeyEntries){
|
|
51
|
+
const { pos: newPos, deleted } = tr.mapping.mapResult(pos);
|
|
52
|
+
if (deleted) continue;
|
|
53
|
+
let newKey = key;
|
|
54
|
+
if (keyToBust === key) {
|
|
55
|
+
newKey = createNodeKey();
|
|
56
|
+
}
|
|
57
|
+
next.posToKey.set(newPos, newKey);
|
|
58
|
+
next.keyToPos.set(newKey, newPos);
|
|
59
|
+
}
|
|
60
|
+
newState.doc.descendants((_, pos)=>{
|
|
61
|
+
if (next.posToKey.has(pos)) return true;
|
|
62
|
+
const key = createNodeKey();
|
|
63
|
+
next.posToKey.set(pos, key);
|
|
64
|
+
next.keyToPos.set(key, pos);
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
props: {
|
|
71
|
+
handleDOMEvents: {
|
|
72
|
+
compositionstart: ()=>{
|
|
73
|
+
composing = true;
|
|
74
|
+
},
|
|
75
|
+
compositionend: ()=>{
|
|
76
|
+
composing = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import cx from "classnames";
|
|
2
|
+
export function kebabCaseToCamelCase(str) {
|
|
3
|
+
return str.replaceAll(/-[a-z]/g, (g)=>g[1]?.toUpperCase() ?? "");
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Converts a CSS style string to an object
|
|
7
|
+
* that can be passed to a React component's
|
|
8
|
+
* `style` prop.
|
|
9
|
+
*/ export function cssToStyles(css) {
|
|
10
|
+
const stylesheet = new CSSStyleSheet();
|
|
11
|
+
stylesheet.insertRule(`* { ${css} }`);
|
|
12
|
+
const insertedRule = stylesheet.cssRules[0];
|
|
13
|
+
const declaration = insertedRule.style;
|
|
14
|
+
const styles = {};
|
|
15
|
+
for(let i = 0; i < declaration.length; i++){
|
|
16
|
+
const property = declaration.item(i);
|
|
17
|
+
const value = declaration.getPropertyValue(property);
|
|
18
|
+
const camelCasePropertyName = property.startsWith("--") ? property : kebabCaseToCamelCase(property);
|
|
19
|
+
styles[camelCasePropertyName] = value;
|
|
20
|
+
}
|
|
21
|
+
return styles;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Merges two sets of React props. Class names
|
|
25
|
+
* will be concatenated and style objects
|
|
26
|
+
* will be merged.
|
|
27
|
+
*/ export function mergeReactProps(a, b) {
|
|
28
|
+
return {
|
|
29
|
+
...a,
|
|
30
|
+
...b,
|
|
31
|
+
className: cx(a.className, b.className),
|
|
32
|
+
style: {
|
|
33
|
+
...a.style,
|
|
34
|
+
...b.style
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Given a record of HTML attributes, returns tho
|
|
40
|
+
* equivalent React props.
|
|
41
|
+
*/ export function htmlAttrsToReactProps(attrs) {
|
|
42
|
+
const props = {};
|
|
43
|
+
for (const [attrName, attrValue] of Object.entries(attrs)){
|
|
44
|
+
switch(attrName.toLowerCase()){
|
|
45
|
+
case "class":
|
|
46
|
+
{
|
|
47
|
+
props.className = attrValue;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "style":
|
|
51
|
+
{
|
|
52
|
+
props.style = cssToStyles(attrValue);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "autocapitalize":
|
|
56
|
+
{
|
|
57
|
+
props.autoCapitalize = attrValue;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "contenteditable":
|
|
61
|
+
{
|
|
62
|
+
if (attrValue === "" || attrValue === "true") {
|
|
63
|
+
props.contentEditable = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (attrValue === "false") {
|
|
67
|
+
props.contentEditable = false;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (attrValue === "plaintext-only") {
|
|
71
|
+
props.contentEditable = "plaintext-only";
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
props.contentEditable = null;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "draggable":
|
|
78
|
+
{
|
|
79
|
+
props.draggable = attrValue != null;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "enterkeyhint":
|
|
83
|
+
{
|
|
84
|
+
props.enterKeyHint = attrValue;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "for":
|
|
88
|
+
{
|
|
89
|
+
props.htmlFor = attrValue;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "hidden":
|
|
93
|
+
{
|
|
94
|
+
props.hidden = attrValue;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "inputmode":
|
|
98
|
+
{
|
|
99
|
+
props.inputMode = attrValue;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "itemprop":
|
|
103
|
+
{
|
|
104
|
+
props.itemProp = attrValue;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "spellcheck":
|
|
108
|
+
{
|
|
109
|
+
if (attrValue === "" || attrValue === "true") {
|
|
110
|
+
props.spellCheck = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (attrValue === "false") {
|
|
114
|
+
props.spellCheck = false;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
props.spellCheck = null;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "tabindex":
|
|
121
|
+
{
|
|
122
|
+
const numValue = parseInt(attrValue, 10);
|
|
123
|
+
if (!Number.isNaN(numValue)) {
|
|
124
|
+
props.tabIndex = numValue;
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "autocomplete":
|
|
129
|
+
{
|
|
130
|
+
props.autoComplete = attrValue;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "autofocus":
|
|
134
|
+
{
|
|
135
|
+
props.autoFocus = attrValue != null;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "formaction":
|
|
139
|
+
{
|
|
140
|
+
props.formAction = attrValue;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "formenctype":
|
|
144
|
+
{
|
|
145
|
+
props.formEnctype = attrValue;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "formmethod":
|
|
149
|
+
{
|
|
150
|
+
props.formMethod = attrValue;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "formnovalidate":
|
|
154
|
+
{
|
|
155
|
+
props.formNoValidate = attrValue;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "formtarget":
|
|
159
|
+
{
|
|
160
|
+
props.formTarget = attrValue;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case "maxlength":
|
|
164
|
+
{
|
|
165
|
+
const numValue = parseInt(attrValue, 10);
|
|
166
|
+
if (!Number.isNaN(numValue)) {
|
|
167
|
+
props.maxLength = attrValue;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "minlength":
|
|
172
|
+
{
|
|
173
|
+
const numValue = parseInt(attrValue, 10);
|
|
174
|
+
if (!Number.isNaN(numValue)) {
|
|
175
|
+
props.minLength = attrValue;
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "max":
|
|
180
|
+
{
|
|
181
|
+
const numValue = parseInt(attrValue, 10);
|
|
182
|
+
if (!Number.isNaN(numValue)) {
|
|
183
|
+
props.max = attrValue;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "min":
|
|
188
|
+
{
|
|
189
|
+
const numValue = parseInt(attrValue, 10);
|
|
190
|
+
if (!Number.isNaN(numValue)) {
|
|
191
|
+
props.min = attrValue;
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "multiple":
|
|
196
|
+
{
|
|
197
|
+
props.multiple = attrValue != null;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "readonly":
|
|
201
|
+
{
|
|
202
|
+
props.readOnly = attrValue != null;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "required":
|
|
206
|
+
{
|
|
207
|
+
props.required = attrValue != null;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "size":
|
|
211
|
+
{
|
|
212
|
+
const numValue = parseInt(attrValue, 10);
|
|
213
|
+
if (!Number.isNaN(numValue)) {
|
|
214
|
+
props.size = attrValue;
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "step":
|
|
219
|
+
{
|
|
220
|
+
if (attrValue === "any") {
|
|
221
|
+
props.step = attrValue;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
const numValue = parseInt(attrValue, 10);
|
|
225
|
+
if (!Number.isNaN(numValue) && numValue > 0) {
|
|
226
|
+
props.step = attrValue;
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "disabled":
|
|
231
|
+
{
|
|
232
|
+
props.disabled = attrValue != null;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "rows":
|
|
236
|
+
{
|
|
237
|
+
const numValue = parseInt(attrValue, 10);
|
|
238
|
+
if (!Number.isNaN(numValue)) {
|
|
239
|
+
props.rows = attrValue;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
default:
|
|
244
|
+
{
|
|
245
|
+
props[attrName] = attrValue;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return props;
|
|
251
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Selection } from "prosemirror-state";
|
|
2
|
+
import { browser } from "../browser.js";
|
|
3
|
+
import { parentNode, selectionCollapsed } from "../dom.js";
|
|
4
|
+
import { hasFocusAndSelection } from "./hasFocusAndSelection.js";
|
|
5
|
+
import { selectionFromDOM } from "./selectionFromDOM.js";
|
|
6
|
+
import { isEquivalentPosition, selectionToDOM } from "./selectionToDOM.js";
|
|
7
|
+
let SelectionState = class SelectionState {
|
|
8
|
+
anchorNode = null;
|
|
9
|
+
anchorOffset = 0;
|
|
10
|
+
focusNode = null;
|
|
11
|
+
focusOffset = 0;
|
|
12
|
+
set(sel) {
|
|
13
|
+
this.anchorNode = sel.anchorNode;
|
|
14
|
+
this.anchorOffset = sel.anchorOffset;
|
|
15
|
+
this.focusNode = sel.focusNode;
|
|
16
|
+
this.focusOffset = sel.focusOffset;
|
|
17
|
+
}
|
|
18
|
+
clear() {
|
|
19
|
+
this.anchorNode = this.focusNode = null;
|
|
20
|
+
}
|
|
21
|
+
eq(sel) {
|
|
22
|
+
return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset && sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export class SelectionDOMObserver {
|
|
26
|
+
view;
|
|
27
|
+
flushingSoon;
|
|
28
|
+
currentSelection;
|
|
29
|
+
suppressingSelectionUpdates;
|
|
30
|
+
constructor(view){
|
|
31
|
+
this.view = view;
|
|
32
|
+
this.flushingSoon = -1;
|
|
33
|
+
this.currentSelection = new SelectionState();
|
|
34
|
+
this.suppressingSelectionUpdates = false;
|
|
35
|
+
this.view = view;
|
|
36
|
+
this.onSelectionChange = this.onSelectionChange.bind(this);
|
|
37
|
+
}
|
|
38
|
+
connectSelection() {
|
|
39
|
+
this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
40
|
+
}
|
|
41
|
+
disconnectSelection() {
|
|
42
|
+
this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
|
|
43
|
+
}
|
|
44
|
+
stop() {
|
|
45
|
+
this.disconnectSelection();
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
this.connectSelection();
|
|
49
|
+
}
|
|
50
|
+
suppressSelectionUpdates() {
|
|
51
|
+
this.suppressingSelectionUpdates = true;
|
|
52
|
+
setTimeout(()=>this.suppressingSelectionUpdates = false, 50);
|
|
53
|
+
}
|
|
54
|
+
setCurSelection() {
|
|
55
|
+
// @ts-expect-error Internal method
|
|
56
|
+
this.currentSelection.set(this.view.domSelectionRange());
|
|
57
|
+
}
|
|
58
|
+
ignoreSelectionChange(sel) {
|
|
59
|
+
if (!sel.focusNode) return true;
|
|
60
|
+
const ancestors = new Set();
|
|
61
|
+
let container;
|
|
62
|
+
for(let scan = sel.focusNode; scan; scan = parentNode(scan))ancestors.add(scan);
|
|
63
|
+
for(let scan = sel.anchorNode; scan; scan = parentNode(scan))if (ancestors.has(scan)) {
|
|
64
|
+
container = scan;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
// @ts-expect-error Internal property (docView)
|
|
68
|
+
const desc = container && this.view.docView.nearestDesc(container);
|
|
69
|
+
if (desc && desc.ignoreMutation({
|
|
70
|
+
type: "selection",
|
|
71
|
+
target: container?.nodeType == 3 ? container?.parentNode : container
|
|
72
|
+
})) {
|
|
73
|
+
this.setCurSelection();
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
registerMutation() {
|
|
79
|
+
// pass
|
|
80
|
+
}
|
|
81
|
+
flushSoon() {
|
|
82
|
+
if (this.flushingSoon < 0) this.flushingSoon = window.setTimeout(()=>{
|
|
83
|
+
this.flushingSoon = -1;
|
|
84
|
+
this.flush();
|
|
85
|
+
}, 20);
|
|
86
|
+
}
|
|
87
|
+
updateSelection() {
|
|
88
|
+
const { view } = this;
|
|
89
|
+
const compositionID = // @ts-expect-error Internal property (input)
|
|
90
|
+
view.input.compositionPendingChanges || // @ts-expect-error Internal property (input)
|
|
91
|
+
(view.composing ? view.input.compositionID : 0);
|
|
92
|
+
// @ts-expect-error Internal property (input)
|
|
93
|
+
view.input.compositionPendingChanges = 0;
|
|
94
|
+
const origin = // @ts-expect-error Internal property (input)
|
|
95
|
+
view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null;
|
|
96
|
+
const newSel = selectionFromDOM(view, origin);
|
|
97
|
+
if (newSel && !view.state.selection.eq(newSel)) {
|
|
98
|
+
const tr = view.state.tr.setSelection(newSel);
|
|
99
|
+
if (origin == "pointer") tr.setMeta("pointer", true);
|
|
100
|
+
else if (origin == "key") tr.scrollIntoView();
|
|
101
|
+
if (compositionID) tr.setMeta("composition", compositionID);
|
|
102
|
+
view.dispatch(tr);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
selectionToDOM() {
|
|
106
|
+
const { view } = this;
|
|
107
|
+
selectionToDOM(view);
|
|
108
|
+
// @ts-expect-error Internal property (domSelectionRange)
|
|
109
|
+
const sel = view.domSelectionRange();
|
|
110
|
+
this.currentSelection.set(sel);
|
|
111
|
+
}
|
|
112
|
+
flush() {
|
|
113
|
+
const { view } = this;
|
|
114
|
+
// @ts-expect-error Internal property (docView)
|
|
115
|
+
if (!view.docView || this.flushingSoon > -1) return;
|
|
116
|
+
// @ts-expect-error Internal property (domSelectionRange)
|
|
117
|
+
const sel = view.domSelectionRange();
|
|
118
|
+
const newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
|
|
119
|
+
let readSel = null;
|
|
120
|
+
// If it looks like the browser has reset the selection to the
|
|
121
|
+
// start of the document after focus, restore the selection from
|
|
122
|
+
// the state
|
|
123
|
+
if (newSel && // @ts-expect-error Internal property (input)
|
|
124
|
+
view.input.lastFocus > Date.now() - 200 && // @ts-expect-error Internal property (input)
|
|
125
|
+
Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
|
|
126
|
+
// @ts-expect-error Internal property (input)
|
|
127
|
+
view.input.lastFocus = 0;
|
|
128
|
+
selectionToDOM(view);
|
|
129
|
+
this.currentSelection.set(sel);
|
|
130
|
+
// @ts-expect-error Internal property (scrollToSelection)
|
|
131
|
+
view.scrollToSelection();
|
|
132
|
+
} else if (newSel) {
|
|
133
|
+
this.updateSelection();
|
|
134
|
+
if (!this.currentSelection.eq(sel)) selectionToDOM(view);
|
|
135
|
+
this.currentSelection.set(sel);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
selectionChanged(sel) {
|
|
139
|
+
return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
|
|
140
|
+
}
|
|
141
|
+
forceFlush() {
|
|
142
|
+
if (this.flushingSoon > -1) {
|
|
143
|
+
window.clearTimeout(this.flushingSoon);
|
|
144
|
+
this.flushingSoon = -1;
|
|
145
|
+
this.flush();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
onSelectionChange() {
|
|
149
|
+
if (!hasFocusAndSelection(this.view)) return;
|
|
150
|
+
if (this.view.composing) return;
|
|
151
|
+
if (this.suppressingSelectionUpdates) return selectionToDOM(this.view);
|
|
152
|
+
// Deletions on IE11 fire their events in the wrong order, giving
|
|
153
|
+
// us a selection change event before the DOM changes are
|
|
154
|
+
// reported.
|
|
155
|
+
if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
|
|
156
|
+
// @ts-expect-error Internal method
|
|
157
|
+
const sel = this.view.domSelectionRange();
|
|
158
|
+
// Selection.isCollapsed isn't reliable on IE
|
|
159
|
+
if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
160
|
+
sel.anchorNode, sel.anchorOffset)) return this.flushSoon();
|
|
161
|
+
}
|
|
162
|
+
this.flush();
|
|
163
|
+
}
|
|
164
|
+
}
|