summaryrefslogtreecommitdiff
path: root/src/static/js/linestylefilter.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/static/js/linestylefilter.js')
-rw-r--r--src/static/js/linestylefilter.js341
1 files changed, 341 insertions, 0 deletions
diff --git a/src/static/js/linestylefilter.js b/src/static/js/linestylefilter.js
new file mode 100644
index 00000000..04b8fb72
--- /dev/null
+++ b/src/static/js/linestylefilter.js
@@ -0,0 +1,341 @@
+/**
+ * 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.linestylefilter
+// %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.
+ */
+
+// requires: easysync2.Changeset
+// requires: top
+// requires: plugins
+// requires: undefined
+
+var Changeset = require('./Changeset');
+var hooks = require('./pluginfw/hooks');
+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)
+{
+
+ 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 += hooks.callAllStr("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 hookFilters = hooks.callAll("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 <foo@bar.com> 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;