summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/node/hooks/express/adminplugins.js4
-rw-r--r--src/node/hooks/express/socketio.js18
-rw-r--r--src/node/hooks/express/webaccess.js117
-rw-r--r--src/node/server.js5
-rw-r--r--src/node/utils/Settings.js15
-rw-r--r--src/package.json1
-rw-r--r--src/static/css/admin.css261
-rw-r--r--src/static/js/admin/plugins.js132
-rw-r--r--src/static/js/pad_editbar.js12
-rw-r--r--src/static/js/pluginfw/hooks.js64
-rw-r--r--src/static/js/pluginfw/installer.js34
-rw-r--r--src/templates/admin/plugins.html253
-rw-r--r--src/templates/pad.html290
13 files changed, 689 insertions, 517 deletions
diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js
index fa7e7077..7b21206c 100644
--- a/src/node/hooks/express/adminplugins.js
+++ b/src/node/hooks/express/adminplugins.js
@@ -21,13 +21,15 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) {
+ if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
+
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) {
+ installer.search(query, true, function (progress) {
if (progress.results)
socket.emit("search-result", progress);
socket.emit("progress", progress);
diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js
index e040f7ac..6774b653 100644
--- a/src/node/hooks/express/socketio.js
+++ b/src/node/hooks/express/socketio.js
@@ -7,11 +7,27 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var padMessageHandler = require("../../handler/PadMessageHandler");
var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler");
-
+var connect = require('connect');
+
exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(args.app);
+ /* Require an express session cookie to be present, and load the
+ * session. See http://www.danielbaulig.de/socket-ioexpress for more
+ * info */
+ io.set('authorization', function (data, accept) {
+ if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
+ data.cookie = connect.utils.parseCookie(data.headers.cookie);
+ data.sessionID = data.cookie.express_sid;
+ args.app.sessionStore.get(data.sessionID, function (err, session) {
+ if (err || !session) return accept('Bad session / session has expired', false);
+ data.session = new connect.middleware.session.Session(data, session);
+ accept(null, true);
+ });
+ });
+
+
//this is only a workaround to ensure it works with all browers behind a proxy
//we should remove this when the new socket.io version is more stable
io.set('transports', ['xhr-polling']);
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js
index 48b5edae..028d8ab1 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.js
@@ -2,55 +2,108 @@ var express = require('express');
var log4js = require('log4js');
var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings');
+var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
+var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
//checks for basic http auth
exports.basicAuth = function (req, res, next) {
-
- // 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();
+ var hookResultMangle = function (cb) {
+ return function (err, data) {
+ return cb(!err && data.length && data[0]);
+ }
}
-
-
- // 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();
+
+ var authorize = function (cb) {
+ // Do not require auth for static paths...this could be a bit brittle
+ if (req.path.match(/^\/(static|javascripts|pluginfw)/)) return cb(true);
+
+ if (req.path.indexOf('/admin') != 0) {
+ if (!settings.requireAuthentication) return cb(true);
+ if (!settings.requireAuthorization && req.session && req.session.user) return cb(true);
}
+
+ if (req.session && req.session.user && req.session.user.is_admin) return cb(true);
+
+ hooks.aCallFirst("authorize", {req: req, res:res, next:next, resource: req.path}, hookResultMangle(cb));
}
- // Do not require auth for static paths...this could be a bit brittle
- else if (req.path.match(/^\/(static|javascripts|pluginfw)/)) {
- return next();
+
+ var authenticate = function (cb) {
+ // If auth headers are present use them to authenticate...
+ if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
+ var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
+ var username = userpass[0];
+ var password = userpass[1];
+
+ if (settings.users[username] != undefined && settings.users[username].password == password) {
+ settings.users[username].username = username;
+ req.session.user = settings.users[username];
+ return cb(true);
+ }
+ return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(cb));
+ }
+ hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb));
}
- // 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 () {
- res.send('Authentication required', 401);
- }, 1000);
- } else {
- res.send('Authentication required', 401);
+ /* Authentication OR authorization failed. */
+ var failure = function () {
+ return hooks.aCallFirst("authFailure", {req: req, res:res, next:next}, hookResultMangle(function (ok) {
+ if (ok) return;
+ /* No plugin handler for invalid auth. Return Auth required
+ * Headers, delayed for 1 second, if authentication failed
+ * before. */
+ res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
+ if (req.headers.authorization) {
+ setTimeout(function () {
+ res.send('Authentication required', 401);
+ }, 1000);
+ } else {
+ res.send('Authentication required', 401);
+ }
+ }));
}
+
+
+ /* This is the actual authentication/authorization hoop. It is done in four steps:
+
+ 1) Try to just access the thing
+ 2) If not allowed using whatever creds are in the current session already, try to authenticate
+ 3) If authentication using already supplied credentials succeeds, try to access the thing again
+ 4) If all els fails, give the user a 401 to request new credentials
+
+ Note that the process could stop already in step 3 with a redirect to login page.
+
+ */
+
+ authorize(function (ok) {
+ if (ok) return next();
+ authenticate(function (ok) {
+ if (!ok) return failure();
+ authorize(function (ok) {
+ if (ok) return next();
+ failure();
+ });
+ });
+ });
}
exports.expressConfigure = function (hook_name, args, cb) {
- 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.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(express.cookieParser());
+
+ /* Do not let express create the session, so that we can retain a
+ * reference to it for socket.io to use. Also, set the key (cookie
+ * name) to a javascript identifier compatible string. Makes code
+ * handling it cleaner :) */
+
+ args.app.sessionStore = new express.session.MemoryStore();
+ args.app.use(express.session({store: args.app.sessionStore,
+ key: 'express_sid',
+ secret: apikey = randomString(32)}));
+
+ args.app.use(exports.basicAuth);
}
diff --git a/src/node/server.js b/src/node/server.js
index 6b443edb..9d2c52e4 100644
--- a/src/node/server.js
+++ b/src/node/server.js
@@ -30,6 +30,7 @@ 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 = "";
@@ -88,11 +89,11 @@ async.waterfall([
//let the server listen
app.listen(settings.port, settings.ip);
console.log("Server is listening at " + settings.ip + ":" + settings.port);
- if(settings.adminHttpAuth){
+ if(!_.isEmpty(settings.users)){
console.log("Plugin admin page listening at " + settings.ip + ":" + settings.port + "/admin/plugins");
}
else{
- console.log("Admin username and password not set in settings.json. To access admin please uncomment and edit adminHttpAuth in settings.json");
+ console.log("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
}
callback(null);
}
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js
index 12fcc55c..cb6a6403 100644
--- a/src/node/utils/Settings.js
+++ b/src/node/utils/Settings.js
@@ -80,15 +80,12 @@ exports.abiword = null;
*/
exports.loglevel = "INFO";
-/**
- * Http basic auth, with "user:password" format
- */
-exports.httpAuth = null;
-
-/**
- * Http basic auth, with "user:password" format
- */
-exports.adminHttpAuth = null;
+/* This setting is used if you need authentication and/or
+ * authorization. Note: /admin always requires authentication, and
+ * either authorization by a module, or a user with is_admin set */
+exports.requireAuthentication = false;
+exports.requireAuthorization = false;
+exports.users = {};
//checks if abiword is avaiable
exports.abiwordAvailable = function()
diff --git a/src/package.json b/src/package.json
index 83441da0..eda385b2 100644
--- a/src/package.json
+++ b/src/package.json
@@ -17,6 +17,7 @@
"ueberDB" : "0.1.7",
"async" : "0.1.18",
"express" : "2.5.8",
+ "connect" : "1.8.7",
"clean-css" : "0.3.2",
"uglify-js" : "1.2.5",
"formidable" : "1.0.9",
diff --git a/src/static/css/admin.css b/src/static/css/admin.css
index 67706fa2..b2dfc1c5 100644
--- a/src/static/css/admin.css
+++ b/src/static/css/admin.css
@@ -1,138 +1,123 @@
-body {
- margin: 0;
- height: 100%;
- color: #333;
- font: 14px helvetica, sans-serif;
- background: #ddd;
- background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
- background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
- background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
- background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
- border-top: 8px solid rgba(51,51,51,.8);
-}
-#wrapper {
- border-top: 1px solid #999;
- margin-top: 160px;
- padding: 15px;
- background: #eee;
- background: -webkit-linear-gradient(#fff,#ccc);
- background: -moz-linear-gradient(#fff,#ccc);
- background: -ms-linear-gradient(#fff,#ccc);
- background: -o-linear-gradient(#fff,#ccc);
- opacity: .9;
- box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
- max-width: 1200px;
- margin: auto;
-}
-#inner {
- width: 300px;
- margin: 0 auto;
-}
-#button:hover {
- cursor: pointer;
- background: #666;
- background: -webkit-linear-gradient(#707070,#666666 50%,#5B5B5B 51%,#474747);
- background: -moz-linear-gradient(#707070,#666666 50%,#5B5B5B 51%,#474747);
- background: -ms-linear-gradient(#707070,#666666 50%,#5B5B5B 51%,#474747);
- background: -o-linear-gradient(#707070,#666666 50%,#5B5B5B 51%,#474747);
-}
-#button:active {
- box-shadow: inset 0 1px 12px rgba(0,0,0,0.9);
- background: #444;
-}
-#label {
- text-align: left;
- text-shadow: 0 1px 1px #fff;
- margin: 16px auto 0;
-}
-form {
- border: 1px solid #bbb;
- border-radius: 3px;
- position: relative;
-}
-button, input {
- font-weight: bold;
- font-size: 15px;
-}
-input[type="button"] {
- height:30px;
-}
-input[type="text"] {
- border-radius: 3px;
- box-sizing: border-box;
- -moz-box-sizing: border-box;
- padding: 10px 45px 10px 10px;
- *padding: 0; /* IE7 hack */
- width: 100%;
- outline: none;
- border: none;
-}
-button{
- display:block;
-}
-@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
- body {
- background: #bbb;
- background: -webkit-linear-gradient(#aaa,#eee 60%) center fixed;
- background: -moz-linear-gradient(#aaa,#eee 60%) center fixed;
- background: -ms-linear-gradient(#aaa,#eee 60%) center fixed;
- }
- #wrapper {
- margin-top: 0;
- }
- #inner {
- width: 95%;
- }
- #label {
- text-align: center;
- }
-}
-
-table {
- border-collapse: collapse;
-}
-td, th {
- border: 1px solid black;
- padding-left: 10px;
- padding-right: 10px;
- padding-top: 2px;
- padding-bottom: 2px;
-}
-.template {
- display: none;
-}
-.dialog {
- display: none;
- position: absolute;
- left: 50%;
- top: 50%;
- width: 700px;
- height: 500px;
- margin-left: -350px;
- margin-top: -250px;
- border: 3px solid #999999;
- background: #eeeeee;
-}
-.dialog .title {
- margin: 0;
- padding: 2px;
- border-bottom: 3px solid #999999;
- font-size: 24px;
- line-height: 24px;
- height: 24px;
- overflow: hidden;
-}
-.dialog .title .close {
- float: right;
-}
-.dialog .history {
- background: #222222;
- color: #eeeeee;
- position: absolute;
- top: 41px;
- bottom: 10px;
- left: 10px;
- right: 10px;
- padding: 2px;
- overflow: auto;
-}
+body {
+ margin: 0;
+ height: 100%;
+ color: #333;
+ font: 14px helvetica, sans-serif;
+ background: #ddd;
+ background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ border-top: 8px solid rgba(51,51,51,.8);
+}
+#wrapper {
+ margin-top: 160px;
+ padding: 15px;
+ background: #fff;
+ opacity: .9;
+ box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
+ max-width: 700px;
+ margin: auto;
+ border-radius: 0 0 7px 7px;
+}
+h1 {
+ font-size: 29px;
+}
+h2 {
+ font-size: 24px;
+}
+.seperator {
+ margin: 10px 0;
+ height: 1px;
+ background: #aaa;
+ background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+}
+form {
+ margin-bottom: 0;
+}
+#inner {
+ width: 300px;
+ margin: 0 auto;
+}
+input {
+ font-weight: bold;
+ font-size: 15px;
+}
+input[type="button"] {
+ padding: 4px 6px;
+ margin: 0;
+}
+input[type="button"].do-install, input[type="button"].do-uninstall {
+ float: right;
+ width: 100px;
+}
+input[type="button"]#do-search {
+ display: block;
+}
+input[type="text"] {
+ border-radius: 3px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ padding: 10px;
+ *padding: 0; /* IE7 hack */
+ width: 100%;
+ outline: none;
+ border: 1px solid #ddd;
+ margin: 0 0 5px 0;
+ max-width: 500px;
+}
+table {
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ border-spacing: 0;
+ width: 100%;
+ margin: 20px 0;
+}
+table thead tr {
+ background: #eee;
+}
+td, th {
+ padding: 5px;
+}
+.template {
+ display: none;
+}
+.dialog {
+ display: none;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 700px;
+ height: 500px;
+ margin-left: -350px;
+ margin-top: -250px;
+ border: 3px solid #999;
+ background: #eee;
+}
+.dialog .title {
+ margin: 0;
+ padding: 2px;
+ border-bottom: 3px solid #999;
+ font-size: 24px;
+ line-height: 24px;
+ height: 24px;
+ overflow: hidden;
+}
+.dialog .title .close {
+ float: right;
+ padding: 1px 10px;
+}
+.dialog .history {
+ background: #222;
+ color: #eee;
+ position: absolute;
+ top: 41px;
+ bottom: 10px;
+ left: 10px;
+ right: 10px;
+ padding: 2px;
+ overflow: auto;
+} \ No newline at end of file
diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js
new file mode 100644
index 00000000..6ae085c2
--- /dev/null
+++ b/src/static/js/admin/plugins.js
@@ -0,0 +1,132 @@
+$(document).ready(function () {
+ var socket = io.connect().of("/pluginfw/installer");
+
+ $('.search-results').data('query', {
+ pattern: '',
+ offset: 0,
+ limit: 4,
+ });
+
+ var doUpdate = false;
+
+ var search = function () {
+ socket.emit("search", $('.search-results').data('query'));
+ }
+
+ function updateHandlers() {
+ $("#progress.dialog .close").unbind('click').click(function () {
+ $("#progress.dialog").hide();
+ });
+
+ $("#do-search").unbind('click').click(function () {
+ var query = $('.search-results').data('query');
+ query.pattern = $("#search-query")[0].value;
+ query.offset = 0;
+ search();
+ });
+
+ $(".do-install").unbind('click').click(function (e) {
+ var row = $(e.target).closest("tr");
+ doUpdate = true;
+ socket.emit("install", row.find(".name").html());
+ });
+
+ $(".do-uninstall").unbind('click').click(function (e) {
+ var row = $(e.target).closest("tr");
+ doUpdate = true;
+ socket.emit("uninstall", row.find(".name").html());
+ });
+
+ $(".do-prev-page").unbind('click').click(function (e) {
+ var query = $('.search-results').data('query');
+ query.offset -= query.limit;
+ if (query.offset < 0) {
+ query.offset = 0;
+ }
+ search();
+ });
+ $(".do-next-page").unbind('click').click(function (e) {
+ var query = $('.search-results').data('query');
+ var total = $('.search-results').data('total');
+ if (query.offset + query.limit < total) {
+ query.offset += query.limit;
+ }
+ search();
+ });
+ }
+
+ updateHandlers();
+
+ socket.on('progress', function (data) {
+ if (data.progress > 0 && $('#progress.dialog').data('progress') > data.progress) return;
+
+ $("#progress.dialog .close").hide();
+ $("#progress.dialog").show();
+
+ $('#progress.dialog').data('progress', data.progress);
+
+ var message = "Unknown status";
+ if (data.message) {
+ message = "<span class='status'>" + data.message.toString() + "</span>";
+ }
+ if (data.error) {
+ message = "<span class='error'>" + data.error.toString() + "<span>";
+ }
+ $("#progress.dialog .message").html(message);
+ $("#progress.dialog .history").append("<div>" + message + "</div>");
+
+ if (data.progress >= 1) {
+ if (data.error) {
+ $("#progress.dialog .close").show();
+ } else {
+ if (doUpdate) {
+ doUpdate = false;
+ socket.emit("load");
+ }
+ $("#progress.dialog").hide();
+ }
+ }
+ });
+
+ socket.on('search-result', function (data) {
+ var widget=$(".search-results");
+
+ widget.data('query', data.query);
+ widget.data('total', data.total);
+
+ widget.find('.offset').html(data.query.offset);
+ widget.find('.limit').html(data.query.offset + data.query.limit);
+ widget.find('.total').html(data.total);
+
+ widget.find(".results *").remove();
+ for (plugin_name in data.results) {
+ var plugin = data.results[plugin_name];
+ var row = widget.find(".template tr").clone();
+
+ for (attr in plugin) {
+ row.find("." + attr).html(plugin[attr]);
+ }
+ widget.find(".results").append(row);
+ }
+
+ updateHandlers();
+ });
+
+ socket.on('installed-results', function (data) {
+ $("#installed-plugins *").remove();
+ for (plugin_name in data.results) {
+ var plugin = data.results[plugin_name];
+ var row = $("#installed-plugin-template").clone();
+
+ for (attr in plugin.package) {
+ row.find("." + attr).html(plugin.package[attr]);
+ }
+ $("#installed-plugins").append(row);
+ }
+ updateHandlers();
+ });
+
+ socket.emit("load");
+ search();
+
+});
diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js
index 95d19505..dec32b46 100644
--- a/src/static/js/pad_editbar.js
+++ b/src/static/js/pad_editbar.js
@@ -182,7 +182,7 @@ var padeditbar = (function()
//hide all modules
if(moduleName == "none")
{
- $("#editbar ul#menu_right > li").removeClass("selected");
+ $(".toolbar ul.menu_right li").removeClass("selected");
for(var i=0;i<modules.length;i++)
{
//skip the userlist
@@ -200,11 +200,11 @@ var padeditbar = (function()
else
{
var nth_child = indexOf(modules, moduleName) + 1;
- if (nth_child > 0 && nth_child <= (modules.length-1)) {
- $("#editbar ul#menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected");
- $("#editbar ul#menu_right li:nth-child(" + nth_child + ")").toggleClass("selected");
- }
- if(modules[modules.length-1] === moduleName) $("#editbar ul#menu_right li").removeClass("selected");
+ if (nth_child > 0 && nth_child <= (modules.length-1)) {
+ $(".toolbar ul.menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected");
+ $(".toolbar ul.menu_right li:nth-child(" + nth_child + ")").toggleClass("selected");
+ }
+ if(modules[modules.length-1] === moduleName) $(".toolbar ul.menu_right li").removeClass("selected");
//hide all modules that are not selected and show the selected one
for(var i=0;i<modules.length;i++)
{
diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js
index c4cd5aeb..49e46c60 100644
--- a/src/static/js/pluginfw/hooks.js
+++ b/src/static/js/pluginfw/hooks.js
@@ -4,27 +4,63 @@ var _;
/* FIXME: Ugly hack, in the future, use same code for server & client */
if (plugins.isClient) {
var async = require("ep_etherpad-lite/static/js/pluginfw/async");
- _ = require("ep_etherpad-lite/static/js/underscore");
+ var _ = require("ep_etherpad-lite/static/js/underscore");
} else {
var async = require("async");
- _ = require("underscore");
+ var _ = require("underscore");
}
exports.bubbleExceptions = true
var hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; };
+
+ // Normalize output to list for both sync and async cases
+ var normalize = function(x) {
+ if (x == undefined) return [];
+ return x;
+ }
+ var normalizedhook = function () {
+ return normalize(hook.hook_fn(hook_name, args, function (x) {
+ return cb(normalize(x));
+ }));
+ }
+
if (exports.bubbleExceptions) {
- return hook.hook_fn(hook_name, args, cb);
+ return normalizedhook();
} else {
try {
- return hook.hook_fn(hook_name, args, cb);
+ return normalizedhook();
} catch (ex) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
}
}
}
+exports.syncMapFirst = function (lst, fn) {
+ var i;
+ var result;
+ for (i = 0; i < lst.length; i++) {
+ result = fn(lst[i])
+ if (result.length) return result;
+ }
+ return undefined;
+}
+
+exports.mapFirst = function (lst, fn, cb) {
+ var i = 0;
+
+ next = function () {
+ if (i >= lst.length) return cb(undefined);
+ fn(lst[i++], function (err, result) {
+ if (err) return cb(err);
+ if (result.length) return cb(null, result);
+ next();
+ });
+ }
+ next();
+}
+
/* Don't use Array.concat as it flatterns arrays within the array */
exports.flatten = function (lst) {
@@ -44,9 +80,9 @@ exports.flatten = function (lst) {
exports.callAll = function (hook_name, args) {
if (!args) args = {};
if (plugins.hooks[hook_name] === undefined) return [];
- return exports.flatten(_.map(plugins.hooks[hook_name], function (hook) {
+ return _.flatten(_.map(plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args);
- }));
+ }), true);
}
exports.aCallAll = function (hook_name, args, cb) {
@@ -59,7 +95,7 @@ exports.aCallAll = function (hook_name, args, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
function (err, res) {
- cb(null, exports.flatten(res));
+ cb(null, _.flatten(res, true));
}
);
}
@@ -67,14 +103,22 @@ exports.aCallAll = function (hook_name, args, cb) {
exports.callFirst = function (hook_name, args) {
if (!args) args = {};
if (plugins.hooks[hook_name][0] === undefined) return [];
- return exports.flatten(hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args));
+ return exports.syncMapFirst(plugins.hooks[hook_name], function (hook) {
+ return hookCallWrapper(hook, hook_name, args);
+ });
}
exports.aCallFirst = function (hook_name, args, cb) {
if (!args) args = {};
if (!cb) cb = function () {};
- if (plugins.hooks[hook_name][0] === undefined) return cb(null, []);
- hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(null, exports.flatten(res)); });
+ if (plugins.hooks[hook_name] === undefined) return cb(null, []);
+ exports.mapFirst(
+ plugins.hooks[hook_name],
+ function (hook, cb) {
+ hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
+ },
+ cb
+ );
}
exports.callAllStr = function(hook_name, args, sep, pre, post) {
diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js
index 127a95aa..1bb8db9e 100644
--- a/src/static/js/pluginfw/installer.js
+++ b/src/static/js/pluginfw/installer.js
@@ -55,19 +55,41 @@ exports.install = function(plugin_name, cb) {
);
};
-exports.search = function(pattern, cb) {
+exports.searchCache = null;
+
+exports.search = function(query, cache, cb) {
withNpm(
function (cb) {
- registry.get(
- "/-/all", null, 600, false, true,
+ var getData = function (cb) {
+ if (cache && exports.searchCache) {
+ cb(null, exports.searchCache);
+ } else {
+ registry.get(
+ "/-/all", null, 600, false, true,
+ function (er, data) {
+ if (er) return cb(er);
+ exports.searchCache = data;
+ cb(er, data);
+ }
+ );
+ }
+ }
+ getData(
function (er, data) {
if (er) return cb(er);
var res = {};
+ var i = 0;
for (key in data) {
- if (key.indexOf(plugins.prefix) == 0 && key.indexOf(pattern) != -1)
- res[key] = data[key];
+ if ( key.indexOf(plugins.prefix) == 0
+ && key.indexOf(query.pattern) != -1) {
+ i++;
+ if (i > query.offset
+ && i <= query.offset + query.limit) {
+ res[key] = data[key];
+ }
+ }
}
- cb(null, {results:res});
+ cb(null, {results:res, query: query, total:i});
}
);
},
diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html
index 6f38d048..6e9b4d2e 100644
--- a/src/templates/admin/plugins.html
+++ b/src/templates/admin/plugins.html
@@ -1,166 +1,87 @@
-<html>
- <head>
- <title>Plugin manager</title>
- <link href="../../static/css/admin.css" rel="stylesheet" type="text/css" />
- <script src="../../static/js/jquery.js"></script>
- <script src="../../socket.io/socket.io.js"></script>
- <script>
- $(document).ready(function () {
- var socket = io.connect().of("/pluginfw/installer");
-
- var doUpdate = false;
-
- function updateHandlers() {
- $("#progress.dialog .close").unbind('click').click(function () {
- $("#progress.dialog").hide();
- });
-
- $("#do-search").unbind('click').click(function () {
- if ($("#search-query")[0].value != "")
- socket.emit("search", $("#search-query")[0].value);
- });
-
- $(".do-install").unbind('click').click(function (e) {
- var row = $(e.target).closest("tr");
- doUpdate = true;
- socket.emit("install", row.find(".name").html());
- });
-
- $(".do-uninstall").unbind('click').click(function (e) {
- var row = $(e.target).closest("tr");
- doUpdate = true;
- socket.emit("uninstall", row.find(".name").html());
- });
- }
-
- updateHandlers();
-
- socket.on('progress', function (data) {
- $("#progress.dialog .close").hide();
- $("#progress.dialog").show();
- var message = "Unknown status";
- if (data.message) {
- message = "<span class='status'>" + data.message.toString() + "</span>";
- }
- if (data.error) {
- message = "<span class='error'>" + data.error.toString() + "<span>";
- }
- $("#progress.dialog .message").html(message);
- $("#progress.dialog .history").append("<div>" + message + "</div>");
-
- if (data.progress >= 1) {
- if (data.error) {
- $("#progress.dialog .close").show();
- } else {
- if (doUpdate) {
- doUpdate = false;
- socket.emit("load");
- }
- $("#progress.dialog").hide();
- }
- }
- });
-
- socket.on('search-result', function (data) {
- $("#search-results *").remove();
- for (plugin_name in data.results) {
- var plugin = data.results[plugin_name];
- var row = $("#search-result-template").clone();
-
- for (attr in plugin) {
- row.find("." + attr).html(plugin[attr]);
- }
- $("#search-results").append(row);
- }
- updateHandlers();
- });
-
- socket.on('installed-results', function (data) {
- $("#installed-plugins *").remove();
- for (plugin_name in data.results) {
- var plugin = data.results[plugin_name];
- var row = $("#installed-plugin-template").clone();
-
- for (attr in plugin.package) {
- row.find("." + attr).html(plugin.package[attr]);
- }
- $("#installed-plugins").append(row);
- }
- updateHandlers();
- });
-
- socket.emit("load");
-
- });
- </script>
- </head>
- <body>
- <div id="wrapper">
-
- <% if (errors.length) { %>
- <div class="errors">
- <% errors.forEach(function (item) { %>
- <div class="error"><%= item.toString() %></div>
- <% }) %>
- </div>
- <% } %>
-
-
- <h1>Installed plugins</h1>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Description</th>
- <td></td>
- </tr>
- </thead>
- <tbody class="template">
- <tr id="installed-plugin-template">
- <td class="name"></td>
- <td class="description"></td>
- <td class="actions">
- <input type="button" value="Uninstall" class="do-uninstall">
- </td>
- </tr>
- </tbody>
- <tbody id="installed-plugins">
- </tbody>
- </table>
-
- <h1>Search for plugins to install</h1>
- <form>
- <input type="text" name="search" value="" id="search-query">
- <input type="button" value="Search" id="do-search">
- </form>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Description</th>
- <td></td>
- </tr>
- </thead>
- <tbody class="template">
- <tr id="search-result-template">
- <td class="name"></td>
- <td class="description"></td>
- <td class="actions">
- <input type="button" value="Install" class="do-install">
- </td>
- </tr>
- </tbody>
- <tbody id="search-results">
- </tbody>
- </table>
-
- <div id="progress" class="dialog">
- <h1 class="title">
- Please wait: <span class="message"></span>
- <input type="button" class="close" value="Close">
- </h1>
- <div class="history"></div>
- </div>
- </div>
- </body>
-</html>
+<html>
+ <head>
+ <title>Plugin manager</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">
+ <script src="../../static/js/jquery.js"></script>
+ <script src="../../socket.io/socket.io.js"></script>
+ <script src="../../static/js/admin/plugins.js"></script>
+ </head>
+ <body>
+ <div id="wrapper">
+
+ <% if (errors.length) { %>
+ <div class="errors">
+ <% errors.forEach(function (item) { %>
+ <div class="error"><%= item.toString() %></div>
+ <% }) %>
+ </div>
+ <% } %>
+
+
+ <h1>Etherpad Lite</h1>
+ <div class="seperator"></div>
+ <h2>Installed plugins</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Description</th>
+ <td></td>
+ </tr>
+ </thead>
+ <tbody class="template">
+ <tr id="installed-plugin-template">
+ <td class="name"></td>
+ <td class="description"></td>
+ <td class="actions">
+ <input type="button" value="Uninstall" class="do-uninstall">
+ </td>
+ </tr>
+ </tbody>
+ <tbody id="installed-plugins">
+ </tbody>
+ </table>
+
+ <div class="paged listing search-results">
+ <div class="seperator"></div>
+ <h2>Search for plugins to install</h2>
+ <form>
+ <input type="text" name="search" placeholder="Search term" id="search-query">
+ <input type="button" value="Search" id="do-search">
+ </form>
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Description</th>
+ <td></td>
+ </tr>
+ </thead>
+ <tbody class="template">
+ <tr>
+ <td class="name"></td>
+ <td class="description"></td>
+ <td class="actions">
+ <input type="button" value="Install" class="do-install">
+ </td>
+ </tr>
+ </tbody>
+ <tbody class="results">
+ </tbody>
+ </table>
+ <input type="button" value="<<" class="do-prev-page">
+ <span class="offset"></span>..<span class="limit"></span> of <span class="total"></span>.
+ <input type="button" value=">>" class="do-next-page">
+ </div>
+
+ <div id="progress" class="dialog">
+ <h1 class="title">
+ Please wait: <span class="message"></span>
+ <input type="button" class="close" value="Close">
+ </h1>
+ <div class="history"></div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/src/templates/pad.html b/src/templates/pad.html
index 91583b36..6181b062 100644
--- a/src/templates/pad.html
+++ b/src/templates/pad.html
@@ -11,79 +11,77 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<% e.begin_block("styles"); %>
- <link href="../static/css/pad.css" rel="stylesheet">
- <link href="../static/custom/pad.css" rel="stylesheet">
- <style title="dynamicsyntax"></style>
+ <link href="../static/css/pad.css" rel="stylesheet">
+ <link href="../static/custom/pad.css" rel="stylesheet">
+ <style title="dynamicsyntax"></style>
<% e.end_block(); %>
<!-- head and body had been removed intentionally -->
<div id="editbar" class="toolbar">
<ul class="menu_left">
- <% e.begin_block("editbarMenuLeft"); %>
- <li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
- <a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
- </li>
- <li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
- <a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
- </li>
- <li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
- <a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
- </li>
- <li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
- <a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
- </li>
- <li class="separator"></li>
- <li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
- <a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
- </li>
- <li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
- <a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
- </li>
- <li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
- <a class="buttonicon buttonicon-indent" title="Indent"></a>
- </li>
- <li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
- <a class="buttonicon buttonicon-outdent" title="Unindent"></a>
- </li>
- <li class="separator"></li>
- <li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
- <a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
- </li>
- <li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
- <a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
- </li>
- <li class="separator"></li>
- <li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
- <a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
- </li>
- <% e.end_block(); %>
- </ul>
+ <% e.begin_block("editbarMenuLeft"); %>
+ <li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
+ <a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
+ </li>
+ <li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
+ <a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
+ </li>
+ <li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
+ <a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
+ </li>
+ <li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
+ <a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
+ </li>
+ <li class="separator"></li>
+ <li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
+ <a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
+ </li>
+ <li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
+ <a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
+ </li>
+ <li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
+ <a class="buttonicon buttonicon-indent" title="Indent"></a>
+ </li>
+ <li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
+ <a class="buttonicon buttonicon-outdent" title="Unindent"></a>
+ </li>
+ <li class="separator"></li>
+ <li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
+ <a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
+ </li>
+ <li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
+ <a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
+ </li>
+ <li class="separator"></li>
+ <li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
+ <a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
+ </li>
+ <% e.end_block(); %>
+ </ul>
<ul class="menu_right">
- <% e.begin_block("editbarMenuRight"); %>
- <li onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;">
- <a id="settingslink" title="Mark this revision as a saved revision">
- <div class="buttonicon buttonicon-savedRevision"></div>
- </a>
- </li>
- <li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
- <a class="buttonicon buttonicon-settings" id="settingslink" title="Settings of this pad"></a>
- </li>
- <li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
- <a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a>
- </li>
- <li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;" >
- <a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a>
- </li>
- <li class="separator"></li>
- <li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
- <a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
- </li>
- <li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
- <span class="buttonicon buttonicon-showusers" id="usericonback"></span>
- <span id="online_count">1</span>
- </li>
- <% e.end_block(); %>
+ <% e.begin_block("editbarMenuRight"); %>
+ <li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
+ <a class="buttonicon buttonicon-settings" title="Settings of this pad"></a>
+ </li>
+ <li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
+ <a class="buttonicon buttonicon-import_export" title="Import/Export from/to different document formats"></a>
+ </li>
+ <li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;">
+ <a class="buttonicon buttonicon-embed" title="Share and Embed this pad"></a>
+ </li>
+ <li id="revisionlink" onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;">
+ <a class="buttonicon buttonicon-savedRevision" title="Mark this revision as a saved revision"></a>
+ </li>
+ <li class="separator"></li>
+ <li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
+ <a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
+ </li>
+ <li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
+ <span class="buttonicon buttonicon-showusers" id="usericonback"></span>
+ <span id="online_count">1</span>
+ </li>
+ <% e.end_block(); %>
</ul>
</div>
@@ -95,17 +93,17 @@
<button id="mycolorpickersave">Save</button>
<button id="mycolorpickercancel">Cancel</button>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
- </div>
+ </div>
<div id="myswatchbox"><div id="myswatch"></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div>
- <div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
+ <div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
</div>
<div id="otherusers">
<div id="guestprompts"></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr><td></td></tr>
</table>
- <div id="nootherusers"></div>
+ <div id="nootherusers"></div>
</div>
<div id="userlistbuttonarea"></div>
</div>
@@ -150,20 +148,20 @@
<div class="column">
<h2>Import from text file, HTML, PDF, Word, ODT or RTF</h2><br>
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
- <div class="importformdiv" id="importformfilediv">
- <input type="file" name="file" size="15" id="importfileinput">
- <div class="importmessage" id="importmessagefail"></div>
- </div>
+ <div class="importformdiv" id="importformfilediv">
+ <input type="file" name="file" size="15" id="importfileinput">
+ <div class="importmessage" id="importmessagefail"></div>
+ </div>
<div id="import"></div>
- <div class="importmessage" id="importmessagesuccess">Successful!</div>
- <div class="importformdiv" id="importformsubmitdiv">
- <input type="hidden" name="padId" value="blpmaXT35R">
- <span class="nowrap">
- <input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
- <img alt="" id="importstatusball" src="../static/img/loading.gif" align="top">
- <img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
- </span>
- </div>
+ <div class="importmessage" id="importmessagesuccess">Successful!</div>
+ <div class="importformdiv" id="importformsubmitdiv">
+ <input type="hidden" name="padId" value="blpmaXT35R">
+ <span class="nowrap">
+ <input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
+ <img alt="" id="importstatusball" src="../static/img/loading.gif" align="top">
+ <img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
+ </span>
+ </div>
</form>
</div>
<div class="column">
@@ -223,79 +221,79 @@
<div id="modaloverlay">
<div id="modaloverlay-inner"></div>
</div>
-
+
<div id="mainmodals">
<% e.begin_block("modals"); %>
- <div id="connectionbox" class="modaldialog">
- <div id="connectionboxinner" class="modaldialog-inner">
- <div class="connecting">Connecting...</div>
- <div class="reconnecting">Reestablishing connection...</div>
- <div class="disconnected">
- <h2 class="h2_disconnect">Disconnected.</h2>
- <h2 class="h2_userdup">Opened in another window.</h2>
- <h2 class="h2_unauth">No Authorization.</h2>
- <div id="disconnected_looping">
- <p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
- </div>
- <div id="disconnected_initsocketfail">
- <p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
- </div>
- <div id="disconnected_userdup">
- <p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
- </div>
- <div id="disconnected_unknown">
- <p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
- </div>
- <div id="disconnected_slowcommit">
- <p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
- </div>
- <div id="disconnected_unauth">
- <p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
- </div>
- <div id="disconnected_deleted">
- <p>This pad was deleted.</p>
- </div>
- <div id="reconnect_advise">
- <p>If this continues to happen, please let us know</p>
- </div>
- <div id="reconnect_form">
- <button id="forcereconnect">Reconnect Now</button>
- </div>
+ <div 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>
- <form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
- <input type="hidden" class="padId" name="padId">
- <input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
- <input type="hidden" class="missedChanges" name="missedChanges">
- </form>
</div>
+ <form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
+ <input type="hidden" class="padId" name="padId">
+ <input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
+ <input type="hidden" class="missedChanges" name="missedChanges">
+ </form>
+ </div>
<% e.end_block(); %>
</div>
-
+
<% e.begin_block("scripts"); %>
- <script type="text/javascript" src="../static/js/require-kernel.js"></script>
- <script type="text/javascript" src="../static/js/jquery.js"></script>
- <script type="text/javascript" src="../socket.io/socket.io.js"></script>
- <script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
- <script type="text/javascript">
- document.domain = document.domain;
- var clientVars = {};
- (function () {
- require.setRootURI("../javascripts/src");
- require.setLibraryURI("../javascripts/lib");
- require.setGlobalKeyPath("require");
+ <script type="text/javascript" src="../static/js/require-kernel.js"></script>
+ <script type="text/javascript" src="../static/js/jquery.js"></script>
+ <script type="text/javascript" src="../socket.io/socket.io.js"></script>
+ <script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
+ <script type="text/javascript">
+ document.domain = document.domain;
+ var clientVars = {};
+ (function () {
+ require.setRootURI("../javascripts/src");
+ require.setLibraryURI("../javascripts/lib");
+ require.setGlobalKeyPath("require");
- var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
- plugins.update(function () {
- require('ep_etherpad-lite/static/js/pad').init();
- });
+ var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
+ plugins.update(function () {
+ require('ep_etherpad-lite/static/js/pad').init();
+ });
- /* TODO: These globals shouldn't exist. */
- pad = require('ep_etherpad-lite/static/js/pad').pad;
- chat = require('ep_etherpad-lite/static/js/chat').chat;
- padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;
- padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp;
- }());
- </script>
+ /* TODO: These globals shouldn't exist. */
+ pad = require('ep_etherpad-lite/static/js/pad').pad;
+ chat = require('ep_etherpad-lite/static/js/chat').chat;
+ padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;
+ padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp;
+ }());
+ </script>
<% e.end_block(); %>
</html>