@tiptap/react 2.0.0-beta.21 → 2.0.0-beta.210
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/README.md +2 -2
- package/dist/index.cjs +437 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +437 -0
- package/package.json +45 -16
- package/src/BubbleMenu.tsx +32 -16
- package/src/Editor.ts +9 -2
- package/src/EditorContent.tsx +55 -12
- package/src/FloatingMenu.tsx +36 -14
- package/src/NodeViewContent.tsx +10 -3
- package/src/NodeViewWrapper.tsx +11 -8
- package/src/ReactNodeViewRenderer.tsx +82 -34
- package/src/ReactRenderer.tsx +38 -29
- package/src/index.ts +6 -6
- package/src/useEditor.ts +16 -4
- package/src/useReactNodeView.ts +1 -0
- package/CHANGELOG.md +0 -190
- package/LICENSE.md +0 -21
- package/dist/packages/react/src/BubbleMenu.d.ts +0 -6
- package/dist/packages/react/src/Editor.d.ts +0 -6
- package/dist/packages/react/src/EditorContent.d.ts +0 -19
- package/dist/packages/react/src/FloatingMenu.d.ts +0 -6
- package/dist/packages/react/src/NodeViewContent.d.ts +0 -6
- package/dist/packages/react/src/NodeViewWrapper.d.ts +0 -6
- package/dist/packages/react/src/ReactNodeViewRenderer.d.ts +0 -9
- package/dist/packages/react/src/ReactRenderer.d.ts +0 -21
- package/dist/packages/react/src/index.d.ts +0 -10
- package/dist/packages/react/src/useEditor.d.ts +0 -3
- package/dist/packages/react/src/useReactNodeView.d.ts +0 -6
- package/dist/tiptap-react.bundle.umd.min.js +0 -54
- package/dist/tiptap-react.bundle.umd.min.js.map +0 -1
- package/dist/tiptap-react.cjs.js +0 -318
- package/dist/tiptap-react.cjs.js.map +0 -1
- package/dist/tiptap-react.esm.js +0 -293
- package/dist/tiptap-react.esm.js.map +0 -1
- package/dist/tiptap-react.umd.js +0 -318
- package/dist/tiptap-react.umd.js.map +0 -1
package/src/EditorContent.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import ReactDOM from 'react-dom'
|
|
1
|
+
import React, { HTMLProps } from 'react'
|
|
2
|
+
import ReactDOM, { flushSync } from 'react-dom'
|
|
3
|
+
|
|
3
4
|
import { Editor } from './Editor'
|
|
4
5
|
import { ReactRenderer } from './ReactRenderer'
|
|
5
6
|
|
|
6
|
-
const Portals: React.FC<{ renderers:
|
|
7
|
+
const Portals: React.FC<{ renderers: Record<string, ReactRenderer> }> = ({ renderers }) => {
|
|
7
8
|
return (
|
|
8
9
|
<>
|
|
9
|
-
{
|
|
10
|
+
{Object.entries(renderers).map(([key, renderer]) => {
|
|
10
11
|
return ReactDOM.createPortal(
|
|
11
12
|
renderer.reactElement,
|
|
12
13
|
renderer.element,
|
|
@@ -17,23 +18,26 @@ const Portals: React.FC<{ renderers: Map<string, ReactRenderer> }> = ({ renderer
|
|
|
17
18
|
)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export interface EditorContentProps {
|
|
21
|
+
export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
|
|
21
22
|
editor: Editor | null,
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface EditorContentState {
|
|
25
|
-
renderers:
|
|
26
|
+
renderers: Record<string, ReactRenderer>
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
|
|
29
30
|
editorContentRef: React.RefObject<any>
|
|
30
31
|
|
|
32
|
+
initialized: boolean
|
|
33
|
+
|
|
31
34
|
constructor(props: EditorContentProps) {
|
|
32
35
|
super(props)
|
|
33
36
|
this.editorContentRef = React.createRef()
|
|
37
|
+
this.initialized = false
|
|
34
38
|
|
|
35
39
|
this.state = {
|
|
36
|
-
renderers:
|
|
40
|
+
renderers: {},
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -55,7 +59,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
55
59
|
|
|
56
60
|
const element = this.editorContentRef.current
|
|
57
61
|
|
|
58
|
-
element.
|
|
62
|
+
element.append(...editor.options.element.childNodes)
|
|
59
63
|
|
|
60
64
|
editor.setOptions({
|
|
61
65
|
element,
|
|
@@ -63,11 +67,47 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
63
67
|
|
|
64
68
|
editor.contentComponent = this
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
editor.createNodeViews()
|
|
71
|
+
|
|
72
|
+
this.initialized = true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
maybeFlushSync(fn: () => void) {
|
|
77
|
+
// Avoid calling flushSync until the editor is initialized.
|
|
78
|
+
// Initialization happens during the componentDidMount or componentDidUpdate
|
|
79
|
+
// lifecycle methods, and React doesn't allow calling flushSync from inside
|
|
80
|
+
// a lifecycle method.
|
|
81
|
+
if (this.initialized) {
|
|
82
|
+
flushSync(fn)
|
|
83
|
+
} else {
|
|
84
|
+
fn()
|
|
68
85
|
}
|
|
69
86
|
}
|
|
70
87
|
|
|
88
|
+
setRenderer(id: string, renderer: ReactRenderer) {
|
|
89
|
+
this.maybeFlushSync(() => {
|
|
90
|
+
this.setState(({ renderers }) => ({
|
|
91
|
+
renderers: {
|
|
92
|
+
...renderers,
|
|
93
|
+
[id]: renderer,
|
|
94
|
+
},
|
|
95
|
+
}))
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
removeRenderer(id: string) {
|
|
100
|
+
this.maybeFlushSync(() => {
|
|
101
|
+
this.setState(({ renderers }) => {
|
|
102
|
+
const nextRenderers = { ...renderers }
|
|
103
|
+
|
|
104
|
+
delete nextRenderers[id]
|
|
105
|
+
|
|
106
|
+
return { renderers: nextRenderers }
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
71
111
|
componentWillUnmount() {
|
|
72
112
|
const { editor } = this.props
|
|
73
113
|
|
|
@@ -89,7 +129,7 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
89
129
|
|
|
90
130
|
const newElement = document.createElement('div')
|
|
91
131
|
|
|
92
|
-
newElement.
|
|
132
|
+
newElement.append(...editor.options.element.childNodes)
|
|
93
133
|
|
|
94
134
|
editor.setOptions({
|
|
95
135
|
element: newElement,
|
|
@@ -97,9 +137,12 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
|
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
render() {
|
|
140
|
+
const { editor, ...rest } = this.props
|
|
141
|
+
|
|
100
142
|
return (
|
|
101
143
|
<>
|
|
102
|
-
<div ref={this.editorContentRef} />
|
|
144
|
+
<div ref={this.editorContentRef} {...rest} />
|
|
145
|
+
{/* @ts-ignore */}
|
|
103
146
|
<Portals renderers={this.state.renderers} />
|
|
104
147
|
</>
|
|
105
148
|
)
|
package/src/FloatingMenu.tsx
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
|
|
2
|
+
import React, {
|
|
3
|
+
useEffect, useState,
|
|
4
|
+
} from 'react'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
7
|
+
|
|
8
|
+
export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'> & {
|
|
5
9
|
className?: string,
|
|
10
|
+
children: React.ReactNode
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
export const FloatingMenu
|
|
9
|
-
const element =
|
|
13
|
+
export const FloatingMenu = (props: FloatingMenuProps) => {
|
|
14
|
+
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
10
15
|
|
|
11
16
|
useEffect(() => {
|
|
12
|
-
|
|
17
|
+
if (!element) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (props.editor.isDestroyed) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
const {
|
|
26
|
+
pluginKey = 'floatingMenu',
|
|
15
27
|
editor,
|
|
16
|
-
|
|
28
|
+
tippyOptions = {},
|
|
29
|
+
shouldShow = null,
|
|
30
|
+
} = props
|
|
31
|
+
|
|
32
|
+
const plugin = FloatingMenuPlugin({
|
|
33
|
+
pluginKey,
|
|
34
|
+
editor,
|
|
35
|
+
element,
|
|
17
36
|
tippyOptions,
|
|
18
|
-
|
|
37
|
+
shouldShow,
|
|
38
|
+
})
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
editor.registerPlugin(plugin)
|
|
41
|
+
return () => editor.unregisterPlugin(pluginKey)
|
|
42
|
+
}, [
|
|
43
|
+
props.editor,
|
|
44
|
+
element,
|
|
45
|
+
])
|
|
24
46
|
|
|
25
47
|
return (
|
|
26
|
-
<div ref={
|
|
48
|
+
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
|
27
49
|
{props.children}
|
|
28
50
|
</div>
|
|
29
51
|
)
|
package/src/NodeViewContent.tsx
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import { useReactNodeView } from './useReactNodeView'
|
|
4
|
+
|
|
3
5
|
export interface NodeViewContentProps {
|
|
4
|
-
|
|
6
|
+
[key: string]: any,
|
|
5
7
|
as?: React.ElementType,
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export const NodeViewContent: React.FC<NodeViewContentProps> = props => {
|
|
9
11
|
const Tag = props.as || 'div'
|
|
12
|
+
const { nodeViewContentRef } = useReactNodeView()
|
|
10
13
|
|
|
11
14
|
return (
|
|
12
15
|
<Tag
|
|
13
|
-
|
|
16
|
+
{...props}
|
|
17
|
+
ref={nodeViewContentRef}
|
|
14
18
|
data-node-view-content=""
|
|
15
|
-
style={{
|
|
19
|
+
style={{
|
|
20
|
+
whiteSpace: 'pre-wrap',
|
|
21
|
+
...props.style,
|
|
22
|
+
}}
|
|
16
23
|
/>
|
|
17
24
|
)
|
|
18
25
|
}
|
package/src/NodeViewWrapper.tsx
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
|
|
2
3
|
import { useReactNodeView } from './useReactNodeView'
|
|
3
4
|
|
|
4
5
|
export interface NodeViewWrapperProps {
|
|
5
|
-
|
|
6
|
+
[key: string]: any,
|
|
6
7
|
as?: React.ElementType,
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = props => {
|
|
10
|
+
export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = React.forwardRef((props, ref) => {
|
|
10
11
|
const { onDragStart } = useReactNodeView()
|
|
11
12
|
const Tag = props.as || 'div'
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
15
|
<Tag
|
|
15
|
-
|
|
16
|
+
{...props}
|
|
17
|
+
ref={ref}
|
|
16
18
|
data-node-view-wrapper=""
|
|
17
19
|
onDragStart={onDragStart}
|
|
18
|
-
style={{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
style={{
|
|
21
|
+
whiteSpace: 'normal',
|
|
22
|
+
...props.style,
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
22
25
|
)
|
|
23
|
-
}
|
|
26
|
+
})
|
|
@@ -1,26 +1,40 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
1
|
import {
|
|
3
2
|
NodeView,
|
|
4
3
|
NodeViewProps,
|
|
5
4
|
NodeViewRenderer,
|
|
5
|
+
NodeViewRendererOptions,
|
|
6
6
|
NodeViewRendererProps,
|
|
7
7
|
} from '@tiptap/core'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
9
|
+
import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
10
|
+
import React from 'react'
|
|
11
|
+
|
|
10
12
|
import { Editor } from './Editor'
|
|
11
13
|
import { ReactRenderer } from './ReactRenderer'
|
|
12
|
-
import { ReactNodeViewContext } from './useReactNodeView'
|
|
13
|
-
|
|
14
|
-
interface ReactNodeViewRendererOptions {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import { ReactNodeViewContext, ReactNodeViewContextProps } from './useReactNodeView'
|
|
15
|
+
|
|
16
|
+
export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
17
|
+
update:
|
|
18
|
+
| ((props: {
|
|
19
|
+
oldNode: ProseMirrorNode
|
|
20
|
+
oldDecorations: Decoration[]
|
|
21
|
+
newNode: ProseMirrorNode
|
|
22
|
+
newDecorations: Decoration[]
|
|
23
|
+
updateProps: () => void
|
|
24
|
+
}) => boolean)
|
|
25
|
+
| null
|
|
26
|
+
as?: string
|
|
27
|
+
className?: string
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
class ReactNodeView extends NodeView<
|
|
20
|
-
|
|
30
|
+
class ReactNodeView extends NodeView<
|
|
31
|
+
React.FunctionComponent,
|
|
32
|
+
Editor,
|
|
33
|
+
ReactNodeViewRendererOptions
|
|
34
|
+
> {
|
|
21
35
|
renderer!: ReactRenderer
|
|
22
36
|
|
|
23
|
-
contentDOMElement!:
|
|
37
|
+
contentDOMElement!: HTMLElement | null
|
|
24
38
|
|
|
25
39
|
mount() {
|
|
26
40
|
const props: NodeViewProps = {
|
|
@@ -31,6 +45,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
31
45
|
extension: this.extension,
|
|
32
46
|
getPos: () => this.getPos(),
|
|
33
47
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
48
|
+
deleteNode: () => this.deleteNode(),
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
if (!(this.component as any).displayName) {
|
|
@@ -42,13 +57,22 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
|
|
45
|
-
const onDragStart = this.onDragStart.bind(this)
|
|
46
60
|
const Component = this.component
|
|
61
|
+
const onDragStart = this.onDragStart.bind(this)
|
|
62
|
+
const nodeViewContentRef: ReactNodeViewContextProps['nodeViewContentRef'] = element => {
|
|
63
|
+
if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
|
|
64
|
+
element.appendChild(this.contentDOMElement)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
47
67
|
|
|
48
68
|
return (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
<>
|
|
70
|
+
{/* @ts-ignore */}
|
|
71
|
+
<ReactNodeViewContext.Provider value={{ onDragStart, nodeViewContentRef }}>
|
|
72
|
+
{/* @ts-ignore */}
|
|
73
|
+
<Component {...componentProps} />
|
|
74
|
+
</ReactNodeViewContext.Provider>
|
|
75
|
+
</>
|
|
52
76
|
)
|
|
53
77
|
}
|
|
54
78
|
|
|
@@ -58,12 +82,26 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
58
82
|
? null
|
|
59
83
|
: document.createElement(this.node.isInline ? 'span' : 'div')
|
|
60
84
|
|
|
85
|
+
if (this.contentDOMElement) {
|
|
86
|
+
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
87
|
+
// With this fix it seems to work fine
|
|
88
|
+
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
89
|
+
this.contentDOMElement.style.whiteSpace = 'inherit'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let as = this.node.isInline ? 'span' : 'div'
|
|
93
|
+
|
|
94
|
+
if (this.options.as) {
|
|
95
|
+
as = this.options.as
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { className = '' } = this.options
|
|
99
|
+
|
|
61
100
|
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
|
62
101
|
editor: this.editor,
|
|
63
102
|
props,
|
|
64
|
-
as
|
|
65
|
-
|
|
66
|
-
: 'div',
|
|
103
|
+
as,
|
|
104
|
+
className: `node-${this.node.type.name} ${className}`.trim(),
|
|
67
105
|
})
|
|
68
106
|
}
|
|
69
107
|
|
|
@@ -75,7 +113,7 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
75
113
|
throw Error('Please use the NodeViewWrapper component for your node view.')
|
|
76
114
|
}
|
|
77
115
|
|
|
78
|
-
return this.renderer.element
|
|
116
|
+
return this.renderer.element as HTMLElement
|
|
79
117
|
}
|
|
80
118
|
|
|
81
119
|
get contentDOM() {
|
|
@@ -83,35 +121,42 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
83
121
|
return null
|
|
84
122
|
}
|
|
85
123
|
|
|
86
|
-
const contentElement = this.dom.querySelector('[data-node-view-content]')
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
this.contentDOMElement
|
|
90
|
-
&& contentElement
|
|
91
|
-
&& !contentElement.contains(this.contentDOMElement)
|
|
92
|
-
) {
|
|
93
|
-
contentElement.appendChild(this.contentDOMElement)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
124
|
return this.contentDOMElement
|
|
97
125
|
}
|
|
98
126
|
|
|
99
127
|
update(node: ProseMirrorNode, decorations: Decoration[]) {
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
const updateProps = (props?: Record<string, any>) => {
|
|
129
|
+
this.renderer.updateProps(props)
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
if (node.type !== this.node.type) {
|
|
105
133
|
return false
|
|
106
134
|
}
|
|
107
135
|
|
|
136
|
+
if (typeof this.options.update === 'function') {
|
|
137
|
+
const oldNode = this.node
|
|
138
|
+
const oldDecorations = this.decorations
|
|
139
|
+
|
|
140
|
+
this.node = node
|
|
141
|
+
this.decorations = decorations
|
|
142
|
+
|
|
143
|
+
return this.options.update({
|
|
144
|
+
oldNode,
|
|
145
|
+
oldDecorations,
|
|
146
|
+
newNode: node,
|
|
147
|
+
newDecorations: decorations,
|
|
148
|
+
updateProps: () => updateProps({ node, decorations }),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
108
152
|
if (node === this.node && this.decorations === decorations) {
|
|
109
153
|
return true
|
|
110
154
|
}
|
|
111
155
|
|
|
112
156
|
this.node = node
|
|
113
157
|
this.decorations = decorations
|
|
114
|
-
|
|
158
|
+
|
|
159
|
+
updateProps({ node, decorations })
|
|
115
160
|
|
|
116
161
|
return true
|
|
117
162
|
}
|
|
@@ -134,7 +179,10 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
|
|
134
179
|
}
|
|
135
180
|
}
|
|
136
181
|
|
|
137
|
-
export function ReactNodeViewRenderer(
|
|
182
|
+
export function ReactNodeViewRenderer(
|
|
183
|
+
component: any,
|
|
184
|
+
options?: Partial<ReactNodeViewRendererOptions>,
|
|
185
|
+
): NodeViewRenderer {
|
|
138
186
|
return (props: NodeViewRendererProps) => {
|
|
139
187
|
// try to get the parent component
|
|
140
188
|
// this is important for vue devtools to show the component hierarchy correctly
|
|
@@ -143,6 +191,6 @@ export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNod
|
|
|
143
191
|
return {}
|
|
144
192
|
}
|
|
145
193
|
|
|
146
|
-
return new ReactNodeView(component, props, options) as ProseMirrorNodeView
|
|
194
|
+
return new ReactNodeView(component, props, options) as unknown as ProseMirrorNodeView
|
|
147
195
|
}
|
|
148
196
|
}
|
package/src/ReactRenderer.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
1
2
|
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import { Editor } from './Editor'
|
|
3
|
+
|
|
4
|
+
import { Editor as ExtendedEditor } from './Editor'
|
|
4
5
|
|
|
5
6
|
function isClassComponent(Component: any) {
|
|
6
7
|
return !!(
|
|
@@ -10,34 +11,57 @@ function isClassComponent(Component: any) {
|
|
|
10
11
|
)
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function isForwardRefComponent(Component: any) {
|
|
15
|
+
return !!(
|
|
16
|
+
typeof Component === 'object'
|
|
17
|
+
&& Component.$$typeof?.toString() === 'Symbol(react.forward_ref)'
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export interface ReactRendererOptions {
|
|
14
22
|
editor: Editor,
|
|
15
|
-
props?:
|
|
23
|
+
props?: Record<string, any>,
|
|
16
24
|
as?: string,
|
|
25
|
+
className?: string,
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
type ComponentType<R, P> =
|
|
29
|
+
React.ComponentClass<P> |
|
|
30
|
+
React.FunctionComponent<P> |
|
|
31
|
+
React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
|
|
32
|
+
|
|
33
|
+
export class ReactRenderer<R = unknown, P = unknown> {
|
|
20
34
|
id: string
|
|
21
35
|
|
|
22
|
-
editor:
|
|
36
|
+
editor: ExtendedEditor
|
|
23
37
|
|
|
24
38
|
component: any
|
|
25
39
|
|
|
26
40
|
element: Element
|
|
27
41
|
|
|
28
|
-
props:
|
|
42
|
+
props: Record<string, any>
|
|
29
43
|
|
|
30
44
|
reactElement: React.ReactNode
|
|
31
45
|
|
|
32
|
-
ref:
|
|
46
|
+
ref: R | null = null
|
|
33
47
|
|
|
34
|
-
constructor(component:
|
|
48
|
+
constructor(component: ComponentType<R, P>, {
|
|
49
|
+
editor,
|
|
50
|
+
props = {},
|
|
51
|
+
as = 'div',
|
|
52
|
+
className = '',
|
|
53
|
+
}: ReactRendererOptions) {
|
|
35
54
|
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
|
36
55
|
this.component = component
|
|
37
|
-
this.editor = editor
|
|
56
|
+
this.editor = editor as ExtendedEditor
|
|
38
57
|
this.props = props
|
|
39
58
|
this.element = document.createElement(as)
|
|
40
59
|
this.element.classList.add('react-renderer')
|
|
60
|
+
|
|
61
|
+
if (className) {
|
|
62
|
+
this.element.classList.add(...className.split(' '))
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
this.render()
|
|
42
66
|
}
|
|
43
67
|
|
|
@@ -45,25 +69,18 @@ export class ReactRenderer {
|
|
|
45
69
|
const Component = this.component
|
|
46
70
|
const props = this.props
|
|
47
71
|
|
|
48
|
-
if (isClassComponent(Component)) {
|
|
49
|
-
props.ref = (ref:
|
|
72
|
+
if (isClassComponent(Component) || isForwardRefComponent(Component)) {
|
|
73
|
+
props.ref = (ref: R) => {
|
|
50
74
|
this.ref = ref
|
|
51
75
|
}
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
this.reactElement = <Component {...props } />
|
|
55
79
|
|
|
56
|
-
|
|
57
|
-
this.editor.contentComponent.setState({
|
|
58
|
-
renderers: this.editor.contentComponent.state.renderers.set(
|
|
59
|
-
this.id,
|
|
60
|
-
this,
|
|
61
|
-
),
|
|
62
|
-
})
|
|
63
|
-
}
|
|
80
|
+
this.editor?.contentComponent?.setRenderer(this.id, this)
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
updateProps(props:
|
|
83
|
+
updateProps(props: Record<string, any> = {}): void {
|
|
67
84
|
this.props = {
|
|
68
85
|
...this.props,
|
|
69
86
|
...props,
|
|
@@ -73,14 +90,6 @@ export class ReactRenderer {
|
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
destroy(): void {
|
|
76
|
-
|
|
77
|
-
const { renderers } = this.editor.contentComponent.state
|
|
78
|
-
|
|
79
|
-
renderers.delete(this.id)
|
|
80
|
-
|
|
81
|
-
this.editor.contentComponent.setState({
|
|
82
|
-
renderers,
|
|
83
|
-
})
|
|
84
|
-
}
|
|
93
|
+
this.editor?.contentComponent?.removeRenderer(this.id)
|
|
85
94
|
}
|
|
86
95
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export * from '@tiptap/core'
|
|
2
1
|
export * from './BubbleMenu'
|
|
3
2
|
export { Editor } from './Editor'
|
|
4
|
-
export * from './FloatingMenu'
|
|
5
|
-
export * from './useEditor'
|
|
6
|
-
export * from './ReactRenderer'
|
|
7
|
-
export * from './ReactNodeViewRenderer'
|
|
8
3
|
export * from './EditorContent'
|
|
9
|
-
export * from './
|
|
4
|
+
export * from './FloatingMenu'
|
|
10
5
|
export * from './NodeViewContent'
|
|
6
|
+
export * from './NodeViewWrapper'
|
|
7
|
+
export * from './ReactNodeViewRenderer'
|
|
8
|
+
export * from './ReactRenderer'
|
|
9
|
+
export * from './useEditor'
|
|
10
|
+
export * from '@tiptap/core'
|
package/src/useEditor.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
1
|
import { EditorOptions } from '@tiptap/core'
|
|
2
|
+
import { DependencyList, useEffect, useState } from 'react'
|
|
3
|
+
|
|
3
4
|
import { Editor } from './Editor'
|
|
4
5
|
|
|
5
6
|
function useForceUpdate() {
|
|
@@ -8,21 +9,32 @@ function useForceUpdate() {
|
|
|
8
9
|
return () => setValue(value => value + 1)
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
|
12
|
+
export const useEditor = (options: Partial<EditorOptions> = {}, deps: DependencyList = []) => {
|
|
12
13
|
const [editor, setEditor] = useState<Editor | null>(null)
|
|
13
14
|
const forceUpdate = useForceUpdate()
|
|
14
15
|
|
|
15
16
|
useEffect(() => {
|
|
17
|
+
let isMounted = true
|
|
18
|
+
|
|
16
19
|
const instance = new Editor(options)
|
|
17
20
|
|
|
18
21
|
setEditor(instance)
|
|
19
22
|
|
|
20
|
-
instance.on('transaction',
|
|
23
|
+
instance.on('transaction', () => {
|
|
24
|
+
requestAnimationFrame(() => {
|
|
25
|
+
requestAnimationFrame(() => {
|
|
26
|
+
if (isMounted) {
|
|
27
|
+
forceUpdate()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
})
|
|
21
32
|
|
|
22
33
|
return () => {
|
|
23
34
|
instance.destroy()
|
|
35
|
+
isMounted = false
|
|
24
36
|
}
|
|
25
|
-
},
|
|
37
|
+
}, deps)
|
|
26
38
|
|
|
27
39
|
return editor
|
|
28
40
|
}
|
package/src/useReactNodeView.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'
|
|
|
2
2
|
|
|
3
3
|
export interface ReactNodeViewContextProps {
|
|
4
4
|
onDragStart: (event: DragEvent) => void,
|
|
5
|
+
nodeViewContentRef: (element: HTMLElement | null) => void,
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export const ReactNodeViewContext = createContext<Partial<ReactNodeViewContextProps>>({
|