Merge remote branch 'pita/develop' into ep_formatting_plugin

Conflicts:
	src/static/js/pad_editbar.js
This commit is contained in:
cohitre 2012-08-13 11:37:14 -07:00
commit ebf1bba0eb
47 changed files with 1606 additions and 685 deletions

View file

@ -1,3 +1,12 @@
# v1.1
* Introduced Plugin framework
* Many bugfixes
* Faster page loading
* Various UI polishes
* Saved Revisions
* Read only Real time view
* More API functionality
# v 1.0.1 # v 1.0.1
* Updated MySQL driver, this fixes some problems with mysql * Updated MySQL driver, this fixes some problems with mysql

View file

@ -72,9 +72,8 @@ Here is the **[FAQ](https://github.com/Pita/etherpad-lite/wiki/FAQ)**
**As any user (we recommend creating a separate user called etherpad-lite):** **As any user (we recommend creating a separate user called etherpad-lite):**
<ol start="3"> <ol start="3">
<li>Move to a folder where you want to install Etherpad Lite. Clone the git repository <code>git clone 'git://github.com/Pita/etherpad-lite.git'</code><br>&nbsp;</li> <li>Move to a folder where you want to install Etherpad Lite. Clone the git repository <code>git clone 'git://github.com/Pita/etherpad-lite.git'</code><br></li>
<li>Change into the directory containing the Etherpad Lite source code clone with <code>cd etherpad-lite</code><br> </li> <li>Change into the directory containing the Etherpad Lite source code clone with <code>cd etherpad-lite</code><br></li>
<li>Install the dependencies with <code>bin/installDeps.sh</code><br>&nbsp;</li>
<li>Start it with <code>bin/run.sh</code><br>&nbsp;</li> <li>Start it with <code>bin/run.sh</code><br>&nbsp;</li>
<li>Open your web browser and visit <a href="http://localhost:9001">http://localhost:9001</a>. You like it? Look at the 'Next Steps' section below</li> <li>Open your web browser and visit <a href="http://localhost:9001">http://localhost:9001</a>. You like it? Look at the 'Next Steps' section below</li>
</ol> </ol>

View file

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
NODE_VERSION="0.6.5" NODE_VERSION="0.8.4"
#Move to the folder where ep-lite is installed #Move to the folder where ep-lite is installed
cd `dirname $0` cd `dirname $0`
@ -41,7 +41,7 @@ echo "do a normal unix install first..."
bin/installDeps.sh || exit 1 bin/installDeps.sh || exit 1
echo "copy the windows settings template..." echo "copy the windows settings template..."
cp settings.json.template_windows settings.json cp settings.json.template settings.json
echo "resolve symbolic links..." echo "resolve symbolic links..."
cp -rL node_modules node_modules_resolved cp -rL node_modules node_modules_resolved
@ -50,7 +50,7 @@ mv node_modules_resolved node_modules
echo "download windows node..." echo "download windows node..."
cd bin cd bin
wget "http://nodejs.org/dist/v$NODE_VERSION/node.exe" -O node.exe wget "http://nodejs.org/dist/v$NODE_VERSION/node.exe" -O ../node.exe
echo "create the zip..." echo "create the zip..."
cd /tmp cd /tmp

View file

@ -35,8 +35,9 @@ fi
#check node version #check node version
NODE_VERSION=$(node --version) NODE_VERSION=$(node --version)
if [ ! $(echo $NODE_VERSION | cut -d "." -f 1-2) = "v0.6" ]; then NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x" >&2 if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ]; then
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x or v0.8.x" >&2
exit 1 exit 1
fi fi

39
bin/installOnWindows.bat Normal file
View file

@ -0,0 +1,39 @@
@echo off
:: change directory to etherpad-lite root
cd /D "%~dp0\.."
:: Is node installed?
cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && exit /B 1 )
echo _
echo Checking node version...
set check_version="if(['6','8'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.6.x or v0.8.x'); process.exit(1) }"
cmd /C node -e %check_version% || exit /B 1
echo _
echo Installing etherpad-lite and dependencies...
cmd /C npm install src/ || exit /B 1
echo _
echo Copying custom templates...
set custom_dir=node_modules\ep_etherpad-lite\static\custom
FOR %%f IN (index pad timeslider) DO (
if NOT EXIST "%custom_dir%\%%f.js" copy "%custom_dir%\js.template" "%custom_dir%\%%f.js"
if NOT EXIST "%custom_dir%\%%f.css" copy "%custom_dir%\css.template" "%custom_dir%\%%f.css"
)
echo _
echo Clearing cache...
del /S var\minified*
echo _
echo Setting up settings.json...
IF NOT EXIST settings.json (
echo Can't find settings.json.
echo Copying settings.json.template...
cmd /C copy settings.json.template settings.json || exit /B 1
)
echo _
echo Installed Etherpad-lite!

View file

@ -1,48 +0,0 @@
/*
This file must be valid JSON. But comments are allowed
Please edit settings.json, not settings.json.template
*/
{
//Ip and port which etherpad should bind at
"ip": "0.0.0.0",
"port" : 9001,
//The Type of the database. You can choose between sqlite and mysql
"dbType" : "dirty",
//the database specific settings
"dbSettings" : {
"filename" : "var/dirty.db"
},
/* An Example of MySQL Configuration
"dbType" : "mysql",
"dbSettings" : {
"user" : "root",
"host" : "localhost",
"password": "",
"database": "store"
},
*/
//the default text of a pad
"defaultPadText" : "Welcome to Etherpad Lite!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad Lite on Github: http:\/\/j.mp/ep-lite\n",
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
"requireSession" : false,
/* Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. */
"editOnly" : false,
/* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
but makes it impossible to debug the javascript/css */
"minify" : false,
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/
"abiword" : null,
/* cache 6 hours = 1000*60*60*6 */
"maxAge": 21600000
}

View file

@ -1,5 +1,9 @@
{ {
"parts": [ "parts": [
{ "name": "express", "hooks": {
"createServer": "ep_etherpad-lite/node/hooks/express:createServer",
"restartServer": "ep_etherpad-lite/node/hooks/express:restartServer"
} },
{ "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } }, { "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } },
{ "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } }, { "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
{ "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } }, { "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },

View file

@ -47,6 +47,8 @@ exports.createGroupPad = groupManager.createGroupPad;
exports.createAuthor = authorManager.createAuthor; exports.createAuthor = authorManager.createAuthor;
exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor; exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
exports.listPadsOfAuthor = authorManager.listPadsOfAuthor;
exports.padUsersCount = padMessageHandler.padUsersCount;
/**********************/ /**********************/
/**SESSION FUNCTIONS***/ /**SESSION FUNCTIONS***/
@ -152,6 +154,13 @@ Example returns:
*/ */
exports.setText = function(padID, text, callback) exports.setText = function(padID, text, callback)
{ {
//text is required
if(typeof text != "string")
{
callback(new customError("text is no string","apierror"));
return;
}
//get the pad //get the pad
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
@ -282,6 +291,27 @@ exports.getRevisionsCount = function(padID, callback)
}); });
} }
/**
getLastEdited(padID) returns the timestamp of the last revision of the pad
Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getLastEdited = function(padID, callback)
{
//get the pad
getPadSafe(padID, true, function(err, pad)
{
if(ERR(err, callback)) return;
pad.getLastEdit(function(err, value) {
if(ERR(err, callback)) return;
callback(null, {lastEdited: value});
});
});
}
/** /**
createPad(padName [, text]) creates a new pad in this group createPad(padName [, text]) creates a new pad in this group
@ -463,6 +493,59 @@ exports.isPasswordProtected = function(padID, callback)
}); });
} }
/**
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null}
*/
exports.listAuthorsOfPad = function(padID, callback)
{
//get the pad
getPadSafe(padID, true, function(err, pad)
{
if(ERR(err, callback)) return;
callback(null, {authorIDs: pad.getAllAuthors()});
});
}
/**
sendClientsMessage(padID, msg) sends a message to all clients connected to the
pad, possibly for the purpose of signalling a plugin.
Note, this will only accept strings from the HTTP API, so sending bogus changes
or chat messages will probably not be possible.
The resulting message will be structured like so:
{
type: 'COLLABROOM',
data: {
type: <msg>,
time: <time the message was sent>
}
}
Example returns:
{code: 0, message:"ok"}
{code: 1, message:"padID does not exist"}
*/
exports.sendClientsMessage = function (padID, msg, callback) {
getPadSafe(padID, true, function (err, pad) {
if (ERR(err, callback)) {
return;
} else {
padMessageHandler.handleCustomMessage(padID, msg, callback);
}
} );
}
/******************************/ /******************************/
/** INTERNAL HELPER FUNCTIONS */ /** INTERNAL HELPER FUNCTIONS */
/******************************/ /******************************/

View file

