UNPKG

superpowers-game-ftext-plugin

Version:

Generic text asset for the Superpowers Game system of Superpowers, the extensible HTML5 2D+3D game engine.

1,003 lines (943 loc) 37.6 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (process){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s if (allowAboveRoot) { for (; up--; up) { parts.unshift('..'); } } return parts; } // Split a filename into [root, dir, basename, ext], unix version // 'root' is just a slash, or nothing. var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; var splitPath = function(filename) { return splitPathRe.exec(filename).slice(1); }; // path.resolve([from ...], to) // posix version exports.resolve = function() { var resolvedPath = '', resolvedAbsolute = false; for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = (i >= 0) ? arguments[i] : process.cwd(); // Skip empty and invalid entries if (typeof path !== 'string') { throw new TypeError('Arguments to path.resolve must be strings'); } else if (!path) { continue; } resolvedPath = path + '/' + resolvedPath; resolvedAbsolute = path.charAt(0) === '/'; } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) // Normalize the path resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { return !!p; }), !resolvedAbsolute).join('/'); return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; // path.normalize(path) // posix version exports.normalize = function(path) { var isAbsolute = exports.isAbsolute(path), trailingSlash = substr(path, -1) === '/'; // Normalize the path path = normalizeArray(filter(path.split('/'), function(p) { return !!p; }), !isAbsolute).join('/'); if (!path && !isAbsolute) { path = '.'; } if (path && trailingSlash) { path += '/'; } return (isAbsolute ? '/' : '') + path; }; // posix version exports.isAbsolute = function(path) { return path.charAt(0) === '/'; }; // posix version exports.join = function() { var paths = Array.prototype.slice.call(arguments, 0); return exports.normalize(filter(paths, function(p, index) { if (typeof p !== 'string') { throw new TypeError('Arguments to path.join must be strings'); } return p; }).join('/')); }; // path.relative(from, to) // posix version exports.relative = function(from, to) { from = exports.resolve(from).substr(1); to = exports.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== '') break; } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== '') break; } if (start > end) return []; return arr.slice(start, end - start + 1); } var fromParts = trim(from.split('/')); var toParts = trim(to.split('/')); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push('..'); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join('/'); }; exports.sep = '/'; exports.delimiter = ':'; exports.dirname = function(path) { var result = splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { // No dirname whatsoever return '.'; } if (dir) { // It has a dirname, strip trailing slash dir = dir.substr(0, dir.length - 1); } return root + dir; }; exports.basename = function(path, ext) { var f = splitPath(path)[2]; // TODO: make this comparison case-insensitive on windows? if (ext && f.substr(-1 * ext.length) === ext) { f = f.substr(0, f.length - ext.length); } return f; }; exports.extname = function(path) { return splitPath(path)[3]; }; function filter (xs, f) { if (xs.filter) return xs.filter(f); var res = []; for (var i = 0; i < xs.length; i++) { if (f(xs[i], i, xs)) res.push(xs[i]); } return res; } // String.prototype.substr - negative index don't work in IE8 var substr = 'ab'.substr(-1) === 'b' ? function (str, start, len) { return str.substr(start, len) } : function (str, start, len) { if (start < 0) start = str.length + start; return str.substr(start, len); } ; }).call(this,require('_process')) },{"_process":2}],2:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = setTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; clearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { setTimeout(drainQueue, 0); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],3:[function(require,module,exports){ var fTextSettingsResource = require("./fTextSettingsResource"); var fTextAsset = require("./fTextAsset"); SupCore.system.data.registerResource("fTextSettings", fTextSettingsResource.default); SupCore.system.data.registerAssetClass("fText", fTextAsset.default); },{"./fTextAsset":4,"./fTextSettingsResource":5}],4:[function(require,module,exports){ var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var OT = require("operational-transform"); var path = require("path"); var fTextAsset = (function (_super) { __extends(fTextAsset, _super); // called from the editor onAssetReceived() as well as on server startup function fTextAsset(id, pub, serverData) { _super.call(this, id, pub, fTextAsset.schema, serverData); } // called on asset creation // options contain the asset's name fTextAsset.prototype.init = function (options, callback) { var defaultContent = ""; this.pub = { text: defaultContent, draft: defaultContent, revisionId: 0, }; _super.prototype.init.call(this, options, callback); }; fTextAsset.prototype.setup = function () { this.document = new OT.Document(this.pub.draft, this.pub.revisionId); this.hasDraft = this.pub.text !== this.pub.draft; }; fTextAsset.prototype.restore = function () { if (this.hasDraft) this.emit("setBadge", "draft", "info"); }; fTextAsset.prototype.destroy = function (callback) { callback(); }; // called on server startup fTextAsset.prototype.load = function (assetPath) { // NOTE: We must not set this.pub with temporary values here, otherwise // the asset will be considered loaded by Dictionary.acquire // and the acquire callback will be called immediately var _this = this; fs.readFile(path.join(assetPath, "ftext.txt"), { encoding: "utf8" }, function (err, text) { fs.readFile(path.join(assetPath, "draft.txt"), { encoding: "utf8" }, function (err, draft) { _this.pub = { revisionId: 0, text: text, draft: (draft != null) ? draft : text }; _this.setup(); _this.emit("load"); }); }); }; // called when it is time to write the asset on disk, not when the user save the asset from the editor fTextAsset.prototype.save = function (assetPath, callback) { var _this = this; fs.writeFile(path.join(assetPath, "ftext.txt"), this.pub.text, { encoding: "utf8" }, function (err) { if (err != null) { callback(err); return; } if (_this.hasDraft) { fs.writeFile(path.join(assetPath, "draft.txt"), _this.pub.draft, { encoding: "utf8" }, callback); } else { // delete the draft.txt file if there is no draft to save and the file exists fs.unlink(path.join(assetPath, "draft.txt"), function (err) { if (err != null && err.code !== "ENOENT") { callback(err); return; } callback(null); }); } }); }; fTextAsset.prototype.server_editText = function (client, operationData, revisionIndex, callback) { if (operationData.userId !== client.id) { callback("Invalid client id"); return; } var operation = new OT.TextOperation(); if (!operation.deserialize(operationData)) { callback("Invalid operation data"); return; } try { operation = this.document.apply(operation, revisionIndex); } catch (err) { callback("Operation can't be applied"); return; } this.pub.draft = this.document.text; this.pub.revisionId++; callback(null, operation.serialize(), this.document.getRevisionId() - 1); if (!this.hasDraft) { this.hasDraft = true; this.emit("setBadge", "draft", "info"); } this.emit("change"); }; fTextAsset.prototype.client_editText = function (operationData, revisionIndex) { var operation = new OT.TextOperation(); operation.deserialize(operationData); this.document.apply(operation, revisionIndex); this.pub.draft = this.document.text; this.pub.revisionId++; }; fTextAsset.prototype.server_saveText = function (client, callback) { this.pub.text = this.pub.draft; callback(null); if (this.hasDraft) { this.hasDraft = false; this.emit("clearBadge", "draft"); } this.emit("change"); }; fTextAsset.prototype.client_saveText = function () { this.pub.text = this.pub.draft; }; fTextAsset.schema = { text: { type: "string" }, draft: { type: "string" }, revisionId: { type: "integer" }, }; return fTextAsset; })(SupCore.Data.Base.Asset); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = fTextAsset; },{"operational-transform":9,"path":1}],5:[function(require,module,exports){ var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var fTextSettingsResource = (function (_super) { __extends(fTextSettingsResource, _super); function fTextSettingsResource(id, pub, serverData) { _super.call(this, id, pub, fTextSettingsResource.schema, serverData); } fTextSettingsResource.prototype.init = function (callback) { var pub = {}; for (var name_1 in fTextSettingsResource.defaultValues) { pub[name_1] = fTextSettingsResource.defaultValues[name_1]; } this.pub = pub; _super.prototype.init.call(this, callback); }; fTextSettingsResource.schema = { theme: { type: "string", mutable: true }, customTheme: { type: "string", mutable: true }, tabSize: { type: "number", min: 1, max: 8, mutable: true }, indentWithTabs: { type: "boolean", mutable: true }, keyMap: { type: "enum", items: ["sublime", "vim", "emacs"], mutable: true }, styleActiveLine: { type: "boolean", mutable: true }, showTrailingSpace: { type: "boolean", mutable: true }, autoCloseBrackets: { type: "boolean", mutable: true }, matchTags: { type: "boolean", mutable: true }, highlightSelectionMatches: { type: "boolean", mutable: true }, lint_json: { type: "boolean", mutable: true }, lint_cson: { type: "boolean", mutable: true }, lint_javascript: { type: "boolean", mutable: true }, lint_jade: { type: "boolean", mutable: true }, lint_stylus: { type: "boolean", mutable: true }, lint_css: { type: "boolean", mutable: true }, lint_yaml: { type: "boolean", mutable: true }, }; fTextSettingsResource.defaultValues = { theme: "default", customTheme: "", tabSize: 2, indentWithTabs: true, keyMap: "sublime", styleActiveLine: true, autoCloseBrackets: true, showTrailingSpace: true, matchTags: true, highlightSelectionMatches: true, lint_json: true, lint_cson: true, lint_javascript: true, lint_jade: true, lint_stylus: true, lint_css: true, lint_yaml: true, }; // note 07/09/15 for some reason, not having a coma after the last entry would cause the defaultValues not to be read in the settings editor... return fTextSettingsResource; })(SupCore.Data.Base.Resource); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = fTextSettingsResource; },{}],6:[function(require,module,exports){ var OT = require("./index"); var Document = (function () { function Document(text, revisionId) { if (text === void 0) { text = ""; } if (revisionId === void 0) { revisionId = 0; } this.operations = []; this.text = text; this._refRevisionId = revisionId; } Document.prototype.apply = function (newOperation, revision) { revision -= this._refRevisionId; // Should't happen if (revision > this.operations.length) throw new Error("The operation base revision is greater than the document revision"); if (revision < this.operations.length) { // Conflict! var missedOperations = new OT.TextOperation(this.operations[revision].userId); missedOperations.targetLength = this.operations[revision].baseLength; for (var index = revision; index < this.operations.length; index++) missedOperations = missedOperations.compose(this.operations[index]); newOperation = missedOperations.transform(newOperation)[1]; } this.text = newOperation.apply(this.text); this.operations.push(newOperation.clone()); return newOperation; }; Document.prototype.getRevisionId = function () { return this.operations.length + this._refRevisionId; }; return Document; })(); module.exports = Document; },{"./index":9}],7:[function(require,module,exports){ var TextOp = (function () { function TextOp(type, attributes) { this.type = type; this.attributes = attributes; } return TextOp; })(); module.exports = TextOp; },{}],8:[function(require,module,exports){ var OT = require("./index"); var TextOperation = (function () { function TextOperation(userId) { this.ops = []; // An operation's baseLength is the length of every string the operation // can be applied to. this.baseLength = 0; // The targetLength is the length of every string that results from applying // the operation on a valid input string. this.targetLength = 0; this.userId = userId; } TextOperation.prototype.serialize = function () { var ops = []; for (var _i = 0, _a = this.ops; _i < _a.length; _i++) { var op = _a[_i]; ops.push({ type: op.type, attributes: op.attributes }); } return { ops: ops, userId: this.userId }; }; TextOperation.prototype.deserialize = function (data) { if (data == null) return false; this.userId = data.userId; for (var _i = 0, _a = data.ops; _i < _a.length; _i++) { var op = _a[_i]; switch (op.type) { case "retain": this.retain(op.attributes.amount); break; case "insert": this.insert(op.attributes.text); break; case "delete": this.delete(op.attributes.text); break; default: return false; } } return true; }; TextOperation.prototype.retain = function (amount) { if (typeof (amount) !== "number" || amount <= 0) return; this.baseLength += amount; this.targetLength += amount; var prevOp = this.ops[this.ops.length - 1]; if (prevOp != null && prevOp.type === "retain") { prevOp.attributes.amount += amount; } else { this.ops.push(new OT.TextOp("retain", { amount: amount })); } }; TextOperation.prototype.insert = function (text) { if (typeof (text) !== "string" || text === "") return; this.targetLength += text.length; var prevOp = this.ops[this.ops.length - 1]; if (prevOp != null && prevOp.type === "insert") { prevOp.attributes.text += text; } else { this.ops.push(new OT.TextOp("insert", { text: text })); } }; TextOperation.prototype.delete = function (text) { if (typeof (text) !== "string" || text === "") return; this.baseLength += text.length; var prevOp = this.ops[this.ops.length - 1]; if (prevOp != null && prevOp.type === "delete") { prevOp.attributes.text += text; } else { this.ops.push(new OT.TextOp("delete", { text: text })); } }; TextOperation.prototype.apply = function (text) { if (text.length !== this.baseLength) throw new Error("The operation's base length must be equal to the string's length."); var index = 0; for (var _i = 0, _a = this.ops; _i < _a.length; _i++) { var op = _a[_i]; switch (op.type) { case "retain": index += op.attributes.amount; break; case "insert": text = text.substring(0, index) + op.attributes.text + text.substring(index, text.length); index += op.attributes.text.length; break; case "delete": text = text.substring(0, index) + text.substring(index + op.attributes.text.length, text.length); break; } } return text; }; TextOperation.prototype.invert = function () { var invertedOperation = new TextOperation(this.userId); for (var _i = 0, _a = this.ops; _i < _a.length; _i++) { var op = _a[_i]; switch (op.type) { case "retain": invertedOperation.retain(op.attributes.amount); break; case "insert": invertedOperation.delete(op.attributes.text); break; case "delete": invertedOperation.insert(op.attributes.text); break; } } return invertedOperation; }; TextOperation.prototype.clone = function () { var operation = new TextOperation(this.userId); for (var _i = 0, _a = this.ops; _i < _a.length; _i++) { var op = _a[_i]; switch (op.type) { case "retain": operation.retain(op.attributes.amount); break; case "insert": operation.insert(op.attributes.text); break; case "delete": operation.delete(op.attributes.text); break; } } return operation; }; TextOperation.prototype.equal = function (otherOperation) { // if (otherOperation.insertedLength !== this.insertedLength) return false; if (otherOperation.ops.length !== this.ops.length) return false; for (var opIndex = 0; opIndex < this.ops.length; opIndex++) { var op = this.ops[opIndex]; var otherOp = otherOperation.ops[opIndex]; if (otherOp.type !== op.type) return false; for (var key in op.attributes) { var attribute = op.attributes[key]; if (attribute !== otherOp.attributes[key]) return false; } } return true; }; /* Largely inspired from Firepad Compose merges two consecutive operations into one operation, that preserves the changes of both. Or, in other words, for each input string S and a pair of consecutive operations A and B, apply(apply(S, A), B) = apply(S, compose(A, B)) must hold. */ TextOperation.prototype.compose = function (operation2) { if (this.targetLength !== operation2.baseLength) throw new Error("The base length of the second operation has to be the target length of the first operation"); // the combined operation var composedOperation = new TextOperation(this.userId); var ops1 = this.clone().ops; var ops2 = operation2.clone().ops; var i1 = 0; // current index into ops1 respectively ops2 var i2 = 0; var op1 = ops1[i1++]; // current ops var op2 = ops2[i2++]; while (true) { // Dispatch on the type of op1 and op2 // end condition: both ops1 and ops2 have been processed if (op1 == null && op2 == null) break; if (op2 == null) { switch (op1.type) { case "retain": composedOperation.retain(op1.attributes.amount); break; case "insert": composedOperation.insert(op1.attributes.text); break; case "delete": composedOperation.delete(op1.attributes.text); break; } op1 = ops1[i1++]; continue; } if (op1 == null) { switch (op2.type) { case "retain": composedOperation.retain(op2.attributes.amount); break; case "insert": composedOperation.insert(op2.attributes.text); break; case "delete": composedOperation.delete(op2.attributes.text); break; } op2 = ops2[i2++]; continue; } if (op1 != null && op1.type === "delete") { composedOperation.delete(op1.attributes.text); op1 = ops1[i1++]; continue; } if (op2 != null && op2.type === "insert") { composedOperation.insert(op2.attributes.text); op2 = ops2[i2++]; continue; } if (op1 == null) throw new Error("Cannot transform operations: first operation is too short."); if (op2 == null) throw new Error("Cannot transform operations: first operation is too long."); if (op1.type === "retain" && op2.type === "retain") { if (op1.attributes.amount === op2.attributes.amount) { composedOperation.retain(op1.attributes.amount); op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.amount > op2.attributes.amount) { composedOperation.retain(op2.attributes.amount); op1.attributes.amount -= op2.attributes.amount; op2 = ops2[i2++]; } else { composedOperation.retain(op1.attributes.amount); op2.attributes.amount -= op1.attributes.amount; op1 = ops1[i1++]; } } else if (op1.type === "insert" && op2.type === "delete") { if (op1.attributes.text.length === op2.attributes.text) { op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.text.length > op2.attributes.text.length) { op1.attributes.text = op1.attributes.text.slice(op2.attributes.text.length); op2 = ops2[i2++]; } else { op2.attributes.text = op2.attributes.text.slice(op1.attributes.text.length); op1 = ops1[i1++]; } } else if (op1.type === "insert" && op2.type === "retain") { if (op1.attributes.text.length > op2.attributes.amount) { composedOperation.insert(op1.attributes.text.slice(0, op2.attributes.amount)); op1.attributes.text = op1.attributes.text.slice(op2.attributes.amount); op2 = ops2[i2++]; } else if (op1.attributes.text.length === op2.attributes.amount) { composedOperation.insert(op1.attributes.text); op1 = ops1[i1++]; op2 = ops2[i2++]; } else { composedOperation.insert(op1.attributes.text); op2.attributes.amount -= op1.attributes.text.length; op1 = ops1[i1++]; } } else if (op1.type === "retain" && op2.type === "delete") { if (op1.attributes.amount === op2.attributes.text.length) { composedOperation.delete(op2.attributes.text); op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.amount > op2.attributes.text.length) { composedOperation.delete(op2.attributes.text); op1.attributes.amount -= op2.attributes.text.length; op2 = ops2[i2++]; } else { composedOperation.delete(op2.attributes.text.slice(0, op1.attributes.amount)); op2.attributes.text = op2.attributes.text.slice(op1.attributes.amount); op1 = ops1[i1++]; } } else { throw new Error("This shouldn't happen: op1: " + JSON.stringify(op1) + ", op2: " + JSON.stringify(op2)); } } return composedOperation; }; /* Largely inspired from Firepad Transform takes two operations A (this) and B (other) that happened concurrently and produces two operations A' and B' (in an array) such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This function is the heart of OT. */ TextOperation.prototype.transform = function (operation2) { var operation1prime, operation2prime; var ops1, ops2; // Give priority with the user id if (this.gotPriority(operation2.userId)) { operation1prime = new TextOperation(this.userId); operation2prime = new TextOperation(operation2.userId); ops1 = this.clone().ops; ops2 = operation2.clone().ops; } else { operation1prime = new TextOperation(operation2.userId); operation2prime = new TextOperation(this.userId); ops1 = operation2.clone().ops; ops2 = this.clone().ops; } var i1 = 0; var i2 = 0; var op1 = ops1[i1++]; var op2 = ops2[i2++]; while (true) { // At every iteration of the loop, the imaginary cursor that both // operation1 and operation2 have that operates on the input string must // have the same position in the input string. // end condition: both ops1 and ops2 have been processed if (op1 == null && op2 == null) break; // next two cases: one or both ops are insert ops // => insert the string in the corresponding prime operation, skip it in // the other one. If both op1 and op2 are insert ops, prefer op1. if (op1 != null && op1.type === "insert") { operation1prime.insert(op1.attributes.text); operation2prime.retain(op1.attributes.text.length); op1 = ops1[i1++]; continue; } if (op2 != null && op2.type === "insert") { operation1prime.retain(op2.attributes.text.length); operation2prime.insert(op2.attributes.text); op2 = ops2[i2++]; continue; } if (op1 == null) throw new Error("Cannot transform operations: first operation is too short."); if (op2 == null) throw new Error("Cannot transform operations: first operation is too long."); if (op1.type === "retain" && op2.type === "retain") { // Simple case: retain/retain var minl = void 0; if (op1.attributes.amount === op2.attributes.amount) { minl = op2.attributes.amount; op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.amount > op2.attributes.amount) { minl = op2.attributes.amount; op1.attributes.amount -= op2.attributes.amount; op2 = ops2[i2++]; } else { minl = op1.attributes.amount; op2.attributes.amount -= op1.attributes.amount; op1 = ops1[i1++]; } operation1prime.retain(minl); operation2prime.retain(minl); } else if (op1.type === "delete" && op2.type === "delete") { // Both operations delete the same string at the same position. We don't // need to produce any operations, we just skip over the delete ops and // handle the case that one operation deletes more than the other. if (op1.attributes.text.length === op2.attributes.text.length) { op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.text.length > op2.attributes.text.length) { op1.attributes.text = op1.attributes.text.slice(op2.attributes.text.length); op2 = ops2[i2++]; } else { op2.attributes.text = op1.attributes.text.slice(op1.attributes.text.length); op1 = ops1[i1++]; } } else if (op1.type === "delete" && op2.type === "retain") { var text = void 0; if (op1.attributes.text.length === op2.attributes.amount) { text = op1.attributes.text; op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.text.length > op2.attributes.amount) { text = op1.attributes.text.slice(0, op2.attributes.amount); op1.attributes.text = op1.attributes.text.slice(op2.attributes.amount); op2 = ops2[i2++]; } else { text = op1.attributes.text; op2.attributes.amount -= op1.attributes.text.length; op1 = ops1[i1++]; } operation1prime.delete(text); } else if (op1.type === "retain" && op2.type === "delete") { var text = void 0; if (op1.attributes.amount === op2.attributes.text.length) { text = op2.attributes.text; op1 = ops1[i1++]; op2 = ops2[i2++]; } else if (op1.attributes.amount > op2.attributes.text.length) { text = op2.attributes.text; op1.attributes.amount -= op2.attributes.text.length; op2 = ops2[i2++]; } else { text = op2.attributes.text.slice(0, op1.attributes.amount); op2.attributes.text = op2.attributes.text.slice(op1.attributes.amount); op1 = ops1[i1++]; } operation2prime.delete(text); } else { throw new Error("The two operations aren't compatible"); } } if (this.gotPriority(operation2.userId)) return [operation1prime, operation2prime]; else return [operation2prime, operation1prime]; }; TextOperation.prototype.gotPriority = function (id2) { return (this.userId <= id2); }; return TextOperation; })(); module.exports = TextOperation; },{"./index":9}],9:[function(require,module,exports){ var TextOp = require("./TextOp"); exports.TextOp = TextOp; var Document = require("./Document"); exports.Document = Document; var TextOperation = require("./TextOperation"); exports.TextOperation = TextOperation; },{"./Document":6,"./TextOp":7,"./TextOperation":8}]},{},[3]);