Blank paragraph added with Remirror/y-prosemirror

I’m using Remirror to build an editor, so the Yjs binding is, I guess, basically y-prosemirror.

The basic process is that, for instance, Editor #1 will have a document open. Editor #2 will connect to that same room to share editing on the document. Pretty standard, I would think.

Now, at a minimum Remirror/Prosemirror requires a document to have one paragraph in it, as per the document schema, so that when Editor #2 is created, before it successfully connects, it consists of a blank paragraph.

Then, when it does connect, and syncs up, that blank paragraph is propagated to the shared document: i.e., a new blank line/paragraph appears in Editor #1’s document, which is reflected in the synced Editor #2 document.

On some level this makes sense to me: Yjs is trying to integrate the two documents. But how can I prevent that from happening?

(There is no way, for instance — at least as far as I can tell — to “pre-connect” the Yjs WebRTC connection, since it is created by the Yjs extension when the Remirror component is mounted. The blank Remirror document for Editor #2 has to be created first.)

Option 1: Allow empty documents. You can allow that in the ProseMirror schema.

Option 2: Create a document with only a single paragraph in it. Encode it and use it as the baseline.

// Do this once on your own computer

const ydoc = new Y.Doc
initRemirror(ydoc)

console.log(buffer.toBase64(Y.encodeStateAsUpdate(ydoc))) // capture this string

Now whenever you create a Yjs document, you use that “baseline” document above.

const baseline = buffer.fromBase64("<the string that you captured above>")

const createYdoc = () => {
  const ydoc = new Y.Doc()
  Y.applyUpdate(ydoc, baseline)
}

That should solve your problem most of the time.

Note that it is always possible to end up in this weird scenario:

  • you have N users connected.
  • you have a document with two paragraphs
  • user1 deletes paragraph1. user2 deletes paragraph2 concurrently.
  • y-prosemirror notices that and tries to return to a valid state. The only option is to insert a new paragraph.
  • You now have N paragraphs, as every user tries to “fix” the state.

I highly recommend allowing empty documents. You can still add option 2 to ensure that you don’t start with an empty document.

Ah, great stuff, thanks so much. Allowing an empty document in the ProseMirror schema is a great starting point. I could’ve sworn ProseMirror required at least one paragraph, so thank you for setting me straight.

Unfortunately this doesn’t seem to be addressing the problem I’m seeing, and I suspect it may actually go at least a little deeper.

First of all, I’m not able to create an empty ProseMirror document: even if I define it as having zero or more paragraphs in the schema, it always creates at least one paragraph. It’s several years old, but a response from Marijn as to whether it’s possible to create an entirely empty document says: “No, that’s not possible. There’s an invariant that only documents that contain at least one valid selection position are allowed.”

But that said, I’m seeing the same thing (I think) occur with non-empty documents.

For instance, two ProseMirror instances connected to the same room will have each a copy of the same document (let’s say the text is “Document”). Something will happen — I’m not sure what, since it seems unpredictable, and may be a reload or network/connection issue or something else — and suddenly the document will become:

Doc
Document
ument

In other words, an entire duplicate copy of the shared document will be inserted in the middle. It seems like this is the same thing as with my original blank paragraph issue. When someone joins the document, their current, freshly created ProseMirror state is an empty paragraph, so the shared document state becomes:

Doc
[blank paragraph]
ument

I’ve been looking for anything in the console that could possibly signal this occurring (much less what to do about it), but so far I’m coming up empty.

It’s a known issue that you should wait for Yjs provider to sync up before you render anything in the editor. Otherwise, the default content created by the user is merged with the incoming doc data thus creating duplicate paragraphs.

So I don’t know exactly what is happening in your code, but try rendering your editor conditionally whether eg WebsocketProvider has emitted a 'sync' event, showing say a loading spinner otherwise.

1 Like