etherpad-lite/static/js/linestylefilter.js

291 lines
8.4 KiB
JavaScript

// 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 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_;
if (typeof(plugins)!='undefined') {
plugins_ = plugins;
} else {
plugins_ = parent.parent.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 (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:)/.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_;
if (typeof(plugins)!='undefined') {
plugins_ = plugins;
} else {
plugins_ = parent.parent.plugins;
}
var hookFilters = plugins_.callHook(
"aceGetFilterStack", {linestylefilter:linestylefilter, browser:browser});
hookFilters.map(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, '');
};