@ -55,6 +55,7 @@ exports.getAuthor4Token = function (token, callback)
/** /**
* Returns the AuthorID for a mapper. * Returns the AuthorID for a mapper.
* @param {String} token The mapper * @param {String} token The mapper
* @param {String} name The name of the author (optional)
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
@ -153,6 +154,7 @@ exports.getAuthorColorId = function (author, callback)
/** /**
* Sets the color Id of the author * Sets the color Id of the author
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} colorId The color id of the author
* @param {Function} callback (optional) * @param {Function} callback (optional)
*/ */
exports.setAuthorColorId = function (author, colorId, callback) exports.setAuthorColorId = function (author, colorId, callback)
@ -173,9 +175,95 @@ exports.getAuthorName = function (author, callback)
/** /**
* Sets the name of the author * Sets the name of the author
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} name The name of the author
* @param {Function} callback (optional) * @param {Function} callback (optional)
*/ */
exports.setAuthorName = function (author, name, callback) exports.setAuthorName = function (author, name, callback)
{ {
db.setSub("globalAuthor:" + author, ["name"], name, callback); db.setSub("globalAuthor:" + author, ["name"], name, callback);
} }
/**
* Returns an array of all pads this author contributed to
* @param {String} author The id of the author
* @param {Function} callback (optional)
*/
exports.listPadsOfAuthor = function (authorID, callback)
{
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
*/
//get the globalAuthor
db.get("globalAuthor:" + authorID, function(err, author)
{
if(ERR(err, callback)) return;
//author does not exists
if(author == null)
{
callback(new customError("authorID does not exist","apierror"))
}
//everything is fine, return the pad IDs
else
{
var pads = [];
if(author.padIDs != null)
{
for (var padId in author.padIDs)
{
pads.push(padId);
}
}
callback(null, {padIDs: pads});
}
});
}
/**
* Adds a new pad to the list of contributions
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.addPad = function (authorID, padID)
{
//get the entry
db.get("globalAuthor:" + authorID, function(err, author)
{
if(ERR(err)) return;
if(author == null) return;
//the entry doesn't exist so far, let's create it
if(author.padIDs == null)
{
author.padIDs = {};
}
//add the entry for this pad
author.padIDs[padID] = 1;// anything, because value is not used
//save the new element back
db.set("globalAuthor:" + authorID, author);
});
}
/**
* Removes a pad from the list of contributions
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.removePad = function (authorID, padID)
{
db.get("globalAuthor:" + authorID, function (err, author)
{
if(ERR(err)) return;
if(author == null) return;
if(author.padIDs != null)
{
//remove pad from author
delete author.padIDs[padID];
db.set("globalAuthor:" + authorID, author);
}
});
}

View file

@ -82,6 +82,10 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
db.set("pad:"+this.id+":revs:"+newRev, newRevData); db.set("pad:"+this.id+":revs:"+newRev, newRevData);
this.saveToDatabase(); this.saveToDatabase();
// set the author to pad
if(author)
authorManager.addPad(author, this.id);
}; };
//save all attributes to the database //save all attributes to the database
@ -102,6 +106,12 @@ Pad.prototype.saveToDatabase = function saveToDatabase(){
db.set("pad:"+this.id, dbObject); db.set("pad:"+this.id, dbObject);
} }
// get time of last edit (changeset application)
Pad.prototype.getLastEdit = function getLastEdit(callback){
var revNum = this.getHeadRevisionNumber();
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
}
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) { Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
}; };
@ -436,6 +446,18 @@ Pad.prototype.remove = function remove(callback) {
db.remove("pad:"+padID+":revs:"+i); db.remove("pad:"+padID+":revs:"+i);
} }
callback();
},
//remove pad from all authors who contributed
function(callback)
{
var authorIDs = _this.getAllAuthors();
authorIDs.forEach(function (authorID)
{
authorManager.removePad(authorID, padID);
});
callback(); callback();
} }
], callback); ], callback);

View file

@ -139,7 +139,7 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it //the entry doesn't exist so far, let's create it
if(group2sessions == null) if(group2sessions == null || group2sessions.sessionIDs == null)
{ {
group2sessions = {sessionIDs : {}}; group2sessions = {sessionIDs : {}};
} }
@ -162,7 +162,7 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it //the entry doesn't exist so far, let's create it
if(author2sessions == null) if(author2sessions == null || author2sessions.sessionIDs == null)
{ {
author2sessions = {sessionIDs : {}}; author2sessions = {sessionIDs : {}};
} }

View file

@ -47,6 +47,7 @@ var functions = {
"createGroupPad" : ["groupID", "padName", "text"], "createGroupPad" : ["groupID", "padName", "text"],
"createAuthor" : ["name"], "createAuthor" : ["name"],
"createAuthorIfNotExistsFor": ["authorMapper" , "name"], "createAuthorIfNotExistsFor": ["authorMapper" , "name"],
"listPadsOfAuthor" : ["authorID"],
"createSession" : ["groupID", "authorID", "validUntil"], "createSession" : ["groupID", "authorID", "validUntil"],
"deleteSession" : ["sessionID"], "deleteSession" : ["sessionID"],
"getSessionInfo" : ["sessionID"], "getSessionInfo" : ["sessionID"],
@ -57,12 +58,16 @@ var functions = {
"getHTML" : ["padID", "rev"], "getHTML" : ["padID", "rev"],
"setHTML" : ["padID", "html"], "setHTML" : ["padID", "html"],
"getRevisionsCount" : ["padID"], "getRevisionsCount" : ["padID"],
"getLastEdited" : ["padID"],
"deletePad" : ["padID"], "deletePad" : ["padID"],
"getReadOnlyID" : ["padID"], "getReadOnlyID" : ["padID"],
"setPublicStatus" : ["padID", "publicStatus"], "setPublicStatus" : ["padID", "publicStatus"],
"getPublicStatus" : ["padID"], "getPublicStatus" : ["padID"],
"setPassword" : ["padID", "password"], "setPassword" : ["padID", "password"],
"isPasswordProtected" : ["padID"] "isPasswordProtected" : ["padID"],
"listAuthorsOfPad" : ["padID"],
"padUsersCount" : ["padID"],
"sendClientsMessage" : ["padID", "msg"]
}; };
/** /**

View file

@ -33,6 +33,7 @@ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins.js");
var log4js = require('log4js'); var log4js = require('log4js');
var messageLogger = log4js.getLogger("message"); var messageLogger = log4js.getLogger("message");
var _ = require('underscore'); var _ = require('underscore');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
/** /**
* A associative array that saves which sessions belong to a pad * A associative array that saves which sessions belong to a pad
@ -158,6 +159,7 @@ exports.handleDisconnect = function(client)
*/ */
exports.handleMessage = function(client, message) exports.handleMessage = function(client, message)
{ {
if(message == null) if(message == null)
{ {
messageLogger.warn("Message is null!"); messageLogger.warn("Message is null!");
@ -169,6 +171,24 @@ exports.handleMessage = function(client, message)
return; return;
} }
var handleMessageHook = function(callback){
var dropMessage = false;
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
// handleMessage will be called, even if the client is not authorized
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( messages ) {
_.each(messages, function(newMessage){
if ( newmessage === null ) {
dropMessage = true;
}
});
// If no plugins explicitly told us to drop the message, its ok to proceed
if(!dropMessage){ callback() };
});
}
var finalHandler = function () {
//Check what type of message we get and delegate to the other methodes //Check what type of message we get and delegate to the other methodes
if(message.type == "CLIENT_READY") { if(message.type == "CLIENT_READY") {
handleClientReady(client, message); handleClientReady(client, message);
@ -194,6 +214,44 @@ exports.handleMessage = function(client, message)
} else { } else {
messageLogger.warn("Dropped message, unknown Message Type " + message.type); messageLogger.warn("Dropped message, unknown Message Type " + message.type);
} }
};
if (message) {
async.series([
handleMessageHook,
//check permissions
function(callback)
{
if(!message.padId){
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
callback();
return;
}
// Note: message.sessionID is an entirely different kind of
// session from the sessions we use here! Beware! FIXME: Call
// our "sessions" "connections".
// FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly
securityManager.checkAccess(message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
//access was granted
if(statusObject.accessStatus == "grant")
{
callback();
}
//no access, send the client a message that tell him why
else
{
client.json.send({accessStatus: statusObject.accessStatus})
}
});
},
finalHandler
]);
}
} }
@ -214,6 +272,28 @@ function handleSaveRevisionMessage(client, message){
}); });
} }
/**
* Handles a custom message (sent via HTTP API request)
*
* @param padID {Pad} the pad to which we're sending this message
* @param msg {String} the message we're sending
*/
exports.handleCustomMessage = function (padID, msg, cb) {
var time = new Date().getTime();
var msg = {
type: 'COLLABROOM',
data: {
type: msg,
time: time
}
};
for (var i in pad2sessions[padID]) {
socketio.sockets.sockets[pad2sessions[padID][i]].json.send(msg);
}
cb(null, {});
}
/** /**
* Handles a Chat Message * Handles a Chat Message
* @param client the client that send this message * @param client the client that send this message
@ -1022,10 +1102,19 @@ function handleChangesetRequest(client, message)
var granularity = message.data.granularity; var granularity = message.data.granularity;
var start = message.data.start; var start = message.data.start;
var end = start + (100 * granularity); var end = start + (100 * granularity);
var padId = message.padId; var padIds;
async.series([
function (callback) {
readOnlyManager.getIds(message.padId, function(err, value) {
if(ERR(err, callback)) return;
padIds = value;
callback();
});
},
function (callback) {
//build the requested rough changesets and send them back //build the requested rough changesets and send them back
getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo) getChangesetInfo(padIds.padId, start, end, granularity, function(err, changesetInfo)
{ {
ERR(err); ERR(err);
@ -1034,6 +1123,8 @@ function handleChangesetRequest(client, message)
client.json.send({type: "CHANGESET_REQ", data: data}); client.json.send({type: "CHANGESET_REQ", data: data});
}); });
}
]);
} }
@ -1308,3 +1399,14 @@ function composePadChangesets(padId, startNum, endNum, callback)
callback(null, changeset); callback(null, changeset);
}); });
} }
/**
* Get the number of users in a pad
*/
exports.padUsersCount = function (padID, callback) {
if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) {
callback(null, {padUsersCount: 0});
} else {
callback(null, {padUsersCount: pad2sessions[padID].length});
}
}

