Behind Teamemo: Collaborative Editing of Rich Text Documents

One of the best features of Teamemo is that you can edit memos together with you team mates in real time. We are using a technology named Operational Transformation (OT) to sync the text changes of all connected editors.

Document Model and Operations

Rich Text document are typically represented as HTML or an Markup language like Markdown in Web Services. However both can be hardly used together with OT in a WYSIWYG-Editor. Therefore we developed our own document model, wich should have the following properties:

  1. Changes should be syncable using Operational Transformation
  2. Two equal rendered documents should have the same representation in the document model
  3. Is should support custom annotations like comments, highlights, authors

For OT our document model must consist of an linear list of text strings annotated with attributes. We choose a simple representation:

 

[
  { insert: 'This is a '},
  { insert: 'bold', attributes: {bold: true}},
  { insert: ' Text' }
]

 

which renders to the following html:

This is a bold Text.

Changes can be described through the change type and the position. For example:

{ at: 5, insert: 'Text '} 

=> 

[
   { insert: 'This Text is a '}, 
   { insert: 'bold', attributes: {bold: true}},
   { insert: ' Text' }
]

 

{ at: 10, delete: 4}  

=> 

[
    { insert: 'This Text is a '},
    { insert: ' Text' }
]

 

After the application of a change, the document is normalized (see examples above). This is achieved by merging insert operations with the same attributes together into one insert operation:

[{ insert: 'This is a '}, { insert: 'Text '}]
 =>[{ insert: 'This Text is a '}]

 

This ensures a unique representation. Nick Santos from Medium wrote a nice article about why this is important.

Further custom annotations such as authorship can easily be added:

[
  { insert: 'This is a ', attributes: { authorId: 42}},
  { insert: 'bold', attributes: {bold: true, authorId: 7}},
  { insert: 'Text', attributes: { authorId: 42} }
]

 

Transform Operations

Let’s imagine Bob and Alice edit the same document:

[ { insert: 'This is a text'} ]

 

They each change the document in a different way:
Bob adds the word ” fantastic ” at position 9.
Alice deletes the phrase ” a text” starting at position 7.

Bob’s change is send to Alice. Alice already changed the document, so Bob’s changes must be transformed with her local changes, using OT:

transform({at: 9, insert: 'fantastic'}, {at: 7, delete: 7} ) = [{at: 7, insert: 'fantastic'}]

 

She updates her document with the transformed change.

[ { insert: 'This is'}, {at: 7, insert: 'fantastic'} ]
 => [ { insert: 'This is fantastic'} ]

 

In the meantime Bob received the changes from Alice and transforms it in respect to his local change:

transform({at: 7, delete: 7}, {at: 9, insert: 'fantastic'} )
 = [{at: 7, delete: 2}, {at: 16, delete: 5}]

 

He updates his document:

[ { insert: 'This is a fantastic text'}, {at: 7, delete: 2}, {at: 16, delete: 5} ]
 => [ { insert: 'This is fantastic'} ]

 

Magically (or thanks to OT) Bob and Alice have the same document. If you want to play with OT, Tim Baumann created a nice visualization.