summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter 'Pita' Martischka <petermartischka@googlemail.com>2011-11-25 16:56:08 -0800
committerPeter 'Pita' Martischka <petermartischka@googlemail.com>2011-11-25 16:56:08 -0800
commit7296913cb0f347f4ea036427eb4e0dcdbaec9814 (patch)
tree2ca6d79f237d9a3aa0abf346cd6ca7e51c685660
parent54b912f352bec8c3c6dced5c5bc9161ba4899366 (diff)
parent943f8c4682296c8b5424f1c5cec40a3fbea24195 (diff)
downloadetherpad-lite-7296913cb0f347f4ea036427eb4e0dcdbaec9814.zip
fixed merge confilicts
-rw-r--r--node/db/API.js22
-rw-r--r--node/handler/APIHandler.js1
-rw-r--r--node/utils/ImportHtml.js92
-rw-r--r--node/utils/contentcollector.js692
-rw-r--r--package.json3
-rw-r--r--static/js/ace2_common.js12
6 files changed, 820 insertions, 2 deletions
diff --git a/node/db/API.js b/node/db/API.js
index 2069ce68..c40c49ca 100644
--- a/node/db/API.js
+++ b/node/db/API.js
@@ -26,6 +26,8 @@ var authorManager = require("./AuthorManager");
var sessionManager = require("./SessionManager");
var async = require("async");
var exportHtml = require("../utils/ExportHtml");
+var importHtml = require("../utils/ImportHtml");
+var cleanText = require("./Pad").cleanText;
/**********************/
/**GROUP FUNCTIONS*****/
@@ -254,6 +256,26 @@ exports.getHTML = function(padID, rev, callback)
});
}
+exports.setHTML = function(padID, html, callback)
+{
+ //get the pad
+ getPadSafe(padID, true, function(err, pad)
+ {
+ if(err)
+ {
+ callback(err);
+ return;
+ }
+
+ // add a new changeset with the new html to the pad
+ importHtml.setPadHTML(pad, cleanText(html));
+
+ //update the clients on the pad
+ padMessageHandler.updatePadClients(pad, callback);
+
+ });
+}
+
/*****************/
/**PAD FUNCTIONS */
/*****************/
diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js
index 04464b08..2159cc40 100644
--- a/node/handler/APIHandler.js
+++ b/node/handler/APIHandler.js
@@ -51,6 +51,7 @@ var functions = {
"getText" : ["padID", "rev"],
"setText" : ["padID", "text"],
"getHTML" : ["padID", "rev"],
+ "setHTML" : ["padID", "html"],
"getRevisionsCount" : ["padID"],
"deletePad" : ["padID"],
"getReadOnlyID" : ["padID"],
diff --git a/node/utils/ImportHtml.js b/node/utils/ImportHtml.js
new file mode 100644
index 00000000..6441708e
--- /dev/null
+++ b/node/utils/ImportHtml.js
@@ -0,0 +1,92 @@
+/**
+ * Copyright Yaco Sistemas S.L. 2011.
+ *
+ * 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 jsdom = require('jsdom').jsdom;
+var log4js = require('log4js');
+
+var Changeset = require("./Changeset");
+var contentcollector = require("./contentcollector");
+var map = require("../../static/js/ace2_common.js").map;
+
+function setPadHTML(pad, html, callback)
+{
+ var apiLogger = log4js.getLogger("ImportHtml");
+
+ // Clean the pad. This makes the rest of the code easier
+ // by several orders of magnitude.
+ pad.setText("");
+ var padText = pad.text();
+
+ // Parse the incoming HTML with jsdom
+ var doc = jsdom(html.replace(/>\n+</g, '><'));
+ apiLogger.debug('html:');
+ apiLogger.debug(html);
+
+ // Convert a dom tree into a list of lines and attribute liens
+ // using the content collector object
+ var cc = contentcollector.makeContentCollector(true, null, pad.pool);
+ cc.collectContent(doc.childNodes[0]);
+ var result = cc.finish();
+ apiLogger.debug('Lines:');
+ var i;
+ for (i = 0; i < result.lines.length; i += 1)
+ {
+ apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]);
+ apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]);
+ }
+
+ // Get the new plain text and its attributes
+ var newText = map(result.lines, function (e) {
+ return e + '\n';
+ }).join('');
+ apiLogger.debug('newText:');
+ apiLogger.debug(newText);
+ var newAttribs = result.lineAttribs.join('|1+1') + '|1+1';
+
+ function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ )
+ {
+ var attribsIter = Changeset.opIterator(attribs);
+ var textIndex = 0;
+ var newTextStart = 0;
+ var newTextEnd = newText.length - 1;
+ while (attribsIter.hasNext())
+ {
+ var op = attribsIter.next();
+ var nextIndex = textIndex + op.chars;
+ if (!(nextIndex <= newTextStart || textIndex >= newTextEnd))
+ {
+ func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
+ }
+ textIndex = nextIndex;
+ }
+ }
+
+ // create a new changeset with a helper builder object
+ var builder = Changeset.builder(1);
+
+ // assemble each line into the builder
+ eachAttribRun(newAttribs, function(start, end, attribs)
+ {
+ builder.insert(newText.substring(start, end), attribs);
+ });
+
+ // the changeset is ready!
+ var theChangeset = builder.toString();
+ apiLogger.debug('The changeset: ' + theChangeset);
+ pad.appendRevision(theChangeset);
+}
+
+exports.setPadHTML = setPadHTML;
diff --git a/node/utils/contentcollector.js b/node/utils/contentcollector.js
new file mode 100644
index 00000000..60bd0a6e
--- /dev/null
+++ b/node/utils/contentcollector.js
@@ -0,0 +1,692 @@
+// 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) == "&nbsp;" && !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")
+ {
+ var type;
+ var rr = cls && /(?:^| )list-(bullet[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/package.json b/package.json
index f1c30caa..78648f0a 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,8 @@
"uglify-js" : "1.1.1",
"gzip" : "0.1.0",
"formidable" : "1.0.7",
- "log4js" : "0.3.9"
+ "log4js" : "0.3.9",
+ "jsdom" : "0.2.9"
},
"version" : "1.0.0"
}
diff --git a/static/js/ace2_common.js b/static/js/ace2_common.js
index 02dc350f..18478586 100644
--- a/static/js/ace2_common.js
+++ b/static/js/ace2_common.js
@@ -74,8 +74,12 @@ function isArray(testObject)
return testObject && typeof testObject === 'object' && !(testObject.propertyIsEnumerable('length')) && typeof testObject.length === 'number';
}
+if (typeof exports !== "undefined")
+{
+ var navigator = {userAgent: "node-js"};
+}
// Figure out what browser is being used (stolen from jquery 1.2.1)
-var userAgent = navigator.userAgent.toLowerCase();
+userAgent = navigator.userAgent.toLowerCase();
var browser = {
version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
safari: /webkit/.test(userAgent),
@@ -85,6 +89,7 @@ var browser = {
windows: /windows/.test(userAgent) // dgreensp
};
+
function getAssoc(obj, name)
{
return obj["_magicdom_" + name];
@@ -130,3 +135,8 @@ function htmlPrettyEscape(str)
{
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\r?\n/g, '\\n');
}
+
+if (typeof exports !== "undefined")
+{
+ exports.map = map;
+} \ No newline at end of file