Skip to content

Instantly share code, notes, and snippets.

@esmevane
Created November 7, 2018 04:24
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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()
}
)
@TeemuKoivisto
Copy link

TeemuKoivisto commented Jun 24, 2019

Hiyah, this code does no longer work so I've updated it here https://gist.github.com/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.

@esmevane
Copy link
Author

Thanks for the update! Nice work. I'll definitely have a minute to update and demo this later on this week.

I bundled SC here mainly 'cause I use it, and I thought it made an effective demonstration of using vendored components. But, you're right, it isn't a requirement here and bundling vendor stuff might add noise for readers anyway. Sorry if that caused any debug noise for you!

@TeemuKoivisto
Copy link

Great! And just as a remark, I am a massive SC fan and use it in virtually every React project. Yet here it sadly might be just useless overhead, dunno about the performance cost but for basic styles maybe plain old SCSS is better. Although writing class names is yucky. Oh well.

There's still a lot of things outside my narrow understanding of how PM and React work together eg how other NodeView's methods should be passed to the React component (eg selectNode, deselectNode, setSelection) and what they actually do. Also how the state-management should happen (through transactions' getMeta?) as well the purpose of some confusing boilerplate I saw in Atlassian's PM-React implementation (eg https://bitbucket.org/atlassian/atlaskit-mk-2/src/0fcae893b790443a30f7dadae00638d6e4238b2f/packages/editor/editor-core/src/event-dispatcher/index.ts?at=master)

It would be nice if an intelligible collection of this PM + React boilerplate could be composed into a minimal setup one could use. If you have any idea how to make that, I'd love to help =).

@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