An attempt at multi cursors sync in a collaborative editing scenario using Quill, a ShareDB backend, and the reedsy/quill-cursors Quill module. For more info on each component, check their pages/repositories.
A working demo is available at: https://quill-sharedb-cursors.herokuapp.com
Before trying to run this example, make sure you have a fairly recent (6 LTS or earlier, 8 LTS recommended) version of Node.
Opted to have MongoDB storage for this particular example, so make sure it's installed and running. Alternatively, if you have Docker installed you can spin an instance quickly by running the command:
docker run -p 27017:27017 mongo
Run the project:
DEBUG=quill-sharedb-cursors:* npm start
For convenience while developing, a build task with watch is included:
npm run watch
For a proper multi cursor feature the following is usually considered:
For more info on how the various efforts/communities are coordinating and working towards this, keep reading below.
Regarding Quill and multi cursors it is important to start on the older v0.20 version of Quill, which already provided a multi cursors module - check it out here - and which formed the basis of follow up work for this topic. It's important to note that this module only supported/had API for carets, but not selections.
During the extensive changes from v0.20 to v1 this module has been since removed, but there is an active ongoing effort to re-implement this feature for Quill v1. To follow this effort and discussion check the issue:
This feature has since been added to Backlog by jhchen to be implemented soon.
During the discussion benbro updated the old v0.20 multi cursors module to work with v1, that can be checked out here:
Additionally, and based on the work it has been done over at Reedsy related to multi cursor sync (both for carets and selections) built on top of the v0.20 of Quill, the multi cursors module was published and made available as a way to contribute to the community and help this effort move forward:
After this functionality is included in the new v1 multi cursors module, this module is to be deprecated.
To check where the efforts for multi cursor sync on middleware/backend stand at the moment, continue to read on below.
In what regards to ShareDB, the issue where the current discussion and effort is being handled is the following:
nateps, the main contributor to the project, puts this high on the priority list and expands a bit on ephemeral data concept, that would benefit a cursor sync feature.
On Meteor thought, haven't found any information of an effort regarding multi cursors discussion or implementation.
Overview and notes on some decisions taken to build the example project.
In the past ShareJS and LiveDB (earlier ShareDB) recommended node-browserchannel - which was pretty good, as it reconnected seamlessly, but it resorted to long-polling. But as a note on its README says, WebSocket support is now reasonably universal, and strongly suggests using raw websockets for new projects.
But by using bare WebSockets, we don't have any connection interruption/reconnection mechanism in place out-of-the-box.
So for this project, reconnecting-websocket was used, along some ping/keep-alive code to help manage the connection 'health'/availability - and keep about the same functionality of browserchannel on this aspect.
Keep in mind that ShareDB Connection does not handle WebSocket/transport reconnections, but it will resubscribe any document and sync pending/offline updates upon socket reconnection. For more on that check share/sharedb#121 and share/sharedb#138.
Socket.io isn't WebSocket API-compatible, so won't work without some sort of wrapper implementing it, it seems. Also there is a warning on ShareJS documentation regarding Socket.io that it sometimes doesn't guarantee in-order message delivery. Checking josephg/ShareJS#375 gives some more insight into it, which points to some feasibility as long as only 'websocket' transport is used and probably configured without transport upgrade on Socket.io/Engine.io.
Eitherway, ShareDB Socket.io support is a poor/unknown at best and without thorough testing. If you manage to get a good working example with ShareDB and Socket.io, please share away!
For this example, simple
ShareDB.MemoryDB (or even
ShareDBMingoMemory) adapter would suffice, but because I wanted this to keep close to a scenario I'm working at the moment, opted to test this in tandem with MongoDB and
Another aspect to keep in mind is that
rich-text doesn't come registered by default as an available OT type on ShareDB, so we need to:
rich-texttype both on Server and on Client;
The basics about having ShareDB and Quill working together rely on listening to two events:
text-changeevent and, for user changes, submit the operation to ShareDB document;
opevent and, for non local update sources, call Quill
There are several ways one could implement cursor sync behaviour, but in this case this implementation has the following components:
helpers/wss-cursors.js- A server part that:
The data each cursor is sending and syncing is:
id, the cursor/client id autogenerated by the server;
name, the name to display on the cursor;
color, CSS color of the cursor;
range, the current range from the client, obtained from
For the most of the time the editor and cursor sync seem to behave as it should be expected. But on a few cases the cursor position gets misplaced - I have a strong suspicion the cause are racing conditions, as these issues are hard to replicate, occur sometimes, and usually involve two or more people typing at the same time.
The main issues currently identified in this example, are:
This code is available under the MIT license.