Files
tiptap/packages/react/src/ReactNodeViewRenderer.tsx
2021-05-29 20:12:21 +02:00

162 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react'
import {
NodeView,
NodeViewProps,
NodeViewRenderer,
NodeViewRendererProps,
} from '@tiptap/core'
import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
import { Node as ProseMirrorNode } from 'prosemirror-model'
import { Editor } from './Editor'
import { ReactRenderer } from './ReactRenderer'
import { ReactNodeViewContext } from './useReactNodeView'
interface ReactNodeViewRendererOptions {
stopEvent: ((event: Event) => boolean) | null,
update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
}
class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
renderer!: ReactRenderer
contentDOMElement!: HTMLElement | null
mount() {
const props: NodeViewProps = {
editor: this.editor,
node: this.node,
decorations: this.decorations,
selected: false,
extension: this.extension,
getPos: () => this.getPos(),
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
deleteNode: () => this.deleteNode(),
}
if (!(this.component as any).displayName) {
const capitalizeFirstChar = (string: string): string => {
return string.charAt(0).toUpperCase() + string.substring(1)
}
this.component.displayName = capitalizeFirstChar(this.extension.name)
}
const ReactNodeViewProvider: React.FunctionComponent = componentProps => {
const onDragStart = this.onDragStart.bind(this)
const Component = this.component
return (
<ReactNodeViewContext.Provider value={{ onDragStart }}>
<Component {...componentProps} />
</ReactNodeViewContext.Provider>
)
}
ReactNodeViewProvider.displayName = 'ReactNodeView'
this.contentDOMElement = this.node.isLeaf
? null
: document.createElement(this.node.isInline ? 'span' : 'div')
if (this.contentDOMElement) {
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
// With this fix it seems to work fine
// See: https://github.com/ueberdosis/tiptap/issues/1197
this.contentDOMElement.style.whiteSpace = 'inherit'
}
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
editor: this.editor,
props,
as: this.node.isInline
? 'span'
: 'div',
})
}
get dom() {
if (
this.renderer.element.firstElementChild
&& !this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper')
) {
throw Error('Please use the NodeViewWrapper component for your node view.')
}
return this.renderer.element
}
get contentDOM() {
if (this.node.isLeaf) {
return null
}
this.maybeMoveContentDOM()
return this.contentDOMElement
}
maybeMoveContentDOM(): void {
const contentElement = this.dom.querySelector('[data-node-view-content]')
if (
this.contentDOMElement
&& contentElement
&& !contentElement.contains(this.contentDOMElement)
) {
contentElement.appendChild(this.contentDOMElement)
}
}
update(node: ProseMirrorNode, decorations: Decoration[]) {
if (typeof this.options.update === 'function') {
return this.options.update(node, decorations)
}
if (node.type !== this.node.type) {
return false
}
if (node === this.node && this.decorations === decorations) {
return true
}
this.node = node
this.decorations = decorations
this.renderer.updateProps({ node, decorations })
this.maybeMoveContentDOM()
return true
}
selectNode() {
this.renderer.updateProps({
selected: true,
})
}
deselectNode() {
this.renderer.updateProps({
selected: false,
})
}
destroy() {
this.renderer.destroy()
this.contentDOMElement = null
}
}
export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer {
return (props: NodeViewRendererProps) => {
// try to get the parent component
// this is important for vue devtools to show the component hierarchy correctly
// maybe its `undefined` because <editor-content> isnt rendered yet
if (!(props.editor as Editor).contentComponent) {
return {}
}
return new ReactNodeView(component, props, options) as ProseMirrorNodeView
}
}