63
src/node/hooks/express.js Normal file
View file

@ -0,0 +1,63 @@
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var express = require('express');
var settings = require('../utils/Settings');
var fs = require('fs');
var path = require('path');
var npm = require("npm/lib/npm.js");
var _ = require("underscore");
var server;
var serverName;
exports.createServer = function () {
//try to get the git version
var version = "";
try
{
var rootPath = path.resolve(npm.dir, '..');
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
}
catch(e)
{
console.warn("Can't get git version for server header\n" + e.message)
}
console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
exports.restartServer();
console.log("You can access your Etherpad-Lite instance at http://" + settings.ip + ":" + settings.port + "/");
if(!_.isEmpty(settings.users)){
console.log("The plugin admin page is at http://" + settings.ip + ":" + settings.port + "/admin/plugins");
}
else{
console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
}
}
exports.restartServer = function () {
if (server) {
console.log("Restarting express server");
server.close();
}
server = express.createServer();
server.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
server.configure(function() {
hooks.callAll("expressConfigure", {"app": server});
});
hooks.callAll("expressCreateServer", {"app": server});
server.listen(settings.port, settings.ip);
}

View file

@ -15,7 +15,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//the pad id was sanitized, so we redirect to the sanitized version //the pad id was sanitized, so we redirect to the sanitized version
if(sanitizedPadId != padId) if(sanitizedPadId != padId)
{ {
var real_url = req.url.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId); var real_url = sanitizedPadId;
var query = url.parse(req.url).query; var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query; if ( query ) real_url += '?' + query;
res.header('Location', real_url); res.header('Location', real_url);

View file

@ -7,8 +7,75 @@ var Yajsml = require('yajsml');
var fs = require("fs"); var fs = require("fs");
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var _ = require("underscore"); var _ = require("underscore");
var urlutil = require('url');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// What follows is a terrible hack to avoid loop-back within the server.
// TODO: Serve files from another service, or directly from the file system.
function requestURI(url, method, headers, callback, redirectCount) {
var parsedURL = urlutil.parse(url);
var status = 500, headers = {}, content = [];
var mockRequest = {
url: url
, method: method
, params: {filename: parsedURL.path.replace(/^\/static\//, '')}
, headers: headers
};
var mockResponse = {
writeHead: function (_status, _headers) {
status = _status;
for (var header in _headers) {
if (Object.prototype.hasOwnProperty.call(_headers, header)) {
headers[header] = _headers[header];
}
}
}
, setHeader: function (header, value) {
headers[header.toLowerCase()] = value.toString();
}
, header: function (header, value) {
headers[header.toLowerCase()] = value.toString();
}
, write: function (_content) {
_content && content.push(_content);
}
, end: function (_content) {
_content && content.push(_content);
callback(status, headers, content.join(''));
}
};
minify.minify(mockRequest, mockResponse);
}
function requestURIs(locations, method, headers, callback) {
var pendingRequests = locations.length;
var responses = [];
function respondFor(i) {
return function (status, headers, content) {
responses[i] = [status, headers, content];
if (--pendingRequests == 0) {
completed();
}
};
}
for (var i = 0, ii = locations.length; i < ii; i++) {
requestURI(locations[i], method, headers, respondFor(i));
}
function completed() {
var statuss = responses.map(function (x) {return x[0]});
var headerss = responses.map(function (x) {return x[1]});
var contentss = responses.map(function (x) {return x[2]});
callback(statuss, headerss, contentss);
};
}
// Cache both minified and static. // Cache both minified and static.
var assetCache = new CachingMiddleware; var assetCache = new CachingMiddleware;
args.app.all('/(javascripts|static)/*', assetCache.handle); args.app.all('/(javascripts|static)/*', assetCache.handle);
@ -24,6 +91,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
, rootURI: 'http://localhost:' + settings.port + '/static/js/' , rootURI: 'http://localhost:' + settings.port + '/static/js/'
, libraryPath: 'javascripts/lib/' , libraryPath: 'javascripts/lib/'
, libraryURI: 'http://localhost:' + settings.port + '/static/plugins/' , libraryURI: 'http://localhost:' + settings.port + '/static/plugins/'
, requestURIs: requestURIs // Loop-back is causing problems, this is a workaround.
}); });
var StaticAssociator = Yajsml.associators.StaticAssociator; var StaticAssociator = Yajsml.associators.StaticAssociator;

View file

@ -88,6 +88,8 @@ exports.basicAuth = function (req, res, next) {
}); });
} }
var secret = null;
exports.expressConfigure = function (hook_name, args, cb) { exports.expressConfigure = function (hook_name, args, cb) {
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
@ -100,10 +102,15 @@ exports.expressConfigure = function (hook_name, args, cb) {
* name) to a javascript identifier compatible string. Makes code * name) to a javascript identifier compatible string. Makes code
* handling it cleaner :) */ * handling it cleaner :) */
args.app.sessionStore = new express.session.MemoryStore(); if (!exports.sessionStore) {
exports.sessionStore = new express.session.MemoryStore();
secret = randomString(32);
}
args.app.sessionStore = exports.sessionStore;
args.app.use(express.session({store: args.app.sessionStore, args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid', key: 'express_sid',
secret: apikey = randomString(32)})); secret: secret}));
args.app.use(exports.basicAuth); args.app.use(exports.basicAuth);
} }

View file

@ -22,36 +22,13 @@
*/ */
var log4js = require('log4js'); var log4js = require('log4js');
var fs = require('fs');
var settings = require('./utils/Settings'); var settings = require('./utils/Settings');
var db = require('./db/DB'); var db = require('./db/DB');
var async = require('async'); var async = require('async');
var express = require('express');
var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm/lib/npm.js"); var npm = require("npm/lib/npm.js");
var _ = require("underscore");
//try to get the git version
var version = "";
try
{
var rootPath = path.resolve(npm.dir, '..');
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
}
catch(e)
{
console.warn("Can't get git version for server header\n" + e.message)
}
console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
//set loglevel //set loglevel
log4js.setGlobalLogLevel(settings.loglevel); log4js.setGlobalLogLevel(settings.loglevel);
@ -69,33 +46,17 @@ async.waterfall([
console.info("Installed plugins: " + plugins.formatPlugins()); console.info("Installed plugins: " + plugins.formatPlugins());
console.debug("Installed parts:\n" + plugins.formatParts()); console.debug("Installed parts:\n" + plugins.formatParts());
console.debug("Installed hooks:\n" + plugins.formatHooks()); console.debug("Installed hooks:\n" + plugins.formatHooks());
// Call loadSettings hook
hooks.aCallAll("loadSettings", { settings: settings });
callback(); callback();
}, },
//initalize the http server //initalize the http server
function (callback) function (callback)
{ {
//create server hooks.callAll("createServer", {});
var app = express.createServer();
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
app.configure(function() { hooks.callAll("expressConfigure", {"app": app}); });
hooks.callAll("expressCreateServer", {"app": app});
//let the server listen
app.listen(settings.port, settings.ip);
console.log("You can access your Etherpad-Lite instance at http://" + settings.ip + ":" + settings.port + "/");
if(!_.isEmpty(settings.users)){
console.log("The plugin admin page is at http://" + settings.ip + ":" + settings.port + "/admin/plugins");
}
else{
console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
}
callback(null); callback(null);
} }
]); ]);

View file

@ -252,8 +252,13 @@ function getDokuWikiFromAtext(pad, atext)
if (line.listLevel && lineContent) if (line.listLevel && lineContent)
{ {
if (line.listTypeName == "number")
{
pieces.push(new Array(line.listLevel + 1).join(' ') + ' - ');
} else {
pieces.push(new Array(line.listLevel + 1).join(' ') + '* '); pieces.push(new Array(line.listLevel + 1).join(' ') + '* ');
} }
}
pieces.push(lineContent); pieces.push(lineContent);
} }

View file

