diff --git a/docs/src/demos/Examples/Default/React/index-nope.jsx b/docs/src/demos/Examples/Default/React/index-nope.jsx index d654737d..171c75a0 100644 --- a/docs/src/demos/Examples/Default/React/index-nope.jsx +++ b/docs/src/demos/Examples/Default/React/index-nope.jsx @@ -1,143 +1,51 @@ -import * as React from 'react' -import ReactDOM from 'react-dom' -import { EditorState } from 'prosemirror-state' -import { EditorView } from 'prosemirror-view' -import { Node, Schema } from 'prosemirror-model' -// import applyDevTools from 'prosemirror-dev-tools' -// import styled from 'styled-components' +// export default () => { +// const editor = useEditor({ +// content: '

hello react

', +// onTransaction() { +// // console.log('update', this) +// }, +// extensions: [ +// ...defaultExtensions(), +// // Paragraph.extend({ +// // addNodeView() { +// // return reactNodeView(() => { +// // // useEffect(() => { +// // // console.log('effect') +// // // }, []); -// Here we have the (too simple) React component which -// we'll be rendering content into. -// -class Underlined extends React.Component { - constructor(props) { - super(props) - this.hole = React.createRef() - } - // We'll put the content into what we render using - // this function, which appends a given node to - // a ref HTMLElement, if present. - // - append(node) { - if (this.hole) { - this.hole.current.appendChild(node) - } - } +// // return ( +// //

+// // +// //

+// // ) +// // }) +// // return ReactNodeViewRenderer(() => { +// // // useEffect(() => { +// // // console.log('effect') +// // // }, []); - render() { - // Just really wanted to prove I could get React AND - // styled-component abilities at the same time. - // - // const UnderlinedText = styled.p` - // text-decoration: underline; - // ` +// // return ( +// //

+// // +// //

+// // ) +// // }) +// // }, +// // }), +// ] +// }) - // The styled components version is basically just a wrapper to do SCSS styling. - // Questionable if it's even needed for such simple styling and because you can't clearly see the - // DOM structure from the code (hence making `& > ${Component}` selectors quite unintuitive) - // return - return

- } -} - -// This class is our actual interactor for ProseMirror itself. -// It glues DOM rendering, React, and ProseMirror nodes together. -// -class Underline { - constructor(node) { - // We'll use this to access our Underlined component's - // instance methods. - // - this.ref = React.createRef() - - // Here, we'll provide a container to render React into. - // Coincidentally, this is where ProseMirror will put its - // generated contentDOM. React will throw out that content - // once rendered, and at the same time we'll append it into - // the component tree, like a fancy shell game. This isn't - // obvious to the user, but would it be more obvious on an - // expensive render? - // - this.dom = document.createElement('span') - - // Finally, we provide an element to render content into. - // We will be moving this node around as we need to. - // - this.contentDOM = document.createElement('span') - - // Better way of doing this would be portals https://reactjs.org/docs/portals.html - ReactDOM.render( - , - this.dom, - this.putContentDomInRef - ) - } - - update(node) { - return true - } - - // This is the least complex part. Now we've put - // all of our interlocking pieces behind refs and - // instance properties, this becomes the callback - // which performs the actual shell game. - // - putContentDomInRef = () => { - this.ref.current.append(this.contentDOM) - } - - // Required to not to leave the React nodes orphaned. - destroy() { - ReactDOM.unmountComponentAtNode(this.dom) - } -} - -export default class Editor extends React.Component { - constructor(props) { - super(props) - this.editorState = EditorState.create({ - schema: new Schema({ - nodes: { - doc: { - content: 'block+' - }, - underline: { - group: 'block', - content: 'inline*', - parseDOM: [{ tag: 'p' }], - toDOM() { return ['p', 0] } - }, - text: { - group: 'inline' - }, - } - }) - }) - } - - createEditorView = (element) => { - if (element != null) { - this.editorView = new EditorView(element, { - nodeViews: { - underline: (node) => new Underline(node) - }, - state: this.editorState, - }) - // applyDevTools(this.editorView) - } - } - - componentWillUnmount() { - if (this.editorView) { - this.editorView.destroy() - } - } - - shouldComponentUpdate() { - return false - } - - render() { - return
{ this.createEditorView(ref) }} /> - } -} +// return ( +// <> +// { editor && +// +// } +// +// +// ) +// } diff --git a/docs/src/demos/Examples/Default/React/index.jsx b/docs/src/demos/Examples/Default/React/index.jsx index 8274e29f..3f8d89df 100644 --- a/docs/src/demos/Examples/Default/React/index.jsx +++ b/docs/src/demos/Examples/Default/React/index.jsx @@ -1,84 +1,168 @@ -import React, { useState, useEffect } from 'react' +import React from 'react' +import { useEditor, EditorContent } from '@tiptap/react' import { defaultExtensions } from '@tiptap/starter-kit' -import Paragraph from '@tiptap/extension-paragraph' -import { Editor, EditorContent, ReactNodeViewRenderer } from '@tiptap/react' import './styles.scss' -import { render, unmountComponentAtNode } from 'react-dom' -const useEditor = (options = {}) => { - const [editor, setEditor] = useState(null) - - useEffect(() => { - const instance = new Editor(options) - setEditor(instance) - - return () => { - instance.destroy() - } - }, []) - - return editor -} - - - -function reactNodeView(Component) { - const renderComponent = (props, dom) => render(, dom) - - return (node, view, getPos, decorations) => { - let dom = document.createElement("div") - renderComponent({ node, view, decorations, getPos }, dom) - - console.log(dom) - return { - dom, - contentDOM: dom.querySelector('[data-node-view-content]'), - update(node, decorations) { - renderComponent({ node, view, decorations, getPos }, dom) - return true - }, - destroy() { - unmountComponentAtNode(dom) - }, - } +const MenuBar = ({ editor }) => { + if (!editor) { + return null } + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + ) } export default () => { const editor = useEditor({ - content: '

