Merge branch 'develop' of git://github.com/Pita/etherpad-lite into body-block

Resolved conflicts:
	src/templates/pad.html
This commit is contained in:
Edy 2012-04-30 15:17:23 +02:00
commit cf54c23228
26 changed files with 730 additions and 483 deletions

View File

@ -40,22 +40,35 @@
"minify" : true, "minify" : true,
/* How long may clients use served javascript code (in seconds)? Without versioning this /* How long may clients use served javascript code (in seconds)? Without versioning this
is may cause problems during deployment. Set to 0 to disable caching */ may cause problems during deployment. Set to 0 to disable caching */
"maxAge" : 21600, // 6 hours "maxAge" : 21600, // 60 * 60 * 6 = 6 hours
/* This is the path to the Abiword executable. Setting it to null, disables abiword. /* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/ Abiword is needed to enable the import/export of pads*/
"abiword" : null, "abiword" : null,
/* This setting is used if you need http basic auth */ /* This setting is used if you require authentication of all users.
// "httpAuth" : "user:pass", Note: /admin always requires authentication. */
"requireAuthentication": false,
/* This setting is used for http basic auth for admin pages. If not set, the admin page won't be accessible from web*/ /* Require authorization by a module, or a user with is_admin set, see below. */
// "adminHttpAuth" : "user:pass", "requireAuthorization": false,
/* Users for basic authentication. is_admin = true gives access to /admin.
If you do not uncomment this, /admin will not be available! */
/*
"users": {
"admin": {
"password": "changeme1",
"is_admin": true
},
"user": {
"password": "changeme1",
"is_admin": false
}
},
*/
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */ /* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO", "loglevel": "INFO"
/* cache 6 hours = 1000*60*60*6 */
"maxAge": 21600000
} }

View File

@ -23,6 +23,7 @@ var ejs = require("ejs");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var resolve = require("resolve");
exports.info = { exports.info = {
buf_stack: [], buf_stack: [],
@ -91,13 +92,28 @@ exports.inherit = function (name, args) {
exports.info.file_stack[exports.info.file_stack.length-1].inherit.push({name:name, args:args}); exports.info.file_stack[exports.info.file_stack.length-1].inherit.push({name:name, args:args});
} }
exports.require = function (name, args) { exports.require = function (name, args, mod) {
if (args == undefined) args = {}; if (args == undefined) args = {};
if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) { var basedir = __dirname;
name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path), name); var paths = [];
if (exports.info.file_stack.length) {
basedir = path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path);
} }
var ejspath = require.resolve(name) if (mod) {
basedir = path.dirname(mod.filename);
paths = mod.paths;
}
var ejspath = resolve.sync(
name,
{
paths : paths,
basedir : basedir,
extensions : [ '.html', '.ejs' ],
}
)
args.e = exports; args.e = exports;
args.require = require; args.require = require;

View File

@ -155,8 +155,6 @@ function createTimesliderClientVars (padId, callback)
var clientVars = { var clientVars = {
viewId: padId, viewId: padId,
colorPalette: ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"], colorPalette: ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"],
sliderEnabled : true,
supportsSlider: true,
savedRevisions: [], savedRevisions: [],
padIdForUrl: padId, padIdForUrl: padId,
fullWidth: false, fullWidth: false,

View File

@ -21,13 +21,15 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.socketio = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer"); var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) { io.on('connection', function (socket) {
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
socket.on("load", function (query) { socket.on("load", function (query) {
socket.emit("installed-results", {results: plugins.plugins}); socket.emit("installed-results", {results: plugins.plugins});
}); });
socket.on("search", function (query) { socket.on("search", function (query) {
socket.emit("progress", {progress:0, message:'Fetching results...'}); socket.emit("progress", {progress:0, message:'Fetching results...'});
installer.search(query, function (progress) { installer.search(query, true, function (progress) {
if (progress.results) if (progress.results)
socket.emit("search-result", progress); socket.emit("search-result", progress);
socket.emit("progress", progress); socket.emit("progress", progress);

View File

@ -7,11 +7,27 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var padMessageHandler = require("../../handler/PadMessageHandler"); var padMessageHandler = require("../../handler/PadMessageHandler");
var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler"); var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler");
var connect = require('connect');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler //init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(args.app); var io = socketio.listen(args.app);
/* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */
io.set('authorization', function (data, accept) {
if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
data.cookie = connect.utils.parseCookie(data.headers.cookie);
data.sessionID = data.cookie.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
accept(null, true);
});
});
//this is only a workaround to ensure it works with all browers behind a proxy //this is only a workaround to ensure it works with all browers behind a proxy
//we should remove this when the new socket.io version is more stable //we should remove this when the new socket.io version is more stable
io.set('transports', ['xhr-polling']); io.set('transports', ['xhr-polling']);

View File

@ -2,50 +2,108 @@ var express = require('express');
var log4js = require('log4js'); var log4js = require('log4js');
var httpLogger = log4js.getLogger("http"); var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings'); var settings = require('../../utils/Settings');
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
//checks for basic http auth //checks for basic http auth
exports.basicAuth = function (req, res, next) { exports.basicAuth = function (req, res, next) {
var hookResultMangle = function (cb) {
// When handling HTTP-Auth, an undefined password will lead to no authorization at all return function (err, data) {
var pass = settings.httpAuth || ''; return cb(!err && data.length && data[0]);
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. var authorize = function (cb) {
res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); // Do not require auth for static paths...this could be a bit brittle
if (req.headers.authorization) { if (req.path.match(/^\/(static|javascripts|pluginfw)/)) return cb(true);
setTimeout(function () {
res.send('Authentication required', 401); if (req.path.indexOf('/admin') != 0) {
}, 1000); if (!settings.requireAuthentication) return cb(true);
} else { if (!settings.requireAuthorization && req.session && req.session.user) return cb(true);
res.send('Authentication required', 401); }
if (req.session && req.session.user && req.session.user.is_admin) return cb(true);
hooks.aCallFirst("authorize", {req: req, res:res, next:next, resource: req.path}, hookResultMangle(cb));
} }
var authenticate = function (cb) {
// If auth headers are present use them to authenticate...
if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
var username = userpass[0];
var password = userpass[1];
if (settings.users[username] != undefined && settings.users[username].password == password) {
settings.users[username].username = username;
req.session.user = settings.users[username];
return cb(true);
}
return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(cb));
}
hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb));
}
/* Authentication OR authorization failed. */
var failure = function () {
return hooks.aCallFirst("authFailure", {req: req, res:res, next:next}, hookResultMangle(function (ok) {
if (ok) return;
/* No plugin handler for invalid auth. Return Auth required
* Headers, delayed for 1 second, if authentication failed
* before. */
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
res.send('Authentication required', 401);
}, 1000);
} else {
res.send('Authentication required', 401);
}
}));
}
/* This is the actual authentication/authorization hoop. It is done in four steps:
1) Try to just access the thing
2) If not allowed using whatever creds are in the current session already, try to authenticate
3) If authentication using already supplied credentials succeeds, try to access the thing again
4) If all els fails, give the user a 401 to request new credentials
Note that the process could stop already in step 3 with a redirect to login page.
*/
authorize(function (ok) {
if (ok) return next();
authenticate(function (ok) {
if (!ok) return failure();
authorize(function (ok) {
if (ok) return next();
failure();
});
});
});
} }
exports.expressConfigure = function (hook_name, args, cb) { exports.expressConfigure = function (hook_name, args, cb) {
args.app.use(exports.basicAuth);
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(express.cookieParser()); args.app.use(express.cookieParser());
/* Do not let express create the session, so that we can retain a
* reference to it for socket.io to use. Also, set the key (cookie
* name) to a javascript identifier compatible string. Makes code
* handling it cleaner :) */
args.app.sessionStore = new express.session.MemoryStore();
args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid',
secret: apikey = randomString(32)}));
args.app.use(exports.basicAuth);
} }