@ -20,7 +20,7 @@ var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security'); var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
function getPadPlainText(pad, revNum) function getPadPlainText(pad, revNum)
{ {
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
@ -402,9 +402,23 @@ function getHTMLFromAtext(pad, atext)
} }
lists.length--; lists.length--;
} }
var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
{
line: line,
apool: apool,
attribLine: attribLines[i],
text: textLines[i]
}, " ", " ", "");
if (lineContentFromHook)
{
pieces.push(lineContentFromHook, '');
}
else
{
pieces.push(lineContent, '<br>'); pieces.push(lineContent, '<br>');
} }
} }
}
for (var k = lists.length - 1; k >= 0; k--) for (var k = lists.length - 1; k >= 0; k--)
{ {
@ -469,6 +483,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
var head = var head =
(noDocType ? '' : '<!doctype html>\n') + (noDocType ? '' : '<!doctype html>\n') +
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' + '<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
'<title>' + Security.escapeHTML(padId) + '</title>\n' +
'<meta charset="utf-8">\n' + '<meta charset="utf-8">\n' +
'<style> * { font-family: arial, sans-serif;\n' + '<style> * { font-family: arial, sans-serif;\n' +
'font-size: 13px;\n' + 'font-size: 13px;\n' +

View file

@ -24,6 +24,7 @@ var os = require("os");
var path = require('path'); var path = require('path');
var argv = require('./Cli').argv; var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js"); var npm = require("npm/lib/npm.js");
var vm = require('vm');
/* Root path of the installation */ /* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, "..")); exports.root = path.normalize(path.join(npm.dir, ".."));
@ -45,6 +46,7 @@ exports.dbType = "dirty";
* This setting is passed with dbType to ueberDB to set up the database * This setting is passed with dbType to ueberDB to set up the database
*/ */
exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };
/** /**
* The default Text of a new pad * The default Text of a new pad
*/ */
@ -102,32 +104,24 @@ exports.abiwordAvailable = function()
// Discover where the settings file lives // Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json"; var settingsFilename = argv.settings || "settings.json";
if (settingsFilename.charAt(0) != '/') { settingsFilename = path.resolve(path.join(root, settingsFilename));
settingsFilename = path.normalize(path.join(root, settingsFilename));
}
var settingsStr var settingsStr;
try{ try{
//read the settings sync //read the settings sync
settingsStr = fs.readFileSync(settingsFilename).toString(); settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e){ } catch(e){
console.warn('No settings file found. Using defaults.'); console.warn('No settings file found. Continuing using defaults!');
settingsStr = '{}';
} }
//remove all comments // try to parse the settings
settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,"");
//try to parse the settings
var settings; var settings;
try try {
{ if(settingsStr) {
settings = JSON.parse(settingsStr); settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json");
} }
catch(e) }catch(e){
{ console.error('There was an error processing your settings.json file: '+e.message);
console.error("There is a syntax error in your settings.json file");
console.error(e.message);
process.exit(1); process.exit(1);
} }
@ -148,8 +142,7 @@ for(var i in settings)
//this setting is unkown, output a warning and throw it away //this setting is unkown, output a warning and throw it away
else else
{ {
console.warn("Unkown Setting: '" + i + "'"); console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
console.warn("This setting doesn't exist or it was removed");
} }
} }

View file

@ -21,9 +21,12 @@ var path = require('path');
var zlib = require('zlib'); var zlib = require('zlib');
var util = require('util'); var util = require('util');
var settings = require('./Settings'); var settings = require('./Settings');
var semver = require('semver');
var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/')); var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined; CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {}; var responseCache = {};

View file

