ProseMirror + CRDT's?

I recently implemented ProseMirror plugin for Yjs (an operation-based CRDT). Demo / src

I map ProseMirror block nodes to YXmlElement (a shared data type that has a node name, attributes, and a list of children) and ProseMiror text nodes to YXmlText (a shared text type that supports formatting attributes - a.k.a. marks).

When the ProseMirror state changes, I compute a diff and apply the changes to the Yjs document. When the Yjs document changes, I simply create a new state based on the Yjs document and replace the old one. I do reuse old ProseMirror nodes, and it seems that this is fairly efficient given the immutable nature of ProseMirror state.

Mapping a CRDT to a ProseMirror document is not always possible, because ProseMirror has a schema that the document needs to comply to. E.g. given a blockquote that must have at least one child. If client1 deletes the first child, and client2 concurrently deletes the second child, you may end up with a blockquote without children (well, that depends on the CRDT). In this case I simply delete the node that does not comply with the schema anymore. This is why I went for the above approach of recreating the state based on the Yjs model, because manipulating the state with transformations would involve a lot more overhead. The disadvantage of my approach is that it destroys all decorations. The next step would be to find a method that only applies the diff between the old and the new state.

5 Likes