added text as a custom type, more tests are working

This commit is contained in:
DadaMonad 2015-02-23 11:41:04 +00:00
parent 860934de06
commit 2e9f8f6d03
22 changed files with 2604 additions and 2255 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,10 @@ module.exports = function() {
ops = {}; ops = {};
execution_listener = []; execution_listener = [];
ops.Operation = (function() { 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.is_deleted = false;
this.garbage_collected = false; this.garbage_collected = false;
this.event_listeners = []; this.event_listeners = [];
@ -181,9 +184,9 @@ module.exports = function() {
ops.Delete = (function(_super) { ops.Delete = (function(_super) {
__extends(Delete, _super); __extends(Delete, _super);
function Delete(uid, deletes) { function Delete(custom_type, uid, deletes) {
this.saveOperation('deletes', deletes); this.saveOperation('deletes', deletes);
Delete.__super__.constructor.call(this, uid); Delete.__super__.constructor.call(this, custom_type, uid);
} }
Delete.prototype.type = "Delete"; Delete.prototype.type = "Delete";
@ -215,12 +218,12 @@ module.exports = function() {
ops.Delete.parse = function(o) { ops.Delete.parse = function(o) {
var deletes_uid, uid; var deletes_uid, uid;
uid = o['uid'], deletes_uid = o['deletes']; uid = o['uid'], deletes_uid = o['deletes'];
return new this(uid, deletes_uid); return new this(null, uid, deletes_uid);
}; };
ops.Insert = (function(_super) { ops.Insert = (function(_super) {
__extends(Insert, _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) { if (content === void 0) {
} else if ((content != null) && (content.creator != null)) { } else if ((content != null) && (content.creator != null)) {
@ -236,7 +239,7 @@ module.exports = function() {
} else { } else {
this.saveOperation('origin', prev_cl); this.saveOperation('origin', prev_cl);
} }
Insert.__super__.constructor.call(this, uid); Insert.__super__.constructor.call(this, custom_type, uid);
} }
Insert.prototype.type = "Insert"; Insert.prototype.type = "Insert";
@ -371,14 +374,21 @@ module.exports = function() {
}; };
Insert.prototype.callOperationSpecificInsertEvents = 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([ return (_ref = this.parent) != null ? _ref.callEvent([
{ {
type: "insert", type: "insert",
position: this.getPosition(), position: this.getPosition(),
object: this.parent, object: this.parent.getCustomType(),
changedBy: this.uid.creator, changedBy: this.uid.creator,
value: this.content value: getContentType(this.content)
} }
]) : void 0; ]) : void 0;
}; };
@ -388,7 +398,7 @@ module.exports = function() {
{ {
type: "delete", type: "delete",
position: this.getPosition(), position: this.getPosition(),
object: this.parent, object: this.parent.getCustomType(),
length: 1, length: 1,
changedBy: o.uid.creator changedBy: o.uid.creator
} }
@ -442,14 +452,14 @@ module.exports = function() {
if (typeof content === "string") { if (typeof content === "string") {
content = JSON.parse(content); 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) { ops.ImmutableObject = (function(_super) {
__extends(ImmutableObject, _super); __extends(ImmutableObject, _super);
function ImmutableObject(uid, _at_content) { function ImmutableObject(custom_type, uid, _at_content) {
this.content = _at_content; this.content = _at_content;
ImmutableObject.__super__.constructor.call(this, uid); ImmutableObject.__super__.constructor.call(this, custom_type, uid);
} }
ImmutableObject.prototype.type = "ImmutableObject"; ImmutableObject.prototype.type = "ImmutableObject";
@ -474,7 +484,7 @@ module.exports = function() {
ops.ImmutableObject.parse = function(json) { ops.ImmutableObject.parse = function(json) {
var content, uid; var content, uid;
uid = json['uid'], content = json['content']; uid = json['uid'], content = json['content'];
return new this(uid, content); return new this(null, uid, content);
}; };
ops.Delimiter = (function(_super) { ops.Delimiter = (function(_super) {
__extends(Delimiter, _super); __extends(Delimiter, _super);
@ -483,7 +493,7 @@ module.exports = function() {
this.saveOperation('prev_cl', prev_cl); this.saveOperation('prev_cl', prev_cl);
this.saveOperation('next_cl', next_cl); this.saveOperation('next_cl', next_cl);
this.saveOperation('origin', prev_cl); this.saveOperation('origin', prev_cl);
Delimiter.__super__.constructor.call(this, { Delimiter.__super__.constructor.call(this, null, {
noOperation: true noOperation: true
}); });
} }

View File

@ -12,11 +12,8 @@ module.exports = function() {
__extends(MapManager, _super); __extends(MapManager, _super);
function MapManager(custom_type, uid) { function MapManager(custom_type, uid) {
if (custom_type != null) {
this.custom_type = custom_type;
}
this._map = {}; this._map = {};
MapManager.__super__.constructor.call(this, uid); MapManager.__super__.constructor.call(this, custom_type, uid);
} }
MapManager.prototype.type = "MapManager"; MapManager.prototype.type = "MapManager";
@ -48,8 +45,8 @@ module.exports = function() {
MapManager.prototype.val = function(name, content) { MapManager.prototype.val = function(name, content) {
var o, prop, rep, res, result, _ref; var o, prop, rep, res, result, _ref;
if (arguments.length > 1) { if (arguments.length > 1) {
if ((content != null) && (content._model != null) && content._model instanceof ops.Operation) { if ((content != null) && (content._getModel != null)) {
rep = content._model; rep = content._getModel(this.custom_types, this.operations);
} else { } else {
rep = content; rep = content;
} }
@ -73,12 +70,7 @@ module.exports = function() {
for (name in _ref) { for (name in _ref) {
o = _ref[name]; o = _ref[name];
if (!o.isContentDeleted()) { if (!o.isContentDeleted()) {
res = prop.val(); result[name] = o.val();
if (res instanceof ops.Operation) {
result[name] = res.getCustomType();
} else {
result[name] = res;
}
} }
} }
return result; return result;
@ -105,7 +97,7 @@ module.exports = function() {
sub: property_name, sub: property_name,
alt: this 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; this._map[property_name] = rm;
rm.setParent(this, property_name); rm.setParent(this, property_name);
rm.execute(); rm.execute();
@ -138,13 +130,13 @@ module.exports = function() {
ops.ListManager = (function(_super) { ops.ListManager = (function(_super) {
__extends(ListManager, _super); __extends(ListManager, _super);
function ListManager(uid) { function ListManager(custom_type, uid) {
this.beginning = new ops.Delimiter(void 0, void 0); this.beginning = new ops.Delimiter(void 0, void 0);
this.end = new ops.Delimiter(this.beginning, void 0); this.end = new ops.Delimiter(this.beginning, void 0);
this.beginning.next_cl = this.end; this.beginning.next_cl = this.end;
this.beginning.execute(); this.beginning.execute();
this.end.execute(); this.end.execute();
ListManager.__super__.constructor.call(this, uid); ListManager.__super__.constructor.call(this, custom_type, uid);
} }
ListManager.prototype.type = "ListManager"; ListManager.prototype.type = "ListManager";
@ -281,42 +273,29 @@ module.exports = function() {
return this.insertAfter(this.end.prev_cl, content); return this.insertAfter(this.end.prev_cl, content);
}; };
ListManager.prototype.insertAfter = function(left, content, options) { ListManager.prototype.insertAfter = function(left, content) {
var c, createContent, right, tmp, _i, _len; var c, 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;
}
};
right = left.next_cl; right = left.next_cl;
while (right.isDeleted()) { while (right.isDeleted()) {
right = right.next_cl; right = right.next_cl;
} }
left = right.prev_cl; left = right.prev_cl;
if (content instanceof ops.Operation) { 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 { } else {
for (_i = 0, _len = content.length; _i < _len; _i++) { for (_i = 0, _len = content.length; _i < _len; _i++) {
c = content[_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; left = tmp;
} }
} }
return this; return this;
}; };
ListManager.prototype.insert = function(position, content, options) { ListManager.prototype.insert = function(position, content) {
var ith; var ith;
ith = this.getOperationByPosition(position); ith = this.getOperationByPosition(position);
return this.insertAfter(ith, [content], options); return this.insertAfter(ith, [content]);
}; };
ListManager.prototype["delete"] = function(position, length) { ListManager.prototype["delete"] = function(position, length) {
@ -327,7 +306,7 @@ module.exports = function() {
if (o instanceof ops.Delimiter) { if (o instanceof ops.Delimiter) {
break; break;
} }
d = (new ops.Delete(void 0, o)).execute(); d = (new ops.Delete(null, void 0, o)).execute();
o = o.next_cl; o = o.next_cl;
while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) { while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) {
o = o.next_cl; o = o.next_cl;
@ -343,6 +322,11 @@ module.exports = function() {
'type': this.type, 'type': this.type,
'uid': this.getUid() '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; return json;
}; };
@ -350,34 +334,20 @@ module.exports = function() {
})(ops.Operation); })(ops.Operation);
ops.ListManager.parse = function(json) { ops.ListManager.parse = function(json) {
var uid; var custom_type, uid;
uid = json['uid']; uid = json['uid'], custom_type = json['custom_type'];
return new this(uid); return new this(custom_type, 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\"!!");
}
}; };
ops.ReplaceManager = (function(_super) { ops.ReplaceManager = (function(_super) {
__extends(ReplaceManager, _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_properties = _at_event_properties;
this.event_this = _at_event_this; this.event_this = _at_event_this;
if (this.event_properties['object'] == null) { if (this.event_properties['object'] == null) {
this.event_properties['object'] = this.event_this; 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"; ReplaceManager.prototype.type = "ReplaceManager";
@ -415,7 +385,7 @@ module.exports = function() {
ReplaceManager.prototype.replace = function(content, replaceable_uid) { ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, relp; var o, relp;
o = this.getLastOperation(); 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; return void 0;
}; };
@ -424,7 +394,7 @@ module.exports = function() {
}; };
ReplaceManager.prototype.deleteContent = 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; return void 0;
}; };
@ -451,9 +421,9 @@ module.exports = function() {
ops.Replaceable = (function(_super) { ops.Replaceable = (function(_super) {
__extends(Replaceable, _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); 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; this.is_deleted = is_deleted;
} }
@ -560,9 +530,9 @@ module.exports = function() {
})(ops.Insert); })(ops.Insert);
ops.Replaceable.parse = function(json) { ops.Replaceable.parse = function(json) {
var content, is_deleted, next, origin, parent, prev, uid; 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']; 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(content, parent, uid, prev, next, origin, is_deleted); return new this(custom_type, content, parent, uid, prev, next, origin, is_deleted);
}; };
return basic_ops; return basic_ops;
}; };

View File

@ -63,6 +63,10 @@ YObject = (function() {
} }
}; };
YObject.prototype["delete"] = function(name) {
return this._model["delete"](name);
};
return YObject; return YObject;
})(); })();

335
build/node/Types/Text.js Normal file
View 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 += '&nbsp;');
} 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;
}

View File

@ -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"); HistoryBuffer = require("./HistoryBuffer");
@ -21,7 +21,7 @@ createY = function(connector) {
}; };
} }
HB = new HistoryBuffer(user_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; ops = ops_manager.operations;
engine = new Engine(HB, ops); engine = new Engine(HB, ops);
adaptConnector(connector, engine, HB, ops_manager.execution_listener); adaptConnector(connector, engine, HB, ops_manager.execution_listener);
@ -29,7 +29,7 @@ createY = function(connector) {
ops.Operation.prototype.operations = ops; ops.Operation.prototype.operations = ops;
ops.Operation.prototype.engine = engine; ops.Operation.prototype.engine = engine;
ops.Operation.prototype.connector = connector; ops.Operation.prototype.connector = connector;
ops.Operation.prototype.custom_ops = this.constructor; ops.Operation.prototype.custom_types = this.constructor;
ct = new createY.Object(); ct = new createY.Object();
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute(); model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute();
ct._setModel(model); ct._setModel(model);
@ -43,3 +43,5 @@ if (typeof window !== "undefined" && window !== null) {
} }
createY.Object = require("./Types/Object"); 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

View File

@ -29,7 +29,7 @@ files =
test : ['./test/Json_test.coffee', './test/Text_test.coffee'] test : ['./test/Json_test.coffee', './test/Text_test.coffee']
gulp : ['./gulpfile.coffee'] gulp : ['./gulpfile.coffee']
examples : ['./examples/**/*.js'] examples : ['./examples/**/*.js']
other: ['./lib/**/*'] other: ['./lib/**/*', './test/*']
files.all = [] files.all = []
for name,file_list of files for name,file_list of files

View File

@ -22,7 +22,9 @@ module.exports = ()->
# @param {Object} uid A unique identifier. # @param {Object} uid A unique identifier.
# If uid is undefined, a new uid will be created before at the end of the execution sequence # 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 @is_deleted = false
@garbage_collected = false @garbage_collected = false
@event_listeners = [] # TODO: rename to observers or sth like that @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} 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. # @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 @saveOperation 'deletes', deletes
super uid super custom_type, uid
type: "Delete" type: "Delete"
@ -260,7 +262,7 @@ module.exports = ()->
'uid' : uid 'uid' : uid
'deletes': deletes_uid 'deletes': deletes_uid
} = o } = o
new this(uid, deletes_uid) new this(null, uid, deletes_uid)
# #
# @nodoc # @nodoc
@ -279,7 +281,7 @@ module.exports = ()->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl) # @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) # @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 # see encode to see, why we are doing it this way
if content is undefined if content is undefined
# nop # nop
@ -294,7 +296,7 @@ module.exports = ()->
@saveOperation 'origin', origin @saveOperation 'origin', origin
else else
@saveOperation 'origin', prev_cl @saveOperation 'origin', prev_cl
super uid super custom_type, uid
type: "Insert" type: "Insert"
@ -436,19 +438,24 @@ module.exports = ()->
@ @
callOperationSpecificInsertEvents: ()-> callOperationSpecificInsertEvents: ()->
getContentType = (content)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@parent?.callEvent [ @parent?.callEvent [
type: "insert" type: "insert"
position: @getPosition() position: @getPosition()
object: @parent object: @parent.getCustomType()
changedBy: @uid.creator changedBy: @uid.creator
value: @content value: getContentType @content
] ]
callOperationSpecificDeleteEvents: (o)-> callOperationSpecificDeleteEvents: (o)->
@parent.callEvent [ @parent.callEvent [
type: "delete" type: "delete"
position: @getPosition() 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 length: 1
changedBy: o.uid.creator changedBy: o.uid.creator
] ]
@ -503,7 +510,7 @@ module.exports = ()->
} = json } = json
if typeof content is "string" if typeof content is "string"
content = JSON.parse(content) 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} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} content # @param {Object} content
# #
constructor: (uid, @content)-> constructor: (custom_type, uid, @content)->
super uid super custom_type, uid
type: "ImmutableObject" type: "ImmutableObject"
@ -544,7 +551,7 @@ module.exports = ()->
'uid' : uid 'uid' : uid
'content' : content 'content' : content
} = json } = json
new this(uid, content) new this(null, uid, content)
# #
# @nodoc # @nodoc
@ -562,7 +569,7 @@ module.exports = ()->
@saveOperation 'prev_cl', prev_cl @saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl @saveOperation 'next_cl', next_cl
@saveOperation 'origin', prev_cl @saveOperation 'origin', prev_cl
super {noOperation: true} super null, {noOperation: true}
type: "Delimiter" type: "Delimiter"

View File

@ -14,10 +14,8 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created. # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# #
constructor: (custom_type, uid)-> constructor: (custom_type, uid)->
if custom_type?
@custom_type = custom_type
@_map = {} @_map = {}
super uid super custom_type, uid
type: "MapManager" type: "MapManager"
@ -39,8 +37,8 @@ module.exports = ()->
# #
val: (name, content)-> val: (name, content)->
if arguments.length > 1 if arguments.length > 1
if content? and content._model? and content._model instanceof ops.Operation if content? and content._getModel?
rep = content._model rep = content._getModel(@custom_types, @operations)
else else
rep = content rep = content
@retrieveSub(name).replace rep @retrieveSub(name).replace rep
@ -59,11 +57,7 @@ module.exports = ()->
result = {} result = {}
for name,o of @_map for name,o of @_map
if not o.isContentDeleted() if not o.isContentDeleted()
res = prop.val() result[name] = o.val()
if res instanceof ops.Operation
result[name] = res.getCustomType()
else
result[name] = res
result result
delete: (name)-> delete: (name)->
@ -79,7 +73,7 @@ module.exports = ()->
noOperation: true noOperation: true
sub: property_name sub: property_name
alt: @ 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 @_map[property_name] = rm
rm.setParent @, property_name rm.setParent @, property_name
rm.execute() 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 {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object. # @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object. # @param {Delimiter} end Reference or Object.
constructor: (uid)-> constructor: (custom_type, uid)->
@beginning = new ops.Delimiter undefined, undefined @beginning = new ops.Delimiter undefined, undefined
@end = new ops.Delimiter @beginning, undefined @end = new ops.Delimiter @beginning, undefined
@beginning.next_cl = @end @beginning.next_cl = @end
@beginning.execute() @beginning.execute()
@end.execute() @end.execute()
super uid super custom_type, uid
type: "ListManager" type: "ListManager"
@ -238,27 +232,18 @@ module.exports = ()->
push: (content)-> push: (content)->
@insertAfter @end.prev_cl, content @insertAfter @end.prev_cl, content
insertAfter: (left, content, options)-> insertAfter: (left, content)->
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
right = left.next_cl right = left.next_cl
while right.isDeleted() 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. 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 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 if content instanceof ops.Operation
(new ops.Insert content, undefined, left, right).execute() (new ops.Insert null, content, undefined, left, right).execute()
else else
for c in content 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 left = tmp
@ @
@ -267,11 +252,11 @@ module.exports = ()->
# #
# @return {ListManager Type} This String object. # @return {ListManager Type} This String object.
# #
insert: (position, content, options)-> insert: (position, content)->
ith = @getOperationByPosition position ith = @getOperationByPosition position
# the (i-1)th character. e.g. "abc" the 1th character is "a" # the (i-1)th character. e.g. "abc" the 1th character is "a"
# the 0th character is the left Delimiter # the 0th character is the left Delimiter
@insertAfter ith, [content], options @insertAfter ith, [content]
# #
# Deletes a part of the word. # Deletes a part of the word.
@ -285,7 +270,7 @@ module.exports = ()->
for i in [0...length] for i in [0...length]
if o instanceof ops.Delimiter if o instanceof ops.Delimiter
break break
d = (new ops.Delete undefined, o).execute() d = (new ops.Delete null, undefined, o).execute()
o = o.next_cl o = o.next_cl
while (not (o instanceof ops.Delimiter)) and o.isDeleted() while (not (o instanceof ops.Delimiter)) and o.isDeleted()
o = o.next_cl o = o.next_cl
@ -301,26 +286,18 @@ module.exports = ()->
'type': @type 'type': @type
'uid' : @getUid() 'uid' : @getUid()
} }
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json json
ops.ListManager.parse = (json)-> ops.ListManager.parse = (json)->
{ {
'uid' : uid 'uid' : uid
'custom_type': custom_type
} = json } = json
new this(uid) new this(custom_type, 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\"!!"
# #
# @nodoc # @nodoc
@ -338,10 +315,10 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created. # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object. # @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end 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']? if not @event_properties['object']?
@event_properties['object'] = @event_this @event_properties['object'] = @event_this
super uid, beginning, end super custom_type, uid, beginning, end
type: "ReplaceManager" type: "ReplaceManager"
@ -378,7 +355,7 @@ module.exports = ()->
# #
replace: (content, replaceable_uid)-> replace: (content, replaceable_uid)->
o = @getLastOperation() 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) # TODO: delete repl (for debugging)
undefined undefined
@ -386,7 +363,7 @@ module.exports = ()->
@getLastOperation().isDeleted() @getLastOperation().isDeleted()
deleteContent: ()-> deleteContent: ()->
(new ops.Delete undefined, @getLastOperation().uid).execute() (new ops.Delete null, undefined, @getLastOperation().uid).execute()
undefined undefined
# #
@ -424,9 +401,9 @@ module.exports = ()->
# @param {ReplaceManager} parent Used to replace this Replaceable with another one. # @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. # @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 @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 @is_deleted = is_deleted
type: "Replaceable" type: "Replaceable"
@ -525,8 +502,9 @@ module.exports = ()->
'next': next 'next': next
'origin' : origin 'origin' : origin
'is_deleted': is_deleted 'is_deleted': is_deleted
'custom_type' : custom_type
} = json } = 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 basic_ops

View File

@ -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 += '&nbsp;'
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
View File

View File

@ -55,6 +55,9 @@ class YObject
res[n] = v res[n] = v
res res
delete: (name)->
@_model.delete(name)
if window? if window?
if window.Y? if window.Y?
window.Y.Object = YObject window.Y.Object = YObject

306
lib/Types/Text.coffee Normal file
View 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 += '&nbsp;'
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

View File

@ -1,5 +1,5 @@
text_ops_uninitialized = require "./Operations/Text" structured_ops_uninitialized = require "./Operations/Structured"
HistoryBuffer = require "./HistoryBuffer" HistoryBuffer = require "./HistoryBuffer"
Engine = require "./Engine" Engine = require "./Engine"
@ -15,7 +15,7 @@ createY = (connector)->
user_id = id user_id = id
HB.resetUserId id HB.resetUserId id
HB = new HistoryBuffer user_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 ops = ops_manager.operations
engine = new Engine HB, ops engine = new Engine HB, ops
@ -25,7 +25,7 @@ createY = (connector)->
ops.Operation.prototype.operations = ops ops.Operation.prototype.operations = ops
ops.Operation.prototype.engine = engine ops.Operation.prototype.engine = engine
ops.Operation.prototype.connector = connector ops.Operation.prototype.connector = connector
ops.Operation.prototype.custom_ops = this.constructor ops.Operation.prototype.custom_types = this.constructor
ct = new createY.Object() ct = new createY.Object()
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute() model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute()
@ -37,3 +37,4 @@ if window?
window.Y = createY window.Y = createY
createY.Object = require "./Types/Object" createY.Object = require "./Types/Object"
createY.Text = require "./Types/Text"

View File

@ -11,17 +11,16 @@ Connector = require "../../y-test/lib/y-test.coffee"
Y = require "../lib/y.coffee" Y = require "../lib/y.coffee"
compare = (o1, o2)-> 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" throw new Error "different types"
else if o1.type is "Object" else if o1._name is "Object"
for name, val of o1.val() for name, val of o1.val()
compare(val, o2.val(name)) compare(val, o2.val(name))
else if o1.type? else if o1._name?
compare(o1.val(), o2.val()) compare(o1.val(), o2.val())
else if o1 isnt o2 else if o1 isnt o2
throw new Error "different values" throw new Error "different values"
Test = require "./TestSuite" Test = require "./TestSuite"
class JsonTest extends Test class JsonTest extends Test
@ -38,17 +37,17 @@ class JsonTest extends Test
root root
else # take child else # take child
elems = null elems = null
if root.type is "Object" if root._name is "Object"
elems = elems =
for oname,val of root.val() for oname,val of root.val()
val val
else if root.type is "Array" else if root._name is "Array"
elems = root.val() elems = root.val()
else else
return root return root
elems = elems.filter (elem)-> 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 if elems.length is 0
root root
else else
@ -58,9 +57,26 @@ class JsonTest extends Test
getGeneratingFunctions: (user_num)-> getGeneratingFunctions: (user_num)->
types = @users[user_num]._model.operations types = @users[user_num]._model.operations
super(user_num).concat [ 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 f : (y)=> # SET PROPERTY
l = y.val().length l = y.val().length
y.val(_.random(0, l-1), @getRandomText(), 'immutable') y.val(_.random(0, l-1), @getRandomText())
null null
types : [types.Array] types : [types.Array]
, f : (y)=> # Delete Array Element , f : (y)=> # Delete Array Element
@ -71,28 +87,14 @@ class JsonTest extends Test
types: [types.Array] types: [types.Array]
, f : (y)=> # insert TEXT mutable , f : (y)=> # insert TEXT mutable
l = y.val().length l = y.val().length
y.val(_.random(0, l-1), @getRamdomObject()) y.val(_.random(0, l-1), new Y.Object(@getRamdomObject()))
types: [types.Array] types: [types.Array]
, f : (y)=> # insert string , f : (y)=> # insert string
l = y.val().length l = y.val().length
y.val(_.random(0, l-1), @getRandomText(), 'immutable') y.val(_.random(0, l-1), @getRandomText())
null null
types : [types.Array] types : [Y.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]
]
describe "JsonFramework", -> describe "JsonFramework", ->
@timeout 500000 @timeout 500000
@ -117,16 +119,16 @@ describe "JsonFramework", ->
@yTest.run() @yTest.run()
u1 = test.users[0] u1 = test.users[0]
u2 = @yTest.users[1] u2 = @yTest.users[1]
ops1 = u1.HB._encode() ops1 = u1._model.HB._encode()
ops2 = u2.HB._encode() ops2 = u2._model.HB._encode()
u1.engine.applyOp ops2, true u1._model.engine.applyOp ops2, true
u2.engine.applyOp ops1, true u2._model.engine.applyOp ops1, true
expect(compare(u1, u2)).to.not.be.undefined expect(compare(u1, u2)).to.not.be.undefined
it "can handle creaton of complex json (1)", -> it "can handle creaton of complex json (1)", ->
@yTest.users[0].val('a', 'q', "mutable") @yTest.users[0].val('a', new Y.Text('q'))
@yTest.users[1].val('a', 't', "mutable") @yTest.users[1].val('a', new Y.Text('t'))
@yTest.compareAll() @yTest.compareAll()
q = @yTest.users[2].val('a') q = @yTest.users[2].val('a')
q.insert(0,'A') q.insert(0,'A')
@ -135,7 +137,7 @@ describe "JsonFramework", ->
it "can handle creaton of complex json (2)", -> it "can handle creaton of complex json (2)", ->
@yTest.getSomeUser().val('x', new Y.Object({'a':'b'})) @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('b', new Y.Object({'a':{}}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'c'})) @yTest.getSomeUser().val('c', new Y.Object({'a':'c'}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'b'})) @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 = @yTest.getSomeUser().val("a").val("a").val("q")
q.insert(0,'A') q.insert(0,'A')
@yTest.compareAll() @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)", -> it "can handle creaton of complex json (3)", ->
@yTest.users[0].val('l', [1,2,3], "mutable") @yTest.users[0].val('l', [1,2,3], "mutable")
@ -156,9 +158,9 @@ describe "JsonFramework", ->
@yTest.compareAll() @yTest.compareAll()
it "handles immutables and primitive data types", -> it "handles immutables and primitive data types", ->
@yTest.getSomeUser().val('string', "text", "immutable") @yTest.getSomeUser().val('string', new Y.Text("text"))
@yTest.getSomeUser().val('number', 4, "immutable") @yTest.getSomeUser().val('number', 4)
@yTest.getSomeUser().val('object', {q:"rr"}, "immutable") @yTest.getSomeUser().val('object', {q:"rr"})
@yTest.getSomeUser().val('null', null) @yTest.getSomeUser().val('null', null)
@yTest.compareAll() @yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text" expect(@yTest.getSomeUser().val('string')).to.equal "text"
@ -167,9 +169,9 @@ describe "JsonFramework", ->
expect(@yTest.getSomeUser().val('null') is null).to.be.ok expect(@yTest.getSomeUser().val('null') is null).to.be.ok
it "handles immutables and primitive data types (2)", -> it "handles immutables and primitive data types (2)", ->
@yTest.users[0].val('string', "text", "immutable") @yTest.users[0].val('string', "text")
@yTest.users[1].val('number', 4, "immutable") @yTest.users[1].val('number', 4)
@yTest.users[2].val('object', {q:"rr"}) @yTest.users[2].val('object', new Y.Object({q:"rr"}))
@yTest.users[0].val('null', null) @yTest.users[0].val('null', null)
@yTest.compareAll() @yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text" expect(@yTest.getSomeUser().val('string')).to.equal "text"
@ -190,7 +192,7 @@ describe "JsonFramework", ->
expect(change.name).to.equal("newStuff") expect(change.name).to.equal("newStuff")
last_task = "observer1" last_task = "observer1"
u.observe observer1 u.observe observer1
u.val("newStuff","someStuff","mutable") u.val("newStuff",new Y.Text("someStuff"))
expect(last_task).to.equal("observer1") expect(last_task).to.equal("observer1")
u.unobserve observer1 u.unobserve observer1
@ -210,7 +212,7 @@ describe "JsonFramework", ->
u.unobserve observer2 u.unobserve observer2
it "Observers work on JSON Types (update type observers, local and foreign)", -> 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() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->
@ -245,7 +247,7 @@ describe "JsonFramework", ->
it "Observers work on JSON Types (delete type observers, local and foreign)", -> 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() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->

View File

@ -7,6 +7,7 @@ _ = require("underscore")
chai.use(sinonChai) chai.use(sinonChai)
Connector = require "../../y-test/lib/y-test.coffee" Connector = require "../../y-test/lib/y-test.coffee"
Y = require "../lib/y"
module.exports = class Test module.exports = class Test
constructor: (@name_suffix = "")-> constructor: (@name_suffix = "")->
@ -76,7 +77,7 @@ module.exports = class Test
pos = _.random 0, (y.val().length-1) pos = _.random 0, (y.val().length-1)
y.insert pos, @getRandomText() y.insert pos, @getRandomText()
null null
types: [types.String] types: [Y.Text]
, ,
f : (y)-> # DELETE TEXT f : (y)-> # DELETE TEXT
if y.val().length > 0 if y.val().length > 0
@ -84,13 +85,24 @@ module.exports = class Test
length = _.random 0, (y.val().length - pos) length = _.random 0, (y.val().length - pos)
ops1 = y.delete pos, length ops1 = y.delete pos, length
undefined undefined
types : [types.String] types : [Y.Text]
] ]
getRandomRoot: (user_num)-> getRandomRoot: (user_num)->
throw new Error "overwrite me!" throw new Error "implement me!"
getContent: (user_num)-> 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)=> generateRandomOp: (user_num)=>
y = @getRandomRoot(user_num) y = @getRandomRoot(user_num)
@ -171,7 +183,7 @@ module.exports = class Test
ops = printOpsInExecutionOrder i+1, i ops = printOpsInExecutionOrder i+1, i
console.log "" console.log ""
expect(@getContent(i)).to.deep.equal(@getContent(i+1)) expect(@compare(@users[i], @users[i+1])).to.not.be.undefined
run: ()-> run: ()->
if @debug if @debug
@ -182,7 +194,7 @@ module.exports = class Test
@doSomething() @doSomething()
@flushAll(false) @flushAll(false)
for u in @users for u in @users
u.HB.emptyGarbage() u._model.HB.emptyGarbage()
for i in [1..Math.floor(@doSomething_amount/2)] for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething() @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) # 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] = @makeNewUser (-1) # this does not want to join with anymody
@users[@users.length-1].HB.renewStateVector @users[0].HB.getOperationCounter() @users[@users.length-1]._model.HB.renewStateVector @users[0]._model.HB.getOperationCounter()
@users[@users.length-1].engine.applyOps @users[0].HB._encode() @users[@users.length-1]._model.engine.applyOps @users[0]._model.HB._encode()
#if @getContent(@users.length-1) isnt @getContent(0) #if @getContent(@users.length-1) isnt @getContent(0)
# console.log "testHBencoding:" # console.log "testHBencoding:"
# console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}" # console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}"
# console.log "Unprocessed ops last: #{@users[@users.length-1].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

