summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatthias Bartelmeß <mba@fourplusone.de>2012-04-05 21:47:59 +0200
committerMatthias Bartelmeß <mba@fourplusone.de>2012-04-05 21:47:59 +0200
commit9e042ee942a4a3cce7fc89270833b72fd6bfb74a (patch)
treefe1115383b869cc73528465a55c60670bf5b343d /src
parent35c0a38a01e65981cb03bd67a717693ec7549b9d (diff)
parentbe5aec7e20e7abeb1da0df3f836974178e960667 (diff)
downloadetherpad-lite-9e042ee942a4a3cce7fc89270833b72fd6bfb74a.zip
Merge branch 'develop' into timeslider_authors
Conflicts: src/static/css/pad.css src/templates/timeslider.html
Diffstat (limited to 'src')
-rw-r--r--src/ep.json6
-rw-r--r--src/node/db/Pad.js101
-rw-r--r--src/node/db/PadManager.js8
-rw-r--r--src/node/eejs/examples/bar.ejs9
-rw-r--r--src/node/eejs/examples/foo.ejs7
-rw-r--r--src/node/eejs/index.js115
-rw-r--r--src/node/handler/PadMessageHandler.js22
-rw-r--r--src/node/handler/TimesliderMessageHandler.js7
-rw-r--r--src/node/hooks/express/adminplugins.js51
-rw-r--r--src/node/hooks/express/specialpages.js16
-rw-r--r--src/node/hooks/express/static.js17
-rw-r--r--src/node/hooks/express/webaccess.js29
-rw-r--r--src/node/server.js4
-rw-r--r--src/node/utils/Minify.js12
-rw-r--r--src/node/utils/Settings.js5
-rw-r--r--src/node/utils/caching_middleware.js6
-rw-r--r--src/node/utils/randomstring.js16
-rw-r--r--src/node/utils/tar.json1
-rw-r--r--src/package.json6
-rw-r--r--src/static/css/pad.css57
-rw-r--r--src/static/css/timeslider.css13
-rw-r--r--src/static/img/etherpad_lite_icons.pngbin5541 -> 8318 bytes
-rw-r--r--src/static/img/star.pngbin0 -> 3241 bytes
-rw-r--r--src/static/js/ace.js13
-rw-r--r--src/static/js/ace2_inner.js63
-rw-r--r--src/static/js/json2.js2
-rw-r--r--src/static/js/pad.js50
-rw-r--r--src/static/js/pad_docbar.js2
-rw-r--r--src/static/js/pad_editbar.js4
-rw-r--r--src/static/js/pad_editor.js4
-rw-r--r--src/static/js/pad_savedrevs.js518
-rw-r--r--src/static/js/pluginfw/hooks.js26
-rw-r--r--src/static/js/pluginfw/installer.js76
-rw-r--r--src/static/js/pluginfw/parent_require.js37
-rw-r--r--src/static/js/pluginfw/plugins.js87
-rw-r--r--src/static/js/pluginfw/read-installed.js324
-rw-r--r--src/static/js/prefixfree.js419
-rw-r--r--src/static/pad.html284
-rw-r--r--src/templates/admin/plugins.html217
-rw-r--r--src/templates/index.html (renamed from src/static/index.html)0
-rw-r--r--src/templates/pad.html307
-rw-r--r--src/templates/timeslider.html (renamed from src/static/timeslider.html)2
42 files changed, 1566 insertions, 1377 deletions
diff --git a/src/ep.json b/src/ep.json
index 59cbf3aa..6bc77735 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 c384f172..b4a39c17 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 4e3a3199..5f08b1b1 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 00000000..6a2cc4ba
--- /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 00000000..daee5f8e
--- /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 00000000..90c69e59
--- /dev/null
+++ b/src/node/eejs/index.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011 RedHog (Egil Möller) <egil.moller@freecode.no>
+ *
+ * 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('<!eejs!super!>');
+}
+
+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('<!eejs!super!>'))
+ exports.info.blocks[name].content = exports.info.blocks[name].content.replace('<!eejs!super!>', 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 c3ee231c..866edeb0 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -191,6 +191,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")
{
@@ -204,6 +209,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
* @param message the message from the client
diff --git a/src/node/handler/TimesliderMessageHandler.js b/src/node/handler/TimesliderMessageHandler.js
index da859779..a6cf8f4d 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 00000000..fa7e7077
--- /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 13cfd821..474f475e 100644
--- a/src/node/hooks/express/specialpages.js
+++ b/src/node/hooks/express/specialpages.js
@@ -1,32 +1,32 @@
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"));
});
//serve robots.txt
args.app.get('/robots.txt', function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/robots.txt");
- res.sendfile(filePath, { maxAge: exports.maxAge });
+ res.sendfile(filePath);
});
//serve favicon.ico
args.app.get('/favicon.ico', function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico");
- res.sendfile(filePath, { maxAge: exports.maxAge }, function(err)
+ res.sendfile(filePath, function(err)
{
//there is no custom favicon, send the default favicon
if(err)
{
filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
- res.sendfile(filePath, { maxAge: exports.maxAge });
+ res.sendfile(filePath);
}
});
});
@@ -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"));
});
//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"));
});
} \ No newline at end of file
diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js
index 9209967c..f284e478 100644
--- a/src/node/hooks/express/static.js
+++ b/src/node/hooks/express/static.js
@@ -6,6 +6,7 @@ var settings = require("../../utils/Settings");
var Yajsml = require('yajsml');
var fs = require("fs");
var ERR = require("async-stacktrace");
+var _ = require("underscore");
exports.expressCreateServer = function (hook_name, args, cb) {
// Cache both minified and static.
@@ -35,8 +36,22 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
+
+ var clientParts = _(plugins.parts)
+ .filter(function(part){ return _(part).has('client_hooks') });
+
+ var clientPlugins = {};
+
+ _(clientParts).chain()
+ .map(function(part){ return part.plugin })
+ .uniq()
+ .each(function(name){
+ clientPlugins[name] = _(plugins.plugins[name]).clone();
+ delete clientPlugins[name]['package'];
+ });
+
res.header("Content-Type","application/json; charset=utf-8");
- res.write(JSON.stringify({"plugins": plugins.plugins, "parts": plugins.parts}));
+ res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
res.end();
});
}
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js
index 8e9f967a..d0e28737 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/server.js b/src/node/server.js
index 8e6d6fa0..bc4333cd 100644
--- a/src/node/server.js
+++ b/src/node/server.js
@@ -51,10 +51,6 @@ console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
-//cache 6 hours, by default
-var hour = 60*60;
-exports.maxAge = settings.maxAge || 6 * hour;
-
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js
index f569d4b9..c5996565 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 !== undefined) {
+ 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);
}
}
@@ -132,7 +131,10 @@ exports.minify = function(req, res, next)
res.end();
} else if (req.method == 'GET') {
getFileCompressed(filename, contentType, function (error, content) {
- if(ERR(error)) return;
+ if(ERR(error, function(){
+ res.writeHead(500, {});
+ res.end();
+ })) return;
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.write(content);
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js
index 24237de4..12fcc55c 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 b8b7e1f1..70d5a08c 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 00000000..4c1bba24
--- /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 f4af3b18..14c93f5c 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 6253ecb1..83441da0 100644
--- a/src/package.json
+++ b/src/package.json
@@ -23,7 +23,11 @@
"log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2",
- "npm" : "1.1",
+ "npm" : "1.1",
+ "ejs" : "0.6.1",
+ "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 0131c719..4df79857 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,19 +35,22 @@ 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%;
white-space: nowrap;
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 32px;
}
-#editbar ul li
+.toolbar ul li
{
background: #fff;
background: linear-gradient(#fff, #f0f0f0);
@@ -62,52 +65,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 +323,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
z-index: 10;
}
-#editbarsavetable
+.toolbarsavetable
{
position:absolute;
top: 6px;
@@ -328,7 +331,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
height: 24px;
}
-#editbarsavetable td, #editbartable td
+.toolbarsavetable td, .toolbartable td
{
white-space: nowrap;
}
@@ -686,14 +689,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;
}
@@ -1032,6 +1036,9 @@ margin-top: 1px;
background-position: 0px -183px;
display: inline-block;
}
+.buttonicon-savedRevision {
+ background-position: 0px -493px
+}
#usericon
{
@@ -1171,13 +1178,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 {
@@ -1192,7 +1199,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%;
@@ -1202,7 +1209,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;
@@ -1224,7 +1231,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 {
@@ -1233,10 +1240,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;
@@ -1265,4 +1272,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 2179c940..03e97048 100644
--- a/src/static/css/timeslider.css
+++ b/src/static/css/timeslider.css
@@ -45,10 +45,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);
@@ -75,8 +75,11 @@
#padmain {top:0px !important;}
#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;
+}
.timeslider-bar
diff --git a/src/static/img/etherpad_lite_icons.png b/src/static/img/etherpad_lite_icons.png
index cadf5ed2..27867d42 100644
--- a/src/static/img/etherpad_lite_icons.png
+++ b/src/static/img/etherpad_lite_icons.png
Binary files differ
diff --git a/src/static/img/star.png b/src/static/img/star.png
new file mode 100644
index 00000000..e0c7099e
--- /dev/null
+++ b/src/static/img/star.png
Binary files differ
diff --git a/src/static/js/ace.js b/src/static/js/ace.js
index 685d45df..4dfcc64e 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("<html><head>");
+ iframeHTML.push('<script type="text/javascript" src="../static/js/jquery.js"></script>');
+
+ 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('\
<script type="text/javascript">\
+ parent_req = require("./pluginfw/parent_require.js");\
+ parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/hooks");\
+ parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/plugins");\
+ parent_req.getRequirementFromParent(require, "./pluginfw/hooks");\
+ parent_req.getRequirementFromParent(require, "./pluginfw/plugins");\
require.define("/plugins", null);\n\
require.define("/plugins.js", function (require, exports, module) {\
module.exports = require("ep_etherpad-lite/static/js/plugins");\
diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js
index a2c4afeb..f8393d0b 100644
--- a/src/static/js/ace2_inner.js
+++ b/src/static/js/ace2_inner.js
@@ -844,7 +844,7 @@ function Ace2Inner(){
var cmdArgs = Array.prototype.slice.call(arguments, 1);
if (CMDS[cmd])
{
- inCallStack(cmd, function()
+ inCallStackIfNecessary(cmd, function()
{
fastIncorp(9);
CMDS[cmd].apply(CMDS, cmdArgs);
@@ -854,7 +854,7 @@ function Ace2Inner(){
function replaceRange(start, end, text)
{
- inCallStack('replaceRange', function()
+ inCallStackIfNecessary('replaceRange', function()
{
fastIncorp(9);
performDocumentReplaceRange(start, end, text);
@@ -1155,7 +1155,7 @@ function Ace2Inner(){
return;
}
- inCallStack("idleWorkTimer", function()
+ inCallStackIfNecessary("idleWorkTimer", function()
{
var isTimeUp = newTimeLimit(250);
@@ -2043,6 +2043,7 @@ function Ace2Inner(){
return [lineNum, col];
}
}
+ editorInfo.ace_getLineAndCharForPoint = getLineAndCharForPoint;
function createDomLineEntry(lineString)
{
@@ -2328,6 +2329,7 @@ function Ace2Inner(){
var cs = builder.toString();
performDocumentApplyChangeset(cs);
}
+ editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange;
function buildKeepToStartOfRange(builder, start)
{
@@ -2853,6 +2855,7 @@ function Ace2Inner(){
currentCallStack.selectionAffected = true;
}
}
+ editorInfo.ace_performSelectionChange = performSelectionChange;
// Change the abstract representation of the document to have a different selection.
// Should not rely on the line representation. Should not affect the DOM.
@@ -3280,7 +3283,7 @@ function Ace2Inner(){
function handleClick(evt)
{
- inCallStack("handleClick", function()
+ inCallStackIfNecessary("handleClick", function()
{
idleWorkTimer.atMost(200);
});
@@ -3602,7 +3605,7 @@ function Ace2Inner(){
var stopped = false;
- inCallStack("handleKeyEvent", function()
+ inCallStackIfNecessary("handleKeyEvent", function()
{
if (type == "keypress" || (isTypeForSpecialKey && keyCode == 13 /*return*/ ))
@@ -4689,7 +4692,7 @@ function Ace2Inner(){
}
// click below the body
- inCallStack("handleOuterClick", function()
+ inCallStackIfNecessary("handleOuterClick", function()
{
// put caret at bottom of doc
fastIncorp(11);
@@ -4726,6 +4729,54 @@ function Ace2Inner(){
else $(elem).removeClass(elem, className);
}
+ function setup()
+ {
+ doc = document; // defined as a var in scope outside
+ inCallStackIfNecessary("setup", function()
+ {
+ var body = doc.getElementById("innerdocbody");
+ root = body; // defined as a var in scope outside
+ if (browser.mozilla) addClass(root, "mozilla");
+ if (browser.safari) addClass(root, "safari");
+ if (browser.msie) addClass(root, "msie");
+ if (browser.msie)
+ {
+ // cache CSS background images
+ try
+ {
+ doc.execCommand("BackgroundImageCache", false, true);
+ }
+ catch (e)
+ { /* throws an error in some IE 6 but not others! */
+ }
+ }
+ setClassPresence(root, "authorColors", true);
+ setClassPresence(root, "doesWrap", doesWrap);
+
+ initDynamicCSS();
+
+ enforceEditability();
+
+ // set up dom and rep
+ while (root.firstChild) root.removeChild(root.firstChild);
+ var oneEntry = createDomLineEntry("");
+ doRepLineSplice(0, rep.lines.length(), [oneEntry]);
+ insertDomLines(null, [oneEntry.domInfo], null);
+ rep.alines = Changeset.splitAttributionLines(
+ Changeset.makeAttribution("\n"), "\n");
+
+ bindTheEventHandlers();
+
+ });
+
+ scheduler.setTimeout(function()
+ {
+ parent.readyFunc(); // defined in code that sets up the inner iframe
+ }, 0);
+
+ isSetUp = true;
+ }
+
function focus()
{
window.focus();
diff --git a/src/static/js/json2.js b/src/static/js/json2.js
index 663f932c..d72b4be9 100644
--- a/src/static/js/json2.js
+++ b/src/static/js/json2.js
@@ -465,7 +465,7 @@ if (!JSON)
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
- throw new SyntaxError('JSON.parse');
+ throw new SyntaxError('JSON.parse: ' + text);
};
}
}());
diff --git a/src/static/js/pad.js b/src/static/js/pad.js
index d19cface..426eb089 100644
--- a/src/static/js/pad.js
+++ b/src/static/js/pad.js
@@ -31,7 +31,6 @@ require('./farbtastic');
require('./excanvas');
JSON = require('./json2');
require('./undo-xpopup');
-require('./prefixfree');
var chat = require('./chat').chat;
var getCollabClient = require('./collab_client').getCollabClient;
@@ -42,7 +41,7 @@ var padeditbar = require('./pad_editbar').padeditbar;
var padeditor = require('./pad_editor').padeditor;
var padimpexp = require('./pad_impexp').padimpexp;
var padmodals = require('./pad_modals').padmodals;
-var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
+var padsavedrevs = require('./pad_savedrevs');
var paduserlist = require('./pad_userlist').paduserlist;
var padutils = require('./pad_utils').padutils;
@@ -50,6 +49,50 @@ var createCookie = require('./pad_utils').createCookie;
var readCookie = require('./pad_utils').readCookie;
var randomString = require('./pad_utils').randomString;
+var hooks = require('./pluginfw/hooks');
+
+function createCookie(name, value, days, path)
+{
+ if (days)
+ {
+ var date = new Date();
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ var expires = "; expires=" + date.toGMTString();
+ }
+ else var expires = "";
+
+ if(!path)
+ path = "/";
+
+ document.cookie = name + "=" + value + expires + "; path=" + path;
+}
+
+function readCookie(name)
+{
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++)
+ {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1, c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
+ }
+ return null;
+}
+
+function randomString()
+{
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ var string_length = 20;
+ var randomstring = '';
+ for (var i = 0; i < string_length; i++)
+ {
+ var rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars.substring(rnum, rnum + 1);
+ }
+ return "t." + randomstring;
+}
+
function getParams()
{
var params = getUrlVars()
@@ -457,7 +500,7 @@ var pad = {
guestPolicy: pad.padOptions.guestPolicy
}, this);
padimpexp.init(this);
- padsavedrevs.init(clientVars.initialRevisionList, this);
+ padsavedrevs.init(this);
padeditor.init(postAceInit, pad.padOptions.view || {}, this);
@@ -491,6 +534,7 @@ var pad = {
if(padcookie.getPref("showAuthorshipColors") == false){
pad.changeViewOption('showAuthorColors', false);
}
+ hooks.aCallAll("postAceInit");
}
},
dispose: function()
diff --git a/src/static/js/pad_docbar.js b/src/static/js/pad_docbar.js
index 08bbb0c4..c5858126 100644
--- a/src/static/js/pad_docbar.js
+++ b/src/static/js/pad_docbar.js
@@ -449,7 +449,7 @@ var paddocbar = (function()
handleResizePage: function()
{
// Side-step circular reference. This should be injected.
- var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
+ var padsavedrevs = require('./pad_savedrevs');
padsavedrevs.handleResizePage();
},
hideLaterIfNoOtherInteraction: function()
diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js
index 0cfd1f20..7e8cf96e 100644
--- a/src/static/js/pad_editbar.js
+++ b/src/static/js/pad_editbar.js
@@ -22,7 +22,7 @@
var padutils = require('./pad_utils').padutils;
var padeditor = require('./pad_editor').padeditor;
-var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
+var padsavedrevs = require('./pad_savedrevs');
function indexOf(array, value) {
for (var i = 0, ii = array.length; i < ii; i++) {
@@ -131,7 +131,7 @@ var padeditbar = (function()
{
self.toogleDropDown("importexport");
}
- else if (cmd == 'save')
+ else if (cmd == 'savedRevision')
{
padsavedrevs.saveNow();
}
diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js
index 12f83aeb..b8a4ea0e 100644
--- a/src/static/js/pad_editor.js
+++ b/src/static/js/pad_editor.js
@@ -87,8 +87,6 @@ var padeditor = (function()
return defaultValue;
}
- self.ace.setProperty("showsauthorcolors", !settings.noColors);
-
self.ace.setProperty("rtlIsTrue", settings.rtlIsTrue);
var v;
@@ -100,6 +98,8 @@ var padeditor = (function()
v = getOption('showAuthorColors', true);
self.ace.setProperty("showsauthorcolors", v);
padutils.setCheckbox($("#options-colorscheck"), v);
+ // Override from parameters
+ self.ace.setProperty("showsauthorcolors", !settings.noColors);
v = getOption('useMonospaceFont', false);
self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));
diff --git a/src/static/js/pad_savedrevs.js b/src/static/js/pad_savedrevs.js
index 2a0f4fde..bf4f941c 100644
--- a/src/static/js/pad_savedrevs.js
+++ b/src/static/js/pad_savedrevs.js
@@ -1,11 +1,5 @@
/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-/**
- * Copyright 2009 Google Inc.
+ * Copyright 2012 Peter 'Pita' Martischka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,507 +14,13 @@
* limitations under the License.
*/
-var padutils = require('./pad_utils').padutils;
-var paddocbar = require('./pad_docbar').paddocbar;
-
-var padsavedrevs = (function()
-{
-
- function reversedCopy(L)
- {
- var L2 = L.slice();
- L2.reverse();
- return L2;
- }
-
- function makeRevisionBox(revisionInfo, rnum)
- {
- var box = $('<div class="srouterbox">' + '<div class="srinnerbox">' + '<a href="javascript:void(0)" class="srname"><!-- --></a>' + '<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>' + '<div class="srtime"><!-- --></div>' + '<div class="srauthor"><!-- --></div>' + '<img class="srtwirly" src="static/img/misc/status-ball.gif">' + '</div></div>');
- setBoxLabel(box, revisionInfo.label);
- setBoxTimestamp(box, revisionInfo.timestamp);
- box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy));
- var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id;
- box.find(".srview").attr('href', viewLink);
- var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');';
- box.find(".srrestore").attr('href', restoreLink);
- box.find(".srname").click(function(evt)
- {
- editRevisionLabel(rnum, box);
- });
- return box;
- }
-
- function setBoxLabel(box, label)
- {
- box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
- }
-
- function setBoxTimestamp(box, timestamp)
- {
- box.find(".srtime").html(padutils.escapeHtml(
- padutils.timediff(new Date(timestamp))));
- }
-
- function getNthBox(n)
- {
- return $("#savedrevisions .srouterbox").eq(n);
- }
-
- function editRevisionLabel(rnum, box)
- {
- var input = $('<input type="text" class="srnameedit"/>');
- box.find(".srnameedit").remove(); // just in case
- var label = box.find(".srname");
- input.width(label.width());
- input.height(label.height());
- input.css('top', label.position().top);
- input.css('left', label.position().left);
- label.after(input);
- label.css('opacity', 0);
-
- function endEdit()
- {
- input.remove();
- label.css('opacity', 1);
- }
- var rev = currentRevisionList[rnum];
- var oldLabel = rev.label;
- input.blur(function()
- {
- var newLabel = input.val();
- if (newLabel && newLabel != oldLabel)
- {
- relabelRevision(rnum, newLabel);
- }
- endEdit();
- });
- input.val(rev.label).focus().select();
- padutils.bindEnterAndEscape(input, function onEnter()
- {
- input.blur();
- }, function onEscape()
- {
- input.val('').blur();
- });
- }
-
- function relabelRevision(rnum, newLabel)
- {
- var rev = currentRevisionList[rnum];
- $.ajax(
- {
- type: 'post',
- url: '/ep/pad/saverevisionlabel',
- data: {
- userId: pad.getUserId(),
- padId: pad.getPadId(),
- revId: rev.id,
- newLabel: newLabel
- },
- success: success,
- error: error
- });
-
- function success(text)
- {
- var newRevisionList = JSON.parse(text);
- self.newRevisionList(newRevisionList);
- pad.sendClientMessage(
- {
- type: 'revisionLabel',
- revisionList: reversedCopy(currentRevisionList),
- savedBy: pad.getUserName(),
- newLabel: newLabel
- });
- }
-
- function error(e)
- {
- alert("Oops! There was an error saving that revision label. Please try again later.");
- }
- }
-
- var currentRevisionList = [];
-
- function setRevisionList(newRevisionList, noAnimation)
- {
- // deals with changed labels and new added revisions
- for (var i = 0; i < currentRevisionList.length; i++)
- {
- var a = currentRevisionList[i];
- var b = newRevisionList[i];
- if (b.label != a.label)
- {
- setBoxLabel(getNthBox(i), b.label);
- }
- }
- for (var j = currentRevisionList.length; j < newRevisionList.length; j++)
- {
- var newBox = makeRevisionBox(newRevisionList[j], j);
- $("#savedrevs-scrollinner").append(newBox);
- newBox.css('left', j * REVISION_BOX_WIDTH);
- }
- var newOnes = (newRevisionList.length > currentRevisionList.length);
- currentRevisionList = newRevisionList;
- if (newOnes)
- {
- setDesiredScroll(getMaxScroll());
- if (noAnimation)
- {
- setScroll(desiredScroll);
- }
-
- if (!noAnimation)
- {
- var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label;
- displaySavedTip(nameOfLast);
- }
- }
- }
-
- function refreshRevisionList()
- {
- for (var i = 0; i < currentRevisionList.length; i++)
- {
- var r = currentRevisionList[i];
- var box = getNthBox(i);
- setBoxTimestamp(box, r.timestamp);
- }
- }
-
- var savedTipAnimator = padutils.makeShowHideAnimator(function(state)
- {
- if (state == -1)
- {
- $("#revision-notifier").css('opacity', 0).css('display', 'block');
- }
- else if (state == 0)
- {
- $("#revision-notifier").css('opacity', 1);
- }
- else if (state == 1)
- {
- $("#revision-notifier").css('opacity', 0).css('display', 'none');
- }
- else if (state < 0)
- {
- $("#revision-notifier").css('opacity', 1);
- }
- else if (state > 0)
- {
- $("#revision-notifier").css('opacity', 1 - state);
- }
- }, false, 25, 300);
-
- function displaySavedTip(text)
- {
- $("#revision-notifier .name").html(padutils.escapeHtml(text));
- savedTipAnimator.show();
- padutils.cancelActions("hide-revision-notifier");
- var hideLater = padutils.getCancellableAction("hide-revision-notifier", function()
- {
- savedTipAnimator.hide();
- });
- window.setTimeout(hideLater, 3000);
- }
-
- var REVISION_BOX_WIDTH = 120;
- var curScroll = 0; // distance between left of revisions and right of view
- var desiredScroll = 0;
-
- function getScrollWidth()
- {
- return REVISION_BOX_WIDTH * currentRevisionList.length;
- }
-
- function getViewportWidth()
- {
- return $("#savedrevs-scrollouter").width();
- }
-
- function getMinScroll()
- {
- return Math.min(getViewportWidth(), getScrollWidth());
- }
-
- function getMaxScroll()
- {
- return getScrollWidth();
- }
-
- function setScroll(newScroll)
- {
- curScroll = newScroll;
- $("#savedrevs-scrollinner").css('right', newScroll);
- updateScrollArrows();
- }
-
- function setDesiredScroll(newDesiredScroll, dontUpdate)
- {
- desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), newDesiredScroll));
- if (!dontUpdate)
- {
- updateScroll();
- }
- }
-
- function updateScroll()
- {
- updateScrollArrows();
- scrollAnimator.scheduleAnimation();
- }
-
- function updateScrollArrows()
- {
- $("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll());
- $("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll());
- }
- var scrollAnimator = padutils.makeAnimationScheduler(function()
- {
- setDesiredScroll(desiredScroll, true); // re-clamp
- if (Math.abs(desiredScroll - curScroll) < 1)
- {
- setScroll(desiredScroll);
- return false;
- }
- else
- {
- setScroll(curScroll + (desiredScroll - curScroll) * 0.5);
- return true;
- }
- }, 50, 2);
-
- var isSaving = false;
-
- function setIsSaving(v)
- {
- isSaving = v;
- rerenderButton();
- }
-
- function haveReachedRevLimit()
- {
- var mv = pad.getPrivilege('maxRevisions');
- return (!(mv < 0 || mv > currentRevisionList.length));
- }
-
- function rerenderButton()
- {
- if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit())
- {
- $("#savedrevs-savenow").css('opacity', 0.75);
- }
- else
- {
- $("#savedrevs-savenow").css('opacity', 1);
- }
- }
-
- var scrollRepeatTimer = null;
- var scrollStartTime = 0;
-
- function setScrollRepeatTimer(dir)
- {
- clearScrollRepeatTimer();
- scrollStartTime = +new Date;
- scrollRepeatTimer = window.setTimeout(function f()
- {
- if (!scrollRepeatTimer)
- {
- return;
- }
- self.scroll(dir);
- var scrollTime = (+new Date) - scrollStartTime;
- var delay = (scrollTime > 2000 ? 50 : 300);
- scrollRepeatTimer = window.setTimeout(f, delay);
- }, 300);
- $(document).bind('mouseup', clearScrollRepeatTimer);
- }
-
- function clearScrollRepeatTimer()
- {
- if (scrollRepeatTimer)
- {
- window.clearTimeout(scrollRepeatTimer);
- scrollRepeatTimer = null;
- }
- $(document).unbind('mouseup', clearScrollRepeatTimer);
- }
-
- var pad = undefined;
- var self = {
- init: function(initialRevisions, _pad)
- {
- pad = _pad;
- self.newRevisionList(initialRevisions, true);
-
- $("#savedrevs-savenow").click(function()
- {
- self.saveNow();
- });
- $("#savedrevs-scrollleft").mousedown(function()
- {
- self.scroll('left');
- setScrollRepeatTimer('left');
- });
- $("#savedrevs-scrollright").mousedown(function()
- {
- self.scroll('right');
- setScrollRepeatTimer('right');
- });
- $("#savedrevs-close").click(function()
- {
- paddocbar.setShownPanel(null);
- });
-
- // update "saved n minutes ago" times
- window.setInterval(function()
- {
- refreshRevisionList();
- }, 60 * 1000);
- },
- restoreRevision: function(rnum)
- {
- var rev = currentRevisionList[rnum];
- var warning = ("Restoring this revision will overwrite the current" + " text of the pad. " + "Are you sure you want to continue?");
- var hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
- var box = getNthBox(rnum);
- if (confirm(warning))
- {
- box.find(".srtwirly").show();
- $.ajax(
- {
- type: 'get',
- url: '/ep/pad/getrevisionatext',
- data: {
- padId: pad.getPadId(),
- revId: rev.id
- },
- success: success,
- error: error
- });
- }
-
- function success(resultJson)
- {
- untwirl();
- var result = JSON.parse(resultJson);
- padeditor.restoreRevisionText(result);
- window.setTimeout(function()
- {
- hidePanel();
- }, 0);
- }
-
- function error(e)
- {
- untwirl();
- alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId());
- }
-
- function untwirl()
- {
- box.find(".srtwirly").hide();
- }
- },
- showReachedLimit: function()
- {
- alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions.");
- },
- newRevisionList: function(lst, noAnimation)
- {
- // server gives us list with newest first;
- // we want chronological order
- var L = reversedCopy(lst);
- setRevisionList(L, noAnimation);
- rerenderButton();
- },
- saveNow: function()
- {
- if (isSaving)
- {
- return;
- }
- if (!pad.isFullyConnected())
- {
- return;
- }
- if (haveReachedRevLimit())
- {
- self.showReachedLimit();
- return;
- }
- setIsSaving(true);
- var savedBy = pad.getUserName() || "unnamed";
- pad.callWhenNotCommitting(submitSave);
-
- function submitSave()
- {
- $.ajax(
- {
- type: 'post',
- url: '/ep/pad/saverevision',
- data: {
- padId: pad.getPadId(),
- savedBy: savedBy,
- savedById: pad.getUserId(),
- revNum: pad.getCollabRevisionNumber()
- },
- success: success,
- error: error
- });
- }
-
- function success(text)
- {
- setIsSaving(false);
- var newRevisionList = JSON.parse(text);
- self.newRevisionList(newRevisionList);
- pad.sendClientMessage(
- {
- type: 'newRevisionList',
- revisionList: newRevisionList,
- savedBy: savedBy
- });
- }
+var pad;
- function error(e)
- {
- setIsSaving(false);
- alert("Oops! The server failed to save the revision. Please try again later.");
- }
- },
- handleResizePage: function()
- {
- updateScrollArrows();
- },
- handleIsFullyConnected: function(isConnected)
- {
- rerenderButton();
- },
- scroll: function(dir)
- {
- var minScroll = getMinScroll();
- var maxScroll = getMaxScroll();
- if (dir == 'left')
- {
- if (desiredScroll > minScroll)
- {
- var n = Math.floor((desiredScroll - 1 - minScroll) / REVISION_BOX_WIDTH);
- setDesiredScroll(Math.max(0, n) * REVISION_BOX_WIDTH + minScroll);
- }
- }
- else if (dir == 'right')
- {
- if (desiredScroll < maxScroll)
- {
- var n = Math.floor((maxScroll - desiredScroll - 1) / REVISION_BOX_WIDTH);
- setDesiredScroll(maxScroll - Math.max(0, n) * REVISION_BOX_WIDTH);
- }
- }
- }
- };
- return self;
-}());
+exports.saveNow = function(){
+ pad.collabClient.sendMessage({"type": "SAVE_REVISION"});
+ alert("This revision is now marked as a saved revision");
+}
-exports.padsavedrevs = padsavedrevs;
+exports.init = function(_pad){
+ pad = _pad;
+}
diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js
index 9c04023f..c4cd5aeb 100644
--- a/src/static/js/pluginfw/hooks.js
+++ b/src/static/js/pluginfw/hooks.js
@@ -10,12 +10,18 @@ if (plugins.isClient) {
_ = require("underscore");
}
+exports.bubbleExceptions = true
+
var hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; };
- try {
+ if (exports.bubbleExceptions) {
return hook.hook_fn(hook_name, args, cb);
- } catch (ex) {
- console.error([hook_name, hook.part.full_name, ex.stack || ex]);
+ } else {
+ try {
+ return hook.hook_fn(hook_name, args, cb);
+ } catch (ex) {
+ console.error([hook_name, hook.part.full_name, ex.stack || ex]);
+ }
}
}
@@ -36,6 +42,7 @@ 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 hookCallWrapper(hook, hook_name, args);
@@ -43,26 +50,31 @@ exports.callAll = function (hook_name, args) {
}
exports.aCallAll = function (hook_name, args, cb) {
- if (plugins.hooks[hook_name] === undefined) cb([]);
+ if (!args) args = {};
+ if (!cb) cb = function () {};
+ if (plugins.hooks[hook_name] === undefined) return cb(null, []);
async.map(
plugins.hooks[hook_name],
function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
function (err, res) {
- cb(exports.flatten(res));
+ cb(null, exports.flatten(res));
}
);
}
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));
}
exports.aCallFirst = function (hook_name, args, cb) {
- if (plugins.hooks[hook_name][0] === undefined) cb([]);
- hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(exports.flatten(res)); });
+ 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)); });
}
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
new file mode 100644
index 00000000..127a95aa
--- /dev/null
+++ b/src/static/js/pluginfw/installer.js
@@ -0,0 +1,76 @@
+var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
+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) {
+ npm.load({}, function (er) {
+ if (er) return cb({progress:1, error:er});
+ npm.on("log", function (message) {
+ cb({progress: 0.5, message:message.msg + ": " + message.pref});
+ });
+ npmfn(function (er, data) {
+ if (er) return cb({progress:1, error:er.code + ": " + er.path});
+ if (!data) data = {};
+ data.progress = 1;
+ data.message = "Done.";
+ cb(data);
+ });
+ });
+}
+
+// All these functions call their callback multiple times with
+// {progress:[0,1], message:STRING, error:object}. They will call it
+// with progress = 1 at least once, and at all times will either
+// message or error be present, not both. It can be called multiple
+// times for all values of propgress except for 1.
+
+exports.uninstall = function(plugin_name, cb) {
+ withNpm(
+ function (cb) {
+ npm.commands.uninstall([plugin_name], function (er) {
+ if (er) return cb(er);
+ hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
+ if (er) return cb(er);
+ plugins.update(cb);
+ });
+ });
+ },
+ cb
+ );
+};
+
+exports.install = function(plugin_name, cb) {
+ withNpm(
+ function (cb) {
+ npm.commands.install([plugin_name], function (er) {
+ if (er) return cb(er);
+ hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
+ if (er) return cb(er);
+ plugins.update(cb);
+ });
+ });
+ },
+ cb
+ );
+};
+
+exports.search = function(pattern, cb) {
+ withNpm(
+ function (cb) {
+ registry.get(
+ "/-/all", null, 600, false, true,
+ function (er, data) {
+ if (er) return cb(er);
+ var res = {};
+ for (key in data) {
+ if (key.indexOf(plugins.prefix) == 0 && key.indexOf(pattern) != -1)
+ res[key] = data[key];
+ }
+ cb(null, {results:res});
+ }
+ );
+ },
+ cb
+ );
+};
diff --git a/src/static/js/pluginfw/parent_require.js b/src/static/js/pluginfw/parent_require.js
new file mode 100644
index 00000000..d7f6190d
--- /dev/null
+++ b/src/static/js/pluginfw/parent_require.js
@@ -0,0 +1,37 @@
+/**
+ * This module allows passing require modules instances to
+ * embedded iframes in a page.
+ * For example, if a page has the "plugins" module initialized,
+ * it is important to use exactly the same "plugins" instance
+ * inside iframes as well. Otherwise, plugins cannot save any
+ * state.
+ */
+
+
+/**
+ * Instructs the require object that when a reqModuleName module
+ * needs to be loaded, that it iterates through the parents of the
+ * current window until it finds one who can execute "require"
+ * statements and asks it to perform require on reqModuleName.
+ *
+ * @params requireDefObj Require object which supports define
+ * statements. This object is accessible after loading require-kernel.
+ * @params reqModuleName Module name e.g. (ep_etherpad-lite/static/js/plugins)
+ */
+exports.getRequirementFromParent = function(requireDefObj, reqModuleName) {
+ requireDefObj.define(reqModuleName, function(require, exports, module) {
+ var t = parent;
+ var max = 0; // make sure I don't go up more than 10 times
+ while (typeof(t) != "undefined") {
+ max++;
+ if (max==10)
+ break;
+ if (typeof(t.require) != "undefined") {
+ module.exports = t.require(reqModuleName);
+ return;
+ }
+ t = t.parent;
+ }
+ });
+
+}
diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js
index aa2dfafb..db0b92e4 100644
--- a/src/static/js/pluginfw/plugins.js
+++ b/src/static/js/pluginfw/plugins.js
@@ -4,7 +4,7 @@ var _;
if (!exports.isClient) {
var npm = require("npm/lib/npm.js");
- var readInstalled = require("npm/lib/utils/read-installed.js");
+ var readInstalled = require("./read-installed.js");
var relativize = require("npm/lib/utils/relativize.js");
var readJson = require("npm/lib/utils/read-json.js");
var path = require("path");
@@ -14,10 +14,9 @@ if (!exports.isClient) {
var util = require("util");
_ = require("underscore");
}else{
- var $, jQuery
+ var $, jQuery;
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
_ = require("ep_etherpad-lite/static/js/underscore");
-
}
exports.prefix = 'ep_';
@@ -31,15 +30,15 @@ exports.ensure = function (cb) {
exports.update(cb);
else
cb();
-}
+};
exports.formatPlugins = function () {
return _.keys(exports.plugins).join(", ");
-}
+};
exports.formatParts = function () {
return _.map(exports.parts, function (part) { return part.full_name; }).join("\n");
-}
+};
exports.formatHooks = function () {
var res = [];
@@ -49,33 +48,39 @@ exports.formatHooks = function () {
});
});
return res.join("\n");
-}
+};
-exports.loadFn = function (path) {
+exports.loadFn = function (path, hookName) {
var x = path.split(":");
var fn = require(x[0]);
- _.each(x[1].split("."), function (name) {
+ var functionName = x[1] ? x[1] : hookName;
+
+ _.each(functionName.split("."), function (name) {
fn = fn[name];
});
return fn;
-}
+};
exports.extractHooks = function (parts, hook_set_name) {
var hooks = {};
_.each(parts,function (part) {
- _.chain(part[hook_set_name] || {}).keys().each(function (hook_name) {
+ _.chain(part[hook_set_name] || {})
+ .keys()
+ .each(function (hook_name) {
if (hooks[hook_name] === undefined) hooks[hook_name] = [];
+
+
var hook_fn_name = part[hook_set_name][hook_name];
- var hook_fn = exports.loadFn(part[hook_set_name][hook_name]);
+ var hook_fn = exports.loadFn(hook_fn_name, hook_name);
if (hook_fn) {
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part});
} else {
- console.error("Unable to load hook function for " + part.full_name + " for hook " + hook_name + ": " + part.hooks[hook_name]);
+ console.error("Unable to load hook function for " + part.full_name + " for hook " + hook_name + ": " + part.hooks[hook_name]);
}
});
});
return hooks;
-}
+};
if (exports.isClient) {
@@ -90,7 +95,7 @@ if (exports.isClient) {
console.error("Failed to load plugin-definitions: " + err);
cb();
});
- }
+ };
} else {
exports.update = function (cb) {
@@ -104,15 +109,15 @@ exports.update = function (cb) {
exports.loadPlugin(packages, plugin_name, plugins, parts, cb);
},
function (err) {
- exports.plugins = plugins;
+ exports.plugins = plugins;
exports.parts = exports.sortParts(parts);
exports.hooks = exports.extractHooks(exports.parts, "hooks");
- exports.loaded = true;
+ exports.loaded = true;
cb(err);
}
);
});
-}
+ };
exports.getPackages = function (cb) {
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
@@ -122,44 +127,50 @@ exports.getPackages = function (cb) {
var packages = {};
function flatten(deps) {
_.chain(deps).keys().each(function (name) {
- if (name.indexOf(exports.prefix) == 0) {
- packages[name] = deps[name];
- }
- if (deps[name].dependencies !== undefined)
- flatten(deps[name].dependencies);
- delete deps[name].dependencies;
+ if (name.indexOf(exports.prefix) === 0) {
+ packages[name] = _.clone(deps[name]);
+ // Delete anything that creates loops so that the plugin
+ // list can be sent as JSON to the web client
+ delete packages[name].dependencies;
+ delete packages[name].parent;
+ }
+
+ if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
});
}
- flatten([data]);
+
+ var tmp = {};
+ tmp[data.name] = data;
+ flatten(tmp);
cb(null, packages);
});
-}
+ };
-exports.loadPlugin = function (packages, plugin_name, plugins, parts, cb) {
+ exports.loadPlugin = function (packages, plugin_name, plugins, parts, cb) {
var plugin_path = path.resolve(packages[plugin_name].path, "ep.json");
fs.readFile(
plugin_path,
function (er, data) {
if (er) {
- console.error("Unable to load plugin definition file " + plugin_path);
+ console.error("Unable to load plugin definition file " + plugin_path);
return cb();
}
try {
var plugin = JSON.parse(data);
- plugin.package = packages[plugin_name];
- plugins[plugin_name] = plugin;
- _.each(plugin.parts, function (part) {
- part.plugin = plugin_name;
- part.full_name = plugin_name + "/" + part.name;
- parts[part.full_name] = part;
- });
+ plugin['package'] = packages[plugin_name];
+ plugins[plugin_name] = plugin;
+ _.each(plugin.parts, function (part) {
+ part.plugin = plugin_name;
+ part.full_name = plugin_name + "/" + part.name;
+ parts[part.full_name] = part;
+ });
} catch (ex) {
- console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString());
+ console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString());
}
cb();
}
);
-}
+ };
exports.partsToParentChildList = function (parts) {
var res = [];
@@ -175,7 +186,7 @@ exports.partsToParentChildList = function (parts) {
}
});
return res;
-}
+};
// Used only in Node, so no need for _
diff --git a/src/static/js/pluginfw/read-installed.js b/src/static/js/pluginfw/read-installed.js
new file mode 100644
index 00000000..cc03b357
--- /dev/null
+++ b/src/static/js/pluginfw/read-installed.js
@@ -0,0 +1,324 @@
+// A copy of npm/lib/utils/read-installed.js
+// that is hacked to not cache everything :)
+
+// Walk through the file-system "database" of installed
+// packages, and create a data object related to the
+// installed versions of each package.
+
+/*
+This will traverse through all node_modules folders,
+resolving the dependencies object to the object corresponding to
+the package that meets that dep, or just the version/range if
+unmet.
+
+Assuming that you had this folder structure:
+
+/path/to
++-- package.json { name = "root" }
+`-- node_modules
+ +-- foo {bar, baz, asdf}
+ | +-- node_modules
+ | +-- bar { baz }
+ | `-- baz
+ `-- asdf
+
+where "foo" depends on bar, baz, and asdf, bar depends on baz,
+and bar and baz are bundled with foo, whereas "asdf" is at
+the higher level (sibling to foo), you'd get this object structure:
+
+{ <package.json data>
+, path: "/path/to"
+, parent: null
+, dependencies:
+ { foo :
+ { version: "1.2.3"
+ , path: "/path/to/node_modules/foo"
+ , parent: <Circular: root>
+ , dependencies:
+ { bar:
+ { parent: <Circular: foo>
+ , path: "/path/to/node_modules/foo/node_modules/bar"
+ , version: "2.3.4"
+ , dependencies: { baz: <Circular: foo.dependencies.baz> }
+ }
+ , baz: { ... }
+ , asdf: <Circular: asdf>
+ }
+ }
+ , asdf: { ... }
+ }
+}
+
+Unmet deps are left as strings.
+Extraneous deps are marked with extraneous:true
+deps that don't meet a requirement are marked with invalid:true
+
+to READ(packagefolder, parentobj, name, reqver)
+obj = read package.json
+installed = ./node_modules/*
+if parentobj is null, and no package.json
+ obj = {dependencies:{<installed>:"*"}}
+deps = Object.keys(obj.dependencies)
+obj.path = packagefolder
+obj.parent = parentobj
+if name, && obj.name !== name, obj.invalid = true
+if reqver, && obj.version !satisfies reqver, obj.invalid = true
+if !reqver && parentobj, obj.extraneous = true
+for each folder in installed
+ obj.dependencies[folder] = READ(packagefolder+node_modules+folder,
+ obj, folder, obj.dependencies[folder])
+# walk tree to find unmet deps
+for each dep in obj.dependencies not in installed
+ r = obj.parent
+ while r
+ if r.dependencies[dep]
+ if r.dependencies[dep].verion !satisfies obj.dependencies[dep]
+ WARN
+ r.dependencies[dep].invalid = true
+ obj.dependencies[dep] = r.dependencies[dep]
+ r = null
+ else r = r.parent
+return obj
+
+
+TODO:
+1. Find unmet deps in parent directories, searching as node does up
+as far as the left-most node_modules folder.
+2. Ignore anything in node_modules that isn't a package folder.
+
+*/
+
+
+var npm = require("npm/lib/npm.js")
+ , fs = require("graceful-fs")
+ , path = require("path")
+ , asyncMap = require("slide").asyncMap
+ , semver = require("semver")
+ , readJson = require("npm/lib/utils/read-json.js")
+ , log = require("npm/lib/utils/log.js")
+
+module.exports = readInstalled
+
+function readInstalled (folder, cb) {
+ /* This is where we clear the cache, these three lines are all the
+ * new code there is */
+ rpSeen = {};
+ riSeen = [];
+ var fuSeen = [];
+
+ var d = npm.config.get("depth")
+ readInstalled_(folder, null, null, null, 0, d, function (er, obj) {
+ if (er) return cb(er)
+ // now obj has all the installed things, where they're installed
+ // figure out the inheritance links, now that the object is built.
+ resolveInheritance(obj)
+ cb(null, obj)
+ })
+}
+
+var rpSeen = {}
+function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
+ //console.error(folder, name)
+
+ var installed
+ , obj
+ , real
+ , link
+
+ fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
+ // error indicates that nothing is installed here
+ if (er) i = []
+ installed = i.filter(function (f) { return f.charAt(0) !== "." })
+ next()
+ })
+
+ readJson(path.resolve(folder, "package.json"), function (er, data) {
+ obj = copy(data)
+
+ if (!parent) {
+ obj = obj || true
+ er = null
+ }
+ return next(er)
+ })
+
+ fs.lstat(folder, function (er, st) {
+ if (er) {
+ if (!parent) real = true
+ return next(er)
+ }
+ fs.realpath(folder, function (er, rp) {
+ //console.error("realpath(%j) = %j", folder, rp)
+ real = rp
+ if (st.isSymbolicLink()) link = rp
+ next(er)
+ })
+ })
+
+ var errState = null
+ , called = false
+ function next (er) {
+ if (errState) return
+ if (er) {
+ errState = er
+ return cb(null, [])
+ }
+ //console.error('next', installed, obj && typeof obj, name, real)
+ if (!installed || !obj || !real || called) return
+ called = true
+ if (rpSeen[real]) return cb(null, rpSeen[real])
+ if (obj === true) {
+ obj = {dependencies:{}, path:folder}
+ installed.forEach(function (i) { obj.dependencies[i] = "*" })
+ }
+ if (name && obj.name !== name) obj.invalid = true
+ obj.realName = name || obj.name
+ obj.dependencies = obj.dependencies || {}
+
+ // "foo":"http://blah" is always presumed valid
+ if (reqver
+ && semver.validRange(reqver)
+ && !semver.satisfies(obj.version, reqver)) {
+ obj.invalid = true
+ }
+
+ if (parent
+ && !(name in parent.dependencies)
+ && !(name in (parent.devDependencies || {}))) {
+ obj.extraneous = true
+ }
+ obj.path = obj.path || folder
+ obj.realPath = real
+ obj.link = link
+ if (parent && !obj.link) obj.parent = parent
+ rpSeen[real] = obj
+ obj.depth = depth
+ if (depth >= maxDepth) return cb(null, obj)
+ asyncMap(installed, function (pkg, cb) {
+ var rv = obj.dependencies[pkg]
+ if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
+ readInstalled_( path.resolve(folder, "node_modules/"+pkg)
+ , obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
+ , cb )
+ }, function (er, installedData) {
+ if (er) return cb(er)
+ installedData.forEach(function (dep) {
+ obj.dependencies[dep.realName] = dep
+ })
+
+ // any strings here are unmet things. however, if it's
+ // optional, then that's fine, so just delete it.
+ if (obj.optionalDependencies) {
+ Object.keys(obj.optionalDependencies).forEach(function (dep) {
+ if (typeof obj.dependencies[dep] === "string") {
+ delete obj.dependencies[dep]
+ }
+ })
+ }
+ return cb(null, obj)
+ })
+ }
+}
+
+// starting from a root object, call findUnmet on each layer of children
+var riSeen = []
+function resolveInheritance (obj) {
+ if (typeof obj !== "object") return
+ if (riSeen.indexOf(obj) !== -1) return
+ riSeen.push(obj)
+ if (typeof obj.dependencies !== "object") {
+ obj.dependencies = {}
+ }
+ Object.keys(obj.dependencies).forEach(function (dep) {
+ findUnmet(obj.dependencies[dep])
+ })
+ Object.keys(obj.dependencies).forEach(function (dep) {
+ resolveInheritance(obj.dependencies[dep])
+ })
+}
+
+// find unmet deps by walking up the tree object.
+// No I/O
+var fuSeen = []
+function findUnmet (obj) {
+ if (fuSeen.indexOf(obj) !== -1) return
+ fuSeen.push(obj)
+ //console.error("find unmet", obj.name, obj.parent && obj.parent.name)
+ var deps = obj.dependencies = obj.dependencies || {}
+ //console.error(deps)
+ Object.keys(deps)
+ .filter(function (d) { return typeof deps[d] === "string" })
+ .forEach(function (d) {
+ //console.error("find unmet", obj.name, d, deps[d])
+ var r = obj.parent
+ , found = null
+ while (r && !found && typeof deps[d] === "string") {
+ // if r is a valid choice, then use that.
+ found = r.dependencies[d]
+ if (!found && r.realName === d) found = r
+
+ if (!found) {
+ r = r.link ? null : r.parent
+ continue
+ }
+ if ( typeof deps[d] === "string"
+ && !semver.satisfies(found.version, deps[d])) {
+ // the bad thing will happen
+ log.warn(obj.path + " requires "+d+"@'"+deps[d]
+ +"' but will load\n"
+ +found.path+",\nwhich is version "+found.version
+ ,"unmet dependency")
+ found.invalid = true
+ }
+ deps[d] = found
+ }
+
+ })
+ log.verbose([obj._id], "returning")
+ return obj
+}
+
+function copy (obj) {
+ if (!obj || typeof obj !== 'object') return obj
+ if (Array.isArray(obj)) return obj.map(copy)
+
+ var o = {}
+ for (var i in obj) o[i] = copy(obj[i])
+ return o
+}
+
+if (module === require.main) {
+ var util = require("util")
+ console.error("testing")
+
+ var called = 0
+ readInstalled(process.cwd(), function (er, map) {
+ console.error(called ++)
+ if (er) return console.error(er.stack || er.message)
+ cleanup(map)
+ console.error(util.inspect(map, true, 10, true))
+ })
+
+ var seen = []
+ function cleanup (map) {
+ if (seen.indexOf(map) !== -1) return
+ seen.push(map)
+ for (var i in map) switch (i) {
+ case "_id":
+ case "path":
+ case "extraneous": case "invalid":
+ case "dependencies": case "name":
+ continue
+ default: delete map[i]
+ }
+ var dep = map.dependencies
+// delete map.dependencies
+ if (dep) {
+// map.dependencies = dep
+ for (var i in dep) if (typeof dep[i] === "object") {
+ cleanup(dep[i])
+ }
+ }
+ return map
+ }
+}
diff --git a/src/static/js/prefixfree.js b/src/static/js/prefixfree.js
deleted file mode 100644
index b5b23466..00000000
--- a/src/static/js/prefixfree.js
+++ /dev/null
@@ -1,419 +0,0 @@
-/**
- * StyleFix 1.0.2
- * @author Lea Verou
- * MIT license
- */
-
-(function(){
-
-if(!window.addEventListener) {
- return;
-}
-
-var self = window.StyleFix = {
- link: function(link) {
- try {
- // Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
- if(link.rel !== 'stylesheet' || link.hasAttribute('data-noprefix')) {
- return;
- }
- }
- catch(e) {
- return;
- }
-
- var url = link.href || link.getAttribute('data-href'),
- base = url.replace(/[^\/]+$/, ''),
- parent = link.parentNode,
- xhr = new XMLHttpRequest();
-
- xhr.open('GET', url);
-
- xhr.onreadystatechange = function() {
- if(xhr.readyState === 4) {
- var css = xhr.responseText;
-
- if(css && link.parentNode) {
- css = self.fix(css, true, link);
-
- // Convert relative URLs to absolute, if needed
- if(base) {
- css = css.replace(/url\(('?|"?)(.+?)\1\)/gi, function($0, quote, url) {
- if(!/^([a-z]{3,10}:|\/|#)/i.test(url)) { // If url not absolute & not a hash
- // May contain sequences like /../ and /./ but those DO work
- return 'url("' + base + url + '")';
- }
-
- return $0;
- });
-
- // behavior URLs shoudn’t be converted (Issue #19)
- css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + base, 'gi'), '$1');
- }
-
- var style = document.createElement('style');
- style.textContent = css;
- style.media = link.media;
- style.disabled = link.disabled;
- style.setAttribute('data-href', link.getAttribute('href'));
-
- parent.insertBefore(style, link);
- parent.removeChild(link);
- }
- }
- };
-
- xhr.send(null);
-
- link.setAttribute('data-inprogress', '');
- },
-
- styleElement: function(style) {
- var disabled = style.disabled;
-
- style.textContent = self.fix(style.textContent, true, style);
-
- style.disabled = disabled;
- },
-
- styleAttribute: function(element) {
- var css = element.getAttribute('style');
-
- css = self.fix(css, false, element);
-
- element.setAttribute('style', css);
- },
-
- process: function() {
- // Linked stylesheets
- $('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);
-
- // Inline stylesheets
- $('style').forEach(StyleFix.styleElement);
-
- // Inline styles
- $('[style]').forEach(StyleFix.styleAttribute);
- },
-
- register: function(fixer, index) {
- (self.fixers = self.fixers || [])
- .splice(index === undefined? self.fixers.length : index, 0, fixer);
- },
-
- fix: function(css, raw) {
- for(var i=0; i<self.fixers.length; i++) {
- css = self.fixers[i](css, raw) || css;
- }
-
- return css;
- },
-
- camelCase: function(str) {
- return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-','');
- },
-
- deCamelCase: function(str) {
- return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
- }
-};
-
-/**************************************
- * Process styles
- **************************************/
-(function(){
- setTimeout(function(){
- $('link[rel="stylesheet"]').forEach(StyleFix.link);
- }, 10);
-
- document.addEventListener('DOMContentLoaded', StyleFix.process, false);
-})();
-
-function $(expr, con) {
- return [].slice.call((con || document).querySelectorAll(expr));
-}
-
-})();
-
-/**
- * PrefixFree 1.0.4
- * @author Lea Verou
- * MIT license
- */
-(function(root, undefined){
-
-if(!window.StyleFix || !window.getComputedStyle) {
- return;
-}
-
-var self = window.PrefixFree = {
- prefixCSS: function(css, raw) {
- var prefix = self.prefix;
-
- function fix(what, before, after, replacement) {
- what = self[what];
-
- if(what.length) {
- var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi');
-
- css = css.replace(regex, replacement);
- }
- }
-
- fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(');
- fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3');
- fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:');
-
- // Prefix properties *inside* values (issue #8)
- if (self.properties.length) {
- var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi');
-
- fix('valueProperties', '\\b', ':(.+?);', function($0) {
- return $0.replace(regex, prefix + "$1")
- });
- }
-
- if(raw) {
- fix('selectors', '', '\\b', self.prefixSelector);
- fix('atrules', '@', '\\b', '@' + prefix + '$1');
- }
-
- // Fix double prefixing
- css = css.replace(RegExp('-' + prefix, 'g'), '-');
-
- return css;
- },
-
- // Warning: prefixXXX functions prefix no matter what, even if the XXX is supported prefix-less
- prefixSelector: function(selector) {
- return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix })
- },
-
- prefixProperty: function(property, camelCase) {
- var prefixed = self.prefix + property;
-
- return camelCase? StyleFix.camelCase(prefixed) : prefixed;
- }
-};
-
-/**************************************
- * Properties
- **************************************/
-(function() {
- var prefixes = {},
- properties = [],
- shorthands = {},
- style = getComputedStyle(document.documentElement, null),
- dummy = document.createElement('div').style;
-
- // Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
- var iterate = function(property) {
- if(property.charAt(0) === '-') {
- properties.push(property);
-
- var parts = property.split('-'),
- prefix = parts[1];
-
- // Count prefix uses
- prefixes[prefix] = ++prefixes[prefix] || 1;
-
- // This helps determining shorthands
- while(parts.length > 3) {
- parts.pop();
-
- var shorthand = parts.join('-');
-
- if(supported(shorthand) && properties.indexOf(shorthand) === -1) {
- properties.push(shorthand);
- }
- }
- }
- },
- supported = function(property) {
- return StyleFix.camelCase(property) in dummy;
- }
-
- // Some browsers have numerical indices for the properties, some don't
- if(style.length > 0) {
- for(var i=0; i<style.length; i++) {
- iterate(style[i])
- }
- }
- else {
- for(var property in style) {
- iterate(StyleFix.deCamelCase(property));
- }
- }
-
- // Find most frequently used prefix
- var highest = {uses:0};
- for(var prefix in prefixes) {
- var uses = prefixes[prefix];
-
- if(highest.uses < uses) {
- highest = {prefix: prefix, uses: uses};
- }
- }
-
- self.prefix = '-' + highest.prefix + '-';
- self.Prefix = StyleFix.camelCase(self.prefix);
-
- self.properties = [];
-
- // Get properties ONLY supported with a prefix
- for(var i=0; i<properties.length; i++) {
- var property = properties[i];
-
- if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
- var unprefixed = property.slice(self.prefix.length);
-
- if(!supported(unprefixed)) {
- self.properties.push(unprefixed);
- }
- }
- }
-
- // IE fix
- if(self.Prefix == 'Ms'
- && !('transform' in dummy)
- && !('MsTransform' in dummy)
- && ('msTransform' in dummy)) {
- self.properties.push('transform', 'transform-origin');
- }
-
- self.properties.sort();
-})();
-
-/**************************************
- * Values
- **************************************/
-(function() {
-// Values that might need prefixing
-var functions = {
- 'linear-gradient': {
- property: 'backgroundImage',
- params: 'red, teal'
- },
- 'calc': {
- property: 'width',
- params: '1px + 5%'
- },
- 'element': {
- property: 'backgroundImage',
- params: '#foo'
- }
-};
-
-
-functions['repeating-linear-gradient'] =
-functions['repeating-radial-gradient'] =
-functions['radial-gradient'] =
-functions['linear-gradient'];
-
-var keywords = {
- 'initial': 'color',
- 'zoom-in': 'cursor',
- 'zoom-out': 'cursor',
- 'box': 'display',
- 'flexbox': 'display',
- 'inline-flexbox': 'display'
-};
-
-self.functions = [];
-self.keywords = [];
-
-var style = document.createElement('div').style;
-
-function supported(value, property) {
- style[property] = '';
- style[property] = value;
-
- return !!style[property];
-}
-
-for (var func in functions) {
- var test = functions[func],
- property = test.property,
- value = func + '(' + test.params + ')';
-
- if (!supported(value, property)
- && supported(self.prefix + value, property)) {
- // It's supported, but with a prefix
- self.functions.push(func);
- }
-}
-
-for (var keyword in keywords) {
- var property = keywords[keyword];
-
- if (!supported(keyword, property)
- && supported(self.prefix + keyword, property)) {
- // It's supported, but with a prefix
- self.keywords.push(keyword);
- }
-}
-
-})();
-
-/**************************************
- * Selectors and @-rules
- **************************************/
-(function() {
-
-var
-selectors = {
- ':read-only': null,
- ':read-write': null,
- ':any-link': null,
- '::selection': null
-},
-
-atrules = {
- 'keyframes': 'name',
- 'viewport': null,
- 'document': 'regexp(".")'
-};
-
-self.selectors = [];
-self.atrules = [];
-
-var style = root.appendChild(document.createElement('style'));
-
-function supported(selector) {
- style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
-
- return !!style.sheet.cssRules.length;
-}
-
-for(var selector in selectors) {
- var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : '');
-
- if(!supported(test) && supported(self.prefixSelector(test))) {
- self.selectors.push(selector);
- }
-}
-
-for(var atrule in atrules) {
- var test = atrule + ' ' + (atrules[atrule] || '');
-
- if(!supported('@' + test) && supported('@' + self.prefix + test)) {
- self.atrules.push(atrule);
- }
-}
-
-root.removeChild(style);
-
-})();
-
-// Properties that accept properties as their value
-self.valueProperties = [
- 'transition',
- 'transition-property'
-]
-
-// Add class for current prefix
-root.className += ' ' + self.prefix;
-
-StyleFix.register(self.prefixCSS);
-
-
-})(document.documentElement);
diff --git a/src/static/pad.html b/src/static/pad.html
deleted file mode 100644
index 95a5b98f..00000000
--- a/src/static/pad.html
+++ /dev/null
@@ -1,284 +0,0 @@
-<!doctype html>
-<html>
-
- <title>Etherpad Lite</title>
-
- <meta charset="utf-8">
- <meta name="robots" content="noindex, nofollow">
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
-
- <link href="../static/css/pad.css" rel="stylesheet">
- <link href="../static/custom/pad.css" rel="stylesheet">
- <style title="dynamicsyntax"></style>
-
- <!-- head and body had been removed intentionally -->
-
- <div id="editbar">
- <ul id="menu_left">
- <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>
- </ul>
- <ul id="menu_right">
- <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>
- </ul>
- </div>
-
- <div id="users">
- <div id="connectionstatus"></div>
- <div id="myuser">
- <div id="mycolorpicker">
- <div id="colorpicker"></div>
- <button id="mycolorpickersave">Save</button>
- <button id="mycolorpickercancel">Cancel</button>
- <span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
- </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>
- <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>
- <div id="userlistbuttonarea"></div>
- </div>
-
- <div id="editorcontainerbox">
- <div id="editorcontainer"></div>
- <div id="editorloadingbox">Loading...</div>
- </div>
-
- <div id="settingsmenu" class="popup">
- <h1>Pad settings</h1>
- <div class="column">
- <h2>My view</h2>
- <p>
- <input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
- <label for="options-stickychat">Chat always on screen</label>
- </p>
- <p>
- <input type="checkbox" id="options-colorscheck">
- <label for="options-colorscheck">Authorship colors</label>
- </p>
- <p>
- <input type="checkbox" id="options-linenoscheck" checked>
- <label for="options-linenoscheck">Line numbers</label>
- </p>
- <p>
- Font type:
- <select id="viewfontmenu">
- <option value="normal">Normal</option>
- <option value="monospace">Monospaced</option>
- </select>
- </p>
- </div>
- <div class="column">
- <h2>Global view</h2>
- <p>Currently nothing.</p>
- <p class="note">These options affect everyone viewing this pad.</p>
- </div>
- </div>
-
- <div id="importexport" class="popup">
- <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 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>
- </form>
- </div>
- <div class="column">
- <h2>Export current pad as</h2>
- <a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
- <a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
- <a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
- <a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
- <a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
- <a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
- <a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
- </div>
- </div>
-
- <div id="embed" class="popup">
- <div id="embedreadonly" class="right">
- <input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
- <label for="readonlyinput">Read only</label>
- </div>
- <h1>Share this pad</h1>
- <div id="linkcode">
- <h2>Link</h2>
- <input id="linkinput" type="text" value="">
- </div>
- <br>
- <div id="embedcode">
- <h2>Embed URL</h2>
- <input id="embedinput" type="text" value="">
- </div>
- <br>
- <div id="qrcode">
- <h2>QR code</h2>
- <div id="qr_center"><img id="embedreadonlyqr"></div>
- </div>
- </div>
-
- <div id="chatthrob"></div>
-
- <div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
- <span id="chatlabel">Chat</span>
- <span class="buttonicon buttonicon-chat"></span>
- <span id="chatcounter">0</span>
- </div>
-
- <div id="chatbox">
- <div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
- <div id="chattext" class="authorColors"></div>
- <div id="chatinputbox">
- <form>
- <input id="chatinput" type="text" maxlength="140">
- </form>
- </div>
- </div>
-
- <div id="focusprotector">&nbsp;</div>
-
- <div id="modaloverlay">
- <div id="modaloverlay-inner"></div>
- </div>
-
- <div id="mainmodals">
- <div id="connectionbox" class="modaldialog">
- <div id="connectionboxinner" class="modaldialog-inner">
- <div class="connecting">Connecting...</div>
- <div class="reconnecting">Reestablishing connection...</div>
- <div class="disconnected">
- <h2 class="h2_disconnect">Disconnected.</h2>
- <h2 class="h2_userdup">Opened in another window.</h2>
- <h2 class="h2_unauth">No Authorization.</h2>
- <div id="disconnected_looping">
- <p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
- </div>
- <div id="disconnected_initsocketfail">
- <p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
- </div>
- <div id="disconnected_userdup">
- <p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
- </div>
- <div id="disconnected_unknown">
- <p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
- </div>
- <div id="disconnected_slowcommit">
- <p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
- </div>
- <div id="disconnected_unauth">
- <p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
- </div>
- <div id="disconnected_deleted">
- <p>This pad was deleted.</p>
- </div>
- <div id="reconnect_advise">
- <p>If this continues to happen, please let us know</p>
- </div>
- <div id="reconnect_form">
- <button id="forcereconnect">Reconnect Now</button>
- </div>
- </div>
- </div>
- <form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
- <input type="hidden" class="padId" name="padId">
- <input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
- <input type="hidden" class="missedChanges" name="missedChanges">
- </form>
- </div>
-
- </div>
-
- <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">
- 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();
- });
-
- /* 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>
-
-</html>
diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html
new file mode 100644
index 00000000..7dcb6fa3
--- /dev/null
+++ b/src/templates/admin/plugins.html
@@ -0,0 +1,217 @@
+<html>
+ <head>
+ <title>Plugin manager</title>
+ <style>
+ 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;
+ }
+ </style>
+ <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>
+ <% 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="I" 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="S" 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="I" 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>
+
+ </body>
+</html>
diff --git a/src/static/index.html b/src/templates/index.html
index 58f68801..58f68801 100644
--- a/src/static/index.html
+++ b/src/templates/index.html
diff --git a/src/templates/pad.html b/src/templates/pad.html
new file mode 100644
index 00000000..e589fb55
--- /dev/null
+++ b/src/templates/pad.html
@@ -0,0 +1,307 @@
+<%
+ var settings = require("ep_etherpad-lite/node/utils/Settings");
+%>
+<!doctype html>
+<html>
+
+ <title>Etherpad Lite</title>
+
+ <meta charset="utf-8">
+ <meta name="robots" content="noindex, nofollow">
+ <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>
+ <% 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>
+ <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(); %>
+ </ul>
+ </div>
+
+ <div id="users">
+ <div id="connectionstatus"></div>
+ <div id="myuser">
+ <div id="mycolorpicker">
+ <div id="colorpicker"></div>
+ <button id="mycolorpickersave">Save</button>
+ <button id="mycolorpickercancel">Cancel</button>
+ <span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
+ </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>
+ <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>
+ <div id="userlistbuttonarea"></div>
+ </div>
+
+ <div id="editorcontainerbox">
+ <div id="editorcontainer"></div>
+ <div id="editorloadingbox">Loading...</div>
+ </div>
+
+ <div id="settingsmenu" class="popup">
+ <h1>Pad settings</h1>
+ <div class="column">
+ <h2>My view</h2>
+ <p>
+ <input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
+ <label for="options-stickychat">Chat always on screen</label>
+ </p>
+ <p>
+ <input type="checkbox" id="options-colorscheck">
+ <label for="options-colorscheck">Authorship colors</label>
+ </p>
+ <p>
+ <input type="checkbox" id="options-linenoscheck" checked>
+ <label for="options-linenoscheck">Line numbers</label>
+ </p>
+ <p>
+ Font type:
+ <select id="viewfontmenu">
+ <option value="normal">Normal</option>
+ <option value="monospace">Monospaced</option>
+ </select>
+ </p>
+ </div>
+ <div class="column">
+ <h2>Global view</h2>
+ <p>Currently nothing.</p>
+ <p class="note">These options affect everyone viewing this pad.</p>
+ </div>
+ </div>
+
+ <div id="importexport" class="popup">
+ <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 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>
+ </form>
+ </div>
+ <div class="column">
+ <h2>Export current pad as</h2>
+ <a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
+ <a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
+ <a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
+ <a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
+ <a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
+ <a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
+ <a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
+ </div>
+ </div>
+
+ <div id="embed" class="popup">
+ <div id="embedreadonly" class="right">
+ <input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
+ <label for="readonlyinput">Read only</label>
+ </div>
+ <h1>Share this pad</h1>
+ <div id="linkcode">
+ <h2>Link</h2>
+ <input id="linkinput" type="text" value="">
+ </div>
+ <br>
+ <div id="embedcode">
+ <h2>Embed URL</h2>
+ <input id="embedinput" type="text" value="">
+ </div>
+ <br>
+ <div id="qrcode">
+ <h2>QR code</h2>
+ <div id="qr_center"><img id="embedreadonlyqr"></div>
+ </div>
+ </div>
+
+ <div id="chatthrob"></div>
+
+ <div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
+ <span id="chatlabel">Chat</span>
+ <span class="buttonicon buttonicon-chat"></span>
+ <span id="chatcounter">0</span>
+ </div>
+
+ <div id="chatbox">
+ <div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
+ <div id="chattext" class="authorColors"></div>
+ <div id="chatinputbox">
+ <form>
+ <input id="chatinput" type="text" maxlength="140">
+ </form>
+ </div>
+ </div>
+
+ <div id="focusprotector">&nbsp;</div>
+
+ <div id="modaloverlay">
+ <div id="modaloverlay-inner"></div>
+ </div>
+
+ <div id="mainmodals">
+ <% e.begin_block("modals"); %>
+ <div id="connectionbox" class="modaldialog">
+ <div id="connectionboxinner" class="modaldialog-inner">
+ <div class="connecting">Connecting...</div>
+ <div class="reconnecting">Reestablishing connection...</div>
+ <div class="disconnected">
+ <h2 class="h2_disconnect">Disconnected.</h2>
+ <h2 class="h2_userdup">Opened in another window.</h2>
+ <h2 class="h2_unauth">No Authorization.</h2>
+ <div id="disconnected_looping">
+ <p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
+ </div>
+ <div id="disconnected_initsocketfail">
+ <p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
+ </div>
+ <div id="disconnected_userdup">
+ <p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
+ </div>
+ <div id="disconnected_unknown">
+ <p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
+ </div>
+ <div id="disconnected_slowcommit">
+ <p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
+ </div>
+ <div id="disconnected_unauth">
+ <p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
+ </div>
+ <div id="disconnected_deleted">
+ <p>This pad was deleted.</p>
+ </div>
+ <div id="reconnect_advise">
+ <p>If this continues to happen, please let us know</p>
+ </div>
+ <div id="reconnect_form">
+ <button id="forcereconnect">Reconnect Now</button>
+ </div>
+ </div>
+ </div>
+ <form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
+ <input type="hidden" class="padId" name="padId">
+ <input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
+ <input type="hidden" class="missedChanges" name="missedChanges">
+ </form>
+ </div>
+ <% e.end_block(); %>
+ </div>
+
+ <% e.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>
+ <% if (settings.minify) { %>
+ <script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
+ <% } %>
+ <script type="text/javascript">
+ var clientVars = {};
+ (function () {
+ <% if (settings.minify) { %>
+ require.setRootURI("../javascripts/src");
+ require.setLibraryURI("../javascripts/lib");
+ require.setGlobalKeyPath("require");
+ <% } else { %>
+ require.setRootURI("../static/js");
+ require.setLibraryURI("../static/plugins");
+ <% } %>
+
+ 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>
+ <% e.end_block(); %>
+</html>
diff --git a/src/static/timeslider.html b/src/templates/timeslider.html
index 3c909ede..12867b7a 100644
--- a/src/static/timeslider.html
+++ b/src/templates/timeslider.html
@@ -63,7 +63,7 @@
</div>
<div class="timeslider-bar">
- <div class="editbarright" id="editbar">
+ <div class="editbarright toolbar" id="editbar">
<ul>
<li onClick="window.padeditbar.toolbarClick('import_export');return false;">
<a id="exportlink" title="Export to different document formats">