@ -10,15 +10,15 @@
"name": "Robin Buse" } "name": "Robin Buse" }
], ],
"dependencies" : { "dependencies" : {
"yajsml" : "1.1.3", "yajsml" : "1.1.4",
"request" : "2.9.100", "request" : "2.9.100",
"require-kernel" : "1.0.5", "require-kernel" : "1.0.5",
"resolve" : "0.2.1", "resolve" : "0.2.1",
"socket.io" : "0.9.6", "socket.io" : "0.9.x",
"ueberDB" : "0.1.7", "ueberDB" : "0.1.7",
"async" : "0.1.18", "async" : "0.1.x",
"express" : "2.5.8", "express" : "2.5.x",
"connect" : "1.8.7", "connect" : "1.x",
"clean-css" : "0.3.2", "clean-css" : "0.3.2",
"uglify-js" : "1.2.5", "uglify-js" : "1.2.5",
"formidable" : "1.0.9", "formidable" : "1.0.9",

View file

@ -401,180 +401,36 @@ table#otheruserstable {
color: #888; color: #888;
font-style: italic; font-style: italic;
} }
.modaldialog.cboxreconnecting .modaldialog-inner,
.modaldialog.cboxconnecting .modaldialog-inner { #connectivity {
background: url(../../static/img/connectingbar.gif) no-repeat center 60px; z-index: 600 !important;
height: 100px;
} }
.modaldialog.cboxreconnecting,
.modaldialog.cboxconnecting, #connectivity * {
.modaldialog.cboxdisconnected {
background: #8FCDE0
}
.cboxdisconnected #connectionboxinner div {
display: none
}
.cboxdisconnected_userdup #connectionboxinner #disconnected_userdup {
display: block
}
.cboxdisconnected_deleted #connectionboxinner #disconnected_deleted {
display: block
}
.cboxdisconnected_initsocketfail #connectionboxinner #disconnected_initsocketfail {
display: block
}
.cboxdisconnected_looping #connectionboxinner #disconnected_looping {
display: block
}
.cboxdisconnected_slowcommit #connectionboxinner #disconnected_slowcommit {
display: block
}
.cboxdisconnected_unauth #connectionboxinner #disconnected_unauth {
display: block
}
.cboxdisconnected_unknown #connectionboxinner #disconnected_unknown {
display: block
}
.cboxdisconnected_initsocketfail #connectionboxinner #reconnect_advise,
.cboxdisconnected_looping #connectionboxinner #reconnect_advise,
.cboxdisconnected_slowcommit #connectionboxinner #reconnect_advise,
.cboxdisconnected_unknown #connectionboxinner #reconnect_advise {
display: block
}
.cboxdisconnected div#reconnect_form {
display: block
}
.cboxdisconnected .disconnected h2 {
display: none
}
.cboxdisconnected .disconnected .h2_disconnect {
display: block
}
.cboxdisconnected_userdup .disconnected h2.h2_disconnect {
display: none
}
.cboxdisconnected_userdup .disconnected h2.h2_userdup {
display: block
}
.cboxdisconnected_unauth .disconnected h2.h2_disconnect {
display: none
}
.cboxdisconnected_unauth .disconnected h2.h2_unauth {
display: block
}
#connectionstatus {
position: absolute;
width: 37px;
height: 41px;
overflow: hidden;
right: 0;
z-index: 11;
}
#connectionboxinner .connecting {
margin-top: 20px;
font-size: 2.0em;
color: #555;
text-align: center;
display: none; display: none;
} }
.cboxconnecting #connectionboxinner .connecting {
display: block #connectivity .visible,
} #connectivity .visible * {
#connectionboxinner .disconnected h2 { display: block;
font-size: 1.8em;
color: #333;
text-align: left;
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 10px;
}
#connectionboxinner .disconnected p {
margin: 10px 10px;
font-size: 1.2em;
line-height: 1.1;
color: #333;
}
#connectionboxinner .disconnected {
display: none
}
.cboxdisconnected #connectionboxinner .disconnected {
display: block
}
#connectionboxinner .reconnecting {
margin-top: 20px;
font-size: 1.6em;
color: #555;
text-align: center;
display: none;
}
.cboxreconnecting #connectionboxinner .reconnecting {
display: block
} }
#reconnect_form button { #reconnect_form button {
font-size: 12pt; font-size: 12pt;
padding: 5px; padding: 5px;
} }
#mainmodals {
z-index: 600; /* higher than the modals themselves: */
}
.modalfield {
font-size: 1.2em;
padding: 1px;
border: 1px solid #bbb;
}
#mainmodals .editempty {
color: #aaa
}
.modaldialog {
position: absolute;
top: 100px;
left: 50%;
margin-left: -243px;
width: 485px;
display: none;
z-index: 501;
zoom: 1;
overflow: hidden;
background: white;
border: 1px solid #999;
}
.modaldialog .modaldialog-inner {
padding: 10pt
}
.modaldialog .modaldialog-hide {
float: right;
background-repeat: no-repeat;
background-image: url(static/img/sharebox4.gif);
display: block;
width: 22px;
height: 22px;
background-position: -454px -6px;
margin-right: -5px;
margin-top: -5px;
}
.modaldialog label,
.modaldialog h1 {
color: #222222;
font-size: 125%;
font-weight: bold;
}
.modaldialog th {
vertical-align: top;
text-align: left;
}
#modaloverlay { .toolbar #overlay {
z-index: 500; z-index: 500;
display: none; display: none;
background-repeat: repeat-both; background-repeat: repeat-both;
width: 100%; width: 100%;
position: absolute; position: absolute;
height: 100%; height: inherit;
left: 0; left: 0;
top: 0; top: 0;
} }
* html #modaloverlay { * html #overlay {
/* for IE 6+ */ /* for IE 6+ */
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100); filter: alpha(opacity=100);
@ -874,6 +730,10 @@ input[type=checkbox] {
.popup input[type=text], #users input[type=text] { .popup input[type=text], #users input[type=text] {
outline: none; outline: none;
} }
.popup button {
padding: 5px;
font-size: 14px;
}
.popup a { .popup a {
text-decoration: none text-decoration: none
} }
@ -895,6 +755,7 @@ input[type=checkbox] {
#settings, #settings,
#importexport, #importexport,
#embed, #embed,
#connectivity,
#users { #users {
position: absolute; position: absolute;
top: 36px; top: 36px;
@ -914,15 +775,6 @@ input[type=checkbox] {
border-left: 1px solid #ccc !important; border-left: 1px solid #ccc !important;
width: 185px !important; width: 185px !important;
} }
@media screen and (max-width: 960px) {
.modaldialog {
position: relative;
margin: 0 auto;
width: 80%;
top: 40px;
left: 0;
}
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.toolbar ul li.separator { .toolbar ul li.separator {
display: none; display: none;
@ -986,6 +838,7 @@ input[type=checkbox] {
} }
#settings, #settings,
#importexport, #importexport,
#connectivity,
#embed { #embed {
left: 0; left: 0;
top: 0; top: 0;

View file

@ -150,6 +150,13 @@
margin-top: 0; margin-top: 0;
padding-right: 6px; padding-right: 6px;
} }
#settings,
#importexport,
#embed,
#connectivity,
#users {
top: 62px;
}
#importexport .popup { #importexport .popup {
width: 185px width: 185px
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -27,8 +27,6 @@
var AttributePool = require("./AttributePool"); var AttributePool = require("./AttributePool");
var _opt = null;
/** /**
* ==================== General Util Functions ======================= * ==================== General Util Functions =======================
*/ */
@ -127,22 +125,13 @@ exports.opIterator = function (opsStr, optStartIndex) {
function nextRegexMatch() { function nextRegexMatch() {
prevIndex = curIndex; prevIndex = curIndex;
var result; var result;
if (_opt) {
result = _opt.nextOpInString(opsStr, curIndex);
if (result) {
if (result.opcode() == '?') {
exports.error("Hit error opcode in op stream");
}
curIndex = result.lastIndex();
}
} else {
regex.lastIndex = curIndex; regex.lastIndex = curIndex;
result = regex.exec(opsStr); result = regex.exec(opsStr);
curIndex = regex.lastIndex; curIndex = regex.lastIndex;
if (result[0] == '?') { if (result[0] == '?') {
exports.error("Hit error opcode in op stream"); exports.error("Hit error opcode in op stream");
} }
}
return result; return result;
} }
var regexResult = nextRegexMatch(); var regexResult = nextRegexMatch();
@ -150,13 +139,7 @@ exports.opIterator = function (opsStr, optStartIndex) {
function next(optObj) { function next(optObj) {
var op = (optObj || obj); var op = (optObj || obj);
if (_opt && regexResult) { if (regexResult[0]) {
op.attribs = regexResult.attribs();
op.lines = regexResult.lines();
op.chars = regexResult.chars();
op.opcode = regexResult.opcode();
regexResult = nextRegexMatch();
} else if ((!_opt) && regexResult[0]) {
op.attribs = regexResult[1]; op.attribs = regexResult[1];
op.lines = exports.parseNum(regexResult[2] || 0); op.lines = exports.parseNum(regexResult[2] || 0);
op.opcode = regexResult[3]; op.opcode = regexResult[3];
@ -169,7 +152,7 @@ exports.opIterator = function (opsStr, optStartIndex) {
} }
function hasNext() { function hasNext() {
return !!(_opt ? regexResult : regexResult[0]); return !!(regexResult[0]);
} }
function lastIndex() { function lastIndex() {
@ -414,35 +397,8 @@ exports.smartOpAssembler = function () {
}; };
}; };
if (_opt) {
exports.mergingOpAssembler = function () {
var assem = _opt.mergingOpAssembler();
function append(op) { exports.mergingOpAssembler = function () {
assem.append(op.opcode, op.chars, op.lines, op.attribs);
}
function toString() {
return assem.toString();
}
function clear() {
assem.clear();
}
function endDocument() {
assem.endDocument();
}
return {
append: append,
toString: toString,
clear: clear,
endDocument: endDocument
};
};
} else {
exports.mergingOpAssembler = function () {
// This assembler can be used in production; it efficiently // This assembler can be used in production; it efficiently
// merges consecutive operations that are mergeable, ignores // merges consecutive operations that are mergeable, ignores
// no-ops, and drops final pure "keeps". It does not re-order // no-ops, and drops final pure "keeps". It does not re-order
@ -514,33 +470,11 @@ if (_opt) {
clear: clear, clear: clear,
endDocument: endDocument endDocument: endDocument
}; };
}; };
}
if (_opt) {
exports.opAssembler = function () {
var assem = _opt.opAssembler();
// this function allows op to be mutated later (doesn't keep a ref)
function append(op) {
assem.append(op.opcode, op.chars, op.lines, op.attribs);
}
function toString() { exports.opAssembler = function () {
return assem.toString();
}
function clear() {
assem.clear();
}
return {
append: append,
toString: toString,
clear: clear
};
};
} else {
exports.opAssembler = function () {
var pieces = []; var pieces = [];
// this function allows op to be mutated later (doesn't keep a ref) // this function allows op to be mutated later (doesn't keep a ref)
@ -565,8 +499,7 @@ if (_opt) {
toString: toString, toString: toString,
clear: clear clear: clear
}; };
}; };
}
/** /**
* A custom made String Iterator * A custom made String Iterator

View file

@ -3237,7 +3237,7 @@ function Ace2Inner(){
} }
//hide the dropdownso //hide the dropdownso
if(window.parent.parent.padeditbar){ // required in case its in an iframe should probably use parent.. See Issue 327 https://github.com/Pita/etherpad-lite/issues/327 if(window.parent.parent.padeditbar){ // required in case its in an iframe should probably use parent.. See Issue 327 https://github.com/Pita/etherpad-lite/issues/327
window.parent.parent.padeditbar.toogleDropDown("none"); window.parent.parent.padeditbar.toggleDropDown("none");
} }
} }

View file

@ -126,6 +126,7 @@ $(document).ready(function () {
socket.on('installed-results', function (data) { socket.on('installed-results', function (data) {
$("#installed-plugins *").remove(); $("#installed-plugins *").remove();
for (plugin_name in data.results) { for (plugin_name in data.results) {
if (plugin_name == "ep_etherpad-lite") continue; // Hack...
var plugin = data.results[plugin_name]; var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone(); var row = $("#installed-plugin-template").clone();

View file

@ -155,9 +155,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
function showReconnectUI() function showReconnectUI()
{ {
var cls = 'modaldialog cboxdisconnected cboxdisconnected_unknown'; padmodals.showModal("disconnected");
$("#connectionbox").get(0).className = cls;
padmodals.showModal("#connectionbox", 500);
} }
var fixPadHeight = _.throttle(function(){ var fixPadHeight = _.throttle(function(){

View file

@ -23,11 +23,12 @@
var padutils = require('./pad_utils').padutils; var padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie; var padcookie = require('./pad_cookie').padcookie;
require('./tinycon');
var chat = (function() var chat = (function()
{ {
var isStuck = false; var isStuck = false;
var chatMentions = 0; var chatMentions = 0;
var title = document.title;
var self = { var self = {
show: function () show: function ()
{ {
@ -35,7 +36,7 @@ var chat = (function()
$("#chatbox").show(); $("#chatbox").show();
self.scrollDown(); self.scrollDown();
chatMentions = 0; chatMentions = 0;
document.title = title; Tinycon.setBubble(0);
}, },
stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen
{ {
@ -62,8 +63,12 @@ var chat = (function()
}, },
scrollDown: function() scrollDown: function()
{ {
if($('#chatbox').css("display") != "none") if($('#chatbox').css("display") != "none"){
if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) {
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow");
self.lastMessage = $('#chattext > p').eq(-1);
}
}
}, },
send: function() send: function()
{ {
@ -122,12 +127,9 @@ var chat = (function()
// chat throb stuff -- Just make it throw for twice as long // chat throb stuff -- Just make it throw for twice as long
if(wasMentioned && !alreadyFocused) if(wasMentioned && !alreadyFocused)
{ // If the user was mentioned show for twice as long and flash the browser window { // If the user was mentioned show for twice as long and flash the browser window
if (chatMentions == 0){
title = document.title;
}
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400); $('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400);
chatMentions++; chatMentions++;
document.title = "("+chatMentions+") " + title; Tinycon.setBubble(chatMentions);
} }
else else
{ {
@ -137,7 +139,7 @@ var chat = (function()
// Clear the chat mentions when the user clicks on the chat input box // Clear the chat mentions when the user clicks on the chat input box
$('#chatinput').click(function(){ $('#chatinput').click(function(){
chatMentions = 0; chatMentions = 0;
document.title = title; Tinycon.setBubble(0);
}); });
self.scrollDown(); self.scrollDown();

View file

@ -21,6 +21,7 @@
*/ */
var chat = require('./chat').chat; var chat = require('./chat').chat;
var hooks = require('./pluginfw/hooks');
// Dependency fill on init. This exists for `pad.socket` only. // Dependency fill on init. This exists for `pad.socket` only.
// TODO: bind directly to the socket. // TODO: bind directly to the socket.
@ -337,6 +338,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{ {
callbacks.onServerMessage(msg.payload); callbacks.onServerMessage(msg.payload);
} }
hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload});
} }
function updateUserInfo(userInfo) function updateUserInfo(userInfo)

View file

@ -25,13 +25,14 @@
var _MAX_LIST_LEVEL = 8; var _MAX_LIST_LEVEL = 8;
var UNorm = require('./unorm');
var Changeset = require('./Changeset'); var Changeset = require('./Changeset');
var hooks = require('./pluginfw/hooks'); var hooks = require('./pluginfw/hooks');
var _ = require('./underscore'); var _ = require('./underscore');
function sanitizeUnicode(s) function sanitizeUnicode(s)
{ {
return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?'); return UNorm.nfc(s).replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
} }
function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author) function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author)
@ -258,7 +259,8 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{ {
state.listNesting--; state.listNesting--;
} }
if(oldListType) state.lineAttributes['list'] = oldListType; if (oldListType && oldListType != 'none') { state.lineAttributes['list'] = oldListType; }
else { delete state.lineAttributes['list']; }
_recalcAttribString(state); _recalcAttribString(state);
} }
@ -309,7 +311,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
['insertorder', 'first'] ['insertorder', 'first']
].concat( ].concat(
_.map(state.lineAttributes,function(value,key){ _.map(state.lineAttributes,function(value,key){
console.log([key, value]) if (window.console) console.log([key, value])
return [key, value]; return [key, value];
}) })
); );

