commit
f95e2793d7
|
@ -41,7 +41,7 @@ echo "do a normal unix install first..."
|
|||
bin/installDeps.sh || exit 1
|
||||
|
||||
echo "copy the windows settings template..."
|
||||
cp settings.json.template_windows settings.json
|
||||
cp settings.json.template settings.json
|
||||
|
||||
echo "resolve symbolic links..."
|
||||
cp -rL node_modules node_modules_resolved
|
||||
|
|
|
@ -35,8 +35,9 @@ fi
|
|||
|
||||
#check node version
|
||||
NODE_VERSION=$(node --version)
|
||||
if [ ! $(echo $NODE_VERSION | cut -d "." -f 1-2) = "v0.6" ]; then
|
||||
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x" >&2
|
||||
NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-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
|
||||
fi
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
@echo off
|
||||
set NODE_VERSION=0.8.1
|
||||
set JQUERY_VERSION=1.7
|
||||
|
||||
:: change directory to etherpad-lite root
|
||||
cd bin
|
||||
cd ..
|
||||
|
||||
echo _
|
||||
echo Updating node...
|
||||
curl -lo bin\node.exe http://nodejs.org/dist/v%NODE_VERSION%/node.exe
|
||||
|
||||
echo _
|
||||
echo Installing etherpad-lite and dependencies...
|
||||
cmd /C npm install src/
|
||||
|
||||
echo _
|
||||
echo Updating jquery...
|
||||
curl -lo "node_modules\ep_etherpad-lite\static\js\jquery.min.js" "http://code.jquery.com/jquery-%JQUERY_VERSION%.min.js"
|
||||
|
||||
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 copy settings.json.template settings.json
|
||||
|
||||
echo _
|
||||
echo Installed Etherpad-lite!
|
|
@ -8,8 +8,8 @@
|
|||
"ip": "0.0.0.0",
|
||||
"port" : 9001,
|
||||
|
||||
//The Type of the database. You can choose between dirty, sqlite and mysql
|
||||
//You should use mysql or sqlite for anything else than testing or development
|
||||
//The Type of the database. You can choose between dirty, postgres, sqlite and mysql
|
||||
//You shouldn't use "dirty" for for anything else than testing or development
|
||||
"dbType" : "dirty",
|
||||
//the database specific settings
|
||||
"dbSettings" : {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
{
|
||||
"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": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
|
||||
{ "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },
|
||||
|
|
|
@ -47,6 +47,8 @@ exports.createGroupPad = groupManager.createGroupPad;
|
|||
|
||||
exports.createAuthor = authorManager.createAuthor;
|
||||
exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
|
||||
exports.listPadsOfAuthor = authorManager.listPadsOfAuthor;
|
||||
exports.padUsersCount = padMessageHandler.padUsersCount;
|
||||
|
||||
/**********************/
|
||||
/**SESSION FUNCTIONS***/
|
||||
|
@ -282,6 +284,24 @@ 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;
|
||||
callback(null, {lastEdited: pad.getLastEdited()});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
createPad(padName [, text]) creates a new pad in this group
|
||||
|
||||
|
@ -463,6 +483,26 @@ 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()});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/******************************/
|
||||
/** INTERNAL HELPER FUNCTIONS */
|
||||
/******************************/
|
||||
|
|
|
@ -55,6 +55,7 @@ exports.getAuthor4Token = function (token, callback)
|
|||
/**
|
||||
* Returns the AuthorID for a mapper.
|
||||
* @param {String} token The mapper
|
||||
* @param {String} name The name of the author (optional)
|
||||
* @param {Function} callback callback (err, author)
|
||||
*/
|
||||
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
|
||||
|
@ -153,6 +154,7 @@ exports.getAuthorColorId = function (author, callback)
|
|||
/**
|
||||
* Sets the color 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)
|
||||
*/
|
||||
exports.setAuthorColorId = function (author, colorId, callback)
|
||||
|
@ -173,9 +175,95 @@ exports.getAuthorName = function (author, callback)
|
|||
/**
|
||||
* Sets the name of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} name The name of the author
|
||||
* @param {Function} callback (optional)
|
||||
*/
|
||||
exports.setAuthorName = function (author, 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);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -80,8 +80,12 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
|
|||
newRevData.meta.atext = this.atext;
|
||||
}
|
||||
|
||||
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
|
||||
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
|
||||
this.saveToDatabase();
|
||||
|
||||
// set the author to pad
|
||||
if(author)
|
||||
authorManager.addPad(author, this.id);
|
||||
};
|
||||
|
||||
//save all attributes to the database
|
||||
|
@ -102,6 +106,12 @@ Pad.prototype.saveToDatabase = function saveToDatabase(){
|
|||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
//remove pad from all authors who contributed
|
||||
function(callback)
|
||||
{
|
||||
var authorIDs = _this.getAllAuthors();
|
||||
|
||||
authorIDs.forEach(function (authorID)
|
||||
{
|
||||
authorManager.removePad(authorID, padID);
|
||||
});
|
||||
|
||||
callback();
|
||||
}
|
||||
], callback);
|
||||
|
|
|
@ -72,3 +72,33 @@ exports.getPadId = function(readOnlyId, callback)
|
|||
{
|
||||
db.get("readonly2pad:" + readOnlyId, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a the padId and readonlyPadId in an object for any id
|
||||
* @param {String} padIdOrReadonlyPadId read only id or real pad id
|
||||
*/
|
||||
exports.getIds = function(padIdOrReadonlyPadId, callback) {
|
||||
var handleRealPadId = function () {
|
||||
exports.getReadOnlyId(padIdOrReadonlyPadId, function (err, value) {
|
||||
callback(null, {
|
||||
readOnlyPadId: value,
|
||||
padId: padIdOrReadonlyPadId,
|
||||
readonly: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (padIdOrReadonlyPadId.indexOf("r.") != 0)
|
||||
return handleRealPadId();
|
||||
|
||||
exports.getPadId(padIdOrReadonlyPadId, function (err, value) {
|
||||
if(ERR(err, callback)) return;
|
||||
if (value == null)
|
||||
return handleRealPadId();
|
||||
callback(null, {
|
||||
readOnlyPadId: padIdOrReadonlyPadId,
|
||||
padId: value,
|
||||
readonly: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -40,13 +40,14 @@ catch(e)
|
|||
//a list of all functions
|
||||
var functions = {
|
||||
"createGroup" : [],
|
||||
"createGroupIfNotExistsFor" : ["groupMapper"],
|
||||
"createGroupIfNotExistsFor" : ["groupMapper"],
|
||||
"deleteGroup" : ["groupID"],
|
||||
"listPads" : ["groupID"],
|
||||
"createPad" : ["padID", "text"],
|
||||
"createGroupPad" : ["groupID", "padName", "text"],
|
||||
"createAuthor" : ["name"],
|
||||
"createAuthorIfNotExistsFor": ["authorMapper" , "name"],
|
||||
"listPadsOfAuthor" : ["authorID"],
|
||||
"createSession" : ["groupID", "authorID", "validUntil"],
|
||||
"deleteSession" : ["sessionID"],
|
||||
"getSessionInfo" : ["sessionID"],
|
||||
|
@ -57,12 +58,15 @@ var functions = {
|
|||
"getHTML" : ["padID", "rev"],
|
||||
"setHTML" : ["padID", "html"],
|
||||
"getRevisionsCount" : ["padID"],
|
||||
"getLastEdited" : ["padID"],
|
||||
"deletePad" : ["padID"],
|
||||
"getReadOnlyID" : ["padID"],
|
||||
"setPublicStatus" : ["padID", "publicStatus"],
|
||||
"getPublicStatus" : ["padID"],
|
||||
"setPassword" : ["padID", "password"],
|
||||
"isPasswordProtected" : ["padID"]
|
||||
"isPasswordProtected" : ["padID"],
|
||||
"listAuthorsOfPad" : ["padID"],
|
||||
"padUsersCount" : ["padID"]
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,20 +33,20 @@ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins.js");
|
|||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var _ = require('underscore');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
|
||||
/**
|
||||
* A associative array that translates a session to a pad
|
||||
*/
|
||||
var session2pad = {};
|
||||
/**
|
||||
* A associative array that saves which sessions belong to a pad
|
||||
*/
|
||||
var pad2sessions = {};
|
||||
|
||||
/**
|
||||
* A associative array that saves some general informations about a session
|
||||
* A associative array that saves informations about a session
|
||||
* key = sessionId
|
||||
* values = author, rev
|
||||
* values = padId, readonlyPadId, readonly, author, rev
|
||||
* padId = the real padId of the pad
|
||||
* readonlyPadId = The readonly pad id of the pad
|
||||
* readonly = Wether the client has only read access (true) or read/write access (false)
|
||||
* rev = That last revision that was send to this client
|
||||
* author = the author name of this session
|
||||
*/
|
||||
|
@ -72,8 +72,7 @@ exports.setSocketIO = function(socket_io)
|
|||
*/
|
||||
exports.handleConnect = function(client)
|
||||
{
|
||||
//Initalize session2pad and sessioninfos for this new session
|
||||
session2pad[client.id]=null;
|
||||
//Initalize sessioninfos for this new session
|
||||
sessioninfos[client.id]={};
|
||||
}
|
||||
|
||||
|
@ -101,7 +100,7 @@ exports.kickSessionsFromPad = function(padID)
|
|||
exports.handleDisconnect = function(client)
|
||||
{
|
||||
//save the padname of this session
|
||||
var sessionPad=session2pad[client.id];
|
||||
var sessionPad=sessioninfos[client.id].padId;
|
||||
|
||||
//if this connection was already etablished with a handshake, send a disconnect message to the others
|
||||
if(sessioninfos[client.id] && sessioninfos[client.id].author)
|
||||
|
@ -149,8 +148,7 @@ exports.handleDisconnect = function(client)
|
|||
}
|
||||
}
|
||||
|
||||
//Delete the session2pad and sessioninfos entrys of this session
|
||||
delete session2pad[client.id];
|
||||
//Delete the sessioninfos entrys of this session
|
||||
delete sessioninfos[client.id];
|
||||
}
|
||||
|
||||
|
@ -161,6 +159,11 @@ exports.handleDisconnect = function(client)
|
|||
*/
|
||||
exports.handleMessage = function(client, message)
|
||||
{
|
||||
_.map(hooks.callAll( "handleMessage", { client: client, message: message }), function ( newmessage ) {
|
||||
if ( newmessage || newmessage === null ) {
|
||||
message = newmessage;
|
||||
}
|
||||
});
|
||||
if(message == null)
|
||||
{
|
||||
messageLogger.warn("Message is null!");
|
||||
|
@ -171,46 +174,76 @@ exports.handleMessage = function(client, message)
|
|||
messageLogger.warn("Message has no type attribute!");
|
||||
return;
|
||||
}
|
||||
|
||||
//Check what type of message we get and delegate to the other methodes
|
||||
if(message.type == "CLIENT_READY")
|
||||
{
|
||||
handleClientReady(client, message);
|
||||
}
|
||||
else if(message.type == "COLLABROOM" && typeof message.data == 'object'){
|
||||
if (message.data.type == "USER_CHANGES")
|
||||
{
|
||||
handleUserChanges(client, message);
|
||||
|
||||
var finalHandler = function () {
|
||||
//Check what type of message we get and delegate to the other methodes
|
||||
if(message.type == "CLIENT_READY") {
|
||||
handleClientReady(client, message);
|
||||
} else if(message.type == "CHANGESET_REQ") {
|
||||
handleChangesetRequest(client, message);
|
||||
} else if(message.type == "COLLABROOM") {
|
||||
if (sessioninfos[client.id].readonly) {
|
||||
messageLogger.warn("Dropped message, COLLABROOM for readonly pad");
|
||||
} else if (message.data.type == "USER_CHANGES") {
|
||||
handleUserChanges(client, message);
|
||||
} else if (message.data.type == "USERINFO_UPDATE") {
|
||||
handleUserInfoUpdate(client, message);
|
||||
} else if (message.data.type == "CHAT_MESSAGE") {
|
||||
handleChatMessage(client, message);
|
||||
} else if (message.data.type == "SAVE_REVISION") {
|
||||
handleSaveRevisionMessage(client, message);
|
||||
} else if (message.data.type == "CLIENT_MESSAGE" &&
|
||||
message.data.payload.type == "suggestUserName") {
|
||||
handleSuggestUserName(client, message);
|
||||
} else {
|
||||
messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type);
|
||||
}
|
||||
} else {
|
||||
messageLogger.warn("Dropped message, unknown Message Type " + message.type);
|
||||
}
|
||||
else if (message.data.type == "USERINFO_UPDATE")
|
||||
{
|
||||
handleUserInfoUpdate(client, message);
|
||||
}
|
||||
else if(message.data.type == "CHAT_MESSAGE")
|
||||
{
|
||||
handleChatMessage(client, message);
|
||||
}
|
||||
else if(message.data.type == "CLIENT_MESSAGE" &&
|
||||
typeof message.data.payload == 'object' &&
|
||||
message.data.payload.type == "suggestUserName")
|
||||
{
|
||||
handleSuggestUserName(client, message);
|
||||
}
|
||||
}
|
||||
//if the message type is unknown, throw an exception
|
||||
else
|
||||
{
|
||||
messageLogger.warn("Dropped message, unknown Message Type " + message.type);
|
||||
};
|
||||
|
||||
if (message && message.padId) {
|
||||
async.series([
|
||||
//check permissions
|
||||
function(callback)
|
||||
{
|
||||
// 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
|
||||
]);
|
||||
} else {
|
||||
finalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a save revision message
|
||||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
function handleSaveRevisionMessage(client, message){
|
||||
var padId = session2pad[client.id];
|
||||
var padId = sessioninfos[client.id].padId;
|
||||
var userId = sessioninfos[client.id].author;
|
||||
|
||||
padManager.getPad(padId, function(err, pad)
|
||||
|
@ -231,7 +264,7 @@ function handleChatMessage(client, message)
|
|||
var time = new Date().getTime();
|
||||
var userId = sessioninfos[client.id].author;
|
||||
var text = message.data.text;
|
||||
var padId = session2pad[client.id];
|
||||
var padId = sessioninfos[client.id].padId;
|
||||
|
||||
var pad;
|
||||
var userName;
|
||||
|
@ -307,7 +340,7 @@ function handleSuggestUserName(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
var padId = session2pad[client.id];
|
||||
var padId = sessioninfos[client.id].padId;
|
||||
|
||||
//search the author and send him this message
|
||||
for(var i in pad2sessions[padId])
|
||||
|
@ -341,7 +374,7 @@ function handleUserInfoUpdate(client, message)
|
|||
authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
|
||||
authorManager.setAuthorName(author, message.data.userInfo.name);
|
||||
|
||||
var padId = session2pad[client.id];
|
||||
var padId = sessioninfos[client.id].padId;
|
||||
|
||||
//set a null name, when there is no name set. cause the client wants it null
|
||||
if(message.data.userInfo.name == null)
|
||||
|
@ -387,7 +420,7 @@ function handleUserChanges(client, message)
|
|||
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//get all Vars we need
|
||||
var baseRev = message.data.baseRev;
|
||||
var wireApool = (new AttributePool()).fromJsonable(message.data.apool);
|
||||
|
@ -399,7 +432,7 @@ function handleUserChanges(client, message)
|
|||
//get the pad
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(session2pad[client.id], function(err, value)
|
||||
padManager.getPad(sessioninfos[client.id].padId, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = value;
|
||||
|
@ -506,17 +539,15 @@ exports.updatePadClients = function(pad, callback)
|
|||
//go trough all sessions on this pad
|
||||
async.forEach(pad2sessions[pad.id], function(session, callback)
|
||||
{
|
||||
var lastRev = sessioninfos[session].rev;
|
||||
|
||||
|
||||
//https://github.com/caolan/async#whilst
|
||||
//send them all new changesets
|
||||
async.whilst(
|
||||
function (){ return lastRev < pad.getHeadRevisionNumber()},
|
||||
function (){ return sessioninfos[session].rev < pad.getHeadRevisionNumber()},
|
||||
function(callback)
|
||||
{
|
||||
var author, revChangeset;
|
||||
|
||||
var r = ++lastRev;
|
||||
{
|
||||
var author, revChangeset, currentTime;
|
||||
var r = sessioninfos[session].rev + 1;
|
||||
|
||||
async.parallel([
|
||||
function (callback)
|
||||
|
@ -536,6 +567,15 @@ exports.updatePadClients = function(pad, callback)
|
|||
revChangeset = value;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback)
|
||||
{
|
||||
pad.getRevisionDate(r, function(err, date)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
currentTime = date;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
|
@ -553,24 +593,30 @@ exports.updatePadClients = function(pad, callback)
|
|||
else
|
||||
{
|
||||
var forWire = Changeset.prepareForWire(revChangeset, pad.pool);
|
||||
var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
|
||||
changeset: forWire.translated,
|
||||
apool: forWire.pool,
|
||||
author: author}};
|
||||
var wireMsg = {"type":"COLLABROOM",
|
||||
"data":{type:"NEW_CHANGES",
|
||||
newRev:r,
|
||||
changeset: forWire.translated,
|
||||
apool: forWire.pool,
|
||||
author: author,
|
||||
currentTime: currentTime,
|
||||
timeDelta: currentTime - sessioninfos[session].time
|
||||
}};
|
||||
|
||||
socketio.sockets.sockets[session].json.send(wireMsg);
|
||||
}
|
||||
|
||||
if(sessioninfos[session] != null)
|
||||
{
|
||||
sessioninfos[session].time = currentTime;
|
||||
sessioninfos[session].rev = r;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
||||
if(sessioninfos[session] != null)
|
||||
{
|
||||
sessioninfos[session].rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
},callback);
|
||||
}
|
||||
|
||||
|
@ -655,14 +701,29 @@ function handleClientReady(client, message)
|
|||
var authorColorId;
|
||||
var pad;
|
||||
var historicalAuthorData = {};
|
||||
var readOnlyId;
|
||||
var currentTime;
|
||||
var chatMessages;
|
||||
var padIds;
|
||||
|
||||
async.series([
|
||||
// Get ro/rw id:s
|
||||
function (callback) {
|
||||
readOnlyManager.getIds(message.padId, function(err, value) {
|
||||
if(ERR(err, callback)) return;
|
||||
padIds = value;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
//check permissions
|
||||
function(callback)
|
||||
{
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
// Note: message.sessionID is an entierly 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 (padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
|
@ -705,21 +766,12 @@ function handleClientReady(client, message)
|
|||
},
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(message.padId, function(err, value)
|
||||
padManager.getPad(padIds.padId, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = value;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
readOnlyManager.getReadOnlyId(message.padId, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
readOnlyId = value;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
|
@ -729,6 +781,16 @@ function handleClientReady(client, message)
|
|||
var authors = pad.getAllAuthors();
|
||||
|
||||
async.parallel([
|
||||
//get timestamp of latest revission needed for timeslider
|
||||
function(callback)
|
||||
{
|
||||
pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
currentTime = date;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get all author data out of the database
|
||||
function(callback)
|
||||
{
|
||||
|
@ -755,35 +817,36 @@ function handleClientReady(client, message)
|
|||
}
|
||||
], callback);
|
||||
|
||||
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//Check if this author is already on the pad, if yes, kick the other sessions!
|
||||
if(pad2sessions[message.padId])
|
||||
if(pad2sessions[padIds.padId])
|
||||
{
|
||||
for(var i in pad2sessions[message.padId])
|
||||
for(var i in pad2sessions[padIds.padId])
|
||||
{
|
||||
if(sessioninfos[pad2sessions[message.padId][i]] && sessioninfos[pad2sessions[message.padId][i]].author == author)
|
||||
if(sessioninfos[pad2sessions[padIds.padId][i]] && sessioninfos[pad2sessions[padIds.padId][i]].author == author)
|
||||
{
|
||||
var socket = socketio.sockets.sockets[pad2sessions[message.padId][i]];
|
||||
var socket = socketio.sockets.sockets[pad2sessions[padIds.padId][i]];
|
||||
if(socket) socket.json.send({disconnect:"userdup"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Save in session2pad that this session belonges to this pad
|
||||
//Save in sessioninfos that this session belonges to this pad
|
||||
var sessionId=String(client.id);
|
||||
session2pad[sessionId] = message.padId;
|
||||
sessioninfos[sessionId].padId = padIds.padId;
|
||||
sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId;
|
||||
sessioninfos[sessionId].readonly = padIds.readonly;
|
||||
|
||||
//check if there is already a pad2sessions entry, if not, create one
|
||||
if(!pad2sessions[message.padId])
|
||||
if(!pad2sessions[padIds.padId])
|
||||
{
|
||||
pad2sessions[message.padId] = [];
|
||||
pad2sessions[padIds.padId] = [];
|
||||
}
|
||||
|
||||
//Saves in pad2sessions that this session belongs to this pad
|
||||
pad2sessions[message.padId].push(sessionId);
|
||||
pad2sessions[padIds.padId].push(sessionId);
|
||||
|
||||
//prepare all values for the wire
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
|
@ -791,6 +854,9 @@ function handleClientReady(client, message)
|
|||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
|
||||
// Warning: never ever send padIds.padId to the client. If the
|
||||
// client is read only you would open a security hole 1 swedish
|
||||
// mile wide...
|
||||
var clientVars = {
|
||||
"accountPrivs": {
|
||||
"maxRevisions": 100
|
||||
|
@ -799,6 +865,7 @@ function handleClientReady(client, message)
|
|||
"initialOptions": {
|
||||
"guestPolicy": "deny"
|
||||
},
|
||||
"savedRevisions": pad.getSavedRevisions(),
|
||||
"collab_client_vars": {
|
||||
"initialAttributedText": atext,
|
||||
"clientIp": "127.0.0.1",
|
||||
|
@ -807,7 +874,8 @@ function handleClientReady(client, message)
|
|||
"historicalAuthorData": historicalAuthorData,
|
||||
"apool": apool,
|
||||
"rev": pad.getHeadRevisionNumber(),
|
||||
"globalPadId": message.padId
|
||||
"globalPadId": message.padId,
|
||||
"time": currentTime,
|
||||
},
|
||||
"colorPalette": ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"],
|
||||
"clientIp": "127.0.0.1",
|
||||
|
@ -817,9 +885,10 @@ function handleClientReady(client, message)
|
|||
"initialTitle": "Pad: " + message.padId,
|
||||
"opts": {},
|
||||
"chatHistory": chatMessages,
|
||||
"numConnectedUsers": pad2sessions[message.padId].length,
|
||||
"numConnectedUsers": pad2sessions[padIds.padId].length,
|
||||
"isProPad": false,
|
||||
"readOnlyId": readOnlyId,
|
||||
"readOnlyId": padIds.readOnlyPadId,
|
||||
"readonly": padIds.readonly,
|
||||
"serverTimestamp": new Date().getTime(),
|
||||
"globalPadId": message.padId,
|
||||
"userId": author,
|
||||
|
@ -831,7 +900,9 @@ function handleClientReady(client, message)
|
|||
"plugins": {
|
||||
"plugins": plugins.plugins,
|
||||
"parts": plugins.parts,
|
||||
}
|
||||
},
|
||||
"initialChangesets": [] // FIXME: REMOVE THIS SHIT
|
||||
|
||||
}
|
||||
|
||||
//Add a username to the clientVars if one avaiable
|
||||
|
@ -852,7 +923,7 @@ function handleClientReady(client, message)
|
|||
else
|
||||
{
|
||||
//Send the clientVars to the Client
|
||||
client.json.send(clientVars);
|
||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||
//Save the revision in sessioninfos
|
||||
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
@ -882,7 +953,7 @@ function handleClientReady(client, message)
|
|||
}
|
||||
|
||||
//Run trough all sessions of this pad
|
||||
async.forEach(pad2sessions[message.padId], function(sessionID, callback)
|
||||
async.forEach(pad2sessions[padIds.padId], function(sessionID, callback)
|
||||
{
|
||||
var author, socket, sessionAuthorName, sessionAuthorColorId;
|
||||
|
||||
|
@ -955,3 +1026,347 @@ function handleClientReady(client, message)
|
|||
ERR(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request for a rough changeset, the timeslider client needs it
|
||||
*/
|
||||
function handleChangesetRequest(client, message)
|
||||
{
|
||||
//check if all ok
|
||||
if(message.data == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no data!");
|
||||
return;
|
||||
}
|
||||
if(message.padId == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no padId!");
|
||||
return;
|
||||
}
|
||||
if(message.data.granularity == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no granularity!");
|
||||
return;
|
||||
}
|
||||
if(message.data.start == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no start!");
|
||||
return;
|
||||
}
|
||||
if(message.data.requestID == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no requestID!");
|
||||
return;
|
||||
}
|
||||
|
||||
var granularity = message.data.granularity;
|
||||
var start = message.data.start;
|
||||
var end = start + (100 * granularity);
|
||||
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
|
||||
getChangesetInfo(padIds.padId, start, end, granularity, function(err, changesetInfo)
|
||||
{
|
||||
ERR(err);
|
||||
|
||||
var data = changesetInfo;
|
||||
data.requestID = message.data.requestID;
|
||||
|
||||
client.json.send({type: "CHANGESET_REQ", data: data});
|
||||
});
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||
*/
|
||||
function getChangesetInfo(padId, startNum, endNum, granularity, callback)
|
||||
{
|
||||
var forwardsChangesets = [];
|
||||
var backwardsChangesets = [];
|
||||
var timeDeltas = [];
|
||||
var apool = new AttributePool();
|
||||
var pad;
|
||||
var composedChangesets = {};
|
||||
var revisionDate = [];
|
||||
var lines;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//calculate the last full endnum
|
||||
var lastRev = pad.getHeadRevisionNumber();
|
||||
if (endNum > lastRev+1) {
|
||||
endNum = lastRev+1;
|
||||
}
|
||||
endNum = Math.floor(endNum / granularity)*granularity;
|
||||
|
||||
var compositesChangesetNeeded = [];
|
||||
var revTimesNeeded = [];
|
||||
|
||||
//figure out which composite Changeset and revTimes we need, to load them in bulk
|
||||
var compositeStart = startNum;
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
//add the composite Changeset we needed
|
||||
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
|
||||
|
||||
//add the t1 time we need
|
||||
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1);
|
||||
//add the t2 time we need
|
||||
revTimesNeeded.push(compositeEnd - 1);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
//get all needed db values parallel
|
||||
async.parallel([
|
||||
function(callback)
|
||||
{
|
||||
//get all needed composite Changesets
|
||||
async.forEach(compositesChangesetNeeded, function(item, callback)
|
||||
{
|
||||
composePadChangesets(padId, item.start, item.end, function(err, changeset)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
composedChangesets[item.start + "/" + item.end] = changeset;
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//get all needed revision Dates
|
||||
async.forEach(revTimesNeeded, function(revNum, callback)
|
||||
{
|
||||
pad.getRevisionDate(revNum, function(err, revDate)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
revisionDate[revNum] = Math.floor(revDate/1000);
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
//get the lines
|
||||
function(callback)
|
||||
{
|
||||
getPadLines(padId, startNum-1, function(err, _lines)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
lines = _lines;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
//doesn't know what happens here excatly :/
|
||||
function(callback)
|
||||
{
|
||||
var compositeStart = startNum;
|
||||
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
if (compositeStart + granularity > endNum)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
|
||||
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
|
||||
|
||||
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
|
||||
Changeset.mutateTextLines(forwards, lines.textlines);
|
||||
|
||||
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
|
||||
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
|
||||
|
||||
var t1, t2;
|
||||
if (compositeStart == 0)
|
||||
{
|
||||
t1 = revisionDate[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
t1 = revisionDate[compositeStart - 1];
|
||||
}
|
||||
|
||||
t2 = revisionDate[compositeEnd - 1];
|
||||
|
||||
timeDeltas.push(t2 - t1);
|
||||
forwardsChangesets.push(forwards2);
|
||||
backwardsChangesets.push(backwards2);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
callback(null, {forwardsChangesets: forwardsChangesets,
|
||||
backwardsChangesets: backwardsChangesets,
|
||||
apool: apool.toJsonable(),
|
||||
actualEndNum: endNum,
|
||||
timeDeltas: timeDeltas,
|
||||
start: startNum,
|
||||
granularity: granularity });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getPadLines function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
|
||||
*/
|
||||
function getPadLines(padId, revNum, callback)
|
||||
{
|
||||
var atext;
|
||||
var result = {};
|
||||
var pad;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get the atext
|
||||
function(callback)
|
||||
{
|
||||
if(revNum >= 0)
|
||||
{
|
||||
pad.getInternalRevisionAText(revNum, function(err, _atext)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
atext = _atext;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
atext = Changeset.makeAText("\n");
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
result.textlines = Changeset.splitTextLines(atext.text);
|
||||
result.alines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
callback(null);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||
*/
|
||||
function composePadChangesets(padId, startNum, endNum, callback)
|
||||
{
|
||||
var pad;
|
||||
var changesets = [];
|
||||
var changeset;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//fetch all changesets we need
|
||||
function(callback)
|
||||
{
|
||||
var changesetsNeeded=[];
|
||||
|
||||
//create a array for all changesets, we will
|
||||
//replace the values with the changeset later
|
||||
for(var r=startNum;r<endNum;r++)
|
||||
{
|
||||
changesetsNeeded.push(r);
|
||||
}
|
||||
|
||||
//get all changesets
|
||||
async.forEach(changesetsNeeded, function(revNum,callback)
|
||||
{
|
||||
pad.getRevisionChangeset(revNum, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
changesets[revNum] = value;
|
||||
callback();
|
||||
});
|
||||
},callback);
|
||||
},
|
||||
//compose Changesets
|
||||
function(callback)
|
||||
{
|
||||
changeset = changesets[startNum];
|
||||
var pool = pad.apool();
|
||||
|
||||
for(var r=startNum+1;r<endNum;r++)
|
||||
{
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
//return err and changeset
|
||||
function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,534 +0,0 @@
|
|||
/**
|
||||
* The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
var ERR = require("async-stacktrace");
|
||||
var async = require("async");
|
||||
var padManager = require("../db/PadManager");
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
|
||||
var settings = require('../utils/Settings');
|
||||
var authorManager = require("../db/AuthorManager");
|
||||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
|
||||
/**
|
||||
* Saves the Socket class we need to send and recieve data from the client
|
||||
*/
|
||||
var socketio;
|
||||
|
||||
/**
|
||||
* This Method is called by server.js to tell the message handler on which socket it should send
|
||||
* @param socket_io The Socket
|
||||
*/
|
||||
exports.setSocketIO = function(socket_io)
|
||||
{
|
||||
socketio=socket_io;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the connection of a new user
|
||||
* @param client the new client
|
||||
*/
|
||||
exports.handleConnect = function(client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the disconnection of a user
|
||||
* @param client the client that leaves
|
||||
*/
|
||||
exports.handleDisconnect = function(client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a message from a user
|
||||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
exports.handleMessage = function(client, message)
|
||||
{
|
||||
//Check what type of message we get and delegate to the other methodes
|
||||
if(message.type == "CLIENT_READY")
|
||||
{
|
||||
handleClientReady(client, message);
|
||||
}
|
||||
else if(message.type == "CHANGESET_REQ")
|
||||
{
|
||||
handleChangesetRequest(client, message);
|
||||
}
|
||||
//if the message type is unkown, throw an exception
|
||||
else
|
||||
{
|
||||
messageLogger.warn("Dropped message, unknown Message Type: '" + message.type + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function handleClientReady(client, message)
|
||||
{
|
||||
if(message.padId == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no padId!");
|
||||
return;
|
||||
}
|
||||
|
||||
//send the timeslider client the clientVars, with this values its able to start
|
||||
createTimesliderClientVars (message.padId, function(err, clientVars)
|
||||
{
|
||||
ERR(err);
|
||||
|
||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request for a rough changeset, the timeslider client needs it
|
||||
*/
|
||||
function handleChangesetRequest(client, message)
|
||||
{
|
||||
//check if all ok
|
||||
if(message.data == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no data!");
|
||||
return;
|
||||
}
|
||||
if(message.padId == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no padId!");
|
||||
return;
|
||||
}
|
||||
if(message.data.granularity == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no granularity!");
|
||||
return;
|
||||
}
|
||||
if(message.data.start == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no start!");
|
||||
return;
|
||||
}
|
||||
if(message.data.requestID == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, changeset request has no requestID!");
|
||||
return;
|
||||
}
|
||||
|
||||
var granularity = message.data.granularity;
|
||||
var start = message.data.start;
|
||||
var end = start + (100 * granularity);
|
||||
var padId = message.padId;
|
||||
|
||||
//build the requested rough changesets and send them back
|
||||
getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo)
|
||||
{
|
||||
ERR(err);
|
||||
|
||||
var data = changesetInfo;
|
||||
data.requestID = message.data.requestID;
|
||||
|
||||
client.json.send({type: "CHANGESET_REQ", data: data});
|
||||
});
|
||||
}
|
||||
|
||||
function createTimesliderClientVars (padId, callback)
|
||||
{
|
||||
var clientVars = {
|
||||
viewId: padId,
|
||||
colorPalette: ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"],
|
||||
savedRevisions: [],
|
||||
padIdForUrl: padId,
|
||||
fullWidth: false,
|
||||
disableRightBar: false,
|
||||
initialChangesets: [],
|
||||
abiwordAvailable: settings.abiwordAvailable(),
|
||||
hooks: [],
|
||||
initialStyledContents: {}
|
||||
};
|
||||
|
||||
var pad;
|
||||
var initialChangesets = [];
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get all saved revisions and add them
|
||||
function(callback)
|
||||
{
|
||||
clientVars.savedRevisions = pad.getSavedRevisions();
|
||||
callback();
|
||||
},
|
||||
//get all authors and add them to
|
||||
function(callback)
|
||||
{
|
||||
var historicalAuthorData = {};
|
||||
//get all authors out of the attribut pool
|
||||
var authors = pad.getAllAuthors();
|
||||
|
||||
//get all author data out of the database
|
||||
async.forEach(authors, function(authorId, callback)
|
||||
{
|
||||
authorManager.getAuthor(authorId, function(err, author)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
historicalAuthorData[authorId] = author;
|
||||
callback();
|
||||
});
|
||||
}, function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
//add historicalAuthorData to the clientVars and continue
|
||||
clientVars.historicalAuthorData = historicalAuthorData;
|
||||
clientVars.initialStyledContents.historicalAuthorData = historicalAuthorData;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get the timestamp of the last revision
|
||||
function(callback)
|
||||
{
|
||||
pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
clientVars.currentTime = date;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//get the head revision Number
|
||||
var lastRev = pad.getHeadRevisionNumber();
|
||||
|
||||
//add the revNum to the client Vars
|
||||
clientVars.revNum = lastRev;
|
||||
clientVars.totalRevs = lastRev;
|
||||
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
|
||||
clientVars.initialStyledContents.apool = apool;
|
||||
clientVars.initialStyledContents.atext = atext;
|
||||
|
||||
var granularities = [100, 10, 1];
|
||||
|
||||
//get the latest rough changesets
|
||||
async.forEach(granularities, function(granularity, callback)
|
||||
{
|
||||
var topGranularity = granularity*10;
|
||||
|
||||
getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity,
|
||||
Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity,
|
||||
function(err, changeset)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
clientVars.initialChangesets.push(changeset);
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, clientVars);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||
*/
|
||||
function getChangesetInfo(padId, startNum, endNum, granularity, callback)
|
||||
{
|
||||
var forwardsChangesets = [];
|
||||
var backwardsChangesets = [];
|
||||
var timeDeltas = [];
|
||||
var apool = new AttributePool();
|
||||
var pad;
|
||||
var composedChangesets = {};
|
||||
var revisionDate = [];
|
||||
var lines;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//calculate the last full endnum
|
||||
var lastRev = pad.getHeadRevisionNumber();
|
||||
if (endNum > lastRev+1) {
|
||||
endNum = lastRev+1;
|
||||
}
|
||||
endNum = Math.floor(endNum / granularity)*granularity;
|
||||
|
||||
var compositesChangesetNeeded = [];
|
||||
var revTimesNeeded = [];
|
||||
|
||||
//figure out which composite Changeset and revTimes we need, to load them in bulk
|
||||
var compositeStart = startNum;
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
//add the composite Changeset we needed
|
||||
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
|
||||
|
||||
//add the t1 time we need
|
||||
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1);
|
||||
//add the t2 time we need
|
||||
revTimesNeeded.push(compositeEnd - 1);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
//get all needed db values parallel
|
||||
async.parallel([
|
||||
function(callback)
|
||||
{
|
||||
//get all needed composite Changesets
|
||||
async.forEach(compositesChangesetNeeded, function(item, callback)
|
||||
{
|
||||
composePadChangesets(padId, item.start, item.end, function(err, changeset)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
composedChangesets[item.start + "/" + item.end] = changeset;
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//get all needed revision Dates
|
||||
async.forEach(revTimesNeeded, function(revNum, callback)
|
||||
{
|
||||
pad.getRevisionDate(revNum, function(err, revDate)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
revisionDate[revNum] = Math.floor(revDate/1000);
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
//get the lines
|
||||
function(callback)
|
||||
{
|
||||
getPadLines(padId, startNum-1, function(err, _lines)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
lines = _lines;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
//doesn't know what happens here excatly :/
|
||||
function(callback)
|
||||
{
|
||||
var compositeStart = startNum;
|
||||
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
if (compositeStart + granularity > endNum)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
|
||||
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
|
||||
|
||||
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
|
||||
Changeset.mutateTextLines(forwards, lines.textlines);
|
||||
|
||||
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
|
||||
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
|
||||
|
||||
var t1, t2;
|
||||
if (compositeStart == 0)
|
||||
{
|
||||
t1 = revisionDate[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
t1 = revisionDate[compositeStart - 1];
|
||||
}
|
||||
|
||||
t2 = revisionDate[compositeEnd - 1];
|
||||
|
||||
timeDeltas.push(t2 - t1);
|
||||
forwardsChangesets.push(forwards2);
|
||||
backwardsChangesets.push(backwards2);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
callback(null, {forwardsChangesets: forwardsChangesets,
|
||||
backwardsChangesets: backwardsChangesets,
|
||||
apool: apool.toJsonable(),
|
||||
actualEndNum: endNum,
|
||||
timeDeltas: timeDeltas,
|
||||
start: startNum,
|
||||
granularity: granularity });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getPadLines function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
|
||||
*/
|
||||
function getPadLines(padId, revNum, callback)
|
||||
{
|
||||
var atext;
|
||||
var result = {};
|
||||
var pad;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get the atext
|
||||
function(callback)
|
||||
{
|
||||
if(revNum >= 0)
|
||||
{
|
||||
pad.getInternalRevisionAText(revNum, function(err, _atext)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
atext = _atext;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
atext = Changeset.makeAText("\n");
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
result.textlines = Changeset.splitTextLines(atext.text);
|
||||
result.alines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
callback(null);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||
*/
|
||||
function composePadChangesets(padId, startNum, endNum, callback)
|
||||
{
|
||||
var pad;
|
||||
var changesets = [];
|
||||
var changeset;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = _pad;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//fetch all changesets we need
|
||||
function(callback)
|
||||
{
|
||||
var changesetsNeeded=[];
|
||||
|
||||
//create a array for all changesets, we will
|
||||
//replace the values with the changeset later
|
||||
for(var r=startNum;r<endNum;r++)
|
||||
{
|
||||
changesetsNeeded.push(r);
|
||||
}
|
||||
|
||||
//get all changesets
|
||||
async.forEach(changesetsNeeded, function(revNum,callback)
|
||||
{
|
||||
pad.getRevisionChangeset(revNum, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
changesets[revNum] = value;
|
||||
callback();
|
||||
});
|
||||
},callback);
|
||||
},
|
||||
//compose Changesets
|
||||
function(callback)
|
||||
{
|
||||
changeset = changesets[startNum];
|
||||
var pool = pad.apool();
|
||||
|
||||
for(var r=startNum+1;r<endNum;r++)
|
||||
{
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
//return err and changeset
|
||||
function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, changeset);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
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 _ = 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);
|
||||
}
|
|
@ -16,6 +16,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
"ep_etherpad-lite/templates/admin/plugins.html",
|
||||
render_args), {});
|
||||
});
|
||||
args.app.get('/admin/plugins/info', function(req, res) {
|
||||
res.send(eejs.require(
|
||||
"ep_etherpad-lite/templates/admin/plugins-info.html",
|
||||
{}), {});
|
||||
});
|
||||
}
|
||||
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
|
|
|
@ -15,11 +15,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//the pad id was sanitized, so we redirect to the sanitized version
|
||||
if(sanitizedPadId != padId)
|
||||
{
|
||||
var real_url = req.url.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId);
|
||||
var real_url = sanitizedPadId;
|
||||
var query = url.parse(req.url).query;
|
||||
if ( query ) real_url += '?' + query;
|
||||
res.header('Location', real_url);
|
||||
res.send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>', 302);
|
||||
res.header('Location', real_url);
|
||||
res.send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>', 302);
|
||||
}
|
||||
//the pad id was fine, so just render it
|
||||
else
|
||||
|
|
|
@ -5,7 +5,6 @@ var socketIORouter = require("../../handler/SocketIORouter");
|
|||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
|
||||
var padMessageHandler = require("../../handler/PadMessageHandler");
|
||||
var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler");
|
||||
|
||||
var connect = require('connect');
|
||||
|
||||
|
@ -59,7 +58,6 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//Initalize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent("pad", padMessageHandler);
|
||||
socketIORouter.addComponent("timeslider", timesliderMessageHandler);
|
||||
|
||||
hooks.callAll("socketio", {"app": args.app, "io": io});
|
||||
}
|
||||
|
|
|
@ -88,6 +88,8 @@ exports.basicAuth = function (req, res, next) {
|
|||
});
|
||||
}
|
||||
|
||||
var secret = null;
|
||||
|
||||
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.
|
||||
// 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
|
||||
* 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,
|
||||
key: 'express_sid',
|
||||
secret: apikey = randomString(32)}));
|
||||
secret: secret}));
|
||||
|
||||
args.app.use(exports.basicAuth);
|
||||
}
|
||||
|
|
|
@ -22,36 +22,13 @@
|
|||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var fs = require('fs');
|
||||
var settings = require('./utils/Settings');
|
||||
var db = require('./db/DB');
|
||||
var async = require('async');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
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
|
||||
log4js.setGlobalLogLevel(settings.loglevel);
|
||||
|
@ -75,27 +52,7 @@ async.waterfall([
|
|||
//initalize the http server
|
||||
function (callback)
|
||||
{
|
||||
//create server
|
||||
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");
|
||||
}
|
||||
hooks.callAll("createServer", {});
|
||||
callback(null);
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -21,9 +21,12 @@ var path = require('path');
|
|||
var zlib = require('zlib');
|
||||
var util = require('util');
|
||||
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/'));
|
||||
CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
|
||||
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
|
||||
|
||||
var responseCache = {};
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
"resolve" : "0.2.1",
|
||||
"socket.io" : "0.9.6",
|
||||
"ueberDB" : "0.1.7",
|
||||
"async" : "0.1.18",
|
||||
"express" : "2.5.8",
|
||||
"connect" : "1.8.7",
|
||||
"async" : "0.1.x",
|
||||
"express" : "2.5.x",
|
||||
"connect" : "1.x",
|
||||
"clean-css" : "0.3.2",
|
||||
"uglify-js" : "1.2.5",
|
||||
"formidable" : "1.0.9",
|
||||
|
|
|
@ -19,6 +19,9 @@ textarea {
|
|||
iframe {
|
||||
position: absolute
|
||||
}
|
||||
.readonly .acl-write {
|
||||
display: none;
|
||||
}
|
||||
#users {
|
||||
background: #f7f7f7;
|
||||
background: -webkit-linear-gradient( #F7F7F7,#EEE);
|
||||
|
@ -992,4 +995,7 @@ input[type=checkbox] {
|
|||
.toolbar ul li .separator {
|
||||
display: none
|
||||
}
|
||||
#online_count {
|
||||
line-height: 24px
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ $(document).ready(function () {
|
|||
socket.on('installed-results', function (data) {
|
||||
$("#installed-plugins *").remove();
|
||||
for (plugin_name in data.results) {
|
||||
if (plugin_name == "ep_etherpad-lite") continue; // Hack...
|
||||
var plugin = data.results[plugin_name];
|
||||
var row = $("#installed-plugin-template").clone();
|
||||
|
||||
|
|
|
@ -84,14 +84,14 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
var appLevelDisconnectReason = null;
|
||||
|
||||
var padContents = {
|
||||
currentRevision: clientVars.revNum,
|
||||
currentTime: clientVars.currentTime,
|
||||
currentLines: Changeset.splitTextLines(clientVars.initialStyledContents.atext.text),
|
||||
currentRevision: clientVars.collab_client_vars.rev,
|
||||
currentTime: clientVars.collab_client_vars.time,
|
||||
currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text),
|
||||
currentDivs: null,
|
||||
// to be filled in once the dom loads
|
||||
apool: (new AttribPool()).fromJsonable(clientVars.initialStyledContents.apool),
|
||||
apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool),
|
||||
alines: Changeset.splitAttributionLines(
|
||||
clientVars.initialStyledContents.atext.attribs, clientVars.initialStyledContents.atext.text),
|
||||
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
|
||||
|
||||
// generates a jquery element containing HTML for a line
|
||||
lineToElement: function(line, aline)
|
||||
|
@ -271,7 +271,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
|
||||
Changeset.mutateTextLines(changeset, padContents);
|
||||
padContents.currentRevision = revision;
|
||||
padContents.currentTime += timeDelta * 1000;
|
||||
padContents.currentTime += timeDelta;
|
||||
debugLog('Time Delta: ', timeDelta)
|
||||
updateTimer();
|
||||
|
||||
|
@ -432,19 +432,6 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
var start = request.rev;
|
||||
var requestID = Math.floor(Math.random() * 100000);
|
||||
|
||||
/*var msg = { "component" : "timeslider",
|
||||
"type":"CHANGESET_REQ",
|
||||
"padId": padId,
|
||||
"token": token,
|
||||
"protocolVersion": 2,
|
||||
"data"
|
||||
{
|
||||
"start": start,
|
||||
"granularity": granularity
|
||||
}};
|
||||
|
||||
socket.send(msg);*/
|
||||
|
||||
sendSocketMsg("CHANGESET_REQ", {
|
||||
"start": start,
|
||||
"granularity": granularity,
|
||||
|
@ -452,19 +439,6 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
});
|
||||
|
||||
self.reqCallbacks[requestID] = callback;
|
||||
|
||||
/*debugLog("loadinging revision", start, "through ajax");
|
||||
$.getJSON("/ep/pad/changes/" + clientVars.padIdForUrl + "?s=" + start + "&g=" + granularity, function (data, textStatus)
|
||||
{
|
||||
if (textStatus !== "success")
|
||||
{
|
||||
console.log(textStatus);
|
||||
BroadcastSlider.showReconnectUI();
|
||||
}
|
||||
self.handleResponse(data, start, granularity, callback);
|
||||
|
||||
setTimeout(self.loadFromQueue, 10); // load the next ajax function
|
||||
});*/
|
||||
},
|
||||
handleSocketResponse: function(message)
|
||||
{
|
||||
|
@ -493,130 +467,58 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
|
||||
}
|
||||
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
|
||||
},
|
||||
handleMessageFromServer: function (obj)
|
||||
{
|
||||
debugLog("handleMessage:", arguments);
|
||||
|
||||
if (obj.type == "COLLABROOM")
|
||||
{
|
||||
obj = obj.data;
|
||||
|
||||
if (obj.type == "NEW_CHANGES")
|
||||
{
|
||||
debugLog(obj);
|
||||
var changeset = Changeset.moveOpsToNewPool(
|
||||
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.inverse(
|
||||
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.moveOpsToNewPool(
|
||||
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
|
||||
}
|
||||
else if (obj.type == "NEW_AUTHORDATA")
|
||||
{
|
||||
var authorMap = {};
|
||||
authorMap[obj.author] = obj.data;
|
||||
receiveAuthorData(authorMap);
|
||||
|
||||
var authors = _.map(padContents.getActiveAuthors(), function(name) {
|
||||
return authorData[name];
|
||||
});
|
||||
|
||||
BroadcastSlider.setAuthors(authors);
|
||||
}
|
||||
else if (obj.type == "NEW_SAVEDREV")
|
||||
{
|
||||
var savedRev = obj.savedRev;
|
||||
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
|
||||
}
|
||||
}
|
||||
else if(obj.type == "CHANGESET_REQ")
|
||||
{
|
||||
changesetLoader.handleSocketResponse(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
debugLog("Unknown message type: " + obj.type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleMessageFromServer()
|
||||
{
|
||||
debugLog("handleMessage:", arguments);
|
||||
var obj = arguments[0]['data'];
|
||||
var expectedType = "COLLABROOM";
|
||||
|
||||
obj = JSON.parse(obj);
|
||||
if (obj['type'] == expectedType)
|
||||
{
|
||||
obj = obj['data'];
|
||||
|
||||
if (obj['type'] == "NEW_CHANGES")
|
||||
{
|
||||
debugLog(obj);
|
||||
var changeset = Changeset.moveOpsToNewPool(
|
||||
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.moveOpsToNewPool(
|
||||
obj.changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
|
||||
}
|
||||
else if (obj['type'] == "NEW_AUTHORDATA")
|
||||
{
|
||||
var authorMap = {};
|
||||
authorMap[obj.author] = obj.data;
|
||||
receiveAuthorData(authorMap);
|
||||
|
||||
var authors = _.map(padContents.getActiveAuthors(), function(name) {
|
||||
return authorData[name];
|
||||
});
|
||||
|
||||
BroadcastSlider.setAuthors(authors);
|
||||
}
|
||||
else if (obj['type'] == "NEW_SAVEDREV")
|
||||
{
|
||||
var savedRev = obj.savedRev;
|
||||
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debugLog("incorrect message type: " + obj['type'] + ", expected " + expectedType);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSocketClosed(params)
|
||||
{
|
||||
debugLog("socket closed!", params);
|
||||
socket = null;
|
||||
|
||||
BroadcastSlider.showReconnectUI();
|
||||
// var reason = appLevelDisconnectReason || params.reason;
|
||||
// var shouldReconnect = params.reconnect;
|
||||
// if (shouldReconnect) {
|
||||
// // determine if this is a tight reconnect loop due to weird connectivity problems
|
||||
// // reconnectTimes.push(+new Date());
|
||||
// var TOO_MANY_RECONNECTS = 8;
|
||||
// var TOO_SHORT_A_TIME_MS = 10000;
|
||||
// if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
|
||||
// ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
|
||||
// TOO_SHORT_A_TIME_MS) {
|
||||
// setChannelState("DISCONNECTED", "looping");
|
||||
// }
|
||||
// else {
|
||||
// setChannelState("RECONNECTING", reason);
|
||||
// setUpSocket();
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// BroadcastSlider.showReconnectUI();
|
||||
// setChannelState("DISCONNECTED", reason);
|
||||
// }
|
||||
}
|
||||
|
||||
function sendMessage(msg)
|
||||
{
|
||||
socket.postMessage(JSON.stringify(
|
||||
{
|
||||
type: "COLLABROOM",
|
||||
data: msg
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function setChannelState(newChannelState, moreInfo)
|
||||
{
|
||||
if (newChannelState != channelState)
|
||||
{
|
||||
channelState = newChannelState;
|
||||
// callbacks.onChannelStateChange(channelState, moreInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function abandonConnection(reason)
|
||||
{
|
||||
if (socket)
|
||||
{
|
||||
socket.onclosed = function()
|
||||
{};
|
||||
socket.onhiccup = function()
|
||||
{};
|
||||
socket.disconnect();
|
||||
}
|
||||
socket = null;
|
||||
setChannelState("DISCONNECTED", reason);
|
||||
}
|
||||
|
||||
/// Since its not used, import 'forEach' has been dropped
|
||||
/*window['onloadFuncts'] = [];
|
||||
window.onload = function ()
|
||||
{
|
||||
window['isloaded'] = true;
|
||||
|
||||
|
||||
forEach(window['onloadFuncts'],function (funct)
|
||||
{
|
||||
funct();
|
||||
});
|
||||
};*/
|
||||
|
||||
// to start upon window load, just push a function onto this array
|
||||
//window['onloadFuncts'].push(setUpSocket);
|
||||
//window['onloadFuncts'].push(function ()
|
||||
|
@ -637,36 +539,19 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
// this is necessary to keep infinite loops of events firing,
|
||||
// since goToRevision changes the slider position
|
||||
var goToRevisionIfEnabledCount = 0;
|
||||
var goToRevisionIfEnabled = function()
|
||||
var goToRevisionIfEnabled = function() {
|
||||
if (goToRevisionIfEnabledCount > 0)
|
||||
{
|
||||
if (goToRevisionIfEnabledCount > 0)
|
||||
{
|
||||
goToRevisionIfEnabledCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
goToRevision.apply(goToRevision, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
goToRevisionIfEnabledCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
goToRevision.apply(goToRevision, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastSlider.onSlider(goToRevisionIfEnabled);
|
||||
|
||||
(function()
|
||||
{
|
||||
for (var i = 0; i < clientVars.initialChangesets.length; i++)
|
||||
{
|
||||
var csgroup = clientVars.initialChangesets[i];
|
||||
var start = clientVars.initialChangesets[i].start;
|
||||
var granularity = clientVars.initialChangesets[i].granularity;
|
||||
debugLog("loading changest on startup: ", start, granularity, csgroup);
|
||||
changesetLoader.handleResponse(csgroup, start, granularity, null);
|
||||
}
|
||||
})();
|
||||
|
||||
var dynamicCSS = makeCSSManager('dynamicsyntax');
|
||||
var authorData = {};
|
||||
|
||||
|
@ -686,7 +571,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
}
|
||||
}
|
||||
|
||||
receiveAuthorData(clientVars.historicalAuthorData);
|
||||
receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData);
|
||||
|
||||
return changesetLoader;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ function loadBroadcastRevisionsJS()
|
|||
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
|
||||
}
|
||||
|
||||
revisionInfo.latest = clientVars.totalRevs || -1;
|
||||
revisionInfo.latest = clientVars.collab_client_vars.rev || -1;
|
||||
|
||||
revisionInfo.createNew = function(index)
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
// These parameters were global, now they are injected. A reference to the
|
||||
// Timeslider controller would probably be more appropriate.
|
||||
var _ = require('./underscore');
|
||||
var padmodals = require('./pad_modals').padmodals;
|
||||
|
||||
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
||||
{
|
||||
|
@ -54,11 +55,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
{
|
||||
slidercallbacks[i](newval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
var updateSliderElements = function()
|
||||
{
|
||||
|
@ -68,12 +65,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
}
|
||||
$("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
var addSavedRevision = function(position, info)
|
||||
{
|
||||
var newSavedRevision = $('<div></div>');
|
||||
|
@ -88,7 +81,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
BroadcastSlider.setSliderPosition(position);
|
||||
});
|
||||
savedRevisions.push(newSavedRevision);
|
||||
};
|
||||
};
|
||||
|
||||
var removeSavedRevision = function(position)
|
||||
{
|
||||
|
@ -96,7 +89,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
savedRevisions.remove(element);
|
||||
element.remove();
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
/* Begin small 'API' */
|
||||
|
||||
|
@ -162,9 +155,9 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
|
||||
function showReconnectUI()
|
||||
{
|
||||
$("#padmain, #rightbars").css('top', "130px");
|
||||
$("#timeslider").show();
|
||||
$('#error').show();
|
||||
var cls = 'modaldialog cboxdisconnected cboxdisconnected_unknown';
|
||||
$("#connectionbox").get(0).className = cls;
|
||||
padmodals.showModal("#connectionbox", 500);
|
||||
}
|
||||
|
||||
var fixPadHeight = _.throttle(function(){
|
||||
|
@ -481,8 +474,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
}
|
||||
|
||||
$("#timeslider").show();
|
||||
setSliderLength(clientVars.totalRevs);
|
||||
setSliderPosition(clientVars.revNum);
|
||||
setSliderLength(clientVars.collab_client_vars.rev);
|
||||
setSliderPosition(clientVars.collab_client_vars.rev);
|
||||
|
||||
_.each(clientVars.savedRevisions, function(revision)
|
||||
{
|
||||
|
|
|
@ -114,9 +114,13 @@ var chat = (function()
|
|||
{
|
||||
var count = Number($("#chatcounter").text());
|
||||
count++;
|
||||
|
||||
// is the users focus already in the chatbox?
|
||||
var alreadyFocused = $("#chatinput").is(":focus");
|
||||
|
||||
$("#chatcounter").text(count);
|
||||
// chat throb stuff -- Just make it throw for twice as long
|
||||
if(wasMentioned)
|
||||
if(wasMentioned && !alreadyFocused)
|
||||
{ // If the user was mentioned show for twice as long and flash the browser window
|
||||
if (chatMentions == 0){
|
||||
title = document.title;
|
||||
|
@ -130,7 +134,11 @@ var chat = (function()
|
|||
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(2000).hide(400);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the chat mentions when the user clicks on the chat input box
|
||||
$('#chatinput').click(function(){
|
||||
chatMentions = 0;
|
||||
document.title = title;
|
||||
});
|
||||
self.scrollDown();
|
||||
|
||||
},
|
||||
|
|
|
@ -310,14 +310,20 @@ function handshake()
|
|||
receivedClientVars = true;
|
||||
|
||||
//set some client vars
|
||||
clientVars = obj;
|
||||
clientVars = obj.data;
|
||||
clientVars.userAgent = "Anonymous";
|
||||
clientVars.collab_client_vars.clientAgent = "Anonymous";
|
||||
|
||||
|
||||
//initalize the pad
|
||||
pad._afterHandshake();
|
||||
initalized = true;
|
||||
|
||||
$("body").addClass(clientVars.readonly ? "readonly" : "readwrite")
|
||||
|
||||
padeditor.ace.callWithAce(function (ace) {
|
||||
ace.ace_setEditable(!clientVars.readonly);
|
||||
});
|
||||
|
||||
// If the LineNumbersDisabled value is set to true then we need to hide the Line Numbers
|
||||
if (settings.LineNumbersDisabled == true)
|
||||
{
|
||||
|
@ -354,6 +360,8 @@ function handshake()
|
|||
//this message advices the client to disconnect
|
||||
if (obj.disconnect)
|
||||
{
|
||||
console.warn("FORCED TO DISCONNECT");
|
||||
console.warn(obj);
|
||||
padconnectionstatus.disconnected(obj.disconnect);
|
||||
socket.disconnect();
|
||||
return;
|
||||
|
|
|
@ -241,7 +241,7 @@ var padeditbar = (function()
|
|||
if ($('#readonlyinput').is(':checked'))
|
||||
{
|
||||
var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/"));
|
||||
var readonlyLink = basePath + "/ro/" + clientVars.readOnlyId;
|
||||
var readonlyLink = basePath + "/p/" + clientVars.readOnlyId;
|
||||
$('#embedinput').val("<iframe name='embed_readonly' src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
|
||||
$('#linkinput').val(readonlyLink);
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ exports.aCallAll = function (hook_name, args, cb) {
|
|||
|
||||
exports.callFirst = function (hook_name, args) {
|
||||
if (!args) args = {};
|
||||
if (plugins.hooks[hook_name][0] === undefined) return [];
|
||||
if (plugins.hooks[hook_name] === undefined) return [];
|
||||
return exports.syncMapFirst(plugins.hooks[hook_name], function (hook) {
|
||||
return hookCallWrapper(hook, hook_name, args);
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
|||
var npm = require("npm");
|
||||
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) {
|
||||
if (er) return cb({progress:1, error:er});
|
||||
npm.on("log", function (message) {
|
||||
|
@ -15,6 +15,7 @@ var withNpm = function (npmfn, cb) {
|
|||
data.progress = 1;
|
||||
data.message = "Done.";
|
||||
cb(data);
|
||||
final();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -36,6 +37,9 @@ exports.uninstall = function(plugin_name, cb) {
|
|||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
hooks.aCallAll("restartServer", {}, function () {});
|
||||
},
|
||||
cb
|
||||
);
|
||||
};
|
||||
|
@ -51,6 +55,9 @@ exports.install = function(plugin_name, cb) {
|
|||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
hooks.aCallAll("restartServer", {}, function () {});
|
||||
},
|
||||
cb
|
||||
);
|
||||
};
|
||||
|
@ -93,6 +100,7 @@ exports.search = function(query, cache, cb) {
|
|||
}
|
||||
);
|
||||
},
|
||||
function () { },
|
||||
cb
|
||||
);
|
||||
};
|
||||
|
|
|
@ -41,28 +41,42 @@ exports.formatParts = function () {
|
|||
return _.map(exports.parts, function (part) { return part.full_name; }).join("\n");
|
||||
};
|
||||
|
||||
exports.formatHooks = function () {
|
||||
exports.formatHooks = function (hook_set_name) {
|
||||
var res = [];
|
||||
_.chain(exports.hooks).keys().forEach(function (hook_name) {
|
||||
_.forEach(exports.hooks[hook_name], function (hook) {
|
||||
res.push(hook.hook_name + ": " + hook.hook_fn_name + " from " + hook.part.full_name);
|
||||
var hooks = exports.extractHooks(exports.parts, hook_set_name || "hooks");
|
||||
|
||||
_.chain(hooks).keys().forEach(function (hook_name) {
|
||||
_.forEach(hooks[hook_name], function (hook) {
|
||||
res.push("<dt>" + hook.hook_name + "</dt><dd>" + hook.hook_fn_name + " from " + hook.part.full_name + "</dd>");
|
||||
});
|
||||
});
|
||||
return res.join("\n");
|
||||
return "<dl>" + res.join("\n") + "</dl>";
|
||||
};
|
||||
|
||||
exports.loadFn = function (path, hookName) {
|
||||
var x = path.split(":");
|
||||
var fn = require(x[0]);
|
||||
var functionName = x[1] ? x[1] : hookName;
|
||||
var functionName
|
||||
, parts = path.split(":");
|
||||
|
||||
// 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) {
|
||||
fn = fn[name];
|
||||
});
|
||||
return fn;
|
||||
};
|
||||
|
||||
exports.extractHooks = function (parts, hook_set_name, plugins) {
|
||||
exports.extractHooks = function (parts, hook_set_name) {
|
||||
var hooks = {};
|
||||
_.each(parts,function (part) {
|
||||
_.chain(part[hook_set_name] || {})
|
||||
|
|
|
@ -70,6 +70,11 @@ function init() {
|
|||
sendSocketMsg("CLIENT_READY", {});
|
||||
});
|
||||
|
||||
socket.on('disconnect', function()
|
||||
{
|
||||
BroadcastSlider.showReconnectUI();
|
||||
});
|
||||
|
||||
//route the incoming messages
|
||||
socket.on('message', function(message)
|
||||
{
|
||||
|
@ -79,13 +84,11 @@ function init() {
|
|||
{
|
||||
handleClientVars(message);
|
||||
}
|
||||
else if(message.type == "CHANGESET_REQ")
|
||||
{
|
||||
changesetLoader.handleSocketResponse(message);
|
||||
}
|
||||
else if(message.accessStatus)
|
||||
{
|
||||
$("body").html("<h2>You have no permission to access this pad</h2>")
|
||||
} else {
|
||||
changesetLoader.handleMessageFromServer(message);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -97,6 +100,12 @@ function init() {
|
|||
} else {
|
||||
$("#returnbutton").attr("href", document.location.href.substring(0,document.location.href.lastIndexOf("/")));
|
||||
}
|
||||
|
||||
$('button#forcereconnect').click(function()
|
||||
{
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -106,7 +115,7 @@ function sendSocketMsg(type, data)
|
|||
var sessionID = readCookie("sessionID");
|
||||
var password = readCookie("password");
|
||||
|
||||
var msg = { "component" : "timeslider",
|
||||
var msg = { "component" : "pad", // FIXME: Remove this stupidity!
|
||||
"type": type,
|
||||
"data": data,
|
||||
"padId": padId,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<%
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
%>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Plugin information</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
|
||||
<link rel="stylesheet" href="../../static/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<h1>Etherpad Lite</h1>
|
||||
<div class="separator"></div>
|
||||
|
||||
<h2>Installed plugins</h2>
|
||||
<pre><%= plugins.formatPlugins() %></pre>
|
||||
|
||||
<h2>Installed parts</h2>
|
||||
<pre><%= plugins.formatParts() %></pre>
|
||||
|
||||
<h2>Installed hooks</h2>
|
||||
<h3>Server side hooks</h3>
|
||||
<div><%= plugins.formatHooks() %></div>
|
||||
|
||||
<h3>Client side hooks</h3>
|
||||
<div><%= plugins.formatHooks("client_hooks") %></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
|
||||
<h1>Etherpad Lite</h1>
|
||||
|
||||
<a href="/admin/plugins/info">Technical information on installed plugins</a>
|
||||
|
||||
<div class="separator"></div>
|
||||
<h2>Installed plugins</h2>
|
||||
<table>
|
||||
|
|
|
@ -24,60 +24,60 @@
|
|||
<div id="editbar" class="toolbar">
|
||||
<ul class="menu_left">
|
||||
<% e.begin_block("editbarMenuLeft"); %>
|
||||
<li id="bold" data-key="bold">
|
||||
<li class="acl-write" id="bold" data-key="bold">
|
||||
<a class="grouped-left" title="Bold (ctrl-B)">
|
||||
<span class="buttonicon buttonicon-bold"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="italic" data-key="italic">
|
||||
<li class="acl-write" id="italic" data-key="italic">
|
||||
<a class="grouped-middle" title="Italics (ctrl-I)">
|
||||
<span class="buttonicon buttonicon-italic"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="underline" data-key="underline">
|
||||
<li class="acl-write" id="underline" data-key="underline">
|
||||
<a class="grouped-middle" title="Underline (ctrl-U)">
|
||||
<span class="buttonicon buttonicon-underline"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="strikethrough" data-key="strikethrough">
|
||||
<li class="acl-write" id="strikethrough" data-key="strikethrough">
|
||||
<a class="grouped-right" title="Strikethrough">
|
||||
<span class="buttonicon buttonicon-strikethrough"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="oderedlist" data-key="insertorderedlist">
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="oderedlist" data-key="insertorderedlist">
|
||||
<a class="grouped-left" title="Toggle Ordered List">
|
||||
<span class="buttonicon buttonicon-insertorderedlist"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="unoderedlist" data-key="insertunorderedlist">
|
||||
<li class="acl-write" id="unoderedlist" data-key="insertunorderedlist">
|
||||
<a class="grouped-middle" title="Toggle Bullet List">
|
||||
<span class="buttonicon buttonicon-insertunorderedlist"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="indent" data-key="indent">
|
||||
<li class="acl-write" id="indent" data-key="indent">
|
||||
<a class="grouped-middle" title="Indent">
|
||||
<span class="buttonicon buttonicon-indent"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="outdent" data-key="outdent">
|
||||
<li class="acl-write" id="outdent" data-key="outdent">
|
||||
<a class="grouped-right" title="Unindent">
|
||||
<span class="buttonicon buttonicon-outdent"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="undo" data-key="undo">
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="undo" data-key="undo">
|
||||
<a class="grouped-left" title="Undo (ctrl-Z)">
|
||||
<span class="buttonicon buttonicon-undo"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="redo" data-key="redo">
|
||||
<li class="acl-write" id="redo" data-key="redo">
|
||||
<a class="grouped-right" title="Redo (ctrl-Y)">
|
||||
<span class="buttonicon buttonicon-redo"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="clearAuthorship" data-key="clearauthorship">
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="clearAuthorship" data-key="clearauthorship">
|
||||
<a title="Clear Authorship Colors">
|
||||
<span class="buttonicon buttonicon-clearauthorship"></span>
|
||||
</a>
|
||||
|
@ -86,24 +86,25 @@
|
|||
</ul>
|
||||
<ul class="menu_right">
|
||||
<% e.begin_block("editbarMenuRight"); %>
|
||||
<li data-key="settings">
|
||||
<li class="acl-write" data-key="settings">
|
||||
<a class="grouped-left" id="settingslink" title="Settings of this pad">
|
||||
<span class="buttonicon buttonicon-settings"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" data-key="savedRevision">
|
||||
<a class="grouped-right" id="revisionlink" title="Mark this revision as a saved revision">
|
||||
<span class="buttonicon buttonicon-savedRevision"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write separator"></li>
|
||||
<li data-key="import_export">
|
||||
<a class="grouped-middle" id="importexportlink" title="Import/Export from/to different document formats">
|
||||
<a class="grouped-left" id="importexportlink" title="Import/Export from/to different document formats">
|
||||
<span class="buttonicon buttonicon-import_export"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-key="embed">
|
||||
<a class="grouped-middle" id="embedlink" title="Share and Embed this pad">
|
||||
<span class="buttonicon buttonicon-embed"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-key="savedRevision">
|
||||
<a class="grouped-right" id="revisionlink" title="Mark this revision as a saved revision">
|
||||
<span class="buttonicon buttonicon-savedRevision"></span>
|
||||
<a class="grouped-right" id="embedlink" title="Share and Embed this pad">
|
||||
<span class="grouped-right buttonicon buttonicon-embed"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
|
@ -185,7 +186,7 @@
|
|||
|
||||
<div id="importexport" class="popup">
|
||||
<h1>Import/Export</h1>
|
||||
<div class="column">
|
||||
<div class="column acl-write">
|
||||
<% e.begin_block("importColumn"); %>
|
||||
<h2>Upload any text file or document</h2><br>
|
||||
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
||||
|
@ -221,7 +222,7 @@
|
|||
|
||||
<div id="embed" class="popup">
|
||||
<% e.begin_block("embedPopup"); %>
|
||||
<div id="embedreadonly" class="right">
|
||||
<div id="embedreadonly" class="right acl-write">
|
||||
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
|
||||
<label for="readonlyinput">Read only</label>
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,48 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mainmodals"></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>
|
||||
</div>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
|
||||
<!-- export code -->
|
||||
<div id="importexport">
|
||||
|
|
Loading…
Reference in New Issue