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,
/* 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 */
"maxAge" : 21600, // 6 hours
may cause problems during deployment. Set to 0 to disable caching */
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/
"abiword" : null,
/* This setting is used if you need http basic auth */
// "httpAuth" : "user:pass",
/* This setting is used if you require authentication of all users.
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*/
// "adminHttpAuth" : "user:pass",
/* Require authorization by a module, or a user with is_admin set, see below. */
"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 */
"loglevel": "INFO",
/* cache 6 hours = 1000*60*60*6 */
"maxAge": 21600000
"loglevel": "INFO"
}

View File

@ -23,6 +23,7 @@ var ejs = require("ejs");
var fs = require("fs");
var path = require("path");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var resolve = require("resolve");
exports.info = {
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.require = function (name, args) {
exports.require = function (name, args, mod) {
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 basedir = __dirname;
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.require = require;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -167,6 +167,12 @@ require.setGlobalKeyPath("require");\n\
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT);
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) {

View File

@ -4635,7 +4635,7 @@ function Ace2Inner(){
function setClassPresence(elem, className, present)
{
if (present) $(elem).addClass(className);
else $(elem).removeClass(elem, className);
else $(elem).removeClass(className);
}
function setup()
@ -5401,7 +5401,9 @@ function Ace2Inner(){
// Init documentAttributeManager
documentAttributeManager = new AttributeManager(rep, performDocumentApplyChangeset);
editorInfo.ace_performDocumentApplyAttributesToRange = documentAttributeManager.setAttributesOnRange;
editorInfo.ace_performDocumentApplyAttributesToRange = function () {
return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments);
};
$(document).ready(function(){
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

@ -161,12 +161,9 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
// just take over the whole slider screen with a reconnect message
function showReconnectUI()
{
if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
{
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
}
$('#error').show();
}
@ -216,7 +213,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
authorsList.append(' (');
_.each(colorsAnonymous, function(color, i){
if( i > 0 ) authorsList.append(' ');
$('<span /> ')
$('<span>&nbsp;</span>')
.css('background-color', color)
.addClass('author author-anonymous')
.appendTo(authorsList);
@ -288,8 +285,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
disableSelection($("#playpause_button")[0]);
disableSelection($("#timeslider")[0]);
if (clientVars.sliderEnabled && clientVars.supportsSlider)
{
$(document).keyup(function(e)
{
var code = -1;
@ -334,7 +329,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
else if (code == 32) playpause();
});
}
$(window).resize(function()
{
@ -486,36 +480,15 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
$("#revision").css('top', "20px");
}
if (clientVars.sliderEnabled)
{
if (clientVars.supportsSlider)
{
$("#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;
}
if (lineClass !== null) result.node.className = lineClass;
hooks.callAll("acePostWriteDomLineHTML", {
node: result.node
});
}
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;

View File

@ -203,7 +203,7 @@ function handshake()
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//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
socket = pad.socket = io.connect(url, {
resource: resource,
@ -1029,7 +1029,7 @@ var settings = {
};
pad.settings = settings;
exports.baseURL = '';
exports.settings = settings;
exports.createCookie = createCookie;
exports.readCookie = readCookie;

View File

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

View File

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

View File

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

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

View File

@ -24,6 +24,7 @@ exports.loaded = false;
exports.plugins = {};
exports.parts = [];
exports.hooks = {};
exports.baseURL = '';
exports.ensure = function (cb) {
if (!exports.loaded)
@ -61,7 +62,7 @@ exports.loadFn = function (path, hookName) {
return fn;
};
exports.extractHooks = function (parts, hook_set_name) {
exports.extractHooks = function (parts, hook_set_name, plugins) {
var hooks = {};
_.each(parts,function (part) {
_.chain(part[hook_set_name] || {})
@ -69,13 +70,26 @@ exports.extractHooks = function (parts, hook_set_name) {
.each(function (hook_name) {
if (hooks[hook_name] === undefined) hooks[hook_name] = [];
var hook_fn_name = part[hook_set_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) {
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]);
}
});
});
@ -90,7 +104,7 @@ if (exports.isClient) {
// which appears to fix the issue.
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.parts = data.parts;
exports.hooks = exports.extractHooks(exports.parts, "client_hooks");

View File

@ -60,7 +60,7 @@ function init() {
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//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
socket = io.connect(url, {resource: resource});
@ -153,4 +153,5 @@ function handleClientVars(message)
}
}
exports.baseURL = '';
exports.init = init;

View File

@ -1,106 +1,15 @@
<html>
<head>
<title>Plugin manager</title>
<link href="../../static/css/admin.css" rel="stylesheet" type="text/css" />
<script src="../../static/js/jquery.js"></script>
<script src="../../socket.io/socket.io.js"></script>
<script>
$(document).ready(function () {
var socket = io.connect().of("/pluginfw/installer");
var doUpdate = false;
function updateHandlers() {
$("#progress.dialog .close").unbind('click').click(function () {
$("#progress.dialog").hide();
});
$("#do-search").unbind('click').click(function () {
if ($("#search-query")[0].value != "")
socket.emit("search", $("#search-query")[0].value);
});
$(".do-install").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("install", row.find(".name").html());
});
$(".do-uninstall").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("uninstall", row.find(".name").html());
});
}
updateHandlers();
socket.on('progress', function (data) {
$("#progress.dialog .close").hide();
$("#progress.dialog").show();
var message = "Unknown status";
if (data.message) {
message = "<span class='status'>" + data.message.toString() + "</span>";
}
if (data.error) {
message = "<span class='error'>" + data.error.toString() + "<span>";
}
$("#progress.dialog .message").html(message);
$("#progress.dialog .history").append("<div>" + message + "</div>");
if (data.progress >= 1) {
if (data.error) {
$("#progress.dialog .close").show();
} else {
if (doUpdate) {
doUpdate = false;
socket.emit("load");
}
$("#progress.dialog").hide();
}
}
});
socket.on('search-result', function (data) {
$("#search-results *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#search-result-template").clone();
for (attr in plugin) {
row.find("." + attr).html(plugin[attr]);
}
$("#search-results").append(row);
}
updateHandlers();
});
socket.on('installed-results', function (data) {
$("#installed-plugins *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone();
for (attr in plugin.package) {
row.find("." + attr).html(plugin.package[attr]);
}
$("#installed-plugins").append(row);
}
updateHandlers();
});
socket.emit("load");
});
</script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<link rel="stylesheet" href="../static/css/admin.css">
<script src="../static/js/jquery.js"></script>
<script src="../socket.io/socket.io.js"></script>
<script src="../static/js/admin/plugins.js"></script>
</head>
<body>
<div id="wrapper">
<h1>Etherpad Lite</h1>
<div class="seperator"></div>
<% if (errors.length) { %>
<div class="errors">
<% errors.forEach(function (item) { %>
@ -110,6 +19,8 @@
<% } %>
<h1>Etherpad Lite</h1>
<div class="seperator"></div>
<h2>Installed plugins</h2>
<table>
<thead>
@ -131,6 +42,8 @@
<tbody id="installed-plugins">
</tbody>
</table>
<div class="paged listing search-results">
<div class="seperator"></div>
<h2>Search for plugins to install</h2>
<form>
@ -146,7 +59,7 @@
</tr>
</thead>
<tbody class="template">
<tr id="search-result-template">
<tr>
<td class="name"></td>
<td class="description"></td>
<td class="actions">
@ -154,9 +67,13 @@
</td>
</tr>
</tbody>
<tbody id="search-results">
<tbody class="results">
</tbody>
</table>
<input type="button" value="<<" class="do-prev-page">
<span class="offset"></span>..<span class="limit"></span> of <span class="total"></span>.
<input type="button" value=">>" class="do-next-page">
</div>
<div id="progress" class="dialog">
<h1 class="title">

View File

@ -62,19 +62,17 @@
</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>
<a class="buttonicon buttonicon-settings" title="Settings of this pad"></a>
</li>
<li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
<a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a>
<a class="buttonicon buttonicon-import_export" title="Import/Export from/to different document formats"></a>
</li>
<li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;">
<a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a>
<a class="buttonicon buttonicon-embed" title="Share and Embed this pad"></a>
</li>
<li id="revisionlink" onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;">
<a class="buttonicon buttonicon-savedRevision" title="Mark this revision as a saved revision"></a>
</li>
<li class="separator"></li>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
@ -116,7 +114,7 @@
<div id="editorloadingbox">Loading...</div>
</div>
<div id="settingsmenu" class="popup">
<div id="settings" class="popup">
<h1>Pad settings</h1>
<div class="column">
<% e.begin_block("mySettings"); %>
@ -293,13 +291,23 @@
document.domain = document.domain;
var clientVars = {};
(function () {
require.setRootURI("../javascripts/src");
require.setLibraryURI("../javascripts/lib");
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');
plugins.baseURL = baseURL;
plugins.update(function () {
require('ep_etherpad-lite/static/js/pad').init();
var pad = require('ep_etherpad-lite/static/js/pad');
pad.baseURL = baseURL;
pad.init();
});
/* TODO: These globals shouldn't exist. */

View File

@ -123,22 +123,35 @@
</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/timeslider.js?callback=require.define"></script>
<script type="text/javascript" src="../../../static/custom/timeslider.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="../../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="../../static/custom/timeslider.js"></script>
<script type="text/javascript" >
document.domain = document.domain;
var clientVars = {};
(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");
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
plugins.baseURL = baseURL;
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. */
padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;