Skip to content

Instantly share code, notes, and snippets.

@esmevane
Created November 7, 2018 04:24
Show Gist options
  • Save esmevane/7326b19e20a5670954b51ea8618d096d to your computer and use it in GitHub Desktop.
Save esmevane/7326b19e20a5670954b51ea8618d096d to your computer and use it in GitHub Desktop.
[ 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()
}
)
@Vinosh123
Copy link

Vinosh123 commented Dec 12, 2022

Hiyah, this code does no longer work so I've updated it here TeemuKoivisto/771e6522f092fa1f0ff9eab7545f8fad

I didn't update all the comments though but if you have time and energy to update your own, it would be much appreciated =). I'm still trying out things with this setup so I'm not 100% how I will end up doing this, probably at least using ReactDOM portals.

I'm not sure will I end up using StyledComponents though as it feels a little bit too much of an overhead for applying simple styles to the components. Sure if they get complicated I might but since in rich-text editing the nesting of the components can't be clearly seen from the code it serves only as a wrapper to do basically SCSS styling.

@TeemuKoivisto

Working really great, Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment