530 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			530 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
if (typeof ot === 'undefined') {
 | 
						||
  // Export for browsers
 | 
						||
  var ot = {};
 | 
						||
}
 | 
						||
 | 
						||
ot.TextOperation = (function () {
 | 
						||
  'use strict';
 | 
						||
 | 
						||
  // Constructor for new operations.
 | 
						||
  function TextOperation () {
 | 
						||
    if (!this || this.constructor !== TextOperation) {
 | 
						||
      // => function was called without 'new'
 | 
						||
      return new TextOperation();
 | 
						||
    }
 | 
						||
 | 
						||
    // When an operation is applied to an input string, you can think of this as
 | 
						||
    // if an imaginary cursor runs over the entire string and skips over some
 | 
						||
    // parts, deletes some parts and inserts characters at some positions. These
 | 
						||
    // actions (skip/delete/insert) are stored as an array in the "ops" property.
 | 
						||
    this.ops = [];
 | 
						||
    // An operation's baseLength is the length of every string the operation
 | 
						||
    // can be applied to.
 | 
						||
    this.baseLength = 0;
 | 
						||
    // The targetLength is the length of every string that results from applying
 | 
						||
    // the operation on a valid input string.
 | 
						||
    this.targetLength = 0;
 | 
						||
  }
 | 
						||
 | 
						||
  TextOperation.prototype.equals = function (other) {
 | 
						||
    if (this.baseLength !== other.baseLength) { return false; }
 | 
						||
    if (this.targetLength !== other.targetLength) { return false; }
 | 
						||
    if (this.ops.length !== other.ops.length) { return false; }
 | 
						||
    for (var i = 0; i < this.ops.length; i++) {
 | 
						||
      if (this.ops[i] !== other.ops[i]) { return false; }
 | 
						||
    }
 | 
						||
    return true;
 | 
						||
  };
 | 
						||
 | 
						||
  // Operation are essentially lists of ops. There are three types of ops:
 | 
						||
  //
 | 
						||
  // * Retain ops: Advance the cursor position by a given number of characters.
 | 
						||
  //   Represented by positive ints.
 | 
						||
  // * Insert ops: Insert a given string at the current cursor position.
 | 
						||
  //   Represented by strings.
 | 
						||
  // * Delete ops: Delete the next n characters. Represented by negative ints.
 | 
						||
 | 
						||
  var isRetain = TextOperation.isRetain = function (op) {
 | 
						||
    return typeof op === 'number' && op > 0;
 | 
						||
  };
 | 
						||
 | 
						||
  var isInsert = TextOperation.isInsert = function (op) {
 | 
						||
    return typeof op === 'string';
 | 
						||
  };
 | 
						||
 | 
						||
  var isDelete = TextOperation.isDelete = function (op) {
 | 
						||
    return typeof op === 'number' && op < 0;
 | 
						||
  };
 | 
						||
 | 
						||
 | 
						||
  // After an operation is constructed, the user of the library can specify the
 | 
						||
  // actions of an operation (skip/insert/delete) with these three builder
 | 
						||
  // methods. They all return the operation for convenient chaining.
 | 
						||
 | 
						||
  // Skip over a given number of characters.
 | 
						||
  TextOperation.prototype.retain = function (n) {
 | 
						||
    if (typeof n !== 'number') {
 | 
						||
      throw new Error("retain expects an integer");
 | 
						||
    }
 | 
						||
    if (n === 0) { return this; }
 | 
						||
    this.baseLength += n;
 | 
						||
    this.targetLength += n;
 | 
						||
    if (isRetain(this.ops[this.ops.length-1])) {
 | 
						||
      // The last op is a retain op => we can merge them into one op.
 | 
						||
      this.ops[this.ops.length-1] += n;
 | 
						||
    } else {
 | 
						||
      // Create a new op.
 | 
						||
      this.ops.push(n);
 | 
						||
    }
 | 
						||
    return this;
 | 
						||
  };
 | 
						||
 | 
						||
  // Insert a string at the current position.
 | 
						||
  TextOperation.prototype.insert = function (str) {
 | 
						||
    if (typeof str !== 'string') {
 | 
						||
      throw new Error("insert expects a string");
 | 
						||
    }
 | 
						||
    if (str === '') { return this; }
 | 
						||
    this.targetLength += str.length;
 | 
						||
    var ops = this.ops;
 | 
						||
    if (isInsert(ops[ops.length-1])) {
 | 
						||
      // Merge insert op.
 | 
						||
      ops[ops.length-1] += str;
 | 
						||
    } else if (isDelete(ops[ops.length-1])) {
 | 
						||
      // It doesn't matter when an operation is applied whether the operation
 | 
						||
      // is delete(3), insert("something") or insert("something"), delete(3).
 | 
						||
      // Here we enforce that in this case, the insert op always comes first.
 | 
						||
      // This makes all operations that have the same effect when applied to
 | 
						||
      // a document of the right length equal in respect to the `equals` method.
 | 
						||
      if (isInsert(ops[ops.length-2])) {
 | 
						||
        ops[ops.length-2] += str;
 | 
						||
      } else {
 | 
						||
        ops[ops.length] = ops[ops.length-1];
 | 
						||
        ops[ops.length-2] = str;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      ops.push(str);
 | 
						||
    }
 | 
						||
    return this;
 | 
						||
  };
 | 
						||
 | 
						||
  // Delete a string at the current position.
 | 
						||
  TextOperation.prototype['delete'] = function (n) {
 | 
						||
    if (typeof n === 'string') { n = n.length; }
 | 
						||
    if (typeof n !== 'number') {
 | 
						||
      throw new Error("delete expects an integer or a string");
 | 
						||
    }
 | 
						||
    if (n === 0) { return this; }
 | 
						||
    if (n > 0) { n = -n; }
 | 
						||
    this.baseLength -= n;
 | 
						||
    if (isDelete(this.ops[this.ops.length-1])) {
 | 
						||
      this.ops[this.ops.length-1] += n;
 | 
						||
    } else {
 | 
						||
      this.ops.push(n);
 | 
						||
    }
 | 
						||
    return this;
 | 
						||
  };
 | 
						||
 | 
						||
  // Tests whether this operation has no effect.
 | 
						||
  TextOperation.prototype.isNoop = function () {
 | 
						||
    return this.ops.length === 0 || (this.ops.length === 1 && isRetain(this.ops[0]));
 | 
						||
  };
 | 
						||
 | 
						||
  // Pretty printing.
 | 
						||
  TextOperation.prototype.toString = function () {
 | 
						||
    // map: build a new array by applying a function to every element in an old
 | 
						||
    // array.
 | 
						||
    var map = Array.prototype.map || function (fn) {
 | 
						||
      var arr = this;
 | 
						||
      var newArr = [];
 | 
						||
      for (var i = 0, l = arr.length; i < l; i++) {
 | 
						||
        newArr[i] = fn(arr[i]);
 | 
						||
      }
 | 
						||
      return newArr;
 | 
						||
    };
 | 
						||
    return map.call(this.ops, function (op) {
 | 
						||
      if (isRetain(op)) {
 | 
						||
        return "retain " + op;
 | 
						||
      } else if (isInsert(op)) {
 | 
						||
        return "insert '" + op + "'";
 | 
						||
      } else {
 | 
						||
        return "delete " + (-op);
 | 
						||
      }
 | 
						||
    }).join(', ');
 | 
						||
  };
 | 
						||
 | 
						||
  // Converts operation into a JSON value.
 | 
						||
  TextOperation.prototype.toJSON = function () {
 | 
						||
    return this.ops;
 | 
						||
  };
 | 
						||
 | 
						||
  // Converts a plain JS object into an operation and validates it.
 | 
						||
  TextOperation.fromJSON = function (ops) {
 | 
						||
    var o = new TextOperation();
 | 
						||
    for (var i = 0, l = ops.length; i < l; i++) {
 | 
						||
      var op = ops[i];
 | 
						||
      if (isRetain(op)) {
 | 
						||
        o.retain(op);
 | 
						||
      } else if (isInsert(op)) {
 | 
						||
        o.insert(op);
 | 
						||
      } else if (isDelete(op)) {
 | 
						||
        o['delete'](op);
 | 
						||
      } else {
 | 
						||
        throw new Error("unknown operation: " + JSON.stringify(op));
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return o;
 | 
						||
  };
 | 
						||
 | 
						||
  // Apply an operation to a string, returning a new string. Throws an error if
 | 
						||
  // there's a mismatch between the input string and the operation.
 | 
						||
  TextOperation.prototype.apply = function (str) {
 | 
						||
    var operation = this;
 | 
						||
    if (str.length !== operation.baseLength) {
 | 
						||
      throw new Error("The operation's base length must be equal to the string's length.");
 | 
						||
    }
 | 
						||
    var newStr = [], j = 0;
 | 
						||
    var strIndex = 0;
 | 
						||
    var ops = this.ops;
 | 
						||
    for (var i = 0, l = ops.length; i < l; i++) {
 | 
						||
      var op = ops[i];
 | 
						||
      if (isRetain(op)) {
 | 
						||
        if (strIndex + op > str.length) {
 | 
						||
          throw new Error("Operation can't retain more characters than are left in the string.");
 | 
						||
        }
 | 
						||
        // Copy skipped part of the old string.
 | 
						||
        newStr[j++] = str.slice(strIndex, strIndex + op);
 | 
						||
        strIndex += op;
 | 
						||
      } else if (isInsert(op)) {
 | 
						||
        // Insert string.
 | 
						||
        newStr[j++] = op;
 | 
						||
      } else { // delete op
 | 
						||
        strIndex -= op;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    if (strIndex !== str.length) {
 | 
						||
      throw new Error("The operation didn't operate on the whole string.");
 | 
						||
    }
 | 
						||
    return newStr.join('');
 | 
						||
  };
 | 
						||
 | 
						||
  // Computes the inverse of an operation. The inverse of an operation is the
 | 
						||
  // operation that reverts the effects of the operation, e.g. when you have an
 | 
						||
  // operation 'insert("hello "); skip(6);' then the inverse is 'delete("hello ");
 | 
						||
  // skip(6);'. The inverse should be used for implementing undo.
 | 
						||
  TextOperation.prototype.invert = function (str) {
 | 
						||
    var strIndex = 0;
 | 
						||
    var inverse = new TextOperation();
 | 
						||
    var ops = this.ops;
 | 
						||
    for (var i = 0, l = ops.length; i < l; i++) {
 | 
						||
      var op = ops[i];
 | 
						||
      if (isRetain(op)) {
 | 
						||
        inverse.retain(op);
 | 
						||
        strIndex += op;
 | 
						||
      } else if (isInsert(op)) {
 | 
						||
        inverse['delete'](op.length);
 | 
						||
      } else { // delete op
 | 
						||
        inverse.insert(str.slice(strIndex, strIndex - op));
 | 
						||
        strIndex -= op;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return inverse;
 | 
						||
  };
 | 
						||
 | 
						||
  // Compose merges two consecutive operations into one operation, that
 | 
						||
  // preserves the changes of both. Or, in other words, for each input string S
 | 
						||
  // and a pair of consecutive operations A and B,
 | 
						||
  // apply(apply(S, A), B) = apply(S, compose(A, B)) must hold.
 | 
						||
  TextOperation.prototype.compose = function (operation2) {
 | 
						||
    var operation1 = this;
 | 
						||
    if (operation1.targetLength !== operation2.baseLength) {
 | 
						||
      throw new Error("The base length of the second operation has to be the target length of the first operation");
 | 
						||
    }
 | 
						||
 | 
						||
    var operation = new TextOperation(); // the combined operation
 | 
						||
    var ops1 = operation1.ops, ops2 = operation2.ops; // for fast access
 | 
						||
    var i1 = 0, i2 = 0; // current index into ops1 respectively ops2
 | 
						||
    var op1 = ops1[i1++], op2 = ops2[i2++]; // current ops
 | 
						||
    while (true) {
 | 
						||
      // Dispatch on the type of op1 and op2
 | 
						||
      if (typeof op1 === 'undefined' && typeof op2 === 'undefined') {
 | 
						||
        // end condition: both ops1 and ops2 have been processed
 | 
						||
        break;
 | 
						||
      }
 | 
						||
 | 
						||
      if (isDelete(op1)) {
 | 
						||
        operation['delete'](op1);
 | 
						||
        op1 = ops1[i1++];
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
      if (isInsert(op2)) {
 | 
						||
        operation.insert(op2);
 | 
						||
        op2 = ops2[i2++];
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      if (typeof op1 === 'undefined') {
 | 
						||
        throw new Error("Cannot compose operations: first operation is too short.");
 | 
						||
      }
 | 
						||
      if (typeof op2 === 'undefined') {
 | 
						||
        throw new Error("Cannot compose operations: first operation is too long.");
 | 
						||
      }
 | 
						||
 | 
						||
      if (isRetain(op1) && isRetain(op2)) {
 | 
						||
        if (op1 > op2) {
 | 
						||
          operation.retain(op2);
 | 
						||
          op1 = op1 - op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1 === op2) {
 | 
						||
          operation.retain(op1);
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          operation.retain(op1);
 | 
						||
          op2 = op2 - op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
      } else if (isInsert(op1) && isDelete(op2)) {
 | 
						||
        if (op1.length > -op2) {
 | 
						||
          op1 = op1.slice(-op2);
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1.length === -op2) {
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          op2 = op2 + op1.length;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
      } else if (isInsert(op1) && isRetain(op2)) {
 | 
						||
        if (op1.length > op2) {
 | 
						||
          operation.insert(op1.slice(0, op2));
 | 
						||
          op1 = op1.slice(op2);
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1.length === op2) {
 | 
						||
          operation.insert(op1);
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          operation.insert(op1);
 | 
						||
          op2 = op2 - op1.length;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
      } else if (isRetain(op1) && isDelete(op2)) {
 | 
						||
        if (op1 > -op2) {
 | 
						||
          operation['delete'](op2);
 | 
						||
          op1 = op1 + op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1 === -op2) {
 | 
						||
          operation['delete'](op2);
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          operation['delete'](op1);
 | 
						||
          op2 = op2 + op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        throw new Error(
 | 
						||
          "This shouldn't happen: op1: " +
 | 
						||
          JSON.stringify(op1) + ", op2: " +
 | 
						||
          JSON.stringify(op2)
 | 
						||
        );
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return operation;
 | 
						||
  };
 | 
						||
 | 
						||
  function getSimpleOp (operation, fn) {
 | 
						||
    var ops = operation.ops;
 | 
						||
    var isRetain = TextOperation.isRetain;
 | 
						||
    switch (ops.length) {
 | 
						||
    case 1:
 | 
						||
      return ops[0];
 | 
						||
    case 2:
 | 
						||
      return isRetain(ops[0]) ? ops[1] : (isRetain(ops[1]) ? ops[0] : null);
 | 
						||
    case 3:
 | 
						||
      if (isRetain(ops[0]) && isRetain(ops[2])) { return ops[1]; }
 | 
						||
    }
 | 
						||
    return null;
 | 
						||
  }
 | 
						||
 | 
						||
  function getStartIndex (operation) {
 | 
						||
    if (isRetain(operation.ops[0])) { return operation.ops[0]; }
 | 
						||
    return 0;
 | 
						||
  }
 | 
						||
 | 
						||
  // When you use ctrl-z to undo your latest changes, you expect the program not
 | 
						||
  // to undo every single keystroke but to undo your last sentence you wrote at
 | 
						||
  // a stretch or the deletion you did by holding the backspace key down. This
 | 
						||
  // This can be implemented by composing operations on the undo stack. This
 | 
						||
  // method can help decide whether two operations should be composed. It
 | 
						||
  // returns true if the operations are consecutive insert operations or both
 | 
						||
  // operations delete text at the same position. You may want to include other
 | 
						||
  // factors like the time since the last change in your decision.
 | 
						||
  TextOperation.prototype.shouldBeComposedWith = function (other) {
 | 
						||
    if (this.isNoop() || other.isNoop()) { return true; }
 | 
						||
 | 
						||
    var startA = getStartIndex(this), startB = getStartIndex(other);
 | 
						||
    var simpleA = getSimpleOp(this), simpleB = getSimpleOp(other);
 | 
						||
    if (!simpleA || !simpleB) { return false; }
 | 
						||
 | 
						||
    if (isInsert(simpleA) && isInsert(simpleB)) {
 | 
						||
      return startA + simpleA.length === startB;
 | 
						||
    }
 | 
						||
 | 
						||
    if (isDelete(simpleA) && isDelete(simpleB)) {
 | 
						||
      // there are two possibilities to delete: with backspace and with the
 | 
						||
      // delete key.
 | 
						||
      return (startB - simpleB === startA) || startA === startB;
 | 
						||
    }
 | 
						||
 | 
						||
    return false;
 | 
						||
  };
 | 
						||
 | 
						||
  // Decides whether two operations should be composed with each other
 | 
						||
  // if they were inverted, that is
 | 
						||
  // `shouldBeComposedWith(a, b) = shouldBeComposedWithInverted(b^{-1}, a^{-1})`.
 | 
						||
  TextOperation.prototype.shouldBeComposedWithInverted = function (other) {
 | 
						||
    if (this.isNoop() || other.isNoop()) { return true; }
 | 
						||
 | 
						||
    var startA = getStartIndex(this), startB = getStartIndex(other);
 | 
						||
    var simpleA = getSimpleOp(this), simpleB = getSimpleOp(other);
 | 
						||
    if (!simpleA || !simpleB) { return false; }
 | 
						||
 | 
						||
    if (isInsert(simpleA) && isInsert(simpleB)) {
 | 
						||
      return startA + simpleA.length === startB || startA === startB;
 | 
						||
    }
 | 
						||
 | 
						||
    if (isDelete(simpleA) && isDelete(simpleB)) {
 | 
						||
      return startB - simpleB === startA;
 | 
						||
    }
 | 
						||
 | 
						||
    return false;
 | 
						||
  };
 | 
						||
 | 
						||
  // Transform takes two operations A and B that happened concurrently and
 | 
						||
  // produces two operations A' and B' (in an array) such that
 | 
						||
  // `apply(apply(S, A), B') = apply(apply(S, B), A')`. This function is the
 | 
						||
  // heart of OT.
 | 
						||
  TextOperation.transform = function (operation1, operation2) {
 | 
						||
    if (operation1.baseLength !== operation2.baseLength) {
 | 
						||
      throw new Error("Both operations have to have the same base length");
 | 
						||
    }
 | 
						||
 | 
						||
    var operation1prime = new TextOperation();
 | 
						||
    var operation2prime = new TextOperation();
 | 
						||
    var ops1 = operation1.ops, ops2 = operation2.ops;
 | 
						||
    var i1 = 0, i2 = 0;
 | 
						||
    var op1 = ops1[i1++], op2 = ops2[i2++];
 | 
						||
    while (true) {
 | 
						||
      // At every iteration of the loop, the imaginary cursor that both
 | 
						||
      // operation1 and operation2 have that operates on the input string must
 | 
						||
      // have the same position in the input string.
 | 
						||
 | 
						||
      if (typeof op1 === 'undefined' && typeof op2 === 'undefined') {
 | 
						||
        // end condition: both ops1 and ops2 have been processed
 | 
						||
        break;
 | 
						||
      }
 | 
						||
 | 
						||
      // next two cases: one or both ops are insert ops
 | 
						||
      // => insert the string in the corresponding prime operation, skip it in
 | 
						||
      // the other one. If both op1 and op2 are insert ops, prefer op1.
 | 
						||
      if (isInsert(op1)) {
 | 
						||
        operation1prime.insert(op1);
 | 
						||
        operation2prime.retain(op1.length);
 | 
						||
        op1 = ops1[i1++];
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
      if (isInsert(op2)) {
 | 
						||
        operation1prime.retain(op2.length);
 | 
						||
        operation2prime.insert(op2);
 | 
						||
        op2 = ops2[i2++];
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      if (typeof op1 === 'undefined') {
 | 
						||
        throw new Error("Cannot compose operations: first operation is too short.");
 | 
						||
      }
 | 
						||
      if (typeof op2 === 'undefined') {
 | 
						||
        throw new Error("Cannot compose operations: first operation is too long.");
 | 
						||
      }
 | 
						||
 | 
						||
      var minl;
 | 
						||
      if (isRetain(op1) && isRetain(op2)) {
 | 
						||
        // Simple case: retain/retain
 | 
						||
        if (op1 > op2) {
 | 
						||
          minl = op2;
 | 
						||
          op1 = op1 - op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1 === op2) {
 | 
						||
          minl = op2;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          minl = op1;
 | 
						||
          op2 = op2 - op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
        operation1prime.retain(minl);
 | 
						||
        operation2prime.retain(minl);
 | 
						||
      } else if (isDelete(op1) && isDelete(op2)) {
 | 
						||
        // Both operations delete the same string at the same position. We don't
 | 
						||
        // need to produce any operations, we just skip over the delete ops and
 | 
						||
        // handle the case that one operation deletes more than the other.
 | 
						||
        if (-op1 > -op2) {
 | 
						||
          op1 = op1 - op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1 === op2) {
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          op2 = op2 - op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
      // next two cases: delete/retain and retain/delete
 | 
						||
      } else if (isDelete(op1) && isRetain(op2)) {
 | 
						||
        if (-op1 > op2) {
 | 
						||
          minl = op2;
 | 
						||
          op1 = op1 + op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (-op1 === op2) {
 | 
						||
          minl = op2;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          minl = -op1;
 | 
						||
          op2 = op2 + op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
        operation1prime['delete'](minl);
 | 
						||
      } else if (isRetain(op1) && isDelete(op2)) {
 | 
						||
        if (op1 > -op2) {
 | 
						||
          minl = -op2;
 | 
						||
          op1 = op1 + op2;
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else if (op1 === -op2) {
 | 
						||
          minl = op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
          op2 = ops2[i2++];
 | 
						||
        } else {
 | 
						||
          minl = op1;
 | 
						||
          op2 = op2 + op1;
 | 
						||
          op1 = ops1[i1++];
 | 
						||
        }
 | 
						||
        operation2prime['delete'](minl);
 | 
						||
      } else {
 | 
						||
        throw new Error("The two operations aren't compatible");
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    return [operation1prime, operation2prime];
 | 
						||
  };
 | 
						||
 | 
						||
  return TextOperation;
 | 
						||
 | 
						||
}());
 | 
						||
 | 
						||
// Export for CommonJS
 | 
						||
if (typeof module === 'object') {
 | 
						||
  module.exports = ot.TextOperation;
 | 
						||
} |