Say, for instance, you give a text to two different people. Each of them makes some changes and hands them back to you.

var rev0 ="Hello adventurer!"

, revA ="Hello treasured adventurer!"

, revB ="Good day adventurers, y'all!"

As a human you're certainly able to make out the changes and tell what's been changed to combine both revisions, for your computer it's harder.
Firstly, you'll need to extract the changes in each version.

var csA = computeChanges(rev0, revA)

var csB = computeChanges(rev0, revB)

Now we can send the changes of revA from side A over the network to B and if we apply them on the original revision we get the full contents of revision A again.

csA.apply(rev0)== revA // true

But we don't want to apply them on the original revision, because we've already changed the text and created revB. We could of course try and apply it anyway:

Imagine a text editor, that allows users to undo any edit they've ever done to a document without undoing all edits that were done afterwards.

We decide to store all edits in a list of changesets, where each applied on top of the other results in the currently visible document.

Let's assume the following document with 4 revisions and 3 edits.

var versions =

[""

,"a"

,"ab"

,"abc"

]

// For posterity we create the edits like this

var edits =[]

for(var i=1; i < versions.length; i++){

edits.push( computeChanges(text[i-1], text[i]))

}

We can undo the last edit, by removing it from the stack of edits, inverting it and applying it on the current text.

var lastEdit = edits.pop()

newEditorContent = lastEdit.invert().apply(currentEditorContent)

Now, if we want to undo any edit, let's say the second instead of the last, we need to construct the inverse changeset of that second edit.

Then, we transform all following edits against this inverse changeset. But in order for this "undo changeset" to fit for the next changeset also, we in turn transform it against all previously iterated edits.

var undoIndex =1

var undoCs = edits[undoIndex].invert()

var newEdits =[], edit

for(var i=undoIndex+1; i < edits.length; i++){

edit = edits[i]

newEdits[i]= edit.transformAgainst(undoCs)

undoCs = undoCs.transformAgainst(edit)

}

This way we can effectively exclude any given changes from all changes that follow it. This is called Exclusion Transformation.

As you know, there are 3 types of operations (Retain, Skip and Insert) in a changeset, but actually, there are four. The forth is an operation type called Mark.

Mark can be used to apply attributes to a text. Currently attributes are like binary flags: Either a char has an attribute or it doesn't. Attributes are integer numbers (you'll need to implement some mapping between attribute names and these ids). You can pass attributes to the Mark operation as follows:

var mark =newMark(/*length:*/5,{

0:1

,7:1

,3:1

,15:1

,-2:1

,11:1

})

Did you notice the negative number? While positive numbers result in the application of some attribute, negative numbers enforce the removal of an attribute that has already been applied on some range of the text.

Now, how can you deal with those attributes? Currently, you'll have to keep changes to attributes in separate changesets. Storing attributes for a document can be done in a changeset with the length of the document into which you merge attribute changes. Applying them is as easy as iterating over the operations of that changeset (changeset.forEach(fn..)) and i.e. inserting HTML tags at respective positions in the corresponding document.

Warning: Attributes are still experimental. There are no tests, yet, and the API may change in the future.