| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 | 
							- var diff = require('fast-diff');
 
- var equal = require('deep-equal');
 
- var extend = require('extend');
 
- var op = require('./op');
 
- var NULL_CHARACTER = String.fromCharCode(0);  // Placeholder char for embed in diff()
 
- var Delta = function (ops) {
 
-   // Assume we are given a well formed ops
 
-   if (Array.isArray(ops)) {
 
-     this.ops = ops;
 
-   } else if (ops != null && Array.isArray(ops.ops)) {
 
-     this.ops = ops.ops;
 
-   } else {
 
-     this.ops = [];
 
-   }
 
- };
 
- Delta.prototype.insert = function (text, attributes) {
 
-   var newOp = {};
 
-   if (text.length === 0) return this;
 
-   newOp.insert = text;
 
-   if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
 
-     newOp.attributes = attributes;
 
-   }
 
-   return this.push(newOp);
 
- };
 
- Delta.prototype['delete'] = function (length) {
 
-   if (length <= 0) return this;
 
-   return this.push({ 'delete': length });
 
- };
 
- Delta.prototype.retain = function (length, attributes) {
 
-   if (length <= 0) return this;
 
-   var newOp = { retain: length };
 
-   if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
 
-     newOp.attributes = attributes;
 
-   }
 
-   return this.push(newOp);
 
- };
 
- Delta.prototype.push = function (newOp) {
 
-   var index = this.ops.length;
 
-   var lastOp = this.ops[index - 1];
 
-   newOp = extend(true, {}, newOp);
 
-   if (typeof lastOp === 'object') {
 
-     if (typeof newOp['delete'] === 'number' && typeof lastOp['delete'] === 'number') {
 
-       this.ops[index - 1] = { 'delete': lastOp['delete'] + newOp['delete'] };
 
-       return this;
 
-     }
 
-     // Since it does not matter if we insert before or after deleting at the same index,
 
-     // always prefer to insert first
 
-     if (typeof lastOp['delete'] === 'number' && newOp.insert != null) {
 
-       index -= 1;
 
-       lastOp = this.ops[index - 1];
 
-       if (typeof lastOp !== 'object') {
 
-         this.ops.unshift(newOp);
 
-         return this;
 
-       }
 
-     }
 
-     if (equal(newOp.attributes, lastOp.attributes)) {
 
-       if (typeof newOp.insert === 'string' && typeof lastOp.insert === 'string') {
 
-         this.ops[index - 1] = { insert: lastOp.insert + newOp.insert };
 
-         if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes
 
-         return this;
 
-       } else if (typeof newOp.retain === 'number' && typeof lastOp.retain === 'number') {
 
-         this.ops[index - 1] = { retain: lastOp.retain + newOp.retain };
 
-         if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes
 
-         return this;
 
-       }
 
-     }
 
-   }
 
-   if (index === this.ops.length) {
 
-     this.ops.push(newOp);
 
-   } else {
 
-     this.ops.splice(index, 0, newOp);
 
-   }
 
-   return this;
 
- };
 
- Delta.prototype.chop = function () {
 
-   var lastOp = this.ops[this.ops.length - 1];
 
-   if (lastOp && lastOp.retain && !lastOp.attributes) {
 
-     this.ops.pop();
 
-   }
 
-   return this;
 
- };
 
- Delta.prototype.filter = function (predicate) {
 
-   return this.ops.filter(predicate);
 
- };
 
- Delta.prototype.forEach = function (predicate) {
 
-   this.ops.forEach(predicate);
 
- };
 
- Delta.prototype.map = function (predicate) {
 
-   return this.ops.map(predicate);
 
- };
 
- Delta.prototype.partition = function (predicate) {
 
-   var passed = [], failed = [];
 
-   this.forEach(function(op) {
 
-     var target = predicate(op) ? passed : failed;
 
-     target.push(op);
 
-   });
 
-   return [passed, failed];
 
- };
 
- Delta.prototype.reduce = function (predicate, initial) {
 
-   return this.ops.reduce(predicate, initial);
 
- };
 
- Delta.prototype.changeLength = function () {
 
-   return this.reduce(function (length, elem) {
 
-     if (elem.insert) {
 
-       return length + op.length(elem);
 
-     } else if (elem.delete) {
 
-       return length - elem.delete;
 
-     }
 
-     return length;
 
-   }, 0);
 
- };
 
