diff --git a/.gitignore b/.gitignore
index 0bd7f0664..2fbb32200 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,5 @@
node_modules
settings.json
-static/js/jquery.js
-static/js/prefixfree.js
APIKEY.txt
bin/abiword.exe
bin/node.exe
@@ -11,6 +9,5 @@ bin/convertSettings.json
*~
*.patch
src/static/js/jquery.js
-src/static/js/prefixfree.js
npm-debug.log
*.DS_Store
diff --git a/README.plugins b/README.plugins
new file mode 100644
index 000000000..72c456447
--- /dev/null
+++ b/README.plugins
@@ -0,0 +1,16 @@
+So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json
+require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded
+A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
+require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name
+That is the basis.
+Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored).
+This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order
+Ordering between plugins is undefined, only parts are ordered.
+
+A plugin usually has one part, but it van have multiple.
+This is so that it can insert some hook registration before that of another plugin, and another one after.
+This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps
+So, that's basically it... apart from client-side hooks
+which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client...
+One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json...
+was that clear?
\ No newline at end of file
diff --git a/available_plugins/ep_fintest/ep.json b/available_plugins/ep_fintest/ep.json
index f9e914914..4ec8e3924 100644
--- a/available_plugins/ep_fintest/ep.json
+++ b/available_plugins/ep_fintest/ep.json
@@ -25,7 +25,8 @@
"hooks": {
"somehookname": "ep_fintest/otherpart:somehook",
"morehook": "ep_fintest/otherpart:morehook",
- "expressCreateServer": "ep_fintest/otherpart:expressServer"
+ "expressCreateServer": "ep_fintest/otherpart:expressServer",
+ "eejsBlock_editbarMenuLeft": "ep_fintest/otherpart:eejsBlock_editbarMenuLeft"
},
"client_hooks": {
"somehookname": "ep_fintest/static/js/test:bar"
diff --git a/available_plugins/ep_fintest/otherpart.js b/available_plugins/ep_fintest/otherpart.js
index ca259f44b..718fb095c 100644
--- a/available_plugins/ep_fintest/otherpart.js
+++ b/available_plugins/ep_fintest/otherpart.js
@@ -14,3 +14,12 @@ exports.expressServer = function (hook_name, args, cb) {
res.send("Abra cadabra");
});
}
+
+exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) {
+ args.content = args.content + '\
+
\
+ \
+
\
+ ';
+ return cb();
+}
diff --git a/bin/installDeps.sh b/bin/installDeps.sh
index e2a43c530..2acebd82e 100755
--- a/bin/installDeps.sh
+++ b/bin/installDeps.sh
@@ -81,21 +81,6 @@ if [ $DOWNLOAD_JQUERY = "true" ]; then
curl -lo src/static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
fi
-echo "Ensure prefixfree is downloaded and up to date..."
-DOWNLOAD_PREFIXFREE="true"
-NEEDED_VERSION="1.0.4"
-if [ -f "src/static/js/prefixfree.js" ]; then
- VERSION=$(cat src/static/js/prefixfree.js | grep "PrefixFree" | grep -o "[0-9].[0-9].[0-9]");
-
- if [ $VERSION = $NEEDED_VERSION ]; then
- DOWNLOAD_PREFIXFREE="false"
- fi
-fi
-
-if [ $DOWNLOAD_PREFIXFREE = "true" ]; then
- curl -lo src/static/js/prefixfree.js -k https://raw.github.com/LeaVerou/prefixfree/master/prefixfree.js || exit 1
-fi
-
#Remove all minified data to force node creating it new
echo "Clear minfified cache..."
rm -f var/minified*
diff --git a/settings.json.template b/settings.json.template
index 3ae0a9815..7aaa5d7ed 100644
--- a/settings.json.template
+++ b/settings.json.template
@@ -50,6 +50,12 @@
/* This setting is used if you need http basic auth */
// "httpAuth" : "user:pass",
+ /* This setting is used for http basic auth for admin pages. If not set, the admin page won't be accessible from web*/
+ // "adminHttpAuth" : "user:pass",
+
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
- "loglevel": "INFO"
+ "loglevel": "INFO",
+
+ /* cache 6 hours = 1000*60*60*6 */
+ "maxAge": 21600000
}
diff --git a/settings.json.template_windows b/settings.json.template_windows
index da661fcae..35b54d8da 100644
--- a/settings.json.template_windows
+++ b/settings.json.template_windows
@@ -40,5 +40,9 @@
/* 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
+ "abiword" : null,
+
+ /* cache 6 hours = 1000*60*60*6 */
+ "maxAge": 21600000
+
}
diff --git a/src/ep.json b/src/ep.json
index 59cbf3aa4..6bc777350 100644
--- a/src/ep.json
+++ b/src/ep.json
@@ -8,7 +8,9 @@
{ "name": "apicalls", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/apicalls:expressCreateServer" } },
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
{ "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
- { "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } }
-
+ { "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
+ { "name": "adminplugins", "hooks": {
+ "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
+ "socketio": "ep_etherpad-lite/node/hooks/express/adminplugins:socketio" } }
]
}
diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js
index c384f172c..b4a39c17e 100644
--- a/src/node/db/Pad.js
+++ b/src/node/db/Pad.js
@@ -15,6 +15,11 @@ var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler");
var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto");
+var randomString = require("../utils/randomstring");
+
+//serialization/deserialization attributes
+var attributeBlackList = ["id"];
+var jsonableList = ["pool"];
/**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
@@ -34,7 +39,7 @@ var Pad = function Pad(id) {
this.publicStatus = false;
this.passwordHash = null;
this.id = id;
-
+ this.savedRevisions = [];
};
exports.Pad = Pad;
@@ -75,15 +80,28 @@ 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, {atext: this.atext,
- pool: this.pool.toJsonable(),
- head: this.head,
- chatHead: this.chatHead,
- publicStatus: this.publicStatus,
- passwordHash: this.passwordHash});
+ db.set("pad:"+this.id+":revs:"+newRev, newRevData);
+ this.saveToDatabase();
};
+//save all attributes to the database
+Pad.prototype.saveToDatabase = function saveToDatabase(){
+ var dbObject = {};
+
+ for(var attr in this){
+ if(typeof this[attr] === "function") continue;
+ if(attributeBlackList.indexOf(attr) !== -1) continue;
+
+ dbObject[attr] = this[attr];
+
+ if(jsonableList.indexOf(attr) !== -1){
+ dbObject[attr] = dbObject[attr].toJsonable();
+ }
+ }
+
+ db.set("pad:"+this.id, dbObject);
+}
+
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
};
@@ -200,11 +218,10 @@ Pad.prototype.setText = function setText(newText) {
};
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
- this.chatHead++;
- //save the chat entry in the database
- db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
- //save the new chat head
- db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
+ this.chatHead++;
+ //save the chat entry in the database
+ db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
+ this.saveToDatabase();
};
Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
@@ -324,27 +341,14 @@ Pad.prototype.init = function init(text, callback) {
//if this pad exists, load it
if(value != null)
{
- _this.head = value.head;
- _this.atext = value.atext;
- _this.pool = _this.pool.fromJsonable(value.pool);
-
- //ensure we have a local chatHead variable
- if(value.chatHead != null)
- _this.chatHead = value.chatHead;
- else
- _this.chatHead = -1;
-
- //ensure we have a local publicStatus variable
- if(value.publicStatus != null)
- _this.publicStatus = value.publicStatus;
- else
- _this.publicStatus = false;
-
- //ensure we have a local passwordHash variable
- if(value.passwordHash != null)
- _this.passwordHash = value.passwordHash;
- else
- _this.passwordHash = null;
+ //copy all attr. To a transfrom via fromJsonable if necassary
+ for(var attr in value){
+ if(jsonableList.indexOf(attr) !== -1){
+ _this[attr] = _this[attr].fromJsonable(value[attr]);
+ } else {
+ _this[attr] = value[attr];
+ }
+ }
}
//this pad doesn't exist, so create it
else
@@ -452,12 +456,12 @@ Pad.prototype.remove = function remove(callback) {
//set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus;
- db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
+ this.saveToDatabase();
};
Pad.prototype.setPassword = function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt());
- db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
+ this.saveToDatabase();
};
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
@@ -468,6 +472,31 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
return this.passwordHash != null;
};
+Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) {
+ //if this revision is already saved, return silently
+ for(var i in this.savedRevisions){
+ if(this.savedRevisions.revNum === revNum){
+ return;
+ }
+ }
+
+ //build the saved revision object
+ var savedRevision = {};
+ savedRevision.revNum = revNum;
+ savedRevision.savedById = savedById;
+ savedRevision.label = label || "Revision " + revNum;
+ savedRevision.timestamp = new Date().getTime();
+ savedRevision.id = randomString(10);
+
+ //save this new saved revision
+ this.savedRevisions.push(savedRevision);
+ this.saveToDatabase();
+};
+
+Pad.prototype.getSavedRevisions = function getSavedRevisions() {
+ return this.savedRevisions;
+};
+
/* Crypto helper methods */
function hash(password, salt)
diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js
index 4e3a31999..5f08b1b1b 100644
--- a/src/node/db/PadManager.js
+++ b/src/node/db/PadManager.js
@@ -115,7 +115,13 @@ exports.doesPadExists = function(padId, callback)
db.get("pad:"+padId, function(err, value)
{
if(ERR(err, callback)) return;
- callback(null, value != null && value.atext);
+ if(value != null && value.atext){
+ callback(null, true);
+ }
+ else
+ {
+ callback(null, false);
+ }
});
}
diff --git a/src/node/eejs/examples/bar.ejs b/src/node/eejs/examples/bar.ejs
new file mode 100644
index 000000000..6a2cc4bab
--- /dev/null
+++ b/src/node/eejs/examples/bar.ejs
@@ -0,0 +1,9 @@
+a
+<% e.begin_block("bar"); %>
+ A
+ <% e.begin_block("foo"); %>
+ XX
+ <% e.end_block(); %>
+ B
+<% e.end_block(); %>
+b
diff --git a/src/node/eejs/examples/foo.ejs b/src/node/eejs/examples/foo.ejs
new file mode 100644
index 000000000..daee5f8e8
--- /dev/null
+++ b/src/node/eejs/examples/foo.ejs
@@ -0,0 +1,7 @@
+<% e.inherit("./bar.ejs"); %>
+
+<% e.begin_define_block("foo"); %>
+ YY
+ <% e.super(); %>
+ ZZ
+<% e.end_define_block(); %>
diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js
new file mode 100644
index 000000000..90c69e595
--- /dev/null
+++ b/src/node/eejs/index.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011 RedHog (Egil Möller)
+ *
+ * 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.
+ */
+
+/* Basic usage:
+ *
+ * require("./index").require("./examples/foo.ejs")
+ */
+
+var ejs = require("ejs");
+var fs = require("fs");
+var path = require("path");
+var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
+
+exports.info = {
+ buf_stack: [],
+ block_stack: [],
+ blocks: {},
+ file_stack: [],
+};
+
+exports._init = function (b, recursive) {
+ exports.info.buf_stack.push(exports.info.buf);
+ exports.info.buf = b;
+}
+
+exports._exit = function (b, recursive) {
+ exports.info.file_stack[exports.info.file_stack.length-1].inherit.forEach(function (item) {
+ exports._require(item.name, item.args);
+ });
+ exports.info.buf = exports.info.buf_stack.pop();
+}
+
+exports.begin_capture = function() {
+ exports.info.buf_stack.push(exports.info.buf.concat());
+ exports.info.buf.splice(0, exports.info.buf.length);
+}
+
+exports.end_capture = function () {
+ var res = exports.info.buf.join("");
+ exports.info.buf.splice.apply(
+ exports.info.buf,
+ [0, exports.info.buf.length].concat(exports.info.buf_stack.pop()));
+ return res;
+}
+
+exports.begin_define_block = function (name) {
+ if (typeof exports.info.blocks[name] == "undefined")
+ exports.info.blocks[name] = {};
+ exports.info.block_stack.push(name);
+ exports.begin_capture();
+}
+
+exports.super = function () {
+ exports.info.buf.push('');
+}
+
+exports.end_define_block = function () {
+ content = exports.end_capture();
+ var name = exports.info.block_stack.pop();
+ if (typeof exports.info.blocks[name].content == "undefined")
+ exports.info.blocks[name].content = content;
+ else if (typeof exports.info.blocks[name].content.indexOf(''))
+ exports.info.blocks[name].content = exports.info.blocks[name].content.replace('', content);
+
+ return exports.info.blocks[name].content;
+}
+
+exports.end_block = function () {
+ var name = exports.info.block_stack[exports.info.block_stack.length-1];
+ var args = {content: exports.end_define_block()};
+ hooks.callAll("eejsBlock_" + name, args);
+ exports.info.buf.push(args.content);
+}
+
+exports.begin_block = exports.begin_define_block;
+
+exports.inherit = function (name, args) {
+ exports.info.file_stack[exports.info.file_stack.length-1].inherit.push({name:name, args:args});
+}
+
+exports.require = function (name, args) {
+ if (args == undefined) args = {};
+
+ if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) {
+ name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path), name);
+ }
+ var ejspath = require.resolve(name)
+
+ args.e = exports;
+ args.require = require;
+ var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
+
+ exports.info.file_stack.push({path: ejspath, inherit: []});
+ var res = ejs.render(template, args);
+ exports.info.file_stack.pop();
+
+ return res;
+}
+
+exports._require = function (name, args) {
+ exports.info.buf.push(exports.require(name, args));
+}
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js
index c3ee231c3..866edeb02 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -190,6 +190,11 @@ exports.handleMessage = function(client, message)
{
handleChatMessage(client, message);
}
+ else if(message.type == "COLLABROOM" &&
+ message.data.type == "SAVE_REVISION")
+ {
+ handleSaveRevisionMessage(client, message);
+ }
else if(message.type == "COLLABROOM" &&
message.data.type == "CLIENT_MESSAGE" &&
message.data.payload.type == "suggestUserName")
@@ -203,6 +208,23 @@ exports.handleMessage = function(client, message)
}
}
+/**
+ * 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 userId = sessioninfos[client.id].author;
+
+ padManager.getPad(padId, function(err, pad)
+ {
+ if(ERR(err)) return;
+
+ pad.addSavedRevision(pad.head, userId);
+ });
+}
+
/**
* Handles a Chat Message
* @param client the client that send this message
diff --git a/src/node/handler/TimesliderMessageHandler.js b/src/node/handler/TimesliderMessageHandler.js
index da8597791..a6cf8f4d8 100644
--- a/src/node/handler/TimesliderMessageHandler.js
+++ b/src/node/handler/TimesliderMessageHandler.js
@@ -166,6 +166,7 @@ function createTimesliderClientVars (padId, callback)
hooks: [],
initialStyledContents: {}
};
+
var pad;
var initialChangesets = [];
@@ -180,6 +181,12 @@ function createTimesliderClientVars (padId, callback)
callback();
});
},
+ //get all saved revisions and add them
+ function(callback)
+ {
+ clientVars.savedRevisions = pad.getSavedRevisions();
+ callback();
+ },
//get all authors and add them to
function(callback)
{
diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js
new file mode 100644
index 000000000..fa7e70771
--- /dev/null
+++ b/src/node/hooks/express/adminplugins.js
@@ -0,0 +1,51 @@
+var path = require('path');
+var eejs = require('ep_etherpad-lite/node/eejs');
+var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
+var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
+
+exports.expressCreateServer = function (hook_name, args, cb) {
+ args.app.get('/admin/plugins', function(req, res) {
+ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
+ var render_args = {
+ plugins: plugins.plugins,
+ search_results: {},
+ errors: [],
+ };
+
+ res.send(eejs.require(
+ "ep_etherpad-lite/templates/admin/plugins.html",
+ render_args), {});
+ });
+}
+
+exports.socketio = function (hook_name, args, cb) {
+ var io = args.io.of("/pluginfw/installer");
+ io.on('connection', function (socket) {
+ socket.on("load", function (query) {
+ socket.emit("installed-results", {results: plugins.plugins});
+ });
+
+ socket.on("search", function (query) {
+ socket.emit("progress", {progress:0, message:'Fetching results...'});
+ installer.search(query, function (progress) {
+ if (progress.results)
+ socket.emit("search-result", progress);
+ socket.emit("progress", progress);
+ });
+ });
+
+ socket.on("install", function (plugin_name) {
+ socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
+ installer.install(plugin_name, function (progress) {
+ socket.emit("progress", progress);
+ });
+ });
+
+ socket.on("uninstall", function (plugin_name) {
+ socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
+ installer.uninstall(plugin_name, function (progress) {
+ socket.emit("progress", progress);
+ });
+ });
+ });
+}
diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js
index 13cfd8215..585a7eab8 100644
--- a/src/node/hooks/express/specialpages.js
+++ b/src/node/hooks/express/specialpages.js
@@ -1,12 +1,12 @@
var path = require('path');
+var eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
//serve index.html under /
args.app.get('/', function(req, res)
{
- var filePath = path.normalize(__dirname + "/../../../static/index.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
+ res.send(eejs.require("ep_etherpad-lite/templates/index.html"), { maxAge: exports.maxAge });
});
//serve robots.txt
@@ -34,15 +34,13 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next)
{
- var filePath = path.normalize(__dirname + "/../../../static/pad.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
+ res.send(eejs.require("ep_etherpad-lite/templates/pad.html"), { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', function(req, res, next)
{
- var filePath = path.normalize(__dirname + "/../../../static/timeslider.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
+ res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"), { maxAge: exports.maxAge });
});
}
\ No newline at end of file
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js
index 8e9f967a9..d0e287373 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.js
@@ -6,14 +6,30 @@ var settings = require('../../utils/Settings');
//checks for basic http auth
exports.basicAuth = function (req, res, next) {
- if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
- // fetch login and password
- if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() == settings.httpAuth) {
- next();
- return;
+
+ // When handling HTTP-Auth, an undefined password will lead to no authorization at all
+ var pass = settings.httpAuth || '';
+
+ if (req.path.indexOf('/admin') == 0) {
+ var pass = settings.adminHttpAuth;
+
+ }
+
+ // Just pass if password is an empty string
+ if (pass === '') {
+ return next();
+ }
+
+
+ // If a password has been set and auth headers are present...
+ if (pass && req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
+ // ...check login and password
+ if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() === pass) {
+ return next();
}
}
+ // Otherwise return Auth required Headers, delayed for 1 second, if auth failed.
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
@@ -25,8 +41,7 @@ exports.basicAuth = function (req, res, next) {
}
exports.expressConfigure = function (hook_name, args, cb) {
- // Activate http basic auth if it has been defined in settings.json
- if(settings.httpAuth != null) args.app.use(exports.basicAuth);
+ args.app.use(exports.basicAuth);
// 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.
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js
index f569d4b92..b5d7b4727 100644
--- a/src/node/utils/Minify.js
+++ b/src/node/utils/Minify.js
@@ -29,7 +29,6 @@ var pro = require("uglify-js").uglify;
var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var RequireKernel = require('require-kernel');
-var server = require('../server');
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
var TAR_PATH = path.join(__dirname, 'tar.json');
@@ -109,10 +108,10 @@ exports.minify = function(req, res, next)
date = new Date(date);
res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString());
- if (server.maxAge) {
- var expiresDate = new Date((new Date()).getTime()+server.maxAge*1000);
+ if (settings.maxAge) {
+ var expiresDate = new Date((new Date()).getTime()+settings.maxAge*1000);
res.setHeader('expires', expiresDate.toUTCString());
- res.setHeader('cache-control', 'max-age=' + server.maxAge);
+ res.setHeader('cache-control', 'max-age=' + settings.maxAge);
}
}
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js
index 24237de49..12fcc55c5 100644
--- a/src/node/utils/Settings.js
+++ b/src/node/utils/Settings.js
@@ -85,6 +85,11 @@ exports.loglevel = "INFO";
*/
exports.httpAuth = null;
+/**
+ * Http basic auth, with "user:password" format
+ */
+exports.adminHttpAuth = null;
+
//checks if abiword is avaiable
exports.abiwordAvailable = function()
{
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js
index b8b7e1f1a..70d5a08c4 100644
--- a/src/node/utils/caching_middleware.js
+++ b/src/node/utils/caching_middleware.js
@@ -18,13 +18,11 @@ var async = require('async');
var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
-var server = require('../server');
var zlib = require('zlib');
var util = require('util');
+var settings = require('./Settings');
-var ROOT_DIR = path.normalize(__dirname + "/../");
-var CACHE_DIR = path.normalize(ROOT_DIR + '../../var/');
-console.log(CACHE_DIR)
+var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {};
diff --git a/src/node/utils/randomstring.js b/src/node/utils/randomstring.js
new file mode 100644
index 000000000..4c1bba244
--- /dev/null
+++ b/src/node/utils/randomstring.js
@@ -0,0 +1,16 @@
+/**
+ * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
+ */
+var randomString = function randomString(len)
+{
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ var randomstring = '';
+ for (var i = 0; i < len; i++)
+ {
+ var rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars.substring(rnum, rnum + 1);
+ }
+ return randomstring;
+};
+
+module.exports = randomString;
diff --git a/src/node/utils/tar.json b/src/node/utils/tar.json
index f4af3b186..14c93f5c7 100644
--- a/src/node/utils/tar.json
+++ b/src/node/utils/tar.json
@@ -22,7 +22,6 @@
, "chat.js"
, "excanvas.js"
, "farbtastic.js"
- , "prefixfree.js"
]
, "timeslider.js": [
"jquery.js"
diff --git a/src/package.json b/src/package.json
index 6253ecb16..0d4ad5386 100644
--- a/src/package.json
+++ b/src/package.json
@@ -23,7 +23,12 @@
"log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2",
- "npm" : "1.1",
+ "npm" : "1.1",
+ "ejs" : "0.6.1",
+ "node.extend" : "1.0.0",
+ "graceful-fs" : "1.1.5",
+ "slide" : "1.1.3",
+ "semver" : "1.0.13",
"underscore" : "1.3.1"
},
"devDependencies": {
diff --git a/src/static/css/pad.css b/src/static/css/pad.css
index 969d00276..19d148a31 100644
--- a/src/static/css/pad.css
+++ b/src/static/css/pad.css
@@ -24,7 +24,7 @@ a img
}
/* menu */
-#editbar ul
+.toolbar ul
{
position: relative;
list-style: none;
@@ -35,18 +35,20 @@ a img
}
-#editbar
+.toolbar
{
background: #f7f7f7;
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
border-bottom: 1px solid #ccc;
- height: 32px;
overflow: hidden;
padding-top: 3px;
- width: 100%;
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 32px;
}
-#editbar ul li
+.toolbar ul li
{
background: #fff;
background: linear-gradient(#fff, #f0f0f0);
@@ -61,52 +63,52 @@ a img
width: 18px;
}
-#editbar ul li a
+.toolbar ul li a
{
text-decoration: none;
color: #ccc;
position: absolute;
}
-#editbar ul li a span
+.toolbar ul li a span
{
position: relative;
top:-2px
}
-#editbar ul li:hover {
+.toolbar ul li:hover {
background: #fff;
background: linear-gradient(#f4f4f4, #e4e4e4);
}
-#editbar ul li:active {
+.toolbar ul li:active {
background: #eee;
background: linear-gradient(#ddd, #fff);
box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
}
-#editbar ul li.separator
+.toolbar ul li.separator
{
border: inherit;
background: inherit;
visibility:hidden;
width: 0px;
}
-#editbar ul li a
+.toolbar ul li a
{
display: block;
}
-#editbar ul li a img
+.toolbar ul li a img
{
padding: 1px;
}
-#editbar ul
+.toolbar ul
{
float: left;
}
-#editbar ul#menu_right
+.toolbar ul.menu_right
{
float: right;
}
@@ -320,7 +322,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
z-index: 10;
}
-#editbarsavetable
+.toolbarsavetable
{
position:absolute;
top: 6px;
@@ -328,7 +330,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
height: 24px;
}
-#editbarsavetable td, #editbartable td
+.toolbarsavetable td, .toolbartable td
{
white-space: nowrap;
}
@@ -688,14 +690,15 @@ a#topbarmaximize {
background: url(static/img/maximize_maximized.png);
}
-#editbarinner h1 {
+.toolbarinner h1 {
line-height: 29px;
font-size: 16px;
padding-left: 6pt;
margin-top: 0;
+ white-space: nowrap;
}
-#editbarinner h1 a {
+.toolbarinner h1 a {
font-size: 12px;
}
@@ -1034,6 +1037,9 @@ margin-top: 1px;
background-position: 0px -183px;
display: inline-block;
}
+.buttonicon-savedRevision {
+ background-position: 0px -493px
+}
#usericon
{
@@ -1173,13 +1179,13 @@ input[type=checkbox] {
}
@media screen and (max-width: 600px) {
- #editbar ul li {
+ .toolbar ul li {
padding: 4px 1px;
}
}
@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
- #editbar ul li {
+ .toolbar ul li {
padding: 4px 3px;
}
#users {
@@ -1194,7 +1200,7 @@ input[type=checkbox] {
#editorcontainer {
margin-bottom: 33px;
}
- #editbar ul#menu_right {
+ .toolbar ul.menu_right {
background: #f7f7f7;
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
width: 100%;
@@ -1204,7 +1210,7 @@ input[type=checkbox] {
bottom: 0;
border-top: 1px solid #ccc;
}
- #editbar ul#menu_right li:last-child {
+ .toolbar ul.menu_right li:last-child {
height: 24px;
border-radius: 0;
margin-top: 0;
@@ -1226,7 +1232,7 @@ input[type=checkbox] {
border-top-right-radius: 0;
border-right: none;
}
- #editbar ul li a span {
+ .toolbar ul li a span {
top: -3px;
}
#usericonback {
@@ -1235,10 +1241,10 @@ input[type=checkbox] {
#qrcode {
display: none;
}
- #editbar ul#menu_right li:not(:last-child) {
+ .toolbar ul.menu_right li:not(:last-child) {
display: block;
}
- #editbar ul#menu_right > li {
+ .toolbar ul.menu_right > li {
background: none;
border: none;
margin-top: 4px;
@@ -1267,4 +1273,4 @@ input[type=checkbox] {
#online_count {
line-height: 24px;
}
-}
\ No newline at end of file
+}
diff --git a/src/static/css/timeslider.css b/src/static/css/timeslider.css
index 926c80123..38e4cfb10 100644
--- a/src/static/css/timeslider.css
+++ b/src/static/css/timeslider.css
@@ -42,10 +42,10 @@
#leftstar, #rightstar, #leftstep, #rightstep
{background:url(../../static/img/stepper_buttons.png) 0 0 no-repeat; height:21px; overflow:hidden; position:absolute;}
-#leftstar {background-position:0 44px; right:34px; top:8px; width:30px;}
-#rightstar {background-position:29px 44px; right:5px; top:8px; width:29px;}
-#leftstep {background-position:0 22px; right:34px; top:20px; width:30px;}
-#rightstep {background-position:29px 22px; right:5px; top:20px; width:29px;}
+#leftstar {background-position:0 -44px; right:34px; top:8px; width:30px;}
+#rightstar {background-position:-29px -44px; right:5px; top:8px; width:29px;}
+#leftstep {background-position:0 -22px; right:34px; top:20px; width:30px;}
+#rightstep {background-position:-29px -22px; right:5px; top:20px; width:29px;}
#timeslider .star {
background-image:url(../../static/img/star.png);
@@ -71,8 +71,11 @@
#padmain {top:30px;}
#editbarright {float:right;}
#returnbutton {color:#222; font-size:16px; line-height:29px; margin-top:0; padding-right:6px;}
-#importexport {top:118px;}
#importexport .popup {width:185px;}
+#importexport{
+ top:118px;
+ width:185px;
+}
/* lists */
.list-bullet2, .list-indent2, .list-number2 {margin-left:3em;}
diff --git a/src/static/img/etherpad_lite_icons.png b/src/static/img/etherpad_lite_icons.png
index cadf5ed2b..27867d428 100644
Binary files a/src/static/img/etherpad_lite_icons.png and b/src/static/img/etherpad_lite_icons.png differ
diff --git a/src/static/img/star.png b/src/static/img/star.png
new file mode 100644
index 000000000..e0c7099e5
Binary files /dev/null and b/src/static/img/star.png differ
diff --git a/src/static/js/ace.js b/src/static/js/ace.js
index 685d45dfb..4dfcc64ef 100644
--- a/src/static/js/ace.js
+++ b/src/static/js/ace.js
@@ -233,14 +233,16 @@ require.setGlobalKeyPath("require");\n\
iframeHTML.push(doctype);
iframeHTML.push("");
+ iframeHTML.push('');
+
+ hooks.callAll("aceInitInnerdocbodyHead", {
+ iframeHTML: iframeHTML
+ });
// For compatability's sake transform in and out.
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
- hooks.callAll("aceInitInnerdocbodyHead", {
- iframeHTML: iframeHTML
- });
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.parse(iframeHTML[i]);
}
@@ -262,6 +264,11 @@ require.setGlobalKeyPath("require");\n\
// Inject my plugins into my child.
iframeHTML.push('\
-
-
-
-
-
-
diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html
new file mode 100644
index 000000000..7dcb6fa3e
--- /dev/null
+++ b/src/templates/admin/plugins.html
@@ -0,0 +1,217 @@
+
+
+ Plugin manager
+
+
+
+
+
+
+ <% if (errors.length) { %>
+
+ <% errors.forEach(function (item) { %>
+
<%= item.toString() %>
+ <% }) %>
+
+ <% } %>
+
+
+
Installed plugins
+
+
+
+
Name
+
Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Search for plugins to install
+
+
+
+
+
Name
+
Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Please wait:
+
+
+
+
+
+
+
+
diff --git a/src/static/index.html b/src/templates/index.html
similarity index 100%
rename from src/static/index.html
rename to src/templates/index.html
diff --git a/src/templates/pad.html b/src/templates/pad.html
new file mode 100644
index 000000000..e589fb55d
--- /dev/null
+++ b/src/templates/pad.html
@@ -0,0 +1,307 @@
+<%
+ var settings = require("ep_etherpad-lite/node/utils/Settings");
+%>
+
+
+
+ Etherpad Lite
+
+
+
+
+
+ <% e.begin_block("styles"); %>
+
+
+
+ <% e.end_block(); %>
+
+
+
+
We're having trouble talking to the EtherPad lite synchronization server. You may be connecting through an incompatible firewall or proxy server.
+
+
+
We were unable to connect to the EtherPad lite synchronization server. This may be due to an incompatibility with your web browser or internet connection.
+
+
+
You seem to have opened this pad in another browser window. If you'd like to use this window instead, you can reconnect.
+
+
+
Lost connection with the EtherPad lite synchronization server. This may be due to a loss of network connectivity.
+
+
+
Server not responding. This may be due to network connectivity issues or high load on the server.
+
+
+
Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.
+
+
+
This pad was deleted.
+
+
+
If this continues to happen, please let us know
+
+
+
+
+
+
+
+
+ <% e.end_block(); %>
+
+
+ <% e.begin_block("scripts"); %>
+
+
+
+ <% if (settings.minify) { %>
+
+ <% } %>
+
+ <% e.end_block(); %>
+
diff --git a/src/static/timeslider.html b/src/templates/timeslider.html
similarity index 98%
rename from src/static/timeslider.html
rename to src/templates/timeslider.html
index 413fbe804..02a343157 100644
--- a/src/static/timeslider.html
+++ b/src/templates/timeslider.html
@@ -119,8 +119,8 @@