refactoring
This commit is contained in:
@@ -1,10 +1,16 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Editor } from './Editor'
|
||||||
|
|
||||||
export class PureEditorContent extends React.Component {
|
type EditorContentProps = {
|
||||||
constructor(props) {
|
editor: Editor | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PureEditorContent extends React.Component<EditorContentProps, EditorContentProps> {
|
||||||
|
editorContentRef: React.RefObject<any>
|
||||||
|
|
||||||
|
constructor(props: EditorContentProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.editorContentRef = React.createRef()
|
this.editorContentRef = React.createRef()
|
||||||
this.editorPortalRef = React.createRef()
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
editor: this.props.editor
|
editor: this.props.editor
|
||||||
@@ -1,208 +1,208 @@
|
|||||||
// @ts-nocheck
|
// // @ts-nocheck
|
||||||
import { Node, NodeViewRenderer, NodeViewRendererProps } from '@tiptap/core'
|
// import { Node, NodeViewRenderer, NodeViewRendererProps } from '@tiptap/core'
|
||||||
import { Decoration, NodeView } from 'prosemirror-view'
|
// import { Decoration, NodeView } from 'prosemirror-view'
|
||||||
import { NodeSelection } from 'prosemirror-state'
|
// import { NodeSelection } from 'prosemirror-state'
|
||||||
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
// import { Node as ProseMirrorNode } from 'prosemirror-model'
|
||||||
import React from 'react'
|
// import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
// import ReactDOM from 'react-dom'
|
||||||
import { Editor } from './Editor'
|
// import { Editor } from './Editor'
|
||||||
import { ReactRenderer } from './ReactRenderer'
|
// import { ReactRenderer } from './ReactRenderer'
|
||||||
|
|
||||||
interface ReactNodeViewRendererOptions {
|
// interface ReactNodeViewRendererOptions {
|
||||||
stopEvent: ((event: Event) => boolean) | null,
|
// stopEvent: ((event: Event) => boolean) | null,
|
||||||
update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
|
// update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
|
||||||
}
|
// }
|
||||||
|
|
||||||
class ReactNodeView implements NodeView {
|
// class ReactNodeView implements NodeView {
|
||||||
|
|
||||||
renderer!: ReactRenderer
|
// renderer!: ReactRenderer
|
||||||
|
|
||||||
editor: Editor
|
// editor: Editor
|
||||||
|
|
||||||
extension!: Node
|
// extension!: Node
|
||||||
|
|
||||||
node!: ProseMirrorNode
|
// node!: ProseMirrorNode
|
||||||
|
|
||||||
decorations!: Decoration[]
|
// decorations!: Decoration[]
|
||||||
|
|
||||||
getPos!: any
|
// getPos!: any
|
||||||
|
|
||||||
isDragging = false
|
// isDragging = false
|
||||||
|
|
||||||
options: ReactNodeViewRendererOptions = {
|
// options: ReactNodeViewRendererOptions = {
|
||||||
stopEvent: null,
|
// stopEvent: null,
|
||||||
update: null,
|
// update: null,
|
||||||
}
|
// }
|
||||||
|
|
||||||
constructor(component: any, props: NodeViewRendererProps, options?: Partial<ReactNodeViewRendererOptions>) {
|
// constructor(component: any, props: NodeViewRendererProps, options?: Partial<ReactNodeViewRendererOptions>) {
|
||||||
this.options = { ...this.options, ...options }
|
// this.options = { ...this.options, ...options }
|
||||||
this.editor = props.editor as Editor
|
// this.editor = props.editor as Editor
|
||||||
this.extension = props.extension
|
// this.extension = props.extension
|
||||||
this.node = props.node
|
// this.node = props.node
|
||||||
this.getPos = props.getPos
|
// this.getPos = props.getPos
|
||||||
this.mount(component)
|
// this.mount(component)
|
||||||
}
|
// }
|
||||||
|
|
||||||
mount(component: any) {
|
// mount(component: any) {
|
||||||
const props = {}
|
// const props = {}
|
||||||
|
|
||||||
if (!component.displayName) {
|
// if (!component.displayName) {
|
||||||
component.displayName = this.extension.config.name
|
// component.displayName = this.extension.config.name
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.renderer = new ReactRenderer(component, {
|
// this.renderer = new ReactRenderer(component, {
|
||||||
editor: this.editor,
|
// editor: this.editor,
|
||||||
props,
|
// props,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
get dom() {
|
// get dom() {
|
||||||
// if (!this.renderer.element) {
|
// // if (!this.renderer.element) {
|
||||||
// return null
|
// // return null
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// if (!this.renderer.element.hasAttribute('data-node-view-wrapper')) {
|
// // if (!this.renderer.element.hasAttribute('data-node-view-wrapper')) {
|
||||||
// throw Error('Please use the NodeViewWrapper component for your node view.')
|
// // throw Error('Please use the NodeViewWrapper component for your node view.')
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
return this.renderer.element
|
// return this.renderer.element
|
||||||
}
|
// }
|
||||||
|
|
||||||
get contentDOM() {
|
// get contentDOM() {
|
||||||
// console.log(this.dom)
|
// // console.log(this.dom)
|
||||||
// return null
|
// // return null
|
||||||
// if (!this.renderer.element) {
|
// // if (!this.renderer.element) {
|
||||||
// return null
|
// // return null
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
const hasContent = !this.node.type.isAtom
|
// const hasContent = !this.node.type.isAtom
|
||||||
|
|
||||||
if (!hasContent) {
|
// if (!hasContent) {
|
||||||
return null
|
// return null
|
||||||
}
|
// }
|
||||||
|
|
||||||
const contentElement = this.dom.querySelector('[data-node-view-content]')
|
// const contentElement = this.dom.querySelector('[data-node-view-content]')
|
||||||
|
|
||||||
return contentElement || this.dom
|
// return contentElement || this.dom
|
||||||
}
|
// }
|
||||||
|
|
||||||
stopEvent(event: Event) {
|
// stopEvent(event: Event) {
|
||||||
if (typeof this.options.stopEvent === 'function') {
|
// if (typeof this.options.stopEvent === 'function') {
|
||||||
return this.options.stopEvent(event)
|
// return this.options.stopEvent(event)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const target = (event.target as HTMLElement)
|
// const target = (event.target as HTMLElement)
|
||||||
const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
|
// const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
|
||||||
|
|
||||||
// ignore all events from child nodes
|
// // ignore all events from child nodes
|
||||||
if (!isInElement) {
|
// if (!isInElement) {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
const { isEditable } = this.editor
|
// const { isEditable } = this.editor
|
||||||
const { isDragging } = this
|
// const { isDragging } = this
|
||||||
const isDraggable = !!this.node.type.spec.draggable
|
// const isDraggable = !!this.node.type.spec.draggable
|
||||||
const isSelectable = NodeSelection.isSelectable(this.node)
|
// const isSelectable = NodeSelection.isSelectable(this.node)
|
||||||
const isCopyEvent = event.type === 'copy'
|
// const isCopyEvent = event.type === 'copy'
|
||||||
const isPasteEvent = event.type === 'paste'
|
// const isPasteEvent = event.type === 'paste'
|
||||||
const isCutEvent = event.type === 'cut'
|
// const isCutEvent = event.type === 'cut'
|
||||||
const isClickEvent = event.type === 'mousedown'
|
// const isClickEvent = event.type === 'mousedown'
|
||||||
const isDragEvent = event.type.startsWith('drag') || event.type === 'drop'
|
// const isDragEvent = event.type.startsWith('drag') || event.type === 'drop'
|
||||||
|
|
||||||
// ProseMirror tries to drag selectable nodes
|
// // ProseMirror tries to drag selectable nodes
|
||||||
// even if `draggable` is set to `false`
|
// // even if `draggable` is set to `false`
|
||||||
// this fix prevents that
|
// // this fix prevents that
|
||||||
if (!isDraggable && isSelectable && isDragEvent) {
|
// if (!isDraggable && isSelectable && isDragEvent) {
|
||||||
event.preventDefault()
|
// event.preventDefault()
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (isDraggable && isDragEvent && !isDragging) {
|
// if (isDraggable && isDragEvent && !isDragging) {
|
||||||
event.preventDefault()
|
// event.preventDefault()
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
// we have to store that dragging started
|
// // we have to store that dragging started
|
||||||
if (isDraggable && isEditable && !isDragging && isClickEvent) {
|
// if (isDraggable && isEditable && !isDragging && isClickEvent) {
|
||||||
const dragHandle = target.closest('[data-drag-handle]')
|
// const dragHandle = target.closest('[data-drag-handle]')
|
||||||
const isValidDragHandle = dragHandle
|
// const isValidDragHandle = dragHandle
|
||||||
&& (this.dom === dragHandle || (this.dom.contains(dragHandle)))
|
// && (this.dom === dragHandle || (this.dom.contains(dragHandle)))
|
||||||
|
|
||||||
if (isValidDragHandle) {
|
// if (isValidDragHandle) {
|
||||||
this.isDragging = true
|
// this.isDragging = true
|
||||||
document.addEventListener('dragend', () => {
|
// document.addEventListener('dragend', () => {
|
||||||
this.isDragging = false
|
// this.isDragging = false
|
||||||
}, { once: true })
|
// }, { once: true })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// these events are handled by prosemirror
|
// // these events are handled by prosemirror
|
||||||
if (
|
// if (
|
||||||
isDragging
|
// isDragging
|
||||||
|| isCopyEvent
|
// || isCopyEvent
|
||||||
|| isPasteEvent
|
// || isPasteEvent
|
||||||
|| isCutEvent
|
// || isCutEvent
|
||||||
|| (isClickEvent && isSelectable)
|
// || (isClickEvent && isSelectable)
|
||||||
) {
|
// ) {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|
||||||
ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
|
// ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
|
||||||
if (mutation.type === 'selection') {
|
// if (mutation.type === 'selection') {
|
||||||
if (this.node.isLeaf) {
|
// if (this.node.isLeaf) {
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!this.contentDOM) {
|
// if (!this.contentDOM) {
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|
||||||
const contentDOMHasChanged = !this.contentDOM.contains(mutation.target)
|
// const contentDOMHasChanged = !this.contentDOM.contains(mutation.target)
|
||||||
|| this.contentDOM === mutation.target
|
// || this.contentDOM === mutation.target
|
||||||
|
|
||||||
return contentDOMHasChanged
|
// return contentDOMHasChanged
|
||||||
}
|
// }
|
||||||
|
|
||||||
destroy() {
|
// destroy() {
|
||||||
this.renderer.destroy()
|
// this.renderer.destroy()
|
||||||
}
|
// }
|
||||||
|
|
||||||
update(node: ProseMirrorNode, decorations: Decoration[]) {
|
// update(node: ProseMirrorNode, decorations: Decoration[]) {
|
||||||
if (typeof this.options.update === 'function') {
|
// if (typeof this.options.update === 'function') {
|
||||||
return this.options.update(node, decorations)
|
// return this.options.update(node, decorations)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (node.type !== this.node.type) {
|
// if (node.type !== this.node.type) {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (node === this.node && this.decorations === decorations) {
|
// if (node === this.node && this.decorations === decorations) {
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.node = node
|
// this.node = node
|
||||||
this.decorations = decorations
|
// this.decorations = decorations
|
||||||
// this.renderer.updateProps({ node, decorations })
|
// // this.renderer.updateProps({ node, decorations })
|
||||||
this.renderer.render()
|
// this.renderer.render()
|
||||||
|
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer {
|
// export function ReactNodeViewRenderer(component: any, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer {
|
||||||
return (props: NodeViewRendererProps) => {
|
// return (props: NodeViewRendererProps) => {
|
||||||
// try to get the parent component
|
// // try to get the parent component
|
||||||
// this is important for vue devtools to show the component hierarchy correctly
|
// // this is important for vue devtools to show the component hierarchy correctly
|
||||||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
// // maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
||||||
if (!(props.editor as Editor).contentComponent) {
|
// if (!(props.editor as Editor).contentComponent) {
|
||||||
return {}
|
// return {}
|
||||||
}
|
// }
|
||||||
|
|
||||||
return new ReactNodeView(component, props, options) as NodeView
|
// return new ReactNodeView(component, props, options) as NodeView
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,58 +1,58 @@
|
|||||||
// @ts-nocheck
|
// // @ts-nocheck
|
||||||
|
|
||||||
import React from 'react'
|
// import React from 'react'
|
||||||
import { render, unmountComponentAtNode } from 'react-dom'
|
// import { render, unmountComponentAtNode } from 'react-dom'
|
||||||
|
|
||||||
import { Editor } from './Editor'
|
// import { Editor } from './Editor'
|
||||||
|
|
||||||
export interface VueRendererOptions {
|
// export interface VueRendererOptions {
|
||||||
as?: string;
|
// as?: string;
|
||||||
editor: Editor;
|
// editor: Editor;
|
||||||
props?: { [key: string]: any };
|
// props?: { [key: string]: any };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class ReactRenderer {
|
// export class ReactRenderer {
|
||||||
id: string
|
// id: string
|
||||||
|
|
||||||
editor: Editor
|
// editor: Editor
|
||||||
|
|
||||||
component: any
|
// component: any
|
||||||
|
|
||||||
teleportElement: Element
|
// teleportElement: Element
|
||||||
|
|
||||||
element: Element
|
// element: Element
|
||||||
|
|
||||||
props: { [key: string]: any }
|
// props: { [key: string]: any }
|
||||||
|
|
||||||
constructor(component: any, { props = {}, editor }: VueRendererOptions) {
|
// constructor(component: any, { props = {}, editor }: VueRendererOptions) {
|
||||||
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
// this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
||||||
this.component = component
|
// this.component = component
|
||||||
this.editor = editor
|
// this.editor = editor
|
||||||
this.props = props
|
// this.props = props
|
||||||
|
|
||||||
this.teleportElement = document.createElement('div')
|
// this.teleportElement = document.createElement('div')
|
||||||
// this.teleportElement.setAttribute('data-bla', '')
|
// // this.teleportElement.setAttribute('data-bla', '')
|
||||||
// render(React.createElement(component), this.teleportElement)
|
// // render(React.createElement(component), this.teleportElement)
|
||||||
// render(React.createElement(component), this.teleportElement)
|
// // render(React.createElement(component), this.teleportElement)
|
||||||
this.render()
|
// this.render()
|
||||||
// this.element = this.teleportElement.firstElementChild as Element
|
// // this.element = this.teleportElement.firstElementChild as Element
|
||||||
this.element = this.teleportElement
|
// this.element = this.teleportElement
|
||||||
}
|
// }
|
||||||
|
|
||||||
render() {
|
// render() {
|
||||||
render(React.createElement(this.component), this.teleportElement)
|
// render(React.createElement(this.component), this.teleportElement)
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateProps(props: { [key: string]: any } = {}) {
|
// updateProps(props: { [key: string]: any } = {}) {
|
||||||
// TODO
|
// // TODO
|
||||||
}
|
// }
|
||||||
|
|
||||||
destroy() {
|
// destroy() {
|
||||||
// TODO
|
// // TODO
|
||||||
// console.log('DESTROY', { bla: this.teleportElement })
|
// // console.log('DESTROY', { bla: this.teleportElement })
|
||||||
// console.log(document.querySelector('[data-bla]'))
|
// // console.log(document.querySelector('[data-bla]'))
|
||||||
unmountComponentAtNode(this.teleportElement)
|
// unmountComponentAtNode(this.teleportElement)
|
||||||
// unmountComponentAtNode(document.querySelector('[data-bla]'))
|
// // unmountComponentAtNode(document.querySelector('[data-bla]'))
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { EditorOptions } from '@tiptap/core'
|
|||||||
import { Editor } from './Editor'
|
import { Editor } from './Editor'
|
||||||
|
|
||||||
function useForceUpdate() {
|
function useForceUpdate() {
|
||||||
const [_, setValue] = useState(0)
|
const [, setValue] = useState(0)
|
||||||
|
|
||||||
return () => setValue(value => value + 1)
|
return () => setValue(value => value + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"target": "es2019",
|
"target": "es2019",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user