- Delta.prototype.length = function () {
 
-   return this.reduce(function (length, elem) {
 
-     return length + op.length(elem);
 
-   }, 0);
 
- };
 
- Delta.prototype.slice = function (start, end) {
 
-   start = start || 0;
 
-   if (typeof end !== 'number') end = Infinity;
 
-   var ops = [];
 
-   var iter = op.iterator(this.ops);
 
-   var index = 0;
 
-   while (index < end && iter.hasNext()) {
 
-     var nextOp;
 
-     if (index < start) {
 
-       nextOp = iter.next(start - index);
 
-     } else {
 
-       nextOp = iter.next(end - index);
 
-       ops.push(nextOp);
 
-     }
 
-     index += op.length(nextOp);
 
-   }
 
-   return new Delta(ops);
 
- };
 
- Delta.prototype.compose = function (other) {
 
-   var thisIter = op.iterator(this.ops);
 
-   var otherIter = op.iterator(other.ops);
 
-   var ops = [];
 
-   var firstOther = otherIter.peek();
 
-   if (firstOther != null && typeof firstOther.retain === 'number' && firstOther.attributes == null) {
 
-     var firstLeft = firstOther.retain;
 
-     while (thisIter.peekType() === 'insert' && thisIter.peekLength() <= firstLeft) {
 
-       firstLeft -= thisIter.peekLength();
 
-       ops.push(thisIter.next());
 
-     }
 
-     if (firstOther.retain - firstLeft > 0) {
 
-       otherIter.next(firstOther.retain - firstLeft);
 
-     }
 
-   }
 
-   var delta = new Delta(ops);
 
-   while (thisIter.hasNext() || otherIter.hasNext()) {
 
-     if (otherIter.peekType() === 'insert') {
 
-       delta.push(otherIter.next());
 
-     } else if (thisIter.peekType() === 'delete') {
 
-       delta.push(thisIter.next());
 
-     } else {
 
-       var length = Math.min(thisIter.peekLength(), otherIter.peekLength());
 
-       var thisOp = thisIter.next(length);
 
-       var otherOp = otherIter.next(length);
 
-       if (typeof otherOp.retain === 'number') {
 
-         var newOp = {};
 
-         if (typeof thisOp.retain === 'number') {
 
-           newOp.retain = length;
 
-         } else {
 
-           newOp.insert = thisOp.insert;
 
-         }
 
-         // Preserve null when composing with a retain, otherwise remove it for inserts
 
-         var attributes = op.attributes.compose(thisOp.attributes, otherOp.attributes, typeof thisOp.retain === 'number');
 
-         if (attributes) newOp.attributes = attributes;
 
-         delta.push(newOp);
 
-         // Optimization if rest of other is just retain
 
-         if (!otherIter.hasNext() && equal(delta.ops[delta.ops.length - 1], newOp)) {
 
-           var rest = new Delta(thisIter.rest());
 
-           return delta.concat(rest).chop();
 
-         }
 
-       // Other op should be delete, we could be an insert or retain
 
-       // Insert + delete cancels out
 
-       } else if (typeof otherOp['delete'] === 'number' && typeof thisOp.retain === 'number') {
 
-         delta.push(otherOp);
 
-       }
 
-     }
 
-   }
 
-   return delta.chop();
 
- };
 
- Delta.prototype.concat = function (other) {
 
-   var delta = new Delta(this.ops.slice());
 
-   if (other.ops.length > 0) {
 
-     delta.push(other.ops[0]);
 
-     delta.ops = delta.ops.concat(other.ops.slice(1));
 
-   }
 
-   return delta;
 
- };
 
