refactoring text types
This commit is contained in:
parent
c65f11b308
commit
5ba0a7492a
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -130,9 +130,11 @@ module.exports = function(HB) {
|
||||
};
|
||||
|
||||
Operation.prototype.saveOperation = function(name, op) {
|
||||
if (((op != null ? op.execute : void 0) != null) || typeof op === "string") {
|
||||
if (op == null) {
|
||||
|
||||
} else if ((op.execute != null) || !((op.op_number != null) && (op.creator != null))) {
|
||||
return this[name] = op;
|
||||
} else if (op != null) {
|
||||
} else {
|
||||
if (this.unchecked == null) {
|
||||
this.unchecked = {};
|
||||
}
|
||||
@ -207,7 +209,14 @@ module.exports = function(HB) {
|
||||
types.Insert = (function(_super) {
|
||||
__extends(Insert, _super);
|
||||
|
||||
function Insert(uid, prev_cl, next_cl, origin, parent) {
|
||||
function Insert(content, uid, prev_cl, next_cl, origin, parent) {
|
||||
if (content === void 0) {
|
||||
|
||||
} else if ((content != null) && (content.creator != null)) {
|
||||
this.saveOperation('content', content);
|
||||
} else {
|
||||
this.content = content;
|
||||
}
|
||||
this.saveOperation('parent', parent);
|
||||
this.saveOperation('prev_cl', prev_cl);
|
||||
this.saveOperation('next_cl', next_cl);
|
||||
@ -221,6 +230,10 @@ module.exports = function(HB) {
|
||||
|
||||
Insert.prototype.type = "Insert";
|
||||
|
||||
Insert.prototype.val = function() {
|
||||
return this.content;
|
||||
};
|
||||
|
||||
Insert.prototype.applyDelete = function(o) {
|
||||
var callLater, garbagecollect, _ref;
|
||||
if (this.deleted_by == null) {
|
||||
@ -242,8 +255,12 @@ module.exports = function(HB) {
|
||||
this.callOperationSpecificDeleteEvents(o);
|
||||
}
|
||||
if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) {
|
||||
return this.prev_cl.applyDelete();
|
||||
this.prev_cl.applyDelete();
|
||||
}
|
||||
if (this.content instanceof types.Operation) {
|
||||
this.content.applyDelete();
|
||||
}
|
||||
return delete this.content;
|
||||
};
|
||||
|
||||
Insert.prototype.cleanup = function() {
|
||||
@ -286,6 +303,9 @@ module.exports = function(HB) {
|
||||
if (!this.validateSavedOperations()) {
|
||||
return false;
|
||||
} else {
|
||||
if (this.content instanceof types.Operation) {
|
||||
this.content.insert_parent = this;
|
||||
}
|
||||
if (this.parent != null) {
|
||||
if (this.prev_cl == null) {
|
||||
this.prev_cl = this.parent.beginning;
|
||||
@ -380,9 +400,39 @@ module.exports = function(HB) {
|
||||
return position;
|
||||
};
|
||||
|
||||
Insert.prototype._encode = function() {
|
||||
var json, _ref;
|
||||
json = {
|
||||
'type': this.type,
|
||||
'uid': this.getUid(),
|
||||
'prev': this.prev_cl.getUid(),
|
||||
'next': this.next_cl.getUid(),
|
||||
'parent': this.parent.getUid()
|
||||
};
|
||||
if (this.origin.type === "Delimiter") {
|
||||
json.origin = "Delimiter";
|
||||
} else if (this.origin !== this.prev_cl) {
|
||||
json.origin = this.origin.getUid();
|
||||
}
|
||||
if (((_ref = this.content) != null ? _ref.getUid : void 0) != null) {
|
||||
json['content'] = this.content.getUid();
|
||||
} else {
|
||||
json['content'] = JSON.stringify(this.content);
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
return Insert;
|
||||
|
||||
})(types.Operation);
|
||||
types.Insert.parse = function(json) {
|
||||
var content, next, origin, parent, prev, uid;
|
||||
content = json['content'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], parent = json['parent'];
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
return new this(content, uid, prev, next, origin, parent);
|
||||
};
|
||||
types.ImmutableObject = (function(_super) {
|
||||
__extends(ImmutableObject, _super);
|
||||
|
||||
|
@ -37,7 +37,7 @@ module.exports = function(HB) {
|
||||
o = val[name];
|
||||
if (o instanceof types.Object) {
|
||||
json[name] = o.toJson(transform_to_value);
|
||||
} else if (o instanceof types.Array) {
|
||||
} else if (o instanceof types.ListManager) {
|
||||
json[name] = o.toJson(transform_to_value);
|
||||
} else if (transform_to_value && o instanceof types.Operation) {
|
||||
json[name] = o.val();
|
||||
|
@ -102,6 +102,42 @@ module.exports = function(HB) {
|
||||
|
||||
ListManager.prototype.type = "ListManager";
|
||||
|
||||
ListManager.prototype.applyDelete = function() {
|
||||
var o;
|
||||
o = this.end;
|
||||
while (o != null) {
|
||||
o.applyDelete();
|
||||
o = o.prev_cl;
|
||||
}
|
||||
return ListManager.__super__.applyDelete.call(this);
|
||||
};
|
||||
|
||||
ListManager.prototype.cleanup = function() {
|
||||
return ListManager.__super__.cleanup.call(this);
|
||||
};
|
||||
|
||||
ListManager.prototype.toJson = function(transform_to_value) {
|
||||
var i, o, val, _i, _len, _results;
|
||||
if (transform_to_value == null) {
|
||||
transform_to_value = false;
|
||||
}
|
||||
val = this.val();
|
||||
_results = [];
|
||||
for (o = _i = 0, _len = val.length; _i < _len; o = ++_i) {
|
||||
i = val[o];
|
||||
if (o instanceof types.Object) {
|
||||
_results.push(o.toJson(transform_to_value));
|
||||
} else if (o instanceof types.ListManager) {
|
||||
_results.push(o.toJson(transform_to_value));
|
||||
} else if (transform_to_value && o instanceof types.Operation) {
|
||||
_results.push(o.val());
|
||||
} else {
|
||||
_results.push(o);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
ListManager.prototype.execute = function() {
|
||||
if (this.validateSavedOperations()) {
|
||||
this.beginning.setParent(this);
|
||||
@ -125,12 +161,53 @@ module.exports = function(HB) {
|
||||
o = this.beginning.next_cl;
|
||||
result = [];
|
||||
while (o !== this.end) {
|
||||
result.push(o);
|
||||
if (!o.is_deleted) {
|
||||
result.push(o);
|
||||
}
|
||||
o = o.next_cl;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
ListManager.prototype.map = function(f) {
|
||||
var o, result;
|
||||
o = this.beginning.next_cl;
|
||||
result = [];
|
||||
while (o !== this.end) {
|
||||
if (!o.is_deleted) {
|
||||
result.push(f(o));
|
||||
}
|
||||
o = o.next_cl;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
ListManager.prototype.fold = function(init, f) {
|
||||
var o;
|
||||
o = this.beginning.next_cl;
|
||||
while (o !== this.end) {
|
||||
if (!o.is_deleted) {
|
||||
init = f(init, o);
|
||||
}
|
||||
o = o.next_cl;
|
||||
}
|
||||
return init;
|
||||
};
|
||||
|
||||
ListManager.prototype.val = function(pos) {
|
||||
var o;
|
||||
if (pos != null) {
|
||||
o = this.getOperationByPosition(pos + 1);
|
||||
if (!(o instanceof types.Delimiter)) {
|
||||
return o.val();
|
||||
} else {
|
||||
throw new Error("this position does not exist");
|
||||
}
|
||||
} else {
|
||||
return this.toArray();
|
||||
}
|
||||
};
|
||||
|
||||
ListManager.prototype.getOperationByPosition = function(position) {
|
||||
var o;
|
||||
o = this.beginning;
|
||||
@ -153,9 +230,97 @@ module.exports = function(HB) {
|
||||
return o;
|
||||
};
|
||||
|
||||
ListManager.prototype.push = function(content) {
|
||||
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 = types[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;
|
||||
}
|
||||
};
|
||||
right = left.next_cl;
|
||||
while (right.isDeleted()) {
|
||||
right = right.next_cl;
|
||||
}
|
||||
left = right.prev_cl;
|
||||
if (content instanceof types.Operation) {
|
||||
(new types.Insert(content, void 0, left, right)).execute();
|
||||
} else {
|
||||
for (_i = 0, _len = content.length; _i < _len; _i++) {
|
||||
c = content[_i];
|
||||
tmp = (new types.Insert(createContent(c, options), void 0, left, right)).execute();
|
||||
left = tmp;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
ListManager.prototype.insert = function(position, content, options) {
|
||||
var ith;
|
||||
ith = this.getOperationByPosition(position);
|
||||
return this.insertAfter(ith, [content], options);
|
||||
};
|
||||
|
||||
ListManager.prototype["delete"] = function(position, length) {
|
||||
var d, delete_ops, i, o, _i;
|
||||
o = this.getOperationByPosition(position + 1);
|
||||
delete_ops = [];
|
||||
for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) {
|
||||
if (o instanceof types.Delimiter) {
|
||||
break;
|
||||
}
|
||||
d = (new types.Delete(void 0, o)).execute();
|
||||
o = o.next_cl;
|
||||
while ((!(o instanceof types.Delimiter)) && o.isDeleted()) {
|
||||
o = o.next_cl;
|
||||
}
|
||||
delete_ops.push(d._encode());
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
ListManager.prototype._encode = function() {
|
||||
var json;
|
||||
json = {
|
||||
'type': this.type,
|
||||
'uid': this.getUid()
|
||||
};
|
||||
return json;
|
||||
};
|
||||
|
||||
return ListManager;
|
||||
|
||||
})(types.Operation);
|
||||
types.ListManager.parse = function(json) {
|
||||
var uid;
|
||||
uid = json['uid'];
|
||||
return new this(uid);
|
||||
};
|
||||
types.Array = function() {};
|
||||
types.Array.create = function(content, mutable) {
|
||||
var ith, list;
|
||||
if (mutable === "mutable") {
|
||||
list = new types.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\"!!");
|
||||
}
|
||||
};
|
||||
types.ReplaceManager = (function(_super) {
|
||||
__extends(ReplaceManager, _super);
|
||||
|
||||
@ -240,13 +405,8 @@ module.exports = function(HB) {
|
||||
__extends(Replaceable, _super);
|
||||
|
||||
function Replaceable(content, parent, uid, prev, next, origin, is_deleted) {
|
||||
if ((content != null) && (content.creator != null)) {
|
||||
this.saveOperation('content', content);
|
||||
} else {
|
||||
this.content = content;
|
||||
}
|
||||
this.saveOperation('parent', parent);
|
||||
Replaceable.__super__.constructor.call(this, uid, prev, next, origin);
|
||||
Replaceable.__super__.constructor.call(this, content, uid, prev, next, origin);
|
||||
this.is_deleted = is_deleted;
|
||||
}
|
||||
|
||||
|
@ -9,241 +9,6 @@ module.exports = function(HB) {
|
||||
structured_types = structured_types_uninitialized(HB);
|
||||
types = structured_types.types;
|
||||
parser = structured_types.parser;
|
||||
types.TextInsert = (function(_super) {
|
||||
__extends(TextInsert, _super);
|
||||
|
||||
function TextInsert(content, uid, prev, next, origin, parent) {
|
||||
if (content != null ? content.creator : void 0) {
|
||||
this.saveOperation('content', content);
|
||||
} else {
|
||||
this.content = content;
|
||||
}
|
||||
TextInsert.__super__.constructor.call(this, uid, prev, next, origin, parent);
|
||||
}
|
||||
|
||||
TextInsert.prototype.type = "TextInsert";
|
||||
|
||||
TextInsert.prototype.getLength = function() {
|
||||
if (this.isDeleted()) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.content.length;
|
||||
}
|
||||
};
|
||||
|
||||
TextInsert.prototype.applyDelete = function() {
|
||||
TextInsert.__super__.applyDelete.apply(this, arguments);
|
||||
if (this.content instanceof types.Operation) {
|
||||
this.content.applyDelete();
|
||||
}
|
||||
return this.content = null;
|
||||
};
|
||||
|
||||
TextInsert.prototype.execute = function() {
|
||||
if (!this.validateSavedOperations()) {
|
||||
return false;
|
||||
} else {
|
||||
if (this.content instanceof types.Operation) {
|
||||
this.content.insert_parent = this;
|
||||
}
|
||||
return TextInsert.__super__.execute.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
TextInsert.prototype.val = function(current_position) {
|
||||
if (this.isDeleted() || (this.content == null)) {
|
||||
return "";
|
||||
} else {
|
||||
return this.content;
|
||||
}
|
||||
};
|
||||
|
||||
TextInsert.prototype._encode = function() {
|
||||
var json, _ref;
|
||||
json = {
|
||||
'type': this.type,
|
||||
'uid': this.getUid(),
|
||||
'prev': this.prev_cl.getUid(),
|
||||
'next': this.next_cl.getUid(),
|
||||
'origin': this.origin.getUid(),
|
||||
'parent': this.parent.getUid()
|
||||
};
|
||||
if (((_ref = this.content) != null ? _ref.getUid : void 0) != null) {
|
||||
json['content'] = this.content.getUid();
|
||||
} else {
|
||||
json['content'] = JSON.stringify(this.content);
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
return TextInsert;
|
||||
|
||||
})(types.Insert);
|
||||
types.TextInsert.parse = function(json) {
|
||||
var content, next, origin, parent, prev, uid;
|
||||
content = json['content'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], parent = json['parent'];
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
return new types.TextInsert(content, uid, prev, next, origin, parent);
|
||||
};
|
||||
types.Array = (function(_super) {
|
||||
__extends(Array, _super);
|
||||
|
||||
function Array() {
|
||||
return Array.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
Array.prototype.type = "Array";
|
||||
|
||||
Array.prototype.applyDelete = function() {
|
||||
var o;
|
||||
o = this.end;
|
||||
while (o != null) {
|
||||
o.applyDelete();
|
||||
o = o.prev_cl;
|
||||
}
|
||||
return Array.__super__.applyDelete.call(this);
|
||||
};
|
||||
|
||||
Array.prototype.cleanup = function() {
|
||||
return Array.__super__.cleanup.call(this);
|
||||
};
|
||||
|
||||
Array.prototype.toJson = function(transform_to_value) {
|
||||
var i, o, val, _i, _len, _results;
|
||||
if (transform_to_value == null) {
|
||||
transform_to_value = false;
|
||||
}
|
||||
val = this.val();
|
||||
_results = [];
|
||||
for (o = _i = 0, _len = val.length; _i < _len; o = ++_i) {
|
||||
i = val[o];
|
||||
if (o instanceof types.Object) {
|
||||
_results.push(o.toJson(transform_to_value));
|
||||
} else if (o instanceof types.Array) {
|
||||
_results.push(o.toJson(transform_to_value));
|
||||
} else if (transform_to_value && o instanceof types.Operation) {
|
||||
_results.push(o.val());
|
||||
} else {
|
||||
_results.push(o);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
Array.prototype.val = function(pos) {
|
||||
var o, result;
|
||||
if (pos != null) {
|
||||
o = this.getOperationByPosition(pos + 1);
|
||||
if (!(o instanceof types.Delimiter)) {
|
||||
return o.val();
|
||||
} else {
|
||||
throw new Error("this position does not exist");
|
||||
}
|
||||
} else {
|
||||
o = this.beginning.next_cl;
|
||||
result = [];
|
||||
while (o !== this.end) {
|
||||
if (!o.isDeleted()) {
|
||||
result.push(o.val());
|
||||
}
|
||||
o = o.next_cl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.push = function(content) {
|
||||
return this.insertAfter(this.end.prev_cl, content);
|
||||
};
|
||||
|
||||
Array.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 = types[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;
|
||||
}
|
||||
};
|
||||
right = left.next_cl;
|
||||
while (right.isDeleted()) {
|
||||
right = right.next_cl;
|
||||
}
|
||||
left = right.prev_cl;
|
||||
if (content instanceof types.Operation) {
|
||||
(new types.TextInsert(content, void 0, left, right)).execute();
|
||||
} else {
|
||||
for (_i = 0, _len = content.length; _i < _len; _i++) {
|
||||
c = content[_i];
|
||||
tmp = (new types.TextInsert(createContent(c, options), void 0, left, right)).execute();
|
||||
left = tmp;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype.insert = function(position, content, options) {
|
||||
var ith;
|
||||
ith = this.getOperationByPosition(position);
|
||||
return this.insertAfter(ith, [content], options);
|
||||
};
|
||||
|
||||
Array.prototype["delete"] = function(position, length) {
|
||||
var d, delete_ops, i, o, _i;
|
||||
o = this.getOperationByPosition(position + 1);
|
||||
delete_ops = [];
|
||||
for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) {
|
||||
if (o instanceof types.Delimiter) {
|
||||
break;
|
||||
}
|
||||
d = (new types.Delete(void 0, o)).execute();
|
||||
o = o.next_cl;
|
||||
while ((!(o instanceof types.Delimiter)) && o.isDeleted()) {
|
||||
o = o.next_cl;
|
||||
}
|
||||
delete_ops.push(d._encode());
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Array.prototype._encode = function() {
|
||||
var json;
|
||||
json = {
|
||||
'type': this.type,
|
||||
'uid': this.getUid()
|
||||
};
|
||||
return json;
|
||||
};
|
||||
|
||||
return Array;
|
||||
|
||||
})(types.ListManager);
|
||||
types.Array.parse = function(json) {
|
||||
var uid;
|
||||
uid = json['uid'];
|
||||
return new this(uid);
|
||||
};
|
||||
types.Array.create = function(content, mutable) {
|
||||
var ith, list;
|
||||
if (mutable === "mutable") {
|
||||
list = new types.Array().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\"!!");
|
||||
}
|
||||
};
|
||||
types.String = (function(_super) {
|
||||
__extends(String, _super);
|
||||
|
||||
@ -255,22 +20,9 @@ module.exports = function(HB) {
|
||||
String.prototype.type = "String";
|
||||
|
||||
String.prototype.val = function() {
|
||||
var c, o;
|
||||
c = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.toArray();
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
o = _ref[_i];
|
||||
if (o.val != null) {
|
||||
_results.push(o.val());
|
||||
} else {
|
||||
_results.push("");
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
return c.join('');
|
||||
return this.fold("", function(left, o) {
|
||||
return left + o.val();
|
||||
});
|
||||
};
|
||||
|
||||
String.prototype.toString = function() {
|
||||
@ -548,7 +300,7 @@ module.exports = function(HB) {
|
||||
|
||||
return String;
|
||||
|
||||
})(types.Array);
|
||||
})(types.ListManager);
|
||||
types.String.parse = function(json) {
|
||||
var uid;
|
||||
uid = json['uid'];
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -168,11 +168,13 @@ module.exports = (HB)->
|
||||
# We use duck-typing to check if op is instantiated since there
|
||||
# could exist multiple classes of $Operation
|
||||
#
|
||||
if op?.execute? or typeof op is "string"
|
||||
# is instantiated, or op is string. Currently "Delimiter" is saved as string
|
||||
if not op?
|
||||
# nop
|
||||
else if op.execute? or not (op.op_number? and op.creator?)
|
||||
# is instantiated, or op is string. Currently "Delimiter" is saved as string
|
||||
# (in combination with @parent you can retrieve the delimiter..)
|
||||
@[name] = op
|
||||
else if op?
|
||||
else
|
||||
# not initialized. Do it when calling $validateSavedOperations()
|
||||
@unchecked ?= {}
|
||||
@unchecked[name] = op
|
||||
@ -267,7 +269,14 @@ module.exports = (HB)->
|
||||
# @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: (uid, prev_cl, next_cl, origin, parent)->
|
||||
constructor: (content, uid, prev_cl, next_cl, origin, parent)->
|
||||
# see encode to see, why we are doing it this way
|
||||
if content is undefined
|
||||
# nop
|
||||
else if content? and content.creator?
|
||||
@saveOperation 'content', content
|
||||
else
|
||||
@content = content
|
||||
@saveOperation 'parent', parent
|
||||
@saveOperation 'prev_cl', prev_cl
|
||||
@saveOperation 'next_cl', next_cl
|
||||
@ -279,6 +288,9 @@ module.exports = (HB)->
|
||||
|
||||
type: "Insert"
|
||||
|
||||
val: ()->
|
||||
@content
|
||||
|
||||
#
|
||||
# set content to null and other stuff
|
||||
# @private
|
||||
@ -301,6 +313,12 @@ module.exports = (HB)->
|
||||
# garbage collect prev_cl
|
||||
@prev_cl.applyDelete()
|
||||
|
||||
# delete content
|
||||
if @content instanceof types.Operation
|
||||
@content.applyDelete()
|
||||
delete @content
|
||||
|
||||
|
||||
cleanup: ()->
|
||||
if @next_cl.isDeleted()
|
||||
# delete all ops that delete this insertion
|
||||
@ -343,6 +361,8 @@ module.exports = (HB)->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
if @content instanceof types.Operation
|
||||
@content.insert_parent = @ # TODO: this is probably not necessary and only nice for debugging
|
||||
if @parent?
|
||||
if not @prev_cl?
|
||||
@prev_cl = @parent.beginning
|
||||
@ -437,6 +457,46 @@ module.exports = (HB)->
|
||||
prev = prev.prev_cl
|
||||
position
|
||||
|
||||
#
|
||||
# Convert all relevant information of this operation to the json-format.
|
||||
# This result can be send to other clients.
|
||||
#
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
'parent': @parent.getUid()
|
||||
}
|
||||
|
||||
if @origin.type is "Delimiter"
|
||||
json.origin = "Delimiter"
|
||||
else if @origin isnt @prev_cl
|
||||
json.origin = @origin.getUid()
|
||||
|
||||
if @content?.getUid?
|
||||
json['content'] = @content.getUid()
|
||||
else
|
||||
json['content'] = JSON.stringify @content
|
||||
json
|
||||
|
||||
types.Insert.parse = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'parent' : parent
|
||||
} = json
|
||||
if typeof content is "string"
|
||||
content = JSON.parse(content)
|
||||
new this content, uid, prev, next, origin, parent
|
||||
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
||||
|
@ -27,7 +27,6 @@ module.exports = (HB)->
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
|
||||
#
|
||||
# Transform this to a Json. If your browser supports Object.observe it will be transformed automatically when a change arrives.
|
||||
# Otherwise you will loose all the sharing-abilities (the new object will be a deep clone)!
|
||||
@ -43,7 +42,7 @@ module.exports = (HB)->
|
||||
for name, o of val
|
||||
if o instanceof types.Object
|
||||
json[name] = o.toJson(transform_to_value)
|
||||
else if o instanceof types.Array
|
||||
else if o instanceof types.ListManager
|
||||
json[name] = o.toJson(transform_to_value)
|
||||
else if transform_to_value and o instanceof types.Operation
|
||||
json[name] = o.val()
|
||||
|
@ -87,6 +87,28 @@ module.exports = (HB)->
|
||||
|
||||
type: "ListManager"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @end
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.prev_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
toJson: (transform_to_value = false)->
|
||||
val = @val()
|
||||
for i, o in val
|
||||
if o instanceof types.Object
|
||||
o.toJson(transform_to_value)
|
||||
else if o instanceof types.ListManager
|
||||
o.toJson(transform_to_value)
|
||||
else if transform_to_value and o instanceof types.Operation
|
||||
o.val()
|
||||
else
|
||||
o
|
||||
|
||||
#
|
||||
# @private
|
||||
# @see Operation.execute
|
||||
@ -113,10 +135,39 @@ module.exports = (HB)->
|
||||
o = @beginning.next_cl
|
||||
result = []
|
||||
while o isnt @end
|
||||
result.push o
|
||||
if not o.is_deleted
|
||||
result.push o
|
||||
o = o.next_cl
|
||||
result
|
||||
|
||||
map: (f)->
|
||||
o = @beginning.next_cl
|
||||
result = []
|
||||
while o isnt @end
|
||||
if not o.is_deleted
|
||||
result.push f(o)
|
||||
o = o.next_cl
|
||||
result
|
||||
|
||||
fold: (init, f)->
|
||||
o = @beginning.next_cl
|
||||
while o isnt @end
|
||||
if not o.is_deleted
|
||||
init = f(init, o)
|
||||
o = o.next_cl
|
||||
init
|
||||
|
||||
val: (pos)->
|
||||
if pos?
|
||||
o = @getOperationByPosition(pos+1)
|
||||
if not (o instanceof types.Delimiter)
|
||||
o.val()
|
||||
else
|
||||
throw new Error "this position does not exist"
|
||||
else
|
||||
@toArray()
|
||||
|
||||
|
||||
#
|
||||
# Retrieves the x-th not deleted element.
|
||||
# e.g. "abc" : the 1th character is "a"
|
||||
@ -142,6 +193,93 @@ module.exports = (HB)->
|
||||
position -= 1
|
||||
o
|
||||
|
||||
push: (content)->
|
||||
@insertAfter @end.prev_cl, content
|
||||
|
||||
insertAfter: (left, content, options)->
|
||||
createContent = (content, options)->
|
||||
if content? and content.constructor?
|
||||
type = types[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
|
||||
|
||||
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
|
||||
|
||||
if content instanceof types.Operation
|
||||
(new types.Insert content, undefined, left, right).execute()
|
||||
else
|
||||
for c in content
|
||||
tmp = (new types.Insert createContent(c, options), undefined, left, right).execute()
|
||||
left = tmp
|
||||
@
|
||||
|
||||
#
|
||||
# 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
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
#
|
||||
# @return {ListManager Type} This String object
|
||||
#
|
||||
delete: (position, length)->
|
||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||
|
||||
delete_ops = []
|
||||
for i in [0...length]
|
||||
if o instanceof types.Delimiter
|
||||
break
|
||||
d = (new types.Delete undefined, o).execute()
|
||||
o = o.next_cl
|
||||
while (not (o instanceof types.Delimiter)) and o.isDeleted()
|
||||
o = o.next_cl
|
||||
delete_ops.push d._encode()
|
||||
@
|
||||
|
||||
#
|
||||
# @private
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
}
|
||||
json
|
||||
|
||||
types.ListManager.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new this(uid)
|
||||
|
||||
types.Array = ()->
|
||||
types.Array.create = (content, mutable)->
|
||||
if (mutable is "mutable")
|
||||
list = new types.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\"!!"
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
||||
@ -245,13 +383,8 @@ module.exports = (HB)->
|
||||
# @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)->
|
||||
# see encode to see, why we are doing it this way
|
||||
if content? and content.creator?
|
||||
@saveOperation 'content', content
|
||||
else
|
||||
@content = content
|
||||
@saveOperation 'parent', parent
|
||||
super uid, prev, next, origin # Parent is already saved by Replaceable
|
||||
super content, uid, prev, next, origin # Parent is already saved by Replaceable
|
||||
@is_deleted = is_deleted
|
||||
|
||||
type: "Replaceable"
|
||||
|
@ -5,225 +5,11 @@ module.exports = (HB)->
|
||||
types = structured_types.types
|
||||
parser = structured_types.parser
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Extends the basic Insert type to an operation that holds a text value
|
||||
#
|
||||
class types.TextInsert extends types.Insert
|
||||
#
|
||||
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (content, uid, prev, next, origin, parent)->
|
||||
if content?.creator
|
||||
@saveOperation 'content', content
|
||||
else
|
||||
@content = content
|
||||
super uid, prev, next, origin, parent
|
||||
|
||||
type: "TextInsert"
|
||||
|
||||
#
|
||||
# Retrieve the effective length of the $content of this operation.
|
||||
#
|
||||
getLength: ()->
|
||||
if @isDeleted()
|
||||
0
|
||||
else
|
||||
@content.length
|
||||
|
||||
applyDelete: ()->
|
||||
super # no braces indeed!
|
||||
if @content instanceof types.Operation
|
||||
@content.applyDelete()
|
||||
@content = null
|
||||
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
if @content instanceof types.Operation
|
||||
@content.insert_parent = @
|
||||
super()
|
||||
|
||||
#
|
||||
# The result will be concatenated with the results from the other insert operations
|
||||
# in order to retrieve the content of the engine.
|
||||
# @see HistoryBuffer.toExecutedArray
|
||||
#
|
||||
val: (current_position)->
|
||||
if @isDeleted() or not @content?
|
||||
""
|
||||
else
|
||||
@content
|
||||
|
||||
#
|
||||
# Convert all relevant information of this operation to the json-format.
|
||||
# This result can be send to other clients.
|
||||
#
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
'origin': @origin.getUid()
|
||||
'parent': @parent.getUid()
|
||||
}
|
||||
|
||||
if @content?.getUid?
|
||||
json['content'] = @content.getUid()
|
||||
else
|
||||
json['content'] = JSON.stringify @content
|
||||
json
|
||||
|
||||
types.TextInsert.parse = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'parent' : parent
|
||||
} = json
|
||||
if typeof content is "string"
|
||||
content = JSON.parse(content)
|
||||
new types.TextInsert content, uid, prev, next, origin, parent
|
||||
|
||||
|
||||
class types.Array extends types.ListManager
|
||||
|
||||
type: "Array"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @end
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.prev_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
toJson: (transform_to_value = false)->
|
||||
val = @val()
|
||||
for i, o in val
|
||||
if o instanceof types.Object
|
||||
o.toJson(transform_to_value)
|
||||
else if o instanceof types.Array
|
||||
o.toJson(transform_to_value)
|
||||
else if transform_to_value and o instanceof types.Operation
|
||||
o.val()
|
||||
else
|
||||
o
|
||||
|
||||
val: (pos)->
|
||||
if pos?
|
||||
o = @getOperationByPosition(pos+1)
|
||||
if not (o instanceof types.Delimiter)
|
||||
o.val()
|
||||
else
|
||||
throw new Error "this position does not exist"
|
||||
else
|
||||
o = @beginning.next_cl
|
||||
result = []
|
||||
while o isnt @end
|
||||
if not o.isDeleted()
|
||||
result.push o.val()
|
||||
o = o.next_cl
|
||||
result
|
||||
|
||||
push: (content)->
|
||||
@insertAfter @end.prev_cl, content
|
||||
|
||||
insertAfter: (left, content, options)->
|
||||
createContent = (content, options)->
|
||||
if content? and content.constructor?
|
||||
type = types[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
|
||||
|
||||
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
|
||||
|
||||
if content instanceof types.Operation
|
||||
(new types.TextInsert content, undefined, left, right).execute()
|
||||
else
|
||||
for c in content
|
||||
tmp = (new types.TextInsert createContent(c, options), undefined, left, right).execute()
|
||||
left = tmp
|
||||
@
|
||||
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {Array 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
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
#
|
||||
# @return {Array Type} This String object
|
||||
#
|
||||
delete: (position, length)->
|
||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||
|
||||
delete_ops = []
|
||||
for i in [0...length]
|
||||
if o instanceof types.Delimiter
|
||||
break
|
||||
d = (new types.Delete undefined, o).execute()
|
||||
o = o.next_cl
|
||||
while (not (o instanceof types.Delimiter)) and o.isDeleted()
|
||||
o = o.next_cl
|
||||
delete_ops.push d._encode()
|
||||
@
|
||||
|
||||
#
|
||||
# @private
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
}
|
||||
json
|
||||
|
||||
types.Array.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new this(uid)
|
||||
|
||||
types.Array.create = (content, mutable)->
|
||||
if (mutable is "mutable")
|
||||
list = new types.Array().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\"!!"
|
||||
|
||||
#
|
||||
# Handles a String-like data structures with support for insert/delete at a word-position.
|
||||
# @note Currently, only Text is supported!
|
||||
#
|
||||
class types.String extends types.Array
|
||||
class types.String extends types.ListManager
|
||||
|
||||
#
|
||||
# @private
|
||||
@ -250,12 +36,8 @@ module.exports = (HB)->
|
||||
# @return {String} The String-representation of this object.
|
||||
#
|
||||
val: ()->
|
||||
c = for o in @toArray()
|
||||
if o.val?
|
||||
o.val()
|
||||
else
|
||||
""
|
||||
c.join('')
|
||||
@fold "", (left, o)->
|
||||
left + o.val()
|
||||
|
||||
#
|
||||
# Same as String.val
|
||||
@ -267,7 +49,7 @@ module.exports = (HB)->
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {Array Type} This String object.
|
||||
# @return {ListManager Type} This String object.
|
||||
#
|
||||
insert: (position, content, options)->
|
||||
ith = @getOperationByPosition position
|
||||
|
@ -11,8 +11,8 @@ Connector = require "../../y-test/lib/y-test.coffee"
|
||||
module.exports = class Test
|
||||
constructor: (@name_suffix = "")->
|
||||
@number_of_test_cases_multiplier = 1
|
||||
@repeat_this = 3 * @number_of_test_cases_multiplier
|
||||
@doSomething_amount = 123 * @number_of_test_cases_multiplier
|
||||
@repeat_this = 1 * @number_of_test_cases_multiplier
|
||||
@doSomething_amount = 1230 * @number_of_test_cases_multiplier
|
||||
@number_of_engines = 5 + @number_of_test_cases_multiplier - 1
|
||||
|
||||
@time = 0 # denotes to the time when run was started
|
||||
|
@ -117,5 +117,51 @@ describe "TextFramework", ->
|
||||
it "can handle many engines, many operations, concurrently (random)", ->
|
||||
console.log("testiy deleted this TODO:dtrn")
|
||||
@yTest.run()
|
||||
|
||||
it "handles double-late-join", ->
|
||||
test = new TextTest("double")
|
||||
test.run()
|
||||
@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
|
||||
compare = (o1, o2)->
|
||||
if o1.type? and o1.type isnt o2.type
|
||||
throw new Error "different types"
|
||||
else if o1.type is "Object"
|
||||
for name, val of o1.val()
|
||||
compare(val, o2.val(name))
|
||||
else if o1.type?
|
||||
compare(o1.val(), o2.val())
|
||||
else if o1 isnt o2
|
||||
throw new Error "different values"
|
||||
compare u1, u2
|
||||
expect(test.getContent(0)).to.deep.equal(@yTest.getContent(1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user