diff --git a/docs/src/demos/Examples/Default/React/index.jsx b/docs/src/demos/Examples/Default/React/index.jsx
index 3f8d89df..b54f9e66 100644
--- a/docs/src/demos/Examples/Default/React/index.jsx
+++ b/docs/src/demos/Examples/Default/React/index.jsx
@@ -1,6 +1,7 @@
import React from 'react'
-import { useEditor, EditorContent } from '@tiptap/react'
+import { useEditor, EditorContent, ReactNodeViewRenderer } from '@tiptap/react'
import { defaultExtensions } from '@tiptap/starter-kit'
+import Paragraph from '@tiptap/extension-paragraph'
import './styles.scss'
const MenuBar = ({ editor }) => {
@@ -125,38 +126,49 @@ const MenuBar = ({ editor }) => {
export default () => {
const editor = useEditor({
extensions: [
- ...defaultExtensions(),
+ Paragraph.extend({
+ addNodeView() {
+ return ReactNodeViewRenderer(({ selected }) => {
+ console.log({selected})
+ return (
+
noooode view {selected}
+ )
+ })
+ }
+ }),
+ ...defaultExtensions().filter(item => item.config.name !== 'paragraph'),
],
- content: `
-
- Hi there,
-
-
- this is a basic basic example of tiptap. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
-
-
- -
- That’s a bullet list with one …
-
- -
- … or two list items.
-
-
-
- Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
-
- body {
- display: none;
-}
-
- I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
-
-
- Wow, that’s amazing. Good work, boy! 👏
-
- — Mom
-
- `,
+ content: `test
`,
+// content: `
+//
+// Hi there,
+//
+//
+// this is a basic basic example of tiptap. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
+//
+//
+// -
+// That’s a bullet list with one …
+//
+// -
+// … or two list items.
+//
+//
+//
+// Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
+//
+// body {
+// display: none;
+// }
+//
+// I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
+//
+//
+// Wow, that’s amazing. Good work, boy! 👏
+//
+// — Mom
+//
+// `,
})
return (
diff --git a/packages/react/src/EditorContent.tsx b/packages/react/src/EditorContent.tsx
index eb14d226..cf86bd0b 100644
--- a/packages/react/src/EditorContent.tsx
+++ b/packages/react/src/EditorContent.tsx
@@ -1,11 +1,67 @@
import React from 'react'
+import ReactDOM from 'react-dom'
import { Editor } from './Editor'
type EditorContentProps = {
editor: Editor | null
}
-export class PureEditorContent extends React.Component {
+// const Portals = ({ editor }: { editor: Editor | null }) => {
+// if (!editor?.contentComponent) {
+// return null
+// }
+
+// console.log('render portals')
+
+// return (
+// portaaals
+// )
+// }
+
+const Portals = ({ renderers }: { renderers: Map }) => {
+ return (
+
+ {Array.from(renderers).map(([key, renderer]) => {
+
+ // console.log({renderer})
+ // return (
+ //
{value}
+ // )
+
+ // return React.createElement(renderer.component)
+
+
+ // return (
+ //
+ // {ReactDOM.createPortal(
+ // React.createElement(renderer.component),
+ // renderer.teleportElement,
+ // )}
+ //
+ // )
+
+ return (
+
+ {renderer.bla}
+
+ )
+
+ // return ReactDOM.createPortal(
+ // React.createElement(renderer.component),
+ // renderer.teleportElement,
+ // )
+ })}
+
+ )
+}
+
+// const Content = React.memo(({ reference }: { reference: React.RefObject }) => {
+// return (
+//
+// )
+// })
+
+export class PureEditorContent extends React.Component {
editorContentRef: React.RefObject
constructor(props: EditorContentProps) {
@@ -13,14 +69,33 @@ export class PureEditorContent extends React.Component {
+ // if (this.props?.editor?.contentComponent) {
+ // this.props.editor.contentComponent.setState({
+ // renderers: this.state.renderers.set(Math.random(), Math.random())
+ // })
+ // }
+ // }, 1000)
}
componentDidUpdate() {
const { editor } = this.props
if (editor && editor.options.element) {
+ if (editor.contentComponent) {
+ return
+ }
+
+ // this.setState({
+ // editor,
+ // })
+
+ console.log('UPDATE')
+
const element = this.editorContentRef.current
element.appendChild(editor.options.element.firstChild)
@@ -39,10 +114,20 @@ export class PureEditorContent extends React.Component
+ <>
+
+ {/* */}
+ {/* */}
+
+
+ >
)
}
}
-export const EditorContent = React.memo(PureEditorContent);
+export const EditorContent = React.memo(PureEditorContent)
+
+// export const EditorContent = PureEditorContent
diff --git a/packages/react/src/ReactNodeViewRenderer.ts b/packages/react/src/ReactNodeViewRenderer.ts
index b735fbc1..f1d6383b 100644
--- a/packages/react/src/ReactNodeViewRenderer.ts
+++ b/packages/react/src/ReactNodeViewRenderer.ts
@@ -1,208 +1,246 @@
-// // @ts-nocheck
-// import { Node, NodeViewRenderer, NodeViewRendererProps } from '@tiptap/core'
-// import { Decoration, NodeView } from 'prosemirror-view'
-// import { NodeSelection } from 'prosemirror-state'
-// import { Node as ProseMirrorNode } from 'prosemirror-model'
-// import React from 'react'
-// import ReactDOM from 'react-dom'
-// import { Editor } from './Editor'
-// import { ReactRenderer } from './ReactRenderer'
+// @ts-nocheck
+import { Node, NodeViewRenderer, NodeViewRendererProps } from '@tiptap/core'
+import { Decoration, NodeView } from 'prosemirror-view'
+import { NodeSelection } from 'prosemirror-state'
+import { Node as ProseMirrorNode } from 'prosemirror-model'
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Editor } from './Editor'
+import { ReactRenderer } from './ReactRenderer'
-// interface ReactNodeViewRendererOptions {
-// stopEvent: ((event: Event) => boolean) | null,
-// update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
-// }
+interface ReactNodeViewRendererOptions {
+ stopEvent: ((event: Event) => 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 = {
-// stopEvent: null,
-// update: null,
-// }
+ options: ReactNodeViewRendererOptions = {
+ stopEvent: null,
+ update: null,
+ }
-// constructor(component: any, props: NodeViewRendererProps, options?: Partial) {
-// this.options = { ...this.options, ...options }
-// this.editor = props.editor as Editor
-// this.extension = props.extension
-// this.node = props.node
-// this.getPos = props.getPos
-// this.mount(component)
-// }
+ domWrapper: Element
-// mount(component: any) {
-// const props = {}
+ contentDOMWrapper: Element
-// if (!component.displayName) {
-// component.displayName = this.extension.config.name
-// }
+ constructor(component: any, props: NodeViewRendererProps, options?: Partial) {
+ this.options = { ...this.options, ...options }
+ this.editor = props.editor as Editor
+ this.extension = props.extension
+ this.node = props.node
+ this.getPos = props.getPos
-// this.renderer = new ReactRenderer(component, {
-// editor: this.editor,
-// props,
-// })
-// }
+ this.domWrapper = document.createElement('div')
+ this.domWrapper.classList.add('dom-wrapper')
+ this.contentDOMWrapper = document.createElement('div')
+ this.contentDOMWrapper.classList.add('content-dom-wrapper')
-// get dom() {
-// // if (!this.renderer.element) {
-// // return null
-// // }
+ this.mount(component)
+ }
-// // if (!this.renderer.element.hasAttribute('data-node-view-wrapper')) {
-// // throw Error('Please use the NodeViewWrapper component for your node view.')
-// // }
+ mount(component: any) {
+ const props = {
+ editor: this.editor,
+ node: this.node,
+ decorations: this.decorations,
+ selected: false,
+ extension: this.extension,
+ getPos: () => this.getPos(),
+ updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
+ }
-// return this.renderer.element
-// }
+ if (!component.displayName) {
+ component.displayName = this.extension.config.name
+ }
-// get contentDOM() {
-// // console.log(this.dom)
-// // return null
-// // if (!this.renderer.element) {
-// // return null
-// // }
+ this.renderer = new ReactRenderer(component, {
+ editor: this.editor,
+ props,
+ })
+ }
-// const hasContent = !this.node.type.isAtom
+ get dom() {
+ return this.renderer.element
+ // return this.domWrapper
-// if (!hasContent) {
-// return null
-// }
+ // if (!this.renderer.element) {
+ // return null
+ // }
-// const contentElement = this.dom.querySelector('[data-node-view-content]')
+ // if (!this.renderer.element.hasAttribute('data-node-view-wrapper')) {
+ // throw Error('Please use the NodeViewWrapper component for your node view.')
+ // }
-// return contentElement || this.dom
-// }
+ // return this.renderer.element
+ }
-// stopEvent(event: Event) {
-// if (typeof this.options.stopEvent === 'function') {
-// return this.options.stopEvent(event)
-// }
+ get contentDOM() {
+ // return this.renderer.element
+ return undefined
+ // return this.renderer.element
+ // return this.contentDOMWrapper
-// const target = (event.target as HTMLElement)
-// const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
+ // console.log(this.dom)
+ // return null
+ // if (!this.renderer.element) {
+ // return null
+ // }
-// // ignore all events from child nodes
-// if (!isInElement) {
-// return false
-// }
+ // const hasContent = !this.node.type.isAtom
-// const { isEditable } = this.editor
-// const { isDragging } = this
-// const isDraggable = !!this.node.type.spec.draggable
-// const isSelectable = NodeSelection.isSelectable(this.node)
-// const isCopyEvent = event.type === 'copy'
-// const isPasteEvent = event.type === 'paste'
-// const isCutEvent = event.type === 'cut'
-// const isClickEvent = event.type === 'mousedown'
-// const isDragEvent = event.type.startsWith('drag') || event.type === 'drop'
+ // if (!hasContent) {
+ // return null
+ // }
-// // ProseMirror tries to drag selectable nodes
-// // even if `draggable` is set to `false`
-// // this fix prevents that
-// if (!isDraggable && isSelectable && isDragEvent) {
-// event.preventDefault()
-// }
+ // const contentElement = this.dom.querySelector('[data-node-view-content]')
-// if (isDraggable && isDragEvent && !isDragging) {
-// event.preventDefault()
-// return false
-// }
+ // return contentElement || this.dom
+ }
-// // we have to store that dragging started
-// if (isDraggable && isEditable && !isDragging && isClickEvent) {
-// const dragHandle = target.closest('[data-drag-handle]')
-// const isValidDragHandle = dragHandle
-// && (this.dom === dragHandle || (this.dom.contains(dragHandle)))
+ stopEvent(event: Event) {
+ if (typeof this.options.stopEvent === 'function') {
+ return this.options.stopEvent(event)
+ }
-// if (isValidDragHandle) {
-// this.isDragging = true
-// document.addEventListener('dragend', () => {
-// this.isDragging = false
-// }, { once: true })
-// }
-// }
+ const target = (event.target as HTMLElement)
+ const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
-// // these events are handled by prosemirror
-// if (
-// isDragging
-// || isCopyEvent
-// || isPasteEvent
-// || isCutEvent
-// || (isClickEvent && isSelectable)
-// ) {
-// return false
-// }
+ // ignore all events from child nodes
+ if (!isInElement) {
+ return false
+ }
-// return true
-// }
+ const { isEditable } = this.editor
+ const { isDragging } = this
+ const isDraggable = !!this.node.type.spec.draggable
+ const isSelectable = NodeSelection.isSelectable(this.node)
+ const isCopyEvent = event.type === 'copy'
+ const isPasteEvent = event.type === 'paste'
+ const isCutEvent = event.type === 'cut'
+ const isClickEvent = event.type === 'mousedown'
+ const isDragEvent = event.type.startsWith('drag') || event.type === 'drop'
-// ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
-// if (mutation.type === 'selection') {
-// if (this.node.isLeaf) {
-// return true
-// }
+ // ProseMirror tries to drag selectable nodes
+ // even if `draggable` is set to `false`
+ // this fix prevents that
+ if (!isDraggable && isSelectable && isDragEvent) {
+ event.preventDefault()
+ }
-// return false
-// }
+ if (isDraggable && isDragEvent && !isDragging) {
+ event.preventDefault()
+ return false
+ }
-// if (!this.contentDOM) {
-// return true
-// }
+ // we have to store that dragging started
+ if (isDraggable && isEditable && !isDragging && isClickEvent) {
+ const dragHandle = target.closest('[data-drag-handle]')
+ const isValidDragHandle = dragHandle
+ && (this.dom === dragHandle || (this.dom.contains(dragHandle)))
-// const contentDOMHasChanged = !this.contentDOM.contains(mutation.target)
-// || this.contentDOM === mutation.target
+ if (isValidDragHandle) {
+ this.isDragging = true
+ document.addEventListener('dragend', () => {
+ this.isDragging = false
+ }, { once: true })
+ }
+ }
-// return contentDOMHasChanged
-// }
+ // these events are handled by prosemirror
+ if (
+ isDragging
+ || isCopyEvent
+ || isPasteEvent
+ || isCutEvent
+ || (isClickEvent && isSelectable)
+ ) {
+ return false
+ }
-// destroy() {
-// this.renderer.destroy()
-// }
+ return true
+ }
-// update(node: ProseMirrorNode, decorations: Decoration[]) {
-// if (typeof this.options.update === 'function') {
-// return this.options.update(node, decorations)
-// }
+ ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
+ if (mutation.type === 'selection') {
+ if (this.node.isLeaf) {
+ return true
+ }
-// if (node.type !== this.node.type) {
-// return false
-// }
+ return false
+ }
-// if (node === this.node && this.decorations === decorations) {
-// return true
-// }
+ if (!this.contentDOM) {
+ return true
+ }
-// this.node = node
-// this.decorations = decorations
-// // this.renderer.updateProps({ node, decorations })
-// this.renderer.render()
+ const contentDOMHasChanged = !this.contentDOM.contains(mutation.target)
+ || this.contentDOM === mutation.target
-// return true
-// }
-// }
+ return contentDOMHasChanged
+ }
-// export function ReactNodeViewRenderer(component: any, options?: Partial): NodeViewRenderer {
-// return (props: NodeViewRendererProps) => {
-// // try to get the parent component
-// // this is important for vue devtools to show the component hierarchy correctly
-// // maybe it’s `undefined` because isn’t rendered yet
-// if (!(props.editor as Editor).contentComponent) {
-// return {}
-// }
+ destroy() {
+ this.renderer.destroy()
+ }
-// return new ReactNodeView(component, props, options) as NodeView
-// }
-// }
+ 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.renderer.render()
+
+ return true
+ }
+
+ selectNode() {
+ this.renderer.updateProps({
+ selected: true,
+ })
+ }
+
+ deselectNode() {
+ this.renderer.updateProps({
+ selected: false,
+ })
+ }
+}
+
+export function ReactNodeViewRenderer(component: any, options?: Partial): NodeViewRenderer {
+ return (props: NodeViewRendererProps) => {
+ // try to get the parent component
+ // this is important for vue devtools to show the component hierarchy correctly
+ // maybe it’s `undefined` because isn’t rendered yet
+ if (!(props.editor as Editor).contentComponent) {
+ return {}
+ }
+
+ return new ReactNodeView(component, props, options) as NodeView
+ }
+}
diff --git a/packages/react/src/ReactRenderer.ts b/packages/react/src/ReactRenderer.ts
index 3bae50d0..98341d71 100644
--- a/packages/react/src/ReactRenderer.ts
+++ b/packages/react/src/ReactRenderer.ts
@@ -1,58 +1,91 @@
-// // @ts-nocheck
+// @ts-nocheck
-// import React from 'react'
-// import { render, unmountComponentAtNode } from 'react-dom'
+import React from 'react'
+import ReactDOM, { render, unmountComponentAtNode } from 'react-dom'
-// import { Editor } from './Editor'
+import { Editor } from './Editor'
-// export interface VueRendererOptions {
-// as?: string;
-// editor: Editor;
-// props?: { [key: string]: any };
-// }
+export interface VueRendererOptions {
+ as?: string;
+ editor: Editor;
+ props?: { [key: string]: any };
+}
-// export class ReactRenderer {
-// id: string
+export class ReactRenderer {
+ 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) {
-// this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
-// this.component = component
-// this.editor = editor
-// this.props = props
+ constructor(component: any, { props = {}, editor }: VueRendererOptions) {
+ this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
+ this.component = component
+ this.editor = editor
+ this.props = props
-// this.teleportElement = document.createElement('div')
-// // this.teleportElement.setAttribute('data-bla', '')
-// // render(React.createElement(component), this.teleportElement)
-// // render(React.createElement(component), this.teleportElement)
-// this.render()
-// // this.element = this.teleportElement.firstElementChild as Element
-// this.element = this.teleportElement
-// }
+ this.teleportElement = document.createElement('div')
+ this.teleportElement.classList.add('teleport-element')
+ this.element = this.teleportElement
-// render() {
-// render(React.createElement(this.component), this.teleportElement)
-// }
+ // this.teleportElement.setAttribute('data-bla', '')
+ // render(React.createElement(component), this.teleportElement)
+ // render(React.createElement(component), this.teleportElement)
+ // this.render()
+ // // this.element = this.teleportElement.firstElementChild as Element
-// updateProps(props: { [key: string]: any } = {}) {
-// // TODO
-// }
+ console.log({ props })
-// destroy() {
-// // TODO
-// // console.log('DESTROY', { bla: this.teleportElement })
-// // console.log(document.querySelector('[data-bla]'))
-// unmountComponentAtNode(this.teleportElement)
-// // unmountComponentAtNode(document.querySelector('[data-bla]'))
-// }
+ // this.bla = ReactDOM.createPortal(
+ // React.createElement(this.component, props),
+ // this.teleportElement,
+ // )
-// }
+ this.bla = React.createElement(this.component, props)
+
+ // console.log({ bla })
+
+ if (this.editor?.contentComponent) {
+ this.editor.contentComponent.setState({
+ renderers: this.editor.contentComponent.state.renderers.set(
+ this.id,
+ this,
+ ),
+ })
+ }
+ }
+
+ render() {
+ render(React.createElement(this.component), this.teleportElement)
+ }
+
+ updateProps(props: { [key: string]: any } = {}) {
+ // TODO
+ console.log('update props', { props })
+ }
+
+ destroy() {
+ // TODO
+ // console.log('DESTROY', { bla: this.teleportElement })
+ // console.log(document.querySelector('[data-bla]'))
+ // unmountComponentAtNode(this.teleportElement)
+ // unmountComponentAtNode(document.querySelector('[data-bla]'))
+
+ if (this.editor?.contentComponent) {
+ const { renderers } = this.editor.contentComponent.state
+
+ renderers.delete(this.id)
+
+ this.editor.contentComponent.setState({
+ renderers,
+ })
+ }
+ }
+
+}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 19177902..27e45903 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -2,6 +2,6 @@
export * from '@tiptap/core'
export { Editor } from './Editor'
export * from './useEditor'
-// export * from './ReactRenderer'
-// export * from './ReactNodeViewRenderer'
+export * from './ReactRenderer'
+export * from './ReactNodeViewRenderer'
export * from './EditorContent'