Merge branch 'master' of git://github.com/Pita/etherpad-lite into api

This commit is contained in:
Peter 'Pita' Martischka 2011-08-10 22:47:50 +01:00
commit 9cc3b543cc
28 changed files with 348 additions and 213 deletions

View File

@ -1,7 +1,7 @@
# About # About
Etherpad lite is a really-real time collaborative editor spawned from the Hell fire of Etherpad. Etherpad lite is a really-real time collaborative editor spawned from the Hell fire of Etherpad.
We're reusing the well tested Etherpad easysync library to make it really realtime. Etherpad Lite We're reusing the well tested Etherpad easysync library to make it really realtime. Etherpad Lite
is based on node.js what makes it much ligther and more stable than the original Etherpad. Our hope is based on node.js what makes it much lighter and more stable than the original Etherpad. Our hope
is that this will encourage more users to install a realtime collaborative editor. A smaller and well is that this will encourage more users to install a realtime collaborative editor. A smaller and well
documented codebase makes it easier for developers to improve the code. Etherpad Lite is optimized documented codebase makes it easier for developers to improve the code. Etherpad Lite is optimized
to be easy embeddable. Look at our [FAQ Page](https://github.com/Pita/etherpad-lite/wiki/FAQ) to be easy embeddable. Look at our [FAQ Page](https://github.com/Pita/etherpad-lite/wiki/FAQ)
@ -32,7 +32,7 @@ Visit <http://pitapoison.de:9001> to test it live. <br>You can find the same ins
**As root:** **As root:**
<ol> <ol>
<li>Install all dependencies. We need the sqlite develob libraries, gzip, git, curl, libssl develop libraries and python <br><code>apt-get install libsqlite3-dev gzip git-core curl python libssl-dev</code></li><br> <li>Install all dependencies. We need the sqlite development libraries, gzip, git, curl, libssl develop libraries and python <br><code>apt-get install libsqlite3-dev gzip git-core curl python libssl-dev</code></li><br>
<li>Install node.js <li>Install node.js
<ol type="a"> <ol type="a">
<li>Download the latest <b>0.4.x</b> node.js release from <a href="http://nodejs.org/#download">http://nodejs.org/#download</a></li> <li>Download the latest <b>0.4.x</b> node.js release from <a href="http://nodejs.org/#download">http://nodejs.org/#download</a></li>
@ -43,7 +43,7 @@ Visit <http://pitapoison.de:9001> to test it live. <br>You can find the same ins
<li>Install npm <code>curl http://npmjs.org/install.sh | sh</code></li> <li>Install npm <code>curl http://npmjs.org/install.sh | sh</code></li>
</ol> </ol>
**As any user (we recommend creating a seperate user called etherpad-lite):** **As any user (we recommend creating a separate user called etherpad-lite):**
<ol start="4"> <ol start="4">
<li> Clone the git repository <code>git clone 'git://github.com/Pita/etherpad-lite.git'</code><br>&nbsp;</li> <li> Clone the git repository <code>git clone 'git://github.com/Pita/etherpad-lite.git'</code><br>&nbsp;</li>
@ -55,7 +55,7 @@ Visit <http://pitapoison.de:9001> to test it live. <br>You can find the same ins
## Troubleshooting ## Troubleshooting
### It fails while installing the sqlite dependency ### It fails while installing the sqlite dependency
The sqlite package of some linux versions (including debian lenny) is too old. We need sqlite >=3.6. You have to use a PPA or debian backports. You find sqlite packages for Ubuntu Hardy [here](https://launchpad.net/~mirabilos/+archive/ppa/+sourcepub/1304941/+listing-archive-extra), Debian Backports can be found [here](http://backports-master.debian.org/Instructions/#index1h2) The sqlite package of some Linux versions (including debian lenny) is too old. We need sqlite >=3.6. You have to use a PPA or debian backports. You find sqlite packages for Ubuntu Hardy [here](https://launchpad.net/~mirabilos/+archive/ppa/+sourcepub/1304941/+listing-archive-extra), Debian Backports can be found [here](http://backports-master.debian.org/Instructions/#index1h2)
### It fails while installing the express dependency, it says my node version is wrong ### It fails while installing the express dependency, it says my node version is wrong
You might have installed node.js version 0.5. You can check that with `node --version`. Please reinstall node 0.4.x You might have installed node.js version 0.5. You can check that with `node --version`. Please reinstall node 0.4.x
@ -93,7 +93,7 @@ You also help the project, if you only host a Etherpad Lite instance and share y
# Modules created for this project # Modules created for this project
* [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access * [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access
* [doc.md](https://github.com/Pita/doc.md) "A simple JSDoc documenation tool that creates markdown for node.js modules exports" - is used to generate the docs * [doc.md](https://github.com/Pita/doc.md) "A simple JSDoc documentation tool that creates markdown for node.js modules exports" - is used to generate the docs
* [channels](https://github.com/Pita/channels) "Event channels in node.js" - ensures that ueberDB operations are atomic and in series for each key * [channels](https://github.com/Pita/channels) "Event channels in node.js" - ensures that ueberDB operations are atomic and in series for each key
# License # License

View File

@ -13,27 +13,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
var async = require("async"); var async = require("async");
var Changeset = require("./Changeset"); var Changeset = require("./Changeset");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
function getPadPlainText(pad, revNum)
function getPadPlainText(pad, revNum) { {
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
pad.atext()); var textLines = atext.text.slice(0, -1).split('\n');
var textLines = atext.text.slice(0,-1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var apool = pad.pool(); var apool = pad.pool();
var pieces = []; var pieces = [];
for(var i=0;i<textLines.length;i++) { for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool); var line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel) { if (line.listLevel)
var numSpaces = line.listLevel*2-1; {
var numSpaces = line.listLevel * 2 - 1;
var bullet = '*'; var bullet = '*';
pieces.push(new Array(numSpaces+1).join(' '), bullet, ' ', line.text, '\n'); pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
} }
else { else
{
pieces.push(line.text, '\n'); pieces.push(line.text, '\n');
} }
} }
@ -41,52 +44,68 @@ function getPadPlainText(pad, revNum) {
return pieces.join(''); return pieces.join('');
} }
function getPadHTML(pad, revNum, callback) { function getPadHTML(pad, revNum, callback)
{
var atext = pad.atext; var atext = pad.atext;
var html; var html;
async.waterfall([ async.waterfall([
// fetch revision atext // fetch revision atext
function (callback) {
if (revNum != undefined) {
pad.getInternalRevisionAText(revNum, function (err, revisionAtext) { function (callback)
atext = revisionAtext; {
callback(err); if (revNum != undefined)
}); {
} else { pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
callback(null); {
} atext = revisionAtext;
}, callback(err);
});
// convert atext to html }
function (callback) { else
html = getHTMLFromAtext(pad, atext); {
callback(null); callback(null);
} }
], },
// run final callback
function (err) { // convert atext to html
callback(err, html);
}
); function (callback)
{
html = getHTMLFromAtext(pad, atext);
callback(null);
}],
// run final callback
function (err)
{
callback(err, html);
});
} }
function getHTMLFromAtext(pad, atext) { function getHTMLFromAtext(pad, atext)
{
var apool = pad.apool(); var apool = pad.apool();
var textLines = atext.text.slice(0,-1).split('\n'); var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var tags = ['h1', 'h2', 'strong','em','u','s']; var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold','italic','underline','strikethrough']; var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
var anumMap = {}; var anumMap = {};
props.forEach(function(propName, i) { props.forEach(function (propName, i)
var propTrueNum = apool.putAttrib([propName,true], true); {
if (propTrueNum >= 0) { var propTrueNum = apool.putAttrib([propName, true], true);
if (propTrueNum >= 0)
{
anumMap[propTrueNum] = i; anumMap[propTrueNum] = i;
} }
}); });
function getLineHTML(text, attribs) { function getLineHTML(text, attribs)
{
var propVals = [false, false, false]; var propVals = [false, false, false];
var ENTER = 1; var ENTER = 1;
var STAY = 2; var STAY = 2;
@ -97,16 +116,18 @@ function getHTMLFromAtext(pad, atext) {
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes // becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text); var taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler(); var assem = Changeset.stringAssembler();
function emitOpenTag(i) { function emitOpenTag(i)
{
assem.append('<'); assem.append('<');
assem.append(tags[i]); assem.append(tags[i]);
assem.append('>'); assem.append('>');
} }
function emitCloseTag(i) {
function emitCloseTag(i)
{
assem.append('</'); assem.append('</');
assem.append(tags[i]); assem.append(tags[i]);
assem.append('>'); assem.append('>');
@ -115,101 +136,123 @@ function getHTMLFromAtext(pad, atext) {
var urls = _findURLs(text); var urls = _findURLs(text);
var idx = 0; var idx = 0;
function processNextChars(numChars) {
if (numChars <= 0) { function processNextChars(numChars)
{
if (numChars <= 0)
{
return; return;
} }
var iter = Changeset.opIterator(Changeset.subattribution(attribs, var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx, idx+numChars));
idx += numChars; idx += numChars;
while (iter.hasNext()) { while (iter.hasNext())
{
var o = iter.next(); var o = iter.next();
var propChanged = false; var propChanged = false;
Changeset.eachAttribNumber(o.attribs, function(a) { Changeset.eachAttribNumber(o.attribs, function (a)
if (a in anumMap) { {
if (a in anumMap)
{
var i = anumMap[a]; // i = 0 => bold, etc. var i = anumMap[a]; // i = 0 => bold, etc.
if (! propVals[i]) { if (!propVals[i])
{
propVals[i] = ENTER; propVals[i] = ENTER;
propChanged = true; propChanged = true;
} }
else { else
{
propVals[i] = STAY; propVals[i] = STAY;
} }
} }
}); });
for(var i=0;i<propVals.length;i++) { for (var i = 0; i < propVals.length; i++)
if (propVals[i] === true) { {
if (propVals[i] === true)
{
propVals[i] = LEAVE; propVals[i] = LEAVE;
propChanged = true; propChanged = true;
} }
else if (propVals[i] === STAY) { else if (propVals[i] === STAY)
{
propVals[i] = true; // set it back propVals[i] = true; // set it back
} }
} }
// now each member of propVal is in {false,LEAVE,ENTER,true} // now each member of propVal is in {false,LEAVE,ENTER,true}
// according to what happens at start of span // according to what happens at start of span
if (propChanged)
if (propChanged) { {
// leaving bold (e.g.) also leaves italics, etc. // leaving bold (e.g.) also leaves italics, etc.
var left = false; var left = false;
for(var i=0;i<propVals.length;i++) { for (var i = 0; i < propVals.length; i++)
{
var v = propVals[i]; var v = propVals[i];
if (! left) { if (!left)
if (v === LEAVE) { {
if (v === LEAVE)
{
left = true; left = true;
} }
} }
else { else
if (v === true) { {
if (v === true)
{
propVals[i] = STAY; // tag will be closed and re-opened propVals[i] = STAY; // tag will be closed and re-opened
} }
} }
} }
for(var i=propVals.length-1; i>=0; i--) { for (var i = propVals.length - 1; i >= 0; i--)
if (propVals[i] === LEAVE) { {
if (propVals[i] === LEAVE)
{
emitCloseTag(i); emitCloseTag(i);
propVals[i] = false; propVals[i] = false;
} }
else if (propVals[i] === STAY) { else if (propVals[i] === STAY)
{
emitCloseTag(i); emitCloseTag(i);
} }
} }
for(var i=0; i<propVals.length; i++) { for (var i = 0; i < propVals.length; i++)
if (propVals[i] === ENTER || propVals[i] === STAY) { {
if (propVals[i] === ENTER || propVals[i] === STAY)
{
emitOpenTag(i); emitOpenTag(i);
propVals[i] = true; propVals[i] = true;
} }
} }
// propVals is now all {true,false} again // propVals is now all {true,false} again
} // end if (propChanged) } // end if (propChanged)
var chars = o.chars; var chars = o.chars;
if (o.lines) { if (o.lines)
{
chars--; // exclude newline at end of line, if present chars--; // exclude newline at end of line, if present
} }
var s = taker.take(chars); var s = taker.take(chars);
assem.append(_escapeHTML(s)); assem.append(_escapeHTML(s));
} // end iteration over spans in line } // end iteration over spans in line
for (var i = propVals.length - 1; i >= 0; i--)
for(var i=propVals.length-1; i>=0; i--) { {
if (propVals[i]) { if (propVals[i])
{
emitCloseTag(i); emitCloseTag(i);
propVals[i] = false; propVals[i] = false;
} }
} }
} // end processNextChars } // end processNextChars
if (urls)
if (urls) { {
urls.forEach(function(urlData) { urls.forEach(function (urlData)
{
var startIndex = urlData[0]; var startIndex = urlData[0];
var url = urlData[1]; var url = urlData[1];
var urlLength = url.length; var urlLength = url.length;
processNextChars(startIndex - idx); processNextChars(startIndex - idx);
assem.append('<a href="'+url.replace(/\"/g, '&quot;')+'">'); assem.append('<a href="' + url.replace(/\"/g, '&quot;') + '">');
processNextChars(urlLength); processNextChars(urlLength);
assem.append('</a>'); assem.append('</a>');
}); });
@ -218,7 +261,6 @@ function getHTMLFromAtext(pad, atext) {
return _processSpaces(assem.toString()); return _processSpaces(assem.toString());
} // end getLineHTML } // end getLineHTML
var pieces = []; var pieces = [];
// Need to deal with constraints imposed on HTML lists; can // Need to deal with constraints imposed on HTML lists; can
@ -228,79 +270,98 @@ function getHTMLFromAtext(pad, atext) {
// so we want to do something reasonable there. We also // so we want to do something reasonable there. We also
// want to deal gracefully with blank lines. // want to deal gracefully with blank lines.
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...] var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
for(var i=0;i<textLines.length;i++) { for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool); var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline); var lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel || lists.length > 0) { if (line.listLevel || lists.length > 0)
{
// do list stuff // do list stuff
var whichList = -1; // index into lists or -1 var whichList = -1; // index into lists or -1
if (line.listLevel) { if (line.listLevel)
{
whichList = lists.length; whichList = lists.length;
for(var j=lists.length-1;j>=0;j--) { for (var j = lists.length - 1; j >= 0; j--)
if (line.listLevel <= lists[j][0]) { {
if (line.listLevel <= lists[j][0])
{
whichList = j; whichList = j;
} }
} }
} }
if (whichList >= lists.length) { if (whichList >= lists.length)
{
lists.push([line.listLevel, line.listTypeName]); lists.push([line.listLevel, line.listTypeName]);
pieces.push('<ul><li>', lineContent || '<br>'); pieces.push('<ul><li>', lineContent || '<br>');
} }
else if (whichList == -1) { else if (whichList == -1)
if (line.text) { {
if (line.text)
{
// non-blank line, end all lists // non-blank line, end all lists
pieces.push(new Array(lists.length+1).join('</li></ul\n>')); pieces.push(new Array(lists.length + 1).join('</li></ul\n>'));
lists.length = 0; lists.length = 0;
pieces.push(lineContent, '<br>'); pieces.push(lineContent, '<br>');
} }
else { else
{
pieces.push('<br><br>'); pieces.push('<br><br>');
} }
} }
else { else
while (whichList < lists.length-1) { {
while (whichList < lists.length - 1)
{
pieces.push('</li></ul>'); pieces.push('</li></ul>');
lists.length--; lists.length--;
} }
pieces.push('</li><li>', lineContent || '<br>'); pieces.push('</li><li>', lineContent || '<br>');
} }
} }
else { else
{
pieces.push(lineContent, '<br>'); pieces.push(lineContent, '<br>');
} }
} }
pieces.push(new Array(lists.length+1).join('</li></ul>')); pieces.push(new Array(lists.length + 1).join('</li></ul>'));
return pieces.join(''); return pieces.join('');
} }
function _analyzeLine(text, aline, apool) { function _analyzeLine(text, aline, apool)
{
var line = {}; var line = {};
// identify list // identify list
var lineMarker = 0; var lineMarker = 0;
line.listLevel = 0; line.listLevel = 0;
if (aline) { if (aline)
{
var opIter = Changeset.opIterator(aline); var opIter = Changeset.opIterator(aline);
if (opIter.hasNext()) { if (opIter.hasNext())
{
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType) { if (listType)
{
lineMarker = 1; lineMarker = 1;
listType = /([a-z]+)([12345678])/.exec(listType); listType = /([a-z]+)([12345678])/.exec(listType);
if (listType) { if (listType)
{
line.listTypeName = listType[1]; line.listTypeName = listType[1];
line.listLevel = Number(listType[2]); line.listLevel = Number(listType[2]);
} }
} }
} }
} }
if (lineMarker) { if (lineMarker)
{
line.text = text.substring(1); line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1); line.aline = Changeset.subattribution(aline, 1);
} }
else { else
{
line.text = text; line.text = text;
line.aline = aline; line.aline = aline;
} }
@ -308,37 +369,32 @@ function _analyzeLine(text, aline, apool) {
return line; return line;
} }
exports.getPadHTMLDocument = function(padId, revNum, noDocType, callback) { exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
padManager.getPad(padId, function(err, pad) {
padManager.getPad(padId, function (err, pad)
{ {
if(err) if (err)
{ {
callback(err); callback(err);
return; return;
} }
var head = (noDocType?'':'<!doctype html>\n')+ var head = (noDocType ? '' : '<!doctype html>\n') + '<html lang="en">\n' + (noDocType ? '' : '<head>\n' + '<meta charset="utf-8">\n' + '<style> * { font-family: arial, sans-serif;\n' + 'font-size: 13px;\n' + 'line-height: 17px; }</style>\n' + '</head>\n') + '<body>';
'<html lang="en">\n'+
(noDocType?'':
'<head>\n'+
'<meta charset="utf-8">\n'+
'<style> * { font-family: arial, sans-serif;\n'+
'font-size: 13px;\n'+
'line-height: 17px; }</style>\n' +
'</head>\n')+
'<body>';
var foot = '</body>\n</html>\n'; var foot = '</body>\n</html>\n';
getPadHTML(pad, revNum, function (err, html) { getPadHTML(pad, revNum, function (err, html)
{
callback(err, head + html + foot); callback(err, head + html + foot);
}); });
}); });
} }
function _escapeHTML(s) { function _escapeHTML(s)
{
var re = /[&<>]/g; var re = /[&<>]/g;
if (! re.MAP) { if (!re.MAP)
{
// persisted across function calls! // persisted across function calls!
re.MAP = { re.MAP = {
'&': '&amp;', '&': '&amp;',
@ -346,53 +402,78 @@ function _escapeHTML(s) {
'>': '&gt;', '>': '&gt;',
}; };
} }
return s.replace(re, function(c) { return re.MAP[c]; });
s = s.replace(re, function (c)
{
return re.MAP[c];
});
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
{
return "&#" +c.charCodeAt(0) + ";"
});
} }
// copied from ACE // copied from ACE
function _processSpaces(s) {
function _processSpaces(s)
{
var doesWrap = true; var doesWrap = true;
if (s.indexOf("<") < 0 && ! doesWrap) { if (s.indexOf("<") < 0 && !doesWrap)
{
// short-cut // short-cut
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
} }
var parts = []; var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) { parts.push(m); }); s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
if (doesWrap) { {
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true; var endOfLine = true;
var beforeSpace = false; var beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for(var i=parts.length-1;i>=0;i--) { for (var i = parts.length - 1; i >= 0; i--)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
if (endOfLine || beforeSpace) {
parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} }
else if (p.charAt(0) != "<") { else if (p.charAt(0) != "<")
endOfLine = false; {
beforeSpace = false; endOfLine = false;
beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for(var i=0;i<parts.length;i++) { for (var i = 0; i < parts.length; i++)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
parts[i] = '&nbsp;'; {
break; parts[i] = '&nbsp;';
break;
} }
else if (p.charAt(0) != "<") { else if (p.charAt(0) != "<")
break; {
break;
} }
} }
} }
else { else
for(var i=0;i<parts.length;i++) { {
for (var i = 0; i < parts.length; i++)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
parts[i] = '&nbsp;'; {
parts[i] = '&nbsp;';
} }
} }
} }
@ -403,15 +484,19 @@ function _processSpaces(s) {
// copied from ACE // copied from ACE
var _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]/; var _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]/;
var _REGEX_SPACE = /\s/; var _REGEX_SPACE = /\s/;
var _REGEX_URLCHAR = new RegExp('('+/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source+'|'+_REGEX_WORDCHAR.source+')'); var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source+_REGEX_URLCHAR.source+'*(?![:.,;])'+_REGEX_URLCHAR.source, 'g'); var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) {
function _findURLs(text)
{
_REGEX_URL.lastIndex = 0; _REGEX_URL.lastIndex = 0;
var urls = null; var urls = null;
var execResult; var execResult;
while ((execResult = _REGEX_URL.exec(text))) { while ((execResult = _REGEX_URL.exec(text)))
{
urls = (urls || []); urls = (urls || []);
var startIndex = execResult.index; var startIndex = execResult.index;
var url = execResult[0]; var url = execResult[0];

View File

@ -61,6 +61,7 @@ a img
padding: 4px 5px; padding: 4px 5px;
height: 18px; height: 18px;
width: 18px;
cursor: pointer; cursor: pointer;
@ -94,6 +95,7 @@ a img
border: inherit; border: inherit;
background: inherit; background: inherit;
visibility:hidden; visibility:hidden;
width: 0px;
} }
#editbar ul li a #editbar ul li a
{ {
@ -308,7 +310,13 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
.hidesidebar #padeditor { right: 0; } .hidesidebar #padeditor { right: 0; }
#vdraggie { #vdraggie {
background: url(static/img/vdraggie.gif) no-repeat top center; /* background: url(static/img/vdraggie.gif) no-repeat top center;*/
width:16px;
height:16px;
background-image:url('../../static/img/etherpad_lite_icons.gif');
background-repeat: no-repeat;
background-position: 0px -300px;
cursor: W-resize; cursor: W-resize;
bottom:0; bottom:0;
position:absolute; position:absolute;
@ -812,7 +820,6 @@ ul#colorpickerswatches li:hover
#chatlabel #chatlabel
{ {
cursor: default;
font-size:13px; font-size:13px;
line-height:16px; line-height:16px;
font-weight:bold; font-weight:bold;
@ -842,6 +849,7 @@ ul#colorpickerswatches li:hover
border-top-left-radius: 5px; border-top-left-radius: 5px;
border-top-right-radius: 5px; border-top-right-radius: 5px;
background-color:#fff; background-color:#fff;
cursor: pointer;
} }
#chaticon a #chaticon a
@ -876,10 +884,11 @@ ul#colorpickerswatches li:hover
#titlecross #titlecross
{ {
font-size:16px; font-size:25px;
float:right; float:right;
text-align: right; text-align: right;
text-decoration: none; text-decoration: none;
cursor: pointer;
color:#555; color:#555;
} }
@ -906,22 +915,26 @@ position: relative;
display: block; display: block;
} }
/*
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable- autohide .ui-resizable-handle { display: none; }
*/
.ui-resizable-nw { .ui-resizable-nw {
cursor: nw-resize;
width: 22px;
height: 22px;
left: 0px; top: 0px;
background-size: 100% auto;
background-image: url("../img/nw-resize.png"); background-image: url("../img/nw-resize.png");
background-repeat: no-repeat; background-repeat: no-repeat;
/* background-position: -5px -5px;*/ background-size: 100% auto;
cursor: nw-resize;
height: 22px;
left: 0;
top: 0;
width: 22px;
}
.ui-resizable-ne
{
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
} }
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right:
-5px; top: -5px;}
#importexport{ #importexport{
position:absolute; position:absolute;
@ -1046,3 +1059,30 @@ padding: 10px;
border-radius: 6px; border-radius: 6px;
opacity:.8; opacity:.8;
} }
.buttonicon{
width:16px;
height:16px;
background-image:url('../../static/img/etherpad_lite_icons.gif');
background-repeat: no-repeat;
margin-left: 1px;
margin-top: 1px;
}
#usericon
{
width:27px !important;
}
#focusprotector
{
z-index: 100;
position: absolute;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: white;
opacity:0.01;
display:none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

View File

@ -3,16 +3,29 @@ var chat = (function()
var self = { var self = {
show: function () show: function ()
{ {
$("#chaticon").hide("slide", { direction: "down" }, 500, function() $("#chaticon").hide("slide", {
direction: "down"
}, 500, function ()
{ {
$("#chatbox").show("slide", { direction: "down" }, 750, self.scrollDown); $("#chatbox").show("slide", {
$("#chatbox").resizable({ handles: 'nw', start: function(event,ui){ direction: "down"
$("#editorcontainer").hide(); }, 750, self.scrollDown);
}, stop: function(event,ui){ $("#chatbox").resizable(
$("#editorcontainer").show(); {
self.scrollDown(); handles: 'nw',
}}); start: function (event, ui)
{
$("#focusprotector").show();
},
stop: function (event, ui)
{
$("#focusprotector").hide();
$("#chatbox").css({right: "20px", bottom: "0px", left: "", top: ""});
self.scrollDown();
}
});
}); });
}, },
hide: function () hide: function ()

View File

@ -25,76 +25,75 @@
<ul id="menu_left"> <ul id="menu_left">
<li> <li>
<a onClick="window.pad&&pad.editbarClick('bold');return false" title="Bold (ctrl-B)"> <a onClick="window.pad&&pad.editbarClick('bold');return false" title="Bold (ctrl-B)">
<img src="../static/img/editbar_bold.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -116px"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('italic'); return false;" title="Italics (ctrl-I)"> <a onClick="window.pad&&pad.editbarClick('italic'); return false;" title="Italics (ctrl-I)">
<img src="../static/img/editbar_italic.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px 0px"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('underline');return false;" title="Underline (ctrl-U)"> <a onClick="window.pad&&pad.editbarClick('underline');return false;" title="Underline (ctrl-U)">
<img src="../static/img/editbar_underline.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -236px;margin-top:1px;"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('strikethrough');return false;" title="Strikethrough"> <a onClick="window.pad&&pad.editbarClick('strikethrough');return false;" title="Strikethrough">
<img src="../static/img/editbar_strikethrough.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -200px"></div>
</a> </a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('insertunorderedlist');return false;" title="Toggle Bullet List"> <a onClick="window.pad&&pad.editbarClick('insertunorderedlist');return false;" title="Toggle Bullet List">
<img src="../static/img/editbar_insertunorderedlist.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -34px"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('indent');return false;" title="Indent List"> <a onClick="window.pad&&pad.editbarClick('indent');return false;" title="Indent List">
<img src="../static/img/editbar_indent.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -52px"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('outdent');return false;" title="Unindent List"> <a onClick="window.pad&&pad.editbarClick('outdent');return false;" title="Unindent List">
<img src="../static/img/editbar_outdent.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -134px"></div>
</a> </a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('undo');return false;" title="Undo (ctrl-Z)"> <a onClick="window.pad&&pad.editbarClick('undo');return false;" title="Undo (ctrl-Z)">
<img src="../static/img/editbar_undo.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -255px"></div>
</a> </a>
</li> </li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('redo');return false;" title="Redo (ctrl-Y)"> <a onClick="window.pad&&pad.editbarClick('redo');return false;" title="Redo (ctrl-Y)">
<img src="../static/img/editbar_redo.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -166px"></div>
</a> </a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li> <li>
<a onClick="window.pad&&pad.editbarClick('clearauthorship');return false;" title="Clear Authorship Colors"> <a onClick="window.pad&&pad.editbarClick('clearauthorship');return false;" title="Clear Authorship Colors">
<img src="../static/img/editbar_clearauthorship.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -86px"></div>
</a> </a>
</li> </li>
</ul> </ul>
<ul id="menu_right"> <ul id="menu_right">
<li> <li>
<a id="readonlylink" onClick="window.pad&&pad.editbarClick('readonly');return false;" title="Create a readonly link for this pad"> <a id="readonlylink" onClick="window.pad&&pad.editbarClick('readonly');return false;" title="Create a readonly link for this pad">
<img src="../static/img/editbar_readonly.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -150px"></div>
</a> </a>
</li> </li>
<li> <li>
<a id="exportlink" onClick="window.pad&&pad.editbarClick('import_export');return false;" <a id="exportlink" onClick="window.pad&&pad.editbarClick('import_export');return false;" title="Import/Export from/to different document formats">
title="Import/Export from/to different document formats"> <div class="buttonicon" style="background-position:0px -68px"></div>
<img src="../static/img/editbar_import_export.gif" width="16" height="16" /> </a>
</a>
</li> </li>
<li> <li>
<a id="embedlink" onClick="window.pad&&pad.editbarClick('embed');return false;" title="Embed this pad"> <a id="embedlink" onClick="window.pad&&pad.editbarClick('embed');return false;" title="Embed this pad">
<img src="../static/img/editbar_embed.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -18px"></div>
</a> </a>
</li> </li>
<!-- <!--
We removed this feature cause its not worth the space it needs in the editbar We removed this feature cause its not worth the space it needs in the editbar
@ -109,23 +108,18 @@ We removed this feature cause its not worth the space it needs in the editbar
</select> </select>
</li>--> </li>-->
<li class="separator"></li> <li class="separator"></li>
<!--<li>
<a onClick="window.pad&&pad.editbarClick('chat'); return false;"
title="(Placeholder, not implemented so far) Open the chat for this pad">
<img src="../static/img/editbar_chat.gif" width="16" height="16" />
</a>
</li>-->
<li> <li>
<a id="timesliderlink" title="Show the history of this pad"> <a id="timesliderlink" title="Show the history of this pad">
<script> <script>
document.getElementById('timesliderlink').href = document.location+ '/timeslider'; document.getElementById('timesliderlink').href = document.location+ '/timeslider';
</script> </script>
<img src="../static/img/editbar_timeslider.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -218px"></div>
</a> </a>
</li> </li>
<li> <li id="usericon">
<a onClick="window.pad&&pad.editbarClick('showusers');return false;" title="Show connected users"> <a onClick="window.pad&&pad.editbarClick('showusers');return false;" title="Show connected users">
<img id="showusersicon" src="../static/img/editbar_showusers.gif" width="16" height="16" /> <span id="online_count">1</span> <div class="buttonicon" style="background-position:0px -184px;display:inline-block;"></div>
<span id="online_count">1</span>
</a> </a>
</li> </li>
</ul> </ul>
@ -256,13 +250,13 @@ Use this link to share a read-only version of your pad:<input id="readonlyInput"
<a onClick="chat.show();return false;" <a onClick="chat.show();return false;"
title="Open the chat for this pad"> title="Open the chat for this pad">
<span id="chatlabel">Chat</span> <span id="chatlabel">Chat</span>
<img src="../static/img/editbar_chat.gif" width="16" height="16" /> <div class="buttonicon" style="background-position:0px -102px;display:inline-block;"></div>
</a> </a>
<span id="chatcounter">0</span> <span id="chatcounter">0</span>
</div> </div>
<div id="chatbox"> <div id="chatbox">
<div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">x&nbsp;</a></div> <div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="chattext" class="authorColors"></div> <div id="chattext" class="authorColors"></div>
<div id="chatinputbox"> <div id="chatinputbox">
<form> <form>
@ -271,10 +265,13 @@ Use this link to share a read-only version of your pad:<input id="readonlyInput"
</div> </div>
</div> </div>
<div id="focusprotector">&nbsp;</div>
<!-- /padeditor --> <!-- /padeditor -->
<div id="modaloverlay"> <div id="modaloverlay">
<div id="modaloverlay-inner"> <div id="modaloverlay-inner">
<!-- --> <!-- -->
</div> </div>
</div> </div>
<div id="mainmodals"> <div id="mainmodals">