View File

@ -30,6 +30,7 @@ var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm/lib/npm.js"); var npm = require("npm/lib/npm.js");
var _ = require("underscore");
//try to get the git version //try to get the git version
var version = ""; var version = "";
@ -88,11 +89,11 @@ async.waterfall([
//let the server listen //let the server listen
app.listen(settings.port, settings.ip); app.listen(settings.port, settings.ip);
console.log("Server is listening at " + settings.ip + ":" + settings.port); console.log("Server is listening at " + settings.ip + ":" + settings.port);
if(settings.adminHttpAuth){ if(!_.isEmpty(settings.users)){
console.log("Plugin admin page listening at " + settings.ip + ":" + settings.port + "/admin/plugins"); console.log("Plugin admin page listening at " + settings.ip + ":" + settings.port + "/admin/plugins");
} }
else{ else{
console.log("Admin username and password not set in settings.json. To access admin please uncomment and edit adminHttpAuth in settings.json"); console.log("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
} }
callback(null); callback(null);
} }

View File

@ -80,15 +80,12 @@ exports.abiword = null;
*/ */
exports.loglevel = "INFO"; exports.loglevel = "INFO";
/** /* This setting is used if you need authentication and/or
* Http basic auth, with "user:password" format * authorization. Note: /admin always requires authentication, and
*/ * either authorization by a module, or a user with is_admin set */
exports.httpAuth = null; exports.requireAuthentication = false;
exports.requireAuthorization = false;
/** exports.users = {};
* Http basic auth, with "user:password" format
*/
exports.adminHttpAuth = null;
//checks if abiword is avaiable //checks if abiword is avaiable
exports.abiwordAvailable = function() exports.abiwordAvailable = function()

View File

@ -13,10 +13,12 @@
"yajsml" : "1.1.2", "yajsml" : "1.1.2",
"request" : "2.9.100", "request" : "2.9.100",
"require-kernel" : "1.0.5", "require-kernel" : "1.0.5",
"resolve" : "0.2.1",
"socket.io" : "0.8.7", "socket.io" : "0.8.7",
"ueberDB" : "0.1.7", "ueberDB" : "0.1.7",
"async" : "0.1.18", "async" : "0.1.18",
"express" : "2.5.8", "express" : "2.5.8",
"connect" : "1.8.7",
"clean-css" : "0.3.2", "clean-css" : "0.3.2",
"uglify-js" : "1.2.5", "uglify-js" : "1.2.5",
"formidable" : "1.0.9", "formidable" : "1.0.9",

View File

@ -1,6 +1,5 @@
body { body {
margin: 0; margin: 0;
height: 100%;
color: #333; color: #333;
font: 14px helvetica, sans-serif; font: 14px helvetica, sans-serif;
background: #ddd; background: #ddd;
@ -42,34 +41,33 @@ form {
width: 300px; width: 300px;
margin: 0 auto; margin: 0 auto;
} }
button, input { input {
font-weight: bold; font-weight: bold;
font-size: 15px; font-size: 15px;
} }
input[type="button"] { input[type="button"] {
height: 30px; padding: 4px 6px;
margin: 0; margin: 0;
display: block;
} }
input[value="Uninstall"], input[value="Install"] { input[type="button"].do-install, input[type="button"].do-uninstall {
float: right; float: right;
width: 100px; width: 100px;
} }
input[type="button"]#do-search {
display: block;
}
input[type="text"] { input[type="text"] {
border-radius: 3px; border-radius: 3px;
box-sizing: border-box; box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
padding: 10px; padding: 10px;
*padding: 0; /* IE7 hack */ *padding: 0; /* IE7 hack */
width: 100%; width: 100%;
outline: none; outline: none;
border: 1px solid #ddd; border: 1px solid #ddd;
margin: 0 0 5px 1px; margin: 0 0 5px 0;
max-width: 500px; max-width: 500px;
} }
button{
display:block;
}
table { table {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 3px; border-radius: 3px;
@ -95,13 +93,13 @@ td, th {
height: 500px; height: 500px;
margin-left: -350px; margin-left: -350px;
margin-top: -250px; margin-top: -250px;
border: 3px solid #999999; border: 3px solid #999;
background: #eeeeee; background: #eee;
} }
.dialog .title { .dialog .title {
margin: 0; margin: 0;
padding: 2px; padding: 2px;
border-bottom: 3px solid #999999; border-bottom: 3px solid #999;
font-size: 24px; font-size: 24px;
line-height: 24px; line-height: 24px;
height: 24px; height: 24px;
@ -109,10 +107,11 @@ td, th {
} }
.dialog .title .close { .dialog .title .close {
float: right; float: right;
padding: 1px 10px;
} }
.dialog .history { .dialog .history {
background: #222222; background: #222;
color: #eeeeee; color: #eee;
position: absolute; position: absolute;
top: 41px; top: 41px;
bottom: 10px; bottom: 10px;

View File

@ -1402,7 +1402,7 @@ input[type=checkbox] {
float: left; float: left;
width: 50%; width: 50%;
} }
#settingsmenu, #settings,
#importexport, #importexport,
#embed { #embed {
position: absolute; position: absolute;
@ -1546,7 +1546,7 @@ input[type=checkbox] {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
} }
#settingsmenu, #settings,
#importexport, #importexport,
#embed { #embed {
left: 0; left: 0;

View File

@ -167,7 +167,13 @@ require.setGlobalKeyPath("require");\n\
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]); buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT); buffer.push(KERNEL_BOOT);
buffer.push('<\/script>'); buffer.push('<\/script>');
} } else {
file = KERNEL_SOURCE;
buffer.push('<script type="application/javascript" src="' + KERNEL_SOURCE + '"><\/script>');
buffer.push('<script type="text/javascript">');
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
}
} }
function pushScriptsTo(buffer) { function pushScriptsTo(buffer) {
/* Folling is for packaging regular expression. */ /* Folling is for packaging regular expression. */

View File

@ -4635,7 +4635,7 @@ function Ace2Inner(){
function setClassPresence(elem, className, present) function setClassPresence(elem, className, present)
{ {
if (present) $(elem).addClass(className); if (present) $(elem).addClass(className);
else $(elem).removeClass(elem, className); else $(elem).removeClass(className);
} }
function setup() function setup()
@ -5401,7 +5401,9 @@ function Ace2Inner(){
// Init documentAttributeManager // Init documentAttributeManager
documentAttributeManager = new AttributeManager(rep, performDocumentApplyChangeset); documentAttributeManager = new AttributeManager(rep, performDocumentApplyChangeset);
editorInfo.ace_performDocumentApplyAttributesToRange = documentAttributeManager.setAttributesOnRange; editorInfo.ace_performDocumentApplyAttributesToRange = function () {
return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments);
};
$(document).ready(function(){ $(document).ready(function(){
doc = document; // defined as a var in scope outside doc = document; // defined as a var in scope outside

View File

@ -0,0 +1,143 @@
$(document).ready(function () {
var socket,
loc = document.location,
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
pathComponents = location.pathname.split('/'),
// Strip admin/plugins
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
resource = baseURL.substring(1) + "socket.io";
//connect
socket = io.connect(url, {resource : resource}).of("/pluginfw/installer");
$('.search-results').data('query', {
pattern: '',
offset: 0,
limit: 4,
});
var doUpdate = false;
var search = function () {
socket.emit("search", $('.search-results').data('query'));
}
function updateHandlers() {
$("#progress.dialog .close").unbind('click').click(function () {
$("#progress.dialog").hide();
});
$("#do-search").unbind('click').click(function () {
var query = $('.search-results').data('query');
query.pattern = $("#search-query")[0].value;
query.offset = 0;
search();
});
$(".do-install").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("install", row.find(".name").html());
});
$(".do-uninstall").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("uninstall", row.find(".name").html());
});
$(".do-prev-page").unbind('click').click(function (e) {
var query = $('.search-results').data('query');
query.offset -= query.limit;
if (query.offset < 0) {
query.offset = 0;
}
search();
});
$(".do-next-page").unbind('click').click(function (e) {
var query = $('.search-results').data('query');
var total = $('.search-results').data('total');
if (query.offset + query.limit < total) {
query.offset += query.limit;
}
search();
});
}
updateHandlers();
socket.on('progress', function (data) {
if (data.progress > 0 && $('#progress.dialog').data('progress') > data.progress) return;
$("#progress.dialog .close").hide();
$("#progress.dialog").show();
$('#progress.dialog').data('progress', data.progress);
var message = "Unknown status";
if (data.message) {
message = "<span class='status'>" + data.message.toString() + "</span>";
}
if (data.error) {
message = "<span class='error'>" + data.error.toString() + "<span>";
}
$("#progress.dialog .message").html(message);
$("#progress.dialog .history").append("<div>" + message + "</div>");
if (data.progress >= 1) {
if (data.error) {
$("#progress.dialog .close").show();
} else {
if (doUpdate) {
doUpdate = false;
socket.emit("load");
}
$("#progress.dialog").hide();
}
}
});
socket.on('search-result', function (data) {
var widget=$(".search-results");
widget.data('query', data.query);
widget.data('total', data.total);
widget.find('.offset').html(data.query.offset);
widget.find('.limit').html(data.query.offset + data.query.limit);
widget.find('.total').html(data.total);
widget.find(".results *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = widget.find(".template tr").clone();
for (attr in plugin) {
row.find("." + attr).html(plugin[attr]);
}
widget.find(".results").append(row);
}
updateHandlers();
});
socket.on('installed-results', function (data) {
$("#installed-plugins *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone();
for (attr in plugin.package) {
row.find("." + attr).html(plugin.package[attr]);
}
$("#installed-plugins").append(row);
}
updateHandlers();
});
socket.emit("load");
search();
});

View File

@ -162,11 +162,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
function showReconnectUI() function showReconnectUI()
{ {
if (!clientVars.sliderEnabled || !clientVars.supportsSlider) $("#padmain, #rightbars").css('top', "130px");
{ $("#timeslider").show();
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
}
$('#error').show(); $('#error').show();
} }
@ -216,7 +213,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
authorsList.append(' ('); authorsList.append(' (');
_.each(colorsAnonymous, function(color, i){ _.each(colorsAnonymous, function(color, i){
if( i > 0 ) authorsList.append(' '); if( i > 0 ) authorsList.append(' ');
$('<span /> ') $('<span>&nbsp;</span>')
.css('background-color', color) .css('background-color', color)
.addClass('author author-anonymous') .addClass('author author-anonymous')
.appendTo(authorsList); .appendTo(authorsList);
@ -287,55 +284,52 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{ {
disableSelection($("#playpause_button")[0]); disableSelection($("#playpause_button")[0]);
disableSelection($("#timeslider")[0]); disableSelection($("#timeslider")[0]);
if (clientVars.sliderEnabled && clientVars.supportsSlider) $(document).keyup(function(e)
{ {
$(document).keyup(function(e) var code = -1;
{ if (!e) var e = window.event;
var code = -1; if (e.keyCode) code = e.keyCode;
if (!e) var e = window.event; else if (e.which) code = e.which;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
if (code == 37) if (code == 37)
{ // left { // left
if (!e.shiftKey) if (!e.shiftKey)
{
setSliderPosition(getSliderPosition() - 1);
}
else
{
var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
}
else if (code == 39)
{ {
if (!e.shiftKey) setSliderPosition(getSliderPosition() - 1);
{
setSliderPosition(getSliderPosition() + 1);
}
else
{
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
} }
else if (code == 32) playpause(); else
{
}); var nextStar = 0; // default to first revision in document
} for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
}
else if (code == 39)
{
if (!e.shiftKey)
{
setSliderPosition(getSliderPosition() + 1);
}
else
{
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
}
else if (code == 32) playpause();
});
$(window).resize(function() $(window).resize(function()
{ {
updateSliderElements(); updateSliderElements();
@ -485,37 +479,16 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
$("#revision").css('right', "20px"); $("#revision").css('right', "20px");
$("#revision").css('top', "20px"); $("#revision").css('top', "20px");
} }
$("#timeslider").show();
if (clientVars.sliderEnabled) setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
_.each(clientVars.savedRevisions, function(revision)
{ {
if (clientVars.supportsSlider) addSavedRevision(revision.revNum, revision);
{ })
$("#timeslider").show();
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
_.each(clientVars.savedRevisions, function(revision)
{
addSavedRevision(revision.revNum, revision);
})
}
else
{
// slider is not supported
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
$("#error").html("The timeslider feature is not supported on this pad. <a href=\"/ep/about/faq#disabledslider\">Why not?</a>");
$("#error").show();
}
}
else
{
if (clientVars.supportsSlider)
{
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
}
}
} }
}); });
})(); })();

View File

@ -229,6 +229,10 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
result.node.innerHTML = curHTML; result.node.innerHTML = curHTML;
} }
if (lineClass !== null) result.node.className = lineClass; if (lineClass !== null) result.node.className = lineClass;
hooks.callAll("acePostWriteDomLineHTML", {
node: result.node
});
} }
result.prepareForAdd = writeHTML; result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML; result.finishUpdate = writeHTML;

