Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[ Typescript / React / ProseMirror ]: Put React components into ProseMirror NodeViews
// 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
You can’t perform that action at this time.