View File

@ -20,7 +20,7 @@ class TextTest extends Test
new Y conn new Y conn
initUsers: (u)-> initUsers: (u)->
u.val("TextTest","","mutable") u.val("TextTest",new Y.Text())
getRandomRoot: (user_num)-> getRandomRoot: (user_num)->
@users[user_num].val("TextTest") @users[user_num].val("TextTest")
@ -46,7 +46,7 @@ describe "TextFramework", ->
expect(u.val()).to.equal("bcxyz") expect(u.val()).to.equal("bcxyz")
it "Observers work on shared Text (insert type observers, local and foreign)", -> 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() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->
@ -80,7 +80,7 @@ describe "TextFramework", ->
u.unobserve observer2 u.unobserve observer2
it "Observers work on shared Text (delete type observers, local and foreign)", -> 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() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->
@ -123,17 +123,17 @@ describe "TextFramework", ->
@yTest.run() @yTest.run()
u1 = test.users[0] u1 = test.users[0]
u2 = @yTest.users[1] u2 = @yTest.users[1]
ops1 = u1.HB._encode() ops1 = u1._model.HB._encode()
ops2 = u2.HB._encode() ops2 = u2._model.HB._encode()
u1.engine.applyOp ops2, true u1._model.engine.applyOp ops2, true
u2.engine.applyOp ops1, true u2._model.engine.applyOp ops1, true
compare = (o1, o2)-> 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" throw new Error "different types"
else if o1.type is "Object" else if o1._name is "Object"
for name, val of o1.val() for name, val of o1.val()
compare(val, o2.val(name)) compare(val, o2.val(name))
else if o1.type? else if o1._name?
compare(o1.val(), o2.val()) compare(o1.val(), o2.val())
else if o1 isnt o2 else if o1 isnt o2
throw new Error "different values" throw new Error "different values"

File diff suppressed because one or more lines are too long

4
y.js

File diff suppressed because one or more lines are too long