View File

@ -203,7 +203,7 @@ function handshake()
//create the url //create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/"; var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are //find out in which subfolder we are
var resource = loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "socket.io"; var resource = exports.baseURL.substring(1) + "socket.io";
//connect //connect
socket = pad.socket = io.connect(url, { socket = pad.socket = io.connect(url, {
resource: resource, resource: resource,
@ -1029,7 +1029,7 @@ var settings = {
}; };
pad.settings = settings; pad.settings = settings;
exports.baseURL = '';
exports.settings = settings; exports.settings = settings;
exports.createCookie = createCookie; exports.createCookie = createCookie;
exports.readCookie = readCookie; exports.readCookie = readCookie;

View File

@ -119,7 +119,7 @@ var padeditbar = (function()
} }
else if (cmd == 'settings') else if (cmd == 'settings')
{ {
self.toogleDropDown("settingsmenu"); self.toogleDropDown("settings");
} }
else if (cmd == 'embed') else if (cmd == 'embed')
{ {
@ -177,12 +177,11 @@ var padeditbar = (function()
}, },
toogleDropDown: function(moduleName) toogleDropDown: function(moduleName)
{ {
var modules = ["settingsmenu", "importexport", "embed", "users"]; var modules = ["settings", "importexport", "embed", "users"];
//hide all modules // hide all modules and remove highlighting of all buttons
if(moduleName == "none") if(moduleName == "none")
{ {
$("#editbar ul#menu_right > li").removeClass("selected");
for(var i=0;i<modules.length;i++) for(var i=0;i<modules.length;i++)
{ {
//skip the userlist //skip the userlist
@ -193,29 +192,27 @@ var padeditbar = (function()
if(module.css('display') != "none") if(module.css('display') != "none")
{ {
$("#" + modules[i] + "link").removeClass("selected");
module.slideUp("fast"); module.slideUp("fast");
} }
} }
} }
else else
{ {
var nth_child = indexOf(modules, moduleName) + 1; // hide all modules that are not selected and remove highlighting
if (nth_child > 0 && nth_child <= (modules.length-1)) { // respectively add highlighting to the corresponding button
$("#editbar ul#menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected");
$("#editbar ul#menu_right li:nth-child(" + nth_child + ")").toggleClass("selected");
}
if(modules[modules.length-1] === moduleName) $("#editbar ul#menu_right li").removeClass("selected");
//hide all modules that are not selected and show the selected one
for(var i=0;i<modules.length;i++) for(var i=0;i<modules.length;i++)
{ {
var module = $("#" + modules[i]); var module = $("#" + modules[i]);
if(module.css('display') != "none") if(module.css('display') != "none")
{ {
$("#" + modules[i] + "link").removeClass("selected");
module.slideUp("fast"); module.slideUp("fast");
} }
else if(modules[i]==moduleName) else if(modules[i]==moduleName)
{ {
$("#" + modules[i] + "link").addClass("selected");
module.slideDown("fast"); module.slideDown("fast");
} }
} }

View File

@ -748,13 +748,14 @@ function closeColorPicker(accept)
var newColor = $("#mycolorpickerpreview").css("background-color"); var newColor = $("#mycolorpickerpreview").css("background-color");
var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
// parts now should be ["rgb(0, 70, 255", "0", "70", "255"] // parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
delete (parts[0]); if (parts) {
for (var i = 1; i <= 3; ++i) { delete (parts[0]);
parts[i] = parseInt(parts[i]).toString(16); for (var i = 1; i <= 3; ++i) {
if (parts[i].length == 1) parts[i] = '0' + parts[i]; parts[i] = parseInt(parts[i]).toString(16);
if (parts[i].length == 1) parts[i] = '0' + parts[i];
}
var newColor = "#" +parts.join(''); // "0070ff"
} }
var newColor = "#" +parts.join(''); // "0070ff"
myUserInfo.colorId = newColor; myUserInfo.colorId = newColor;
pad.notifyChangeColor(newColor); pad.notifyChangeColor(newColor);
paduserlist.renderMyUserInfo(); paduserlist.renderMyUserInfo();

View File

@ -4,27 +4,63 @@ var _;
/* FIXME: Ugly hack, in the future, use same code for server & client */ /* FIXME: Ugly hack, in the future, use same code for server & client */
if (plugins.isClient) { if (plugins.isClient) {
var async = require("ep_etherpad-lite/static/js/pluginfw/async"); var async = require("ep_etherpad-lite/static/js/pluginfw/async");
_ = require("ep_etherpad-lite/static/js/underscore"); var _ = require("ep_etherpad-lite/static/js/underscore");
} else { } else {
var async = require("async"); var async = require("async");
_ = require("underscore"); var _ = require("underscore");
} }
exports.bubbleExceptions = true exports.bubbleExceptions = true
var hookCallWrapper = function (hook, hook_name, args, cb) { var hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; }; if (cb === undefined) cb = function (x) { return x; };
// Normalize output to list for both sync and async cases
var normalize = function(x) {
if (x == undefined) return [];
return x;
}
var normalizedhook = function () {
return normalize(hook.hook_fn(hook_name, args, function (x) {
return cb(normalize(x));
}));
}
if (exports.bubbleExceptions) { if (exports.bubbleExceptions) {
return hook.hook_fn(hook_name, args, cb); return normalizedhook();
} else { } else {
try { try {
return hook.hook_fn(hook_name, args, cb); return normalizedhook();
} catch (ex) { } catch (ex) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]); console.error([hook_name, hook.part.full_name, ex.stack || ex]);
} }
} }
} }
exports.syncMapFirst = function (lst, fn) {
var i;
var result;
for (i = 0; i < lst.length; i++) {
result = fn(lst[i])
if (result.length) return result;
}
return undefined;
}
exports.mapFirst = function (lst, fn, cb) {
var i = 0;
next = function () {
if (i >= lst.length) return cb(undefined);
fn(lst[i++], function (err, result) {
if (err) return cb(err);
if (result.length) return cb(null, result);
next();
});
}
next();
}
/* Don't use Array.concat as it flatterns arrays within the array */ /* Don't use Array.concat as it flatterns arrays within the array */
exports.flatten = function (lst) { exports.flatten = function (lst) {
@ -44,9 +80,9 @@ exports.flatten = function (lst) {
exports.callAll = function (hook_name, args) { exports.callAll = function (hook_name, args) {
if (!args) args = {}; if (!args) args = {};
if (plugins.hooks[hook_name] === undefined) return []; if (plugins.hooks[hook_name] === undefined) return [];
return exports.flatten(_.map(plugins.hooks[hook_name], function (hook) { return _.flatten(_.map(plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args); return hookCallWrapper(hook, hook_name, args);
})); }), true);
} }
exports.aCallAll = function (hook_name, args, cb) { exports.aCallAll = function (hook_name, args, cb) {
@ -59,7 +95,7 @@ exports.aCallAll = function (hook_name, args, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); }); hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
}, },
function (err, res) { function (err, res) {
cb(null, exports.flatten(res)); cb(null, _.flatten(res, true));
} }
); );
} }
@ -67,14 +103,22 @@ exports.aCallAll = function (hook_name, args, cb) {
exports.callFirst = function (hook_name, args) { exports.callFirst = function (hook_name, args) {
if (!args) args = {}; if (!args) args = {};
if (plugins.hooks[hook_name][0] === undefined) return []; if (plugins.hooks[hook_name][0] === undefined) return [];
return exports.flatten(hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args)); return exports.syncMapFirst(plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args);
});
} }
exports.aCallFirst = function (hook_name, args, cb) { exports.aCallFirst = function (hook_name, args, cb) {
if (!args) args = {}; if (!args) args = {};
if (!cb) cb = function () {}; if (!cb) cb = function () {};
if (plugins.hooks[hook_name][0] === undefined) return cb(null, []); if (plugins.hooks[hook_name] === undefined) return cb(null, []);
hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(null, exports.flatten(res)); }); exports.mapFirst(
plugins.hooks[hook_name],
function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
cb
);
} }
exports.callAllStr = function(hook_name, args, sep, pre, post) { exports.callAllStr = function(hook_name, args, sep, pre, post) {

View File

@ -55,19 +55,41 @@ exports.install = function(plugin_name, cb) {
); );
}; };
exports.search = function(pattern, cb) { exports.searchCache = null;
exports.search = function(query, cache, cb) {
withNpm( withNpm(
function (cb) { function (cb) {
registry.get( var getData = function (cb) {
"/-/all", null, 600, false, true, if (cache && exports.searchCache) {
cb(null, exports.searchCache);
} else {
registry.get(
"/-/all", null, 600, false, true,
function (er, data) {
if (er) return cb(er);
exports.searchCache = data;
cb(er, data);
}
);
}
}
getData(
function (er, data) { function (er, data) {
if (er) return cb(er); if (er) return cb(er);
var res = {}; var res = {};
var i = 0;
for (key in data) { for (key in data) {
if (key.indexOf(plugins.prefix) == 0 && key.indexOf(pattern) != -1) if ( key.indexOf(plugins.prefix) == 0
res[key] = data[key]; && key.indexOf(query.pattern) != -1) {
i++;
if (i > query.offset
&& i <= query.offset + query.limit) {
res[key] = data[key];
}
}
} }
cb(null, {results:res}); cb(null, {results:res, query: query, total:i});
} }
); );
}, },

View File

@ -24,6 +24,7 @@ exports.loaded = false;
exports.plugins = {}; exports.plugins = {};
exports.parts = []; exports.parts = [];
exports.hooks = {}; exports.hooks = {};
exports.baseURL = '';
exports.ensure = function (cb) { exports.ensure = function (cb) {
if (!exports.loaded) if (!exports.loaded)
@ -61,7 +62,7 @@ exports.loadFn = function (path, hookName) {
return fn; return fn;
}; };
exports.extractHooks = function (parts, hook_set_name) { exports.extractHooks = function (parts, hook_set_name, plugins) {
var hooks = {}; var hooks = {};
_.each(parts,function (part) { _.each(parts,function (part) {
_.chain(part[hook_set_name] || {}) _.chain(part[hook_set_name] || {})
@ -69,14 +70,27 @@ exports.extractHooks = function (parts, hook_set_name) {
.each(function (hook_name) { .each(function (hook_name) {
if (hooks[hook_name] === undefined) hooks[hook_name] = []; if (hooks[hook_name] === undefined) hooks[hook_name] = [];
var hook_fn_name = part[hook_set_name][hook_name]; var hook_fn_name = part[hook_set_name][hook_name];
var hook_fn = exports.loadFn(hook_fn_name, hook_name);
/* On the server side, you can't just
* require("pluginname/whatever") if the plugin is installed as
* a dependency of another plugin! Bah, pesky little details of
* npm... */
if (!exports.isClient) {
hook_fn_name = path.normalize(path.join(path.dirname(exports.plugins[part.plugin].package.path), hook_fn_name));
}
try {
var hook_fn = exports.loadFn(hook_fn_name, hook_name);
if (!hook_fn) {
throw "Not a function";
}
} catch (exc) {
console.error("Failed to load '" + hook_fn_name + "' for '" + part.full_name + "/" + hook_set_name + "/" + hook_name + "': " + exc.toString())
}
if (hook_fn) { if (hook_fn) {
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part}); 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]);
}
}); });
}); });
return hooks; return hooks;
@ -90,7 +104,7 @@ if (exports.isClient) {
// which appears to fix the issue. // which appears to fix the issue.
var callback = function () {setTimeout(cb, 0);}; var callback = function () {setTimeout(cb, 0);};
jQuery.getJSON('/pluginfw/plugin-definitions.json', function(data) { jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json', function(data) {
exports.plugins = data.plugins; exports.plugins = data.plugins;
exports.parts = data.parts; exports.parts = data.parts;
exports.hooks = exports.extractHooks(exports.parts, "client_hooks"); exports.hooks = exports.extractHooks(exports.parts, "client_hooks");
@ -139,7 +153,7 @@ exports.update = function (cb) {
if (err) cb(err); if (err) cb(err);
exports.plugins = plugins; exports.plugins = plugins;
exports.parts = exports.sortParts(parts); exports.parts = exports.sortParts(parts);
exports.hooks = exports.extractHooks(exports.parts, "hooks"); exports.hooks = exports.extractHooks(exports.parts, "hooks");
exports.loaded = true; exports.loaded = true;
exports.callInit(cb); exports.callInit(cb);
} }

View File

@ -60,8 +60,8 @@ function init() {
//create the url //create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/"; var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are //find out in which subfolder we are
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io"; var resource = exports.baseURL.substring(1) + 'socket.io';
//build up the socket io connection //build up the socket io connection
socket = io.connect(url, {resource: resource}); socket = io.connect(url, {resource: resource});
@ -153,4 +153,5 @@ function handleClientVars(message)
} }
} }
exports.baseURL = '';
exports.init = init; exports.init = init;

View File

@ -1,106 +1,15 @@
<html> <html>
<head> <head>
<title>Plugin manager</title> <title>Plugin manager</title>
<link href="../../static/css/admin.css" rel="stylesheet" type="text/css" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<script src="../../static/js/jquery.js"></script> <link rel="stylesheet" href="../static/css/admin.css">
<script src="../../socket.io/socket.io.js"></script> <script src="../static/js/jquery.js"></script>
<script> <script src="../socket.io/socket.io.js"></script>
$(document).ready(function () { <script src="../static/js/admin/plugins.js"></script>
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> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
<h1>Etherpad Lite</h1>
<div class="seperator"></div>
<% if (errors.length) { %> <% if (errors.length) { %>
<div class="errors"> <div class="errors">
<% errors.forEach(function (item) { %> <% errors.forEach(function (item) { %>
@ -110,6 +19,8 @@
<% } %> <% } %>
<h1>Etherpad Lite</h1>
<div class="seperator"></div>
<h2>Installed plugins</h2> <h2>Installed plugins</h2>
<table> <table>
<thead> <thead>
@ -131,32 +42,38 @@
<tbody id="installed-plugins"> <tbody id="installed-plugins">
</tbody> </tbody>
</table> </table>
<div class="seperator"></div>
<h2>Search for plugins to install</h2> <div class="paged listing search-results">
<form> <div class="seperator"></div>
<input type="text" name="search" placeholder="Search term" id="search-query"> <h2>Search for plugins to install</h2>
<input type="button" value="Search" id="do-search"> <form>
</form> <input type="text" name="search" placeholder="Search term" id="search-query">
<table> <input type="button" value="Search" id="do-search">
<thead> </form>
<tr> <table>
<th>Name</th> <thead>
<th>Description</th> <tr>
<td></td> <th>Name</th>
</tr> <th>Description</th>
</thead> <td></td>
<tbody class="template"> </tr>
<tr id="search-result-template"> </thead>
<td class="name"></td> <tbody class="template">
<td class="description"></td> <tr>
<td class="actions"> <td class="name"></td>
<input type="button" value="Install" class="do-install"> <td class="description"></td>
</td> <td class="actions">
</tr> <input type="button" value="Install" class="do-install">
</tbody> </td>
<tbody id="search-results"> </tr>
</tbody> </tbody>
</table> <tbody class="results">
</tbody>
</table>
<input type="button" value="<<" class="do-prev-page">
<span class="offset"></span>..<span class="limit"></span> of <span class="total"></span>.
<input type="button" value=">>" class="do-next-page">
</div>
<div id="progress" class="dialog"> <div id="progress" class="dialog">
<h1 class="title"> <h1 class="title">
@ -167,4 +84,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -11,9 +11,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<% e.begin_block("styles"); %> <% e.begin_block("styles"); %>
<link href="../static/css/pad.css" rel="stylesheet"> <link href="../static/css/pad.css" rel="stylesheet">
<link href="../static/custom/pad.css" rel="stylesheet"> <link href="../static/custom/pad.css" rel="stylesheet">
<style title="dynamicsyntax"></style> <style title="dynamicsyntax"></style>
<% e.end_block(); %> <% e.end_block(); %>
<!-- head and body had been removed intentionally --> <!-- head and body had been removed intentionally -->
@ -21,70 +21,68 @@
<% e.begin_block("body"); %> <% e.begin_block("body"); %>
<div id="editbar" class="toolbar"> <div id="editbar" class="toolbar">
<ul class="menu_left"> <ul class="menu_left">
<% e.begin_block("editbarMenuLeft"); %> <% e.begin_block("editbarMenuLeft"); %>
<li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false"> <li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a> <a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
</li> </li>
<li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;"> <li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a> <a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
</li> </li>
<li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" > <li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a> <a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
</li> </li>
<li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;"> <li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a> <a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;"> <li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a> <a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
</li> </li>
<li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;"> <li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a> <a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
</li> </li>
<li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;"> <li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
<a class="buttonicon buttonicon-indent" title="Indent"></a> <a class="buttonicon buttonicon-indent" title="Indent"></a>
</li> </li>
<li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;"> <li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
<a class="buttonicon buttonicon-outdent" title="Unindent"></a> <a class="buttonicon buttonicon-outdent" title="Unindent"></a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;"> <li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a> <a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
</li> </li>
<li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;"> <li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
<a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a> <a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
</li> </li>
<li class="separator"></li> <li class="separator"></li>
<li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;"> <li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
<a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a> <a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
</li> </li>
<% e.end_block(); %> <% e.end_block(); %>
</ul> </ul>
<ul class="menu_right"> <ul class="menu_right">
<% e.begin_block("editbarMenuRight"); %> <% e.begin_block("editbarMenuRight"); %>
<li onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;"> <li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
<a id="settingslink" title="Mark this revision as a saved revision"> <a class="buttonicon buttonicon-settings" title="Settings of this pad"></a>
<div class="buttonicon buttonicon-savedRevision"></div> </li>
</a> <li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
</li> <a class="buttonicon buttonicon-import_export" title="Import/Export from/to different document formats"></a>
<li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;"> </li>
<a class="buttonicon buttonicon-settings" id="settingslink" title="Settings of this pad"></a> <li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;">
</li> <a class="buttonicon buttonicon-embed" title="Share and Embed this pad"></a>
<li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;"> </li>
<a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a> <li id="revisionlink" onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;">
</li> <a class="buttonicon buttonicon-savedRevision" title="Mark this revision as a saved revision"></a>
<li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;" > </li>
<a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a> <li class="separator"></li>
</li> <li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
<li class="separator"></li> <a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'"> </li>
<a class="buttonicon buttonicon-history" title="Show the history of this pad"></a> <li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
</li> <span class="buttonicon buttonicon-showusers" id="usericonback"></span>
<li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users"> <span id="online_count">1</span>
<span class="buttonicon buttonicon-showusers" id="usericonback"></span> </li>
<span id="online_count">1</span> <% e.end_block(); %>
</li>
<% e.end_block(); %>
</ul> </ul>
</div> </div>
@ -96,17 +94,17 @@
<button id="mycolorpickersave">Save</button> <button id="mycolorpickersave">Save</button>
<button id="mycolorpickercancel">Cancel</button> <button id="mycolorpickercancel">Cancel</button>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span> <span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
</div> </div>
<div id="myswatchbox"><div id="myswatch"></div></div> <div id="myswatchbox"><div id="myswatch"></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div> <div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div>
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div> <div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
</div> </div>
<div id="otherusers"> <div id="otherusers">
<div id="guestprompts"></div> <div id="guestprompts"></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0"> <table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr><td></td></tr> <tr><td></td></tr>
</table> </table>
<div id="nootherusers"></div> <div id="nootherusers"></div>
</div> </div>
<div id="userlistbuttonarea"></div> <div id="userlistbuttonarea"></div>
</div> </div>
@ -116,7 +114,7 @@
<div id="editorloadingbox">Loading...</div> <div id="editorloadingbox">Loading...</div>
</div> </div>
<div id="settingsmenu" class="popup"> <div id="settings" class="popup">
<h1>Pad settings</h1> <h1>Pad settings</h1>
<div class="column"> <div class="column">
<% e.begin_block("mySettings"); %> <% e.begin_block("mySettings"); %>
@ -156,20 +154,20 @@
<% e.begin_block("importColumn"); %> <% e.begin_block("importColumn"); %>
<h2>Import from text file, HTML, PDF, Word, ODT or RTF</h2><br> <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"> <form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv"> <div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="15" id="importfileinput"> <input type="file" name="file" size="15" id="importfileinput">
<div class="importmessage" id="importmessagefail"></div> <div class="importmessage" id="importmessagefail"></div>
</div> </div>
<div id="import"></div> <div id="import"></div>
<div class="importmessage" id="importmessagesuccess">Successful!</div> <div class="importmessage" id="importmessagesuccess">Successful!</div>
<div class="importformdiv" id="importformsubmitdiv"> <div class="importformdiv" id="importformsubmitdiv">
<input type="hidden" name="padId" value="blpmaXT35R"> <input type="hidden" name="padId" value="blpmaXT35R">
<span class="nowrap"> <span class="nowrap">
<input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput"> <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="importstatusball" src="../static/img/loading.gif" align="top">
<img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top"> <img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
</span> </span>
</div> </div>
</form> </form>
<% e.end_block(); %> <% e.end_block(); %>
</div> </div>
@ -234,80 +232,90 @@
<div id="modaloverlay"> <div id="modaloverlay">
<div id="modaloverlay-inner"></div> <div id="modaloverlay-inner"></div>
</div> </div>
<div id="mainmodals"> <div id="mainmodals">
<% e.begin_block("modals"); %> <% e.begin_block("modals"); %>
<div id="connectionbox" class="modaldialog"> <div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner"> <div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">Connecting...</div> <div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div> <div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected"> <div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2> <h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2> <h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2> <h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping"> <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> <p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div> </div>
<div id="disconnected_initsocketfail"> <div 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> <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>
<div id="disconnected_userdup"> <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> <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>
<div id="disconnected_unknown"> <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> <p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div> </div>
<div id="disconnected_slowcommit"> <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> <p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div> </div>
<div id="disconnected_unauth"> <div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p> <p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div> </div>
<div id="disconnected_deleted"> <div id="disconnected_deleted">
<p>This pad was deleted.</p> <p>This pad was deleted.</p>
</div> </div>
<div id="reconnect_advise"> <div id="reconnect_advise">
<p>If this continues to happen, please let us know</p> <p>If this continues to happen, please let us know</p>
</div> </div>
<div id="reconnect_form"> <div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button> <button id="forcereconnect">Reconnect Now</button>
</div>
</div> </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>
<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(); %> <% e.end_block(); %>
</div> </div>
<% e.end_block(); %> <% e.end_block(); %>
<% e.begin_block("scripts"); %> <% e.begin_block("scripts"); %>
<script type="text/javascript" src="../static/js/require-kernel.js"></script> <script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../static/js/jquery.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="../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" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
<script type="text/javascript"> <script type="text/javascript">
document.domain = document.domain; document.domain = document.domain;
var clientVars = {}; var clientVars = {};
(function () { (function () {
require.setRootURI("../javascripts/src");
require.setLibraryURI("../javascripts/lib");
require.setGlobalKeyPath("require"); var pathComponents = location.pathname.split('/');
// Strip 'p' and the padname from the pathname and set as baseURL
var baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/';
require.setRootURI(baseURL + "javascripts/src");
require.setLibraryURI(baseURL + "javascripts/lib");
require.setGlobalKeyPath("require");
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
plugins.update(function () { plugins.baseURL = baseURL;
require('ep_etherpad-lite/static/js/pad').init(); plugins.update(function () {
}); var pad = require('ep_etherpad-lite/static/js/pad');
pad.baseURL = baseURL;
pad.init();
});
/* TODO: These globals shouldn't exist. */ /* TODO: These globals shouldn't exist. */
pad = require('ep_etherpad-lite/static/js/pad').pad; pad = require('ep_etherpad-lite/static/js/pad').pad;
chat = require('ep_etherpad-lite/static/js/chat').chat; chat = require('ep_etherpad-lite/static/js/chat').chat;
padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;
padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp; padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp;
}()); }());
</script> </script>
<% e.end_block(); %> <% e.end_block(); %>
</html> </html>

View File

@ -123,22 +123,35 @@
</div> </div>
</div> </div>
<script type="text/javascript" src="../../../static/js/require-kernel.js"></script> <script type="text/javascript" src="../../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../../../static/js/jquery.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="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../../javascripts/lib/ep_etherpad-lite/static/js/timeslider.js?callback=require.define"></script> <script type="text/javascript" src="../../javascripts/lib/ep_etherpad-lite/static/js/timeslider.js?callback=require.define"></script>
<script type="text/javascript" src="../../../static/custom/timeslider.js"></script> <script type="text/javascript" src="../../static/custom/timeslider.js"></script>
<script type="text/javascript" > <script type="text/javascript" >
document.domain = document.domain; document.domain = document.domain;
var clientVars = {}; var clientVars = {};
(function () { (function () {
require.setRootURI("../../../javascripts/src");
require.setLibraryURI("../../../javascripts/lib"); var pathComponents = location.pathname.split('/');
// Strip 'p', the padname and 'timeslider' from the pathname and set as baseURL
var baseURL = pathComponents.slice(0,pathComponents.length-3).join('/') + '/';
require.setRootURI(baseURL + "javascripts/src");
require.setLibraryURI(baseURL + "javascripts/lib");
require.setGlobalKeyPath("require"); require.setGlobalKeyPath("require");
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
plugins.baseURL = baseURL;
plugins.update(function () { plugins.update(function () {
require('ep_etherpad-lite/static/js/timeslider').init(); var timeslider = require('ep_etherpad-lite/static/js/timeslider')
timeslider.baseURL = baseURL;
timeslider.init();
/* TODO: These globals shouldn't exist. */ /* TODO: These globals shouldn't exist. */
padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;