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 }; };