- Delta.prototype.diff = function (other, index) {
 
-   if (this.ops === other.ops) {
 
-     return new Delta();
 
-   }
 
-   var strings = [this, other].map(function (delta) {
 
-     return delta.map(function (op) {
 
-       if (op.insert != null) {
 
-         return typeof op.insert === 'string' ? op.insert : NULL_CHARACTER;
 
-       }
 
-       var prep = (delta === other) ? 'on' : 'with';
 
-       throw new Error('diff() called ' + prep + ' non-document');
 
-     }).join('');
 
-   });
 
-   var delta = new Delta();
 
-   var diffResult = diff(strings[0], strings[1], index);
 
-   var thisIter = op.iterator(this.ops);
 
-   var otherIter = op.iterator(other.ops);
 
-   diffResult.forEach(function (component) {
 
-     var length = component[1].length;
 
-     while (length > 0) {
 
-       var opLength = 0;
 
-       switch (component[0]) {
 
-         case diff.INSERT:
 
-           opLength = Math.min(otherIter.peekLength(), length);
 
-           delta.push(otherIter.next(opLength));
 
-           break;
 
-         case diff.DELETE:
 
-           opLength = Math.min(length, thisIter.peekLength());
 
-           thisIter.next(opLength);
 
-           delta['delete'](opLength);
 
-           break;
 
-         case diff.EQUAL:
 
-           opLength = Math.min(thisIter.peekLength(), otherIter.peekLength(), length);
 
-           var thisOp = thisIter.next(opLength);
 
-           var otherOp = otherIter.next(opLength);
 
-           if (equal(thisOp.insert, otherOp.insert)) {
 
-             delta.retain(opLength, op.attributes.diff(thisOp.attributes, otherOp.attributes));
 
-           } else {
 
-             delta.push(otherOp)['delete'](opLength);
 
-           }
 
-           break;
 
-       }
 
-       length -= opLength;
 
-     }
 
-   });
 
-   return delta.chop();
 
- };
 
- Delta.prototype.eachLine = function (predicate, newline) {
 
-   newline = newline || '\n';
 
-   var iter = op.iterator(this.ops);
 
-   var line = new Delta();
 
-   var i = 0;
 
-   while (iter.hasNext()) {
 
-     if (iter.peekType() !== 'insert') return;
 
-     var thisOp = iter.peek();
 
-     var start = op.length(thisOp) - iter.peekLength();
 
-     var index = typeof thisOp.insert === 'string' ?
 
-       thisOp.insert.indexOf(newline, start) - start : -1;
 
-     if (index < 0) {
 
-       line.push(iter.next());
 
-     } else if (index > 0) {
 
-       line.push(iter.next(index));
 
-     } else {
 
-       if (predicate(line, iter.next(1).attributes || {}, i) === false) {
 
-         return;
 
-       }
 
-       i += 1;
 
-       line = new Delta();
 
-     }
 
-   }
 
-   if (line.length() > 0) {
 
-     predicate(line, {}, i);
 
-   }
 
- };
 
- Delta.prototype.transform = function (other, priority) {
 
-   priority = !!priority;
 
-   if (typeof other === 'number') {
 
-     return this.transformPosition(other, priority);
 
-   }
 
-   var thisIter = op.iterator(this.ops);
 
-   var otherIter = op.iterator(other.ops);
 
-   var delta = new Delta();
 
-   while (thisIter.hasNext() || otherIter.hasNext()) {
 
-     if (thisIter.peekType() === 'insert' && (priority || otherIter.peekType() !== 'insert')) {
 
-       delta.retain(op.length(thisIter.next()));
 
-     } else if (otherIter.peekType() === 'insert') {
 
-       delta.push(otherIter.next());
 
-     } else {
 
-       var length = Math.min(thisIter.peekLength(), otherIter.peekLength());
 
-       var thisOp = thisIter.next(length);
 
-       var otherOp = otherIter.next(length);
 
-       if (thisOp['delete']) {
 
-         // Our delete either makes their delete redundant or removes their retain
 
-         continue;
 
-       } else if (otherOp['delete']) {
 
-         delta.push(otherOp);
 
-       } else {
 
-         // We retain either their retain or insert
 
-         delta.retain(length, op.attributes.transform(thisOp.attributes, otherOp.attributes, priority));
 
-       }
 
-     }
 
-   }
 
-   return delta.chop();
 
- };
 
- Delta.prototype.transformPosition = function (index, priority) {
 
-   priority = !!priority;
 
-   var thisIter = op.iterator(this.ops);
 
-   var offset = 0;
 
-   while (thisIter.hasNext() && offset <= index) {
 
-     var length = thisIter.peekLength();
 
-     var nextType = thisIter.peekType();
 
-     thisIter.next();
 
-     if (nextType === 'delete') {
 
-       index -= Math.min(length, index - offset);
 
-       continue;
 
-     } else if (nextType === 'insert' && (offset < index || !priority)) {
 
-       index += length;
 
-     }
 
-     offset += length;
 
-   }
 
-   return index;
 
- };
 
- module.exports = Delta;
 
 
  |