@tiptap/react 2.5.8 → 2.6.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/index.cjs +438 -272
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +438 -272
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +438 -272
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/Editor.d.ts +4 -0
- package/dist/packages/core/src/Extension.d.ts +5 -4
- package/dist/packages/core/src/Mark.d.ts +5 -4
- package/dist/packages/core/src/Node.d.ts +5 -4
- package/dist/packages/core/src/commands/setMeta.d.ts +2 -1
- package/dist/packages/core/src/commands/splitListItem.d.ts +2 -1
- package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +10 -4
- package/dist/packages/core/src/pasteRules/nodePasteRule.d.ts +2 -1
- package/dist/packages/react/src/Context.d.ts +1 -1
- package/dist/packages/react/src/Editor.d.ts +4 -2
- package/dist/packages/react/src/EditorContent.d.ts +4 -8
- package/dist/packages/react/src/useEditor.d.ts +1 -2
- package/dist/packages/react/src/useEditorState.d.ts +1 -1
- package/package.json +7 -7
- package/src/Context.tsx +3 -2
- package/src/Editor.ts +4 -2
- package/src/EditorContent.tsx +93 -47
- package/src/NodeViewWrapper.tsx +1 -0
- package/src/ReactNodeViewRenderer.tsx +14 -16
- package/src/ReactRenderer.tsx +11 -2
- package/src/useEditor.ts +243 -89
- package/src/useEditorState.ts +76 -62
package/dist/index.cjs
CHANGED
|
@@ -11,138 +11,6 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
|
|
|
11
11
|
var React__default = /*#__PURE__*/_interopDefaultCompat(React);
|
|
12
12
|
var ReactDOM__default = /*#__PURE__*/_interopDefaultCompat(ReactDOM);
|
|
13
13
|
|
|
14
|
-
const mergeRefs = (...refs) => {
|
|
15
|
-
return (node) => {
|
|
16
|
-
refs.forEach(ref => {
|
|
17
|
-
if (typeof ref === 'function') {
|
|
18
|
-
ref(node);
|
|
19
|
-
}
|
|
20
|
-
else if (ref) {
|
|
21
|
-
ref.current = node;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
const Portals = ({ renderers }) => {
|
|
27
|
-
return (React__default.default.createElement(React__default.default.Fragment, null, Object.entries(renderers).map(([key, renderer]) => {
|
|
28
|
-
return ReactDOM__default.default.createPortal(renderer.reactElement, renderer.element, key);
|
|
29
|
-
})));
|
|
30
|
-
};
|
|
31
|
-
class PureEditorContent extends React__default.default.Component {
|
|
32
|
-
constructor(props) {
|
|
33
|
-
super(props);
|
|
34
|
-
this.editorContentRef = React__default.default.createRef();
|
|
35
|
-
this.initialized = false;
|
|
36
|
-
this.state = {
|
|
37
|
-
renderers: {},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
componentDidMount() {
|
|
41
|
-
this.init();
|
|
42
|
-
}
|
|
43
|
-
componentDidUpdate() {
|
|
44
|
-
this.init();
|
|
45
|
-
}
|
|
46
|
-
init() {
|
|
47
|
-
const { editor } = this.props;
|
|
48
|
-
if (editor && !editor.isDestroyed && editor.options.element) {
|
|
49
|
-
if (editor.contentComponent) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const element = this.editorContentRef.current;
|
|
53
|
-
element.append(...editor.options.element.childNodes);
|
|
54
|
-
editor.setOptions({
|
|
55
|
-
element,
|
|
56
|
-
});
|
|
57
|
-
editor.contentComponent = this;
|
|
58
|
-
editor.createNodeViews();
|
|
59
|
-
this.initialized = true;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
maybeFlushSync(fn) {
|
|
63
|
-
// Avoid calling flushSync until the editor is initialized.
|
|
64
|
-
// Initialization happens during the componentDidMount or componentDidUpdate
|
|
65
|
-
// lifecycle methods, and React doesn't allow calling flushSync from inside
|
|
66
|
-
// a lifecycle method.
|
|
67
|
-
if (this.initialized) {
|
|
68
|
-
ReactDOM.flushSync(fn);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
fn();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
setRenderer(id, renderer) {
|
|
75
|
-
this.maybeFlushSync(() => {
|
|
76
|
-
this.setState(({ renderers }) => ({
|
|
77
|
-
renderers: {
|
|
78
|
-
...renderers,
|
|
79
|
-
[id]: renderer,
|
|
80
|
-
},
|
|
81
|
-
}));
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
removeRenderer(id) {
|
|
85
|
-
this.maybeFlushSync(() => {
|
|
86
|
-
this.setState(({ renderers }) => {
|
|
87
|
-
const nextRenderers = { ...renderers };
|
|
88
|
-
delete nextRenderers[id];
|
|
89
|
-
return { renderers: nextRenderers };
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
componentWillUnmount() {
|
|
94
|
-
const { editor } = this.props;
|
|
95
|
-
if (!editor) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.initialized = false;
|
|
99
|
-
if (!editor.isDestroyed) {
|
|
100
|
-
editor.view.setProps({
|
|
101
|
-
nodeViews: {},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
editor.contentComponent = null;
|
|
105
|
-
if (!editor.options.element.firstChild) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const newElement = document.createElement('div');
|
|
109
|
-
newElement.append(...editor.options.element.childNodes);
|
|
110
|
-
editor.setOptions({
|
|
111
|
-
element: newElement,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
render() {
|
|
115
|
-
const { editor, innerRef, ...rest } = this.props;
|
|
116
|
-
return (React__default.default.createElement(React__default.default.Fragment, null,
|
|
117
|
-
React__default.default.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
|
|
118
|
-
React__default.default.createElement(Portals, { renderers: this.state.renderers })));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// EditorContent should be re-created whenever the Editor instance changes
|
|
122
|
-
const EditorContentWithKey = React.forwardRef((props, ref) => {
|
|
123
|
-
const key = React__default.default.useMemo(() => {
|
|
124
|
-
return Math.floor(Math.random() * 0xFFFFFFFF).toString();
|
|
125
|
-
}, [props.editor]);
|
|
126
|
-
// Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
|
|
127
|
-
return React__default.default.createElement(PureEditorContent, {
|
|
128
|
-
key,
|
|
129
|
-
innerRef: ref,
|
|
130
|
-
...props,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
const EditorContent = React__default.default.memo(EditorContentWithKey);
|
|
134
|
-
|
|
135
|
-
class Editor extends core.Editor {
|
|
136
|
-
constructor() {
|
|
137
|
-
super(...arguments);
|
|
138
|
-
this.contentComponent = null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
var withSelector = {exports: {}};
|
|
143
|
-
|
|
144
|
-
var withSelector_production_min = {};
|
|
145
|
-
|
|
146
14
|
var shim = {exports: {}};
|
|
147
15
|
|
|
148
16
|
var useSyncExternalStoreShim_production_min = {};
|
|
@@ -413,19 +281,173 @@ function requireUseSyncExternalStoreShim_development () {
|
|
|
413
281
|
return useSyncExternalStoreShim_development;
|
|
414
282
|
}
|
|
415
283
|
|
|
416
|
-
|
|
284
|
+
if (process.env.NODE_ENV === 'production') {
|
|
285
|
+
shim.exports = requireUseSyncExternalStoreShim_production_min();
|
|
286
|
+
} else {
|
|
287
|
+
shim.exports = requireUseSyncExternalStoreShim_development();
|
|
288
|
+
}
|
|
417
289
|
|
|
418
|
-
|
|
419
|
-
if (hasRequiredShim) return shim.exports;
|
|
420
|
-
hasRequiredShim = 1;
|
|
290
|
+
var shimExports = shim.exports;
|
|
421
291
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
292
|
+
const mergeRefs = (...refs) => {
|
|
293
|
+
return (node) => {
|
|
294
|
+
refs.forEach(ref => {
|
|
295
|
+
if (typeof ref === 'function') {
|
|
296
|
+
ref(node);
|
|
297
|
+
}
|
|
298
|
+
else if (ref) {
|
|
299
|
+
ref.current = node;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* This component renders all of the editor's node views.
|
|
306
|
+
*/
|
|
307
|
+
const Portals = ({ contentComponent, }) => {
|
|
308
|
+
// For performance reasons, we render the node view portals on state changes only
|
|
309
|
+
const renderers = shimExports.useSyncExternalStore(contentComponent.subscribe, contentComponent.getSnapshot, contentComponent.getServerSnapshot);
|
|
310
|
+
// This allows us to directly render the portals without any additional wrapper
|
|
311
|
+
return (React__default.default.createElement(React__default.default.Fragment, null, Object.values(renderers)));
|
|
312
|
+
};
|
|
313
|
+
function getInstance() {
|
|
314
|
+
const subscribers = new Set();
|
|
315
|
+
let renderers = {};
|
|
316
|
+
return {
|
|
317
|
+
/**
|
|
318
|
+
* Subscribe to the editor instance's changes.
|
|
319
|
+
*/
|
|
320
|
+
subscribe(callback) {
|
|
321
|
+
subscribers.add(callback);
|
|
322
|
+
return () => {
|
|
323
|
+
subscribers.delete(callback);
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
getSnapshot() {
|
|
327
|
+
return renderers;
|
|
328
|
+
},
|
|
329
|
+
getServerSnapshot() {
|
|
330
|
+
return renderers;
|
|
331
|
+
},
|
|
332
|
+
/**
|
|
333
|
+
* Adds a new NodeView Renderer to the editor.
|
|
334
|
+
*/
|
|
335
|
+
setRenderer(id, renderer) {
|
|
336
|
+
renderers = {
|
|
337
|
+
...renderers,
|
|
338
|
+
[id]: ReactDOM__default.default.createPortal(renderer.reactElement, renderer.element, id),
|
|
339
|
+
};
|
|
340
|
+
subscribers.forEach(subscriber => subscriber());
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* Removes a NodeView Renderer from the editor.
|
|
344
|
+
*/
|
|
345
|
+
removeRenderer(id) {
|
|
346
|
+
const nextRenderers = { ...renderers };
|
|
347
|
+
delete nextRenderers[id];
|
|
348
|
+
renderers = nextRenderers;
|
|
349
|
+
subscribers.forEach(subscriber => subscriber());
|
|
350
|
+
},
|
|
351
|
+
};
|
|
428
352
|
}
|
|
353
|
+
class PureEditorContent extends React__default.default.Component {
|
|
354
|
+
constructor(props) {
|
|
355
|
+
var _a;
|
|
356
|
+
super(props);
|
|
357
|
+
this.editorContentRef = React__default.default.createRef();
|
|
358
|
+
this.initialized = false;
|
|
359
|
+
this.state = {
|
|
360
|
+
hasContentComponentInitialized: Boolean((_a = props.editor) === null || _a === void 0 ? void 0 : _a.contentComponent),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
componentDidMount() {
|
|
364
|
+
this.init();
|
|
365
|
+
}
|
|
366
|
+
componentDidUpdate() {
|
|
367
|
+
this.init();
|
|
368
|
+
}
|
|
369
|
+
init() {
|
|
370
|
+
const { editor } = this.props;
|
|
371
|
+
if (editor && !editor.isDestroyed && editor.options.element) {
|
|
372
|
+
if (editor.contentComponent) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const element = this.editorContentRef.current;
|
|
376
|
+
element.append(...editor.options.element.childNodes);
|
|
377
|
+
editor.setOptions({
|
|
378
|
+
element,
|
|
379
|
+
});
|
|
380
|
+
editor.contentComponent = getInstance();
|
|
381
|
+
// Has the content component been initialized?
|
|
382
|
+
if (!this.state.hasContentComponentInitialized) {
|
|
383
|
+
// Subscribe to the content component
|
|
384
|
+
this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
|
|
385
|
+
this.setState(prevState => {
|
|
386
|
+
if (!prevState.hasContentComponentInitialized) {
|
|
387
|
+
return {
|
|
388
|
+
hasContentComponentInitialized: true,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return prevState;
|
|
392
|
+
});
|
|
393
|
+
// Unsubscribe to previous content component
|
|
394
|
+
if (this.unsubscribeToContentComponent) {
|
|
395
|
+
this.unsubscribeToContentComponent();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
editor.createNodeViews();
|
|
400
|
+
this.initialized = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
componentWillUnmount() {
|
|
404
|
+
const { editor } = this.props;
|
|
405
|
+
if (!editor) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.initialized = false;
|
|
409
|
+
if (!editor.isDestroyed) {
|
|
410
|
+
editor.view.setProps({
|
|
411
|
+
nodeViews: {},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (this.unsubscribeToContentComponent) {
|
|
415
|
+
this.unsubscribeToContentComponent();
|
|
416
|
+
}
|
|
417
|
+
editor.contentComponent = null;
|
|
418
|
+
if (!editor.options.element.firstChild) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const newElement = document.createElement('div');
|
|
422
|
+
newElement.append(...editor.options.element.childNodes);
|
|
423
|
+
editor.setOptions({
|
|
424
|
+
element: newElement,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
render() {
|
|
428
|
+
const { editor, innerRef, ...rest } = this.props;
|
|
429
|
+
return (React__default.default.createElement(React__default.default.Fragment, null,
|
|
430
|
+
React__default.default.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
|
|
431
|
+
(editor === null || editor === void 0 ? void 0 : editor.contentComponent) && React__default.default.createElement(Portals, { contentComponent: editor.contentComponent })));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// EditorContent should be re-created whenever the Editor instance changes
|
|
435
|
+
const EditorContentWithKey = React.forwardRef((props, ref) => {
|
|
436
|
+
const key = React__default.default.useMemo(() => {
|
|
437
|
+
return Math.floor(Math.random() * 0xffffffff).toString();
|
|
438
|
+
}, [props.editor]);
|
|
439
|
+
// Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
|
|
440
|
+
return React__default.default.createElement(PureEditorContent, {
|
|
441
|
+
key,
|
|
442
|
+
innerRef: ref,
|
|
443
|
+
...props,
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
const EditorContent = React__default.default.memo(EditorContentWithKey);
|
|
447
|
+
|
|
448
|
+
var withSelector = {exports: {}};
|
|
449
|
+
|
|
450
|
+
var withSelector_production_min = {};
|
|
429
451
|
|
|
430
452
|
/**
|
|
431
453
|
* @license React
|
|
@@ -442,7 +464,7 @@ var hasRequiredWithSelector_production_min;
|
|
|
442
464
|
function requireWithSelector_production_min () {
|
|
443
465
|
if (hasRequiredWithSelector_production_min) return withSelector_production_min;
|
|
444
466
|
hasRequiredWithSelector_production_min = 1;
|
|
445
|
-
var h=React__default.default,n=
|
|
467
|
+
var h=React__default.default,n=shimExports;function p(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}var q="function"===typeof Object.is?Object.is:p,r=n.useSyncExternalStore,t=h.useRef,u=h.useEffect,v=h.useMemo,w=h.useDebugValue;
|
|
446
468
|
withSelector_production_min.useSyncExternalStoreWithSelector=function(a,b,e,l,g){var c=t(null);if(null===c.current){var f={hasValue:!1,value:null};c.current=f;}else f=c.current;c=v(function(){function a(a){if(!c){c=!0;d=a;a=l(a);if(void 0!==g&&f.hasValue){var b=f.value;if(g(b,a))return k=b}return k=a}b=k;if(q(d,a))return b;var e=l(a);if(void 0!==g&&g(b,e))return b;d=a;return k=e}var c=!1,d,k,m=void 0===e?null:e;return [function(){return a(b())},null===m?void 0:function(){return a(m())}]},[b,e,l,g]);var d=r(a,c[0],c[1]);
|
|
447
469
|
u(function(){f.hasValue=!0;f.value=d;},[d]);w(d);return d};
|
|
448
470
|
return withSelector_production_min;
|
|
@@ -478,7 +500,7 @@ function requireWithSelector_development () {
|
|
|
478
500
|
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
|
|
479
501
|
}
|
|
480
502
|
var React = React__default.default;
|
|
481
|
-
var shim =
|
|
503
|
+
var shim = shimExports;
|
|
482
504
|
|
|
483
505
|
/**
|
|
484
506
|
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
|
@@ -632,71 +654,75 @@ var withSelectorExports = withSelector.exports;
|
|
|
632
654
|
* To synchronize the editor instance with the component state,
|
|
633
655
|
* we need to create a separate instance that is not affected by the component re-renders.
|
|
634
656
|
*/
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
657
|
+
class EditorStateManager {
|
|
658
|
+
constructor(initialEditor) {
|
|
659
|
+
this.transactionNumber = 0;
|
|
660
|
+
this.lastTransactionNumber = 0;
|
|
661
|
+
this.subscribers = new Set();
|
|
662
|
+
this.editor = initialEditor;
|
|
663
|
+
this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 };
|
|
664
|
+
this.getSnapshot = this.getSnapshot.bind(this);
|
|
665
|
+
this.getServerSnapshot = this.getServerSnapshot.bind(this);
|
|
666
|
+
this.watch = this.watch.bind(this);
|
|
667
|
+
this.subscribe = this.subscribe.bind(this);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Get the current editor instance.
|
|
671
|
+
*/
|
|
672
|
+
getSnapshot() {
|
|
673
|
+
if (this.transactionNumber === this.lastTransactionNumber) {
|
|
674
|
+
return this.lastSnapshot;
|
|
675
|
+
}
|
|
676
|
+
this.lastTransactionNumber = this.transactionNumber;
|
|
677
|
+
this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber };
|
|
678
|
+
return this.lastSnapshot;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Always disable the editor on the server-side.
|
|
682
|
+
*/
|
|
683
|
+
getServerSnapshot() {
|
|
684
|
+
return { editor: null, transactionNumber: 0 };
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Subscribe to the editor instance's changes.
|
|
688
|
+
*/
|
|
689
|
+
subscribe(callback) {
|
|
690
|
+
this.subscribers.add(callback);
|
|
691
|
+
return () => {
|
|
692
|
+
this.subscribers.delete(callback);
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Watch the editor instance for changes.
|
|
697
|
+
*/
|
|
698
|
+
watch(nextEditor) {
|
|
699
|
+
this.editor = nextEditor;
|
|
700
|
+
if (this.editor) {
|
|
701
|
+
/**
|
|
702
|
+
* This will force a re-render when the editor state changes.
|
|
703
|
+
* This is to support things like `editor.can().toggleBold()` in components that `useEditor`.
|
|
704
|
+
* This could be more efficient, but it's a good trade-off for now.
|
|
705
|
+
*/
|
|
706
|
+
const fn = () => {
|
|
707
|
+
this.transactionNumber += 1;
|
|
708
|
+
this.subscribers.forEach(callback => callback());
|
|
709
|
+
};
|
|
710
|
+
const currentEditor = this.editor;
|
|
711
|
+
currentEditor.on('transaction', fn);
|
|
664
712
|
return () => {
|
|
665
|
-
|
|
713
|
+
currentEditor.off('transaction', fn);
|
|
666
714
|
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
*/
|
|
671
|
-
watch(nextEditor) {
|
|
672
|
-
editor = nextEditor;
|
|
673
|
-
if (editor) {
|
|
674
|
-
/**
|
|
675
|
-
* This will force a re-render when the editor state changes.
|
|
676
|
-
* This is to support things like `editor.can().toggleBold()` in components that `useEditor`.
|
|
677
|
-
* This could be more efficient, but it's a good trade-off for now.
|
|
678
|
-
*/
|
|
679
|
-
const fn = () => {
|
|
680
|
-
transactionNumber += 1;
|
|
681
|
-
subscribers.forEach(callback => callback());
|
|
682
|
-
};
|
|
683
|
-
const currentEditor = editor;
|
|
684
|
-
currentEditor.on('transaction', fn);
|
|
685
|
-
return () => {
|
|
686
|
-
currentEditor.off('transaction', fn);
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
};
|
|
691
|
-
return editorInstance;
|
|
715
|
+
}
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
692
718
|
}
|
|
693
719
|
function useEditorState(options) {
|
|
694
|
-
const [editorInstance] = React.useState(() =>
|
|
720
|
+
const [editorInstance] = React.useState(() => new EditorStateManager(options.editor));
|
|
695
721
|
// Using the `useSyncExternalStore` hook to sync the editor instance with the component state
|
|
696
722
|
const selectedState = withSelectorExports.useSyncExternalStoreWithSelector(editorInstance.subscribe, editorInstance.getSnapshot, editorInstance.getServerSnapshot, options.selector, options.equalityFn);
|
|
697
723
|
React.useEffect(() => {
|
|
698
724
|
return editorInstance.watch(options.editor);
|
|
699
|
-
}, [options.editor]);
|
|
725
|
+
}, [options.editor, editorInstance]);
|
|
700
726
|
React.useDebugValue(selectedState);
|
|
701
727
|
return selectedState;
|
|
702
728
|
}
|
|
@@ -705,25 +731,50 @@ const isDev = process.env.NODE_ENV !== 'production';
|
|
|
705
731
|
const isSSR = typeof window === 'undefined';
|
|
706
732
|
const isNext = isSSR || Boolean(typeof window !== 'undefined' && window.next);
|
|
707
733
|
/**
|
|
708
|
-
*
|
|
734
|
+
* This class handles the creation, destruction, and re-creation of the editor instance.
|
|
709
735
|
*/
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
736
|
+
class EditorInstanceManager {
|
|
737
|
+
constructor(options) {
|
|
738
|
+
/**
|
|
739
|
+
* The current editor instance.
|
|
740
|
+
*/
|
|
741
|
+
this.editor = null;
|
|
742
|
+
/**
|
|
743
|
+
* The subscriptions to notify when the editor instance
|
|
744
|
+
* has been created or destroyed.
|
|
745
|
+
*/
|
|
746
|
+
this.subscriptions = new Set();
|
|
747
|
+
/**
|
|
748
|
+
* Whether the editor has been mounted.
|
|
749
|
+
*/
|
|
750
|
+
this.isComponentMounted = false;
|
|
751
|
+
/**
|
|
752
|
+
* The most recent dependencies array.
|
|
753
|
+
*/
|
|
754
|
+
this.previousDeps = null;
|
|
755
|
+
/**
|
|
756
|
+
* The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance.
|
|
757
|
+
*/
|
|
758
|
+
this.instanceId = '';
|
|
759
|
+
this.options = options;
|
|
760
|
+
this.subscriptions = new Set();
|
|
761
|
+
this.setEditor(this.getInitialEditor());
|
|
762
|
+
this.getEditor = this.getEditor.bind(this);
|
|
763
|
+
this.getServerSnapshot = this.getServerSnapshot.bind(this);
|
|
764
|
+
this.subscribe = this.subscribe.bind(this);
|
|
765
|
+
this.refreshEditorInstance = this.refreshEditorInstance.bind(this);
|
|
766
|
+
this.scheduleDestroy = this.scheduleDestroy.bind(this);
|
|
767
|
+
this.onRender = this.onRender.bind(this);
|
|
768
|
+
this.createEditor = this.createEditor.bind(this);
|
|
769
|
+
}
|
|
770
|
+
setEditor(editor) {
|
|
771
|
+
this.editor = editor;
|
|
772
|
+
this.instanceId = Math.random().toString(36).slice(2, 9);
|
|
773
|
+
// Notify all subscribers that the editor instance has been created
|
|
774
|
+
this.subscriptions.forEach(cb => cb());
|
|
775
|
+
}
|
|
776
|
+
getInitialEditor() {
|
|
777
|
+
if (this.options.current.immediatelyRender === undefined) {
|
|
727
778
|
if (isSSR || isNext) {
|
|
728
779
|
// TODO in the next major release, we should throw an error here
|
|
729
780
|
if (isDev) {
|
|
@@ -737,55 +788,151 @@ function useEditor(options = {}, deps = []) {
|
|
|
737
788
|
return null;
|
|
738
789
|
}
|
|
739
790
|
// Default to immediately rendering when client-side rendering
|
|
740
|
-
return createEditor(
|
|
791
|
+
return this.createEditor();
|
|
741
792
|
}
|
|
742
|
-
if (options.immediatelyRender && isSSR && isDev) {
|
|
793
|
+
if (this.options.current.immediatelyRender && isSSR && isDev) {
|
|
743
794
|
// Warn in development, to make sure the developer is aware that tiptap cannot be SSR'd, set `immediatelyRender` to `false` to avoid hydration mismatches.
|
|
744
795
|
throw new Error('Tiptap Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches.');
|
|
745
796
|
}
|
|
746
|
-
if (options.immediatelyRender) {
|
|
747
|
-
return createEditor(
|
|
797
|
+
if (this.options.current.immediatelyRender) {
|
|
798
|
+
return this.createEditor();
|
|
748
799
|
}
|
|
749
800
|
return null;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Create a new editor instance. And attach event listeners.
|
|
804
|
+
*/
|
|
805
|
+
createEditor() {
|
|
806
|
+
const optionsToApply = {
|
|
807
|
+
...this.options.current,
|
|
808
|
+
// Always call the most recent version of the callback function by default
|
|
809
|
+
onBeforeCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
810
|
+
onBlur: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
811
|
+
onCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
812
|
+
onDestroy: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
813
|
+
onFocus: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
814
|
+
onSelectionUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
815
|
+
onTransaction: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
816
|
+
onUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
817
|
+
onContentError: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
|
|
818
|
+
};
|
|
819
|
+
const editor = new core.Editor(optionsToApply);
|
|
820
|
+
// no need to keep track of the event listeners, they will be removed when the editor is destroyed
|
|
821
|
+
return editor;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Get the current editor instance.
|
|
825
|
+
*/
|
|
826
|
+
getEditor() {
|
|
827
|
+
return this.editor;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Always disable the editor on the server-side.
|
|
831
|
+
*/
|
|
832
|
+
getServerSnapshot() {
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Subscribe to the editor instance's changes.
|
|
837
|
+
*/
|
|
838
|
+
subscribe(onStoreChange) {
|
|
839
|
+
this.subscriptions.add(onStoreChange);
|
|
840
|
+
return () => {
|
|
841
|
+
this.subscriptions.delete(onStoreChange);
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* On each render, we will create, update, or destroy the editor instance.
|
|
846
|
+
* @param deps The dependencies to watch for changes
|
|
847
|
+
* @returns A cleanup function
|
|
848
|
+
*/
|
|
849
|
+
onRender(deps) {
|
|
850
|
+
// The returned callback will run on each render
|
|
851
|
+
return () => {
|
|
852
|
+
this.isComponentMounted = true;
|
|
853
|
+
// Cleanup any scheduled destructions, since we are currently rendering
|
|
854
|
+
clearTimeout(this.scheduledDestructionTimeout);
|
|
855
|
+
if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
|
|
856
|
+
// if the editor does exist & deps are empty, we don't need to re-initialize the editor
|
|
857
|
+
// we can fast-path to update the editor options on the existing instance
|
|
858
|
+
this.editor.setOptions(this.options.current);
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
// When the editor:
|
|
862
|
+
// - does not yet exist
|
|
863
|
+
// - is destroyed
|
|
864
|
+
// - the deps array changes
|
|
865
|
+
// We need to destroy the editor instance and re-initialize it
|
|
866
|
+
this.refreshEditorInstance(deps);
|
|
767
867
|
}
|
|
868
|
+
return () => {
|
|
869
|
+
this.isComponentMounted = false;
|
|
870
|
+
this.scheduleDestroy();
|
|
871
|
+
};
|
|
768
872
|
};
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Recreate the editor instance if the dependencies have changed.
|
|
876
|
+
*/
|
|
877
|
+
refreshEditorInstance(deps) {
|
|
878
|
+
if (this.editor && !this.editor.isDestroyed) {
|
|
879
|
+
// Editor instance already exists
|
|
880
|
+
if (this.previousDeps === null) {
|
|
881
|
+
// If lastDeps has not yet been initialized, reuse the current editor instance
|
|
882
|
+
this.previousDeps = deps;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const depsAreEqual = this.previousDeps.length === deps.length
|
|
886
|
+
&& this.previousDeps.every((dep, index) => dep === deps[index]);
|
|
887
|
+
if (depsAreEqual) {
|
|
888
|
+
// deps exist and are equal, no need to recreate
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
774
891
|
}
|
|
775
|
-
if (
|
|
776
|
-
//
|
|
777
|
-
|
|
778
|
-
editorInstance.setOptions(options);
|
|
779
|
-
return () => destroyUnusedEditor(editorInstance);
|
|
892
|
+
if (this.editor && !this.editor.isDestroyed) {
|
|
893
|
+
// Destroy the editor instance if it exists
|
|
894
|
+
this.editor.destroy();
|
|
780
895
|
}
|
|
781
|
-
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
896
|
+
this.setEditor(this.createEditor());
|
|
897
|
+
// Update the lastDeps to the current deps
|
|
898
|
+
this.previousDeps = deps;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Schedule the destruction of the editor instance.
|
|
902
|
+
* This will only destroy the editor if it was not mounted on the next tick.
|
|
903
|
+
* This is to avoid destroying the editor instance when it's actually still mounted.
|
|
904
|
+
*/
|
|
905
|
+
scheduleDestroy() {
|
|
906
|
+
const currentInstanceId = this.instanceId;
|
|
907
|
+
const currentEditor = this.editor;
|
|
908
|
+
// Wait a tick to see if the component is still mounted
|
|
909
|
+
this.scheduledDestructionTimeout = setTimeout(() => {
|
|
910
|
+
if (this.isComponentMounted && this.instanceId === currentInstanceId) {
|
|
911
|
+
// If still mounted on the next tick, with the same instanceId, do not destroy the editor
|
|
912
|
+
if (currentEditor) {
|
|
913
|
+
// just re-apply options as they might have changed
|
|
914
|
+
currentEditor.setOptions(this.options.current);
|
|
915
|
+
}
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (currentEditor && !currentEditor.isDestroyed) {
|
|
919
|
+
currentEditor.destroy();
|
|
920
|
+
if (this.instanceId === currentInstanceId) {
|
|
921
|
+
this.setEditor(null);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}, 0);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function useEditor(options = {}, deps = []) {
|
|
928
|
+
const mostRecentOptions = React.useRef(options);
|
|
929
|
+
mostRecentOptions.current = options;
|
|
930
|
+
const [instanceManager] = React.useState(() => new EditorInstanceManager(mostRecentOptions));
|
|
931
|
+
const editor = shimExports.useSyncExternalStore(instanceManager.subscribe, instanceManager.getEditor, instanceManager.getServerSnapshot);
|
|
932
|
+
React.useDebugValue(editor);
|
|
933
|
+
// This effect will handle creating/updating the editor instance
|
|
934
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
935
|
+
React.useEffect(instanceManager.onRender(deps));
|
|
789
936
|
// The default behavior is to re-render on each transaction
|
|
790
937
|
// This is legacy behavior that will be removed in future versions
|
|
791
938
|
useEditorState({
|
|
@@ -861,6 +1008,13 @@ const BubbleMenu = (props) => {
|
|
|
861
1008
|
return (React__default.default.createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
|
|
862
1009
|
};
|
|
863
1010
|
|
|
1011
|
+
class Editor extends core.Editor {
|
|
1012
|
+
constructor() {
|
|
1013
|
+
super(...arguments);
|
|
1014
|
+
this.contentComponent = null;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
864
1018
|
const FloatingMenu = (props) => {
|
|
865
1019
|
const [element, setElement] = React.useState(null);
|
|
866
1020
|
const { editor: currentEditor } = useCurrentEditor();
|
|
@@ -912,7 +1066,9 @@ const NodeViewContent = props => {
|
|
|
912
1066
|
const NodeViewWrapper = React__default.default.forwardRef((props, ref) => {
|
|
913
1067
|
const { onDragStart } = useReactNodeView();
|
|
914
1068
|
const Tag = props.as || 'div';
|
|
915
|
-
return (
|
|
1069
|
+
return (
|
|
1070
|
+
// @ts-ignore
|
|
1071
|
+
React__default.default.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
|
|
916
1072
|
whiteSpace: 'normal',
|
|
917
1073
|
...props.style,
|
|
918
1074
|
} }));
|
|
@@ -966,7 +1122,16 @@ class ReactRenderer {
|
|
|
966
1122
|
this.element.setAttribute(key, attrs[key]);
|
|
967
1123
|
});
|
|
968
1124
|
}
|
|
969
|
-
this.
|
|
1125
|
+
if (this.editor.isInitialized) {
|
|
1126
|
+
// On first render, we need to flush the render synchronously
|
|
1127
|
+
// Renders afterwards can be async, but this fixes a cursor positioning issue
|
|
1128
|
+
ReactDOM.flushSync(() => {
|
|
1129
|
+
this.render();
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
this.render();
|
|
1134
|
+
}
|
|
970
1135
|
}
|
|
971
1136
|
render() {
|
|
972
1137
|
var _a, _b;
|
|
@@ -977,7 +1142,7 @@ class ReactRenderer {
|
|
|
977
1142
|
this.ref = ref;
|
|
978
1143
|
};
|
|
979
1144
|
}
|
|
980
|
-
this.reactElement = React__default.default.createElement(Component,
|
|
1145
|
+
this.reactElement = React__default.default.createElement(Component, props);
|
|
981
1146
|
(_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) === null || _b === void 0 ? void 0 : _b.setRenderer(this.id, this);
|
|
982
1147
|
}
|
|
983
1148
|
updateProps(props = {}) {
|
|
@@ -1011,18 +1176,19 @@ class ReactNodeView extends core.NodeView {
|
|
|
1011
1176
|
};
|
|
1012
1177
|
this.component.displayName = capitalizeFirstChar(this.extension.name);
|
|
1013
1178
|
}
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
element.appendChild(this.contentDOMElement);
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
return (React__default.default.createElement(React__default.default.Fragment, null,
|
|
1023
|
-
React__default.default.createElement(ReactNodeViewContext.Provider, { value: { onDragStart, nodeViewContentRef } },
|
|
1024
|
-
React__default.default.createElement(Component, { ...componentProps }))));
|
|
1179
|
+
const onDragStart = this.onDragStart.bind(this);
|
|
1180
|
+
const nodeViewContentRef = element => {
|
|
1181
|
+
if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
|
|
1182
|
+
element.appendChild(this.contentDOMElement);
|
|
1183
|
+
}
|
|
1025
1184
|
};
|
|
1185
|
+
const context = { onDragStart, nodeViewContentRef };
|
|
1186
|
+
const Component = this.component;
|
|
1187
|
+
// For performance reasons, we memoize the provider component
|
|
1188
|
+
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
1189
|
+
const ReactNodeViewProvider = React__default.default.memo(componentProps => {
|
|
1190
|
+
return (React__default.default.createElement(ReactNodeViewContext.Provider, { value: context }, React__default.default.createElement(Component, componentProps)));
|
|
1191
|
+
});
|
|
1026
1192
|
ReactNodeViewProvider.displayName = 'ReactNodeView';
|
|
1027
1193
|
if (this.node.isLeaf) {
|
|
1028
1194
|
this.contentDOMElement = null;
|