Created
November 7, 2018 04:24
-
-
Save esmevane/7326b19e20a5670954b51ea8618d096d to your computer and use it in GitHub Desktop.
[ Typescript / React / ProseMirror ]: Put React components into ProseMirror NodeViews
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This assumes Styled Components is in play, as well. | |
// | |
// Here we have the (too simple) React component which | |
// we'll be rendering content into. | |
// | |
class Underlined extends React.Component<any, any> { | |
public hole: HTMLElement | null | |
// We'll put the content into what we render using | |
// this function, which appends a given node to | |
// a ref HTMLElement, if present. | |
// | |
public append(node: HTMLElement) { | |
if (this.hole) { | |
this.hole.appendChild(node) | |
} | |
} | |
public render() { | |
// Just really wanted to prove I could get React AND | |
// styled-component abilities at the same time. | |
// | |
const UnderlinedText = styled.span` | |
text-decoration: underline; | |
` | |
// We want to render the content dom node in the styled | |
// component. So, we set its inner ref (a SC quirk). | |
// | |
return <UnderlinedText innerRef={(node) => (this.hole = node)} /> | |
} | |
} | |
// This class is our actual interactor for ProseMirror itself. | |
// It glues DOM rendering, React, and ProseMirror nodes together. | |
// | |
class Underline { | |
public dom: HTMLElement | |
public contentDOM: HTMLElement | |
public ref: React.RefObject<any> | |
constructor(public node: Node) { | |
// We'll use this to access our Underlined component's | |
// instance methods. | |
// | |
this.ref = React.createRef<any>() | |
// 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') | |
ReactDOM.render( | |
<Underlined ref={this.ref} />, | |
this.dom, | |
this.putContentDomInRef | |
) | |
} | |
public update(node: 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. | |
// | |
private putContentDomInRef = () => { | |
this.ref.current.append(this.contentDOM) | |
} | |
} | |
// An example of using the NodeView object in an | |
// editor view. | |
// | |
new EditorView( | |
node, | |
{ | |
nodeViews: { | |
underline: (node: Node) => new Underline(node) | |
}, | |
state: EditorState.createEmpty() | |
} | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@TeemuKoivisto
Working really great, Thanks.