View file

@ -790,8 +790,6 @@ var pad = {
}, 1000); }, 1000);
} }
padsavedrevs.handleIsFullyConnected(isConnected);
// pad.determineSidebarVisibility(isConnected && !isInitialConnect); // pad.determineSidebarVisibility(isConnected && !isInitialConnect);
pad.determineChatVisibility(isConnected && !isInitialConnect); pad.determineChatVisibility(isConnected && !isInitialConnect);
pad.determineAuthorshipColorsVisibility(); pad.determineAuthorshipColorsVisibility();

View file

@ -21,6 +21,7 @@
*/ */
var padmodals = require('./pad_modals').padmodals; var padmodals = require('./pad_modals').padmodals;
var padeditbar = require('./pad_editbar').padeditbar;
var padconnectionstatus = (function() var padconnectionstatus = (function()
{ {
@ -42,15 +43,18 @@ var padconnectionstatus = (function()
status = { status = {
what: 'connected' what: 'connected'
}; };
padmodals.hideModal(500);
padmodals.showModal('connected');
padmodals.hideOverlay(500);
}, },
reconnecting: function() reconnecting: function()
{ {
status = { status = {
what: 'reconnecting' what: 'reconnecting'
}; };
$("#connectionbox").get(0).className = 'modaldialog cboxreconnecting';
padmodals.showModal("#connectionbox", 500); padmodals.showModal('reconnecting');
padmodals.showOverlay(500);
}, },
disconnected: function(msg) disconnected: function(msg)
{ {
@ -61,20 +65,15 @@ var padconnectionstatus = (function()
what: 'disconnected', what: 'disconnected',
why: msg why: msg
}; };
var k = String(msg).toLowerCase(); // known reason why var k = String(msg).toLowerCase(); // known reason why
if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth')) if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth'))
{ {
k = 'unknown'; k = 'disconnected';
} }
var cls = 'modaldialog cboxdisconnected cboxdisconnected_' + k; padmodals.showModal(k);
$("#connectionbox").get(0).className = cls; padmodals.showOverlay(500);
padmodals.showModal("#connectionbox", 500);
$('button#forcereconnect').click(function()
{
window.location.reload();
});
}, },
isFullyConnected: function() isFullyConnected: function()
{ {

View file

@ -139,17 +139,21 @@ var padeditbar = (function()
{ {
if(cmd == "showusers") if(cmd == "showusers")
{ {
self.toogleDropDown("users"); self.toggleDropDown("users");
} }
else if (cmd == 'settings') else if (cmd == 'settings')
{ {
self.toogleDropDown("settings"); self.toggleDropDown("settings");
}
else if (cmd == 'connectivity')
{
self.toggleDropDown("connectivity");
} }
else if (cmd == 'embed') else if (cmd == 'embed')
{ {
self.setEmbedLinks(); self.setEmbedLinks();
$('#linkinput').focus().select(); $('#linkinput').focus().select();
self.toogleDropDown("embed"); self.toggleDropDown("embed");
} }
else if (cmd == 'import_export') else if (cmd == 'import_export')
{ {
@ -165,13 +169,13 @@ var padeditbar = (function()
} }
if(padeditor.ace) padeditor.ace.focus(); if(padeditor.ace) padeditor.ace.focus();
}, },
toogleDropDown: function(moduleName) toggleDropDown: function(moduleName, cb)
{ {
var modules = ["settings", "importexport", "embed", "users"]; var modules = ["settings", "connectivity", "importexport", "embed", "users"];
// hide all modules and remove highlighting of all buttons // hide all modules and remove highlighting of all buttons
if(moduleName == "none") if(moduleName == "none")
{ {
var returned = false
for(var i=0;i<modules.length;i++) for(var i=0;i<modules.length;i++)
{ {
//skip the userlist //skip the userlist
@ -183,9 +187,11 @@ var padeditbar = (function()
if(module.css('display') != "none") if(module.css('display') != "none")
{ {
$("#" + modules[i] + "link").removeClass("selected"); $("#" + modules[i] + "link").removeClass("selected");
module.slideUp("fast"); module.slideUp("fast", cb);
returned = true;
} }
} }
if(!returned && cb) return cb();
} }
else else
{ {
@ -203,7 +209,7 @@ var padeditbar = (function()
else if(modules[i]==moduleName) else if(modules[i]==moduleName)
{ {
$("#" + modules[i] + "link").addClass("selected"); $("#" + modules[i] + "link").addClass("selected");
module.slideDown("fast"); module.slideDown("fast", cb);
} }
} }
} }

View file

@ -21,6 +21,7 @@
*/ */
var padutils = require('./pad_utils').padutils; var padutils = require('./pad_utils').padutils;
var padeditbar = require('./pad_editbar').padeditbar;
var padmodals = (function() var padmodals = (function()
{ {
@ -30,17 +31,16 @@ var padmodals = (function()
{ {
pad = _pad; pad = _pad;
}, },
showModal: function(modalId, duration) showModal: function(messageId)
{ {
$(".modaldialog").hide(); padeditbar.toggleDropDown("none", function() {
$(modalId).show().css( $("#connectivity .visible").removeClass('visible');
{ $("#connectivity ."+messageId).addClass('visible');
'opacity': 0 padeditbar.toggleDropDown("connectivity");
}).animate( });
{ },
'opacity': 1 showOverlay: function(duration) {
}, duration); $("#overlay").show().css(
$("#modaloverlay").show().css(
{ {
'opacity': 0 'opacity': 0
}).animate( }).animate(
@ -48,19 +48,8 @@ var padmodals = (function()
'opacity': 1 'opacity': 1
}, duration); }, duration);
}, },
hideModal: function(duration) hideOverlay: function(duration) {
{ $("#overlay").animate(
padutils.cancelActions('hide-feedbackbox');
padutils.cancelActions('hide-sharebox');
$("#sharebox-response").hide();
$(".modaldialog").animate(
{
'opacity': 0
}, duration, function()
{
$("#modaloverlay").hide();
});
$("#modaloverlay").animate(
{ {
'opacity': 0 'opacity': 0
}, duration, function() }, duration, function()

View file

@ -21,6 +21,7 @@
*/ */
var padutils = require('./pad_utils').padutils; var padutils = require('./pad_utils').padutils;
var hooks = require('./pluginfw/hooks');
var myUserInfo = {}; var myUserInfo = {};
@ -529,6 +530,10 @@ var paduserlist = (function()
return; return;
} }
hooks.callAll('userJoinOrUpdate', {
userInfo: info
});
var userData = {}; var userData = {};
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name; userData.name = info.name;

View file

@ -3,7 +3,7 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm"); var npm = require("npm");
var registry = require("npm/lib/utils/npm-registry-client/index.js"); var registry = require("npm/lib/utils/npm-registry-client/index.js");
var withNpm = function (npmfn, cb) { var withNpm = function (npmfn, final, cb) {
npm.load({}, function (er) { npm.load({}, function (er) {
if (er) return cb({progress:1, error:er}); if (er) return cb({progress:1, error:er});
npm.on("log", function (message) { npm.on("log", function (message) {
@ -15,6 +15,7 @@ var withNpm = function (npmfn, cb) {
data.progress = 1; data.progress = 1;
data.message = "Done."; data.message = "Done.";
cb(data); cb(data);
final();
}); });
}); });
} }
@ -36,6 +37,9 @@ exports.uninstall = function(plugin_name, cb) {
}); });
}); });
}, },
function () {
hooks.aCallAll("restartServer", {}, function () {});
},
cb cb
); );
}; };
@ -51,6 +55,9 @@ exports.install = function(plugin_name, cb) {
}); });
}); });
}, },
function () {
hooks.aCallAll("restartServer", {}, function () {});
},
cb cb
); );
}; };
@ -93,6 +100,7 @@ exports.search = function(query, cache, cb) {
} }
); );
}, },
function () { },
cb cb
); );
}; };

View file

