share/sharedb

Add cursor synchronization to an editor

MrTin opened this issue · 7 comments

MrTin commented

Hi ShareDB Team,

It's so fun to play around with this – thanks for sharing ShareDB to the open source community!

I was wondering how I could go about and add cursor sync to ShareDB. I'm trying out the rich-text example (that uses Quill) and was curious how you could make it look like Google Docs, where it points out another clients cursor, color and name. I was looking at https://github.com/hivejs/hive-plugin-cursor-broadcast-codemirror but I realized that you guys proabably have a better answer to my question.

Is this a good example to use middleware for? Am I thinking right here? ShareDB listens to the WS stream so won't I make it "crash" by sending my own commands (cursor position) in the stream?

My idea is the following:

  • Create a middleware for authentication using JWT.
  • During socket connection, store the color and name of the client.
  • Create a middleware for synchronizing cursor position(?)

Thanks for any help!

Regards,
Martin

Have you seen the quill issue? - quilljs/quill#918
Quill before 1.0 had multicursor module, the @benbro guy updated the module for 1.0.

MrTin commented

Thanks! Will take a look :) If I understand the solution correct he is not keeping track of where I have my cursor but rather indicate where the latest op occured. Simple enough to me!

This is high on my list of features to get in at some point, though it does require proper integration with OT and ideally support for ephemeral data sync.

OT integration

When you make edits locally, they need to offset the position of other users' cursors in your local client synchronously. For example, if your cursor is after mine and I start typing, each character I type should increase the position of your cursor by one character. Thus, the cursor UI does need to understand the OT type's operations and know how to adjust all the cursors being displayed by the edit ops that are being created locally and remotely. You can see this in the quill multi-cursor module here: https://github.com/benbro/quill/blob/multi-cursor/modules/multi-cursor.js#L76. As @zag2art pointed out, looks like this is going to get re-implemented in Quill for richtext and we should be able to use that out of the box.

Other OT types (such as strings in JSON0 or the text type) would need their own implementations of the same kind of logic for their own op formats.

Ephemeral data

The other part of the equation is broadcasting your cursor position to all the other users interacting with the same document and vice versa. You could store this in document data and it would synced to all users, but since we really only care about the data in realtime, there is no point in persisting ops that represent changes in cursor state. There are various solutions to syncing this ephemeral data without any explicit support in ShareDB, and you can just send messages out of bound. If you only have a single server that all editors of the same doc are connected to, then you can even keep this state in memory and echo any updates to all other users. It gets more complex when you have users connected to multiple servers, but you could solve that outside of ShareDB as well.

That being said, I do think this is a pretty common use case for ShareDB, so I would like to have some sort of built in support for synching ephemeral data tied to user presence and cursors associated with a document but not stored permanently. Have not yet put time into figuring out the implementation.

I'm encountering lots of use cases for this "ephemeral data" concept.

The solution I'm thinking of implementing is to have two ShareDB collections for a single set of documents, one for data where you want to keep the history around forever (e.g. text edits) and one for ephemeral data where you want to periodically clear out the history, (e.g. cursor and presence data, using a tool like https://github.com/dmapper/docclean ).

Hi @MrTin,

I also had this need and went on and published a repo with this working using Quill editor here, if it's interesting:

https://quill-sharedb-cursors.herokuapp.com / https://github.com/pedrosanta/quill-sharedb-cursors

It does pretty much doing what @nateps was saying on the first part of his comment...

When you make edits locally, they need to offset the position of other users' cursors in your local client synchronously. For example, if your cursor is after mine and I start typing, each character I type should increase the position of your cursor by one character. Thus, the cursor UI does need to understand the OT type's operations and know how to adjust all the cursors being displayed by the edit ops that are being created locally and remotely. You can see this in the quill multi-cursor module here: https://github.com/benbro/quill/blob/multi-cursor/modules/multi-cursor.js#L76. As @zag2art pointed out, looks like this is going to get re-implemented in Quill for richtext and we should be able to use that out of the box.

Other OT types (such as strings in JSON0 or the text type) would need their own implementations of the same kind of logic for their own op formats.

... but it adds in cursor sync when you move the caret (arrow keys) or click on the document, which is being done through a separate socket - which can lead to some rather nasty racing conditions[1] and issues at times. (Still gathering more insight on those, btw.)

I think that this particular issue would benefit immensely from this concept of Ephemeral Data like @nateps puts it. Looking forward and excited to see it implemented - and happy to colaborate if useful!

1 - 'selection-change' update through cursor socket followed by an insert sent through ShareDB socket, sometimes are processed with reversed order, leading to an out-of-sync cursor.

ICYMI, documented the racing condition on pedrosanta/quill-sharedb-cursors#1.

Addressed by #322