added text as a custom type, more tests are working
This commit is contained in:
		
							parent
							
								
									860934de06
								
							
						
					
					
						commit
						2e9f8f6d03
					
				
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -7,7 +7,10 @@ module.exports = function() {
 | 
			
		||||
  ops = {};
 | 
			
		||||
  execution_listener = [];
 | 
			
		||||
  ops.Operation = (function() {
 | 
			
		||||
    function Operation(uid) {
 | 
			
		||||
    function Operation(custom_type, uid) {
 | 
			
		||||
      if (custom_type != null) {
 | 
			
		||||
        this.custom_type = custom_type;
 | 
			
		||||
      }
 | 
			
		||||
      this.is_deleted = false;
 | 
			
		||||
      this.garbage_collected = false;
 | 
			
		||||
      this.event_listeners = [];
 | 
			
		||||
@ -181,9 +184,9 @@ module.exports = function() {
 | 
			
		||||
  ops.Delete = (function(_super) {
 | 
			
		||||
    __extends(Delete, _super);
 | 
			
		||||
 | 
			
		||||
    function Delete(uid, deletes) {
 | 
			
		||||
    function Delete(custom_type, uid, deletes) {
 | 
			
		||||
      this.saveOperation('deletes', deletes);
 | 
			
		||||
      Delete.__super__.constructor.call(this, uid);
 | 
			
		||||
      Delete.__super__.constructor.call(this, custom_type, uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Delete.prototype.type = "Delete";
 | 
			
		||||
@ -215,12 +218,12 @@ module.exports = function() {
 | 
			
		||||
  ops.Delete.parse = function(o) {
 | 
			
		||||
    var deletes_uid, uid;
 | 
			
		||||
    uid = o['uid'], deletes_uid = o['deletes'];
 | 
			
		||||
    return new this(uid, deletes_uid);
 | 
			
		||||
    return new this(null, uid, deletes_uid);
 | 
			
		||||
  };
 | 
			
		||||
  ops.Insert = (function(_super) {
 | 
			
		||||
    __extends(Insert, _super);
 | 
			
		||||
 | 
			
		||||
    function Insert(content, uid, prev_cl, next_cl, origin, parent) {
 | 
			
		||||
    function Insert(custom_type, content, uid, prev_cl, next_cl, origin, parent) {
 | 
			
		||||
      if (content === void 0) {
 | 
			
		||||
 | 
			
		||||
      } else if ((content != null) && (content.creator != null)) {
 | 
			
		||||
@ -236,7 +239,7 @@ module.exports = function() {
 | 
			
		||||
      } else {
 | 
			
		||||
        this.saveOperation('origin', prev_cl);
 | 
			
		||||
      }
 | 
			
		||||
      Insert.__super__.constructor.call(this, uid);
 | 
			
		||||
      Insert.__super__.constructor.call(this, custom_type, uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Insert.prototype.type = "Insert";
 | 
			
		||||
@ -371,14 +374,21 @@ module.exports = function() {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Insert.prototype.callOperationSpecificInsertEvents = function() {
 | 
			
		||||
      var _ref;
 | 
			
		||||
      var getContentType, _ref;
 | 
			
		||||
      getContentType = function(content) {
 | 
			
		||||
        if (content instanceof ops.Operation) {
 | 
			
		||||
          return content.getCustomType();
 | 
			
		||||
        } else {
 | 
			
		||||
          return content;
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      return (_ref = this.parent) != null ? _ref.callEvent([
 | 
			
		||||
        {
 | 
			
		||||
          type: "insert",
 | 
			
		||||
          position: this.getPosition(),
 | 
			
		||||
          object: this.parent,
 | 
			
		||||
          object: this.parent.getCustomType(),
 | 
			
		||||
          changedBy: this.uid.creator,
 | 
			
		||||
          value: this.content
 | 
			
		||||
          value: getContentType(this.content)
 | 
			
		||||
        }
 | 
			
		||||
      ]) : void 0;
 | 
			
		||||
    };
 | 
			
		||||
@ -388,7 +398,7 @@ module.exports = function() {
 | 
			
		||||
        {
 | 
			
		||||
          type: "delete",
 | 
			
		||||
          position: this.getPosition(),
 | 
			
		||||
          object: this.parent,
 | 
			
		||||
          object: this.parent.getCustomType(),
 | 
			
		||||
          length: 1,
 | 
			
		||||
          changedBy: o.uid.creator
 | 
			
		||||
        }
 | 
			
		||||
@ -442,14 +452,14 @@ module.exports = function() {
 | 
			
		||||
    if (typeof content === "string") {
 | 
			
		||||
      content = JSON.parse(content);
 | 
			
		||||
    }
 | 
			
		||||
    return new this(content, uid, prev, next, origin, parent);
 | 
			
		||||
    return new this(null, content, uid, prev, next, origin, parent);
 | 
			
		||||
  };
 | 
			
		||||
  ops.ImmutableObject = (function(_super) {
 | 
			
		||||
    __extends(ImmutableObject, _super);
 | 
			
		||||
 | 
			
		||||
    function ImmutableObject(uid, _at_content) {
 | 
			
		||||
    function ImmutableObject(custom_type, uid, _at_content) {
 | 
			
		||||
      this.content = _at_content;
 | 
			
		||||
      ImmutableObject.__super__.constructor.call(this, uid);
 | 
			
		||||
      ImmutableObject.__super__.constructor.call(this, custom_type, uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ImmutableObject.prototype.type = "ImmutableObject";
 | 
			
		||||
@ -474,7 +484,7 @@ module.exports = function() {
 | 
			
		||||
  ops.ImmutableObject.parse = function(json) {
 | 
			
		||||
    var content, uid;
 | 
			
		||||
    uid = json['uid'], content = json['content'];
 | 
			
		||||
    return new this(uid, content);
 | 
			
		||||
    return new this(null, uid, content);
 | 
			
		||||
  };
 | 
			
		||||
  ops.Delimiter = (function(_super) {
 | 
			
		||||
    __extends(Delimiter, _super);
 | 
			
		||||
@ -483,7 +493,7 @@ module.exports = function() {
 | 
			
		||||
      this.saveOperation('prev_cl', prev_cl);
 | 
			
		||||
      this.saveOperation('next_cl', next_cl);
 | 
			
		||||
      this.saveOperation('origin', prev_cl);
 | 
			
		||||
      Delimiter.__super__.constructor.call(this, {
 | 
			
		||||
      Delimiter.__super__.constructor.call(this, null, {
 | 
			
		||||
        noOperation: true
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -12,11 +12,8 @@ module.exports = function() {
 | 
			
		||||
    __extends(MapManager, _super);
 | 
			
		||||
 | 
			
		||||
    function MapManager(custom_type, uid) {
 | 
			
		||||
      if (custom_type != null) {
 | 
			
		||||
        this.custom_type = custom_type;
 | 
			
		||||
      }
 | 
			
		||||
      this._map = {};
 | 
			
		||||
      MapManager.__super__.constructor.call(this, uid);
 | 
			
		||||
      MapManager.__super__.constructor.call(this, custom_type, uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MapManager.prototype.type = "MapManager";
 | 
			
		||||
@ -48,8 +45,8 @@ module.exports = function() {
 | 
			
		||||
    MapManager.prototype.val = function(name, content) {
 | 
			
		||||
      var o, prop, rep, res, result, _ref;
 | 
			
		||||
      if (arguments.length > 1) {
 | 
			
		||||
        if ((content != null) && (content._model != null) && content._model instanceof ops.Operation) {
 | 
			
		||||
          rep = content._model;
 | 
			
		||||
        if ((content != null) && (content._getModel != null)) {
 | 
			
		||||
          rep = content._getModel(this.custom_types, this.operations);
 | 
			
		||||
        } else {
 | 
			
		||||
          rep = content;
 | 
			
		||||
        }
 | 
			
		||||
@ -73,12 +70,7 @@ module.exports = function() {
 | 
			
		||||
        for (name in _ref) {
 | 
			
		||||
          o = _ref[name];
 | 
			
		||||
          if (!o.isContentDeleted()) {
 | 
			
		||||
            res = prop.val();
 | 
			
		||||
            if (res instanceof ops.Operation) {
 | 
			
		||||
              result[name] = res.getCustomType();
 | 
			
		||||
            } else {
 | 
			
		||||
              result[name] = res;
 | 
			
		||||
            }
 | 
			
		||||
            result[name] = o.val();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
@ -105,7 +97,7 @@ module.exports = function() {
 | 
			
		||||
          sub: property_name,
 | 
			
		||||
          alt: this
 | 
			
		||||
        };
 | 
			
		||||
        rm = new ops.ReplaceManager(event_properties, event_this, rm_uid);
 | 
			
		||||
        rm = new ops.ReplaceManager(null, event_properties, event_this, rm_uid);
 | 
			
		||||
        this._map[property_name] = rm;
 | 
			
		||||
        rm.setParent(this, property_name);
 | 
			
		||||
        rm.execute();
 | 
			
		||||
@ -138,13 +130,13 @@ module.exports = function() {
 | 
			
		||||
  ops.ListManager = (function(_super) {
 | 
			
		||||
    __extends(ListManager, _super);
 | 
			
		||||
 | 
			
		||||
    function ListManager(uid) {
 | 
			
		||||
    function ListManager(custom_type, uid) {
 | 
			
		||||
      this.beginning = new ops.Delimiter(void 0, void 0);
 | 
			
		||||
      this.end = new ops.Delimiter(this.beginning, void 0);
 | 
			
		||||
      this.beginning.next_cl = this.end;
 | 
			
		||||
      this.beginning.execute();
 | 
			
		||||
      this.end.execute();
 | 
			
		||||
      ListManager.__super__.constructor.call(this, uid);
 | 
			
		||||
      ListManager.__super__.constructor.call(this, custom_type, uid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ListManager.prototype.type = "ListManager";
 | 
			
		||||
@ -281,42 +273,29 @@ module.exports = function() {
 | 
			
		||||
      return this.insertAfter(this.end.prev_cl, content);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ListManager.prototype.insertAfter = function(left, content, options) {
 | 
			
		||||
      var c, createContent, right, tmp, _i, _len;
 | 
			
		||||
      createContent = function(content, options) {
 | 
			
		||||
        var type;
 | 
			
		||||
        if ((content != null) && (content.constructor != null)) {
 | 
			
		||||
          type = ops[content.constructor.name];
 | 
			
		||||
          if ((type != null) && (type.create != null)) {
 | 
			
		||||
            return type.create(content, options);
 | 
			
		||||
          } else {
 | 
			
		||||
            throw new Error("The " + content.constructor.name + "-type is not (yet) supported in Y.");
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          return content;
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    ListManager.prototype.insertAfter = function(left, content) {
 | 
			
		||||
      var c, right, tmp, _i, _len;
 | 
			
		||||
      right = left.next_cl;
 | 
			
		||||
      while (right.isDeleted()) {
 | 
			
		||||
        right = right.next_cl;
 | 
			
		||||
      }
 | 
			
		||||
      left = right.prev_cl;
 | 
			
		||||
      if (content instanceof ops.Operation) {
 | 
			
		||||
        (new ops.Insert(content, void 0, left, right)).execute();
 | 
			
		||||
        (new ops.Insert(null, content, void 0, left, right)).execute();
 | 
			
		||||
      } else {
 | 
			
		||||
        for (_i = 0, _len = content.length; _i < _len; _i++) {
 | 
			
		||||
          c = content[_i];
 | 
			
		||||
          tmp = (new ops.Insert(createContent(c, options), void 0, left, right)).execute();
 | 
			
		||||
          tmp = (new ops.Insert(null, c, void 0, left, right)).execute();
 | 
			
		||||
          left = tmp;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return this;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ListManager.prototype.insert = function(position, content, options) {
 | 
			
		||||
    ListManager.prototype.insert = function(position, content) {
 | 
			
		||||
      var ith;
 | 
			
		||||
      ith = this.getOperationByPosition(position);
 | 
			
		||||
      return this.insertAfter(ith, [content], options);
 | 
			
		||||
      return this.insertAfter(ith, [content]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ListManager.prototype["delete"] = function(position, length) {
 | 
			
		||||
@ -327,7 +306,7 @@ module.exports = function() {
 | 
			
		||||
        if (o instanceof ops.Delimiter) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        d = (new ops.Delete(void 0, o)).execute();
 | 
			
		||||
        d = (new ops.Delete(null, void 0, o)).execute();
 | 
			
		||||
        o = o.next_cl;
 | 
			
		||||
        while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) {
 | 
			
		||||
          o = o.next_cl;
 | 
			
		||||
@ -343,6 +322,11 @@ module.exports = function() {
 | 
			
		||||
        'type': this.type,
 | 
			
		||||
        'uid': this.getUid()
 | 
			
		||||
      };
 | 
			
		||||
      if (this.custom_type.constructor === String) {
 | 
			
		||||
        json.custom_type = this.custom_type;
 | 
			
		||||
      } else {
 | 
			
		||||
        json.custom_type = this.custom_type._name;
 | 
			
		||||
      }
 | 
			
		||||
      return json;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -350,34 +334,20 @@ module.exports = function() {
 | 
			
		||||
 | 
			
		||||
  })(ops.Operation);
 | 
			
		||||
  ops.ListManager.parse = function(json) {
 | 
			
		||||
    var uid;
 | 
			
		||||
    uid = json['uid'];
 | 
			
		||||
    return new this(uid);
 | 
			
		||||
  };
 | 
			
		||||
  ops.Array = function() {};
 | 
			
		||||
  ops.Array.create = function(content, mutable) {
 | 
			
		||||
    var ith, list;
 | 
			
		||||
    if (mutable === "mutable") {
 | 
			
		||||
      list = new ops.ListManager().execute();
 | 
			
		||||
      ith = list.getOperationByPosition(0);
 | 
			
		||||
      list.insertAfter(ith, content);
 | 
			
		||||
      return list;
 | 
			
		||||
    } else if ((mutable == null) || (mutable === "immutable")) {
 | 
			
		||||
      return content;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error("Specify either \"mutable\" or \"immutable\"!!");
 | 
			
		||||
    }
 | 
			
		||||
    var custom_type, uid;
 | 
			
		||||
    uid = json['uid'], custom_type = json['custom_type'];
 | 
			
		||||
    return new this(custom_type, uid);
 | 
			
		||||
  };
 | 
			
		||||
  ops.ReplaceManager = (function(_super) {
 | 
			
		||||
    __extends(ReplaceManager, _super);
 | 
			
		||||
 | 
			
		||||
    function ReplaceManager(_at_event_properties, _at_event_this, uid, beginning, end) {
 | 
			
		||||
    function ReplaceManager(custom_type, _at_event_properties, _at_event_this, uid, beginning, end) {
 | 
			
		||||
      this.event_properties = _at_event_properties;
 | 
			
		||||
      this.event_this = _at_event_this;
 | 
			
		||||
      if (this.event_properties['object'] == null) {
 | 
			
		||||
        this.event_properties['object'] = this.event_this;
 | 
			
		||||
      }
 | 
			
		||||
      ReplaceManager.__super__.constructor.call(this, uid, beginning, end);
 | 
			
		||||
      ReplaceManager.__super__.constructor.call(this, custom_type, uid, beginning, end);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ReplaceManager.prototype.type = "ReplaceManager";
 | 
			
		||||
@ -415,7 +385,7 @@ module.exports = function() {
 | 
			
		||||
    ReplaceManager.prototype.replace = function(content, replaceable_uid) {
 | 
			
		||||
      var o, relp;
 | 
			
		||||
      o = this.getLastOperation();
 | 
			
		||||
      relp = (new ops.Replaceable(content, this, replaceable_uid, o, o.next_cl)).execute();
 | 
			
		||||
      relp = (new ops.Replaceable(null, content, this, replaceable_uid, o, o.next_cl)).execute();
 | 
			
		||||
      return void 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -424,7 +394,7 @@ module.exports = function() {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ReplaceManager.prototype.deleteContent = function() {
 | 
			
		||||
      (new ops.Delete(void 0, this.getLastOperation().uid)).execute();
 | 
			
		||||
      (new ops.Delete(null, void 0, this.getLastOperation().uid)).execute();
 | 
			
		||||
      return void 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -451,9 +421,9 @@ module.exports = function() {
 | 
			
		||||
  ops.Replaceable = (function(_super) {
 | 
			
		||||
    __extends(Replaceable, _super);
 | 
			
		||||
 | 
			
		||||
    function Replaceable(content, parent, uid, prev, next, origin, is_deleted) {
 | 
			
		||||
    function Replaceable(custom_type, content, parent, uid, prev, next, origin, is_deleted) {
 | 
			
		||||
      this.saveOperation('parent', parent);
 | 
			
		||||
      Replaceable.__super__.constructor.call(this, content, uid, prev, next, origin);
 | 
			
		||||
      Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin);
 | 
			
		||||
      this.is_deleted = is_deleted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -560,9 +530,9 @@ module.exports = function() {
 | 
			
		||||
 | 
			
		||||
  })(ops.Insert);
 | 
			
		||||
  ops.Replaceable.parse = function(json) {
 | 
			
		||||
    var content, is_deleted, next, origin, parent, prev, uid;
 | 
			
		||||
    content = json['content'], parent = json['parent'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], is_deleted = json['is_deleted'];
 | 
			
		||||
    return new this(content, parent, uid, prev, next, origin, is_deleted);
 | 
			
		||||
    var content, custom_type, is_deleted, next, origin, parent, prev, uid;
 | 
			
		||||
    content = json['content'], parent = json['parent'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], is_deleted = json['is_deleted'], custom_type = json['custom_type'];
 | 
			
		||||
    return new this(custom_type, content, parent, uid, prev, next, origin, is_deleted);
 | 
			
		||||
  };
 | 
			
		||||
  return basic_ops;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,10 @@ YObject = (function() {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YObject.prototype["delete"] = function(name) {
 | 
			
		||||
    return this._model["delete"](name);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return YObject;
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										335
									
								
								build/node/Types/Text.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										335
									
								
								build/node/Types/Text.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,335 @@
 | 
			
		||||
var YText;
 | 
			
		||||
 | 
			
		||||
YText = (function() {
 | 
			
		||||
  function YText(text) {
 | 
			
		||||
    this.textfields = [];
 | 
			
		||||
    if (text == null) {
 | 
			
		||||
      this._text = "";
 | 
			
		||||
    } else if (text.constructor === String) {
 | 
			
		||||
      this._text = text;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error("Y.Text expects a String as a constructor");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  YText.prototype._name = "Text";
 | 
			
		||||
 | 
			
		||||
  YText.prototype._getModel = function(types, ops) {
 | 
			
		||||
    if (this._model == null) {
 | 
			
		||||
      this._model = new ops.ListManager(this).execute();
 | 
			
		||||
      this.insert(0, this._text);
 | 
			
		||||
    }
 | 
			
		||||
    delete this._text;
 | 
			
		||||
    return this._model;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype._setModel = function(_at__model) {
 | 
			
		||||
    this._model = _at__model;
 | 
			
		||||
    return delete this._text;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.val = function() {
 | 
			
		||||
    return this._model.fold("", function(left, o) {
 | 
			
		||||
      return left + o.val();
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.observe = function() {
 | 
			
		||||
    return this._model.observe.apply(this._model, arguments);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.unobserve = function() {
 | 
			
		||||
    return this._model.unobserve.apply(this._model, arguments);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.toString = function() {
 | 
			
		||||
    return this.val();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.insert = function(position, content) {
 | 
			
		||||
    var ith;
 | 
			
		||||
    if (content.constructor !== String) {
 | 
			
		||||
      throw new Error("Y.String.insert expects a String as the second parameter!");
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof position !== "number") {
 | 
			
		||||
      throw new Error("Y.String.insert expects a Number as the second parameter!");
 | 
			
		||||
    }
 | 
			
		||||
    if (content.length > 0) {
 | 
			
		||||
      ith = this._model.getOperationByPosition(position);
 | 
			
		||||
      return this._model.insertAfter(ith, content);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype["delete"] = function(position, length) {
 | 
			
		||||
    return this._model["delete"](position, length);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  YText.prototype.bind = function(textfield, dom_root) {
 | 
			
		||||
    var createRange, creator_token, t, word, writeContent, writeRange, _i, _len, _ref;
 | 
			
		||||
    if (dom_root == null) {
 | 
			
		||||
      dom_root = window;
 | 
			
		||||
    }
 | 
			
		||||
    if (dom_root.getSelection == null) {
 | 
			
		||||
      dom_root = window;
 | 
			
		||||
    }
 | 
			
		||||
    _ref = this.textfields;
 | 
			
		||||
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
 | 
			
		||||
      t = _ref[_i];
 | 
			
		||||
      if (t === textfield) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    creator_token = false;
 | 
			
		||||
    word = this;
 | 
			
		||||
    textfield.value = this.val();
 | 
			
		||||
    this.textfields.push(textfield);
 | 
			
		||||
    if ((textfield.selectionStart != null) && (textfield.setSelectionRange != null)) {
 | 
			
		||||
      createRange = function(fix) {
 | 
			
		||||
        var left, right;
 | 
			
		||||
        left = textfield.selectionStart;
 | 
			
		||||
        right = textfield.selectionEnd;
 | 
			
		||||
        if (fix != null) {
 | 
			
		||||
          left = fix(left);
 | 
			
		||||
          right = fix(right);
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
          left: left,
 | 
			
		||||
          right: right
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
      writeRange = function(range) {
 | 
			
		||||
        writeContent(word.val());
 | 
			
		||||
        return textfield.setSelectionRange(range.left, range.right);
 | 
			
		||||
      };
 | 
			
		||||
      writeContent = function(content) {
 | 
			
		||||
        return textfield.value = content;
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      createRange = function(fix) {
 | 
			
		||||
        var clength, edited_element, range, s;
 | 
			
		||||
        range = {};
 | 
			
		||||
        s = dom_root.getSelection();
 | 
			
		||||
        clength = textfield.textContent.length;
 | 
			
		||||
        range.left = Math.min(s.anchorOffset, clength);
 | 
			
		||||
        range.right = Math.min(s.focusOffset, clength);
 | 
			
		||||
        if (fix != null) {
 | 
			
		||||
          range.left = fix(range.left);
 | 
			
		||||
          range.right = fix(range.right);
 | 
			
		||||
        }
 | 
			
		||||
        edited_element = s.focusNode;
 | 
			
		||||
        if (edited_element === textfield || edited_element === textfield.childNodes[0]) {
 | 
			
		||||
          range.isReal = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          range.isReal = false;
 | 
			
		||||
        }
 | 
			
		||||
        return range;
 | 
			
		||||
      };
 | 
			
		||||
      writeRange = function(range) {
 | 
			
		||||
        var r, s, textnode;
 | 
			
		||||
        writeContent(word.val());
 | 
			
		||||
        textnode = textfield.childNodes[0];
 | 
			
		||||
        if (range.isReal && (textnode != null)) {
 | 
			
		||||
          if (range.left < 0) {
 | 
			
		||||
            range.left = 0;
 | 
			
		||||
          }
 | 
			
		||||
          range.right = Math.max(range.left, range.right);
 | 
			
		||||
          if (range.right > textnode.length) {
 | 
			
		||||
            range.right = textnode.length;
 | 
			
		||||
          }
 | 
			
		||||
          range.left = Math.min(range.left, range.right);
 | 
			
		||||
          r = document.createRange();
 | 
			
		||||
          r.setStart(textnode, range.left);
 | 
			
		||||
          r.setEnd(textnode, range.right);
 | 
			
		||||
          s = window.getSelection();
 | 
			
		||||
          s.removeAllRanges();
 | 
			
		||||
          return s.addRange(r);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      writeContent = function(content) {
 | 
			
		||||
        var c, content_array, i, _j, _len1, _results;
 | 
			
		||||
        content_array = content.replace(new RegExp("\n", 'g'), " ").split(" ");
 | 
			
		||||
        textfield.innerText = "";
 | 
			
		||||
        _results = [];
 | 
			
		||||
        for (i = _j = 0, _len1 = content_array.length; _j < _len1; i = ++_j) {
 | 
			
		||||
          c = content_array[i];
 | 
			
		||||
          textfield.innerText += c;
 | 
			
		||||
          if (i !== content_array.length - 1) {
 | 
			
		||||
            _results.push(textfield.innerHTML += ' ');
 | 
			
		||||
          } else {
 | 
			
		||||
            _results.push(void 0);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return _results;
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    writeContent(this.val());
 | 
			
		||||
    this.observe(function(events) {
 | 
			
		||||
      var event, fix, o_pos, r, _j, _len1, _results;
 | 
			
		||||
      _results = [];
 | 
			
		||||
      for (_j = 0, _len1 = events.length; _j < _len1; _j++) {
 | 
			
		||||
        event = events[_j];
 | 
			
		||||
        if (!creator_token) {
 | 
			
		||||
          if (event.type === "insert") {
 | 
			
		||||
            o_pos = event.position;
 | 
			
		||||
            fix = function(cursor) {
 | 
			
		||||
              if (cursor <= o_pos) {
 | 
			
		||||
                return cursor;
 | 
			
		||||
              } else {
 | 
			
		||||
                cursor += 1;
 | 
			
		||||
                return cursor;
 | 
			
		||||
              }
 | 
			
		||||
            };
 | 
			
		||||
            r = createRange(fix);
 | 
			
		||||
            _results.push(writeRange(r));
 | 
			
		||||
          } else if (event.type === "delete") {
 | 
			
		||||
            o_pos = event.position;
 | 
			
		||||
            fix = function(cursor) {
 | 
			
		||||
              if (cursor < o_pos) {
 | 
			
		||||
                return cursor;
 | 
			
		||||
              } else {
 | 
			
		||||
                cursor -= 1;
 | 
			
		||||
                return cursor;
 | 
			
		||||
              }
 | 
			
		||||
            };
 | 
			
		||||
            r = createRange(fix);
 | 
			
		||||
            _results.push(writeRange(r));
 | 
			
		||||
          } else {
 | 
			
		||||
            _results.push(void 0);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          _results.push(void 0);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return _results;
 | 
			
		||||
    });
 | 
			
		||||
    textfield.onkeypress = function(event) {
 | 
			
		||||
      var char, diff, pos, r;
 | 
			
		||||
      if (word.is_deleted) {
 | 
			
		||||
        textfield.onkeypress = null;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      creator_token = true;
 | 
			
		||||
      char = null;
 | 
			
		||||
      if (event.keyCode === 13) {
 | 
			
		||||
        char = '\n';
 | 
			
		||||
      } else if (event.key != null) {
 | 
			
		||||
        if (event.charCode === 32) {
 | 
			
		||||
          char = " ";
 | 
			
		||||
        } else {
 | 
			
		||||
          char = event.key;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        char = window.String.fromCharCode(event.keyCode);
 | 
			
		||||
      }
 | 
			
		||||
      if (char.length > 1) {
 | 
			
		||||
        return true;
 | 
			
		||||
      } else if (char.length > 0) {
 | 
			
		||||
        r = createRange();
 | 
			
		||||
        pos = Math.min(r.left, r.right);
 | 
			
		||||
        diff = Math.abs(r.right - r.left);
 | 
			
		||||
        word["delete"](pos, diff);
 | 
			
		||||
        word.insert(pos, char);
 | 
			
		||||
        r.left = pos + char.length;
 | 
			
		||||
        r.right = r.left;
 | 
			
		||||
        writeRange(r);
 | 
			
		||||
      }
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
      creator_token = false;
 | 
			
		||||
      return false;
 | 
			
		||||
    };
 | 
			
		||||
    textfield.onpaste = function(event) {
 | 
			
		||||
      if (word.is_deleted) {
 | 
			
		||||
        textfield.onpaste = null;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      return event.preventDefault();
 | 
			
		||||
    };
 | 
			
		||||
    textfield.oncut = function(event) {
 | 
			
		||||
      if (word.is_deleted) {
 | 
			
		||||
        textfield.oncut = null;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      return event.preventDefault();
 | 
			
		||||
    };
 | 
			
		||||
    return textfield.onkeydown = function(event) {
 | 
			
		||||
      var del_length, diff, new_pos, pos, r, val;
 | 
			
		||||
      creator_token = true;
 | 
			
		||||
      if (word.is_deleted) {
 | 
			
		||||
        textfield.onkeydown = null;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      r = createRange();
 | 
			
		||||
      pos = Math.min(r.left, r.right, word.val().length);
 | 
			
		||||
      diff = Math.abs(r.left - r.right);
 | 
			
		||||
      if ((event.keyCode != null) && event.keyCode === 8) {
 | 
			
		||||
        if (diff > 0) {
 | 
			
		||||
          word["delete"](pos, diff);
 | 
			
		||||
          r.left = pos;
 | 
			
		||||
          r.right = pos;
 | 
			
		||||
          writeRange(r);
 | 
			
		||||
        } else {
 | 
			
		||||
          if ((event.ctrlKey != null) && event.ctrlKey) {
 | 
			
		||||
            val = word.val();
 | 
			
		||||
            new_pos = pos;
 | 
			
		||||
            del_length = 0;
 | 
			
		||||
            if (pos > 0) {
 | 
			
		||||
              new_pos--;
 | 
			
		||||
              del_length++;
 | 
			
		||||
            }
 | 
			
		||||
            while (new_pos > 0 && val[new_pos] !== " " && val[new_pos] !== '\n') {
 | 
			
		||||
              new_pos--;
 | 
			
		||||
              del_length++;
 | 
			
		||||
            }
 | 
			
		||||
            word["delete"](new_pos, pos - new_pos);
 | 
			
		||||
            r.left = new_pos;
 | 
			
		||||
            r.right = new_pos;
 | 
			
		||||
            writeRange(r);
 | 
			
		||||
          } else {
 | 
			
		||||
            if (pos > 0) {
 | 
			
		||||
              word["delete"](pos - 1, 1);
 | 
			
		||||
              r.left = pos - 1;
 | 
			
		||||
              r.right = pos - 1;
 | 
			
		||||
              writeRange(r);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        creator_token = false;
 | 
			
		||||
        return false;
 | 
			
		||||
      } else if ((event.keyCode != null) && event.keyCode === 46) {
 | 
			
		||||
        if (diff > 0) {
 | 
			
		||||
          word["delete"](pos, diff);
 | 
			
		||||
          r.left = pos;
 | 
			
		||||
          r.right = pos;
 | 
			
		||||
          writeRange(r);
 | 
			
		||||
        } else {
 | 
			
		||||
          word["delete"](pos, 1);
 | 
			
		||||
          r.left = pos;
 | 
			
		||||
          r.right = pos;
 | 
			
		||||
          writeRange(r);
 | 
			
		||||
        }
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        creator_token = false;
 | 
			
		||||
        return false;
 | 
			
		||||
      } else {
 | 
			
		||||
        creator_token = false;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return YText;
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
if (typeof window !== "undefined" && window !== null) {
 | 
			
		||||
  if (window.Y != null) {
 | 
			
		||||
    window.Y.Text = YText;
 | 
			
		||||
  } else {
 | 
			
		||||
    throw new Error("You must first import Y!");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (typeof module !== "undefined" && module !== null) {
 | 
			
		||||
  module.exports = YText;
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
var Engine, HistoryBuffer, adaptConnector, createY, text_ops_uninitialized;
 | 
			
		||||
var Engine, HistoryBuffer, adaptConnector, createY, structured_ops_uninitialized;
 | 
			
		||||
 | 
			
		||||
text_ops_uninitialized = require("./Operations/Text");
 | 
			
		||||
structured_ops_uninitialized = require("./Operations/Structured");
 | 
			
		||||
 | 
			
		||||
HistoryBuffer = require("./HistoryBuffer");
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ createY = function(connector) {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  HB = new HistoryBuffer(user_id);
 | 
			
		||||
  ops_manager = text_ops_uninitialized(HB, this.constructor);
 | 
			
		||||
  ops_manager = structured_ops_uninitialized(HB, this.constructor);
 | 
			
		||||
  ops = ops_manager.operations;
 | 
			
		||||
  engine = new Engine(HB, ops);
 | 
			
		||||
  adaptConnector(connector, engine, HB, ops_manager.execution_listener);
 | 
			
		||||
@ -29,7 +29,7 @@ createY = function(connector) {
 | 
			
		||||
  ops.Operation.prototype.operations = ops;
 | 
			
		||||
  ops.Operation.prototype.engine = engine;
 | 
			
		||||
  ops.Operation.prototype.connector = connector;
 | 
			
		||||
  ops.Operation.prototype.custom_ops = this.constructor;
 | 
			
		||||
  ops.Operation.prototype.custom_types = this.constructor;
 | 
			
		||||
  ct = new createY.Object();
 | 
			
		||||
  model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute();
 | 
			
		||||
  ct._setModel(model);
 | 
			
		||||
@ -43,3 +43,5 @@ if (typeof window !== "undefined" && window !== null) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
createY.Object = require("./Types/Object");
 | 
			
		||||
 | 
			
		||||
createY.Text = require("./Types/Text");
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -29,7 +29,7 @@ files =
 | 
			
		||||
  test : ['./test/Json_test.coffee', './test/Text_test.coffee']
 | 
			
		||||
  gulp : ['./gulpfile.coffee']
 | 
			
		||||
  examples : ['./examples/**/*.js']
 | 
			
		||||
  other: ['./lib/**/*']
 | 
			
		||||
  other: ['./lib/**/*', './test/*']
 | 
			
		||||
 | 
			
		||||
files.all = []
 | 
			
		||||
for name,file_list of files
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,9 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier.
 | 
			
		||||
    # If uid is undefined, a new uid will be created before at the end of the execution sequence
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (uid)->
 | 
			
		||||
    constructor: (custom_type, uid)->
 | 
			
		||||
      if custom_type?
 | 
			
		||||
        @custom_type = custom_type
 | 
			
		||||
      @is_deleted = false
 | 
			
		||||
      @garbage_collected = false
 | 
			
		||||
      @event_listeners = [] # TODO: rename to observers or sth like that
 | 
			
		||||
@ -221,9 +223,9 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    # @param {Object} deletes UID or reference of the operation that this to be deleted.
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (uid, deletes)->
 | 
			
		||||
    constructor: (custom_type, uid, deletes)->
 | 
			
		||||
      @saveOperation 'deletes', deletes
 | 
			
		||||
      super uid
 | 
			
		||||
      super custom_type, uid
 | 
			
		||||
 | 
			
		||||
    type: "Delete"
 | 
			
		||||
 | 
			
		||||
@ -260,7 +262,7 @@ module.exports = ()->
 | 
			
		||||
      'uid' : uid
 | 
			
		||||
      'deletes': deletes_uid
 | 
			
		||||
    } = o
 | 
			
		||||
    new this(uid, deletes_uid)
 | 
			
		||||
    new this(null, uid, deletes_uid)
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # @nodoc
 | 
			
		||||
@ -279,7 +281,7 @@ module.exports = ()->
 | 
			
		||||
    # @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
 | 
			
		||||
    # @param {Operation} next_cl The successor of this operation in the complete-list (cl)
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (content, uid, prev_cl, next_cl, origin, parent)->
 | 
			
		||||
    constructor: (custom_type, content, uid, prev_cl, next_cl, origin, parent)->
 | 
			
		||||
      # see encode to see, why we are doing it this way
 | 
			
		||||
      if content is undefined
 | 
			
		||||
        # nop
 | 
			
		||||
@ -294,7 +296,7 @@ module.exports = ()->
 | 
			
		||||
        @saveOperation 'origin', origin
 | 
			
		||||
      else
 | 
			
		||||
        @saveOperation 'origin', prev_cl
 | 
			
		||||
      super uid
 | 
			
		||||
      super custom_type, uid
 | 
			
		||||
 | 
			
		||||
    type: "Insert"
 | 
			
		||||
 | 
			
		||||
@ -436,19 +438,24 @@ module.exports = ()->
 | 
			
		||||
        @
 | 
			
		||||
 | 
			
		||||
    callOperationSpecificInsertEvents: ()->
 | 
			
		||||
      getContentType = (content)->
 | 
			
		||||
        if content instanceof ops.Operation
 | 
			
		||||
          content.getCustomType()
 | 
			
		||||
        else
 | 
			
		||||
          content
 | 
			
		||||
      @parent?.callEvent [
 | 
			
		||||
        type: "insert"
 | 
			
		||||
        position: @getPosition()
 | 
			
		||||
        object: @parent
 | 
			
		||||
        object: @parent.getCustomType()
 | 
			
		||||
        changedBy: @uid.creator
 | 
			
		||||
        value: @content
 | 
			
		||||
        value: getContentType @content
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
    callOperationSpecificDeleteEvents: (o)->
 | 
			
		||||
      @parent.callEvent [
 | 
			
		||||
        type: "delete"
 | 
			
		||||
        position: @getPosition()
 | 
			
		||||
        object: @parent # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
 | 
			
		||||
        object: @parent.getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
 | 
			
		||||
        length: 1
 | 
			
		||||
        changedBy: o.uid.creator
 | 
			
		||||
      ]
 | 
			
		||||
@ -503,7 +510,7 @@ module.exports = ()->
 | 
			
		||||
    } = json
 | 
			
		||||
    if typeof content is "string"
 | 
			
		||||
      content = JSON.parse(content)
 | 
			
		||||
    new this content, uid, prev, next, origin, parent
 | 
			
		||||
    new this null, content, uid, prev, next, origin, parent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -517,8 +524,8 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    # @param {Object} content
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (uid, @content)->
 | 
			
		||||
      super uid
 | 
			
		||||
    constructor: (custom_type, uid, @content)->
 | 
			
		||||
      super custom_type, uid
 | 
			
		||||
 | 
			
		||||
    type: "ImmutableObject"
 | 
			
		||||
 | 
			
		||||
@ -544,7 +551,7 @@ module.exports = ()->
 | 
			
		||||
      'uid' : uid
 | 
			
		||||
      'content' : content
 | 
			
		||||
    } = json
 | 
			
		||||
    new this(uid, content)
 | 
			
		||||
    new this(null, uid, content)
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # @nodoc
 | 
			
		||||
@ -562,7 +569,7 @@ module.exports = ()->
 | 
			
		||||
      @saveOperation 'prev_cl', prev_cl
 | 
			
		||||
      @saveOperation 'next_cl', next_cl
 | 
			
		||||
      @saveOperation 'origin', prev_cl
 | 
			
		||||
      super {noOperation: true}
 | 
			
		||||
      super null, {noOperation: true}
 | 
			
		||||
 | 
			
		||||
    type: "Delimiter"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,8 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (custom_type, uid)->
 | 
			
		||||
      if custom_type?
 | 
			
		||||
        @custom_type = custom_type
 | 
			
		||||
      @_map = {}
 | 
			
		||||
      super uid
 | 
			
		||||
      super custom_type, uid
 | 
			
		||||
 | 
			
		||||
    type: "MapManager"
 | 
			
		||||
 | 
			
		||||
@ -39,8 +37,8 @@ module.exports = ()->
 | 
			
		||||
    #
 | 
			
		||||
    val: (name, content)->
 | 
			
		||||
      if arguments.length > 1
 | 
			
		||||
        if content? and content._model? and content._model instanceof ops.Operation
 | 
			
		||||
          rep = content._model
 | 
			
		||||
        if content? and content._getModel?
 | 
			
		||||
          rep = content._getModel(@custom_types, @operations)
 | 
			
		||||
        else
 | 
			
		||||
          rep = content
 | 
			
		||||
        @retrieveSub(name).replace rep
 | 
			
		||||
@ -59,11 +57,7 @@ module.exports = ()->
 | 
			
		||||
        result = {}
 | 
			
		||||
        for name,o of @_map
 | 
			
		||||
          if not o.isContentDeleted()
 | 
			
		||||
            res = prop.val()
 | 
			
		||||
            if res instanceof ops.Operation
 | 
			
		||||
              result[name] = res.getCustomType()
 | 
			
		||||
            else
 | 
			
		||||
              result[name] = res
 | 
			
		||||
            result[name] = o.val()
 | 
			
		||||
        result
 | 
			
		||||
 | 
			
		||||
    delete: (name)->
 | 
			
		||||
@ -79,7 +73,7 @@ module.exports = ()->
 | 
			
		||||
          noOperation: true
 | 
			
		||||
          sub: property_name
 | 
			
		||||
          alt: @
 | 
			
		||||
        rm = new ops.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
 | 
			
		||||
        rm = new ops.ReplaceManager null, event_properties, event_this, rm_uid # this operation shall not be saved in the HB
 | 
			
		||||
        @_map[property_name] = rm
 | 
			
		||||
        rm.setParent @, property_name
 | 
			
		||||
        rm.execute()
 | 
			
		||||
@ -119,13 +113,13 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    # @param {Delimiter} beginning Reference or Object.
 | 
			
		||||
    # @param {Delimiter} end Reference or Object.
 | 
			
		||||
    constructor: (uid)->
 | 
			
		||||
    constructor: (custom_type, uid)->
 | 
			
		||||
      @beginning = new ops.Delimiter undefined, undefined
 | 
			
		||||
      @end =       new ops.Delimiter @beginning, undefined
 | 
			
		||||
      @beginning.next_cl = @end
 | 
			
		||||
      @beginning.execute()
 | 
			
		||||
      @end.execute()
 | 
			
		||||
      super uid
 | 
			
		||||
      super custom_type, uid
 | 
			
		||||
 | 
			
		||||
    type: "ListManager"
 | 
			
		||||
 | 
			
		||||
@ -238,27 +232,18 @@ module.exports = ()->
 | 
			
		||||
    push: (content)->
 | 
			
		||||
      @insertAfter @end.prev_cl, content
 | 
			
		||||
 | 
			
		||||
    insertAfter: (left, content, options)->
 | 
			
		||||
      createContent = (content, options)->
 | 
			
		||||
        if content? and content.constructor?
 | 
			
		||||
          type = ops[content.constructor.name]
 | 
			
		||||
          if type? and type.create?
 | 
			
		||||
            type.create content, options
 | 
			
		||||
          else
 | 
			
		||||
            throw new Error "The #{content.constructor.name}-type is not (yet) supported in Y."
 | 
			
		||||
        else
 | 
			
		||||
          content
 | 
			
		||||
 | 
			
		||||
    insertAfter: (left, content)->
 | 
			
		||||
      right = left.next_cl
 | 
			
		||||
      while right.isDeleted()
 | 
			
		||||
        right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
 | 
			
		||||
      left = right.prev_cl
 | 
			
		||||
 | 
			
		||||
      # TODO: always expect an array as content. Then you can combine this with the other option (else)
 | 
			
		||||
      if content instanceof ops.Operation
 | 
			
		||||
        (new ops.Insert content, undefined, left, right).execute()
 | 
			
		||||
        (new ops.Insert null, content, undefined, left, right).execute()
 | 
			
		||||
      else
 | 
			
		||||
        for c in content
 | 
			
		||||
          tmp = (new ops.Insert createContent(c, options), undefined, left, right).execute()
 | 
			
		||||
          tmp = (new ops.Insert null, c, undefined, left, right).execute()
 | 
			
		||||
          left = tmp
 | 
			
		||||
      @
 | 
			
		||||
 | 
			
		||||
@ -267,11 +252,11 @@ module.exports = ()->
 | 
			
		||||
    #
 | 
			
		||||
    # @return {ListManager Type} This String object.
 | 
			
		||||
    #
 | 
			
		||||
    insert: (position, content, options)->
 | 
			
		||||
    insert: (position, content)->
 | 
			
		||||
      ith = @getOperationByPosition position
 | 
			
		||||
      # the (i-1)th character. e.g. "abc" the 1th character is "a"
 | 
			
		||||
      # the 0th character is the left Delimiter
 | 
			
		||||
      @insertAfter ith, [content], options
 | 
			
		||||
      @insertAfter ith, [content]
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Deletes a part of the word.
 | 
			
		||||
@ -285,7 +270,7 @@ module.exports = ()->
 | 
			
		||||
      for i in [0...length]
 | 
			
		||||
        if o instanceof ops.Delimiter
 | 
			
		||||
          break
 | 
			
		||||
        d = (new ops.Delete undefined, o).execute()
 | 
			
		||||
        d = (new ops.Delete null, undefined, o).execute()
 | 
			
		||||
        o = o.next_cl
 | 
			
		||||
        while (not (o instanceof ops.Delimiter)) and o.isDeleted()
 | 
			
		||||
          o = o.next_cl
 | 
			
		||||
@ -301,26 +286,18 @@ module.exports = ()->
 | 
			
		||||
        'type': @type
 | 
			
		||||
        'uid' : @getUid()
 | 
			
		||||
      }
 | 
			
		||||
      if @custom_type.constructor is String
 | 
			
		||||
        json.custom_type = @custom_type
 | 
			
		||||
      else
 | 
			
		||||
        json.custom_type = @custom_type._name
 | 
			
		||||
      json
 | 
			
		||||
 | 
			
		||||
  ops.ListManager.parse = (json)->
 | 
			
		||||
    {
 | 
			
		||||
      'uid' : uid
 | 
			
		||||
      'custom_type': custom_type
 | 
			
		||||
    } = json
 | 
			
		||||
    new this(uid)
 | 
			
		||||
 | 
			
		||||
  ops.Array = ()->
 | 
			
		||||
  ops.Array.create = (content, mutable)->
 | 
			
		||||
      if (mutable is "mutable")
 | 
			
		||||
        list = new ops.ListManager().execute()
 | 
			
		||||
        ith = list.getOperationByPosition 0
 | 
			
		||||
        list.insertAfter ith, content
 | 
			
		||||
        list
 | 
			
		||||
      else if (not mutable?) or (mutable is "immutable")
 | 
			
		||||
        content
 | 
			
		||||
      else
 | 
			
		||||
        throw new Error "Specify either \"mutable\" or \"immutable\"!!"
 | 
			
		||||
 | 
			
		||||
    new this(custom_type, uid)
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # @nodoc
 | 
			
		||||
@ -338,10 +315,10 @@ module.exports = ()->
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    # @param {Delimiter} beginning Reference or Object.
 | 
			
		||||
    # @param {Delimiter} end Reference or Object.
 | 
			
		||||
    constructor: (@event_properties, @event_this, uid, beginning, end)->
 | 
			
		||||
    constructor: (custom_type, @event_properties, @event_this, uid, beginning, end)->
 | 
			
		||||
      if not @event_properties['object']?
 | 
			
		||||
        @event_properties['object'] = @event_this
 | 
			
		||||
      super uid, beginning, end
 | 
			
		||||
      super custom_type, uid, beginning, end
 | 
			
		||||
 | 
			
		||||
    type: "ReplaceManager"
 | 
			
		||||
 | 
			
		||||
@ -378,7 +355,7 @@ module.exports = ()->
 | 
			
		||||
    #
 | 
			
		||||
    replace: (content, replaceable_uid)->
 | 
			
		||||
      o = @getLastOperation()
 | 
			
		||||
      relp = (new ops.Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
 | 
			
		||||
      relp = (new ops.Replaceable null, content, @, replaceable_uid, o, o.next_cl).execute()
 | 
			
		||||
      # TODO: delete repl (for debugging)
 | 
			
		||||
      undefined
 | 
			
		||||
 | 
			
		||||
@ -386,7 +363,7 @@ module.exports = ()->
 | 
			
		||||
      @getLastOperation().isDeleted()
 | 
			
		||||
 | 
			
		||||
    deleteContent: ()->
 | 
			
		||||
      (new ops.Delete undefined, @getLastOperation().uid).execute()
 | 
			
		||||
      (new ops.Delete null, undefined, @getLastOperation().uid).execute()
 | 
			
		||||
      undefined
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
@ -424,9 +401,9 @@ module.exports = ()->
 | 
			
		||||
    # @param {ReplaceManager} parent Used to replace this Replaceable with another one.
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (content, parent, uid, prev, next, origin, is_deleted)->
 | 
			
		||||
    constructor: (custom_type, content, parent, uid, prev, next, origin, is_deleted)->
 | 
			
		||||
      @saveOperation 'parent', parent
 | 
			
		||||
      super content, uid, prev, next, origin # Parent is already saved by Replaceable
 | 
			
		||||
      super custom_type, content, uid, prev, next, origin # Parent is already saved by Replaceable
 | 
			
		||||
      @is_deleted = is_deleted
 | 
			
		||||
 | 
			
		||||
    type: "Replaceable"
 | 
			
		||||
@ -525,8 +502,9 @@ module.exports = ()->
 | 
			
		||||
      'next': next
 | 
			
		||||
      'origin' : origin
 | 
			
		||||
      'is_deleted': is_deleted
 | 
			
		||||
      'custom_type' : custom_type
 | 
			
		||||
    } = json
 | 
			
		||||
    new this(content, parent, uid, prev, next, origin, is_deleted)
 | 
			
		||||
    new this(custom_type, content, parent, uid, prev, next, origin, is_deleted)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  basic_ops
 | 
			
		||||
 | 
			
		||||
@ -1,308 +0,0 @@
 | 
			
		||||
structured_ops_uninitialized = require "./Structured"
 | 
			
		||||
 | 
			
		||||
module.exports = ()->
 | 
			
		||||
  structured_ops = structured_ops_uninitialized()
 | 
			
		||||
  ops = structured_ops.operations
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Handles a String-like data structures with support for insert/delete at a word-position.
 | 
			
		||||
  # @note Currently, only Text is supported!
 | 
			
		||||
  #
 | 
			
		||||
  class ops.String extends ops.ListManager
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # @private
 | 
			
		||||
    # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
    #
 | 
			
		||||
    constructor: (uid)->
 | 
			
		||||
      @textfields = []
 | 
			
		||||
      super uid
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Identifies this class.
 | 
			
		||||
    # Use it to check whether this is a word-type or something else.
 | 
			
		||||
    #
 | 
			
		||||
    # @example
 | 
			
		||||
    #   var x = y.val('unknown')
 | 
			
		||||
    #   if (x.type === "String") {
 | 
			
		||||
    #     console.log JSON.stringify(x.toJson())
 | 
			
		||||
    #   }
 | 
			
		||||
    #
 | 
			
		||||
    type: "String"
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Get the String-representation of this word.
 | 
			
		||||
    # @return {String} The String-representation of this object.
 | 
			
		||||
    #
 | 
			
		||||
    val: ()->
 | 
			
		||||
      @fold "", (left, o)->
 | 
			
		||||
        left + o.val()
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Same as String.val
 | 
			
		||||
    # @see String.val
 | 
			
		||||
    #
 | 
			
		||||
    toString: ()->
 | 
			
		||||
      @val()
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Inserts a string into the word.
 | 
			
		||||
    #
 | 
			
		||||
    # @return {ListManager Type} This String object.
 | 
			
		||||
    #
 | 
			
		||||
    insert: (position, content, options)->
 | 
			
		||||
      ith = @getOperationByPosition position
 | 
			
		||||
      # the (i-1)th character. e.g. "abc" the 1th character is "a"
 | 
			
		||||
      # the 0th character is the left Delimiter
 | 
			
		||||
      @insertAfter ith, content, options
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Bind this String to a textfield or input field.
 | 
			
		||||
    #
 | 
			
		||||
    # @example
 | 
			
		||||
    #   var textbox = document.getElementById("textfield");
 | 
			
		||||
    #   y.bind(textbox);
 | 
			
		||||
    #
 | 
			
		||||
    bind: (textfield, dom_root)->
 | 
			
		||||
      dom_root ?= window
 | 
			
		||||
      if (not dom_root.getSelection?)
 | 
			
		||||
        dom_root = window
 | 
			
		||||
 | 
			
		||||
      # don't duplicate!
 | 
			
		||||
      for t in @textfields
 | 
			
		||||
        if t is textfield
 | 
			
		||||
          return
 | 
			
		||||
      creator_token = false;
 | 
			
		||||
 | 
			
		||||
      word = @
 | 
			
		||||
      textfield.value = @val()
 | 
			
		||||
      @textfields.push textfield
 | 
			
		||||
 | 
			
		||||
      if textfield.selectionStart? and textfield.setSelectionRange?
 | 
			
		||||
        createRange = (fix)->
 | 
			
		||||
          left = textfield.selectionStart
 | 
			
		||||
          right = textfield.selectionEnd
 | 
			
		||||
          if fix?
 | 
			
		||||
            left = fix left
 | 
			
		||||
            right = fix right
 | 
			
		||||
          {
 | 
			
		||||
            left: left
 | 
			
		||||
            right: right
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        writeRange = (range)->
 | 
			
		||||
          writeContent word.val()
 | 
			
		||||
          textfield.setSelectionRange range.left, range.right
 | 
			
		||||
 | 
			
		||||
        writeContent = (content)->
 | 
			
		||||
          textfield.value = content
 | 
			
		||||
      else
 | 
			
		||||
        createRange = (fix)->
 | 
			
		||||
          range = {}
 | 
			
		||||
          s = dom_root.getSelection()
 | 
			
		||||
          clength = textfield.textContent.length
 | 
			
		||||
          range.left = Math.min s.anchorOffset, clength
 | 
			
		||||
          range.right = Math.min s.focusOffset, clength
 | 
			
		||||
          if fix?
 | 
			
		||||
            range.left = fix range.left
 | 
			
		||||
            range.right = fix range.right
 | 
			
		||||
 | 
			
		||||
          edited_element = s.focusNode
 | 
			
		||||
          if edited_element is textfield or edited_element is textfield.childNodes[0]
 | 
			
		||||
            range.isReal = true
 | 
			
		||||
          else
 | 
			
		||||
            range.isReal = false
 | 
			
		||||
          range
 | 
			
		||||
 | 
			
		||||
        writeRange = (range)->
 | 
			
		||||
          writeContent word.val()
 | 
			
		||||
          textnode = textfield.childNodes[0]
 | 
			
		||||
          if range.isReal and textnode?
 | 
			
		||||
            if range.left < 0
 | 
			
		||||
              range.left = 0
 | 
			
		||||
            range.right = Math.max range.left, range.right
 | 
			
		||||
            if range.right > textnode.length
 | 
			
		||||
              range.right = textnode.length
 | 
			
		||||
            range.left = Math.min range.left, range.right
 | 
			
		||||
            r = document.createRange()
 | 
			
		||||
            r.setStart(textnode, range.left)
 | 
			
		||||
            r.setEnd(textnode, range.right)
 | 
			
		||||
            s = window.getSelection()
 | 
			
		||||
            s.removeAllRanges()
 | 
			
		||||
            s.addRange(r)
 | 
			
		||||
        writeContent = (content)->
 | 
			
		||||
          content_array = content.replace(new RegExp("\n",'g')," ").split(" ")
 | 
			
		||||
          textfield.innerText = ""
 | 
			
		||||
          for c, i in content_array
 | 
			
		||||
            textfield.innerText += c
 | 
			
		||||
            if i isnt content_array.length-1
 | 
			
		||||
              textfield.innerHTML += ' '
 | 
			
		||||
 | 
			
		||||
      writeContent this.val()
 | 
			
		||||
 | 
			
		||||
      @observe (events)->
 | 
			
		||||
        for event in events
 | 
			
		||||
          if not creator_token
 | 
			
		||||
            if event.type is "insert"
 | 
			
		||||
              o_pos = event.position
 | 
			
		||||
              fix = (cursor)->
 | 
			
		||||
                if cursor <= o_pos
 | 
			
		||||
                  cursor
 | 
			
		||||
                else
 | 
			
		||||
                  cursor += 1
 | 
			
		||||
                  cursor
 | 
			
		||||
              r = createRange fix
 | 
			
		||||
              writeRange r
 | 
			
		||||
 | 
			
		||||
            else if event.type is "delete"
 | 
			
		||||
              o_pos = event.position
 | 
			
		||||
              fix = (cursor)->
 | 
			
		||||
                if cursor < o_pos
 | 
			
		||||
                  cursor
 | 
			
		||||
                else
 | 
			
		||||
                  cursor -= 1
 | 
			
		||||
                  cursor
 | 
			
		||||
              r = createRange fix
 | 
			
		||||
              writeRange r
 | 
			
		||||
 | 
			
		||||
      # consume all text-insert changes.
 | 
			
		||||
      textfield.onkeypress = (event)->
 | 
			
		||||
        if word.is_deleted
 | 
			
		||||
          # if word is deleted, do not do anything ever again
 | 
			
		||||
          textfield.onkeypress = null
 | 
			
		||||
          return true
 | 
			
		||||
        creator_token = true
 | 
			
		||||
        char = null
 | 
			
		||||
        if event.keyCode is 13
 | 
			
		||||
          char = '\n'
 | 
			
		||||
        else if event.key?
 | 
			
		||||
          if event.charCode is 32
 | 
			
		||||
            char = " "
 | 
			
		||||
          else
 | 
			
		||||
            char = event.key
 | 
			
		||||
        else
 | 
			
		||||
          char = window.String.fromCharCode event.keyCode
 | 
			
		||||
        if char.length > 1
 | 
			
		||||
          return true
 | 
			
		||||
        else if char.length > 0
 | 
			
		||||
          r = createRange()
 | 
			
		||||
          pos = Math.min r.left, r.right
 | 
			
		||||
          diff = Math.abs(r.right - r.left)
 | 
			
		||||
          word.delete pos, diff
 | 
			
		||||
          word.insert pos, char
 | 
			
		||||
          r.left = pos + char.length
 | 
			
		||||
          r.right = r.left
 | 
			
		||||
          writeRange r
 | 
			
		||||
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        creator_token = false
 | 
			
		||||
        false
 | 
			
		||||
 | 
			
		||||
      textfield.onpaste = (event)->
 | 
			
		||||
        if word.is_deleted
 | 
			
		||||
          # if word is deleted, do not do anything ever again
 | 
			
		||||
          textfield.onpaste = null
 | 
			
		||||
          return true
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
      textfield.oncut = (event)->
 | 
			
		||||
        if word.is_deleted
 | 
			
		||||
          # if word is deleted, do not do anything ever again
 | 
			
		||||
          textfield.oncut = null
 | 
			
		||||
          return true
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
 | 
			
		||||
      #
 | 
			
		||||
      # consume deletes. Note that
 | 
			
		||||
      #   chrome: won't consume deletions on keypress event.
 | 
			
		||||
      #   keyCode is deprecated. BUT: I don't see another way.
 | 
			
		||||
      #     since event.key is not implemented in the current version of chrome.
 | 
			
		||||
      #     Every browser supports keyCode. Let's stick with it for now..
 | 
			
		||||
      #
 | 
			
		||||
      textfield.onkeydown = (event)->
 | 
			
		||||
        creator_token = true
 | 
			
		||||
        if word.is_deleted
 | 
			
		||||
          # if word is deleted, do not do anything ever again
 | 
			
		||||
          textfield.onkeydown = null
 | 
			
		||||
          return true
 | 
			
		||||
        r = createRange()
 | 
			
		||||
        pos = Math.min(r.left, r.right, word.val().length)
 | 
			
		||||
        diff = Math.abs(r.left - r.right)
 | 
			
		||||
        if event.keyCode? and event.keyCode is 8 # Backspace
 | 
			
		||||
          if diff > 0
 | 
			
		||||
            word.delete pos, diff
 | 
			
		||||
            r.left = pos
 | 
			
		||||
            r.right = pos
 | 
			
		||||
            writeRange r
 | 
			
		||||
          else
 | 
			
		||||
            if event.ctrlKey? and event.ctrlKey
 | 
			
		||||
              val = word.val()
 | 
			
		||||
              new_pos = pos
 | 
			
		||||
              del_length = 0
 | 
			
		||||
              if pos > 0
 | 
			
		||||
                new_pos--
 | 
			
		||||
                del_length++
 | 
			
		||||
              while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
 | 
			
		||||
                new_pos--
 | 
			
		||||
                del_length++
 | 
			
		||||
              word.delete new_pos, (pos-new_pos)
 | 
			
		||||
              r.left = new_pos
 | 
			
		||||
              r.right = new_pos
 | 
			
		||||
              writeRange r
 | 
			
		||||
            else
 | 
			
		||||
              if pos > 0
 | 
			
		||||
                word.delete (pos-1), 1
 | 
			
		||||
                r.left = pos-1
 | 
			
		||||
                r.right = pos-1
 | 
			
		||||
                writeRange r
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          creator_token = false
 | 
			
		||||
          return false
 | 
			
		||||
        else if event.keyCode? and event.keyCode is 46 # Delete
 | 
			
		||||
          if diff > 0
 | 
			
		||||
            word.delete pos, diff
 | 
			
		||||
            r.left = pos
 | 
			
		||||
            r.right = pos
 | 
			
		||||
            writeRange r
 | 
			
		||||
          else
 | 
			
		||||
            word.delete pos, 1
 | 
			
		||||
            r.left = pos
 | 
			
		||||
            r.right = pos
 | 
			
		||||
            writeRange r
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          creator_token = false
 | 
			
		||||
          return false
 | 
			
		||||
        else
 | 
			
		||||
          creator_token = false
 | 
			
		||||
          true
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # @private
 | 
			
		||||
    # Encode this operation in such a way that it can be parsed by remote peers.
 | 
			
		||||
    #
 | 
			
		||||
    _encode: ()->
 | 
			
		||||
      json = {
 | 
			
		||||
        'type': @type
 | 
			
		||||
        'uid' : @getUid()
 | 
			
		||||
      }
 | 
			
		||||
      json
 | 
			
		||||
 | 
			
		||||
  ops.String.parse = (json)->
 | 
			
		||||
    {
 | 
			
		||||
      'uid' : uid
 | 
			
		||||
    } = json
 | 
			
		||||
    new this(uid)
 | 
			
		||||
 | 
			
		||||
  ops.String.create = (content, mutable)->
 | 
			
		||||
    if (mutable is "mutable")
 | 
			
		||||
      word = new ops.String().execute()
 | 
			
		||||
      word.insert 0, content
 | 
			
		||||
      word
 | 
			
		||||
    else if (not mutable?) or (mutable is "immutable")
 | 
			
		||||
      content
 | 
			
		||||
    else
 | 
			
		||||
      throw new Error "Specify either \"mutable\" or \"immutable\"!!"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  structured_ops
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								lib/Types/List.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/Types/List.coffee
									
									
									
									
									
										Normal file
									
								
							@ -55,6 +55,9 @@ class YObject
 | 
			
		||||
          res[n] = v
 | 
			
		||||
        res
 | 
			
		||||
 | 
			
		||||
  delete: (name)->
 | 
			
		||||
    @_model.delete(name)
 | 
			
		||||
 | 
			
		||||
if window?
 | 
			
		||||
  if window.Y?
 | 
			
		||||
    window.Y.Object = YObject
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										306
									
								
								lib/Types/Text.coffee
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								lib/Types/Text.coffee
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,306 @@
 | 
			
		||||
#
 | 
			
		||||
# Handles a String-like data structures with support for insert/delete at a word-position.
 | 
			
		||||
# @note Currently, only Text is supported!
 | 
			
		||||
#
 | 
			
		||||
class YText
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # @private
 | 
			
		||||
  # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
 | 
			
		||||
  #
 | 
			
		||||
  constructor: (text)->
 | 
			
		||||
    @textfields = []
 | 
			
		||||
    if not text?
 | 
			
		||||
      @_text = ""
 | 
			
		||||
    else if text.constructor is String
 | 
			
		||||
      @_text = text
 | 
			
		||||
    else
 | 
			
		||||
      throw new Error "Y.Text expects a String as a constructor"
 | 
			
		||||
 | 
			
		||||
  _name: "Text"
 | 
			
		||||
 | 
			
		||||
  _getModel: (types, ops)->
 | 
			
		||||
    if not @_model?
 | 
			
		||||
      @_model = new ops.ListManager(@).execute()
 | 
			
		||||
      @insert 0, @_text
 | 
			
		||||
    delete @_text
 | 
			
		||||
    @_model
 | 
			
		||||
 | 
			
		||||
  _setModel: (@_model)->
 | 
			
		||||
    delete @_text
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Get the String-representation of this word.
 | 
			
		||||
  # @return {String} The String-representation of this object.
 | 
			
		||||
  #
 | 
			
		||||
  val: ()->
 | 
			
		||||
    @_model.fold "", (left, o)->
 | 
			
		||||
      left + o.val()
 | 
			
		||||
 | 
			
		||||
  observe: ()->
 | 
			
		||||
    @_model.observe.apply @_model, arguments
 | 
			
		||||
 | 
			
		||||
  unobserve: ()->
 | 
			
		||||
    @_model.unobserve.apply @_model, arguments
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Same as String.val
 | 
			
		||||
  # @see String.val
 | 
			
		||||
  #
 | 
			
		||||
  toString: ()->
 | 
			
		||||
    @val()
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Inserts a string into the word.
 | 
			
		||||
  #
 | 
			
		||||
  # @return {ListManager Type} This String object.
 | 
			
		||||
  #
 | 
			
		||||
  insert: (position, content)->
 | 
			
		||||
    if content.constructor isnt String
 | 
			
		||||
      throw new Error "Y.String.insert expects a String as the second parameter!"
 | 
			
		||||
    if typeof position isnt "number"
 | 
			
		||||
      throw new Error "Y.String.insert expects a Number as the second parameter!"
 | 
			
		||||
    if content.length > 0
 | 
			
		||||
      ith = @_model.getOperationByPosition position
 | 
			
		||||
      # the (i-1)th character. e.g. "abc" the 1th character is "a"
 | 
			
		||||
      # the 0th character is the left Delimiter
 | 
			
		||||
      @_model.insertAfter ith, content
 | 
			
		||||
 | 
			
		||||
  delete: (position, length)->
 | 
			
		||||
    @_model.delete position, length
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Bind this String to a textfield or input field.
 | 
			
		||||
  #
 | 
			
		||||
  # @example
 | 
			
		||||
  #   var textbox = document.getElementById("textfield");
 | 
			
		||||
  #   y.bind(textbox);
 | 
			
		||||
  #
 | 
			
		||||
  bind: (textfield, dom_root)->
 | 
			
		||||
    dom_root ?= window
 | 
			
		||||
    if (not dom_root.getSelection?)
 | 
			
		||||
      dom_root = window
 | 
			
		||||
 | 
			
		||||
    # don't duplicate!
 | 
			
		||||
    for t in @textfields
 | 
			
		||||
      if t is textfield
 | 
			
		||||
        return
 | 
			
		||||
    creator_token = false;
 | 
			
		||||
 | 
			
		||||
    word = @
 | 
			
		||||
    textfield.value = @val()
 | 
			
		||||
    @textfields.push textfield
 | 
			
		||||
 | 
			
		||||
    if textfield.selectionStart? and textfield.setSelectionRange?
 | 
			
		||||
      createRange = (fix)->
 | 
			
		||||
        left = textfield.selectionStart
 | 
			
		||||
        right = textfield.selectionEnd
 | 
			
		||||
        if fix?
 | 
			
		||||
          left = fix left
 | 
			
		||||
          right = fix right
 | 
			
		||||
        {
 | 
			
		||||
          left: left
 | 
			
		||||
          right: right
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      writeRange = (range)->
 | 
			
		||||
        writeContent word.val()
 | 
			
		||||
        textfield.setSelectionRange range.left, range.right
 | 
			
		||||
 | 
			
		||||
      writeContent = (content)->
 | 
			
		||||
        textfield.value = content
 | 
			
		||||
    else
 | 
			
		||||
      createRange = (fix)->
 | 
			
		||||
        range = {}
 | 
			
		||||
        s = dom_root.getSelection()
 | 
			
		||||
        clength = textfield.textContent.length
 | 
			
		||||
        range.left = Math.min s.anchorOffset, clength
 | 
			
		||||
        range.right = Math.min s.focusOffset, clength
 | 
			
		||||
        if fix?
 | 
			
		||||
          range.left = fix range.left
 | 
			
		||||
          range.right = fix range.right
 | 
			
		||||
 | 
			
		||||
        edited_element = s.focusNode
 | 
			
		||||
        if edited_element is textfield or edited_element is textfield.childNodes[0]
 | 
			
		||||
          range.isReal = true
 | 
			
		||||
        else
 | 
			
		||||
          range.isReal = false
 | 
			
		||||
        range
 | 
			
		||||
 | 
			
		||||
      writeRange = (range)->
 | 
			
		||||
        writeContent word.val()
 | 
			
		||||
        textnode = textfield.childNodes[0]
 | 
			
		||||
        if range.isReal and textnode?
 | 
			
		||||
          if range.left < 0
 | 
			
		||||
            range.left = 0
 | 
			
		||||
          range.right = Math.max range.left, range.right
 | 
			
		||||
          if range.right > textnode.length
 | 
			
		||||
            range.right = textnode.length
 | 
			
		||||
          range.left = Math.min range.left, range.right
 | 
			
		||||
          r = document.createRange()
 | 
			
		||||
          r.setStart(textnode, range.left)
 | 
			
		||||
          r.setEnd(textnode, range.right)
 | 
			
		||||
          s = window.getSelection()
 | 
			
		||||
          s.removeAllRanges()
 | 
			
		||||
          s.addRange(r)
 | 
			
		||||
      writeContent = (content)->
 | 
			
		||||
        content_array = content.replace(new RegExp("\n",'g')," ").split(" ")
 | 
			
		||||
        textfield.innerText = ""
 | 
			
		||||
        for c, i in content_array
 | 
			
		||||
          textfield.innerText += c
 | 
			
		||||
          if i isnt content_array.length-1
 | 
			
		||||
            textfield.innerHTML += ' '
 | 
			
		||||
 | 
			
		||||
    writeContent this.val()
 | 
			
		||||
 | 
			
		||||
    @observe (events)->
 | 
			
		||||
      for event in events
 | 
			
		||||
        if not creator_token
 | 
			
		||||
          if event.type is "insert"
 | 
			
		||||
            o_pos = event.position
 | 
			
		||||
            fix = (cursor)->
 | 
			
		||||
              if cursor <= o_pos
 | 
			
		||||
                cursor
 | 
			
		||||
              else
 | 
			
		||||
                cursor += 1
 | 
			
		||||
                cursor
 | 
			
		||||
            r = createRange fix
 | 
			
		||||
            writeRange r
 | 
			
		||||
 | 
			
		||||
          else if event.type is "delete"
 | 
			
		||||
            o_pos = event.position
 | 
			
		||||
            fix = (cursor)->
 | 
			
		||||
              if cursor < o_pos
 | 
			
		||||
                cursor
 | 
			
		||||
              else
 | 
			
		||||
                cursor -= 1
 | 
			
		||||
                cursor
 | 
			
		||||
            r = createRange fix
 | 
			
		||||
            writeRange r
 | 
			
		||||
 | 
			
		||||
    # consume all text-insert changes.
 | 
			
		||||
    textfield.onkeypress = (event)->
 | 
			
		||||
      if word.is_deleted
 | 
			
		||||
        # if word is deleted, do not do anything ever again
 | 
			
		||||
        textfield.onkeypress = null
 | 
			
		||||
        return true
 | 
			
		||||
      creator_token = true
 | 
			
		||||
      char = null
 | 
			
		||||
      if event.keyCode is 13
 | 
			
		||||
        char = '\n'
 | 
			
		||||
      else if event.key?
 | 
			
		||||
        if event.charCode is 32
 | 
			
		||||
          char = " "
 | 
			
		||||
        else
 | 
			
		||||
          char = event.key
 | 
			
		||||
      else
 | 
			
		||||
        char = window.String.fromCharCode event.keyCode
 | 
			
		||||
      if char.length > 1
 | 
			
		||||
        return true
 | 
			
		||||
      else if char.length > 0
 | 
			
		||||
        r = createRange()
 | 
			
		||||
        pos = Math.min r.left, r.right
 | 
			
		||||
        diff = Math.abs(r.right - r.left)
 | 
			
		||||
        word.delete pos, diff
 | 
			
		||||
        word.insert pos, char
 | 
			
		||||
        r.left = pos + char.length
 | 
			
		||||
        r.right = r.left
 | 
			
		||||
        writeRange r
 | 
			
		||||
 | 
			
		||||
      event.preventDefault()
 | 
			
		||||
      creator_token = false
 | 
			
		||||
      false
 | 
			
		||||
 | 
			
		||||
    textfield.onpaste = (event)->
 | 
			
		||||
      if word.is_deleted
 | 
			
		||||
        # if word is deleted, do not do anything ever again
 | 
			
		||||
        textfield.onpaste = null
 | 
			
		||||
        return true
 | 
			
		||||
      event.preventDefault()
 | 
			
		||||
    textfield.oncut = (event)->
 | 
			
		||||
      if word.is_deleted
 | 
			
		||||
        # if word is deleted, do not do anything ever again
 | 
			
		||||
        textfield.oncut = null
 | 
			
		||||
        return true
 | 
			
		||||
      event.preventDefault()
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # consume deletes. Note that
 | 
			
		||||
    #   chrome: won't consume deletions on keypress event.
 | 
			
		||||
    #   keyCode is deprecated. BUT: I don't see another way.
 | 
			
		||||
    #     since event.key is not implemented in the current version of chrome.
 | 
			
		||||
    #     Every browser supports keyCode. Let's stick with it for now..
 | 
			
		||||
    #
 | 
			
		||||
    textfield.onkeydown = (event)->
 | 
			
		||||
      creator_token = true
 | 
			
		||||
      if word.is_deleted
 | 
			
		||||
        # if word is deleted, do not do anything ever again
 | 
			
		||||
        textfield.onkeydown = null
 | 
			
		||||
        return true
 | 
			
		||||
      r = createRange()
 | 
			
		||||
      pos = Math.min(r.left, r.right, word.val().length)
 | 
			
		||||
      diff = Math.abs(r.left - r.right)
 | 
			
		||||
      if event.keyCode? and event.keyCode is 8 # Backspace
 | 
			
		||||
        if diff > 0
 | 
			
		||||
          word.delete pos, diff
 | 
			
		||||
          r.left = pos
 | 
			
		||||
          r.right = pos
 | 
			
		||||
          writeRange r
 | 
			
		||||
        else
 | 
			
		||||
          if event.ctrlKey? and event.ctrlKey
 | 
			
		||||
            val = word.val()
 | 
			
		||||
            new_pos = pos
 | 
			
		||||
            del_length = 0
 | 
			
		||||
            if pos > 0
 | 
			
		||||
              new_pos--
 | 
			
		||||
              del_length++
 | 
			
		||||
            while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
 | 
			
		||||
              new_pos--
 | 
			
		||||
              del_length++
 | 
			
		||||
            word.delete new_pos, (pos-new_pos)
 | 
			
		||||
            r.left = new_pos
 | 
			
		||||
            r.right = new_pos
 | 
			
		||||
            writeRange r
 | 
			
		||||
          else
 | 
			
		||||
            if pos > 0
 | 
			
		||||
              word.delete (pos-1), 1
 | 
			
		||||
              r.left = pos-1
 | 
			
		||||
              r.right = pos-1
 | 
			
		||||
              writeRange r
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        creator_token = false
 | 
			
		||||
        return false
 | 
			
		||||
      else if event.keyCode? and event.keyCode is 46 # Delete
 | 
			
		||||
        if diff > 0
 | 
			
		||||
          word.delete pos, diff
 | 
			
		||||
          r.left = pos
 | 
			
		||||
          r.right = pos
 | 
			
		||||
          writeRange r
 | 
			
		||||
        else
 | 
			
		||||
          word.delete pos, 1
 | 
			
		||||
          r.left = pos
 | 
			
		||||
          r.right = pos
 | 
			
		||||
          writeRange r
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        creator_token = false
 | 
			
		||||
        return false
 | 
			
		||||
      else
 | 
			
		||||
        creator_token = false
 | 
			
		||||
        true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if window?
 | 
			
		||||
  if window.Y?
 | 
			
		||||
    window.Y.Text = YText
 | 
			
		||||
  else
 | 
			
		||||
    throw new Error "You must first import Y!"
 | 
			
		||||
 | 
			
		||||
if module?
 | 
			
		||||
  module.exports = YText
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
 | 
			
		||||
text_ops_uninitialized = require "./Operations/Text"
 | 
			
		||||
structured_ops_uninitialized = require "./Operations/Structured"
 | 
			
		||||
 | 
			
		||||
HistoryBuffer = require "./HistoryBuffer"
 | 
			
		||||
Engine = require "./Engine"
 | 
			
		||||
@ -15,7 +15,7 @@ createY = (connector)->
 | 
			
		||||
      user_id = id
 | 
			
		||||
      HB.resetUserId id
 | 
			
		||||
  HB = new HistoryBuffer user_id
 | 
			
		||||
  ops_manager = text_ops_uninitialized HB, this.constructor
 | 
			
		||||
  ops_manager = structured_ops_uninitialized HB, this.constructor
 | 
			
		||||
  ops = ops_manager.operations
 | 
			
		||||
 | 
			
		||||
  engine = new Engine HB, ops
 | 
			
		||||
@ -25,7 +25,7 @@ createY = (connector)->
 | 
			
		||||
  ops.Operation.prototype.operations = ops
 | 
			
		||||
  ops.Operation.prototype.engine = engine
 | 
			
		||||
  ops.Operation.prototype.connector = connector
 | 
			
		||||
  ops.Operation.prototype.custom_ops = this.constructor
 | 
			
		||||
  ops.Operation.prototype.custom_types = this.constructor
 | 
			
		||||
 | 
			
		||||
  ct = new createY.Object()
 | 
			
		||||
  model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute()
 | 
			
		||||
@ -37,3 +37,4 @@ if window?
 | 
			
		||||
  window.Y = createY
 | 
			
		||||
 | 
			
		||||
createY.Object = require "./Types/Object"
 | 
			
		||||
createY.Text = require "./Types/Text"
 | 
			
		||||
 | 
			
		||||
@ -11,17 +11,16 @@ Connector = require "../../y-test/lib/y-test.coffee"
 | 
			
		||||
Y = require "../lib/y.coffee"
 | 
			
		||||
 | 
			
		||||
compare = (o1, o2)->
 | 
			
		||||
  if o1.type? and o1.type isnt o2.type
 | 
			
		||||
  if o1._name? and o1._name isnt o2._name
 | 
			
		||||
    throw new Error "different types"
 | 
			
		||||
  else if o1.type is "Object"
 | 
			
		||||
  else if o1._name is "Object"
 | 
			
		||||
    for name, val of o1.val()
 | 
			
		||||
      compare(val, o2.val(name))
 | 
			
		||||
  else if o1.type?
 | 
			
		||||
  else if o1._name?
 | 
			
		||||
    compare(o1.val(), o2.val())
 | 
			
		||||
  else if o1 isnt o2
 | 
			
		||||
    throw new Error "different values"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Test = require "./TestSuite"
 | 
			
		||||
 | 
			
		||||
class JsonTest extends Test
 | 
			
		||||
@ -38,17 +37,17 @@ class JsonTest extends Test
 | 
			
		||||
      root
 | 
			
		||||
    else # take child
 | 
			
		||||
      elems = null
 | 
			
		||||
      if root.type is "Object"
 | 
			
		||||
      if root._name is "Object"
 | 
			
		||||
        elems =
 | 
			
		||||
          for oname,val of root.val()
 | 
			
		||||
            val
 | 
			
		||||
      else if root.type is "Array"
 | 
			
		||||
      else if root._name is "Array"
 | 
			
		||||
        elems = root.val()
 | 
			
		||||
      else
 | 
			
		||||
        return root
 | 
			
		||||
 | 
			
		||||
      elems = elems.filter (elem)->
 | 
			
		||||
        elem? and ((elem.type is "Array") or (elem.type is "Object"))
 | 
			
		||||
        elem? and ((elem._name is "Array") or (elem._name is "Object"))
 | 
			
		||||
      if elems.length is 0
 | 
			
		||||
        root
 | 
			
		||||
      else
 | 
			
		||||
@ -58,9 +57,26 @@ class JsonTest extends Test
 | 
			
		||||
  getGeneratingFunctions: (user_num)->
 | 
			
		||||
    types = @users[user_num]._model.operations
 | 
			
		||||
    super(user_num).concat [
 | 
			
		||||
        f : (y)=> # Delete Object Property
 | 
			
		||||
          list = for name, o of y.val()
 | 
			
		||||
            name
 | 
			
		||||
          if list.length > 0
 | 
			
		||||
            key = list[_.random(0,list.length-1)]
 | 
			
		||||
            y.delete(key)
 | 
			
		||||
        types: [Y.Object]
 | 
			
		||||
      ,
 | 
			
		||||
        f : (y)=> # SET Object Property
 | 
			
		||||
          y.val(@getRandomKey(), new Y.Object(@getRandomObject()))
 | 
			
		||||
        types: [Y.Object]
 | 
			
		||||
      ,
 | 
			
		||||
        f : (y)=> # SET PROPERTY TEXT
 | 
			
		||||
          y.val(@getRandomKey(), new Y.Text(@getRandomText()))
 | 
			
		||||
        types: [Y.Object]
 | 
			
		||||
      ]
 | 
			
		||||
###
 | 
			
		||||
        f : (y)=> # SET PROPERTY
 | 
			
		||||
          l = y.val().length
 | 
			
		||||
          y.val(_.random(0, l-1), @getRandomText(), 'immutable')
 | 
			
		||||
          y.val(_.random(0, l-1), @getRandomText())
 | 
			
		||||
          null
 | 
			
		||||
        types : [types.Array]
 | 
			
		||||
      , f : (y)=> # Delete Array Element
 | 
			
		||||
@ -71,28 +87,14 @@ class JsonTest extends Test
 | 
			
		||||
        types: [types.Array]
 | 
			
		||||
      , f : (y)=> # insert TEXT mutable
 | 
			
		||||
          l = y.val().length
 | 
			
		||||
          y.val(_.random(0, l-1), @getRamdomObject())
 | 
			
		||||
          y.val(_.random(0, l-1), new Y.Object(@getRamdomObject()))
 | 
			
		||||
        types: [types.Array]
 | 
			
		||||
      , f : (y)=> # insert string
 | 
			
		||||
          l = y.val().length
 | 
			
		||||
          y.val(_.random(0, l-1), @getRandomText(), 'immutable')
 | 
			
		||||
          y.val(_.random(0, l-1), @getRandomText())
 | 
			
		||||
          null
 | 
			
		||||
        types : [types.Array]
 | 
			
		||||
      , f : (y)=> # Delete Object Property
 | 
			
		||||
          list = for name, o of y.val()
 | 
			
		||||
            name
 | 
			
		||||
          if list.length > 0
 | 
			
		||||
            key = list[_random(0,list.length-1)]
 | 
			
		||||
            y.delete(key)
 | 
			
		||||
        types: [types.Object]
 | 
			
		||||
      , f : (y)=> # SET Object Property
 | 
			
		||||
          y.val(@getRandomKey(), @getRandomObject())
 | 
			
		||||
        types: [types.Object]
 | 
			
		||||
      ,
 | 
			
		||||
        f : (y)=> # SET PROPERTY TEXT
 | 
			
		||||
          y.val(@getRandomKey(), @getRandomText(), 'mutable')
 | 
			
		||||
        types: [types.Object]
 | 
			
		||||
    ]
 | 
			
		||||
        types : [Y.Array]
 | 
			
		||||
###
 | 
			
		||||
 | 
			
		||||
describe "JsonFramework", ->
 | 
			
		||||
  @timeout 500000
 | 
			
		||||
@ -117,16 +119,16 @@ describe "JsonFramework", ->
 | 
			
		||||
    @yTest.run()
 | 
			
		||||
    u1 = test.users[0]
 | 
			
		||||
    u2 = @yTest.users[1]
 | 
			
		||||
    ops1 = u1.HB._encode()
 | 
			
		||||
    ops2 = u2.HB._encode()
 | 
			
		||||
    u1.engine.applyOp ops2, true
 | 
			
		||||
    u2.engine.applyOp ops1, true
 | 
			
		||||
    ops1 = u1._model.HB._encode()
 | 
			
		||||
    ops2 = u2._model.HB._encode()
 | 
			
		||||
    u1._model.engine.applyOp ops2, true
 | 
			
		||||
    u2._model.engine.applyOp ops1, true
 | 
			
		||||
 | 
			
		||||
    expect(compare(u1, u2)).to.not.be.undefined
 | 
			
		||||
 | 
			
		||||
  it "can handle creaton of complex json (1)", ->
 | 
			
		||||
    @yTest.users[0].val('a', 'q', "mutable")
 | 
			
		||||
    @yTest.users[1].val('a', 't', "mutable")
 | 
			
		||||
    @yTest.users[0].val('a', new Y.Text('q'))
 | 
			
		||||
    @yTest.users[1].val('a', new Y.Text('t'))
 | 
			
		||||
    @yTest.compareAll()
 | 
			
		||||
    q = @yTest.users[2].val('a')
 | 
			
		||||
    q.insert(0,'A')
 | 
			
		||||
@ -135,7 +137,7 @@ describe "JsonFramework", ->
 | 
			
		||||
 | 
			
		||||
  it "can handle creaton of complex json (2)", ->
 | 
			
		||||
    @yTest.getSomeUser().val('x', new Y.Object({'a':'b'}))
 | 
			
		||||
    @yTest.getSomeUser().val('a', new Y.Object({'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}))
 | 
			
		||||
    @yTest.getSomeUser().val('a', new Y.Object({'a':{q: new Y.Text("dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")}}))
 | 
			
		||||
    @yTest.getSomeUser().val('b', new Y.Object({'a':{}}))
 | 
			
		||||
    @yTest.getSomeUser().val('c', new Y.Object({'a':'c'}))
 | 
			
		||||
    @yTest.getSomeUser().val('c', new Y.Object({'a':'b'}))
 | 
			
		||||
@ -143,7 +145,7 @@ describe "JsonFramework", ->
 | 
			
		||||
    q = @yTest.getSomeUser().val("a").val("a").val("q")
 | 
			
		||||
    q.insert(0,'A')
 | 
			
		||||
    @yTest.compareAll()
 | 
			
		||||
    expect(@yTest.getSomeUser().val("a").val("a").val("q")).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
 | 
			
		||||
    expect(@yTest.getSomeUser().val("a").val("a").val("q").val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
 | 
			
		||||
 | 
			
		||||
  it "can handle creaton of complex json (3)", ->
 | 
			
		||||
    @yTest.users[0].val('l', [1,2,3], "mutable")
 | 
			
		||||
@ -156,9 +158,9 @@ describe "JsonFramework", ->
 | 
			
		||||
    @yTest.compareAll()
 | 
			
		||||
 | 
			
		||||
  it "handles immutables and primitive data types", ->
 | 
			
		||||
    @yTest.getSomeUser().val('string', "text", "immutable")
 | 
			
		||||
    @yTest.getSomeUser().val('number', 4, "immutable")
 | 
			
		||||
    @yTest.getSomeUser().val('object', {q:"rr"}, "immutable")
 | 
			
		||||
    @yTest.getSomeUser().val('string', new Y.Text("text"))
 | 
			
		||||
    @yTest.getSomeUser().val('number', 4)
 | 
			
		||||
    @yTest.getSomeUser().val('object', {q:"rr"})
 | 
			
		||||
    @yTest.getSomeUser().val('null', null)
 | 
			
		||||
    @yTest.compareAll()
 | 
			
		||||
    expect(@yTest.getSomeUser().val('string')).to.equal "text"
 | 
			
		||||
@ -167,9 +169,9 @@ describe "JsonFramework", ->
 | 
			
		||||
    expect(@yTest.getSomeUser().val('null') is null).to.be.ok
 | 
			
		||||
 | 
			
		||||
  it "handles immutables and primitive data types (2)", ->
 | 
			
		||||
    @yTest.users[0].val('string', "text", "immutable")
 | 
			
		||||
    @yTest.users[1].val('number', 4, "immutable")
 | 
			
		||||
    @yTest.users[2].val('object', {q:"rr"})
 | 
			
		||||
    @yTest.users[0].val('string', "text")
 | 
			
		||||
    @yTest.users[1].val('number', 4)
 | 
			
		||||
    @yTest.users[2].val('object', new Y.Object({q:"rr"}))
 | 
			
		||||
    @yTest.users[0].val('null', null)
 | 
			
		||||
    @yTest.compareAll()
 | 
			
		||||
    expect(@yTest.getSomeUser().val('string')).to.equal "text"
 | 
			
		||||
@ -190,7 +192,7 @@ describe "JsonFramework", ->
 | 
			
		||||
      expect(change.name).to.equal("newStuff")
 | 
			
		||||
      last_task = "observer1"
 | 
			
		||||
    u.observe observer1
 | 
			
		||||
    u.val("newStuff","someStuff","mutable")
 | 
			
		||||
    u.val("newStuff",new Y.Text("someStuff"))
 | 
			
		||||
    expect(last_task).to.equal("observer1")
 | 
			
		||||
    u.unobserve observer1
 | 
			
		||||
 | 
			
		||||
@ -210,7 +212,7 @@ describe "JsonFramework", ->
 | 
			
		||||
    u.unobserve observer2
 | 
			
		||||
 | 
			
		||||
  it "Observers work on JSON Types (update type observers, local and foreign)", ->
 | 
			
		||||
    u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
 | 
			
		||||
    u = @yTest.users[0].val("newStuff", new Y.Text("oldStuff")).val("moreStuff",new Y.Text("moreOldStuff"))
 | 
			
		||||
    @yTest.flushAll()
 | 
			
		||||
    last_task = null
 | 
			
		||||
    observer1 = (changes)->
 | 
			
		||||
@ -245,7 +247,7 @@ describe "JsonFramework", ->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  it "Observers work on JSON Types (delete type observers, local and foreign)", ->
 | 
			
		||||
    u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
 | 
			
		||||
    u = @yTest.users[0].val("newStuff",new Y.Text("oldStuff")).val("moreStuff",new Y.Text("moreOldStuff"))
 | 
			
		||||
    @yTest.flushAll()
 | 
			
		||||
    last_task = null
 | 
			
		||||
    observer1 = (changes)->
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ _         = require("underscore")
 | 
			
		||||
chai.use(sinonChai)
 | 
			
		||||
 | 
			
		||||
Connector = require "../../y-test/lib/y-test.coffee"
 | 
			
		||||
Y = require "../lib/y"
 | 
			
		||||
 | 
			
		||||
module.exports = class Test
 | 
			
		||||
  constructor: (@name_suffix = "")->
 | 
			
		||||
@ -76,7 +77,7 @@ module.exports = class Test
 | 
			
		||||
          pos = _.random 0, (y.val().length-1)
 | 
			
		||||
          y.insert pos, @getRandomText()
 | 
			
		||||
          null
 | 
			
		||||
        types: [types.String]
 | 
			
		||||
        types: [Y.Text]
 | 
			
		||||
      ,
 | 
			
		||||
        f : (y)-> # DELETE TEXT
 | 
			
		||||
          if y.val().length > 0
 | 
			
		||||
@ -84,13 +85,24 @@ module.exports = class Test
 | 
			
		||||
            length = _.random 0, (y.val().length - pos)
 | 
			
		||||
            ops1 = y.delete pos, length
 | 
			
		||||
          undefined
 | 
			
		||||
        types : [types.String]
 | 
			
		||||
        types : [Y.Text]
 | 
			
		||||
    ]
 | 
			
		||||
  getRandomRoot: (user_num)->
 | 
			
		||||
    throw new Error "overwrite me!"
 | 
			
		||||
    throw new Error "implement me!"
 | 
			
		||||
 | 
			
		||||
  getContent: (user_num)->
 | 
			
		||||
    throw new Error "overwrite me!"
 | 
			
		||||
    throw new Error "implement me!"
 | 
			
		||||
 | 
			
		||||
  compare: (o1, o2)->
 | 
			
		||||
    if o1._name? and o1._name isnt o2._name
 | 
			
		||||
      throw new Error "different types"
 | 
			
		||||
    else if o1._name is "Object"
 | 
			
		||||
      for name, val of o1.val()
 | 
			
		||||
        @compare(val, o2.val(name))
 | 
			
		||||
    else if o1._name?
 | 
			
		||||
      @compare(o1.val(), o2.val())
 | 
			
		||||
    else if o1 isnt o2
 | 
			
		||||
      throw new Error "different values"
 | 
			
		||||
 | 
			
		||||
  generateRandomOp: (user_num)=>
 | 
			
		||||
    y = @getRandomRoot(user_num)
 | 
			
		||||
@ -171,7 +183,7 @@ module.exports = class Test
 | 
			
		||||
          ops = printOpsInExecutionOrder i+1, i
 | 
			
		||||
 | 
			
		||||
          console.log ""
 | 
			
		||||
      expect(@getContent(i)).to.deep.equal(@getContent(i+1))
 | 
			
		||||
      expect(@compare(@users[i], @users[i+1])).to.not.be.undefined
 | 
			
		||||
 | 
			
		||||
  run: ()->
 | 
			
		||||
    if @debug
 | 
			
		||||
@ -182,7 +194,7 @@ module.exports = class Test
 | 
			
		||||
        @doSomething()
 | 
			
		||||
      @flushAll(false)
 | 
			
		||||
      for u in @users
 | 
			
		||||
        u.HB.emptyGarbage()
 | 
			
		||||
        u._model.HB.emptyGarbage()
 | 
			
		||||
      for i in [1..Math.floor(@doSomething_amount/2)]
 | 
			
		||||
        @doSomething()
 | 
			
		||||
 | 
			
		||||
@ -195,12 +207,11 @@ module.exports = class Test
 | 
			
		||||
    # in case of JsonFramework, every user will create its JSON first! therefore, the testusers id must be small than all the others (see InsertType)
 | 
			
		||||
    @users[@users.length] = @makeNewUser (-1) # this does not want to join with anymody
 | 
			
		||||
 | 
			
		||||
    @users[@users.length-1].HB.renewStateVector @users[0].HB.getOperationCounter()
 | 
			
		||||
    @users[@users.length-1].engine.applyOps @users[0].HB._encode()
 | 
			
		||||
    @users[@users.length-1]._model.HB.renewStateVector @users[0]._model.HB.getOperationCounter()
 | 
			
		||||
    @users[@users.length-1]._model.engine.applyOps @users[0]._model.HB._encode()
 | 
			
		||||
 | 
			
		||||
    #if @getContent(@users.length-1) isnt @getContent(0)
 | 
			
		||||
    #  console.log "testHBencoding:"
 | 
			
		||||
    #  console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}"
 | 
			
		||||
    #  console.log "Unprocessed ops last: #{@users[@users.length-1].engine.unprocessed_ops.length}"
 | 
			
		||||
    expect(@getContent(@users.length-1)).to.deep.equal(@getContent(0))
 | 
			
		||||
 | 
			
		||||
    expect(@compare(@users[@users.length-1], @users[0])).to.not.be.undefined
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ class TextTest extends Test
 | 
			
		||||
    new Y conn
 | 
			
		||||
 | 
			
		||||
  initUsers: (u)->
 | 
			
		||||
    u.val("TextTest","","mutable")
 | 
			
		||||
    u.val("TextTest",new Y.Text())
 | 
			
		||||
 | 
			
		||||
  getRandomRoot: (user_num)->
 | 
			
		||||
    @users[user_num].val("TextTest")
 | 
			
		||||
@ -46,7 +46,7 @@ describe "TextFramework", ->
 | 
			
		||||
    expect(u.val()).to.equal("bcxyz")
 | 
			
		||||
 | 
			
		||||
  it "Observers work on shared Text (insert type observers, local and foreign)", ->
 | 
			
		||||
    u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
 | 
			
		||||
    u = @yTest.users[0].val("TextTest",new Y.Text("my awesome Text")).val("TextTest")
 | 
			
		||||
    @yTest.flushAll()
 | 
			
		||||
    last_task = null
 | 
			
		||||
    observer1 = (changes)->
 | 
			
		||||
@ -80,7 +80,7 @@ describe "TextFramework", ->
 | 
			
		||||
    u.unobserve observer2
 | 
			
		||||
 | 
			
		||||
  it "Observers work on shared Text (delete type observers, local and foreign)", ->
 | 
			
		||||
    u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
 | 
			
		||||
    u = @yTest.users[0].val("TextTest",new Y.Text("my awesome Text")).val("TextTest")
 | 
			
		||||
    @yTest.flushAll()
 | 
			
		||||
    last_task = null
 | 
			
		||||
    observer1 = (changes)->
 | 
			
		||||
@ -123,17 +123,17 @@ describe "TextFramework", ->
 | 
			
		||||
    @yTest.run()
 | 
			
		||||
    u1 = test.users[0]
 | 
			
		||||
    u2 = @yTest.users[1]
 | 
			
		||||
    ops1 = u1.HB._encode()
 | 
			
		||||
    ops2 = u2.HB._encode()
 | 
			
		||||
    u1.engine.applyOp ops2, true
 | 
			
		||||
    u2.engine.applyOp ops1, true
 | 
			
		||||
    ops1 = u1._model.HB._encode()
 | 
			
		||||
    ops2 = u2._model.HB._encode()
 | 
			
		||||
    u1._model.engine.applyOp ops2, true
 | 
			
		||||
    u2._model.engine.applyOp ops1, true
 | 
			
		||||
    compare = (o1, o2)->
 | 
			
		||||
      if o1.type? and o1.type isnt o2.type
 | 
			
		||||
      if o1._name? and o1._name isnt o2._name
 | 
			
		||||
        throw new Error "different types"
 | 
			
		||||
      else if o1.type is "Object"
 | 
			
		||||
      else if o1._name is "Object"
 | 
			
		||||
        for name, val of o1.val()
 | 
			
		||||
          compare(val, o2.val(name))
 | 
			
		||||
      else if o1.type?
 | 
			
		||||
      else if o1._name?
 | 
			
		||||
        compare(o1.val(), o2.val())
 | 
			
		||||
      else if o1 isnt o2
 | 
			
		||||
        throw new Error "different values"
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user