@ -54,9 +54,21 @@ exports.formatHooks = function (hook_set_name) {
}; };
exports.loadFn = function (path, hookName) { exports.loadFn = function (path, hookName) {
var x = path.split(":"); var functionName
var fn = require(x[0]); , parts = path.split(":");
var functionName = x[1] ? x[1] : hookName;
// on windows: C:\foo\bar:xyz
if(parts[0].length == 1) {
if(parts.length == 3)
functionName = parts.pop();
path = parts.join(":");
}else{
path = parts[0];
functionName = parts[1];
}
var fn = require(path);
functionName = functionName ? functionName : hookName;
_.each(functionName.split("."), function (name) { _.each(functionName.split("."), function (name) {
fn = fn[name]; fn = fn[name];

237
src/static/js/tinycon.js Normal file
View file

@ -0,0 +1,237 @@
/*!
* Tinycon - A small library for manipulating the Favicon
* Tom Moor, http://tommoor.com
* Copyright (c) 2012 Tom Moor
* MIT Licensed
* @version 0.2.6
*/
(function(){
var Tinycon = {};
var currentFavicon = null;
var originalFavicon = null;
var originalTitle = document.title;
var faviconImage = null;
var canvas = null;
var options = {};
var defaults = {
width: 7,
height: 9,
font: '10px arial',
colour: '#ffffff',
background: '#F03D25',
fallback: true
};
var ua = (function () {
var agent = navigator.userAgent.toLowerCase();
// New function has access to 'agent' via closure
return function (browser) {
return agent.indexOf(browser) !== -1;
};
}());
var browser = {
ie: ua('msie'),
chrome: ua('chrome'),
webkit: ua('chrome') || ua('safari'),
safari: ua('safari') && !ua('chrome'),
mozilla: ua('mozilla') && !ua('chrome') && !ua('safari')
};
// private methods
var getFaviconTag = function(){
var links = document.getElementsByTagName('link');
for(var i=0, len=links.length; i < len; i++) {
if ((links[i].getAttribute('rel') || '').match(/\bicon\b/)) {
return links[i];
}
}
return false;
};
var removeFaviconTag = function(){
var links = document.getElementsByTagName('link');
var head = document.getElementsByTagName('head')[0];
for(var i=0, len=links.length; i < len; i++) {
var exists = (typeof(links[i]) !== 'undefined');
if (exists && links[i].getAttribute('rel') === 'icon') {
head.removeChild(links[i]);
}
}
};
var getCurrentFavicon = function(){
if (!originalFavicon || !currentFavicon) {
var tag = getFaviconTag();
originalFavicon = currentFavicon = tag ? tag.getAttribute('href') : '/favicon.ico';
}
return currentFavicon;
};
var getCanvas = function (){
if (!canvas) {
canvas = document.createElement("canvas");
canvas.width = 16;
canvas.height = 16;
}
return canvas;
};
var setFaviconTag = function(url){
removeFaviconTag();
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'icon';
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);
};
var log = function(message){
if (window.console) window.console.log(message);
};
var drawFavicon = function(num, colour) {
// fallback to updating the browser title if unsupported
if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') {
return updateTitle(num);
}
var context = getCanvas().getContext("2d");
var colour = colour || '#000000';
var num = num || 0;
var src = getCurrentFavicon();
faviconImage = new Image();
faviconImage.onload = function() {
// clear canvas
context.clearRect(0, 0, 16, 16);
// draw original favicon
context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, 16, 16);
// draw bubble over the top
if (num > 0) drawBubble(context, num, colour);
// refresh tag in page
refreshFavicon();
};
// allow cross origin resource requests if the image is not a data:uri
// as detailed here: https://github.com/mrdoob/three.js/issues/1305
if (!src.match(/^data/)) {
faviconImage.crossOrigin = 'anonymous';
}
faviconImage.src = src;
};
var updateTitle = function(num) {
if (options.fallback) {
if (num > 0) {
document.title = '('+num+') ' + originalTitle;
} else {
document.title = originalTitle;
}
}
};
var drawBubble = function(context, num, colour) {
// bubble needs to be larger for double digits
var len = (num+"").length-1;
var width = options.width + (6*len);
var w = 16-width;
var h = 16-options.height;
// webkit seems to render fonts lighter than firefox
context.font = (browser.webkit ? 'bold ' : '') + options.font;
context.fillStyle = options.background;
context.strokeStyle = options.background;
context.lineWidth = 1;
// bubble
context.fillRect(w,h,width-1,options.height);
// rounded left
context.beginPath();
context.moveTo(w-0.5,h+1);
context.lineTo(w-0.5,15);
context.stroke();
// rounded right
context.beginPath();
context.moveTo(15.5,h+1);
context.lineTo(15.5,15);
context.stroke();
// bottom shadow
context.beginPath();
context.strokeStyle = "rgba(0,0,0,0.3)";
context.moveTo(w,16);
context.lineTo(15,16);
context.stroke();
// number
context.fillStyle = options.colour;
context.textAlign = "right";
context.textBaseline = "top";
// unfortunately webkit/mozilla are a pixel different in text positioning
context.fillText(num, 15, browser.mozilla ? 7 : 6);
};
var refreshFavicon = function(){
// check support
if (!getCanvas().getContext) return;
setFaviconTag(getCanvas().toDataURL());
};
// public methods
Tinycon.setOptions = function(custom){
options = {};
for(var key in defaults){
options[key] = custom.hasOwnProperty(key) ? custom[key] : defaults[key];
}
return this;
};
Tinycon.setImage = function(url){
currentFavicon = url;
refreshFavicon();
return this;
};
Tinycon.setBubble = function(num, colour){
// validate
if(isNaN(parseFloat(num)) || !isFinite(num)) return log('Bubble must be a number');
drawFavicon(num, colour);
return this;
};
Tinycon.reset = function(){
Tinycon.setImage(originalFavicon);
};
Tinycon.setOptions(defaults);
window.Tinycon = Tinycon;
})();

404
src/static/js/unorm.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>API Test and Examples Page</title> <title>API Test and Examples Page</title>
<script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/jquery.js"></script>
<style type="text/css"> <style type="text/css">
body { body {
font-size:9pt; font-size:9pt;
@ -65,23 +65,24 @@
callFunction(name, results_node, params); callFunction(name, results_node, params);
}); });
var template = $('#template')
$('.define').each(function() { $('.define').each(function() {
var functionName = parseName($(this).text()); var functionName = parseName($(this).text());
var parameters = parseParameters($(this).text()); var parameters = parseParameters($(this).text());
var $template = $('#template').clone(); var testGroup = template.clone();
$template.find('h2').text(functionName + "()"); testGroup.find('h2').text(functionName + "()");
var $table = $template.find('table'); var table = testGroup.find('table');
$(parameters).each(function(index, el) { $(parameters).each(function(index, el) {
$table.prepend('<tr><td>' + el + ':</td>' + table.prepend('<tr><td>' + el + ':</td>' +
'<td style="width:200px"><input type="text" size="10" name="' + el + '" /></td></tr>'); '<td style="width:200px"><input type="text" size="10" name="' + el + '" /></td></tr>');
}); });
$template.css({display: "block"}); testGroup.css({display: "block"});
$template.appendTo('body'); testGroup.appendTo('body');
}); });
}); });
@ -113,13 +114,12 @@
$('#result').text('Calling ' + memberName + "()..."); $('#result').text('Calling ' + memberName + "()...");
params["apikey"]=$("#apikey").val(); params["apikey"]=$("#apikey").val();
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: "/api/1/" + memberName, url: "/api/1/" + memberName,
data: params, data: params,
success: function(json) { success: function(json,status,xhr) {
results_node.text(json); results_node.text(xhr.responseText);
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
results_node.html("textStatus: " + textStatus + "<br />errorThrown: " + errorThrown); results_node.html("textStatus: " + textStatus + "<br />errorThrown: " + errorThrown);
@ -137,7 +137,8 @@
<td class="buttonBox" colspan="2" style="text-align:right;"><input type="button" value="Run" /></td> <td class="buttonBox" colspan="2" style="text-align:right;"><input type="button" value="Run" /></td>
</tr> </tr>
</table> </table>
<div class="results"/> <div class="results"></div>
</div> </div>
<div class="define">createGroup()</div> <div class="define">createGroup()</div>
<div class="define">deleteGroup(groupID)</div> <div class="define">deleteGroup(groupID)</div>
@ -155,6 +156,7 @@
<div class="define">getText(padID,rev)</div> <div class="define">getText(padID,rev)</div>
<div class="define">setText(padID,text)</div> <div class="define">setText(padID,text)</div>
<div class="define">getRevisionsCount(padID)</div> <div class="define">getRevisionsCount(padID)</div>
<div class="define">getLastEdited(padID)</div>
<div class="define">deletePad(padID)</div> <div class="define">deletePad(padID)</div>
<div class="define">getReadOnlyID(padID)</div> <div class="define">getReadOnlyID(padID)</div>
<div class="define">setPublicStatus(padID,publicStatus)</div> <div class="define">setPublicStatus(padID,publicStatus)</div>

View file

@ -21,7 +21,7 @@
<h1>Etherpad Lite</h1> <h1>Etherpad Lite</h1>
<a href="/admin/plugins/info">Technical information on installed plugins</a> <a href="plugins/info">Technical information on installed plugins</a>
<div class="separator"></div> <div class="separator"></div>
<h2>Installed plugins</h2> <h2>Installed plugins</h2>

View file

@ -22,15 +22,23 @@
<% e.begin_block("body"); %> <% e.begin_block("body"); %>
<div id="editbar" class="toolbar"> <div id="editbar" class="toolbar">
<div id="overlay">
<div id="overlay-inner"></div>
</div>
<ul class="menu_left"> <ul class="menu_left">
<% e.begin_block("editbarMenuLeft"); %> <% e.begin_block("editbarMenuLeft"); %>
<% e.end_block(); %> <% e.end_block(); %>
</ul> </ul>
<ul class="menu_right"> <ul class="menu_right">
<% e.begin_block("editbarMenuRight"); %> <% e.begin_block("editbarMenuRight"); %>
<li class="acl-write" data-key="settings"> <li data-key="import_export">
<a class="grouped-left" id="settingslink" title="Settings of this pad"> <a class="grouped-left" id="importexportlink" title="Import/Export from/to different document formats">
<span class="buttonicon buttonicon-settings"></span> <span class="buttonicon buttonicon-import_export"></span>
</a>
</li>
<li onClick="document.location = document.location.pathname+ '/timeslider'">
<a id="timesliderlink" class="grouped-middle" title="Show the history of this pad">
<span class="buttonicon buttonicon-history"></span>
</a> </a>
</li> </li>
<li class="acl-write" data-key="savedRevision"> <li class="acl-write" data-key="savedRevision">
@ -39,9 +47,9 @@
</a> </a>
</li> </li>
<li class="acl-write separator"></li> <li class="acl-write separator"></li>
<li data-key="import_export"> <li class="acl-write" data-key="settings">
<a class="grouped-left" id="importexportlink" title="Import/Export from/to different document formats"> <a class="grouped-left" id="settingslink" title="Settings of this pad">
<span class="buttonicon buttonicon-import_export"></span> <span class="buttonicon buttonicon-settings"></span>
</a> </a>
</li> </li>
<li data-key="embed"> <li data-key="embed">
@ -50,11 +58,6 @@
</a> </a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li onClick="document.location = document.location.pathname+ '/timeslider'">
<a id="timesliderlink" title="Show the history of this pad">
<span class="buttonicon buttonicon-history"></span>
</a>
</li>
<li id="usericon" data-key="showusers"> <li id="usericon" data-key="showusers">
<a title="Show connected users"> <a title="Show connected users">
<span class="buttonicon buttonicon-showusers"></span> <span class="buttonicon buttonicon-showusers"></span>
@ -90,7 +93,10 @@
<div id="editorcontainerbox"> <div id="editorcontainerbox">
<div id="editorcontainer"></div> <div id="editorcontainer"></div>
<div id="editorloadingbox">Loading...</div> <div id="editorloadingbox">
<p>Loading...</p>
<noscript><strong>Sorry, you have to enable Javascript in order to use this.</strong></noscript>
</div>
</div> </div>
<div id="settings" class="popup"> <div id="settings" class="popup">
@ -162,6 +168,60 @@
</div> </div>
</div> </div>
<div id="connectivity" class="popup">
<% e.begin_block("modals"); %>
<div class="connected visible">
<h2>Connected.</h2>
</div>
<div class="reconnecting">
<h1>Reestablishing connection...</h1>
<p><img alt="" border="0" src="/static/img/connectingbar.gif" /></p>
</div>
<div class="userdup">
<h1>Opened in another window.</h1>
<h2>You seem to have opened this pad in another browser window.</h2>
<p>If you'd like to use this window instead, you can reconnect.</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<div class="unauth">
<h1>No Authorization.</h1>
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<div class="looping">
<h1>Disconnected.</h1>
<h2>We're having trouble talking to the EtherPad lite synchronization server.</h2>
<p>You may be connecting through an incompatible firewall or proxy server.</p>
</div>
<div class="initsocketfail">
<h1>Disconnected.</h1>
<h2>We were unable to connect to the EtherPad lite synchronization server.</h2>
<p>This may be due to an incompatibility with your web browser or internet connection.</p>
</div>
<div class="slowcommit">
<h1>Disconnected.</h1>
<h2>Server not responding.</h2>
<p>This may be due to network connectivity issues or high load on the server.</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<div class="deleted">
<h1>Disconnected.</h1>
<p>This pad was deleted.</p>
</div>
<div class="disconnected">
<h1>Disconnected.</h1>
<h2>Lost connection with the EtherPad lite synchronization server.</h2>
<p>This may be due to a loss of network connectivity. If this continues to happen, please let us know</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
<% e.end_block(); %>
</div>
<div id="embed" class="popup"> <div id="embed" class="popup">
<% e.begin_block("embedPopup"); %> <% e.begin_block("embedPopup"); %>
<div id="embedreadonly" class="right acl-write"> <div id="embedreadonly" class="right acl-write">
@ -201,60 +261,26 @@
<div id="focusprotector">&nbsp;</div> <div id="focusprotector">&nbsp;</div>
<div id="modaloverlay">
<div id="modaloverlay-inner"></div>
</div>
<div id="mainmodals">
<% e.begin_block("modals"); %>
<div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div>
<div id="disconnected_initsocketfail">
<p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
</div>
<div id="disconnected_userdup">
<p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
</div>
<div id="disconnected_unknown">
<p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div>
<div id="disconnected_slowcommit">
<p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div>
<div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div>
<div id="disconnected_deleted">
<p>This pad was deleted.</p>
</div>
<div id="reconnect_advise">
<p>If this continues to happen, please let us know</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button>
</div>
</div>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
</div>
<% e.end_block(); %>
</div>
<% e.end_block(); %> <% e.end_block(); %>
<% e.begin_block("scripts"); %> <% e.begin_block("scripts"); %>
<script type="text/javascript">
/* Display errors on page load to the user
(Gets overridden by padutils.setupGlobalExceptionHandler)
*/
(function() {
var originalHandler = window.onerror;
window.onerror = function(msg, url, line) {
var box = document.getElementById('editorloadingbox');
box.innerHTML = '<p><b>An error occured while loading the pad</b></p>'
+ '<p><b>'+msg+'</b> '
+ '<small>in '+ url +' (line '+ line +')</small></p>';
// call original error handler
if(typeof(originalHandler) == 'function') originalHandler.call(null, arguments);
};
})();
</script>
<script type="text/javascript" src="../static/js/require-kernel.js"></script> <script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script> <script type="text/javascript" src="../socket.io/socket.io.js"></script>
@ -286,8 +312,15 @@
} }
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
plugins.baseURL = baseURL; plugins.baseURL = baseURL;
plugins.update(function () { plugins.update(function () {
// Call documentReady hook
$(function() {
hooks.aCallAll('documentReady');
});
var pad = require('ep_etherpad-lite/static/js/pad'); var pad = require('ep_etherpad-lite/static/js/pad');
pad.baseURL = baseURL; pad.baseURL = baseURL;
pad.init(); pad.init();

View file

@ -33,6 +33,12 @@
<div class="stepper" id="rightstep"></div> <div class="stepper" id="rightstep"></div>
</div> </div>
</div> </div>
<div id="overlay">
<div id="overlay-inner">
<!-- -->
</div>
</div>
</div> </div>
<div class="timeslider-bar"> <div class="timeslider-bar">
@ -70,54 +76,59 @@
</div><!-- /padmain --> </div><!-- /padmain -->
</div><!-- /padpage --> </div><!-- /padpage -->
<div id="modaloverlay"> <div id="connectivity" class="popup">
<div id="modaloverlay-inner">
<!-- -->
</div>
</div>
<div id="mainmodals">
<% e.begin_block("modals"); %> <% e.begin_block("modals"); %>
<div id="connectionbox" class="modaldialog"> <div class="connected visible">
<div id="connectionboxinner" class="modaldialog-inner"> <h2>Connected.</h2>
<div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div> </div>
<div id="disconnected_initsocketfail"> <div class="reconnecting">
<p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p> <h1>Reestablishing connection...</h1>
<p><img alt="" border="0" src="/static/img/connectingbar.gif" /></p>
</div> </div>
<div id="disconnected_userdup"> <div class="userdup">
<p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p> <h1>Opened in another window.</h1>
</div> <h2>You seem to have opened this pad in another browser window.</h2>
<div id="disconnected_unknown"> <p>If you'd like to use this window instead, you can reconnect.</p>
<p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div>
<div id="disconnected_slowcommit">
<p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div>
<div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div>
<div id="disconnected_deleted">
<p>This pad was deleted.</p>
</div>
<div id="reconnect_advise">
<p>If this continues to happen, please let us know</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button> <button id="forcereconnect">Reconnect Now</button>
</div> </div>
<div class="unauth">
<h1>No Authorization.</h1>
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
<button id="forcereconnect">Reconnect Now</button>
</div> </div>
<div class="looping">
<h1>Disconnected.</h1>
<h2>We're having trouble talking to the EtherPad lite synchronization server.</h2>
<p>You may be connecting through an incompatible firewall or proxy server.</p>
</div> </div>
<div class="initsocketfail">
<h1>Disconnected.</h1>
<h2>We were unable to connect to the EtherPad lite synchronization server.</h2>
<p>This may be due to an incompatibility with your web browser or internet connection.</p>
</div> </div>
<div class="slowcommit">
<h1>Disconnected.</h1>
<h2>Server not responding.</h2>
<p>This may be due to network connectivity issues or high load on the server.</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<div class="deleted">
<h1>Disconnected.</h1>
<p>This pad was deleted.</p>
</div>
<div class="disconnected">
<h1>Disconnected.</h1>
<h2>Lost connection with the EtherPad lite synchronization server.</h2>
<p>This may be due to a loss of network connectivity. If this continues to happen, please let us know</p>
<button id="forcereconnect">Reconnect Now</button>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
<% e.end_block(); %> <% e.end_block(); %>
</div> </div>
<!-- export code --> <!-- export code -->
<div id="importexport"> <div id="importexport">

View file

@ -1 +1 @@
bin\node.exe node_modules\ep_etherpad-lite\node\server.js node node_modules\ep_etherpad-lite\node\server.js