textblockHacks, decorations, and collaboration

I’m trying to solve an issue with the way textblockHacks works during collaborative editing. We render an empty span decoration to represent the remote collaborator’s cursor, and when the remote collaborator is typing fast, we see “bouncing” text in the local editor.

This occurs because of the way textblockHacks inserts a br tag after the local content, even if that content is a decoration. I’m trying to find a “ProseMirror-ish” solution to this issue. All I can think of is setting the node schema to inline, but as it’s being rendered as a paragraph, that doesn’t really seem like the right thing.

I figured I’d ask here and see if anyone has encountered and/or resolved this issue already.

What exactly is causing the ‘bouncing’? The <br> nodes should be invisible when at the end of the paragraph.

When we create our “action” element, which is a custom schema element that renders as a paragraph, the textblock code triggers and adds a br element to the end.

When we then have our collab caret widget showing at the end of an Action element, the update code when receiving text from a remote collaborator goes like this:

  1. Add copies of all children (the span and the br) after the existing widget - this is where we see the “downward bounce” as the extra br pushes the following paragraph down
  2. Delete the old copies of the children (now-redundant span and br) - this is the “upward bounce” as the extra br disappears.

Perhaps we’re missing styling? It’s not clear what would make the br tags invisible.

Further clarification:

We’re using separate updates for selection and change requests. We may not be getting these changes in perfect sync with the dispatched change.

The order of execution thus varies a bit, but the two streams of change are as follows:

Starting from this element: <p>“tex”<span class=‘Prosemirror-widget’></span><br></p>

Text Change

  1. Dispatch the character on client 1
  2. send text change transaction to server
  3. receive text change from server on client 2
  4. apply text change. This means that our paragraph looks like this: <p>“tex”<span class=‘Prosemirror-widget’></span>“t”</p>

Selection Change

  1. Dispatch the selection change on client 1
  2. send selection change to server
  3. receive selection change from server on client 2
  4. apply selection change on client 2. Our paragraph now looks like: <p>“text”<span class=‘Prosemirror-widget’></span><br></p>

It seems like this order isn’t perfectly reliable due to the delay between dispatch and finalization, but it’s hard to be certain whether that’s a factor or not.

Ah, you’re adding something that renders as a block to a textblock? That’s not very well supported at the moment, and will indeed have the effect of a BR node being appended, on the assumption that it’ll be necessary to make the paragraph render properly, and to make selection behave correctly around that node.

Is it possible to place the widget after the paragraph, in a block context? That’ll be less confusing to both ProseMirror and the browser’s contentEditable implementation.

I’m not sure that it’s about something rendering as a block. The br is appended because of the way that the ViewDesc renders children. Basically, if there’s a non-TextViewDesc element at the end of the block, even if it’s just a span element like a widget decoration, it appends a br tag.

We’re looking at overriding the ViewDesc itself, but I’ve also been studying different caret implementations to see if there’s a better solution. For our case, it makes sense to render the element inline, but it’s possible we’ll be better off taking the decoration out of the element and rendering as a floating div or something.

Yes, and for inline children the br is appropriate, whereas for a block it causes issues. So yeah, it is about rendering as a block.