hello react

', extensions: [ ...defaultExtensions(), - Paragraph.extend({ - addNodeView() { - return reactNodeView(() => { - // useEffect(() => { - // console.log('effect') - // }, []); - - return ( -

- -

- ) - }) - return ReactNodeViewRenderer(() => { - // useEffect(() => { - // console.log('effect') - // }, []); - - return ( -

- -

- ) - }) - }, - }), - ] + ], + 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.jsx b/packages/react/src/EditorContent.jsx index e7395037..64044ad6 100644 --- a/packages/react/src/EditorContent.jsx +++ b/packages/react/src/EditorContent.jsx @@ -31,7 +31,9 @@ import { Editor } from './Editor' // ) // } -export class EditorContent extends React.Component { + + +export class PureEditorContent extends React.Component { constructor(props) { super(props) this.editorContentRef = React.createRef() @@ -69,3 +71,6 @@ export class EditorContent extends React.Component { ) } } + + +export const EditorContent = React.memo(PureEditorContent); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 27fc78c4..27e45903 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,9 +1,7 @@ // @ts-nocheck export * from '@tiptap/core' export { Editor } from './Editor' -// export { -// Editor, EditorContext, useEditor, -// } from './components/Editor' +export * from './useEditor' export * from './ReactRenderer' export * from './ReactNodeViewRenderer' export * from './EditorContent' diff --git a/packages/react/src/useEditor.ts b/packages/react/src/useEditor.ts new file mode 100644 index 00000000..a4b9ac33 --- /dev/null +++ b/packages/react/src/useEditor.ts @@ -0,0 +1,31 @@ +// @ts-nocheck + +import { useState, useEffect } from 'react' +import { Editor } from './Editor' + +function useForceUpdate() { + const [_, setValue] = useState(0) + + return () => setValue(value => value + 1) +} + +export const useEditor = (options = {}) => { + const [editor, setEditor] = useState(null) + const forceUpdate = useForceUpdate() + + useEffect(() => { + const instance = new Editor(options) + + setEditor(instance) + + instance.on('transaction', () => { + forceUpdate() + }) + + return () => { + instance.destroy() + } + }, []) + + return editor +}