664 lines
19 KiB
JavaScript
664 lines
19 KiB
JavaScript
var slice = [].slice,
|
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
hasProp = {}.hasOwnProperty;
|
|
|
|
module.exports = function() {
|
|
var execution_listener, ops;
|
|
ops = {};
|
|
execution_listener = [];
|
|
ops.Operation = (function() {
|
|
function Operation(custom_type, uid, content, content_operations) {
|
|
var name, op;
|
|
if (custom_type != null) {
|
|
this.custom_type = custom_type;
|
|
}
|
|
this.is_deleted = false;
|
|
this.garbage_collected = false;
|
|
this.event_listeners = [];
|
|
if (uid != null) {
|
|
this.uid = uid;
|
|
}
|
|
if (content === void 0) {
|
|
|
|
} else if ((content != null) && (content.creator != null)) {
|
|
this.saveOperation('content', content);
|
|
} else {
|
|
this.content = content;
|
|
}
|
|
if (content_operations != null) {
|
|
this.content_operations = {};
|
|
for (name in content_operations) {
|
|
op = content_operations[name];
|
|
this.saveOperation(name, op, 'content_operations');
|
|
}
|
|
}
|
|
}
|
|
|
|
Operation.prototype.type = "Operation";
|
|
|
|
Operation.prototype.getContent = function(name) {
|
|
var content, n, ref, ref1, v;
|
|
if (this.content != null) {
|
|
if (this.content.getCustomType != null) {
|
|
return this.content.getCustomType();
|
|
} else if (this.content.constructor === Object) {
|
|
if (name != null) {
|
|
if (this.content[name] != null) {
|
|
return this.content[name];
|
|
} else {
|
|
return this.content_operations[name].getCustomType();
|
|
}
|
|
} else {
|
|
content = {};
|
|
ref = this.content;
|
|
for (n in ref) {
|
|
v = ref[n];
|
|
content[n] = v;
|
|
}
|
|
if (this.content_operations != null) {
|
|
ref1 = this.content_operations;
|
|
for (n in ref1) {
|
|
v = ref1[n];
|
|
v = v.getCustomType();
|
|
content[n] = v;
|
|
}
|
|
}
|
|
return content;
|
|
}
|
|
} else {
|
|
return this.content;
|
|
}
|
|
} else {
|
|
return this.content;
|
|
}
|
|
};
|
|
|
|
Operation.prototype.retrieveSub = function() {
|
|
throw new Error("sub properties are not enable on this operation type!");
|
|
};
|
|
|
|
Operation.prototype.observe = function(f) {
|
|
return this.event_listeners.push(f);
|
|
};
|
|
|
|
Operation.prototype.unobserve = function(f) {
|
|
return this.event_listeners = this.event_listeners.filter(function(g) {
|
|
return f !== g;
|
|
});
|
|
};
|
|
|
|
Operation.prototype.deleteAllObservers = function() {
|
|
return this.event_listeners = [];
|
|
};
|
|
|
|
Operation.prototype["delete"] = function() {
|
|
(new ops.Delete(void 0, this)).execute();
|
|
return null;
|
|
};
|
|
|
|
Operation.prototype.callEvent = function() {
|
|
var callon;
|
|
if (this.custom_type != null) {
|
|
callon = this.getCustomType();
|
|
} else {
|
|
callon = this;
|
|
}
|
|
return this.forwardEvent.apply(this, [callon].concat(slice.call(arguments)));
|
|
};
|
|
|
|
Operation.prototype.forwardEvent = function() {
|
|
var args, f, j, len, op, ref, results;
|
|
op = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
|
ref = this.event_listeners;
|
|
results = [];
|
|
for (j = 0, len = ref.length; j < len; j++) {
|
|
f = ref[j];
|
|
results.push(f.call.apply(f, [op].concat(slice.call(args))));
|
|
}
|
|
return results;
|
|
};
|
|
|
|
Operation.prototype.isDeleted = function() {
|
|
return this.is_deleted;
|
|
};
|
|
|
|
Operation.prototype.applyDelete = function(garbagecollect) {
|
|
if (garbagecollect == null) {
|
|
garbagecollect = true;
|
|
}
|
|
if (!this.garbage_collected) {
|
|
this.is_deleted = true;
|
|
if (garbagecollect) {
|
|
this.garbage_collected = true;
|
|
return this.HB.addToGarbageCollector(this);
|
|
}
|
|
}
|
|
};
|
|
|
|
Operation.prototype.cleanup = function() {
|
|
this.HB.removeOperation(this);
|
|
return this.deleteAllObservers();
|
|
};
|
|
|
|
Operation.prototype.setParent = function(parent1) {
|
|
this.parent = parent1;
|
|
};
|
|
|
|
Operation.prototype.getParent = function() {
|
|
return this.parent;
|
|
};
|
|
|
|
Operation.prototype.getUid = function() {
|
|
var map_uid;
|
|
if (this.uid.noOperation == null) {
|
|
return this.uid;
|
|
} else {
|
|
if (this.uid.alt != null) {
|
|
map_uid = this.uid.alt.cloneUid();
|
|
map_uid.sub = this.uid.sub;
|
|
return map_uid;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
Operation.prototype.cloneUid = function() {
|
|
var n, ref, uid, v;
|
|
uid = {};
|
|
ref = this.getUid();
|
|
for (n in ref) {
|
|
v = ref[n];
|
|
uid[n] = v;
|
|
}
|
|
return uid;
|
|
};
|
|
|
|
Operation.prototype.execute = function() {
|
|
var j, l, len;
|
|
if (this.validateSavedOperations()) {
|
|
this.is_executed = true;
|
|
if (this.uid == null) {
|
|
this.uid = this.HB.getNextOperationIdentifier();
|
|
}
|
|
if (this.uid.noOperation == null) {
|
|
this.HB.addOperation(this);
|
|
for (j = 0, len = execution_listener.length; j < len; j++) {
|
|
l = execution_listener[j];
|
|
l(this._encode());
|
|
}
|
|
}
|
|
return this;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
Operation.prototype.saveOperation = function(name, op, base) {
|
|
var base1, dest, j, last_path, len, path, paths;
|
|
if (base == null) {
|
|
base = "this";
|
|
}
|
|
if ((op != null) && (op._getModel != null)) {
|
|
op = op._getModel(this.custom_types, this.operations);
|
|
}
|
|
if (op == null) {
|
|
|
|
} else if ((op.execute != null) || !((op.op_number != null) && (op.creator != null))) {
|
|
if (base === "this") {
|
|
return this[name] = op;
|
|
} else {
|
|
dest = this[base];
|
|
paths = name.split("/");
|
|
last_path = paths.pop();
|
|
for (j = 0, len = paths.length; j < len; j++) {
|
|
path = paths[j];
|
|
dest = dest[path];
|
|
}
|
|
return dest[last_path] = op;
|
|
}
|
|
} else {
|
|
if (this.unchecked == null) {
|
|
this.unchecked = {};
|
|
}
|
|
if ((base1 = this.unchecked)[base] == null) {
|
|
base1[base] = {};
|
|
}
|
|
return this.unchecked[base][name] = op;
|
|
}
|
|
};
|
|
|
|
Operation.prototype.validateSavedOperations = function() {
|
|
var base, base_name, dest, j, last_path, len, name, op, op_uid, path, paths, ref, success, uninstantiated;
|
|
uninstantiated = {};
|
|
success = true;
|
|
ref = this.unchecked;
|
|
for (base_name in ref) {
|
|
base = ref[base_name];
|
|
for (name in base) {
|
|
op_uid = base[name];
|
|
op = this.HB.getOperation(op_uid);
|
|
if (op) {
|
|
if (base_name === "this") {
|
|
this[name] = op;
|
|
} else {
|
|
dest = this[base_name];
|
|
paths = name.split("/");
|
|
last_path = paths.pop();
|
|
for (j = 0, len = paths.length; j < len; j++) {
|
|
path = paths[j];
|
|
dest = dest[path];
|
|
}
|
|
dest[last_path] = op;
|
|
}
|
|
} else {
|
|
if (uninstantiated[base_name] == null) {
|
|
uninstantiated[base_name] = {};
|
|
}
|
|
uninstantiated[base_name][name] = op_uid;
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
if (!success) {
|
|
this.unchecked = uninstantiated;
|
|
return false;
|
|
} else {
|
|
delete this.unchecked;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
Operation.prototype.getCustomType = function() {
|
|
var Type, j, len, ref, t;
|
|
if (this.custom_type == null) {
|
|
return this;
|
|
} else {
|
|
if (this.custom_type.constructor === String) {
|
|
Type = this.custom_types;
|
|
ref = this.custom_type.split(".");
|
|
for (j = 0, len = ref.length; j < len; j++) {
|
|
t = ref[j];
|
|
Type = Type[t];
|
|
}
|
|
this.custom_type = new Type();
|
|
this.custom_type._setModel(this);
|
|
}
|
|
return this.custom_type;
|
|
}
|
|
};
|
|
|
|
Operation.prototype._encode = function(json) {
|
|
var n, o, operations, ref, ref1;
|
|
if (json == null) {
|
|
json = {};
|
|
}
|
|
json.type = this.type;
|
|
json.uid = this.getUid();
|
|
if (this.custom_type != null) {
|
|
if (this.custom_type.constructor === String) {
|
|
json.custom_type = this.custom_type;
|
|
} else {
|
|
json.custom_type = this.custom_type._name;
|
|
}
|
|
}
|
|
if (((ref = this.content) != null ? ref.getUid : void 0) != null) {
|
|
json.content = this.content.getUid();
|
|
} else {
|
|
json.content = this.content;
|
|
}
|
|
if (this.content_operations != null) {
|
|
operations = {};
|
|
ref1 = this.content_operations;
|
|
for (n in ref1) {
|
|
o = ref1[n];
|
|
if (o._getModel != null) {
|
|
o = o._getModel(this.custom_types, this.operations);
|
|
}
|
|
operations[n] = o.getUid();
|
|
}
|
|
json.content_operations = operations;
|
|
}
|
|
return json;
|
|
};
|
|
|
|
return Operation;
|
|
|
|
})();
|
|
ops.Delete = (function(superClass) {
|
|
extend(Delete, superClass);
|
|
|
|
function Delete(custom_type, uid, deletes) {
|
|
this.saveOperation('deletes', deletes);
|
|
Delete.__super__.constructor.call(this, custom_type, uid);
|
|
}
|
|
|
|
Delete.prototype.type = "Delete";
|
|
|
|
Delete.prototype._encode = function() {
|
|
return {
|
|
'type': "Delete",
|
|
'uid': this.getUid(),
|
|
'deletes': this.deletes.getUid()
|
|
};
|
|
};
|
|
|
|
Delete.prototype.execute = function() {
|
|
var res;
|
|
if (this.validateSavedOperations()) {
|
|
res = Delete.__super__.execute.apply(this, arguments);
|
|
if (res) {
|
|
this.deletes.applyDelete(this);
|
|
}
|
|
return res;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
return Delete;
|
|
|
|
})(ops.Operation);
|
|
ops.Delete.parse = function(o) {
|
|
var deletes_uid, uid;
|
|
uid = o['uid'], deletes_uid = o['deletes'];
|
|
return new this(null, uid, deletes_uid);
|
|
};
|
|
ops.Insert = (function(superClass) {
|
|
extend(Insert, superClass);
|
|
|
|
function Insert(custom_type, content, content_operations, parent, uid, prev_cl, next_cl, origin) {
|
|
this.saveOperation('parent', parent);
|
|
this.saveOperation('prev_cl', prev_cl);
|
|
this.saveOperation('next_cl', next_cl);
|
|
if (origin != null) {
|
|
this.saveOperation('origin', origin);
|
|
} else {
|
|
this.saveOperation('origin', prev_cl);
|
|
}
|
|
Insert.__super__.constructor.call(this, custom_type, uid, content, content_operations);
|
|
}
|
|
|
|
Insert.prototype.type = "Insert";
|
|
|
|
Insert.prototype.val = function() {
|
|
return this.getContent();
|
|
};
|
|
|
|
Insert.prototype.getNext = function(i) {
|
|
var n;
|
|
if (i == null) {
|
|
i = 1;
|
|
}
|
|
n = this;
|
|
while (i > 0 && n.is_deleted && (n.next_cl != null)) {
|
|
n = n.next_cl;
|
|
if (!n.is_deleted) {
|
|
i--;
|
|
}
|
|
}
|
|
return n;
|
|
};
|
|
|
|
Insert.prototype.getPrev = function(i) {
|
|
var n;
|
|
if (i == null) {
|
|
i = 1;
|
|
}
|
|
n = this;
|
|
while (i > 0 && n.is_deleted && (n.prev_cl != null)) {
|
|
n = n.prev_cl;
|
|
if (!n.is_deleted) {
|
|
i--;
|
|
}
|
|
}
|
|
return n;
|
|
};
|
|
|
|
Insert.prototype.applyDelete = function(o) {
|
|
var callLater, garbagecollect, ref;
|
|
if (this.deleted_by == null) {
|
|
this.deleted_by = [];
|
|
}
|
|
callLater = false;
|
|
if ((this.parent != null) && !this.is_deleted && (o != null)) {
|
|
callLater = true;
|
|
}
|
|
if (o != null) {
|
|
this.deleted_by.push(o);
|
|
}
|
|
garbagecollect = false;
|
|
if (this.next_cl.isDeleted()) {
|
|
garbagecollect = true;
|
|
}
|
|
Insert.__super__.applyDelete.call(this, garbagecollect);
|
|
if (callLater) {
|
|
this.parent.callOperationSpecificDeleteEvents(this, o);
|
|
}
|
|
if ((ref = this.prev_cl) != null ? ref.isDeleted() : void 0) {
|
|
return this.prev_cl.applyDelete();
|
|
}
|
|
};
|
|
|
|
Insert.prototype.cleanup = function() {
|
|
var d, j, len, o, ref;
|
|
if (this.next_cl.isDeleted()) {
|
|
ref = this.deleted_by;
|
|
for (j = 0, len = ref.length; j < len; j++) {
|
|
d = ref[j];
|
|
d.cleanup();
|
|
}
|
|
o = this.next_cl;
|
|
while (o.type !== "Delimiter") {
|
|
if (o.origin === this) {
|
|
o.origin = this.prev_cl;
|
|
}
|
|
o = o.next_cl;
|
|
}
|
|
this.prev_cl.next_cl = this.next_cl;
|
|
this.next_cl.prev_cl = this.prev_cl;
|
|
if (this.content instanceof ops.Operation) {
|
|
this.content.referenced_by--;
|
|
if (this.content.referenced_by <= 0 && !this.content.is_deleted) {
|
|
this.content.applyDelete();
|
|
}
|
|
}
|
|
delete this.content;
|
|
return Insert.__super__.cleanup.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
Insert.prototype.getDistanceToOrigin = function() {
|
|
var d, o;
|
|
d = 0;
|
|
o = this.prev_cl;
|
|
while (true) {
|
|
if (this.origin === o) {
|
|
break;
|
|
}
|
|
d++;
|
|
o = o.prev_cl;
|
|
}
|
|
return d;
|
|
};
|
|
|
|
Insert.prototype.execute = function() {
|
|
var base1, distance_to_origin, i, o;
|
|
if (!this.validateSavedOperations()) {
|
|
return false;
|
|
} else {
|
|
if (this.content instanceof ops.Operation) {
|
|
this.content.insert_parent = this;
|
|
if ((base1 = this.content).referenced_by == null) {
|
|
base1.referenced_by = 0;
|
|
}
|
|
this.content.referenced_by++;
|
|
}
|
|
if (this.parent != null) {
|
|
if (this.prev_cl == null) {
|
|
this.prev_cl = this.parent.beginning;
|
|
}
|
|
if (this.origin == null) {
|
|
this.origin = this.prev_cl;
|
|
} else if (this.origin === "Delimiter") {
|
|
this.origin = this.parent.beginning;
|
|
}
|
|
if (this.next_cl == null) {
|
|
this.next_cl = this.parent.end;
|
|
}
|
|
}
|
|
if (this.prev_cl != null) {
|
|
distance_to_origin = this.getDistanceToOrigin();
|
|
o = this.prev_cl.next_cl;
|
|
i = distance_to_origin;
|
|
while (true) {
|
|
if (o !== this.next_cl) {
|
|
if (o.getDistanceToOrigin() === i) {
|
|
if (o.uid.creator < this.uid.creator) {
|
|
this.prev_cl = o;
|
|
distance_to_origin = i + 1;
|
|
} else {
|
|
|
|
}
|
|
} else if (o.getDistanceToOrigin() < i) {
|
|
if (i - distance_to_origin <= o.getDistanceToOrigin()) {
|
|
this.prev_cl = o;
|
|
distance_to_origin = i + 1;
|
|
} else {
|
|
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
i++;
|
|
o = o.next_cl;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
this.next_cl = this.prev_cl.next_cl;
|
|
this.prev_cl.next_cl = this;
|
|
this.next_cl.prev_cl = this;
|
|
}
|
|
this.setParent(this.prev_cl.getParent());
|
|
Insert.__super__.execute.apply(this, arguments);
|
|
this.parent.callOperationSpecificInsertEvents(this);
|
|
return this;
|
|
}
|
|
};
|
|
|
|
Insert.prototype.getPosition = function() {
|
|
var position, prev;
|
|
position = 0;
|
|
prev = this.prev_cl;
|
|
while (true) {
|
|
if (prev instanceof ops.Delimiter) {
|
|
break;
|
|
}
|
|
if (!prev.isDeleted()) {
|
|
position++;
|
|
}
|
|
prev = prev.prev_cl;
|
|
}
|
|
return position;
|
|
};
|
|
|
|
Insert.prototype._encode = function(json) {
|
|
if (json == null) {
|
|
json = {};
|
|
}
|
|
json.prev = this.prev_cl.getUid();
|
|
json.next = this.next_cl.getUid();
|
|
if (this.origin.type === "Delimiter") {
|
|
json.origin = "Delimiter";
|
|
} else if (this.origin !== this.prev_cl) {
|
|
json.origin = this.origin.getUid();
|
|
}
|
|
json.parent = this.parent.getUid();
|
|
return Insert.__super__._encode.call(this, json);
|
|
};
|
|
|
|
return Insert;
|
|
|
|
})(ops.Operation);
|
|
ops.Insert.parse = function(json) {
|
|
var content, content_operations, next, origin, parent, prev, uid;
|
|
content = json['content'], content_operations = json['content_operations'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], parent = json['parent'];
|
|
return new this(null, content, content_operations, parent, uid, prev, next, origin);
|
|
};
|
|
ops.Delimiter = (function(superClass) {
|
|
extend(Delimiter, superClass);
|
|
|
|
function Delimiter(prev_cl, next_cl, origin) {
|
|
this.saveOperation('prev_cl', prev_cl);
|
|
this.saveOperation('next_cl', next_cl);
|
|
this.saveOperation('origin', prev_cl);
|
|
Delimiter.__super__.constructor.call(this, null, {
|
|
noOperation: true
|
|
});
|
|
}
|
|
|
|
Delimiter.prototype.type = "Delimiter";
|
|
|
|
Delimiter.prototype.applyDelete = function() {
|
|
var o;
|
|
Delimiter.__super__.applyDelete.call(this);
|
|
o = this.prev_cl;
|
|
while (o != null) {
|
|
o.applyDelete();
|
|
o = o.prev_cl;
|
|
}
|
|
return void 0;
|
|
};
|
|
|
|
Delimiter.prototype.cleanup = function() {
|
|
return Delimiter.__super__.cleanup.call(this);
|
|
};
|
|
|
|
Delimiter.prototype.execute = function() {
|
|
var ref, ref1;
|
|
if (((ref = this.unchecked) != null ? ref['next_cl'] : void 0) != null) {
|
|
return Delimiter.__super__.execute.apply(this, arguments);
|
|
} else if ((ref1 = this.unchecked) != null ? ref1['prev_cl'] : void 0) {
|
|
if (this.validateSavedOperations()) {
|
|
if (this.prev_cl.next_cl != null) {
|
|
throw new Error("Probably duplicated operations");
|
|
}
|
|
this.prev_cl.next_cl = this;
|
|
return Delimiter.__super__.execute.apply(this, arguments);
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if ((this.prev_cl != null) && (this.prev_cl.next_cl == null)) {
|
|
delete this.prev_cl.unchecked.next_cl;
|
|
this.prev_cl.next_cl = this;
|
|
return Delimiter.__super__.execute.apply(this, arguments);
|
|
} else if ((this.prev_cl != null) || (this.next_cl != null) || true) {
|
|
return Delimiter.__super__.execute.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
Delimiter.prototype._encode = function() {
|
|
var ref, ref1;
|
|
return {
|
|
'type': this.type,
|
|
'uid': this.getUid(),
|
|
'prev': (ref = this.prev_cl) != null ? ref.getUid() : void 0,
|
|
'next': (ref1 = this.next_cl) != null ? ref1.getUid() : void 0
|
|
};
|
|
};
|
|
|
|
return Delimiter;
|
|
|
|
})(ops.Operation);
|
|
ops.Delimiter.parse = function(json) {
|
|
var next, prev, uid;
|
|
uid = json['uid'], prev = json['prev'], next = json['next'];
|
|
return new this(uid, prev, next);
|
|
};
|
|
return {
|
|
'operations': ops,
|
|
'execution_listener': execution_listener
|
|
};
|
|
};
|