etherpad-lite/static/js/domline.js

293 lines
6.6 KiB
JavaScript
Raw Normal View History

2011-03-26 14:10:41 +01:00
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
// %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: top
// requires: plugins
// requires: undefined
var domline = {};
2011-07-07 19:59:34 +02:00
domline.noop = function()
{};
domline.identity = function(x)
{
return x;
};
2011-03-26 14:10:41 +01:00
2011-07-07 19:59:34 +02:00
domline.addToLineClass = function(lineClass, cls)
{
2011-03-26 14:10:41 +01:00
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
2011-07-07 19:59:34 +02:00
cls.replace(/\S+/g, function(c)
{
if (c.indexOf("line:") == 0)
{
2011-03-26 14:10:41 +01:00
// add class to line
2011-07-07 19:59:34 +02:00
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
2011-03-26 14:10:41 +01:00
}
});
return lineClass;
}
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
2011-07-07 19:59:34 +02:00
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
};
2011-03-26 14:10:41 +01:00
var browser = (optBrowser || {});
var document = optDocument;
2011-07-07 19:59:34 +02:00
if (document)
{
2011-03-26 14:10:41 +01:00
result.node = document.createElement("div");
}
2011-07-07 19:59:34 +02:00
else
{
result.node = {
innerHTML: '',
className: ''
};
2011-03-26 14:10:41 +01:00
}
var html = [];
var preHtml, postHtml;
var curHTML = null;
2011-07-07 19:59:34 +02:00
function processSpaces(s)
{
2011-03-26 14:10:41 +01:00
return domline.processSpaces(s, doesWrap);
}
var identity = domline.identity;
var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line';
2011-07-07 19:59:34 +02:00
result.appendSpan = function(txt, cls)
{
if (cls.indexOf('list') >= 0)
{
2011-03-26 14:10:41 +01:00
var listType = /(?:^| )list:(\S+)/.exec(cls);
2011-07-07 19:59:34 +02:00
if (listType)
{
2011-03-26 14:10:41 +01:00
listType = listType[1];
2011-07-07 19:59:34 +02:00
if (listType)
{
preHtml = '<ul class="list-' + listType + '"><li>';
2011-03-26 14:10:41 +01:00
postHtml = '</li></ul>';
}
result.lineMarker += txt.length;
return; // don't append any text
}
}
var href = null;
var simpleTags = null;
2011-07-07 19:59:34 +02:00
if (cls.indexOf('url') >= 0)
{
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
{
href = url;
return space + "url";
2011-03-26 14:10:41 +01:00
});
}
2011-07-07 19:59:34 +02:00
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;
2011-03-26 14:10:41 +01:00
});
}
var extraOpenTags = "";
var extraCloseTags = "";
var plugins_;
2011-07-07 19:59:34 +02:00
if (typeof(plugins) != 'undefined')
{
2011-03-26 14:10:41 +01:00
plugins_ = plugins;
2011-07-07 19:59:34 +02:00
}
else
{
2011-03-26 14:10:41 +01:00
plugins_ = parent.parent.plugins;
}
2011-07-07 19:59:34 +02:00
plugins_.callHook("aceCreateDomLine", {
domline: domline,
cls: cls
}).map(function(modifier)
{
2011-03-26 14:10:41 +01:00
cls = modifier.cls;
2011-07-07 19:59:34 +02:00
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
2011-03-26 14:10:41 +01:00
});
2011-07-07 19:59:34 +02:00
if ((!txt) && cls)
{
2011-03-26 14:10:41 +01:00
lineClass = domline.addToLineClass(lineClass, cls);
}
2011-07-07 19:59:34 +02:00
else if (txt)
{
if (href)
{
extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '&quot;') + '">';
extraCloseTags = '</a>' + extraCloseTags;
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
if (simpleTags)
{
simpleTags.sort();
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
2011-03-26 14:10:41 +01:00
}
};
2011-07-07 19:59:34 +02:00
result.clearSpans = function()
{
2011-03-26 14:10:41 +01:00
html = [];
lineClass = ''; // non-null to cause update
result.lineMarker = 0;
};
2011-07-07 19:59:34 +02:00
function writeHTML()
{
2011-03-26 14:10:41 +01:00
var newHTML = perHtmlLineProcess(html.join(''));
2011-07-07 19:59:34 +02:00
if (!newHTML)
{
if ((!document) || (!optBrowser))
{
2011-03-26 14:10:41 +01:00
newHTML += '&nbsp;';
}
2011-07-07 19:59:34 +02:00
else if (!browser.msie)
{
2011-03-26 14:10:41 +01:00
newHTML += '<br/>';
}
}
2011-07-07 19:59:34 +02:00
if (nonEmpty)
{
newHTML = (preHtml || '') + newHTML + (postHtml || '');
2011-03-26 14:10:41 +01:00
}
html = preHtml = postHtml = null; // free memory
2011-07-07 19:59:34 +02:00
if (newHTML !== curHTML)
{
2011-03-26 14:10:41 +01:00
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass !== null) result.node.className = lineClass;
}
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
2011-07-07 19:59:34 +02:00
result.getInnerHTML = function()
{
return curHTML || '';
};
2011-03-26 14:10:41 +01:00
return result;
};
2011-07-07 19:59:34 +02:00
domline.escapeHTML = function(s)
{
var re = /[&<>'"]/g;
/']/; // stupid indentation thing
if (!re.MAP)
{
2011-03-26 14:10:41 +01:00
// persisted across function calls!
re.MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
}
2011-07-07 19:59:34 +02:00
return s.replace(re, function(c)
{
return re.MAP[c];
});
2011-03-26 14:10:41 +01:00
};
2011-07-07 19:59:34 +02:00
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
{
2011-03-26 14:10:41 +01:00
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
2011-07-07 19:59:34 +02:00
s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
{
parts.push(m);
});
if (doesWrap)
{
2011-03-26 14:10:41 +01:00
var endOfLine = true;
var beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
2011-07-07 19:59:34 +02:00
for (var i = parts.length - 1; i >= 0; i--)
{
2011-03-26 14:10:41 +01:00
var p = parts[i];
2011-07-07 19:59:34 +02:00
if (p == " ")
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
else if (p.charAt(0) != "<")
{
endOfLine = false;
beforeSpace = false;
2011-03-26 14:10:41 +01:00
}
}
// beginning of line is nbsp
2011-07-07 19:59:34 +02:00
for (var i = 0; i < parts.length; i++)
{
2011-03-26 14:10:41 +01:00
var p = parts[i];
2011-07-07 19:59:34 +02:00
if (p == " ")
{
parts[i] = '&nbsp;';
break;
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
else if (p.charAt(0) != "<")
{
break;
2011-03-26 14:10:41 +01:00
}
}
}
2011-07-07 19:59:34 +02:00
else
{
for (var i = 0; i < parts.length; i++)
{
2011-03-26 14:10:41 +01:00
var p = parts[i];
2011-07-07 19:59:34 +02:00
if (p == " ")
{
parts[i] = '&nbsp;';
2011-03-26 14:10:41 +01:00
}
}
}
return parts.join('');
};