diff --git a/bin/checkPad.js b/bin/checkPad.js
index 9e0544415..356b07799 100644
--- a/bin/checkPad.js
+++ b/bin/checkPad.js
@@ -15,7 +15,8 @@ var log4js = require("log4js");
log4js.setGlobalLogLevel("INFO");
var async = require("async");
var db = require('../node/db/DB');
-var Changeset = require('../node/utils/Changeset');
+var CommonCode = require('../node/utils/common_code');
+var Changeset = CommonCode.require("/Changeset");
var padManager;
async.series([
diff --git a/bin/convert.js b/bin/convert.js
index 4302114c6..c5dc535cd 100644
--- a/bin/convert.js
+++ b/bin/convert.js
@@ -1,11 +1,12 @@
+var CommonCode = require('../node/utils/common_code');
var startTime = new Date().getTime();
var fs = require("fs");
var ueberDB = require("ueberDB");
var mysql = require("mysql");
var async = require("async");
-var Changeset = require("../node/utils/Changeset");
-var randomString = require("../node/utils/randomstring");
-var AttributePoolFactory = require("../node/utils/AttributePoolFactory");
+var Changeset = CommonCode.require("/Changeset");
+var randomString = CommonCode.require('/pad_utils').randomString;
+var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var settingsFile = process.argv[2];
var sqlOutputFile = process.argv[3];
diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js
index 7c054a56f..9baf63475 100644
--- a/node/db/AuthorManager.js
+++ b/node/db/AuthorManager.js
@@ -18,11 +18,11 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
-
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
/**
* Checks if the author exists
diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js
index 7e3b7d6d1..bd294ba62 100644
--- a/node/db/GroupManager.js
+++ b/node/db/GroupManager.js
@@ -18,9 +18,10 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var padManager = require("./PadManager");
diff --git a/node/db/Pad.js b/node/db/Pad.js
index 632eebe8c..99a53143c 100644
--- a/node/db/Pad.js
+++ b/node/db/Pad.js
@@ -2,9 +2,11 @@
* The pad object, defined with joose
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
-var Changeset = require("../utils/Changeset");
-var AttributePoolFactory = require("../utils/AttributePoolFactory");
+var Changeset = CommonCode.require("/Changeset");
+var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
+var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var settings = require('../utils/Settings');
@@ -477,15 +479,7 @@ function hash(password, salt)
function generateSalt()
{
- var len = 86;
- var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
- var randomstring = '';
- for (var i = 0; i < len; i++)
- {
- var rnum = Math.floor(Math.random() * chars.length);
- randomstring += chars.substring(rnum, rnum + 1);
- }
- return randomstring;
+ return randomstring(86);
}
function compare(hashStr, password)
diff --git a/node/db/ReadOnlyManager.js b/node/db/ReadOnlyManager.js
index 1e5079c52..e5dab99b4 100644
--- a/node/db/ReadOnlyManager.js
+++ b/node/db/ReadOnlyManager.js
@@ -18,11 +18,11 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
-
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
/**
* returns a read only id for a pad
diff --git a/node/db/SecurityManager.js b/node/db/SecurityManager.js
index 4b86d868a..33ab37d44 100644
--- a/node/db/SecurityManager.js
+++ b/node/db/SecurityManager.js
@@ -18,6 +18,7 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
@@ -25,8 +26,7 @@ var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings")
-
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
/**
* This function controlls the access to a pad, it checks if the user can access a pad.
diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js
index 084d4a695..c5af33c68 100644
--- a/node/db/SessionManager.js
+++ b/node/db/SessionManager.js
@@ -18,9 +18,10 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var groupMangager = require("./GroupManager");
diff --git a/node/easysync_tests.js b/node/easysync_tests.js
index 5b73b7170..8e7398bea 100644
--- a/node/easysync_tests.js
+++ b/node/easysync_tests.js
@@ -20,8 +20,9 @@
* limitations under the License.
*/
-var Changeset = require('./utils/Changeset');
-var AttributePoolFactory = require("./utils/AttributePoolFactory");
+var CommonCode = require('./utils/common_code');
+var Changeset = CommonCode.require("/Changeset");
+var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
function random() {
this.nextInt = function (maxValue) {
diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js
index ca45e1f8f..a7f66151c 100644
--- a/node/handler/APIHandler.js
+++ b/node/handler/APIHandler.js
@@ -18,11 +18,12 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var fs = require("fs");
var api = require("../db/API");
var padManager = require("../db/PadManager");
-var randomString = require("../utils/randomstring");
+var randomString = CommonCode.require('/pad_utils').randomString;
//ensure we have an apikey
var apikey = null;
diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js
index 4a078542c..19aa1f9b2 100644
--- a/node/handler/PadMessageHandler.js
+++ b/node/handler/PadMessageHandler.js
@@ -18,11 +18,12 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
-var Changeset = require("../utils/Changeset");
-var AttributePoolFactory = require("../utils/AttributePoolFactory");
+var Changeset = CommonCode.require("/Changeset");
+var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var authorManager = require("../db/AuthorManager");
var readOnlyManager = require("../db/ReadOnlyManager");
var settings = require('../utils/Settings');
diff --git a/node/handler/TimesliderMessageHandler.js b/node/handler/TimesliderMessageHandler.js
index b30a9fc9d..188068430 100644
--- a/node/handler/TimesliderMessageHandler.js
+++ b/node/handler/TimesliderMessageHandler.js
@@ -18,11 +18,12 @@
* limitations under the License.
*/
+var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
-var Changeset = require("../utils/Changeset");
-var AttributePoolFactory = require("../utils/AttributePoolFactory");
+var Changeset = CommonCode.require("/Changeset");
+var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var settings = require('../utils/Settings');
var authorManager = require("../db/AuthorManager");
var log4js = require('log4js');
diff --git a/node/utils/ExportDokuWiki.js b/node/utils/ExportDokuWiki.js
index 48e4b2915..abe6d3471 100644
--- a/node/utils/ExportDokuWiki.js
+++ b/node/utils/ExportDokuWiki.js
@@ -15,7 +15,8 @@
*/
var async = require("async");
-var Changeset = require("./Changeset");
+var CommonCode = require('./common_code');
+var Changeset = CommonCode.require("/Changeset");
var padManager = require("../db/PadManager");
function getPadDokuWiki(pad, revNum, callback)
diff --git a/node/utils/ExportHtml.js b/node/utils/ExportHtml.js
index d4be80d20..afeafd3a9 100644
--- a/node/utils/ExportHtml.js
+++ b/node/utils/ExportHtml.js
@@ -14,10 +14,12 @@
* limitations under the License.
*/
+var CommonCode = require('./common_code');
var async = require("async");
-var Changeset = require("./Changeset");
+var Changeset = CommonCode.require("/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
+var Security = CommonCode.require('/security');
function getPadPlainText(pad, revNum)
{
@@ -269,7 +271,7 @@ function getHTMLFromAtext(pad, atext)
//from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), "");
- assem.append(_escapeHTML(s));
+ assem.append(_encodeWhitespace(Security.escapeHTML(s)));
} // end iteration over spans in line
var tags2close = [];
@@ -292,7 +294,7 @@ function getHTMLFromAtext(pad, atext)
var url = urlData[1];
var urlLength = url.length;
processNextChars(startIndex - idx);
- assem.append('');
+ assem.append('');
processNextChars(urlLength);
assem.append('');
});
@@ -493,25 +495,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
});
}
-function _escapeHTML(s)
-{
- var re = /[&"<>]/g;
- if (!re.MAP)
- {
- // persisted across function calls!
- re.MAP = {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- };
- }
-
- s = s.replace(re, function (c)
- {
- return re.MAP[c];
- });
-
+function _encodeWhitespace(s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
{
return "" +c.charCodeAt(0) + ";"
diff --git a/node/utils/ImportHtml.js b/node/utils/ImportHtml.js
index 1b0bcaea7..ce8663697 100644
--- a/node/utils/ImportHtml.js
+++ b/node/utils/ImportHtml.js
@@ -17,9 +17,10 @@
var jsdom = require('jsdom-nocontextifiy').jsdom;
var log4js = require('log4js');
-var Changeset = require("./Changeset");
-var contentcollector = require("./contentcollector");
-var map = require("../../static/js/ace2_common.js").map;
+var CommonCode = require('../utils/common_code');
+var Changeset = CommonCode.require("/Changeset");
+var contentcollector = CommonCode.require("/contentcollector");
+var map = CommonCode.require("/ace2_common").map;
function setPadHTML(pad, html, callback)
{
diff --git a/node/utils/common_code.js b/node/utils/common_code.js
new file mode 100644
index 000000000..359c9cfec
--- /dev/null
+++ b/node/utils/common_code.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var RequireKernel = require('require-kernel/');
+
+var CLIENT_JS_SRC = __dirname + '/../../static/js/';
+var client_require = RequireKernel.requireForPaths('file://' + CLIENT_JS_SRC);
+
+exports.require = client_require;
diff --git a/node/utils/contentcollector.js b/node/utils/contentcollector.js
deleted file mode 100644
index a7fa940a6..000000000
--- a/node/utils/contentcollector.js
+++ /dev/null
@@ -1,692 +0,0 @@
-// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.contentcollector
-// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
-// %APPJET%: import("etherpad.admin.plugins");
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var Changeset = require("../utils/Changeset");
-
-var _MAX_LIST_LEVEL = 8;
-
-function sanitizeUnicode(s)
-{
- return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
-}
-
-function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author)
-{
- browser = browser || {};
-
- var plugins_;
- if (typeof(plugins) != 'undefined')
- {
- plugins_ = plugins;
- }
- else
- {
- plugins_ = {callHook: function () {}};
- }
-
- var dom = domInterface || {
- isNodeText: function(n)
- {
- return (n.nodeType == 3);
- },
- nodeTagName: function(n)
- {
- return n.tagName;
- },
- nodeValue: function(n)
- {
- return n.nodeValue;
- },
- nodeNumChildren: function(n)
- {
- return n.childNodes.length;
- },
- nodeChild: function(n, i)
- {
- return n.childNodes.item(i);
- },
- nodeProp: function(n, p)
- {
- return n[p];
- },
- nodeAttr: function(n, a)
- {
- return n.getAttribute(a);
- },
- optNodeInnerHTML: function(n)
- {
- return n.innerHTML;
- }
- };
-
- var _blockElems = {
- "div": 1,
- "p": 1,
- "pre": 1,
- "li": 1
- };
-
- function isBlockElement(n)
- {
- return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()];
- }
-
- function textify(str)
- {
- return sanitizeUnicode(
- str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
- }
-
- function getAssoc(node, name)
- {
- return dom.nodeProp(node, "_magicdom_" + name);
- }
-
- var lines = (function()
- {
- var textArray = [];
- var attribsArray = [];
- var attribsBuilder = null;
- var op = Changeset.newOp('+');
- var self = {
- length: function()
- {
- return textArray.length;
- },
- atColumnZero: function()
- {
- return textArray[textArray.length - 1] === "";
- },
- startNew: function()
- {
- textArray.push("");
- self.flush(true);
- attribsBuilder = Changeset.smartOpAssembler();
- },
- textOfLine: function(i)
- {
- return textArray[i];
- },
- appendText: function(txt, attrString)
- {
- textArray[textArray.length - 1] += txt;
- //dmesg(txt+" / "+attrString);
- op.attribs = attrString;
- op.chars = txt.length;
- attribsBuilder.append(op);
- },
- textLines: function()
- {
- return textArray.slice();
- },
- attribLines: function()
- {
- return attribsArray;
- },
- // call flush only when you're done
- flush: function(withNewline)
- {
- if (attribsBuilder)
- {
- attribsArray.push(attribsBuilder.toString());
- attribsBuilder = null;
- }
- }
- };
- self.startNew();
- return self;
- }());
- var cc = {};
-
- function _ensureColumnZero(state)
- {
- if (!lines.atColumnZero())
- {
- cc.startNewLine(state);
- }
- }
- var selection, startPoint, endPoint;
- var selStart = [-1, -1],
- selEnd = [-1, -1];
- var blockElems = {
- "div": 1,
- "p": 1,
- "pre": 1
- };
-
- function _isEmpty(node, state)
- {
- // consider clean blank lines pasted in IE to be empty
- if (dom.nodeNumChildren(node) == 0) return true;
- if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == " " && !getAssoc(node, "unpasted"))
- {
- if (state)
- {
- var child = dom.nodeChild(node, 0);
- _reachPoint(child, 0, state);
- _reachPoint(child, 1, state);
- }
- return true;
- }
- return false;
- }
-
- function _pointHere(charsAfter, state)
- {
- var ln = lines.length() - 1;
- var chr = lines.textOfLine(ln).length;
- if (chr == 0 && state.listType && state.listType != 'none')
- {
- chr += 1; // listMarker
- }
- chr += charsAfter;
- return [ln, chr];
- }
-
- function _reachBlockPoint(nd, idx, state)
- {
- if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
- }
-
- function _reachPoint(nd, idx, state)
- {
- if (startPoint && nd == startPoint.node && startPoint.index == idx)
- {
- selStart = _pointHere(0, state);
- }
- if (endPoint && nd == endPoint.node && endPoint.index == idx)
- {
- selEnd = _pointHere(0, state);
- }
- }
- cc.incrementFlag = function(state, flagName)
- {
- state.flags[flagName] = (state.flags[flagName] || 0) + 1;
- }
- cc.decrementFlag = function(state, flagName)
- {
- state.flags[flagName]--;
- }
- cc.incrementAttrib = function(state, attribName)
- {
- if (!state.attribs[attribName])
- {
- state.attribs[attribName] = 1;
- }
- else
- {
- state.attribs[attribName]++;
- }
- _recalcAttribString(state);
- }
- cc.decrementAttrib = function(state, attribName)
- {
- state.attribs[attribName]--;
- _recalcAttribString(state);
- }
-
- function _enterList(state, listType)
- {
- var oldListType = state.listType;
- state.listLevel = (state.listLevel || 0) + 1;
- if (listType != 'none')
- {
- state.listNesting = (state.listNesting || 0) + 1;
- }
- state.listType = listType;
- _recalcAttribString(state);
- return oldListType;
- }
-
- function _exitList(state, oldListType)
- {
- state.listLevel--;
- if (state.listType != 'none')
- {
- state.listNesting--;
- }
- state.listType = oldListType;
- _recalcAttribString(state);
- }
-
- function _enterAuthor(state, author)
- {
- var oldAuthor = state.author;
- state.authorLevel = (state.authorLevel || 0) + 1;
- state.author = author;
- _recalcAttribString(state);
- return oldAuthor;
- }
-
- function _exitAuthor(state, oldAuthor)
- {
- state.authorLevel--;
- state.author = oldAuthor;
- _recalcAttribString(state);
- }
-
- function _recalcAttribString(state)
- {
- var lst = [];
- for (var a in state.attribs)
- {
- if (state.attribs[a])
- {
- lst.push([a, 'true']);
- }
- }
- if (state.authorLevel > 0)
- {
- var authorAttrib = ['author', state.author];
- if (apool.putAttrib(authorAttrib, true) >= 0)
- {
- // require that author already be in pool
- // (don't add authors from other documents, etc.)
- lst.push(authorAttrib);
- }
- }
- state.attribString = Changeset.makeAttribsString('+', lst, apool);
- }
-
- function _produceListMarker(state)
- {
- lines.appendText('*', Changeset.makeAttribsString('+', [
- ['list', state.listType],
- ['insertorder', 'first']
- ], apool));
- }
- cc.startNewLine = function(state)
- {
- if (state)
- {
- var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
- if (atBeginningOfLine && state.listType && state.listType != 'none')
- {
- _produceListMarker(state);
- }
- }
- lines.startNew();
- }
- cc.notifySelection = function(sel)
- {
- if (sel)
- {
- selection = sel;
- startPoint = selection.startPoint;
- endPoint = selection.endPoint;
- }
- };
- cc.doAttrib = function(state, na)
- {
- state.localAttribs = (state.localAttribs || []);
- state.localAttribs.push(na);
- cc.incrementAttrib(state, na);
- };
- cc.collectContent = function(node, state)
- {
- if (!state)
- {
- state = {
- flags: { /*name -> nesting counter*/
- },
- localAttribs: null,
- attribs: { /*name -> nesting counter*/
- },
- attribString: ''
- };
- }
- var localAttribs = state.localAttribs;
- state.localAttribs = null;
- var isBlock = isBlockElement(node);
- var isEmpty = _isEmpty(node, state);
- if (isBlock) _ensureColumnZero(state);
- var startLine = lines.length() - 1;
- _reachBlockPoint(node, 0, state);
- if (dom.isNodeText(node))
- {
- var txt = dom.nodeValue(node);
- var rest = '';
- var x = 0; // offset into original text
- if (txt.length == 0)
- {
- if (startPoint && node == startPoint.node)
- {
- selStart = _pointHere(0, state);
- }
- if (endPoint && node == endPoint.node)
- {
- selEnd = _pointHere(0, state);
- }
- }
- while (txt.length > 0)
- {
- var consumed = 0;
- if (state.flags.preMode)
- {
- var firstLine = txt.split('\n', 1)[0];
- consumed = firstLine.length + 1;
- rest = txt.substring(consumed);
- txt = firstLine;
- }
- else
- { /* will only run this loop body once */
- }
- if (startPoint && node == startPoint.node && startPoint.index - x <= txt.length)
- {
- selStart = _pointHere(startPoint.index - x, state);
- }
- if (endPoint && node == endPoint.node && endPoint.index - x <= txt.length)
- {
- selEnd = _pointHere(endPoint.index - x, state);
- }
- var txt2 = txt;
- if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt))
- {
- // prevents textnodes containing just "\n" from being significant
- // in safari when pasting text, now that we convert them to
- // spaces instead of removing them, because in other cases
- // removing "\n" from pasted HTML will collapse words together.
- txt2 = "";
- }
- var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
- if (atBeginningOfLine)
- {
- // newlines in the source mustn't become spaces at beginning of line box
- txt2 = txt2.replace(/^\n*/, '');
- }
- if (atBeginningOfLine && state.listType && state.listType != 'none')
- {
- _produceListMarker(state);
- }
- lines.appendText(textify(txt2), state.attribString);
- x += consumed;
- txt = rest;
- if (txt.length > 0)
- {
- cc.startNewLine(state);
- }
- }
- }
- else
- {
- var tname = (dom.nodeTagName(node) || "").toLowerCase();
- if (tname == "br")
- {
- cc.startNewLine(state);
- }
- else if (tname == "script" || tname == "style")
- {
- // ignore
- }
- else if (!isEmpty)
- {
- var styl = dom.nodeAttr(node, "style");
- var cls = dom.nodeProp(node, "className");
-
- var isPre = (tname == "pre");
- if ((!isPre) && browser.safari)
- {
- isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
- }
- if (isPre) cc.incrementFlag(state, 'preMode');
- var oldListTypeOrNull = null;
- var oldAuthorOrNull = null;
- if (collectStyles)
- {
- plugins_.callHook('collectContentPre', {
- cc: cc,
- state: state,
- tname: tname,
- styl: styl,
- cls: cls
- });
- if (tname == "b" || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || tname == "strong")
- {
- cc.doAttrib(state, "bold");
- }
- if (tname == "i" || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || tname == "em")
- {
- cc.doAttrib(state, "italic");
- }
- if (tname == "u" || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || tname == "ins")
- {
- cc.doAttrib(state, "underline");
- }
- if (tname == "s" || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == "del")
- {
- cc.doAttrib(state, "strikethrough");
- }
- if (tname == "ul" || tname == "ol")
- {
- var type;
- var rr = cls && /(?:^| )list-([a-z]+[12345678])\b/.exec(cls);
- type = rr && rr[1] || "bullet" + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
- oldListTypeOrNull = (_enterList(state, type) || 'none');
- }
- else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/))
- {
- oldListTypeOrNull = (_enterList(state, type) || 'none');
- }
- if (className2Author && cls)
- {
- var classes = cls.match(/\S+/g);
- if (classes && classes.length > 0)
- {
- for (var i = 0; i < classes.length; i++)
- {
- var c = classes[i];
- var a = className2Author(c);
- if (a)
- {
- oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
- break;
- }
- }
- }
- }
- }
-
- var nc = dom.nodeNumChildren(node);
- for (var i = 0; i < nc; i++)
- {
- var c = dom.nodeChild(node, i);
- cc.collectContent(c, state);
- }
-
- if (collectStyles)
- {
- plugins_.callHook('collectContentPost', {
- cc: cc,
- state: state,
- tname: tname,
- styl: styl,
- cls: cls
- });
- }
-
- if (isPre) cc.decrementFlag(state, 'preMode');
- if (state.localAttribs)
- {
- for (var i = 0; i < state.localAttribs.length; i++)
- {
- cc.decrementAttrib(state, state.localAttribs[i]);
- }
- }
- if (oldListTypeOrNull)
- {
- _exitList(state, oldListTypeOrNull);
- }
- if (oldAuthorOrNull)
- {
- _exitAuthor(state, oldAuthorOrNull);
- }
- }
- }
- if (!browser.msie)
- {
- _reachBlockPoint(node, 1, state);
- }
- if (isBlock)
- {
- if (lines.length() - 1 == startLine)
- {
- cc.startNewLine(state);
- }
- else
- {
- _ensureColumnZero(state);
- }
- }
-
- if (browser.msie)
- {
- // in IE, a point immediately after a DIV appears on the next line
- _reachBlockPoint(node, 1, state);
- }
-
- state.localAttribs = localAttribs;
- };
- // can pass a falsy value for end of doc
- cc.notifyNextNode = function(node)
- {
- // an "empty block" won't end a line; this addresses an issue in IE with
- // typing into a blank line at the end of the document. typed text
- // goes into the body, and the empty line div still looks clean.
- // it is incorporated as dirty by the rule that a dirty region has
- // to end a line.
- if ((!node) || (isBlockElement(node) && !_isEmpty(node)))
- {
- _ensureColumnZero(null);
- }
- };
- // each returns [line, char] or [-1,-1]
- var getSelectionStart = function()
- {
- return selStart;
- };
- var getSelectionEnd = function()
- {
- return selEnd;
- };
-
- // returns array of strings for lines found, last entry will be "" if
- // last line is complete (i.e. if a following span should be on a new line).
- // can be called at any point
- cc.getLines = function()
- {
- return lines.textLines();
- };
-
- cc.finish = function()
- {
- lines.flush();
- var lineAttribs = lines.attribLines();
- var lineStrings = cc.getLines();
-
- lineStrings.length--;
- lineAttribs.length--;
-
- var ss = getSelectionStart();
- var se = getSelectionEnd();
-
- function fixLongLines()
- {
- // design mode does not deal with with really long lines!
- var lineLimit = 2000; // chars
- var buffer = 10; // chars allowed over before wrapping
- var linesWrapped = 0;
- var numLinesAfter = 0;
- for (var i = lineStrings.length - 1; i >= 0; i--)
- {
- var oldString = lineStrings[i];
- var oldAttribString = lineAttribs[i];
- if (oldString.length > lineLimit + buffer)
- {
- var newStrings = [];
- var newAttribStrings = [];
- while (oldString.length > lineLimit)
- {
- //var semiloc = oldString.lastIndexOf(';', lineLimit-1);
- //var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
- lengthToTake = lineLimit;
- newStrings.push(oldString.substring(0, lengthToTake));
- oldString = oldString.substring(lengthToTake);
- newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
- oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
- }
- if (oldString.length > 0)
- {
- newStrings.push(oldString);
- newAttribStrings.push(oldAttribString);
- }
-
- function fixLineNumber(lineChar)
- {
- if (lineChar[0] < 0) return;
- var n = lineChar[0];
- var c = lineChar[1];
- if (n > i)
- {
- n += (newStrings.length - 1);
- }
- else if (n == i)
- {
- var a = 0;
- while (c > newStrings[a].length)
- {
- c -= newStrings[a].length;
- a++;
- }
- n += a;
- }
- lineChar[0] = n;
- lineChar[1] = c;
- }
- fixLineNumber(ss);
- fixLineNumber(se);
- linesWrapped++;
- numLinesAfter += newStrings.length;
-
- newStrings.unshift(i, 1);
- lineStrings.splice.apply(lineStrings, newStrings);
- newAttribStrings.unshift(i, 1);
- lineAttribs.splice.apply(lineAttribs, newAttribStrings);
- }
- }
- return {
- linesWrapped: linesWrapped,
- numLinesAfter: numLinesAfter
- };
- }
- var wrapData = fixLongLines();
-
- return {
- selStart: ss,
- selEnd: se,
- linesWrapped: wrapData.linesWrapped,
- numLinesAfter: wrapData.numLinesAfter,
- lines: lineStrings,
- lineAttribs: lineAttribs
- };
- }
-
- return cc;
-}
-
-exports.makeContentCollector = makeContentCollector;
diff --git a/node/utils/randomstring.js b/node/utils/randomstring.js
deleted file mode 100644
index 4c1bba244..000000000
--- a/node/utils/randomstring.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
- */
-var randomString = function randomString(len)
-{
- var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- var randomstring = '';
- for (var i = 0; i < len; i++)
- {
- var rnum = Math.floor(Math.random() * chars.length);
- randomstring += chars.substring(rnum, rnum + 1);
- }
- return randomstring;
-};
-
-module.exports = randomString;
diff --git a/node/utils/tar.json b/node/utils/tar.json
index b319791cf..e922dddeb 100644
--- a/node/utils/tar.json
+++ b/node/utils/tar.json
@@ -1,6 +1,7 @@
{
"pad.js": [
"jquery.js"
+ , "security.js"
, "pad.js"
, "ace2_common.js"
, "pad_utils.js"
@@ -25,6 +26,7 @@
]
, "timeslider.js": [
"jquery.js"
+ , "security.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
@@ -39,10 +41,11 @@
, "pad_modals.js"
, "pad_savedrevs.js"
, "pad_impexp.js"
- , "easysync2_client.js"
- , "domline_client.js"
- , "linestylefilter_client.js"
- , "cssmanager_client.js"
+ , "AttributePoolFactory.js"
+ , "Changeset.js"
+ , "domline.js"
+ , "linestylefilter.js"
+ , "cssmanager.js"
, "broadcast.js"
, "broadcast_slider.js"
, "broadcast_revisions.js"
@@ -50,9 +53,11 @@
]
, "ace2_inner.js": [
"ace2_common.js"
+ , "AttributePoolFactory.js"
+ , "Changeset.js"
+ , "security.js"
, "skiplist.js"
, "virtual_lines.js"
- , "easysync2.js"
, "cssmanager.js"
, "colorutils.js"
, "undomodule.js"
diff --git a/node/utils/AttributePoolFactory.js b/static/js/AttributePoolFactory.js
similarity index 97%
rename from node/utils/AttributePoolFactory.js
rename to static/js/AttributePoolFactory.js
index 807c2b393..00b58dbb3 100644
--- a/node/utils/AttributePoolFactory.js
+++ b/static/js/AttributePoolFactory.js
@@ -1,8 +1,8 @@
/**
- * This code represents the Attribute Pool Object of the original Etherpad.
+ * This code represents the Attribute Pool Object of the original Etherpad.
* 90% of the code is still like in the original Etherpad
* Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
- * You can find a explanation what a attribute pool is here:
+ * You can find a explanation what a attribute pool is here:
* https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
*/
diff --git a/node/utils/Changeset.js b/static/js/Changeset.js
similarity index 99%
rename from node/utils/Changeset.js
rename to static/js/Changeset.js
index 9e1b60ebe..715836d5f 100644
--- a/node/utils/Changeset.js
+++ b/static/js/Changeset.js
@@ -1,10 +1,10 @@
/*
* This is the Changeset library copied from the old Etherpad with some modifications to use it in node.js
* Can be found in https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
- */
+ */
/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
+ * This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
@@ -25,7 +25,7 @@
* limitations under the License.
*/
-var AttributePoolFactory = require("./AttributePoolFactory");
+var AttributePoolFactory = require("/AttributePoolFactory");
var _opt = null;
diff --git a/static/js/ace2_common.js b/static/js/ace2_common.js
index 1ce7810aa..b4c72a92f 100644
--- a/static/js/ace2_common.js
+++ b/static/js/ace2_common.js
@@ -20,6 +20,7 @@
* limitations under the License.
*/
+var Security = require('/security');
function isNodeText(node)
{
@@ -137,14 +138,7 @@ function binarySearchInfinite(expectedLength, func)
function htmlPrettyEscape(str)
{
- return str.replace(/[&"<>]/g, function (c) {
- return {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- }[c] || c;
- }).replace(/\r?\n/g, '\\n');
+ return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
}
exports.isNodeText = isNodeText;
diff --git a/static/js/ace2_inner.js b/static/js/ace2_inner.js
index 31d4dac94..d21135743 100644
--- a/static/js/ace2_inner.js
+++ b/static/js/ace2_inner.js
@@ -42,8 +42,8 @@ var colorutils = require('/colorutils').colorutils;
var makeContentCollector = require('/contentcollector').makeContentCollector;
var makeCSSManager = require('/cssmanager').makeCSSManager;
var domline = require('/domline').domline;
-var AttribPool = require('/easysync2').AttribPool;
-var Changeset = require('/easysync2').Changeset;
+var AttribPool = require('/AttributePoolFactory').createAttributePool;
+var Changeset = require('/Changeset');
var linestylefilter = require('/linestylefilter').linestylefilter;
var newSkipList = require('/skiplist').newSkipList;
var undoModule = require('/undomodule').undoModule;
diff --git a/static/js/broadcast.js b/static/js/broadcast.js
index 020f47e7c..4a7b01681 100644
--- a/static/js/broadcast.js
+++ b/static/js/broadcast.js
@@ -20,11 +20,11 @@
* limitations under the License.
*/
-var makeCSSManager = require('/cssmanager_client').makeCSSManager;
-var domline = require('/domline_client').domline;
-var Changeset = require('/easysync2_client').Changeset;
-var AttribPool = require('/easysync2_client').AttribPool;
-var linestylefilter = require('/linestylefilter_client').linestylefilter;
+var makeCSSManager = require('/cssmanager').makeCSSManager;
+var domline = require('/domline').domline;
+var AttribPool = require('/AttributePoolFactory').createAttributePool;
+var Changeset = require('/Changeset');
+var linestylefilter = require('/linestylefilter').linestylefilter;
var colorutils = require('/colorutils').colorutils;
// These parameters were global, now they are injected. A reference to the
diff --git a/static/js/changesettracker.js b/static/js/changesettracker.js
index 7b0fb3e46..e34dc107e 100644
--- a/static/js/changesettracker.js
+++ b/static/js/changesettracker.js
@@ -20,8 +20,8 @@
* limitations under the License.
*/
-var Changeset = require('/easysync2').Changeset;
-var AttribPool = require('/easysync2').AttribPool;
+var AttribPool = require('/AttributePoolFactory').createAttributePool;
+var Changeset = require('/Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
{
diff --git a/static/js/contentcollector.js b/static/js/contentcollector.js
index 0437ccd7b..96dc4b7dd 100644
--- a/static/js/contentcollector.js
+++ b/static/js/contentcollector.js
@@ -25,7 +25,7 @@
var _MAX_LIST_LEVEL = 8;
-var Changeset = require('/easysync2').Changeset
+var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
function sanitizeUnicode(s)
diff --git a/static/js/cssmanager_client.js b/static/js/cssmanager_client.js
deleted file mode 100644
index 6d9d989e8..000000000
--- a/static/js/cssmanager_client.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/cssmanager.js
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function makeCSSManager(emptyStylesheetTitle)
-{
-
- function getSheetByTitle(title)
- {
- var allSheets = document.styleSheets;
- for (var i = 0; i < allSheets.length; i++)
- {
- var s = allSheets[i];
- if (s.title == title)
- {
- return s;
- }
- }
- return null;
- }
-
-/*function getSheetTagByTitle(title) {
- var allStyleTags = document.getElementsByTagName("style");
- for(var i=0;i= 0)
- {
- browserDeleteRule(i);
- selectorList.splice(i, 1);
- }
- }
-
- return {
- selectorStyle: selectorStyle,
- removeSelectorStyle: removeSelectorStyle,
- info: function()
- {
- return selectorList.length + ":" + browserRules().length;
- }
- };
-}
-
-exports.makeCSSManager = makeCSSManager;
diff --git a/static/js/domline.js b/static/js/domline.js
index 8d8c2ea9e..15528bf7a 100644
--- a/static/js/domline.js
+++ b/static/js/domline.js
@@ -26,6 +26,7 @@
// requires: plugins
// requires: undefined
+var Security = require('/security');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
@@ -103,17 +104,17 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
if (listType)
{
listType = listType[1];
- start = start?'start="'+start[1]+'"':'';
+ start = start?'start="'+Security.escapeHTMLAttribute(start[1])+'"':'';
if (listType)
{
if(listType.indexOf("number") < 0)
{
- preHtml = '- ';
+ preHtml = '';
}
else
{
- preHtml = '
- ';
+ preHtml = '
- ';
postHtml = '
';
}
}
@@ -168,7 +169,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
href = "http://"+href;
}
- extraOpenTags = extraOpenTags + '';
+ extraOpenTags = extraOpenTags + '';
extraCloseTags = '' + extraCloseTags;
}
if (simpleTags)
@@ -178,7 +179,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
simpleTags.reverse();
extraCloseTags = '' + simpleTags.join('>') + '>' + extraCloseTags;
}
- html.push('', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '');
+ html.push('', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '');
}
};
result.clearSpans = function()
@@ -224,27 +225,6 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
return result;
};
-domline.escapeHTML = function(s)
-{
- var re = /[&<>'"]/g;
- /']/; // stupid indentation thing
- if (!re.MAP)
- {
- // persisted across function calls!
- re.MAP = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
- }
- return s.replace(re, function(c)
- {
- return re.MAP[c];
- });
-};
-
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
diff --git a/static/js/domline_client.js b/static/js/domline_client.js
deleted file mode 100644
index 87b6ed558..000000000
--- a/static/js/domline_client.js
+++ /dev/null
@@ -1,309 +0,0 @@
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/domline.js
-// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// requires: top
-// requires: plugins
-// requires: undefined
-
-var plugins = require('/plugins').plugins;
-var map = require('/ace2_common').map;
-
-var domline = {};
-domline.noop = function()
-{};
-domline.identity = function(x)
-{
- return x;
-};
-
-domline.addToLineClass = function(lineClass, cls)
-{
- // an "empty span" at any point can be used to add classes to
- // the line, using line:className. otherwise, we ignore
- // the span.
- cls.replace(/\S+/g, function(c)
- {
- if (c.indexOf("line:") == 0)
- {
- // add class to line
- lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
- }
- });
- return lineClass;
-}
-
-// if "document" is falsy we don't create a DOM node, just
-// an object with innerHTML and className
-domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
-{
- var result = {
- node: null,
- appendSpan: domline.noop,
- prepareForAdd: domline.noop,
- notifyAdded: domline.noop,
- clearSpans: domline.noop,
- finishUpdate: domline.noop,
- lineMarker: 0
- };
-
- var browser = (optBrowser || {});
- var document = optDocument;
-
- if (document)
- {
- result.node = document.createElement("div");
- }
- else
- {
- result.node = {
- innerHTML: '',
- className: ''
- };
- }
-
- var html = [];
- var preHtml, postHtml;
- var curHTML = null;
-
- function processSpaces(s)
- {
- return domline.processSpaces(s, doesWrap);
- }
- var identity = domline.identity;
- var perTextNodeProcess = (doesWrap ? identity : processSpaces);
- var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
- var lineClass = 'ace-line';
- result.appendSpan = function(txt, cls)
- {
- if (cls.indexOf('list') >= 0)
- {
- var listType = /(?:^| )list:(\S+)/.exec(cls);
- var start = /(?:^| )start:(\S+)/.exec(cls);
- if (listType)
- {
- listType = listType[1];
- start = start?'start="'+start[1]+'"':'';
- if (listType)
- {
- if(listType.indexOf("number") < 0)
- {
- preHtml = '';
- }
- else
- {
- preHtml = '- ';
- postHtml = '
';
- }
- }
- result.lineMarker += txt.length;
- return; // don't append any text
- }
- }
- var href = null;
- var simpleTags = null;
- if (cls.indexOf('url') >= 0)
- {
- cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
- {
- href = url;
- return space + "url";
- });
- }
- if (cls.indexOf('tag') >= 0)
- {
- cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
- {
- if (!simpleTags) simpleTags = [];
- simpleTags.push(tag.toLowerCase());
- return space + tag;
- });
- }
-
- var extraOpenTags = "";
- var extraCloseTags = "";
-
- var plugins_ = plugins;
-
- map(plugins_.callHook("aceCreateDomLine", {
- domline: domline,
- cls: cls
- }), function(modifier)
- {
- cls = modifier.cls;
- extraOpenTags = extraOpenTags + modifier.extraOpenTags;
- extraCloseTags = modifier.extraCloseTags + extraCloseTags;
- });
-
- if ((!txt) && cls)
- {
- lineClass = domline.addToLineClass(lineClass, cls);
- }
- else if (txt)
- {
- if (href)
- {
- if(!~href.indexOf("http")) // if the url doesn't include http or https etc prefix it.
- {
- href = "http://"+href;
- }
- extraOpenTags = extraOpenTags + '';
- extraCloseTags = '' + extraCloseTags;
- }
- if (simpleTags)
- {
- simpleTags.sort();
- extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
- simpleTags.reverse();
- extraCloseTags = '' + simpleTags.join('>') + '>' + extraCloseTags;
- }
- html.push('', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '');
- }
- };
- result.clearSpans = function()
- {
- html = [];
- lineClass = ''; // non-null to cause update
- result.lineMarker = 0;
- };
-
- function writeHTML()
- {
- var newHTML = perHtmlLineProcess(html.join(''));
- if (!newHTML)
- {
- if ((!document) || (!optBrowser))
- {
- newHTML += ' ';
- }
- else if (!browser.msie)
- {
- newHTML += '
';
- }
- }
- if (nonEmpty)
- {
- newHTML = (preHtml || '') + newHTML + (postHtml || '');
- }
- html = preHtml = postHtml = null; // free memory
- if (newHTML !== curHTML)
- {
- curHTML = newHTML;
- result.node.innerHTML = curHTML;
- }
- if (lineClass !== null) result.node.className = lineClass;
- }
- result.prepareForAdd = writeHTML;
- result.finishUpdate = writeHTML;
- result.getInnerHTML = function()
- {
- return curHTML || '';
- };
-
- return result;
-};
-
-domline.escapeHTML = function(s)
-{
- var re = /[&<>'"]/g;
- /']/; // stupid indentation thing
- if (!re.MAP)
- {
- // persisted across function calls!
- re.MAP = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
- }
- return s.replace(re, function(c)
- {
- return re.MAP[c];
- });
-};
-
-domline.processSpaces = function(s, doesWrap)
-{
- if (s.indexOf("<") < 0 && !doesWrap)
- {
- // short-cut
- return s.replace(/ /g, ' ');
- }
- var parts = [];
- s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
- {
- parts.push(m);
- });
- if (doesWrap)
- {
- var endOfLine = true;
- var beforeSpace = false;
- // last space in a run is normal, others are nbsp,
- // end of line is nbsp
- for (var i = parts.length - 1; i >= 0; i--)
- {
- var p = parts[i];
- if (p == " ")
- {
- if (endOfLine || beforeSpace) parts[i] = ' ';
- endOfLine = false;
- beforeSpace = true;
- }
- else if (p.charAt(0) != "<")
- {
- endOfLine = false;
- beforeSpace = false;
- }
- }
- // beginning of line is nbsp
- for (var i = 0; i < parts.length; i++)
- {
- var p = parts[i];
- if (p == " ")
- {
- parts[i] = ' ';
- break;
- }
- else if (p.charAt(0) != "<")
- {
- break;
- }
- }
- }
- else
- {
- for (var i = 0; i < parts.length; i++)
- {
- var p = parts[i];
- if (p == " ")
- {
- parts[i] = ' ';
- }
- }
- }
- return parts.join('');
-};
-
-exports.domline = domline;
diff --git a/static/js/easysync2.js b/static/js/easysync2.js
deleted file mode 100644
index cef868a1d..000000000
--- a/static/js/easysync2.js
+++ /dev/null
@@ -1,2513 +0,0 @@
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.easysync2
-// %APPJET%: jimport("com.etherpad.Easysync2Support");
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//var _opt = (this.Easysync2Support || null);
-var _opt = null; // disable optimization for now
-
-function AttribPool()
-{
- var p = {};
- p.numToAttrib = {}; // e.g. {0: ['foo','bar']}
- p.attribToNum = {}; // e.g. {'foo,bar': 0}
- p.nextNum = 0;
-
- p.putAttrib = function(attrib, dontAddIfAbsent)
- {
- var str = String(attrib);
- if (str in p.attribToNum)
- {
- return p.attribToNum[str];
- }
- if (dontAddIfAbsent)
- {
- return -1;
- }
- var num = p.nextNum++;
- p.attribToNum[str] = num;
- p.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
- return num;
- };
-
- p.getAttrib = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return pair;
- return [pair[0], pair[1]]; // return a mutable copy
- };
-
- p.getAttribKey = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return '';
- return pair[0];
- };
-
- p.getAttribValue = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return '';
- return pair[1];
- };
-
- p.eachAttrib = function(func)
- {
- for (var n in p.numToAttrib)
- {
- var pair = p.numToAttrib[n];
- func(pair[0], pair[1]);
- }
- };
-
- p.toJsonable = function()
- {
- return {
- numToAttrib: p.numToAttrib,
- nextNum: p.nextNum
- };
- };
-
- p.fromJsonable = function(obj)
- {
- p.numToAttrib = obj.numToAttrib;
- p.nextNum = obj.nextNum;
- p.attribToNum = {};
- for (var n in p.numToAttrib)
- {
- p.attribToNum[String(p.numToAttrib[n])] = Number(n);
- }
- return p;
- };
-
- return p;
-}
-
-var Changeset = {};
-
-Changeset.error = function error(msg)
-{
- var e = new Error(msg);
- e.easysync = true;
- throw e;
-};
-Changeset.assert = function assert(b, msgParts)
-{
- if (!b)
- {
- var msg = Array.prototype.slice.call(arguments, 1).join('');
- Changeset.error("Changeset: " + msg);
- }
-};
-
-Changeset.parseNum = function(str)
-{
- return parseInt(str, 36);
-};
-Changeset.numToString = function(num)
-{
- return num.toString(36).toLowerCase();
-};
-Changeset.toBaseTen = function(cs)
-{
- var dollarIndex = cs.indexOf('$');
- var beforeDollar = cs.substring(0, dollarIndex);
- var fromDollar = cs.substring(dollarIndex);
- return beforeDollar.replace(/[0-9a-z]+/g, function(s)
- {
- return String(Changeset.parseNum(s));
- }) + fromDollar;
-};
-
-Changeset.oldLen = function(cs)
-{
- return Changeset.unpack(cs).oldLen;
-};
-Changeset.newLen = function(cs)
-{
- return Changeset.unpack(cs).newLen;
-};
-
-Changeset.opIterator = function(opsStr, optStartIndex)
-{
- //print(opsStr);
- var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
- var startIndex = (optStartIndex || 0);
- var curIndex = startIndex;
- var prevIndex = curIndex;
-
- function nextRegexMatch()
- {
- prevIndex = curIndex;
- var result;
- if (_opt)
- {
- result = _opt.nextOpInString(opsStr, curIndex);
- if (result)
- {
- if (result.opcode() == '?')
- {
- Changeset.error("Hit error opcode in op stream");
- }
- curIndex = result.lastIndex();
- }
- }
- else
- {
- regex.lastIndex = curIndex;
- result = regex.exec(opsStr);
- curIndex = regex.lastIndex;
- if (result[0] == '?')
- {
- Changeset.error("Hit error opcode in op stream");
- }
- }
- return result;
- }
- var regexResult = nextRegexMatch();
- var obj = Changeset.newOp();
-
- function next(optObj)
- {
- var op = (optObj || obj);
- if (_opt && regexResult)
- {
- op.attribs = regexResult.attribs();
- op.lines = regexResult.lines();
- op.chars = regexResult.chars();
- op.opcode = regexResult.opcode();
- regexResult = nextRegexMatch();
- }
- else if ((!_opt) && regexResult[0])
- {
- op.attribs = regexResult[1];
- op.lines = Changeset.parseNum(regexResult[2] || 0);
- op.opcode = regexResult[3];
- op.chars = Changeset.parseNum(regexResult[4]);
- regexResult = nextRegexMatch();
- }
- else
- {
- Changeset.clearOp(op);
- }
- return op;
- }
-
- function hasNext()
- {
- return !!(_opt ? regexResult : regexResult[0]);
- }
-
- function lastIndex()
- {
- return prevIndex;
- }
- return {
- next: next,
- hasNext: hasNext,
- lastIndex: lastIndex
- };
-};
-
-Changeset.clearOp = function(op)
-{
- op.opcode = '';
- op.chars = 0;
- op.lines = 0;
- op.attribs = '';
-};
-Changeset.newOp = function(optOpcode)
-{
- return {
- opcode: (optOpcode || ''),
- chars: 0,
- lines: 0,
- attribs: ''
- };
-};
-Changeset.cloneOp = function(op)
-{
- return {
- opcode: op.opcode,
- chars: op.chars,
- lines: op.lines,
- attribs: op.attribs
- };
-};
-Changeset.copyOp = function(op1, op2)
-{
- op2.opcode = op1.opcode;
- op2.chars = op1.chars;
- op2.lines = op1.lines;
- op2.attribs = op1.attribs;
-};
-Changeset.opString = function(op)
-{
- // just for debugging
- if (!op.opcode) return 'null';
- var assem = Changeset.opAssembler();
- assem.append(op);
- return assem.toString();
-};
-Changeset.stringOp = function(str)
-{
- // just for debugging
- return Changeset.opIterator(str).next();
-};
-
-Changeset.checkRep = function(cs)
-{
- // doesn't check things that require access to attrib pool (e.g. attribute order)
- // or original string (e.g. newline positions)
- var unpacked = Changeset.unpack(cs);
- var oldLen = unpacked.oldLen;
- var newLen = unpacked.newLen;
- var ops = unpacked.ops;
- var charBank = unpacked.charBank;
-
- var assem = Changeset.smartOpAssembler();
- var oldPos = 0;
- var calcNewLen = 0;
- var numInserted = 0;
- var iter = Changeset.opIterator(ops);
- while (iter.hasNext())
- {
- var o = iter.next();
- switch (o.opcode)
- {
- case '=':
- oldPos += o.chars;
- calcNewLen += o.chars;
- break;
- case '-':
- oldPos += o.chars;
- Changeset.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs);
- break;
- case '+':
- {
- calcNewLen += o.chars;
- numInserted += o.chars;
- Changeset.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs);
- break;
- }
- }
- assem.append(o);
- }
-
- calcNewLen += oldLen - oldPos;
- charBank = charBank.substring(0, numInserted);
- while (charBank.length < numInserted)
- {
- charBank += "?";
- }
-
- assem.endDocument();
- var normalized = Changeset.pack(oldLen, calcNewLen, assem.toString(), charBank);
- Changeset.assert(normalized == cs, normalized, ' != ', cs);
-
- return cs;
-}
-
-Changeset.smartOpAssembler = function()
-{
- // Like opAssembler but able to produce conforming changesets
- // from slightly looser input, at the cost of speed.
- // Specifically:
- // - merges consecutive operations that can be merged
- // - strips final "="
- // - ignores 0-length changes
- // - reorders consecutive + and - (which margingOpAssembler doesn't do)
- var minusAssem = Changeset.mergingOpAssembler();
- var plusAssem = Changeset.mergingOpAssembler();
- var keepAssem = Changeset.mergingOpAssembler();
- var assem = Changeset.stringAssembler();
- var lastOpcode = '';
- var lengthChange = 0;
-
- function flushKeeps()
- {
- assem.append(keepAssem.toString());
- keepAssem.clear();
- }
-
- function flushPlusMinus()
- {
- assem.append(minusAssem.toString());
- minusAssem.clear();
- assem.append(plusAssem.toString());
- plusAssem.clear();
- }
-
- function append(op)
- {
- if (!op.opcode) return;
- if (!op.chars) return;
-
- if (op.opcode == '-')
- {
- if (lastOpcode == '=')
- {
- flushKeeps();
- }
- minusAssem.append(op);
- lengthChange -= op.chars;
- }
- else if (op.opcode == '+')
- {
- if (lastOpcode == '=')
- {
- flushKeeps();
- }
- plusAssem.append(op);
- lengthChange += op.chars;
- }
- else if (op.opcode == '=')
- {
- if (lastOpcode != '=')
- {
- flushPlusMinus();
- }
- keepAssem.append(op);
- }
- lastOpcode = op.opcode;
- }
-
- function appendOpWithText(opcode, text, attribs, pool)
- {
- var op = Changeset.newOp(opcode);
- op.attribs = Changeset.makeAttribsString(opcode, attribs, pool);
- var lastNewlinePos = text.lastIndexOf('\n');
- if (lastNewlinePos < 0)
- {
- op.chars = text.length;
- op.lines = 0;
- append(op);
- }
- else
- {
- op.chars = lastNewlinePos + 1;
- op.lines = text.match(/\n/g).length;
- append(op);
- op.chars = text.length - (lastNewlinePos + 1);
- op.lines = 0;
- append(op);
- }
- }
-
- function toString()
- {
- flushPlusMinus();
- flushKeeps();
- return assem.toString();
- }
-
- function clear()
- {
- minusAssem.clear();
- plusAssem.clear();
- keepAssem.clear();
- assem.clear();
- lengthChange = 0;
- }
-
- function endDocument()
- {
- keepAssem.endDocument();
- }
-
- function getLengthChange()
- {
- return lengthChange;
- }
-
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument,
- appendOpWithText: appendOpWithText,
- getLengthChange: getLengthChange
- };
-};
-
-if (_opt)
-{
- Changeset.mergingOpAssembler = function()
- {
- var assem = _opt.mergingOpAssembler();
-
- function append(op)
- {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
-
- function toString()
- {
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- }
-
- function endDocument()
- {
- assem.endDocument();
- }
-
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument
- };
- };
-}
-else
-{
- Changeset.mergingOpAssembler = function()
- {
- // This assembler can be used in production; it efficiently
- // merges consecutive operations that are mergeable, ignores
- // no-ops, and drops final pure "keeps". It does not re-order
- // operations.
- var assem = Changeset.opAssembler();
- var bufOp = Changeset.newOp();
-
- // If we get, for example, insertions [xxx\n,yyy], those don't merge,
- // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
- // This variable stores the length of yyy and any other newline-less
- // ops immediately after it.
- var bufOpAdditionalCharsAfterNewline = 0;
-
- function flush(isEndDocument)
- {
- if (bufOp.opcode)
- {
- if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs)
- {
- // final merged keep, leave it implicit
- }
- else
- {
- assem.append(bufOp);
- if (bufOpAdditionalCharsAfterNewline)
- {
- bufOp.chars = bufOpAdditionalCharsAfterNewline;
- bufOp.lines = 0;
- assem.append(bufOp);
- bufOpAdditionalCharsAfterNewline = 0;
- }
- }
- bufOp.opcode = '';
- }
- }
-
- function append(op)
- {
- if (op.chars > 0)
- {
- if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs)
- {
- if (op.lines > 0)
- {
- // bufOp and additional chars are all mergeable into a multi-line op
- bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
- bufOp.lines += op.lines;
- bufOpAdditionalCharsAfterNewline = 0;
- }
- else if (bufOp.lines == 0)
- {
- // both bufOp and op are in-line
- bufOp.chars += op.chars;
- }
- else
- {
- // append in-line text to multi-line bufOp
- bufOpAdditionalCharsAfterNewline += op.chars;
- }
- }
- else
- {
- flush();
- Changeset.copyOp(op, bufOp);
- }
- }
- }
-
- function endDocument()
- {
- flush(true);
- }
-
- function toString()
- {
- flush();
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- Changeset.clearOp(bufOp);
- }
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument
- };
- };
-}
-
-if (_opt)
-{
- Changeset.opAssembler = function()
- {
- var assem = _opt.opAssembler();
- // this function allows op to be mutated later (doesn't keep a ref)
-
-
- function append(op)
- {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
-
- function toString()
- {
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- }
- return {
- append: append,
- toString: toString,
- clear: clear
- };
- };
-}
-else
-{
- Changeset.opAssembler = function()
- {
- var pieces = [];
- // this function allows op to be mutated later (doesn't keep a ref)
-
-
- function append(op)
- {
- pieces.push(op.attribs);
- if (op.lines)
- {
- pieces.push('|', Changeset.numToString(op.lines));
- }
- pieces.push(op.opcode);
- pieces.push(Changeset.numToString(op.chars));
- }
-
- function toString()
- {
- return pieces.join('');
- }
-
- function clear()
- {
- pieces.length = 0;
- }
- return {
- append: append,
- toString: toString,
- clear: clear
- };
- };
-}
-
-Changeset.stringIterator = function(str)
-{
- var curIndex = 0;
-
- function assertRemaining(n)
- {
- Changeset.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")");
- }
-
- function take(n)
- {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- curIndex += n;
- return s;
- }
-
- function peek(n)
- {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- return s;
- }
-
- function skip(n)
- {
- assertRemaining(n);
- curIndex += n;
- }
-
- function remaining()
- {
- return str.length - curIndex;
- }
- return {
- take: take,
- skip: skip,
- remaining: remaining,
- peek: peek
- };
-};
-
-Changeset.stringAssembler = function()
-{
- var pieces = [];
-
- function append(x)
- {
- pieces.push(String(x));
- }
-
- function toString()
- {
- return pieces.join('');
- }
- return {
- append: append,
- toString: toString
- };
-};
-
-// "lines" need not be an array as long as it supports certain calls (lines_foo inside).
-Changeset.textLinesMutator = function(lines)
-{
- // Mutates lines, an array of strings, in place.
- // Mutation operations have the same constraints as changeset operations
- // with respect to newlines, but not the other additional constraints
- // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline).
- // Can be used to mutate lists of strings where the last char of each string
- // is not actually a newline, but for the purposes of N and L values,
- // the caller should pretend it is, and for things to work right in that case, the input
- // to insert() should be a single line with no newlines.
- var curSplice = [0, 0];
- var inSplice = false;
- // position in document after curSplice is applied:
- var curLine = 0,
- curCol = 0;
- // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
- // curLine >= curSplice[0]
- // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
- // curCol == 0
-
- function lines_applySplice(s)
- {
- lines.splice.apply(lines, s);
- }
-
- function lines_toSource()
- {
- return lines.toSource();
- }
-
- function lines_get(idx)
- {
- if (lines.get)
- {
- return lines.get(idx);
- }
- else
- {
- return lines[idx];
- }
- }
- // can be unimplemented if removeLines's return value not needed
-
-
- function lines_slice(start, end)
- {
- if (lines.slice)
- {
- return lines.slice(start, end);
- }
- else
- {
- return [];
- }
- }
-
- function lines_length()
- {
- if ((typeof lines.length) == "number")
- {
- return lines.length;
- }
- else
- {
- return lines.length();
- }
- }
-
- function enterSplice()
- {
- curSplice[0] = curLine;
- curSplice[1] = 0;
- if (curCol > 0)
- {
- putCurLineInSplice();
- }
- inSplice = true;
- }
-
- function leaveSplice()
- {
- lines_applySplice(curSplice);
- curSplice.length = 2;
- curSplice[0] = curSplice[1] = 0;
- inSplice = false;
- }
-
- function isCurLineInSplice()
- {
- return (curLine - curSplice[0] < (curSplice.length - 2));
- }
-
- function debugPrint(typ)
- {
- print(typ + ": " + curSplice.toSource() + " / " + curLine + "," + curCol + " / " + lines_toSource());
- }
-
- function putCurLineInSplice()
- {
- if (!isCurLineInSplice())
- {
- curSplice.push(lines_get(curSplice[0] + curSplice[1]));
- curSplice[1]++;
- }
- return 2 + curLine - curSplice[0];
- }
-
- function skipLines(L, includeInSplice)
- {
- if (L)
- {
- if (includeInSplice)
- {
- if (!inSplice)
- {
- enterSplice();
- }
- for (var i = 0; i < L; i++)
- {
- curCol = 0;
- putCurLineInSplice();
- curLine++;
- }
- }
- else
- {
- if (inSplice)
- {
- if (L > 1)
- {
- leaveSplice();
- }
- else
- {
- putCurLineInSplice();
- }
- }
- curLine += L;
- curCol = 0;
- }
- //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
-/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
- print("BLAH");
- putCurLineInSplice();
- }*/
- // tests case foo in remove(), which isn't otherwise covered in current impl
- }
- //debugPrint("skip");
- }
-
- function skip(N, L, includeInSplice)
- {
- if (N)
- {
- if (L)
- {
- skipLines(L, includeInSplice);
- }
- else
- {
- if (includeInSplice && !inSplice)
- {
- enterSplice();
- }
- if (inSplice)
- {
- putCurLineInSplice();
- }
- curCol += N;
- //debugPrint("skip");
- }
- }
- }
-
- function removeLines(L)
- {
- var removed = '';
- if (L)
- {
- if (!inSplice)
- {
- enterSplice();
- }
-
- function nextKLinesText(k)
- {
- var m = curSplice[0] + curSplice[1];
- return lines_slice(m, m + k).join('');
- }
- if (isCurLineInSplice())
- {
- //print(curCol);
- if (curCol == 0)
- {
- removed = curSplice[curSplice.length - 1];
- // print("FOO"); // case foo
- curSplice.length--;
- removed += nextKLinesText(L - 1);
- curSplice[1] += L - 1;
- }
- else
- {
- removed = nextKLinesText(L - 1);
- curSplice[1] += L - 1;
- var sline = curSplice.length - 1;
- removed = curSplice[sline].substring(curCol) + removed;
- curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]);
- curSplice[1] += 1;
- }
- }
- else
- {
- removed = nextKLinesText(L);
- curSplice[1] += L;
- }
- //debugPrint("remove");
- }
- return removed;
- }
-
- function remove(N, L)
- {
- var removed = '';
- if (N)
- {
- if (L)
- {
- return removeLines(L);
- }
- else
- {
- if (!inSplice)
- {
- enterSplice();
- }
- var sline = putCurLineInSplice();
- removed = curSplice[sline].substring(curCol, curCol + N);
- curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N);
- //debugPrint("remove");
- }
- }
- return removed;
- }
-
- function insert(text, L)
- {
- if (text)
- {
- if (!inSplice)
- {
- enterSplice();
- }
- if (L)
- {
- var newLines = Changeset.splitTextLines(text);
- if (isCurLineInSplice())
- {
- //if (curCol == 0) {
- //curSplice.length--;
- //curSplice[1]--;
- //Array.prototype.push.apply(curSplice, newLines);
- //curLine += newLines.length;
- //}
- //else {
- var sline = curSplice.length - 1;
- var theLine = curSplice[sline];
- var lineCol = curCol;
- curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
- curLine++;
- newLines.splice(0, 1);
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- curSplice.push(theLine.substring(lineCol));
- curCol = 0;
- //}
- }
- else
- {
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- }
- }
- else
- {
- var sline = putCurLineInSplice();
- curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol);
- curCol += text.length;
- }
- //debugPrint("insert");
- }
- }
-
- function hasMore()
- {
- //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]);
- var docLines = lines_length();
- if (inSplice)
- {
- docLines += curSplice.length - 2 - curSplice[1];
- }
- return curLine < docLines;
- }
-
- function close()
- {
- if (inSplice)
- {
- leaveSplice();
- }
- //debugPrint("close");
- }
-
- var self = {
- skip: skip,
- remove: remove,
- insert: insert,
- close: close,
- hasMore: hasMore,
- removeLines: removeLines,
- skipLines: skipLines
- };
- return self;
-};
-
-Changeset.applyZip = function(in1, idx1, in2, idx2, func)
-{
- var iter1 = Changeset.opIterator(in1, idx1);
- var iter2 = Changeset.opIterator(in2, idx2);
- var assem = Changeset.smartOpAssembler();
- var op1 = Changeset.newOp();
- var op2 = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext())
- {
- if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
- if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
- func(op1, op2, opOut);
- if (opOut.opcode)
- {
- //print(opOut.toSource());
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- assem.endDocument();
- return assem.toString();
-};
-
-Changeset.unpack = function(cs)
-{
- var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
- var headerMatch = headerRegex.exec(cs);
- if ((!headerMatch) || (!headerMatch[0]))
- {
- Changeset.error("Not a changeset: " + cs);
- }
- var oldLen = Changeset.parseNum(headerMatch[1]);
- var changeSign = (headerMatch[2] == '>') ? 1 : -1;
- var changeMag = Changeset.parseNum(headerMatch[3]);
- var newLen = oldLen + changeSign * changeMag;
- var opsStart = headerMatch[0].length;
- var opsEnd = cs.indexOf("$");
- if (opsEnd < 0) opsEnd = cs.length;
- return {
- oldLen: oldLen,
- newLen: newLen,
- ops: cs.substring(opsStart, opsEnd),
- charBank: cs.substring(opsEnd + 1)
- };
-};
-
-Changeset.pack = function(oldLen, newLen, opsStr, bank)
-{
- var lenDiff = newLen - oldLen;
- var lenDiffStr = (lenDiff >= 0 ? '>' + Changeset.numToString(lenDiff) : '<' + Changeset.numToString(-lenDiff));
- var a = [];
- a.push('Z:', Changeset.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
- return a.join('');
-};
-
-Changeset.applyToText = function(cs, str)
-{
- var unpacked = Changeset.unpack(cs);
- Changeset.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var strIter = Changeset.stringIterator(str);
- var assem = Changeset.stringAssembler();
- while (csIter.hasNext())
- {
- var op = csIter.next();
- switch (op.opcode)
- {
- case '+':
- assem.append(bankIter.take(op.chars));
- break;
- case '-':
- strIter.skip(op.chars);
- break;
- case '=':
- assem.append(strIter.take(op.chars));
- break;
- }
- }
- assem.append(strIter.take(strIter.remaining()));
- return assem.toString();
-};
-
-Changeset.mutateTextLines = function(cs, lines)
-{
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var mut = Changeset.textLinesMutator(lines);
- while (csIter.hasNext())
- {
- var op = csIter.next();
- switch (op.opcode)
- {
- case '+':
- mut.insert(bankIter.take(op.chars), op.lines);
- break;
- case '-':
- mut.remove(op.chars, op.lines);
- break;
- case '=':
- mut.skip(op.chars, op.lines, ( !! op.attribs));
- break;
- }
- }
- mut.close();
-};
-
-Changeset.composeAttributes = function(att1, att2, resultIsMutation, pool)
-{
- // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
- // Sometimes attribute (key,value) pairs are treated as attribute presence
- // information, while other times they are treated as operations that
- // mutate a set of attributes, and this affects whether an empty value
- // is a deletion or a change.
- // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
- // ([], [(bold, )], true) -> [(bold, )]
- // ([], [(bold, )], false) -> []
- // ([], [(bold, true)], true) -> [(bold, true)]
- // ([], [(bold, true)], false) -> [(bold, true)]
- // ([(bold, true)], [(bold, )], true) -> [(bold, )]
- // ([(bold, true)], [(bold, )], false) -> []
- // pool can be null if att2 has no attributes.
- if ((!att1) && resultIsMutation)
- {
- // In the case of a mutation (i.e. composing two changesets),
- // an att2 composed with an empy att1 is just att2. If att1
- // is part of an attribution string, then att2 may remove
- // attributes that are already gone, so don't do this optimization.
- return att2;
- }
- if (!att2) return att1;
- var atts = [];
- att1.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- atts.push(pool.getAttrib(Changeset.parseNum(a)));
- return '';
- });
- att2.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- var pair = pool.getAttrib(Changeset.parseNum(a));
- var found = false;
- for (var i = 0; i < atts.length; i++)
- {
- var oldPair = atts[i];
- if (oldPair[0] == pair[0])
- {
- if (pair[1] || resultIsMutation)
- {
- oldPair[1] = pair[1];
- }
- else
- {
- atts.splice(i, 1);
- }
- found = true;
- break;
- }
- }
- if ((!found) && (pair[1] || resultIsMutation))
- {
- atts.push(pair);
- }
- return '';
- });
- atts.sort();
- var buf = Changeset.stringAssembler();
- for (var i = 0; i < atts.length; i++)
- {
- buf.append('*');
- buf.append(Changeset.numToString(pool.putAttrib(atts[i])));
- }
- //print(att1+" / "+att2+" / "+buf.toString());
- return buf.toString();
-};
-
-Changeset._slicerZipperFunc = function(attOp, csOp, opOut, pool)
-{
- // attOp is the op from the sequence that is being operated on, either an
- // attribution string or the earlier of two changesets being composed.
- // pool can be null if definitely not needed.
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- if (attOp.opcode == '-')
- {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- }
- else if (!attOp.opcode)
- {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- else
- {
- switch (csOp.opcode)
- {
- case '-':
- {
- if (csOp.chars <= attOp.chars)
- {
- // delete or delete part
- if (attOp.opcode == '=')
- {
- opOut.opcode = '-';
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = '';
- }
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- csOp.opcode = '';
- if (!attOp.chars)
- {
- attOp.opcode = '';
- }
- }
- else
- {
- // delete and keep going
- if (attOp.opcode == '=')
- {
- opOut.opcode = '-';
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = '';
- }
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- attOp.opcode = '';
- }
- break;
- }
- case '+':
- {
- // insert
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- break;
- }
- case '=':
- {
- if (csOp.chars <= attOp.chars)
- {
- // keep or keep part
- opOut.opcode = attOp.opcode;
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
- csOp.opcode = '';
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- if (!attOp.chars)
- {
- attOp.opcode = '';
- }
- }
- else
- {
- // keep and keep going
- opOut.opcode = attOp.opcode;
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
- attOp.opcode = '';
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- }
- break;
- }
- case '':
- {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- break;
- }
- }
- }
-};
-
-Changeset.applyToAttribution = function(cs, astr, pool)
-{
- var unpacked = Changeset.unpack(cs);
-
- return Changeset.applyZip(astr, 0, unpacked.ops, 0, function(op1, op2, opOut)
- {
- return Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- });
-};
-
-/*Changeset.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) {
- var iter = Changeset.opIterator(opsStr, optStartIndex);
- var bankIndex = 0;
-
-};*/
-
-Changeset.mutateAttributionLines = function(cs, lines, pool)
-{
- //dmesg(cs);
- //dmesg(lines.toSource()+" ->");
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var csBank = unpacked.charBank;
- var csBankIndex = 0;
- // treat the attribution lines as text lines, mutating a line at a time
- var mut = Changeset.textLinesMutator(lines);
-
- var lineIter = null;
-
- function isNextMutOp()
- {
- return (lineIter && lineIter.hasNext()) || mut.hasMore();
- }
-
- function nextMutOp(destOp)
- {
- if ((!(lineIter && lineIter.hasNext())) && mut.hasMore())
- {
- var line = mut.removeLines(1);
- lineIter = Changeset.opIterator(line);
- }
- if (lineIter && lineIter.hasNext())
- {
- lineIter.next(destOp);
- }
- else
- {
- destOp.opcode = '';
- }
- }
- var lineAssem = null;
-
- function outputMutOp(op)
- {
- //print("outputMutOp: "+op.toSource());
- if (!lineAssem)
- {
- lineAssem = Changeset.mergingOpAssembler();
- }
- lineAssem.append(op);
- if (op.lines > 0)
- {
- Changeset.assert(op.lines == 1, "Can't have op.lines of ", op.lines, " in attribution lines");
- // ship it to the mut
- mut.insert(lineAssem.toString(), 1);
- lineAssem = null;
- }
- }
-
- var csOp = Changeset.newOp();
- var attOp = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp())
- {
- if ((!csOp.opcode) && csIter.hasNext())
- {
- csIter.next(csOp);
- }
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null));
- //print("csOp: "+csOp.toSource());
- if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext())))
- {
- break; // done
- }
- else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext())))
- {
- // skip multiple lines; this is what makes small changes not order of the document size
- mut.skipLines(csOp.lines);
- //print("skipped: "+csOp.lines);
- csOp.opcode = '';
- }
- else if (csOp.opcode == '+')
- {
- if (csOp.lines > 1)
- {
- var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
- Changeset.copyOp(csOp, opOut);
- csOp.chars -= firstLineLen;
- csOp.lines--;
- opOut.lines = 1;
- opOut.chars = firstLineLen;
- }
- else
- {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- outputMutOp(opOut);
- csBankIndex += opOut.chars;
- opOut.opcode = '';
- }
- else
- {
- if ((!attOp.opcode) && isNextMutOp())
- {
- nextMutOp(attOp);
- }
- //print("attOp: "+attOp.toSource());
- Changeset._slicerZipperFunc(attOp, csOp, opOut, pool);
- if (opOut.opcode)
- {
- outputMutOp(opOut);
- opOut.opcode = '';
- }
- }
- }
-
- Changeset.assert(!lineAssem, "line assembler not finished");
- mut.close();
-
- //dmesg("-> "+lines.toSource());
-};
-
-Changeset.joinAttributionLines = function(theAlines)
-{
- var assem = Changeset.mergingOpAssembler();
- for (var i = 0; i < theAlines.length; i++)
- {
- var aline = theAlines[i];
- var iter = Changeset.opIterator(aline);
- while (iter.hasNext())
- {
- assem.append(iter.next());
- }
- }
- return assem.toString();
-};
-
-Changeset.splitAttributionLines = function(attrOps, text)
-{
- var iter = Changeset.opIterator(attrOps);
- var assem = Changeset.mergingOpAssembler();
- var lines = [];
- var pos = 0;
-
- function appendOp(op)
- {
- assem.append(op);
- if (op.lines > 0)
- {
- lines.push(assem.toString());
- assem.clear();
- }
- pos += op.chars;
- }
-
- while (iter.hasNext())
- {
- var op = iter.next();
- var numChars = op.chars;
- var numLines = op.lines;
- while (numLines > 1)
- {
- var newlineEnd = text.indexOf('\n', pos) + 1;
- Changeset.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines");
- op.chars = newlineEnd - pos;
- op.lines = 1;
- appendOp(op);
- numChars -= op.chars;
- numLines -= op.lines;
- }
- if (numLines == 1)
- {
- op.chars = numChars;
- op.lines = 1;
- }
- appendOp(op);
- }
-
- return lines;
-};
-
-Changeset.splitTextLines = function(text)
-{
- return text.match(/[^\n]*(?:\n|[^\n]$)/g);
-};
-
-Changeset.compose = function(cs1, cs2, pool)
-{
- var unpacked1 = Changeset.unpack(cs1);
- var unpacked2 = Changeset.unpack(cs2);
- var len1 = unpacked1.oldLen;
- var len2 = unpacked1.newLen;
- Changeset.assert(len2 == unpacked2.oldLen, "mismatched composition");
- var len3 = unpacked2.newLen;
- var bankIter1 = Changeset.stringIterator(unpacked1.charBank);
- var bankIter2 = Changeset.stringIterator(unpacked2.charBank);
- var bankAssem = Changeset.stringAssembler();
-
- var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut)
- {
- //var debugBuilder = Changeset.stringAssembler();
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' / ');
- var op1code = op1.opcode;
- var op2code = op2.opcode;
- if (op1code == '+' && op2code == '-')
- {
- bankIter1.skip(Math.min(op1.chars, op2.chars));
- }
- Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- if (opOut.opcode == '+')
- {
- if (op2code == '+')
- {
- bankAssem.append(bankIter2.take(opOut.chars));
- }
- else
- {
- bankAssem.append(bankIter1.take(opOut.chars));
- }
- }
-
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' -> ');
- //debugBuilder.append(Changeset.opString(opOut));
- //print(debugBuilder.toString());
- });
-
- return Changeset.pack(len1, len3, newOps, bankAssem.toString());
-};
-
-Changeset.attributeTester = function(attribPair, pool)
-{
- // returns a function that tests if a string of attributes
- // (e.g. *3*4) contains a given attribute key,value that
- // is already present in the pool.
- if (!pool)
- {
- return never;
- }
- var attribNum = pool.putAttrib(attribPair, true);
- if (attribNum < 0)
- {
- return never;
- }
- else
- {
- var re = new RegExp('\\*' + Changeset.numToString(attribNum) + '(?!\\w)');
- return function(attribs)
- {
- return re.test(attribs);
- };
- }
-
- function never(attribs)
- {
- return false;
- }
-};
-
-Changeset.identity = function(N)
-{
- return Changeset.pack(N, N, "", "");
-};
-
-Changeset.makeSplice = function(oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool)
-{
- var oldLen = oldFullText.length;
-
- if (spliceStart >= oldLen)
- {
- spliceStart = oldLen - 1;
- }
- if (numRemoved > oldFullText.length - spliceStart - 1)
- {
- numRemoved = oldFullText.length - spliceStart - 1;
- }
- var oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
- var newLen = oldLen + newText.length - oldText.length;
-
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('=', oldFullText.substring(0, spliceStart));
- assem.appendOpWithText('-', oldText);
- assem.appendOpWithText('+', newText, optNewTextAPairs, pool);
- assem.endDocument();
- return Changeset.pack(oldLen, newLen, assem.toString(), newText);
-};
-
-Changeset.toSplices = function(cs)
-{
- // get a list of splices, [startChar, endChar, newText]
- var unpacked = Changeset.unpack(cs);
- var splices = [];
-
- var oldPos = 0;
- var iter = Changeset.opIterator(unpacked.ops);
- var charIter = Changeset.stringIterator(unpacked.charBank);
- var inSplice = false;
- while (iter.hasNext())
- {
- var op = iter.next();
- if (op.opcode == '=')
- {
- oldPos += op.chars;
- inSplice = false;
- }
- else
- {
- if (!inSplice)
- {
- splices.push([oldPos, oldPos, ""]);
- inSplice = true;
- }
- if (op.opcode == '-')
- {
- oldPos += op.chars;
- splices[splices.length - 1][1] += op.chars;
- }
- else if (op.opcode == '+')
- {
- splices[splices.length - 1][2] += charIter.take(op.chars);
- }
- }
- }
-
- return splices;
-};
-
-Changeset.characterRangeFollow = function(cs, startChar, endChar, insertionsAfter)
-{
- var newStartChar = startChar;
- var newEndChar = endChar;
- var splices = Changeset.toSplices(cs);
- var lengthChangeSoFar = 0;
- for (var i = 0; i < splices.length; i++)
- {
- var splice = splices[i];
- var spliceStart = splice[0] + lengthChangeSoFar;
- var spliceEnd = splice[1] + lengthChangeSoFar;
- var newTextLength = splice[2].length;
- var thisLengthChange = newTextLength - (spliceEnd - spliceStart);
-
- if (spliceStart <= newStartChar && spliceEnd >= newEndChar)
- {
- // splice fully replaces/deletes range
- // (also case that handles insertion at a collapsed selection)
- if (insertionsAfter)
- {
- newStartChar = newEndChar = spliceStart;
- }
- else
- {
- newStartChar = newEndChar = spliceStart + newTextLength;
- }
- }
- else if (spliceEnd <= newStartChar)
- {
- // splice is before range
- newStartChar += thisLengthChange;
- newEndChar += thisLengthChange;
- }
- else if (spliceStart >= newEndChar)
- {
- // splice is after range
- }
- else if (spliceStart >= newStartChar && spliceEnd <= newEndChar)
- {
- // splice is inside range
- newEndChar += thisLengthChange;
- }
- else if (spliceEnd < newEndChar)
- {
- // splice overlaps beginning of range
- newStartChar = spliceStart + newTextLength;
- newEndChar += thisLengthChange;
- }
- else
- {
- // splice overlaps end of range
- newEndChar = spliceStart;
- }
-
- lengthChangeSoFar += thisLengthChange;
- }
-
- return [newStartChar, newEndChar];
-};
-
-Changeset.moveOpsToNewPool = function(cs, oldPool, newPool)
-{
- // works on changeset or attribution string
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
- var fromDollar = cs.substring(dollarPos);
- // order of attribs stays the same
- return upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- var oldNum = Changeset.parseNum(a);
- var pair = oldPool.getAttrib(oldNum);
- var newNum = newPool.putAttrib(pair);
- return '*' + Changeset.numToString(newNum);
- }) + fromDollar;
-};
-
-Changeset.makeAttribution = function(text)
-{
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('+', text);
- return assem.toString();
-};
-
-// callable on a changeset, attribution string, or attribs property of an op
-Changeset.eachAttribNumber = function(cs, func)
-{
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- func(Changeset.parseNum(a));
- return '';
- });
-};
-
-// callable on a changeset, attribution string, or attribs property of an op,
-// though it may easily create adjacent ops that can be merged.
-Changeset.filterAttribNumbers = function(cs, filter)
-{
- return Changeset.mapAttribNumbers(cs, filter);
-};
-
-Changeset.mapAttribNumbers = function(cs, func)
-{
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function(s, a)
- {
- var n = func(Changeset.parseNum(a));
- if (n === true)
- {
- return s;
- }
- else if ((typeof n) === "number")
- {
- return '*' + Changeset.numToString(n);
- }
- else
- {
- return '';
- }
- });
-
- return newUpToDollar + cs.substring(dollarPos);
-};
-
-Changeset.makeAText = function(text, attribs)
-{
- return {
- text: text,
- attribs: (attribs || Changeset.makeAttribution(text))
- };
-};
-
-Changeset.applyToAText = function(cs, atext, pool)
-{
- return {
- text: Changeset.applyToText(cs, atext.text),
- attribs: Changeset.applyToAttribution(cs, atext.attribs, pool)
- };
-};
-
-Changeset.cloneAText = function(atext)
-{
- return {
- text: atext.text,
- attribs: atext.attribs
- };
-};
-
-Changeset.copyAText = function(atext1, atext2)
-{
- atext2.text = atext1.text;
- atext2.attribs = atext1.attribs;
-};
-
-Changeset.appendATextToAssembler = function(atext, assem)
-{
- // intentionally skips last newline char of atext
- var iter = Changeset.opIterator(atext.attribs);
- var op = Changeset.newOp();
- while (iter.hasNext())
- {
- iter.next(op);
- if (!iter.hasNext())
- {
- // last op, exclude final newline
- if (op.lines <= 1)
- {
- op.lines = 0;
- op.chars--;
- if (op.chars)
- {
- assem.append(op);
- }
- }
- else
- {
- var nextToLastNewlineEnd = atext.text.lastIndexOf('\n', atext.text.length - 2) + 1;
- var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
- op.lines--;
- op.chars -= (lastLineLength + 1);
- assem.append(op);
- op.lines = 0;
- op.chars = lastLineLength;
- if (op.chars)
- {
- assem.append(op);
- }
- }
- }
- else
- {
- assem.append(op);
- }
- }
-};
-
-Changeset.prepareForWire = function(cs, pool)
-{
- var newPool = new AttribPool();
- var newCs = Changeset.moveOpsToNewPool(cs, pool, newPool);
- return {
- translated: newCs,
- pool: newPool
- };
-};
-
-Changeset.isIdentity = function(cs)
-{
- var unpacked = Changeset.unpack(cs);
- return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
-};
-
-Changeset.opAttributeValue = function(op, key, pool)
-{
- return Changeset.attribsAttributeValue(op.attribs, key, pool);
-};
-
-Changeset.attribsAttributeValue = function(attribs, key, pool)
-{
- var value = '';
- if (attribs)
- {
- Changeset.eachAttribNumber(attribs, function(n)
- {
- if (pool.getAttribKey(n) == key)
- {
- value = pool.getAttribValue(n);
- }
- });
- }
- return value;
-};
-
-Changeset.builder = function(oldLen)
-{
- var assem = Changeset.smartOpAssembler();
- var o = Changeset.newOp();
- var charBank = Changeset.stringAssembler();
-
- var self = {
- // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case)
- keep: function(N, L, attribs, pool)
- {
- o.opcode = '=';
- o.attribs = (attribs && Changeset.makeAttribsString('=', attribs, pool)) || '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- keepText: function(text, attribs, pool)
- {
- assem.appendOpWithText('=', text, attribs, pool);
- return self;
- },
- insert: function(text, attribs, pool)
- {
- assem.appendOpWithText('+', text, attribs, pool);
- charBank.append(text);
- return self;
- },
- remove: function(N, L)
- {
- o.opcode = '-';
- o.attribs = '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- toString: function()
- {
- assem.endDocument();
- var newLen = oldLen + assem.getLengthChange();
- return Changeset.pack(oldLen, newLen, assem.toString(), charBank.toString());
- }
- };
-
- return self;
-};
-
-Changeset.makeAttribsString = function(opcode, attribs, pool)
-{
- // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work
- if (!attribs)
- {
- return '';
- }
- else if ((typeof attribs) == "string")
- {
- return attribs;
- }
- else if (pool && attribs && attribs.length)
- {
- if (attribs.length > 1)
- {
- attribs = attribs.slice();
- attribs.sort();
- }
- var result = [];
- for (var i = 0; i < attribs.length; i++)
- {
- var pair = attribs[i];
- if (opcode == '=' || (opcode == '+' && pair[1]))
- {
- result.push('*' + Changeset.numToString(pool.putAttrib(pair)));
- }
- }
- return result.join('');
- }
-};
-
-// like "substring" but on a single-line attribution string
-Changeset.subattribution = function(astr, start, optEnd)
-{
- var iter = Changeset.opIterator(astr, 0);
- var assem = Changeset.smartOpAssembler();
- var attOp = Changeset.newOp();
- var csOp = Changeset.newOp();
- var opOut = Changeset.newOp();
-
- function doCsOp()
- {
- if (csOp.chars)
- {
- while (csOp.opcode && (attOp.opcode || iter.hasNext()))
- {
- if (!attOp.opcode) iter.next(attOp);
-
- if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && attOp.lines > 0 && csOp.lines <= 0)
- {
- csOp.lines++;
- }
-
- Changeset._slicerZipperFunc(attOp, csOp, opOut, null);
- if (opOut.opcode)
- {
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- }
- }
-
- csOp.opcode = '-';
- csOp.chars = start;
-
- doCsOp();
-
- if (optEnd === undefined)
- {
- if (attOp.opcode)
- {
- assem.append(attOp);
- }
- while (iter.hasNext())
- {
- iter.next(attOp);
- assem.append(attOp);
- }
- }
- else
- {
- csOp.opcode = '=';
- csOp.chars = optEnd - start;
- doCsOp();
- }
-
- return assem.toString();
-};
-
-Changeset.inverse = function(cs, lines, alines, pool)
-{
- // lines and alines are what the changeset is meant to apply to.
- // They may be arrays or objects with .get(i) and .length methods.
- // They include final newlines on lines.
-
-
- function lines_get(idx)
- {
- if (lines.get)
- {
- return lines.get(idx);
- }
- else
- {
- return lines[idx];
- }
- }
-
- function lines_length()
- {
- if ((typeof lines.length) == "number")
- {
- return lines.length;
- }
- else
- {
- return lines.length();
- }
- }
-
- function alines_get(idx)
- {
- if (alines.get)
- {
- return alines.get(idx);
- }
- else
- {
- return alines[idx];
- }
- }
-
- function alines_length()
- {
- if ((typeof alines.length) == "number")
- {
- return alines.length;
- }
- else
- {
- return alines.length();
- }
- }
-
- var curLine = 0;
- var curChar = 0;
- var curLineOpIter = null;
- var curLineOpIterLine;
- var curLineNextOp = Changeset.newOp('+');
-
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var builder = Changeset.builder(unpacked.newLen);
-
- function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ )
- {
-
- if ((!curLineOpIter) || (curLineOpIterLine != curLine))
- {
- // create curLineOpIter and advance it to curChar
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- curLineOpIterLine = curLine;
- var indexIntoLine = 0;
- var done = false;
- while (!done)
- {
- curLineOpIter.next(curLineNextOp);
- if (indexIntoLine + curLineNextOp.chars >= curChar)
- {
- curLineNextOp.chars -= (curChar - indexIntoLine);
- done = true;
- }
- else
- {
- indexIntoLine += curLineNextOp.chars;
- }
- }
- }
-
- while (numChars > 0)
- {
- if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext()))
- {
- curLine++;
- curChar = 0;
- curLineOpIterLine = curLine;
- curLineNextOp.chars = 0;
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- }
- if (!curLineNextOp.chars)
- {
- curLineOpIter.next(curLineNextOp);
- }
- var charsToUse = Math.min(numChars, curLineNextOp.chars);
- func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
- numChars -= charsToUse;
- curLineNextOp.chars -= charsToUse;
- curChar += charsToUse;
- }
-
- if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext()))
- {
- curLine++;
- curChar = 0;
- }
- }
-
- function skip(N, L)
- {
- if (L)
- {
- curLine += L;
- curChar = 0;
- }
- else
- {
- if (curLineOpIter && curLineOpIterLine == curLine)
- {
- consumeAttribRuns(N, function()
- {});
- }
- else
- {
- curChar += N;
- }
- }
- }
-
- function nextText(numChars)
- {
- var len = 0;
- var assem = Changeset.stringAssembler();
- var firstString = lines_get(curLine).substring(curChar);
- len += firstString.length;
- assem.append(firstString);
-
- var lineNum = curLine + 1;
- while (len < numChars)
- {
- var nextString = lines_get(lineNum);
- len += nextString.length;
- assem.append(nextString);
- lineNum++;
- }
-
- return assem.toString().substring(0, numChars);
- }
-
- function cachedStrFunc(func)
- {
- var cache = {};
- return function(s)
- {
- if (!cache[s])
- {
- cache[s] = func(s);
- }
- return cache[s];
- };
- }
-
- var attribKeys = [];
- var attribValues = [];
- while (csIter.hasNext())
- {
- var csOp = csIter.next();
- if (csOp.opcode == '=')
- {
- if (csOp.attribs)
- {
- attribKeys.length = 0;
- attribValues.length = 0;
- Changeset.eachAttribNumber(csOp.attribs, function(n)
- {
- attribKeys.push(pool.getAttribKey(n));
- attribValues.push(pool.getAttribValue(n));
- });
- var undoBackToAttribs = cachedStrFunc(function(attribs)
- {
- var backAttribs = [];
- for (var i = 0; i < attribKeys.length; i++)
- {
- var appliedKey = attribKeys[i];
- var appliedValue = attribValues[i];
- var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, pool);
- if (appliedValue != oldValue)
- {
- backAttribs.push([appliedKey, oldValue]);
- }
- }
- return Changeset.makeAttribsString('=', backAttribs, pool);
- });
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
- {
- builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
- });
- }
- else
- {
- skip(csOp.chars, csOp.lines);
- builder.keep(csOp.chars, csOp.lines);
- }
- }
- else if (csOp.opcode == '+')
- {
- builder.remove(csOp.chars, csOp.lines);
- }
- else if (csOp.opcode == '-')
- {
- var textBank = nextText(csOp.chars);
- var textBankIndex = 0;
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
- {
- builder.insert(textBank.substr(textBankIndex, len), attribs);
- textBankIndex += len;
- });
- }
- }
-
- return Changeset.checkRep(builder.toString());
-};
-
-// %CLIENT FILE ENDS HERE%
-Changeset.follow = function(cs1, cs2, reverseInsertOrder, pool)
-{
- var unpacked1 = Changeset.unpack(cs1);
- var unpacked2 = Changeset.unpack(cs2);
- var len1 = unpacked1.oldLen;
- var len2 = unpacked2.oldLen;
- Changeset.assert(len1 == len2, "mismatched follow");
- var chars1 = Changeset.stringIterator(unpacked1.charBank);
- var chars2 = Changeset.stringIterator(unpacked2.charBank);
-
- var oldLen = unpacked1.newLen;
- var oldPos = 0;
- var newLen = 0;
-
- var hasInsertFirst = Changeset.attributeTester(['insertorder', 'first'], pool);
-
- var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut)
- {
- if (op1.opcode == '+' || op2.opcode == '+')
- {
- var whichToDo;
- if (op2.opcode != '+')
- {
- whichToDo = 1;
- }
- else if (op1.opcode != '+')
- {
- whichToDo = 2;
- }
- else
- {
- // both +
- var firstChar1 = chars1.peek(1);
- var firstChar2 = chars2.peek(1);
- var insertFirst1 = hasInsertFirst(op1.attribs);
- var insertFirst2 = hasInsertFirst(op2.attribs);
- if (insertFirst1 && !insertFirst2)
- {
- whichToDo = 1;
- }
- else if (insertFirst2 && !insertFirst1)
- {
- whichToDo = 2;
- }
- // insert string that doesn't start with a newline first so as not to break up lines
- else if (firstChar1 == '\n' && firstChar2 != '\n')
- {
- whichToDo = 2;
- }
- else if (firstChar1 != '\n' && firstChar2 == '\n')
- {
- whichToDo = 1;
- }
- // break symmetry:
- else if (reverseInsertOrder)
- {
- whichToDo = 2;
- }
- else
- {
- whichToDo = 1;
- }
- }
- if (whichToDo == 1)
- {
- chars1.skip(op1.chars);
- opOut.opcode = '=';
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- opOut.attribs = '';
- op1.opcode = '';
- }
- else
- {
- // whichToDo == 2
- chars2.skip(op2.chars);
- Changeset.copyOp(op2, opOut);
- op2.opcode = '';
- }
- }
- else if (op1.opcode == '-')
- {
- if (!op2.opcode)
- {
- op1.opcode = '';
- }
- else
- {
- if (op1.chars <= op2.chars)
- {
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (!op2.chars)
- {
- op2.opcode = '';
- }
- }
- else
- {
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- }
- }
- else if (op2.opcode == '-')
- {
- Changeset.copyOp(op2, opOut);
- if (!op1.opcode)
- {
- op2.opcode = '';
- }
- else if (op2.chars <= op1.chars)
- {
- // delete part or all of a keep
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- if (!op1.chars)
- {
- op1.opcode = '';
- }
- }
- else
- {
- // delete all of a keep, and keep going
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- op2.lines -= op1.lines;
- op2.chars -= op1.chars;
- op1.opcode = '';
- }
- }
- else if (!op1.opcode)
- {
- Changeset.copyOp(op2, opOut);
- op2.opcode = '';
- }
- else if (!op2.opcode)
- {
- Changeset.copyOp(op1, opOut);
- op1.opcode = '';
- }
- else
- {
- // both keeps
- opOut.opcode = '=';
- opOut.attribs = Changeset.followAttributes(op1.attribs, op2.attribs, pool);
- if (op1.chars <= op2.chars)
- {
- opOut.chars = op1.chars;
- opOut.lines = op1.lines;
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (!op2.chars)
- {
- op2.opcode = '';
- }
- }
- else
- {
- opOut.chars = op2.chars;
- opOut.lines = op2.lines;
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- }
- switch (opOut.opcode)
- {
- case '=':
- oldPos += opOut.chars;
- newLen += opOut.chars;
- break;
- case '-':
- oldPos += opOut.chars;
- break;
- case '+':
- newLen += opOut.chars;
- break;
- }
- });
- newLen += oldLen - oldPos;
-
- return Changeset.pack(oldLen, newLen, newOps, unpacked2.charBank);
-};
-
-Changeset.followAttributes = function(att1, att2, pool)
-{
- // The merge of two sets of attribute changes to the same text
- // takes the lexically-earlier value if there are two values
- // for the same key. Otherwise, all key/value changes from
- // both attribute sets are taken. This operation is the "follow",
- // so a set of changes is produced that can be applied to att1
- // to produce the merged set.
- if ((!att2) || (!pool)) return '';
- if (!att1) return att2;
- var atts = [];
- att2.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- atts.push(pool.getAttrib(Changeset.parseNum(a)));
- return '';
- });
- att1.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- var pair1 = pool.getAttrib(Changeset.parseNum(a));
- for (var i = 0; i < atts.length; i++)
- {
- var pair2 = atts[i];
- if (pair1[0] == pair2[0])
- {
- if (pair1[1] <= pair2[1])
- {
- // winner of merge is pair1, delete this attribute
- atts.splice(i, 1);
- }
- break;
- }
- }
- return '';
- });
- // we've only removed attributes, so they're already sorted
- var buf = Changeset.stringAssembler();
- for (var i = 0; i < atts.length; i++)
- {
- buf.append('*');
- buf.append(Changeset.numToString(pool.putAttrib(atts[i])));
- }
- return buf.toString();
-};
-
-exports.Changeset = Changeset;
-exports.AttribPool = AttribPool;
diff --git a/static/js/easysync2_client.js b/static/js/easysync2_client.js
deleted file mode 100644
index f4f3d08fb..000000000
--- a/static/js/easysync2_client.js
+++ /dev/null
@@ -1,2274 +0,0 @@
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/easysync2.js
-// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.easysync2
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-//var _opt = (this.Easysync2Support || null);
-var _opt = null; // disable optimization for now
-
-function AttribPool()
-{
- var p = {};
- p.numToAttrib = {}; // e.g. {0: ['foo','bar']}
- p.attribToNum = {}; // e.g. {'foo,bar': 0}
- p.nextNum = 0;
-
- p.putAttrib = function(attrib, dontAddIfAbsent)
- {
- var str = String(attrib);
- if (str in p.attribToNum)
- {
- return p.attribToNum[str];
- }
- if (dontAddIfAbsent)
- {
- return -1;
- }
- var num = p.nextNum++;
- p.attribToNum[str] = num;
- p.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
- return num;
- };
-
- p.getAttrib = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return pair;
- return [pair[0], pair[1]]; // return a mutable copy
- };
-
- p.getAttribKey = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return '';
- return pair[0];
- };
-
- p.getAttribValue = function(num)
- {
- var pair = p.numToAttrib[num];
- if (!pair) return '';
- return pair[1];
- };
-
- p.eachAttrib = function(func)
- {
- for (var n in p.numToAttrib)
- {
- var pair = p.numToAttrib[n];
- func(pair[0], pair[1]);
- }
- };
-
- p.toJsonable = function()
- {
- return {
- numToAttrib: p.numToAttrib,
- nextNum: p.nextNum
- };
- };
-
- p.fromJsonable = function(obj)
- {
- p.numToAttrib = obj.numToAttrib;
- p.nextNum = obj.nextNum;
- p.attribToNum = {};
- for (var n in p.numToAttrib)
- {
- p.attribToNum[String(p.numToAttrib[n])] = Number(n);
- }
- return p;
- };
-
- return p;
-}
-
-var Changeset = {};
-
-Changeset.error = function error(msg)
-{
- var e = new Error(msg);
- e.easysync = true;
- throw e;
-};
-Changeset.assert = function assert(b, msgParts)
-{
- if (!b)
- {
- var msg = Array.prototype.slice.call(arguments, 1).join('');
- Changeset.error("Changeset: " + msg);
- }
-};
-
-Changeset.parseNum = function(str)
-{
- return parseInt(str, 36);
-};
-Changeset.numToString = function(num)
-{
- return num.toString(36).toLowerCase();
-};
-Changeset.toBaseTen = function(cs)
-{
- var dollarIndex = cs.indexOf('$');
- var beforeDollar = cs.substring(0, dollarIndex);
- var fromDollar = cs.substring(dollarIndex);
- return beforeDollar.replace(/[0-9a-z]+/g, function(s)
- {
- return String(Changeset.parseNum(s));
- }) + fromDollar;
-};
-
-Changeset.oldLen = function(cs)
-{
- return Changeset.unpack(cs).oldLen;
-};
-Changeset.newLen = function(cs)
-{
- return Changeset.unpack(cs).newLen;
-};
-
-Changeset.opIterator = function(opsStr, optStartIndex)
-{
- //print(opsStr);
- var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
- var startIndex = (optStartIndex || 0);
- var curIndex = startIndex;
- var prevIndex = curIndex;
-
- function nextRegexMatch()
- {
- prevIndex = curIndex;
- var result;
- if (_opt)
- {
- result = _opt.nextOpInString(opsStr, curIndex);
- if (result)
- {
- if (result.opcode() == '?')
- {
- Changeset.error("Hit error opcode in op stream");
- }
- curIndex = result.lastIndex();
- }
- }
- else
- {
- regex.lastIndex = curIndex;
- result = regex.exec(opsStr);
- curIndex = regex.lastIndex;
- if (result[0] == '?')
- {
- Changeset.error("Hit error opcode in op stream");
- }
- }
- return result;
- }
- var regexResult = nextRegexMatch();
- var obj = Changeset.newOp();
-
- function next(optObj)
- {
- var op = (optObj || obj);
- if (_opt && regexResult)
- {
- op.attribs = regexResult.attribs();
- op.lines = regexResult.lines();
- op.chars = regexResult.chars();
- op.opcode = regexResult.opcode();
- regexResult = nextRegexMatch();
- }
- else if ((!_opt) && regexResult[0])
- {
- op.attribs = regexResult[1];
- op.lines = Changeset.parseNum(regexResult[2] || 0);
- op.opcode = regexResult[3];
- op.chars = Changeset.parseNum(regexResult[4]);
- regexResult = nextRegexMatch();
- }
- else
- {
- Changeset.clearOp(op);
- }
- return op;
- }
-
- function hasNext()
- {
- return !!(_opt ? regexResult : regexResult[0]);
- }
-
- function lastIndex()
- {
- return prevIndex;
- }
- return {
- next: next,
- hasNext: hasNext,
- lastIndex: lastIndex
- };
-};
-
-Changeset.clearOp = function(op)
-{
- op.opcode = '';
- op.chars = 0;
- op.lines = 0;
- op.attribs = '';
-};
-Changeset.newOp = function(optOpcode)
-{
- return {
- opcode: (optOpcode || ''),
- chars: 0,
- lines: 0,
- attribs: ''
- };
-};
-Changeset.cloneOp = function(op)
-{
- return {
- opcode: op.opcode,
- chars: op.chars,
- lines: op.lines,
- attribs: op.attribs
- };
-};
-Changeset.copyOp = function(op1, op2)
-{
- op2.opcode = op1.opcode;
- op2.chars = op1.chars;
- op2.lines = op1.lines;
- op2.attribs = op1.attribs;
-};
-Changeset.opString = function(op)
-{
- // just for debugging
- if (!op.opcode) return 'null';
- var assem = Changeset.opAssembler();
- assem.append(op);
- return assem.toString();
-};
-Changeset.stringOp = function(str)
-{
- // just for debugging
- return Changeset.opIterator(str).next();
-};
-
-Changeset.checkRep = function(cs)
-{
- // doesn't check things that require access to attrib pool (e.g. attribute order)
- // or original string (e.g. newline positions)
- var unpacked = Changeset.unpack(cs);
- var oldLen = unpacked.oldLen;
- var newLen = unpacked.newLen;
- var ops = unpacked.ops;
- var charBank = unpacked.charBank;
-
- var assem = Changeset.smartOpAssembler();
- var oldPos = 0;
- var calcNewLen = 0;
- var numInserted = 0;
- var iter = Changeset.opIterator(ops);
- while (iter.hasNext())
- {
- var o = iter.next();
- switch (o.opcode)
- {
- case '=':
- oldPos += o.chars;
- calcNewLen += o.chars;
- break;
- case '-':
- oldPos += o.chars;
- Changeset.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs);
- break;
- case '+':
- {
- calcNewLen += o.chars;
- numInserted += o.chars;
- Changeset.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs);
- break;
- }
- }
- assem.append(o);
- }
-
- calcNewLen += oldLen - oldPos;
- charBank = charBank.substring(0, numInserted);
- while (charBank.length < numInserted)
- {
- charBank += "?";
- }
-
- assem.endDocument();
- var normalized = Changeset.pack(oldLen, calcNewLen, assem.toString(), charBank);
- Changeset.assert(normalized == cs, normalized, ' != ', cs);
-
- return cs;
-}
-
-Changeset.smartOpAssembler = function()
-{
- // Like opAssembler but able to produce conforming changesets
- // from slightly looser input, at the cost of speed.
- // Specifically:
- // - merges consecutive operations that can be merged
- // - strips final "="
- // - ignores 0-length changes
- // - reorders consecutive + and - (which margingOpAssembler doesn't do)
- var minusAssem = Changeset.mergingOpAssembler();
- var plusAssem = Changeset.mergingOpAssembler();
- var keepAssem = Changeset.mergingOpAssembler();
- var assem = Changeset.stringAssembler();
- var lastOpcode = '';
- var lengthChange = 0;
-
- function flushKeeps()
- {
- assem.append(keepAssem.toString());
- keepAssem.clear();
- }
-
- function flushPlusMinus()
- {
- assem.append(minusAssem.toString());
- minusAssem.clear();
- assem.append(plusAssem.toString());
- plusAssem.clear();
- }
-
- function append(op)
- {
- if (!op.opcode) return;
- if (!op.chars) return;
-
- if (op.opcode == '-')
- {
- if (lastOpcode == '=')
- {
- flushKeeps();
- }
- minusAssem.append(op);
- lengthChange -= op.chars;
- }
- else if (op.opcode == '+')
- {
- if (lastOpcode == '=')
- {
- flushKeeps();
- }
- plusAssem.append(op);
- lengthChange += op.chars;
- }
- else if (op.opcode == '=')
- {
- if (lastOpcode != '=')
- {
- flushPlusMinus();
- }
- keepAssem.append(op);
- }
- lastOpcode = op.opcode;
- }
-
- function appendOpWithText(opcode, text, attribs, pool)
- {
- var op = Changeset.newOp(opcode);
- op.attribs = Changeset.makeAttribsString(opcode, attribs, pool);
- var lastNewlinePos = text.lastIndexOf('\n');
- if (lastNewlinePos < 0)
- {
- op.chars = text.length;
- op.lines = 0;
- append(op);
- }
- else
- {
- op.chars = lastNewlinePos + 1;
- op.lines = text.match(/\n/g).length;
- append(op);
- op.chars = text.length - (lastNewlinePos + 1);
- op.lines = 0;
- append(op);
- }
- }
-
- function toString()
- {
- flushPlusMinus();
- flushKeeps();
- return assem.toString();
- }
-
- function clear()
- {
- minusAssem.clear();
- plusAssem.clear();
- keepAssem.clear();
- assem.clear();
- lengthChange = 0;
- }
-
- function endDocument()
- {
- keepAssem.endDocument();
- }
-
- function getLengthChange()
- {
- return lengthChange;
- }
-
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument,
- appendOpWithText: appendOpWithText,
- getLengthChange: getLengthChange
- };
-};
-
-if (_opt)
-{
- Changeset.mergingOpAssembler = function()
- {
- var assem = _opt.mergingOpAssembler();
-
- function append(op)
- {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
-
- function toString()
- {
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- }
-
- function endDocument()
- {
- assem.endDocument();
- }
-
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument
- };
- };
-}
-else
-{
- Changeset.mergingOpAssembler = function()
- {
- // This assembler can be used in production; it efficiently
- // merges consecutive operations that are mergeable, ignores
- // no-ops, and drops final pure "keeps". It does not re-order
- // operations.
- var assem = Changeset.opAssembler();
- var bufOp = Changeset.newOp();
-
- // If we get, for example, insertions [xxx\n,yyy], those don't merge,
- // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
- // This variable stores the length of yyy and any other newline-less
- // ops immediately after it.
- var bufOpAdditionalCharsAfterNewline = 0;
-
- function flush(isEndDocument)
- {
- if (bufOp.opcode)
- {
- if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs)
- {
- // final merged keep, leave it implicit
- }
- else
- {
- assem.append(bufOp);
- if (bufOpAdditionalCharsAfterNewline)
- {
- bufOp.chars = bufOpAdditionalCharsAfterNewline;
- bufOp.lines = 0;
- assem.append(bufOp);
- bufOpAdditionalCharsAfterNewline = 0;
- }
- }
- bufOp.opcode = '';
- }
- }
-
- function append(op)
- {
- if (op.chars > 0)
- {
- if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs)
- {
- if (op.lines > 0)
- {
- // bufOp and additional chars are all mergeable into a multi-line op
- bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
- bufOp.lines += op.lines;
- bufOpAdditionalCharsAfterNewline = 0;
- }
- else if (bufOp.lines == 0)
- {
- // both bufOp and op are in-line
- bufOp.chars += op.chars;
- }
- else
- {
- // append in-line text to multi-line bufOp
- bufOpAdditionalCharsAfterNewline += op.chars;
- }
- }
- else
- {
- flush();
- Changeset.copyOp(op, bufOp);
- }
- }
- }
-
- function endDocument()
- {
- flush(true);
- }
-
- function toString()
- {
- flush();
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- Changeset.clearOp(bufOp);
- }
- return {
- append: append,
- toString: toString,
- clear: clear,
- endDocument: endDocument
- };
- };
-}
-
-if (_opt)
-{
- Changeset.opAssembler = function()
- {
- var assem = _opt.opAssembler();
- // this function allows op to be mutated later (doesn't keep a ref)
-
- function append(op)
- {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
-
- function toString()
- {
- return assem.toString();
- }
-
- function clear()
- {
- assem.clear();
- }
- return {
- append: append,
- toString: toString,
- clear: clear
- };
- };
-}
-else
-{
- Changeset.opAssembler = function()
- {
- var pieces = [];
- // this function allows op to be mutated later (doesn't keep a ref)
-
- function append(op)
- {
- pieces.push(op.attribs);
- if (op.lines)
- {
- pieces.push('|', Changeset.numToString(op.lines));
- }
- pieces.push(op.opcode);
- pieces.push(Changeset.numToString(op.chars));
- }
-
- function toString()
- {
- return pieces.join('');
- }
-
- function clear()
- {
- pieces.length = 0;
- }
- return {
- append: append,
- toString: toString,
- clear: clear
- };
- };
-}
-
-Changeset.stringIterator = function(str)
-{
- var curIndex = 0;
-
- function assertRemaining(n)
- {
- Changeset.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")");
- }
-
- function take(n)
- {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- curIndex += n;
- return s;
- }
-
- function peek(n)
- {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- return s;
- }
-
- function skip(n)
- {
- assertRemaining(n);
- curIndex += n;
- }
-
- function remaining()
- {
- return str.length - curIndex;
- }
- return {
- take: take,
- skip: skip,
- remaining: remaining,
- peek: peek
- };
-};
-
-Changeset.stringAssembler = function()
-{
- var pieces = [];
-
- function append(x)
- {
- pieces.push(String(x));
- }
-
- function toString()
- {
- return pieces.join('');
- }
- return {
- append: append,
- toString: toString
- };
-};
-
-// "lines" need not be an array as long as it supports certain calls (lines_foo inside).
-Changeset.textLinesMutator = function(lines)
-{
- // Mutates lines, an array of strings, in place.
- // Mutation operations have the same constraints as changeset operations
- // with respect to newlines, but not the other additional constraints
- // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline).
- // Can be used to mutate lists of strings where the last char of each string
- // is not actually a newline, but for the purposes of N and L values,
- // the caller should pretend it is, and for things to work right in that case, the input
- // to insert() should be a single line with no newlines.
- var curSplice = [0, 0];
- var inSplice = false;
- // position in document after curSplice is applied:
- var curLine = 0,
- curCol = 0;
- // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
- // curLine >= curSplice[0]
- // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
- // curCol == 0
-
- function lines_applySplice(s)
- {
- lines.splice.apply(lines, s);
- }
-
- function lines_toSource()
- {
- return lines.toSource();
- }
-
- function lines_get(idx)
- {
- if (lines.get)
- {
- return lines.get(idx);
- }
- else
- {
- return lines[idx];
- }
- }
- // can be unimplemented if removeLines's return value not needed
-
- function lines_slice(start, end)
- {
- if (lines.slice)
- {
- return lines.slice(start, end);
- }
- else
- {
- return [];
- }
- }
-
- function lines_length()
- {
- if ((typeof lines.length) == "number")
- {
- return lines.length;
- }
- else
- {
- return lines.length();
- }
- }
-
- function enterSplice()
- {
- curSplice[0] = curLine;
- curSplice[1] = 0;
- if (curCol > 0)
- {
- putCurLineInSplice();
- }
- inSplice = true;
- }
-
- function leaveSplice()
- {
- lines_applySplice(curSplice);
- curSplice.length = 2;
- curSplice[0] = curSplice[1] = 0;
- inSplice = false;
- }
-
- function isCurLineInSplice()
- {
- return (curLine - curSplice[0] < (curSplice.length - 2));
- }
-
- function debugPrint(typ)
- {
- print(typ + ": " + curSplice.toSource() + " / " + curLine + "," + curCol + " / " + lines_toSource());
- }
-
- function putCurLineInSplice()
- {
- if (!isCurLineInSplice())
- {
- curSplice.push(lines_get(curSplice[0] + curSplice[1]));
- curSplice[1]++;
- }
- return 2 + curLine - curSplice[0];
- }
-
- function skipLines(L, includeInSplice)
- {
- if (L)
- {
- if (includeInSplice)
- {
- if (!inSplice)
- {
- enterSplice();
- }
- for (var i = 0; i < L; i++)
- {
- curCol = 0;
- putCurLineInSplice();
- curLine++;
- }
- }
- else
- {
- if (inSplice)
- {
- if (L > 1)
- {
- leaveSplice();
- }
- else
- {
- putCurLineInSplice();
- }
- }
- curLine += L;
- curCol = 0;
- }
- //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
-/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
- print("BLAH");
- putCurLineInSplice();
- }*/
- // tests case foo in remove(), which isn't otherwise covered in current impl
- }
- //debugPrint("skip");
- }
-
- function skip(N, L, includeInSplice)
- {
- if (N)
- {
- if (L)
- {
- skipLines(L, includeInSplice);
- }
- else
- {
- if (includeInSplice && !inSplice)
- {
- enterSplice();
- }
- if (inSplice)
- {
- putCurLineInSplice();
- }
- curCol += N;
- //debugPrint("skip");
- }
- }
- }
-
- function removeLines(L)
- {
- var removed = '';
- if (L)
- {
- if (!inSplice)
- {
- enterSplice();
- }
-
- function nextKLinesText(k)
- {
- var m = curSplice[0] + curSplice[1];
- return lines_slice(m, m + k).join('');
- }
- if (isCurLineInSplice())
- {
- //print(curCol);
- if (curCol == 0)
- {
- removed = curSplice[curSplice.length - 1];
- // print("FOO"); // case foo
- curSplice.length--;
- removed += nextKLinesText(L - 1);
- curSplice[1] += L - 1;
- }
- else
- {
- removed = nextKLinesText(L - 1);
- curSplice[1] += L - 1;
- var sline = curSplice.length - 1;
- removed = curSplice[sline].substring(curCol) + removed;
- curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]);
- curSplice[1] += 1;
- }
- }
- else
- {
- removed = nextKLinesText(L);
- curSplice[1] += L;
- }
- //debugPrint("remove");
- }
- return removed;
- }
-
- function remove(N, L)
- {
- var removed = '';
- if (N)
- {
- if (L)
- {
- return removeLines(L);
- }
- else
- {
- if (!inSplice)
- {
- enterSplice();
- }
- var sline = putCurLineInSplice();
- removed = curSplice[sline].substring(curCol, curCol + N);
- curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N);
- //debugPrint("remove");
- }
- }
- return removed;
- }
-
- function insert(text, L)
- {
- if (text)
- {
- if (!inSplice)
- {
- enterSplice();
- }
- if (L)
- {
- var newLines = Changeset.splitTextLines(text);
- if (isCurLineInSplice())
- {
- //if (curCol == 0) {
- //curSplice.length--;
- //curSplice[1]--;
- //Array.prototype.push.apply(curSplice, newLines);
- //curLine += newLines.length;
- //}
- //else {
- var sline = curSplice.length - 1;
- var theLine = curSplice[sline];
- var lineCol = curCol;
- curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
- curLine++;
- newLines.splice(0, 1);
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- curSplice.push(theLine.substring(lineCol));
- curCol = 0;
- //}
- }
- else
- {
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- }
- }
- else
- {
- var sline = putCurLineInSplice();
- curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol);
- curCol += text.length;
- }
- //debugPrint("insert");
- }
- }
-
- function hasMore()
- {
- //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]);
- var docLines = lines_length();
- if (inSplice)
- {
- docLines += curSplice.length - 2 - curSplice[1];
- }
- return curLine < docLines;
- }
-
- function close()
- {
- if (inSplice)
- {
- leaveSplice();
- }
- //debugPrint("close");
- }
-
- var self = {
- skip: skip,
- remove: remove,
- insert: insert,
- close: close,
- hasMore: hasMore,
- removeLines: removeLines,
- skipLines: skipLines
- };
- return self;
-};
-
-Changeset.applyZip = function(in1, idx1, in2, idx2, func)
-{
- var iter1 = Changeset.opIterator(in1, idx1);
- var iter2 = Changeset.opIterator(in2, idx2);
- var assem = Changeset.smartOpAssembler();
- var op1 = Changeset.newOp();
- var op2 = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext())
- {
- if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
- if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
- func(op1, op2, opOut);
- if (opOut.opcode)
- {
- //print(opOut.toSource());
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- assem.endDocument();
- return assem.toString();
-};
-
-Changeset.unpack = function(cs)
-{
- var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
- var headerMatch = headerRegex.exec(cs);
- if ((!headerMatch) || (!headerMatch[0]))
- {
- Changeset.error("Not a changeset: " + cs);
- }
- var oldLen = Changeset.parseNum(headerMatch[1]);
- var changeSign = (headerMatch[2] == '>') ? 1 : -1;
- var changeMag = Changeset.parseNum(headerMatch[3]);
- var newLen = oldLen + changeSign * changeMag;
- var opsStart = headerMatch[0].length;
- var opsEnd = cs.indexOf("$");
- if (opsEnd < 0) opsEnd = cs.length;
- return {
- oldLen: oldLen,
- newLen: newLen,
- ops: cs.substring(opsStart, opsEnd),
- charBank: cs.substring(opsEnd + 1)
- };
-};
-
-Changeset.pack = function(oldLen, newLen, opsStr, bank)
-{
- var lenDiff = newLen - oldLen;
- var lenDiffStr = (lenDiff >= 0 ? '>' + Changeset.numToString(lenDiff) : '<' + Changeset.numToString(-lenDiff));
- var a = [];
- a.push('Z:', Changeset.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
- return a.join('');
-};
-
-Changeset.applyToText = function(cs, str)
-{
- var unpacked = Changeset.unpack(cs);
- Changeset.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var strIter = Changeset.stringIterator(str);
- var assem = Changeset.stringAssembler();
- while (csIter.hasNext())
- {
- var op = csIter.next();
- switch (op.opcode)
- {
- case '+':
- assem.append(bankIter.take(op.chars));
- break;
- case '-':
- strIter.skip(op.chars);
- break;
- case '=':
- assem.append(strIter.take(op.chars));
- break;
- }
- }
- assem.append(strIter.take(strIter.remaining()));
- return assem.toString();
-};
-
-Changeset.mutateTextLines = function(cs, lines)
-{
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var mut = Changeset.textLinesMutator(lines);
- while (csIter.hasNext())
- {
- var op = csIter.next();
- switch (op.opcode)
- {
- case '+':
- mut.insert(bankIter.take(op.chars), op.lines);
- break;
- case '-':
- mut.remove(op.chars, op.lines);
- break;
- case '=':
- mut.skip(op.chars, op.lines, ( !! op.attribs));
- break;
- }
- }
- mut.close();
-};
-
-Changeset.composeAttributes = function(att1, att2, resultIsMutation, pool)
-{
- // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
- // Sometimes attribute (key,value) pairs are treated as attribute presence
- // information, while other times they are treated as operations that
- // mutate a set of attributes, and this affects whether an empty value
- // is a deletion or a change.
- // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
- // ([], [(bold, )], true) -> [(bold, )]
- // ([], [(bold, )], false) -> []
- // ([], [(bold, true)], true) -> [(bold, true)]
- // ([], [(bold, true)], false) -> [(bold, true)]
- // ([(bold, true)], [(bold, )], true) -> [(bold, )]
- // ([(bold, true)], [(bold, )], false) -> []
- // pool can be null if att2 has no attributes.
- if ((!att1) && resultIsMutation)
- {
- // In the case of a mutation (i.e. composing two changesets),
- // an att2 composed with an empy att1 is just att2. If att1
- // is part of an attribution string, then att2 may remove
- // attributes that are already gone, so don't do this optimization.
- return att2;
- }
- if (!att2) return att1;
- var atts = [];
- att1.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- atts.push(pool.getAttrib(Changeset.parseNum(a)));
- return '';
- });
- att2.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- var pair = pool.getAttrib(Changeset.parseNum(a));
- var found = false;
- for (var i = 0; i < atts.length; i++)
- {
- var oldPair = atts[i];
- if (oldPair[0] == pair[0])
- {
- if (pair[1] || resultIsMutation)
- {
- oldPair[1] = pair[1];
- }
- else
- {
- atts.splice(i, 1);
- }
- found = true;
- break;
- }
- }
- if ((!found) && (pair[1] || resultIsMutation))
- {
- atts.push(pair);
- }
- return '';
- });
- atts.sort();
- var buf = Changeset.stringAssembler();
- for (var i = 0; i < atts.length; i++)
- {
- buf.append('*');
- buf.append(Changeset.numToString(pool.putAttrib(atts[i])));
- }
- //print(att1+" / "+att2+" / "+buf.toString());
- return buf.toString();
-};
-
-Changeset._slicerZipperFunc = function(attOp, csOp, opOut, pool)
-{
- // attOp is the op from the sequence that is being operated on, either an
- // attribution string or the earlier of two changesets being composed.
- // pool can be null if definitely not needed.
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- if (attOp.opcode == '-')
- {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- }
- else if (!attOp.opcode)
- {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- else
- {
- switch (csOp.opcode)
- {
- case '-':
- {
- if (csOp.chars <= attOp.chars)
- {
- // delete or delete part
- if (attOp.opcode == '=')
- {
- opOut.opcode = '-';
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = '';
- }
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- csOp.opcode = '';
- if (!attOp.chars)
- {
- attOp.opcode = '';
- }
- }
- else
- {
- // delete and keep going
- if (attOp.opcode == '=')
- {
- opOut.opcode = '-';
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = '';
- }
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- attOp.opcode = '';
- }
- break;
- }
- case '+':
- {
- // insert
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- break;
- }
- case '=':
- {
- if (csOp.chars <= attOp.chars)
- {
- // keep or keep part
- opOut.opcode = attOp.opcode;
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
- csOp.opcode = '';
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- if (!attOp.chars)
- {
- attOp.opcode = '';
- }
- }
- else
- {
- // keep and keep going
- opOut.opcode = attOp.opcode;
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
- attOp.opcode = '';
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- }
- break;
- }
- case '':
- {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- break;
- }
- }
- }
-};
-
-Changeset.applyToAttribution = function(cs, astr, pool)
-{
- var unpacked = Changeset.unpack(cs);
-
- return Changeset.applyZip(astr, 0, unpacked.ops, 0, function(op1, op2, opOut)
- {
- return Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- });
-};
-
-/*Changeset.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) {
- var iter = Changeset.opIterator(opsStr, optStartIndex);
- var bankIndex = 0;
-
-};*/
-
-Changeset.mutateAttributionLines = function(cs, lines, pool)
-{
- //dmesg(cs);
- //dmesg(lines.toSource()+" ->");
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var csBank = unpacked.charBank;
- var csBankIndex = 0;
- // treat the attribution lines as text lines, mutating a line at a time
- var mut = Changeset.textLinesMutator(lines);
-
- var lineIter = null;
-
- function isNextMutOp()
- {
- return (lineIter && lineIter.hasNext()) || mut.hasMore();
- }
-
- function nextMutOp(destOp)
- {
- if ((!(lineIter && lineIter.hasNext())) && mut.hasMore())
- {
- var line = mut.removeLines(1);
- lineIter = Changeset.opIterator(line);
- }
- if (lineIter && lineIter.hasNext())
- {
- lineIter.next(destOp);
- }
- else
- {
- destOp.opcode = '';
- }
- }
- var lineAssem = null;
-
- function outputMutOp(op)
- {
- //print("outputMutOp: "+op.toSource());
- if (!lineAssem)
- {
- lineAssem = Changeset.mergingOpAssembler();
- }
- lineAssem.append(op);
- if (op.lines > 0)
- {
- Changeset.assert(op.lines == 1, "Can't have op.lines of ", op.lines, " in attribution lines");
- // ship it to the mut
- mut.insert(lineAssem.toString(), 1);
- lineAssem = null;
- }
- }
-
- var csOp = Changeset.newOp();
- var attOp = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp())
- {
- if ((!csOp.opcode) && csIter.hasNext())
- {
- csIter.next(csOp);
- }
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null));
- //print("csOp: "+csOp.toSource());
- if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext())))
- {
- break; // done
- }
- else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext())))
- {
- // skip multiple lines; this is what makes small changes not order of the document size
- mut.skipLines(csOp.lines);
- //print("skipped: "+csOp.lines);
- csOp.opcode = '';
- }
- else if (csOp.opcode == '+')
- {
- if (csOp.lines > 1)
- {
- var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
- Changeset.copyOp(csOp, opOut);
- csOp.chars -= firstLineLen;
- csOp.lines--;
- opOut.lines = 1;
- opOut.chars = firstLineLen;
- }
- else
- {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- outputMutOp(opOut);
- csBankIndex += opOut.chars;
- opOut.opcode = '';
- }
- else
- {
- if ((!attOp.opcode) && isNextMutOp())
- {
- nextMutOp(attOp);
- }
- //print("attOp: "+attOp.toSource());
- Changeset._slicerZipperFunc(attOp, csOp, opOut, pool);
- if (opOut.opcode)
- {
- outputMutOp(opOut);
- opOut.opcode = '';
- }
- }
- }
-
- Changeset.assert(!lineAssem, "line assembler not finished");
- mut.close();
-
- //dmesg("-> "+lines.toSource());
-};
-
-Changeset.joinAttributionLines = function(theAlines)
-{
- var assem = Changeset.mergingOpAssembler();
- for (var i = 0; i < theAlines.length; i++)
- {
- var aline = theAlines[i];
- var iter = Changeset.opIterator(aline);
- while (iter.hasNext())
- {
- assem.append(iter.next());
- }
- }
- return assem.toString();
-};
-
-Changeset.splitAttributionLines = function(attrOps, text)
-{
- var iter = Changeset.opIterator(attrOps);
- var assem = Changeset.mergingOpAssembler();
- var lines = [];
- var pos = 0;
-
- function appendOp(op)
- {
- assem.append(op);
- if (op.lines > 0)
- {
- lines.push(assem.toString());
- assem.clear();
- }
- pos += op.chars;
- }
-
- while (iter.hasNext())
- {
- var op = iter.next();
- var numChars = op.chars;
- var numLines = op.lines;
- while (numLines > 1)
- {
- var newlineEnd = text.indexOf('\n', pos) + 1;
- Changeset.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines");
- op.chars = newlineEnd - pos;
- op.lines = 1;
- appendOp(op);
- numChars -= op.chars;
- numLines -= op.lines;
- }
- if (numLines == 1)
- {
- op.chars = numChars;
- op.lines = 1;
- }
- appendOp(op);
- }
-
- return lines;
-};
-
-Changeset.splitTextLines = function(text)
-{
- return text.match(/[^\n]*(?:\n|[^\n]$)/g);
-};
-
-Changeset.compose = function(cs1, cs2, pool)
-{
- var unpacked1 = Changeset.unpack(cs1);
- var unpacked2 = Changeset.unpack(cs2);
- var len1 = unpacked1.oldLen;
- var len2 = unpacked1.newLen;
- Changeset.assert(len2 == unpacked2.oldLen, "mismatched composition");
- var len3 = unpacked2.newLen;
- var bankIter1 = Changeset.stringIterator(unpacked1.charBank);
- var bankIter2 = Changeset.stringIterator(unpacked2.charBank);
- var bankAssem = Changeset.stringAssembler();
-
- var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut)
- {
- //var debugBuilder = Changeset.stringAssembler();
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' / ');
- var op1code = op1.opcode;
- var op2code = op2.opcode;
- if (op1code == '+' && op2code == '-')
- {
- bankIter1.skip(Math.min(op1.chars, op2.chars));
- }
- Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- if (opOut.opcode == '+')
- {
- if (op2code == '+')
- {
- bankAssem.append(bankIter2.take(opOut.chars));
- }
- else
- {
- bankAssem.append(bankIter1.take(opOut.chars));
- }
- }
-
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' -> ');
- //debugBuilder.append(Changeset.opString(opOut));
- //print(debugBuilder.toString());
- });
-
- return Changeset.pack(len1, len3, newOps, bankAssem.toString());
-};
-
-Changeset.attributeTester = function(attribPair, pool)
-{
- // returns a function that tests if a string of attributes
- // (e.g. *3*4) contains a given attribute key,value that
- // is already present in the pool.
- if (!pool)
- {
- return never;
- }
- var attribNum = pool.putAttrib(attribPair, true);
- if (attribNum < 0)
- {
- return never;
- }
- else
- {
- var re = new RegExp('\\*' + Changeset.numToString(attribNum) + '(?!\\w)');
- return function(attribs)
- {
- return re.test(attribs);
- };
- }
-
- function never(attribs)
- {
- return false;
- }
-};
-
-Changeset.identity = function(N)
-{
- return Changeset.pack(N, N, "", "");
-};
-
-Changeset.makeSplice = function(oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool)
-{
- var oldLen = oldFullText.length;
-
- if (spliceStart >= oldLen)
- {
- spliceStart = oldLen - 1;
- }
- if (numRemoved > oldFullText.length - spliceStart - 1)
- {
- numRemoved = oldFullText.length - spliceStart - 1;
- }
- var oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
- var newLen = oldLen + newText.length - oldText.length;
-
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('=', oldFullText.substring(0, spliceStart));
- assem.appendOpWithText('-', oldText);
- assem.appendOpWithText('+', newText, optNewTextAPairs, pool);
- assem.endDocument();
- return Changeset.pack(oldLen, newLen, assem.toString(), newText);
-};
-
-Changeset.toSplices = function(cs)
-{
- // get a list of splices, [startChar, endChar, newText]
- var unpacked = Changeset.unpack(cs);
- var splices = [];
-
- var oldPos = 0;
- var iter = Changeset.opIterator(unpacked.ops);
- var charIter = Changeset.stringIterator(unpacked.charBank);
- var inSplice = false;
- while (iter.hasNext())
- {
- var op = iter.next();
- if (op.opcode == '=')
- {
- oldPos += op.chars;
- inSplice = false;
- }
- else
- {
- if (!inSplice)
- {
- splices.push([oldPos, oldPos, ""]);
- inSplice = true;
- }
- if (op.opcode == '-')
- {
- oldPos += op.chars;
- splices[splices.length - 1][1] += op.chars;
- }
- else if (op.opcode == '+')
- {
- splices[splices.length - 1][2] += charIter.take(op.chars);
- }
- }
- }
-
- return splices;
-};
-
-Changeset.characterRangeFollow = function(cs, startChar, endChar, insertionsAfter)
-{
- var newStartChar = startChar;
- var newEndChar = endChar;
- var splices = Changeset.toSplices(cs);
- var lengthChangeSoFar = 0;
- for (var i = 0; i < splices.length; i++)
- {
- var splice = splices[i];
- var spliceStart = splice[0] + lengthChangeSoFar;
- var spliceEnd = splice[1] + lengthChangeSoFar;
- var newTextLength = splice[2].length;
- var thisLengthChange = newTextLength - (spliceEnd - spliceStart);
-
- if (spliceStart <= newStartChar && spliceEnd >= newEndChar)
- {
- // splice fully replaces/deletes range
- // (also case that handles insertion at a collapsed selection)
- if (insertionsAfter)
- {
- newStartChar = newEndChar = spliceStart;
- }
- else
- {
- newStartChar = newEndChar = spliceStart + newTextLength;
- }
- }
- else if (spliceEnd <= newStartChar)
- {
- // splice is before range
- newStartChar += thisLengthChange;
- newEndChar += thisLengthChange;
- }
- else if (spliceStart >= newEndChar)
- {
- // splice is after range
- }
- else if (spliceStart >= newStartChar && spliceEnd <= newEndChar)
- {
- // splice is inside range
- newEndChar += thisLengthChange;
- }
- else if (spliceEnd < newEndChar)
- {
- // splice overlaps beginning of range
- newStartChar = spliceStart + newTextLength;
- newEndChar += thisLengthChange;
- }
- else
- {
- // splice overlaps end of range
- newEndChar = spliceStart;
- }
-
- lengthChangeSoFar += thisLengthChange;
- }
-
- return [newStartChar, newEndChar];
-};
-
-Changeset.moveOpsToNewPool = function(cs, oldPool, newPool)
-{
- // works on changeset or attribution string
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
- var fromDollar = cs.substring(dollarPos);
- // order of attribs stays the same
- return upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- var oldNum = Changeset.parseNum(a);
- var pair = oldPool.getAttrib(oldNum);
- var newNum = newPool.putAttrib(pair);
- return '*' + Changeset.numToString(newNum);
- }) + fromDollar;
-};
-
-Changeset.makeAttribution = function(text)
-{
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('+', text);
- return assem.toString();
-};
-
-// callable on a changeset, attribution string, or attribs property of an op
-Changeset.eachAttribNumber = function(cs, func)
-{
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
- {
- func(Changeset.parseNum(a));
- return '';
- });
-};
-
-// callable on a changeset, attribution string, or attribs property of an op,
-// though it may easily create adjacent ops that can be merged.
-Changeset.filterAttribNumbers = function(cs, filter)
-{
- return Changeset.mapAttribNumbers(cs, filter);
-};
-
-Changeset.mapAttribNumbers = function(cs, func)
-{
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0)
- {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function(s, a)
- {
- var n = func(Changeset.parseNum(a));
- if (n === true)
- {
- return s;
- }
- else if ((typeof n) === "number")
- {
- return '*' + Changeset.numToString(n);
- }
- else
- {
- return '';
- }
- });
-
- return newUpToDollar + cs.substring(dollarPos);
-};
-
-Changeset.makeAText = function(text, attribs)
-{
- return {
- text: text,
- attribs: (attribs || Changeset.makeAttribution(text))
- };
-};
-
-Changeset.applyToAText = function(cs, atext, pool)
-{
- return {
- text: Changeset.applyToText(cs, atext.text),
- attribs: Changeset.applyToAttribution(cs, atext.attribs, pool)
- };
-};
-
-Changeset.cloneAText = function(atext)
-{
- return {
- text: atext.text,
- attribs: atext.attribs
- };
-};
-
-Changeset.copyAText = function(atext1, atext2)
-{
- atext2.text = atext1.text;
- atext2.attribs = atext1.attribs;
-};
-
-Changeset.appendATextToAssembler = function(atext, assem)
-{
- // intentionally skips last newline char of atext
- var iter = Changeset.opIterator(atext.attribs);
- var op = Changeset.newOp();
- while (iter.hasNext())
- {
- iter.next(op);
- if (!iter.hasNext())
- {
- // last op, exclude final newline
- if (op.lines <= 1)
- {
- op.lines = 0;
- op.chars--;
- if (op.chars)
- {
- assem.append(op);
- }
- }
- else
- {
- var nextToLastNewlineEnd = atext.text.lastIndexOf('\n', atext.text.length - 2) + 1;
- var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
- op.lines--;
- op.chars -= (lastLineLength + 1);
- assem.append(op);
- op.lines = 0;
- op.chars = lastLineLength;
- if (op.chars)
- {
- assem.append(op);
- }
- }
- }
- else
- {
- assem.append(op);
- }
- }
-};
-
-Changeset.prepareForWire = function(cs, pool)
-{
- var newPool = new AttribPool();
- var newCs = Changeset.moveOpsToNewPool(cs, pool, newPool);
- return {
- translated: newCs,
- pool: newPool
- };
-};
-
-Changeset.isIdentity = function(cs)
-{
- var unpacked = Changeset.unpack(cs);
- return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
-};
-
-Changeset.opAttributeValue = function(op, key, pool)
-{
- return Changeset.attribsAttributeValue(op.attribs, key, pool);
-};
-
-Changeset.attribsAttributeValue = function(attribs, key, pool)
-{
- var value = '';
- if (attribs)
- {
- Changeset.eachAttribNumber(attribs, function(n)
- {
- if (pool.getAttribKey(n) == key)
- {
- value = pool.getAttribValue(n);
- }
- });
- }
- return value;
-};
-
-Changeset.builder = function(oldLen)
-{
- var assem = Changeset.smartOpAssembler();
- var o = Changeset.newOp();
- var charBank = Changeset.stringAssembler();
-
- var self = {
- // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case)
- keep: function(N, L, attribs, pool)
- {
- o.opcode = '=';
- o.attribs = (attribs && Changeset.makeAttribsString('=', attribs, pool)) || '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- keepText: function(text, attribs, pool)
- {
- assem.appendOpWithText('=', text, attribs, pool);
- return self;
- },
- insert: function(text, attribs, pool)
- {
- assem.appendOpWithText('+', text, attribs, pool);
- charBank.append(text);
- return self;
- },
- remove: function(N, L)
- {
- o.opcode = '-';
- o.attribs = '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- toString: function()
- {
- assem.endDocument();
- var newLen = oldLen + assem.getLengthChange();
- return Changeset.pack(oldLen, newLen, assem.toString(), charBank.toString());
- }
- };
-
- return self;
-};
-
-Changeset.makeAttribsString = function(opcode, attribs, pool)
-{
- // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work
- if (!attribs)
- {
- return '';
- }
- else if ((typeof attribs) == "string")
- {
- return attribs;
- }
- else if (pool && attribs && attribs.length)
- {
- if (attribs.length > 1)
- {
- attribs = attribs.slice();
- attribs.sort();
- }
- var result = [];
- for (var i = 0; i < attribs.length; i++)
- {
- var pair = attribs[i];
- if (opcode == '=' || (opcode == '+' && pair[1]))
- {
- result.push('*' + Changeset.numToString(pool.putAttrib(pair)));
- }
- }
- return result.join('');
- }
-};
-
-// like "substring" but on a single-line attribution string
-Changeset.subattribution = function(astr, start, optEnd)
-{
- var iter = Changeset.opIterator(astr, 0);
- var assem = Changeset.smartOpAssembler();
- var attOp = Changeset.newOp();
- var csOp = Changeset.newOp();
- var opOut = Changeset.newOp();
-
- function doCsOp()
- {
- if (csOp.chars)
- {
- while (csOp.opcode && (attOp.opcode || iter.hasNext()))
- {
- if (!attOp.opcode) iter.next(attOp);
-
- if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && attOp.lines > 0 && csOp.lines <= 0)
- {
- csOp.lines++;
- }
-
- Changeset._slicerZipperFunc(attOp, csOp, opOut, null);
- if (opOut.opcode)
- {
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- }
- }
-
- csOp.opcode = '-';
- csOp.chars = start;
-
- doCsOp();
-
- if (optEnd === undefined)
- {
- if (attOp.opcode)
- {
- assem.append(attOp);
- }
- while (iter.hasNext())
- {
- iter.next(attOp);
- assem.append(attOp);
- }
- }
- else
- {
- csOp.opcode = '=';
- csOp.chars = optEnd - start;
- doCsOp();
- }
-
- return assem.toString();
-};
-
-Changeset.inverse = function(cs, lines, alines, pool)
-{
- // lines and alines are what the changeset is meant to apply to.
- // They may be arrays or objects with .get(i) and .length methods.
- // They include final newlines on lines.
-
- function lines_get(idx)
- {
- if (lines.get)
- {
- return lines.get(idx);
- }
- else
- {
- return lines[idx];
- }
- }
-
- function lines_length()
- {
- if ((typeof lines.length) == "number")
- {
- return lines.length;
- }
- else
- {
- return lines.length();
- }
- }
-
- function alines_get(idx)
- {
- if (alines.get)
- {
- return alines.get(idx);
- }
- else
- {
- return alines[idx];
- }
- }
-
- function alines_length()
- {
- if ((typeof alines.length) == "number")
- {
- return alines.length;
- }
- else
- {
- return alines.length();
- }
- }
-
- var curLine = 0;
- var curChar = 0;
- var curLineOpIter = null;
- var curLineOpIterLine;
- var curLineNextOp = Changeset.newOp('+');
-
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var builder = Changeset.builder(unpacked.newLen);
-
- function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ )
- {
-
- if ((!curLineOpIter) || (curLineOpIterLine != curLine))
- {
- // create curLineOpIter and advance it to curChar
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- curLineOpIterLine = curLine;
- var indexIntoLine = 0;
- var done = false;
- while (!done)
- {
- curLineOpIter.next(curLineNextOp);
- if (indexIntoLine + curLineNextOp.chars >= curChar)
- {
- curLineNextOp.chars -= (curChar - indexIntoLine);
- done = true;
- }
- else
- {
- indexIntoLine += curLineNextOp.chars;
- }
- }
- }
-
- while (numChars > 0)
- {
- if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext()))
- {
- curLine++;
- curChar = 0;
- curLineOpIterLine = curLine;
- curLineNextOp.chars = 0;
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- }
- if (!curLineNextOp.chars)
- {
- curLineOpIter.next(curLineNextOp);
- }
- var charsToUse = Math.min(numChars, curLineNextOp.chars);
- func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
- numChars -= charsToUse;
- curLineNextOp.chars -= charsToUse;
- curChar += charsToUse;
- }
-
- if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext()))
- {
- curLine++;
- curChar = 0;
- }
- }
-
- function skip(N, L)
- {
- if (L)
- {
- curLine += L;
- curChar = 0;
- }
- else
- {
- if (curLineOpIter && curLineOpIterLine == curLine)
- {
- consumeAttribRuns(N, function()
- {});
- }
- else
- {
- curChar += N;
- }
- }
- }
-
- function nextText(numChars)
- {
- var len = 0;
- var assem = Changeset.stringAssembler();
- var firstString = lines_get(curLine).substring(curChar);
- len += firstString.length;
- assem.append(firstString);
-
- var lineNum = curLine + 1;
- while (len < numChars)
- {
- var nextString = lines_get(lineNum);
- len += nextString.length;
- assem.append(nextString);
- lineNum++;
- }
-
- return assem.toString().substring(0, numChars);
- }
-
- function cachedStrFunc(func)
- {
- var cache = {};
- return function(s)
- {
- if (!cache[s])
- {
- cache[s] = func(s);
- }
- return cache[s];
- };
- }
-
- var attribKeys = [];
- var attribValues = [];
- while (csIter.hasNext())
- {
- var csOp = csIter.next();
- if (csOp.opcode == '=')
- {
- if (csOp.attribs)
- {
- attribKeys.length = 0;
- attribValues.length = 0;
- Changeset.eachAttribNumber(csOp.attribs, function(n)
- {
- attribKeys.push(pool.getAttribKey(n));
- attribValues.push(pool.getAttribValue(n));
- });
- var undoBackToAttribs = cachedStrFunc(function(attribs)
- {
- var backAttribs = [];
- for (var i = 0; i < attribKeys.length; i++)
- {
- var appliedKey = attribKeys[i];
- var appliedValue = attribValues[i];
- var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, pool);
- if (appliedValue != oldValue)
- {
- backAttribs.push([appliedKey, oldValue]);
- }
- }
- return Changeset.makeAttribsString('=', backAttribs, pool);
- });
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
- {
- builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
- });
- }
- else
- {
- skip(csOp.chars, csOp.lines);
- builder.keep(csOp.chars, csOp.lines);
- }
- }
- else if (csOp.opcode == '+')
- {
- builder.remove(csOp.chars, csOp.lines);
- }
- else if (csOp.opcode == '-')
- {
- var textBank = nextText(csOp.chars);
- var textBankIndex = 0;
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
- {
- builder.insert(textBank.substr(textBankIndex, len), attribs);
- textBankIndex += len;
- });
- }
- }
-
- return Changeset.checkRep(builder.toString());
-};
-
-exports.Changeset = Changeset;
-exports.AttribPool = AttribPool;
diff --git a/static/js/linestylefilter.js b/static/js/linestylefilter.js
index d0b5bc6e8..4b4557247 100644
--- a/static/js/linestylefilter.js
+++ b/static/js/linestylefilter.js
@@ -28,7 +28,7 @@
// requires: plugins
// requires: undefined
-var Changeset = require('/easysync2').Changeset
+var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
diff --git a/static/js/linestylefilter_client.js b/static/js/linestylefilter_client.js
deleted file mode 100644
index f057e21a2..000000000
--- a/static/js/linestylefilter_client.js
+++ /dev/null
@@ -1,343 +0,0 @@
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/linestylefilter.js
-// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// requires: easysync2.Changeset
-// requires: top
-// requires: plugins
-// requires: undefined
-
-var Changeset = require('/easysync2_client').Changeset
-var plugins = require('/plugins').plugins;
-var map = require('/ace2_common').map;
-
-var linestylefilter = {};
-
-linestylefilter.ATTRIB_CLASSES = {
- 'bold': 'tag:b',
- 'italic': 'tag:i',
- 'underline': 'tag:u',
- 'strikethrough': 'tag:s'
-};
-
-linestylefilter.getAuthorClassName = function(author)
-{
- return "author-" + author.replace(/[^a-y0-9]/g, function(c)
- {
- if (c == ".") return "-";
- return 'z' + c.charCodeAt(0) + 'z';
- });
-};
-
-// lineLength is without newline; aline includes newline,
-// but may be falsy if lineLength == 0
-linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
-{
-
- var plugins_ = plugins;
-
- if (lineLength == 0) return textAndClassFunc;
-
- var nextAfterAuthorColors = textAndClassFunc;
-
- var authorColorFunc = (function()
- {
- var lineEnd = lineLength;
- var curIndex = 0;
- var extraClasses;
- var leftInAuthor;
-
- function attribsToClasses(attribs)
- {
- var classes = '';
- Changeset.eachAttribNumber(attribs, function(n)
- {
- var key = apool.getAttribKey(n);
- if (key)
- {
- var value = apool.getAttribValue(n);
- if (value)
- {
- if (key == 'author')
- {
- classes += ' ' + linestylefilter.getAuthorClassName(value);
- }
- else if (key == 'list')
- {
- classes += ' list:' + value;
- }
- else if (key == 'start')
- {
- classes += ' start:' + value;
- }
- else if (linestylefilter.ATTRIB_CLASSES[key])
- {
- classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
- }
- else
- {
- classes += plugins_.callHookStr("aceAttribsToClasses", {
- linestylefilter: linestylefilter,
- key: key,
- value: value
- }, " ", " ", "");
- }
- }
- }
- });
- return classes.substring(1);
- }
-
- var attributionIter = Changeset.opIterator(aline);
- var nextOp, nextOpClasses;
-
- function goNextOp()
- {
- nextOp = attributionIter.next();
- nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
- }
- goNextOp();
-
- function nextClasses()
- {
- if (curIndex < lineEnd)
- {
- extraClasses = nextOpClasses;
- leftInAuthor = nextOp.chars;
- goNextOp();
- while (nextOp.opcode && nextOpClasses == extraClasses)
- {
- leftInAuthor += nextOp.chars;
- goNextOp();
- }
- }
- }
- nextClasses();
-
- return function(txt, cls)
- {
- while (txt.length > 0)
- {
- if (leftInAuthor <= 0)
- {
- // prevent infinite loop if something funny's going on
- return nextAfterAuthorColors(txt, cls);
- }
- var spanSize = txt.length;
- if (spanSize > leftInAuthor)
- {
- spanSize = leftInAuthor;
- }
- var curTxt = txt.substring(0, spanSize);
- txt = txt.substring(spanSize);
- nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
- curIndex += spanSize;
- leftInAuthor -= spanSize;
- if (leftInAuthor == 0)
- {
- nextClasses();
- }
- }
- };
- })();
- return authorColorFunc;
-};
-
-linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
-{
- var at = /@/g;
- at.lastIndex = 0;
- var splitPoints = null;
- var execResult;
- while ((execResult = at.exec(lineText)))
- {
- if (!splitPoints)
- {
- splitPoints = [];
- }
- splitPoints.push(execResult.index);
- }
-
- if (!splitPoints) return textAndClassFunc;
-
- return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
-};
-
-linestylefilter.getRegexpFilter = function(regExp, tag)
-{
- return function(lineText, textAndClassFunc)
- {
- regExp.lastIndex = 0;
- var regExpMatchs = null;
- var splitPoints = null;
- var execResult;
- while ((execResult = regExp.exec(lineText)))
- {
- if (!regExpMatchs)
- {
- regExpMatchs = [];
- splitPoints = [];
- }
- var startIndex = execResult.index;
- var regExpMatch = execResult[0];
- regExpMatchs.push([startIndex, regExpMatch]);
- splitPoints.push(startIndex, startIndex + regExpMatch.length);
- }
-
- if (!regExpMatchs) return textAndClassFunc;
-
- function regExpMatchForIndex(idx)
- {
- for (var k = 0; k < regExpMatchs.length; k++)
- {
- var u = regExpMatchs[k];
- if (idx >= u[0] && idx < u[0] + u[1].length)
- {
- return u[1];
- }
- }
- return false;
- }
-
- var handleRegExpMatchsAfterSplit = (function()
- {
- var curIndex = 0;
- return function(txt, cls)
- {
- var txtlen = txt.length;
- var newCls = cls;
- var regExpMatch = regExpMatchForIndex(curIndex);
- if (regExpMatch)
- {
- newCls += " " + tag + ":" + regExpMatch;
- }
- textAndClassFunc(txt, newCls);
- curIndex += txtlen;
- };
- })();
-
- return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
- };
-};
-
-
-linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
-linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
-linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
-linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
-linestylefilter.REGEX_URL, 'url');
-
-linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
-{
- var nextPointIndex = 0;
- var idx = 0;
-
- // don't split at 0
- while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
- {
- nextPointIndex++;
- }
-
- function spanHandler(txt, cls)
- {
- if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
- {
- func(txt, cls);
- idx += txt.length;
- }
- else
- {
- var splitPoints = splitPointsOpt;
- var pointLocInSpan = splitPoints[nextPointIndex] - idx;
- var txtlen = txt.length;
- if (pointLocInSpan >= txtlen)
- {
- func(txt, cls);
- idx += txt.length;
- if (pointLocInSpan == txtlen)
- {
- nextPointIndex++;
- }
- }
- else
- {
- if (pointLocInSpan > 0)
- {
- func(txt.substring(0, pointLocInSpan), cls);
- idx += pointLocInSpan;
- }
- nextPointIndex++;
- // recurse
- spanHandler(txt.substring(pointLocInSpan), cls);
- }
- }
- }
- return spanHandler;
-};
-
-linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
-{
- var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
-
- var plugins_ = plugins;
-
- var hookFilters = plugins_.callHook("aceGetFilterStack", {
- linestylefilter: linestylefilter,
- browser: browser
- });
- map(hookFilters, function(hookFilter)
- {
- func = hookFilter(lineText, func);
- });
-
- if (browser !== undefined && browser.msie)
- {
- // IE7+ will take an e-mail address like and linkify it to foo@bar.com.
- // We then normalize it back to text with no angle brackets. It's weird. So always
- // break spans at an "at" sign.
- func = linestylefilter.getAtSignSplitterFilter(
- lineText, func);
- }
- return func;
-};
-
-// domLineObj is like that returned by domline.createDomLine
-linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
-{
- // remove final newline from text if any
- var text = textLine;
- if (text.slice(-1) == '\n')
- {
- text = text.substring(0, text.length - 1);
- }
-
- function textAndClassFunc(tokenText, tokenClass)
- {
- domLineObj.appendSpan(tokenText, tokenClass);
- }
-
- var func = linestylefilter.getFilterStack(text, textAndClassFunc);
- func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
- func(text, '');
-};
-
-exports.linestylefilter = linestylefilter;
diff --git a/static/js/pad.js b/static/js/pad.js
index 9dab9c618..eb480080f 100644
--- a/static/js/pad.js
+++ b/static/js/pad.js
@@ -46,47 +46,9 @@ var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
var paduserlist = require('/pad_userlist').paduserlist;
var padutils = require('/pad_utils').padutils;
-function createCookie(name, value, days, path)
-{
- if (days)
- {
- var date = new Date();
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
- var expires = "; expires=" + date.toGMTString();
- }
- else var expires = "";
-
- if(!path)
- path = "/";
-
- document.cookie = name + "=" + value + expires + "; path=" + path;
-}
-
-function readCookie(name)
-{
- var nameEQ = name + "=";
- var ca = document.cookie.split(';');
- for (var i = 0; i < ca.length; i++)
- {
- var c = ca[i];
- while (c.charAt(0) == ' ') c = c.substring(1, c.length);
- if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
- }
- return null;
-}
-
-function randomString()
-{
- var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- var string_length = 20;
- var randomstring = '';
- for (var i = 0; i < string_length; i++)
- {
- var rnum = Math.floor(Math.random() * chars.length);
- randomstring += chars.substring(rnum, rnum + 1);
- }
- return "t." + randomstring;
-}
+var createCookie = require('/pad_utils').createCookie;
+var readCookie = require('/pad_utils').readCookie;
+var randomString = require('/pad_utils').randomString;
function getParams()
{
@@ -210,7 +172,7 @@ function handshake()
var token = readCookie("token");
if (token == null)
{
- token = randomString();
+ token = "t." + randomString();
createCookie("token", token, 60);
}
diff --git a/static/js/pad_utils.js b/static/js/pad_utils.js
index 071185a80..fb538211f 100644
--- a/static/js/pad_utils.js
+++ b/static/js/pad_utils.js
@@ -20,17 +20,58 @@
* limitations under the License.
*/
+var Security = require('/security');
+
+/**
+ * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
+ */
+
+function randomString(len)
+{
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ var randomstring = '';
+ len = len || 20
+ for (var i = 0; i < len; i++)
+ {
+ var rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars.substring(rnum, rnum + 1);
+ }
+ return randomstring;
+}
+
+function createCookie(name, value, days, path)
+{
+ if (days)
+ {
+ var date = new Date();
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ var expires = "; expires=" + date.toGMTString();
+ }
+ else var expires = "";
+
+ if(!path)
+ path = "/";
+
+ document.cookie = name + "=" + value + expires + "; path=" + path;
+}
+
+function readCookie(name)
+{
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++)
+ {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1, c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
+ }
+ return null;
+}
+
var padutils = {
escapeHtml: function(x)
{
- return String(x).replace(/[&"<>]/g, function (c) {
- return {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- }[c] || c;
- });
+ return Security.escapeHTML(String(x));
},
uniqueId: function()
{
@@ -159,7 +200,7 @@ var padutils = {
{
if (i > idx)
{
- pieces.push(padutils.escapeHtml(text.substring(idx, i)));
+ pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i;
}
}
@@ -170,7 +211,7 @@ var padutils = {
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex);
- pieces.push('');
+ pieces.push('');
advanceTo(startIndex + href.length);
pieces.push('');
}
@@ -481,4 +522,7 @@ padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
padutils.binarySearch = require('/ace2_common').binarySearch;
+exports.randomString = randomString;
+exports.createCookie = createCookie;
+exports.readCookie = readCookie;
exports.padutils = padutils;
diff --git a/static/js/security.js b/static/js/security.js
new file mode 100644
index 000000000..6f42d0516
--- /dev/null
+++ b/static/js/security.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var HTML_ENTITY_MAP = {
+ '&': '&'
+, '<': '<'
+, '>': '>'
+, '"': '"'
+, "'": '''
+, '/': '/'
+};
+
+// OSWASP Guidlines: &, <, >, ", ' plus forward slash.
+var HTML_CHARACTERS_EXPRESSION = /[&"'<>\/]/g;
+function escapeHTML(text) {
+ return text && text.replace(HTML_CHARACTERS_EXPRESSION, function (c) {
+ return HTML_ENTITY_MAP[c] || c;
+ });
+}
+
+// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+var HTML_ATTRIBUTE_CHARACTERS_EXPRESSION =
+ /[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
+function escapeHTMLAttribute(text) {
+ return text && text.replace(HTML_ATTRIBUTE_CHARACTERS_EXPRESSION, function (c) {
+ return "" + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ";";
+ });
+};
+
+// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+var JAVASCRIPT_CHARACTERS_EXPRESSION =
+ /[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
+function escapeJavaScriptData(text) {
+ return text && text.replace(JAVASCRIPT_CHARACTERS_EXPRESSION, function (c) {
+ return "\\x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ });
+}
+
+exports.escapeHTML = escapeHTML;
+exports.escapeHTMLAttribute = escapeHTMLAttribute;
+exports.escapeJavaScriptData = escapeJavaScriptData;
diff --git a/static/js/timeslider.js b/static/js/timeslider.js
index d2fce8fd7..143ef3281 100644
--- a/static/js/timeslider.js
+++ b/static/js/timeslider.js
@@ -26,39 +26,9 @@ require('/jquery');
JSON = require('/json2');
require('/undo-xpopup');
-function createCookie(name,value,days)
-{
- if (days) {
- var date = new Date();
- date.setTime(date.getTime()+(days*24*60*60*1000));
- var expires = "; expires="+date.toGMTString();
- }
- else var expires = "";
- document.cookie = name+"="+value+expires+"; path=/";
-}
-
-function readCookie(name)
-{
- var nameEQ = name + "=";
- var ca = document.cookie.split(';');
- for(var i=0;i < ca.length;i++) {
- var c = ca[i];
- while (c.charAt(0)==' ') c = c.substring(1,c.length);
- if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
- }
- return null;
-}
-
-function randomString() {
- var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- var string_length = 20;
- var randomstring = '';
- for (var i=0; i