Merge branch 'develop'

This commit is contained in:
Peter 'Pita' Martischka 2012-05-29 21:48:48 +01:00
commit 96877bc2b6
155 changed files with 16175 additions and 4845 deletions

7
.gitignore vendored
View File

@ -1,7 +1,5 @@
node_modules node_modules
settings.json settings.json
static/js/jquery.js
static/js/prefixfree.js
APIKEY.txt APIKEY.txt
bin/abiword.exe bin/abiword.exe
bin/node.exe bin/node.exe
@ -10,4 +8,7 @@ var/dirty.db
bin/convertSettings.json bin/convertSettings.json
*~ *~
*.patch *.patch
*.DS_Store src/static/js/jquery.js
npm-debug.log
*.DS_Store
.ep_initialized

16
README.plugins Normal file
View File

@ -0,0 +1,16 @@
So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json
require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded
A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name
That is the basis.
Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored).
This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order
Ordering between plugins is undefined, only parts are ordered.
A plugin usually has one part, but it van have multiple.
This is so that it can insert some hook registration before that of another plugin, and another one after.
This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps
So, that's basically it... apart from client-side hooks
which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client...
One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json...
was that clear?

View File

@ -0,0 +1,7 @@
.git*
docs/
examples/
support/
test/
testing.js
.DS_Store

View File

@ -0,0 +1,36 @@
{
"parts": [
{
"name": "somepart",
"pre": [],
"post": ["ep_onemoreplugin/partone"]
},
{
"name": "partlast",
"pre": ["ep_fintest/otherpart"],
"post": [],
"hooks": {
"somehookname": "ep_fintest/partlast:somehook"
}
},
{
"name": "partfirst",
"pre": [],
"post": ["ep_onemoreplugin/somepart"]
},
{
"name": "otherpart",
"pre": ["ep_fintest/somepart", "ep_otherplugin/main"],
"post": [],
"hooks": {
"somehookname": "ep_fintest/otherpart:somehook",
"morehook": "ep_fintest/otherpart:morehook",
"expressCreateServer": "ep_fintest/otherpart:expressServer",
"eejsBlock_editbarMenuLeft": "ep_fintest/otherpart:eejsBlock_editbarMenuLeft"
},
"client_hooks": {
"somehookname": "ep_fintest/static/js/test:bar"
}
}
]
}

View File

@ -0,0 +1,25 @@
test = require("ep_fintest/static/js/test.js");
console.log("FOOO:", test.foo);
exports.somehook = function (hook_name, args, cb) {
return cb(["otherpart:somehook was here"]);
}
exports.morehook = function (hook_name, args, cb) {
return cb(["otherpart:morehook was here"]);
}
exports.expressServer = function (hook_name, args, cb) {
args.app.get('/otherpart', function(req, res) {
res.send("<em>Abra cadabra</em>");
});
}
exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) {
args.content = args.content + '\
<li id="testButton" onClick="window.pad&amp;&amp;pad.editbarClick(\'clearauthorship\');return false;">\
<a class="buttonicon buttonicon-test" title="Test test test"></a>\
</li>\
';
return cb();
}

View File

@ -0,0 +1,9 @@
{
"name": "ep_fintest",
"description": "A test plugin",
"version": "0.0.1",
"author": "RedHog (Egil Moeller) <egil.moller@freecode.no>",
"contributors": [],
"dependencies": {},
"engines": { "node": ">= 0.4.1 < 0.7.0" }
}

View File

@ -0,0 +1,3 @@
exports.somehook = function (hook_name, args, cb) {
return cb(["partlast:somehook was here"]);
}

View File

@ -0,0 +1,5 @@
exports.foo = 42;
exports.bar = function (hook_name, args, cb) {
return cb(["FOOOO"]);
}

View File

@ -0,0 +1 @@
<em>Test bla bla</em>

View File

@ -4,19 +4,19 @@
if(process.argv.length != 3) if(process.argv.length != 3)
{ {
console.error("Use: node checkPad.js $PADID"); console.error("Use: node bin/checkPad.js $PADID");
process.exit(1); process.exit(1);
} }
//get the padID //get the padID
var padId = process.argv[2]; var padId = process.argv[2];
//initalize the database //initalize the database
var log4js = require("log4js"); var log4js = require("../src/node_modules/log4js");
log4js.setGlobalLogLevel("INFO"); log4js.setGlobalLogLevel("INFO");
var async = require("async"); var async = require("../src/node_modules/async");
var db = require('../node/db/DB'); var db = require('../src/node/db/DB');
var CommonCode = require('../node/utils/common_code');
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager; var padManager;
async.series([ async.series([
@ -28,7 +28,7 @@ async.series([
//get the pad //get the pad
function (callback) function (callback)
{ {
padManager = require('../node/db/PadManager'); padManager = require('../src/node/db/PadManager');
padManager.doesPadExists(padId, function(err, exists) padManager.doesPadExists(padId, function(err, exists)
{ {

View File

@ -1,12 +1,12 @@
var CommonCode = require('../node/utils/common_code');
var startTime = new Date().getTime(); var startTime = new Date().getTime();
var fs = require("fs"); var fs = require("fs");
var ueberDB = require("ueberDB"); var ueberDB = require("ueberDB");
var mysql = require("mysql"); var mysql = require("mysql");
var async = require("async"); var async = require("async");
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory"); var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var settingsFile = process.argv[2]; var settingsFile = process.argv[2];
var sqlOutputFile = process.argv[3]; var sqlOutputFile = process.argv[3];
@ -384,7 +384,7 @@ function convertPad(padId, callback)
} }
//generate the latest atext //generate the latest atext
var fullAPool = AttributePoolFactory.createAttributePool().fromJsonable(apool); var fullAPool = (new AttributePool()).fromJsonable(apool);
var keyRev = Math.floor(padmeta.head / padmeta.keyRevInterval) * padmeta.keyRevInterval; var keyRev = Math.floor(padmeta.head / padmeta.keyRevInterval) * padmeta.keyRevInterval;
var atext = changesetsMeta[keyRev].atext; var atext = changesetsMeta[keyRev].atext;
var curRev = keyRev; var curRev = keyRev;

View File

@ -22,8 +22,7 @@ node-inspector &
echo "If you are new to node-inspector, take a look at this video: http://youtu.be/AOnK3NVnxL8" echo "If you are new to node-inspector, take a look at this video: http://youtu.be/AOnK3NVnxL8"
cd "node" node --debug node_modules/ep_etherpad-lite/node/server.js $*
node --debug server.js
#kill node-inspector before ending #kill node-inspector before ending
kill $! kill $!

View File

@ -55,7 +55,13 @@ if [ ! -f $settings ]; then
fi fi
echo "Ensure that all dependencies are up to date..." echo "Ensure that all dependencies are up to date..."
npm install || { (
mkdir -p node_modules
cd node_modules
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
cd ep_etherpad-lite
npm install
) || {
rm -rf node_modules rm -rf node_modules
exit 1 exit 1
} }
@ -63,8 +69,8 @@ npm install || {
echo "Ensure jQuery is downloaded and up to date..." echo "Ensure jQuery is downloaded and up to date..."
DOWNLOAD_JQUERY="true" DOWNLOAD_JQUERY="true"
NEEDED_VERSION="1.7.1" NEEDED_VERSION="1.7.1"
if [ -f "static/js/jquery.js" ]; then if [ -f "src/static/js/jquery.js" ]; then
VERSION=$(cat static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?"); VERSION=$(cat src/static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");
if [ ${VERSION#v} = $NEEDED_VERSION ]; then if [ ${VERSION#v} = $NEEDED_VERSION ]; then
DOWNLOAD_JQUERY="false" DOWNLOAD_JQUERY="false"
@ -72,22 +78,7 @@ if [ -f "static/js/jquery.js" ]; then
fi fi
if [ $DOWNLOAD_JQUERY = "true" ]; then if [ $DOWNLOAD_JQUERY = "true" ]; then
curl -lo static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1 curl -lo src/static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
fi
echo "Ensure prefixfree is downloaded and up to date..."
DOWNLOAD_PREFIXFREE="true"
NEEDED_VERSION="1.0.4"
if [ -f "static/js/prefixfree.js" ]; then
VERSION=$(cat static/js/prefixfree.js | grep "PrefixFree" | grep -o "[0-9].[0-9].[0-9]");
if [ $VERSION = $NEEDED_VERSION ]; then
DOWNLOAD_PREFIXFREE="false"
fi
fi
if [ $DOWNLOAD_PREFIXFREE = "true" ]; then
curl -lo static/js/prefixfree.js -k https://raw.github.com/LeaVerou/prefixfree/master/prefixfree.js || exit 1
fi fi
#Remove all minified data to force node creating it new #Remove all minified data to force node creating it new
@ -98,12 +89,12 @@ echo "ensure custom css/js files are created..."
for f in "index" "pad" "timeslider" for f in "index" "pad" "timeslider"
do do
if [ ! -f "static/custom/$f.js" ]; then if [ ! -f "src/static/custom/$f.js" ]; then
cp -v "static/custom/js.template" "static/custom/$f.js" || exit 1 cp -v "src/static/custom/js.template" "src/static/custom/$f.js" || exit 1
fi fi
if [ ! -f "static/custom/$f.css" ]; then if [ ! -f "src/static/custom/$f.css" ]; then
cp -v "static/custom/css.template" "static/custom/$f.css" || exit 1 cp -v "src/static/custom/css.template" "src/static/custom/$f.css" || exit 1
fi fi
done done

View File

@ -25,5 +25,4 @@ bin/installDeps.sh $* || exit 1
#Move to the node folder and start #Move to the node folder and start
echo "start..." echo "start..."
cd "node" node node_modules/ep_etherpad-lite/node/server.js $*
node server.js $*

View File

@ -1,500 +0,0 @@
/**
* This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
* Static file Requests are answered directly from this module, Socket.IO messages are passed
* to MessageHandler and minfied requests are passed to minified.
*/
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var log4js = require('log4js');
var os = require("os");
var socketio = require('socket.io');
var fs = require('fs');
var settings = require('./utils/Settings');
var db = require('./db/DB');
var async = require('async');
var express = require('express');
var path = require('path');
var minify = require('./utils/Minify');
var CachingMiddleware = require('./utils/caching_middleware');
var Yajsml = require('yajsml');
var formidable = require('formidable');
var apiHandler;
var exportHandler;
var importHandler;
var exporthtml;
var readOnlyManager;
var padManager;
var securityManager;
var socketIORouter;
//try to get the git version
var version = "";
try
{
var rootPath = path.normalize(__dirname + "/../")
var ref = fs.readFileSync(rootPath + ".git/HEAD", "utf-8");
var refPath = rootPath + ".git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
}
catch(e)
{
console.warn("Can't get git version for server header\n" + e.message)
}
console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
exports.maxAge = settings.maxAge;
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
async.waterfall([
//initalize the database
function (callback)
{
db.init(callback);
},
//initalize the http server
function (callback)
{
//create server
var app = express.createServer();
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
app.param('pad', function (req, res, next, padId) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
}
else
{
padManager.sanitizePadId(padId, function(sanitizedPadId) {
//the pad id was sanitized, so we redirect to the sanitized version
if(sanitizedPadId != padId)
{
var real_path = req.path.replace(/^\/p\/[^\/]+/, './' + sanitizedPadId);
res.header('Location', real_path);
res.send('You should be redirected to <a href="' + real_path + '">' + real_path + '</a>', 302);
}
//the pad id was fine, so just render it
else
{
next();
}
});
}
});
//load modules that needs a initalized db
readOnlyManager = require("./db/ReadOnlyManager");
exporthtml = require("./utils/ExportHtml");
exportHandler = require('./handler/ExportHandler');
importHandler = require('./handler/ImportHandler');
apiHandler = require('./handler/APIHandler');
padManager = require('./db/PadManager');
securityManager = require('./db/SecurityManager');
socketIORouter = require("./handler/SocketIORouter");
//install logging
var httpLogger = log4js.getLogger("http");
app.configure(function()
{
// Activate http basic auth if it has been defined in settings.json
if(settings.httpAuth != null) app.use(basic_auth);
// 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"))
app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
app.use(express.cookieParser());
});
app.error(function(err, req, res, next){
res.send(500);
console.error(err.stack ? err.stack : err.toString());
gracefulShutdown();
});
// Cache both minified and static.
var assetCache = new CachingMiddleware;
app.all('/(minified|static)/*', assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc.
app.all('/static/:filename(*)', minify.minify);
// Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side.
var jsServer = new (Yajsml.Server)({
rootPath: 'minified/'
, rootURI: 'http://localhost:' + settings.port + '/static/js/'
});
var StaticAssociator = Yajsml.associators.StaticAssociator;
var associations =
Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations);
jsServer.setAssociator(associator);
app.use(jsServer);
//checks for padAccess
function hasPadAccess(req, res, callback)
{
securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj)
{
if(ERR(err, callback)) return;
//there is access, continue
if(accessObj.accessStatus == "grant")
{
callback();
}
//no access
else
{
res.send("403 - Can't touch this", 403);
}
});
}
//checks for basic http auth
function basic_auth (req, res, next) {
if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
// fetch login and password
if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() == settings.httpAuth) {
next();
return;
}
}
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);
}
}
//serve read only pad
app.get('/ro/:id', function(req, res)
{
var html;
var padId;
var pad;
async.series([
//translate the read only pad to a padId
function(callback)
{
readOnlyManager.getPadId(req.params.id, function(err, _padId)
{
if(ERR(err, callback)) return;
padId = _padId;
//we need that to tell hasPadAcess about the pad
req.params.pad = padId;
callback();
});
},
//render the html document
function(callback)
{
//return if the there is no padId
if(padId == null)
{
callback("notfound");
return;
}
hasPadAccess(req, res, function()
{
//render the html document
exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
{
if(ERR(err, callback)) return;
html = _html;
callback();
});
});
}
], function(err)
{
//throw any unexpected error
if(err && err != "notfound")
ERR(err);
if(err == "notfound")
res.send('404 - Not Found', 404);
else
res.send(html);
});
});
//serve pad.html under /p
app.get('/p/:pad', function(req, res, next)
{
var filePath = path.normalize(__dirname + "/../static/pad.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/timeslider', function(req, res, next)
{
var filePath = path.normalize(__dirname + "/../static/timeslider.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/:rev?/export/:type', function(req, res, next)
{
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
//send a 404 if we don't support this filetype
if(types.indexOf(req.params.type) == -1)
{
next();
return;
}
//if abiword is disabled, and this is a format we only support with abiword, output a message
if(settings.abiword == null &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
{
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
return;
}
res.header("Access-Control-Allow-Origin", "*");
hasPadAccess(req, res, function()
{
exportHandler.doExport(req, res, req.params.pad, req.params.type);
});
});
//handle import requests
app.post('/p/:pad/import', function(req, res, next)
{
//if abiword is disabled, skip handling this request
if(settings.abiword == null)
{
next();
return;
}
hasPadAccess(req, res, function()
{
importHandler.doImport(req, res, req.params.pad);
});
});
var apiLogger = log4js.getLogger("API");
//This is for making an api call, collecting all post information and passing it to the apiHandler
var apiCaller = function(req, res, fields)
{
res.header("Content-Type", "application/json; charset=utf-8");
apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
//wrap the send function so we can log the response
res._send = res.send;
res.send = function(response)
{
response = JSON.stringify(response);
apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
//is this a jsonp call, if yes, add the function call
if(req.query.jsonp)
response = req.query.jsonp + "(" + response + ")";
res._send(response);
}
//call the api handler
apiHandler.handle(req.params.func, fields, req, res);
}
//This is a api GET call, collect all post informations and pass it to the apiHandler
app.get('/api/1/:func', function(req, res)
{
apiCaller(req, res, req.query)
});
//This is a api POST call, collect all post informations and pass it to the apiHandler
app.post('/api/1/:func', function(req, res)
{
new formidable.IncomingForm().parse(req, function(err, fields, files)
{
apiCaller(req, res, fields)
});
});
//The Etherpad client side sends information about how a disconnect happen
app.post('/ep/pad/connection-diagnostic-info', function(req, res)
{
new formidable.IncomingForm().parse(req, function(err, fields, files)
{
console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
res.end("OK");
});
});
//The Etherpad client side sends information about client side javscript errors
app.post('/jserror', function(req, res)
{
new formidable.IncomingForm().parse(req, function(err, fields, files)
{
console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
res.end("OK");
});
});
//serve index.html under /
app.get('/', function(req, res)
{
var filePath = path.normalize(__dirname + "/../static/index.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve robots.txt
app.get('/robots.txt', function(req, res)
{
var filePath = path.normalize(__dirname + "/../static/robots.txt");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve favicon.ico
app.get('/favicon.ico', function(req, res)
{
var filePath = path.normalize(__dirname + "/../static/custom/favicon.ico");
res.sendfile(filePath, { maxAge: exports.maxAge }, function(err)
{
//there is no custom favicon, send the default favicon
if(err)
{
filePath = path.normalize(__dirname + "/../static/favicon.ico");
res.sendfile(filePath, { maxAge: exports.maxAge });
}
});
});
//let the server listen
app.listen(settings.port, settings.ip);
console.log("Server is listening at " + settings.ip + ":" + settings.port);
var onShutdown = false;
var gracefulShutdown = function(err)
{
if(err && err.stack)
{
console.error(err.stack);
}
else if(err)
{
console.error(err);
}
//ensure there is only one graceful shutdown running
if(onShutdown) return;
onShutdown = true;
console.log("graceful shutdown...");
//stop the http server
app.close();
//do the db shutdown
db.db.doShutdown(function()
{
console.log("db sucessfully closed.");
process.exit(0);
});
setTimeout(function(){
process.exit(1);
}, 3000);
}
//connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1)
{
//sigint is so far not working on windows
//https://github.com/joyent/node/issues/1553
process.on('SIGINT', gracefulShutdown);
}
process.on('uncaughtException', gracefulShutdown);
//init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(app);
//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']);
var socketIOLogger = log4js.getLogger("socket.io");
io.set('logger', {
debug: function (str)
{
socketIOLogger.debug.apply(socketIOLogger, arguments);
},
info: function (str)
{
socketIOLogger.info.apply(socketIOLogger, arguments);
},
warn: function (str)
{
socketIOLogger.warn.apply(socketIOLogger, arguments);
},
error: function (str)
{
socketIOLogger.error.apply(socketIOLogger, arguments);
},
});
//minify socket.io javascript
if(settings.minify)
io.enable('browser client minification');
var padMessageHandler = require("./handler/PadMessageHandler");
var timesliderMessageHandler = require("./handler/TimesliderMessageHandler");
//Initalize the Socket.IO Router
socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler);
socketIORouter.addComponent("timeslider", timesliderMessageHandler);
callback(null);
}
]);

View File

@ -13,7 +13,7 @@
"dbType" : "dirty", "dbType" : "dirty",
//the database specific settings //the database specific settings
"dbSettings" : { "dbSettings" : {
"filename" : "../var/dirty.db" "filename" : "var/dirty.db"
}, },
/* An Example of MySQL Configuration /* An Example of MySQL Configuration
@ -39,16 +39,35 @@
but makes it impossible to debug the javascript/css */ but makes it impossible to debug the javascript/css */
"minify" : true, "minify" : true,
/* How long may clients use served javascript code? Without versioning this /* How long may clients use served javascript code (in seconds)? Without versioning this
is may cause problems during deployment. */ may cause problems during deployment. Set to 0 to disable caching */
"maxAge" : 21600000, // 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,
/* 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 */ /* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO" "loglevel": "INFO"

View File

@ -12,7 +12,7 @@
"dbType" : "dirty", "dbType" : "dirty",
//the database specific settings //the database specific settings
"dbSettings" : { "dbSettings" : {
"filename" : "../var/dirty.db" "filename" : "var/dirty.db"
}, },
/* An Example of MySQL Configuration /* An Example of MySQL Configuration
@ -40,5 +40,9 @@
/* 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,
/* cache 6 hours = 1000*60*60*6 */
"maxAge": 21600000
} }

16
src/ep.json Normal file
View File

@ -0,0 +1,16 @@
{
"parts": [
{ "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } },
{ "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
{ "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },
{ "name": "padreadonly", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padreadonly:expressCreateServer" } },
{ "name": "webaccess", "hooks": { "expressConfigure": "ep_etherpad-lite/node/hooks/express/webaccess:expressConfigure" } },
{ "name": "apicalls", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/apicalls:expressCreateServer" } },
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
{ "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
{ "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
{ "name": "adminplugins", "hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins:socketio" } }
]
}

View File

@ -431,7 +431,7 @@ exports.setPassword = function(padID, password, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//set the password //set the password
pad.setPassword(password); pad.setPassword(password == "" ? null : password);
callback(); callback();
}); });

View File

@ -18,11 +18,11 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
/** /**
* Checks if the author exists * Checks if the author exists

View File

@ -18,10 +18,10 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var customError = require("../utils/customError"); var customError = require("../utils/customError");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var padManager = require("./PadManager"); var padManager = require("./PadManager");

View File

@ -2,11 +2,11 @@
* The pad object, defined with joose * The pad object, defined with joose
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory"); var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var settings = require('../utils/Settings'); var settings = require('../utils/Settings');
@ -15,6 +15,11 @@ var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler"); var padMessageHandler = require("../handler/PadMessageHandler");
var readOnlyManager = require("./ReadOnlyManager"); var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto"); var crypto = require("crypto");
var randomString = require("../utils/randomstring");
//serialization/deserialization attributes
var attributeBlackList = ["id"];
var jsonableList = ["pool"];
/** /**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
@ -28,13 +33,13 @@ exports.cleanText = function (txt) {
var Pad = function Pad(id) { var Pad = function Pad(id) {
this.atext = Changeset.makeAText("\n"); this.atext = Changeset.makeAText("\n");
this.pool = AttributePoolFactory.createAttributePool(); this.pool = new AttributePool();
this.head = -1; this.head = -1;
this.chatHead = -1; this.chatHead = -1;
this.publicStatus = false; this.publicStatus = false;
this.passwordHash = null; this.passwordHash = null;
this.id = id; this.id = id;
this.savedRevisions = [];
}; };
exports.Pad = Pad; exports.Pad = Pad;
@ -75,15 +80,28 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
newRevData.meta.atext = this.atext; newRevData.meta.atext = this.atext;
} }
db.set("pad:"+this.id+":revs:"+newRev, newRevData); db.set("pad:"+this.id+":revs:"+newRev, newRevData);
db.set("pad:"+this.id, {atext: this.atext, this.saveToDatabase();
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
publicStatus: this.publicStatus,
passwordHash: this.passwordHash});
}; };
//save all attributes to the database
Pad.prototype.saveToDatabase = function saveToDatabase(){
var dbObject = {};
for(var attr in this){
if(typeof this[attr] === "function") continue;
if(attributeBlackList.indexOf(attr) !== -1) continue;
dbObject[attr] = this[attr];
if(jsonableList.indexOf(attr) !== -1){
dbObject[attr] = dbObject[attr].toJsonable();
}
}
db.set("pad:"+this.id, dbObject);
}
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) { Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
}; };
@ -200,11 +218,10 @@ Pad.prototype.setText = function setText(newText) {
}; };
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) { Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++; this.chatHead++;
//save the chat entry in the database //save the chat entry in the database
db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time}); db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
//save the new chat head this.saveToDatabase();
db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
}; };
Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) { Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
@ -324,27 +341,14 @@ Pad.prototype.init = function init(text, callback) {
//if this pad exists, load it //if this pad exists, load it
if(value != null) if(value != null)
{ {
_this.head = value.head; //copy all attr. To a transfrom via fromJsonable if necassary
_this.atext = value.atext; for(var attr in value){
_this.pool = _this.pool.fromJsonable(value.pool); if(jsonableList.indexOf(attr) !== -1){
_this[attr] = _this[attr].fromJsonable(value[attr]);
//ensure we have a local chatHead variable } else {
if(value.chatHead != null) _this[attr] = value[attr];
_this.chatHead = value.chatHead; }
else }
_this.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
_this.publicStatus = value.publicStatus;
else
_this.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
_this.passwordHash = value.passwordHash;
else
_this.passwordHash = null;
} }
//this pad doesn't exist, so create it //this pad doesn't exist, so create it
else else
@ -452,12 +456,12 @@ Pad.prototype.remove = function remove(callback) {
//set in db //set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) { Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus; this.publicStatus = publicStatus;
db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus); this.saveToDatabase();
}; };
Pad.prototype.setPassword = function setPassword(password) { Pad.prototype.setPassword = function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt()); this.passwordHash = password == null ? null : hash(password, generateSalt());
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash); this.saveToDatabase();
}; };
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) { Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
@ -468,6 +472,31 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
return this.passwordHash != null; return this.passwordHash != null;
}; };
Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) {
//if this revision is already saved, return silently
for(var i in this.savedRevisions){
if(this.savedRevisions.revNum === revNum){
return;
}
}
//build the saved revision object
var savedRevision = {};
savedRevision.revNum = revNum;
savedRevision.savedById = savedById;
savedRevision.label = label || "Revision " + revNum;
savedRevision.timestamp = new Date().getTime();
savedRevision.id = randomString(10);
//save this new saved revision
this.savedRevisions.push(savedRevision);
this.saveToDatabase();
};
Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions;
};
/* Crypto helper methods */ /* Crypto helper methods */
function hash(password, salt) function hash(password, salt)

View File

@ -18,11 +18,11 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
/** /**
* returns a read only id for a pad * returns a read only id for a pad

View File

@ -18,7 +18,7 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
@ -26,7 +26,7 @@ var authorManager = require("./AuthorManager");
var padManager = require("./PadManager"); var padManager = require("./PadManager");
var sessionManager = require("./SessionManager"); var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings") var settings = require("../utils/Settings")
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
/** /**
* This function controlls the access to a pad, it checks if the user can access a pad. * This function controlls the access to a pad, it checks if the user can access a pad.

View File

@ -18,10 +18,10 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var customError = require("../utils/customError"); var customError = require("../utils/customError");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var groupMangager = require("./GroupManager"); var groupMangager = require("./GroupManager");

View File

@ -20,9 +20,9 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('./utils/common_code');
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory"); var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
function random() { function random() {
this.nextInt = function (maxValue) { this.nextInt = function (maxValue) {
@ -227,7 +227,7 @@ function runTests() {
return attribs; // it's already an attrib pool return attribs; // it's already an attrib pool
} else { } else {
// assume it's an array of attrib strings to be split and added // assume it's an array of attrib strings to be split and added
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
attribs.forEach(function (kv) { attribs.forEach(function (kv) {
p.putAttrib(kv.split(',')); p.putAttrib(kv.split(','));
}); });
@ -325,7 +325,7 @@ function runTests() {
runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], "Z:5>1|2=2+1$x", ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"], ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]); runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], "Z:5>1|2=2+1$x", ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"], ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]);
var testPoolWithChars = (function () { var testPoolWithChars = (function () {
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
p.putAttrib(['char', 'newline']); p.putAttrib(['char', 'newline']);
for (var i = 1; i < 36; i++) { for (var i = 1; i < 36; i++) {
p.putAttrib(['char', Changeset.numToString(i)]); p.putAttrib(['char', Changeset.numToString(i)]);
@ -560,7 +560,7 @@ function runTests() {
var rand = new random(); var rand = new random();
print("> testCompose#" + randomSeed); print("> testCompose#" + randomSeed);
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
var startText = randomMultiline(10, 20, rand) + '\n'; var startText = randomMultiline(10, 20, rand) + '\n';
@ -594,7 +594,7 @@ function runTests() {
(function simpleComposeAttributesTest() { (function simpleComposeAttributesTest() {
print("> simpleComposeAttributesTest"); print("> simpleComposeAttributesTest");
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
p.putAttrib(['bold', '']); p.putAttrib(['bold', '']);
p.putAttrib(['bold', 'true']); p.putAttrib(['bold', 'true']);
var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x"); var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x");
@ -604,7 +604,7 @@ function runTests() {
})(); })();
(function followAttributesTest() { (function followAttributesTest() {
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
p.putAttrib(['x', '']); p.putAttrib(['x', '']);
p.putAttrib(['x', 'abc']); p.putAttrib(['x', 'abc']);
p.putAttrib(['x', 'def']); p.putAttrib(['x', 'def']);
@ -633,7 +633,7 @@ function runTests() {
var rand = new random(); var rand = new random();
print("> testFollow#" + randomSeed); print("> testFollow#" + randomSeed);
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
var startText = randomMultiline(10, 20, rand) + '\n'; var startText = randomMultiline(10, 20, rand) + '\n';
@ -682,8 +682,8 @@ function runTests() {
(function testMoveOpsToNewPool() { (function testMoveOpsToNewPool() {
print("> testMoveOpsToNewPool"); print("> testMoveOpsToNewPool");
var pool1 = AttributePoolFactory.createAttributePool(); var pool1 = new AttributePool();
var pool2 = AttributePoolFactory.createAttributePool(); var pool2 = new AttributePool();
pool1.putAttrib(['baz', 'qux']); pool1.putAttrib(['baz', 'qux']);
pool1.putAttrib(['foo', 'bar']); pool1.putAttrib(['foo', 'bar']);
@ -738,7 +738,7 @@ function runTests() {
(function testOpAttributeValue() { (function testOpAttributeValue() {
print("> testOpAttributeValue"); print("> testOpAttributeValue");
var p = AttributePoolFactory.createAttributePool(); var p = new AttributePool();
p.putAttrib(['name', 'david']); p.putAttrib(['name', 'david']);
p.putAttrib(['color', 'green']); p.putAttrib(['color', 'green']);

View File

@ -0,0 +1,9 @@
a
<% e.begin_block("bar"); %>
A
<% e.begin_block("foo"); %>
XX
<% e.end_block(); %>
B
<% e.end_block(); %>
b

View File

@ -0,0 +1,7 @@
<% e.inherit("./bar.ejs"); %>
<% e.begin_define_block("foo"); %>
YY
<% e.super(); %>
ZZ
<% e.end_define_block(); %>

131
src/node/eejs/index.js Normal file
View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 2011 RedHog (Egil Möller) <egil.moller@freecode.no>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Basic usage:
*
* require("./index").require("./examples/foo.ejs")
*/
var ejs = require("ejs");
var fs = require("fs");
var path = require("path");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var resolve = require("resolve");
exports.info = {
buf_stack: [],
block_stack: [],
blocks: {},
file_stack: [],
};
exports._init = function (b, recursive) {
exports.info.buf_stack.push(exports.info.buf);
exports.info.buf = b;
}
exports._exit = function (b, recursive) {
exports.info.file_stack[exports.info.file_stack.length-1].inherit.forEach(function (item) {
exports._require(item.name, item.args);
});
exports.info.buf = exports.info.buf_stack.pop();
}
exports.begin_capture = function() {
exports.info.buf_stack.push(exports.info.buf.concat());
exports.info.buf.splice(0, exports.info.buf.length);
}
exports.end_capture = function () {
var res = exports.info.buf.join("");
exports.info.buf.splice.apply(
exports.info.buf,
[0, exports.info.buf.length].concat(exports.info.buf_stack.pop()));
return res;
}
exports.begin_define_block = function (name) {
if (typeof exports.info.blocks[name] == "undefined")
exports.info.blocks[name] = {};
exports.info.block_stack.push(name);
exports.begin_capture();
}
exports.super = function () {
exports.info.buf.push('<!eejs!super!>');
}
exports.end_define_block = function () {
content = exports.end_capture();
var name = exports.info.block_stack.pop();
if (typeof exports.info.blocks[name].content == "undefined")
exports.info.blocks[name].content = content;
else if (typeof exports.info.blocks[name].content.indexOf('<!eejs!super!>'))
exports.info.blocks[name].content = exports.info.blocks[name].content.replace('<!eejs!super!>', content);
return exports.info.blocks[name].content;
}
exports.end_block = function () {
var name = exports.info.block_stack[exports.info.block_stack.length-1];
var args = {content: exports.end_define_block()};
hooks.callAll("eejsBlock_" + name, args);
exports.info.buf.push(args.content);
}
exports.begin_block = exports.begin_define_block;
exports.inherit = function (name, args) {
exports.info.file_stack[exports.info.file_stack.length-1].inherit.push({name:name, args:args});
}
exports.require = function (name, args, mod) {
if (args == undefined) args = {};
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);
}
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;
var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
exports.info.file_stack.push({path: ejspath, inherit: []});
var res = ejs.render(template, args);
exports.info.file_stack.pop();
return res;
}
exports._require = function (name, args) {
exports.info.buf.push(exports.require(name, args));
}

View File

@ -18,23 +18,23 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var fs = require("fs"); var fs = require("fs");
var api = require("../db/API"); var api = require("../db/API");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var randomString = CommonCode.require('/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
//ensure we have an apikey //ensure we have an apikey
var apikey = null; var apikey = null;
try try
{ {
apikey = fs.readFileSync("../APIKEY.txt","utf8"); apikey = fs.readFileSync("./APIKEY.txt","utf8");
} }
catch(e) catch(e)
{ {
apikey = randomString(32); apikey = randomString(32);
fs.writeFileSync("../APIKEY.txt",apikey,"utf8"); fs.writeFileSync("./APIKEY.txt",apikey,"utf8");
} }
//a list of all functions //a list of all functions

View File

@ -196,6 +196,6 @@ exports.doImport = function(req, res, padId)
ERR(err); ERR(err);
//close the connection //close the connection
res.send("<script>document.domain = document.domain; var impexp = window.top.require('/pad_impexp').padimpexp.handleFrameCall('" + status + "'); </script>", 200); res.send("<script type='text/javascript' src='/static/js/jquery.js'></script><script> if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.top.require('/pad_impexp').padimpexp.handleFrameCall('" + status + "');</script>", 200);
}); });
} }

View File

@ -18,18 +18,21 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var async = require("async"); var async = require("async");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory"); var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var AttributeManager = require("ep_etherpad-lite/static/js/AttributeManager");
var authorManager = require("../db/AuthorManager"); var authorManager = require("../db/AuthorManager");
var readOnlyManager = require("../db/ReadOnlyManager"); var readOnlyManager = require("../db/ReadOnlyManager");
var settings = require('../utils/Settings'); var settings = require('../utils/Settings');
var securityManager = require("../db/SecurityManager"); var securityManager = require("../db/SecurityManager");
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins.js");
var log4js = require('log4js'); var log4js = require('log4js');
var messageLogger = log4js.getLogger("message"); var messageLogger = log4js.getLogger("message");
var _ = require('underscore');
/** /**
* A associative array that translates a session to a pad * A associative array that translates a session to a pad
@ -127,7 +130,11 @@ exports.handleDisconnect = function(client)
//Go trough all user that are still on the pad, and send them the USER_LEAVE message //Go trough all user that are still on the pad, and send them the USER_LEAVE message
for(i in pad2sessions[sessionPad]) for(i in pad2sessions[sessionPad])
{ {
socketio.sockets.sockets[pad2sessions[sessionPad][i]].json.send(messageToTheOtherUsers); var socket = socketio.sockets.sockets[pad2sessions[sessionPad][i]];
if(socket !== undefined){
socket.json.send(messageToTheOtherUsers);
}
} }
}); });
} }
@ -197,6 +204,23 @@ exports.handleMessage = function(client, message)
} }
} }
/**
* Handles a save revision message
* @param client the client that send this message
* @param message the message from the client
*/
function handleSaveRevisionMessage(client, message){
var padId = session2pad[client.id];
var userId = sessioninfos[client.id].author;
padManager.getPad(padId, function(err, pad)
{
if(ERR(err)) return;
pad.addSavedRevision(pad.head, userId);
});
}
/** /**
* Handles a Chat Message * Handles a Chat Message
* @param client the client that send this message * @param client the client that send this message
@ -366,7 +390,7 @@ function handleUserChanges(client, message)
//get all Vars we need //get all Vars we need
var baseRev = message.data.baseRev; var baseRev = message.data.baseRev;
var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool); var wireApool = (new AttributePool()).fromJsonable(message.data.apool);
var changeset = message.data.changeset; var changeset = message.data.changeset;
var r, apool, pad; var r, apool, pad;
@ -563,8 +587,12 @@ function _correctMarkersInPad(atext, apool) {
var offset = 0; var offset = 0;
while (iter.hasNext()) { while (iter.hasNext()) {
var op = iter.next(); var op = iter.next();
var listValue = Changeset.opAttributeValue(op, 'list', apool);
if (listValue) { var hasMarker = _.find(AttributeManager.lineAttributes, function(attribute){
return Changeset.opAttributeValue(op, attribute, apool);
}) !== undefined;
if (hasMarker) {
for(var i=0;i<op.chars;i++) { for(var i=0;i<op.chars;i++) {
if (offset > 0 && text.charAt(offset-1) != '\n') { if (offset > 0 && text.charAt(offset-1) != '\n') {
badMarkers.push(offset); badMarkers.push(offset);
@ -736,9 +764,10 @@ function handleClientReady(client, message)
{ {
for(var i in pad2sessions[message.padId]) for(var i in pad2sessions[message.padId])
{ {
if(sessioninfos[pad2sessions[message.padId][i]].author == author) if(sessioninfos[pad2sessions[message.padId][i]] && sessioninfos[pad2sessions[message.padId][i]].author == author)
{ {
socketio.sockets.sockets[pad2sessions[message.padId][i]].json.send({disconnect:"userdup"}); var socket = socketio.sockets.sockets[pad2sessions[message.padId][i]];
if(socket) socket.json.send({disconnect:"userdup"});
} }
} }
} }
@ -799,9 +828,12 @@ function handleClientReady(client, message)
"hideSidebar": false "hideSidebar": false
}, },
"abiwordAvailable": settings.abiwordAvailable(), "abiwordAvailable": settings.abiwordAvailable(),
"hooks": {} "plugins": {
"plugins": plugins.plugins,
"parts": plugins.parts,
}
} }
//Add a username to the clientVars if one avaiable //Add a username to the clientVars if one avaiable
if(authorName != null) if(authorName != null)
{ {

View File

@ -18,12 +18,12 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var async = require("async"); var async = require("async");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory"); var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var settings = require('../utils/Settings'); var settings = require('../utils/Settings');
var authorManager = require("../db/AuthorManager"); var authorManager = require("../db/AuthorManager");
var log4js = require('log4js'); var log4js = require('log4js');
@ -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,
@ -166,6 +164,7 @@ function createTimesliderClientVars (padId, callback)
hooks: [], hooks: [],
initialStyledContents: {} initialStyledContents: {}
}; };
var pad; var pad;
var initialChangesets = []; var initialChangesets = [];
@ -180,6 +179,12 @@ function createTimesliderClientVars (padId, callback)
callback(); callback();
}); });
}, },
//get all saved revisions and add them
function(callback)
{
clientVars.savedRevisions = pad.getSavedRevisions();
callback();
},
//get all authors and add them to //get all authors and add them to
function(callback) function(callback)
{ {
@ -265,7 +270,7 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
var forwardsChangesets = []; var forwardsChangesets = [];
var backwardsChangesets = []; var backwardsChangesets = [];
var timeDeltas = []; var timeDeltas = [];
var apool = AttributePoolFactory.createAttributePool(); var apool = new AttributePool();
var pad; var pad;
var composedChangesets = {}; var composedChangesets = {};
var revisionDate = []; var revisionDate = [];

View File

@ -0,0 +1,53 @@
var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) {
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var render_args = {
plugins: plugins.plugins,
search_results: {},
errors: [],
};
res.send(eejs.require(
"ep_etherpad-lite/templates/admin/plugins.html",
render_args), {});
});
}
exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) {
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, true, function (progress) {
if (progress.results)
socket.emit("search-result", progress);
socket.emit("progress", progress);
});
});
socket.on("install", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
installer.install(plugin_name, function (progress) {
socket.emit("progress", progress);
});
});
socket.on("uninstall", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
installer.uninstall(plugin_name, function (progress) {
socket.emit("progress", progress);
});
});
});
}

View File

@ -0,0 +1,60 @@
var log4js = require('log4js');
var apiLogger = log4js.getLogger("API");
var formidable = require('formidable');
var apiHandler = require('../../handler/APIHandler');
//This is for making an api call, collecting all post information and passing it to the apiHandler
var apiCaller = function(req, res, fields) {
res.header("Content-Type", "application/json; charset=utf-8");
apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
//wrap the send function so we can log the response
//note: res._send seems to be already in use, so better use a "unique" name
res._____send = res.send;
res.send = function (response) {
response = JSON.stringify(response);
apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
//is this a jsonp call, if yes, add the function call
if(req.query.jsonp)
response = req.query.jsonp + "(" + response + ")";
res._____send(response);
}
//call the api handler
apiHandler.handle(req.params.func, fields, req, res);
}
exports.apiCaller = apiCaller;
exports.expressCreateServer = function (hook_name, args, cb) {
//This is a api GET call, collect all post informations and pass it to the apiHandler
args.app.get('/api/1/:func', function (req, res) {
apiCaller(req, res, req.query)
});
//This is a api POST call, collect all post informations and pass it to the apiHandler
args.app.post('/api/1/:func', function(req, res) {
new formidable.IncomingForm().parse(req, function (err, fields, files) {
apiCaller(req, res, fields)
});
});
//The Etherpad client side sends information about how a disconnect happen
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
new formidable.IncomingForm().parse(req, function(err, fields, files) {
console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
res.end("OK");
});
});
//The Etherpad client side sends information about client side javscript errors
args.app.post('/jserror', function(req, res) {
new formidable.IncomingForm().parse(req, function(err, fields, files) {
console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
res.end("OK");
});
});
}

View File

@ -0,0 +1,52 @@
var os = require("os");
var db = require('../../db/DB');
exports.onShutdown = false;
exports.gracefulShutdown = function(err) {
if(err && err.stack) {
console.error(err.stack);
} else if(err) {
console.error(err);
}
//ensure there is only one graceful shutdown running
if(exports.onShutdown) return;
exports.onShutdown = true;
console.log("graceful shutdown...");
//stop the http server
exports.app.close();
//do the db shutdown
db.db.doShutdown(function() {
console.log("db sucessfully closed.");
process.exit(0);
});
setTimeout(function(){
process.exit(1);
}, 3000);
}
exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app;
args.app.error(function(err, req, res, next){
res.send(500);
console.error(err.stack ? err.stack : err.toString());
exports.gracefulShutdown();
});
//connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1) {
//sigint is so far not working on windows
//https://github.com/joyent/node/issues/1553
process.on('SIGINT', exports.gracefulShutdown);
}
process.on('uncaughtException', exports.gracefulShutdown);
}

View File

@ -0,0 +1,41 @@
var hasPadAccess = require("../../padaccess");
var settings = require('../../utils/Settings');
var exportHandler = require('../../handler/ExportHandler');
var importHandler = require('../../handler/ImportHandler');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) {
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
//send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
next();
return;
}
//if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.abiword == null &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
return;
}
res.header("Access-Control-Allow-Origin", "*");
hasPadAccess(req, res, function() {
exportHandler.doExport(req, res, req.params.pad, req.params.type);
});
});
//handle import requests
args.app.post('/p/:pad/import', function(req, res, next) {
//if abiword is disabled, skip handling this request
if(settings.abiword == null) {
next();
return;
}
hasPadAccess(req, res, function() {
importHandler.doImport(req, res, req.params.pad);
});
});
}

View File

@ -0,0 +1,65 @@
var async = require('async');
var ERR = require("async-stacktrace");
var readOnlyManager = require("../../db/ReadOnlyManager");
var hasPadAccess = require("../../padaccess");
var exporthtml = require("../../utils/ExportHtml");
exports.expressCreateServer = function (hook_name, args, cb) {
//serve read only pad
args.app.get('/ro/:id', function(req, res)
{
var html;
var padId;
var pad;
async.series([
//translate the read only pad to a padId
function(callback)
{
readOnlyManager.getPadId(req.params.id, function(err, _padId)
{
if(ERR(err, callback)) return;
padId = _padId;
//we need that to tell hasPadAcess about the pad
req.params.pad = padId;
callback();
});
},
//render the html document
function(callback)
{
//return if the there is no padId
if(padId == null)
{
callback("notfound");
return;
}
hasPadAccess(req, res, function()
{
//render the html document
exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
{
if(ERR(err, callback)) return;
html = _html;
callback();
});
});
}
], function(err)
{
//throw any unexpected error
if(err && err != "notfound")
ERR(err);
if(err == "notfound")
res.send('404 - Not Found', 404);
else
res.send(html);
});
});
}

View File

@ -0,0 +1,29 @@
var padManager = require('../../db/PadManager');
exports.expressCreateServer = function (hook_name, args, cb) {
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', function (req, res, next, padId) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
}
else
{
padManager.sanitizePadId(padId, function(sanitizedPadId) {
//the pad id was sanitized, so we redirect to the sanitized version
if(sanitizedPadId != padId)
{
var real_path = req.path.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId);
res.header('Location', real_path);
res.send('You should be redirected to <a href="' + real_path + '">' + real_path + '</a>', 302);
}
//the pad id was fine, so just render it
else
{
next();
}
});
}
});
}

View File

@ -0,0 +1,65 @@
var log4js = require('log4js');
var socketio = require('socket.io');
var settings = require('../../utils/Settings');
var socketIORouter = require("../../handler/SocketIORouter");
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']);
var socketIOLogger = log4js.getLogger("socket.io");
io.set('logger', {
debug: function (str)
{
socketIOLogger.debug.apply(socketIOLogger, arguments);
},
info: function (str)
{
socketIOLogger.info.apply(socketIOLogger, arguments);
},
warn: function (str)
{
socketIOLogger.warn.apply(socketIOLogger, arguments);
},
error: function (str)
{
socketIOLogger.error.apply(socketIOLogger, arguments);
},
});
//minify socket.io javascript
if(settings.minify)
io.enable('browser client minification');
//Initalize the Socket.IO Router
socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler);
socketIORouter.addComponent("timeslider", timesliderMessageHandler);
hooks.callAll("socketio", {"app": args.app, "io": io});
}

View File

@ -0,0 +1,46 @@
var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
//serve index.html under /
args.app.get('/', function(req, res)
{
res.send(eejs.require("ep_etherpad-lite/templates/index.html"));
});
//serve robots.txt
args.app.get('/robots.txt', function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/robots.txt");
res.sendfile(filePath);
});
//serve favicon.ico
args.app.get('/favicon.ico', function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico");
res.sendfile(filePath, function(err)
{
//there is no custom favicon, send the default favicon
if(err)
{
filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
res.sendfile(filePath);
}
});
});
//serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next)
{
res.send(eejs.require("ep_etherpad-lite/templates/pad.html"));
});
//serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', function(req, res, next)
{
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"));
});
}

View File

@ -0,0 +1,57 @@
var path = require('path');
var minify = require('../../utils/Minify');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var CachingMiddleware = require('../../utils/caching_middleware');
var settings = require("../../utils/Settings");
var Yajsml = require('yajsml');
var fs = require("fs");
var ERR = require("async-stacktrace");
var _ = require("underscore");
exports.expressCreateServer = function (hook_name, args, cb) {
// Cache both minified and static.
var assetCache = new CachingMiddleware;
args.app.all('/(javascripts|static)/*', assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc.
args.app.all('/static/:filename(*)', minify.minify);
// Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side.
var jsServer = new (Yajsml.Server)({
rootPath: 'javascripts/src/'
, rootURI: 'http://localhost:' + settings.port + '/static/js/'
, libraryPath: 'javascripts/lib/'
, libraryURI: 'http://localhost:' + settings.port + '/static/plugins/'
});
var StaticAssociator = Yajsml.associators.StaticAssociator;
var associations =
Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations);
jsServer.setAssociator(associator);
args.app.use(jsServer);
// serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
var clientParts = _(plugins.parts)
.filter(function(part){ return _(part).has('client_hooks') });
var clientPlugins = {};
_(clientParts).chain()
.map(function(part){ return part.plugin })
.uniq()
.each(function(name){
clientPlugins[name] = _(plugins.plugins[name]).clone();
delete clientPlugins[name]['package'];
});
res.header("Content-Type","application/json; charset=utf-8");
res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
res.end();
});
}

View File

@ -0,0 +1,109 @@
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) {
var hookResultMangle = function (cb) {
return function (err, data) {
return cb(!err && data.length && data[0]);
}
}
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 () {
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) {
// 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);
}

21
src/node/padaccess.js Normal file
View File

@ -0,0 +1,21 @@
var ERR = require("async-stacktrace");
var securityManager = require('./db/SecurityManager');
//checks for padAccess
module.exports = function (req, res, callback) {
// FIXME: Why is this ever undefined??
if (req.cookies === undefined) req.cookies = {};
securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj) {
if(ERR(err, callback)) return;
//there is access, continue
if(accessObj.accessStatus == "grant") {
callback();
//no access
} else {
res.send("403 - Can't touch this", 403);
}
});
}

101
src/node/server.js Executable file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env node
/**
* This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
* Static file Requests are answered directly from this module, Socket.IO messages are passed
* to MessageHandler and minfied requests are passed to minified.
*/
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var log4js = require('log4js');
var fs = require('fs');
var settings = require('./utils/Settings');
var db = require('./db/DB');
var async = require('async');
var express = require('express');
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 = "";
try
{
var rootPath = path.resolve(npm.dir, '..');
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
}
catch(e)
{
console.warn("Can't get git version for server header\n" + e.message)
}
console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
async.waterfall([
//initalize the database
function (callback)
{
db.init(callback);
},
plugins.update,
function (callback) {
console.info("Installed plugins: " + plugins.formatPlugins());
console.debug("Installed parts:\n" + plugins.formatParts());
console.debug("Installed hooks:\n" + plugins.formatHooks());
callback();
},
//initalize the http server
function (callback)
{
//create server
var app = express.createServer();
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
app.configure(function() { hooks.callAll("expressConfigure", {"app": app}); });
hooks.callAll("expressCreateServer", {"app": app});
//let the server listen
app.listen(settings.port, settings.ip);
console.log("You can access your Etherpad-Lite instance at http://" + settings.ip + ":" + settings.port + "/");
if(!_.isEmpty(settings.users)){
console.log("The plugin admin page is at http://" + settings.ip + ":" + settings.port + "/admin/plugins");
}
else{
console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
}
callback(null);
}
]);

View File

@ -15,8 +15,8 @@
*/ */
var async = require("async"); var async = require("async");
var CommonCode = require('./common_code');
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
function getPadDokuWiki(pad, revNum, callback) function getPadDokuWiki(pad, revNum, callback)

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
var CommonCode = require('./common_code');
var async = require("async"); var async = require("async");
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var Security = CommonCode.require('/security'); var Security = require('ep_etherpad-lite/static/js/security');
function getPadPlainText(pad, revNum) function getPadPlainText(pad, revNum)
{ {

View File

@ -17,10 +17,10 @@
var jsdom = require('jsdom-nocontextifiy').jsdom; var jsdom = require('jsdom-nocontextifiy').jsdom;
var log4js = require('log4js'); var log4js = require('log4js');
var CommonCode = require('../utils/common_code');
var Changeset = CommonCode.require("/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var contentcollector = CommonCode.require("/contentcollector"); var contentcollector = require("ep_etherpad-lite/static/js/contentcollector");
var map = CommonCode.require("/ace2_common").map; var map = require("ep_etherpad-lite/static/js/ace2_common").map;
function setPadHTML(pad, html, callback) function setPadHTML(pad, html, callback)
{ {

View File

@ -27,19 +27,22 @@ var cleanCSS = require('clean-css');
var jsp = require("uglify-js").parser; var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify; var pro = require("uglify-js").uglify;
var path = require('path'); var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var RequireKernel = require('require-kernel'); var RequireKernel = require('require-kernel');
var server = require('../server');
var ROOT_DIR = path.normalize(__dirname + "/../../static/"); var ROOT_DIR = path.normalize(__dirname + "/../../static/");
var TAR_PATH = path.join(__dirname, 'tar.json'); var TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
// Rewrite tar to include modules with no extensions and proper rooted paths. // Rewrite tar to include modules with no extensions and proper rooted paths.
var LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
exports.tar = {}; exports.tar = {};
for (var key in tar) { for (var key in tar) {
exports.tar['/' + key] = exports.tar[LIBRARY_PREFIX + '/' + key] =
tar[key].map(function (p) {return '/' + p}).concat( tar[key].map(function (p) {return LIBRARY_PREFIX + '/' + p}).concat(
tar[key].map(function (p) {return '/' + p.replace(/\.js$/, '')}) tar[key].map(function (p) {
return LIBRARY_PREFIX + '/' + p.replace(/\.js$/, '')
})
); );
} }
@ -63,6 +66,22 @@ exports.minify = function(req, res, next)
return; return;
} }
/* Handle static files for plugins:
paths like "plugins/ep_myplugin/static/js/test.js"
are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
*/
var match = filename.match(/^plugins\/([^\/]+)\/static\/(.*)/);
if (match) {
var pluginName = match[1];
var resourcePath = match[2];
var plugin = plugins.plugins[pluginName];
if (plugin) {
var pluginPath = plugin.package.realPath;
filename = path.relative(ROOT_DIR, pluginPath + '/static/' + resourcePath);
}
}
// What content type should this be? // What content type should this be?
// TODO: This should use a MIME module. // TODO: This should use a MIME module.
var contentType; var contentType;
@ -89,10 +108,10 @@ exports.minify = function(req, res, next)
date = new Date(date); date = new Date(date);
res.setHeader('last-modified', date.toUTCString()); res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString()); res.setHeader('date', (new Date()).toUTCString());
if (server.maxAge) { if (settings.maxAge !== undefined) {
var expiresDate = new Date((new Date()).getTime()+server.maxAge*1000); var expiresDate = new Date((new Date()).getTime()+settings.maxAge*1000);
res.setHeader('expires', expiresDate.toUTCString()); res.setHeader('expires', expiresDate.toUTCString());
res.setHeader('cache-control', 'max-age=' + server.maxAge); res.setHeader('cache-control', 'max-age=' + settings.maxAge);
} }
} }
@ -112,7 +131,10 @@ exports.minify = function(req, res, next)
res.end(); res.end();
} else if (req.method == 'GET') { } else if (req.method == 'GET') {
getFileCompressed(filename, contentType, function (error, content) { getFileCompressed(filename, contentType, function (error, content) {
if(ERR(error)) return; if(ERR(error, function(){
res.writeHead(500, {});
res.end();
})) return;
res.header("Content-Type", contentType); res.header("Content-Type", contentType);
res.writeHead(200, {}); res.writeHead(200, {});
res.write(content); res.write(content);

View File

@ -23,6 +23,10 @@ var fs = require("fs");
var os = require("os"); var os = require("os");
var path = require('path'); var path = require('path');
var argv = require('./Cli').argv; var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js");
/* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, ".."));
/** /**
* The IP ep-lite should listen to * The IP ep-lite should listen to
@ -40,7 +44,7 @@ exports.dbType = "dirty";
/** /**
* This setting is passed with dbType to ueberDB to set up the database * This setting is passed with dbType to ueberDB to set up the database
*/ */
exports.dbSettings = { "filename" : "../var/dirty.db" }; exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };
/** /**
* The default Text of a new pad * The default Text of a new pad
*/ */
@ -76,10 +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 = {};
//checks if abiword is avaiable //checks if abiword is avaiable
exports.abiwordAvailable = function() exports.abiwordAvailable = function()
@ -96,11 +102,19 @@ exports.abiwordAvailable = function()
// Discover where the settings file lives // Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json"; var settingsFilename = argv.settings || "settings.json";
var settingsPath = settingsFilename.charAt(0) == '/' ? '' : path.normalize(__dirname + "/../../"); if (settingsFilename.charAt(0) != '/') {
settingsFilename = path.normalize(path.join(root, settingsFilename));
//read the settings sync }
var settingsStr = fs.readFileSync(settingsPath + settingsFilename).toString();
var settingsStr
try{
//read the settings sync
settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e){
console.warn('No settings file found. Using defaults.');
settingsStr = '{}';
}
//remove all comments //remove all comments
settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,""); settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,"");
@ -138,3 +152,7 @@ for(var i in settings)
console.warn("This setting doesn't exist or it was removed"); console.warn("This setting doesn't exist or it was removed");
} }
} }
if(exports.dbType === "dirty"){
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.")
}

View File

@ -18,12 +18,12 @@ var async = require('async');
var Buffer = require('buffer').Buffer; var Buffer = require('buffer').Buffer;
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var server = require('../server');
var zlib = require('zlib'); var zlib = require('zlib');
var util = require('util'); var util = require('util');
var settings = require('./Settings');
var ROOT_DIR = path.normalize(__dirname + "/../"); var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
var CACHE_DIR = ROOT_DIR + '../var/'; CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {}; var responseCache = {};
@ -37,7 +37,7 @@ function CachingMiddleware() {
} }
CachingMiddleware.prototype = new function () { CachingMiddleware.prototype = new function () {
function handle(req, res, next) { function handle(req, res, next) {
if (!(req.method == "GET" || req.method == "HEAD")) { if (!(req.method == "GET" || req.method == "HEAD") || !CACHE_DIR) {
return next(undefined, req, res); return next(undefined, req, res);
} }
@ -73,6 +73,9 @@ CachingMiddleware.prototype = new function () {
var _headers = {}; var _headers = {};
old_res.setHeader = res.setHeader; old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) { res.setHeader = function (key, value) {
// Don't set cookies, see issue #707
if (key.toLowerCase() === 'set-cookie') return;
_headers[key.toLowerCase()] = value; _headers[key.toLowerCase()] = value;
old_res.setHeader.call(res, key, value); old_res.setHeader.call(res, key, value);
}; };

View File

@ -0,0 +1,16 @@
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
var randomString = function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
};
module.exports = randomString;

View File

@ -1,13 +1,7 @@
{ {
"pad.js": [ "pad.js": [
"jquery.js" "pad.js"
, "security.js"
, "pad.js"
, "ace2_common.js"
, "pad_utils.js" , "pad_utils.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
, "pad_cookie.js" , "pad_cookie.js"
, "pad_editor.js" , "pad_editor.js"
, "pad_editbar.js" , "pad_editbar.js"
@ -22,17 +16,11 @@
, "chat.js" , "chat.js"
, "excanvas.js" , "excanvas.js"
, "farbtastic.js" , "farbtastic.js"
, "prefixfree.js"
] ]
, "timeslider.js": [ , "timeslider.js": [
"jquery.js" "timeslider.js"
, "security.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
, "colorutils.js" , "colorutils.js"
, "draggable.js" , "draggable.js"
, "ace2_common.js"
, "pad_utils.js" , "pad_utils.js"
, "pad_cookie.js" , "pad_cookie.js"
, "pad_editor.js" , "pad_editor.js"
@ -41,7 +29,7 @@
, "pad_modals.js" , "pad_modals.js"
, "pad_savedrevs.js" , "pad_savedrevs.js"
, "pad_impexp.js" , "pad_impexp.js"
, "AttributePoolFactory.js" , "AttributePool.js"
, "Changeset.js" , "Changeset.js"
, "domline.js" , "domline.js"
, "linestylefilter.js" , "linestylefilter.js"
@ -49,13 +37,12 @@
, "broadcast.js" , "broadcast.js"
, "broadcast_slider.js" , "broadcast_slider.js"
, "broadcast_revisions.js" , "broadcast_revisions.js"
, "timeslider.js"
] ]
, "ace2_inner.js": [ , "ace2_inner.js": [
"ace2_common.js" "ace2_inner.js"
, "AttributePoolFactory.js" , "AttributePool.js"
, "Changeset.js" , "Changeset.js"
, "security.js" , "ChangesetUtils.js"
, "skiplist.js" , "skiplist.js"
, "virtual_lines.js" , "virtual_lines.js"
, "cssmanager.js" , "cssmanager.js"
@ -65,6 +52,18 @@
, "changesettracker.js" , "changesettracker.js"
, "linestylefilter.js" , "linestylefilter.js"
, "domline.js" , "domline.js"
, "ace2_inner.js" , "AttributeManager.js"
]
, "ace2_common.js": [
"ace2_common.js"
, "jquery.js"
, "rjquery.js"
, "underscore.js"
, "security.js"
, "json2.js"
, "pluginfw/plugins.js"
, "pluginfw/hooks.js"
, "pluginfw/async.js"
, "pluginfw/parent_require.js"
] ]
} }

View File

@ -1,5 +1,5 @@
{ {
"name" : "etherpad-lite", "name" : "ep_etherpad-lite",
"description" : "A Etherpad based on node.js", "description" : "A Etherpad based on node.js",
"homepage" : "https://github.com/Pita/etherpad-lite", "homepage" : "https://github.com/Pita/etherpad-lite",
"keywords" : ["etherpad", "realtime", "collaborative", "editor"], "keywords" : ["etherpad", "realtime", "collaborative", "editor"],
@ -10,20 +10,29 @@
"name": "Robin Buse" } "name": "Robin Buse" }
], ],
"dependencies" : { "dependencies" : {
"yajsml" : "1.1.2", "yajsml" : "1.1.3",
"request" : "2.9.100", "request" : "2.9.100",
"require-kernel" : "1.0.3", "require-kernel" : "1.0.5",
"socket.io" : "0.8.7", "resolve" : "0.2.1",
"socket.io" : "0.9.6",
"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",
"log4js" : "0.4.1", "log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10", "jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2" "async-stacktrace" : "0.0.2",
"npm" : "1.1",
"ejs" : "0.6.1",
"graceful-fs" : "1.1.5",
"slide" : "1.1.3",
"semver" : "1.0.13",
"underscore" : "1.3.1"
}, },
"bin": { "etherpad-lite": "./node/server.js" },
"devDependencies": { "devDependencies": {
"jshint" : "*" "jshint" : "*"
}, },

122
src/static/css/admin.css Normal file
View File

@ -0,0 +1,122 @@
body {
margin: 0;
color: #333;
font: 14px helvetica, sans-serif;
background: #ddd;
background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
border-top: 8px solid rgba(51,51,51,.8);
}
#wrapper {
margin-top: 160px;
padding: 15px;
background: #fff;
opacity: .9;
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
max-width: 700px;
margin: auto;
border-radius: 0 0 7px 7px;
}
h1 {
font-size: 29px;
}
h2 {
font-size: 24px;
}
.separator {
margin: 10px 0;
height: 1px;
background: #aaa;
background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
}
form {
margin-bottom: 0;
}
#inner {
width: 300px;
margin: 0 auto;
}
input {
font-weight: bold;
font-size: 15px;
}
input[type="button"] {
padding: 4px 6px;
margin: 0;
}
input[type="button"].do-install, input[type="button"].do-uninstall {
float: right;
width: 100px;
}
input[type="button"]#do-search {
display: block;
}
input[type="text"] {
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 10px;
*padding: 0; /* IE7 hack */
width: 100%;
outline: none;
border: 1px solid #ddd;
margin: 0 0 5px 0;
max-width: 500px;
}
table {
border: 1px solid #ddd;
border-radius: 3px;
border-spacing: 0;
width: 100%;
margin: 20px 0;
}
table thead tr {
background: #eee;
}
td, th {
padding: 5px;
}
.template {
display: none;
}
.dialog {
display: none;
position: absolute;
left: 50%;
top: 50%;
width: 700px;
height: 500px;
margin-left: -350px;
margin-top: -250px;
border: 3px solid #999;
background: #eee;
}
.dialog .title {
margin: 0;
padding: 2px;
border-bottom: 3px solid #999;
font-size: 24px;
line-height: 24px;
height: 24px;
overflow: hidden;
}
.dialog .title .close {
float: right;
padding: 1px 10px;
}
.dialog .history {
background: #222;
color: #eee;
position: absolute;
top: 41px;
bottom: 10px;
left: 10px;
right: 10px;
padding: 2px;
overflow: auto;
}

View File

@ -5,6 +5,13 @@
html { cursor: text; } /* in Safari, produces text cursor for whole doc (inc. below body) */ html { cursor: text; } /* in Safari, produces text cursor for whole doc (inc. below body) */
span { cursor: auto; } span { cursor: auto; }
::selection {
background: #acf;
}
::-moz-selection {
background: #acf;
}
a { cursor: pointer !important; } a { cursor: pointer !important; }
ul, ol, li { ul, ol, li {

995
src/static/css/pad.css Normal file
View File

@ -0,0 +1,995 @@
*,
html,
body,
p {
margin: 0;
padding: 0;
}
.clear {
clear: both
}
html {
font-size: 62.5%;
width: 100%;
}
body,
textarea {
font-family: Helvetica, Arial, sans-serif
}
iframe {
position: absolute
}
#users {
background: #f7f7f7;
background: -webkit-linear-gradient( #F7F7F7,#EEE);
background: -moz-linear-gradient( #F7F7F7,#EEE);
background: -ms-linear-gradient( #F7F7F7,#EEE);
background: -o-linear-gradient( #F7F7F7,#EEE);
background: linear-gradient( #F7F7F7,#EEE);
width: 160px;
color: #fff;
padding: 5px;
border-radius: 0 0 6px 6px;
border: 1px solid #ccc;
}
#otherusers {
max-height: 400px;
overflow: auto;
}
a img {
border: 0
}
/* menu */
.toolbar {
background: #f7f7f7;
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
border-bottom: 1px solid #ccc;
overflow: hidden;
padding-top: 4px;
width: 100%;
white-space: nowrap;
height: 32px;
}
.toolbar ul {
position: relative;
list-style: none;
padding-right: 3px;
padding-left: 1px;
z-index: 2;
overflow: hidden;
float: left
}
.toolbar ul.menu_right {
float: right
}
.toolbar ul li {
float: left;
margin-left: 2px;
}
.toolbar ul li.separator {
border: inherit;
background: inherit;
visibility: hidden;
width: 0px;
padding: 5px;
}
.toolbar ul li a:hover {
text-decoration: none;
}
.toolbar ul li a:hover {
background: #fff;
background: -webkit-linear-gradient(#f4f4f4, #e4e4e4);
background: -moz-linear-gradient(#f4f4f4, #e4e4e4);
background: -o-linear-gradient(#f4f4f4, #e4e4e4);
background: -ms-linear-gradient(#f4f4f4, #e4e4e4);
background: linear-gradient(#f4f4f4, #e4e4e4);
}
.toolbar ul li a:active {
background: #eee;
background: -webkit-linear-gradient(#ddd, #fff);
background: -moz-linear-gradient(#ddd, #fff);
background: -o-linear-gradient(#ddd, #fff);
background: -ms-linear-gradient(#ddd, #fff);
background: linear-gradient(#ddd, #fff);
-webkit-box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
-moz-box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
}
.toolbar ul li a {
background: #fff;
background: -webkit-linear-gradient(#fff, #f0f0f0);
background: -moz-linear-gradient(#fff, #f0f0f0);
background: -o-linear-gradient(#fff, #f0f0f0);
background: -ms-linear-gradient(#fff, #f0f0f0);
background: linear-gradient(#fff, #f0f0f0);
border: 1px solid #ccc;
border-radius: 3px;
color: #ccc;
cursor: pointer;
display: inline-block;
min-height: 18px;
overflow: hidden;
padding: 4px 5px;
text-align: center;
text-decoration: none;
min-width: 18px;
}
.toolbar ul li a .buttonicon {
position: relative;
top: 1px;
}
.toolbar ul li a.grouped-left {
border-radius: 3px 0 0 3px;
}
.toolbar ul li a.grouped-middle {
border-radius: 0;
margin-left: -2px;
border-left: 0;
}
.toolbar ul li a.grouped-right {
border-radius: 0 3px 3px 0;
margin-left: -2px;
border-left: 0;
}
.toolbar ul li a.selected {
background: #eee !important;
background: -webkit-linear-gradient(#EEE, #F0F0F0) !important;
background: -moz-linear-gradient(#EEE, #F0F0F0) !important;
background: -o-linear-gradient(#EEE, #F0F0F0) !important;
background: -ms-linear-gradient(#EEE, #F0F0F0) !important;
background: linear-gradient(#EEE, #F0F0F0) !important;
}
.toolbar ul li select {
background: #fff;
padding: 4px;
line-height: 22px; /* fix for safari (win/mac) */
height: 28px; /* fix for chrome (mac) */
border-radius: 3px;
border: 1px solid #ccc;
outline: none;
}
#usericon a {
min-width: 30px;
text-align: left;
}
#usericon a #online_count {
color: #777;
font-size: 10px;
position: relative;
top: 2px;
}
#editorcontainer {
position: absolute;
width: 100%;
top: 37px; /* + 1px border */
left: 0px;
bottom: 0px;
z-index: 1;
}
#editorcontainer iframe {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
#editorloadingbox {
padding-top: 100px;
padding-bottom: 100px;
font-size: 2.5em;
color: #aaa;
text-align: center;
position: absolute;
width: 100%;
height: 30px;
z-index: 100;
}
#editorcontainerbox {
position: absolute;
bottom: 0;
top: 0;
width: 100%;
}
#padpage {
position: absolute;
top: 0px;
bottom: 0px;
width: 100%;
}
#padmain {
margin-top: 0px;
position: absolute;
top: 63px !important;
left: 0px;
right: 0px;
bottom: 0px;
zoom: 1;
}
#padeditor {
bottom: 0px;
left: 0;
position: absolute;
right: 0px;
top: 0;
zoom: 1;
}
#myswatchbox {
position: absolute;
left: 5px;
top: 5px;
width: 24px;
height: 24px;
border: 1px solid #000;
background: transparent;
cursor: pointer;
}
#myswatch {
width: 100%;
height: 100%;
background: transparent; /*...initially*/
}
#mycolorpicker {
width: 232px;
height: 265px;
position: absolute;
left: -250px;
top: 0px;
z-index: 101;
display: none;
border-radius: 0 0 6px 6px;
background: #f7f7f7;
border: 1px solid #ccc;
border-top: 0;
padding-left: 10px;
padding-top: 10px;
}
#mycolorpickersave {
left: 10px;
font-weight: bold;
}
#mycolorpickercancel {
left: 85px
}
#mycolorpickersave,
#mycolorpickercancel {
background: #fff;
background: -webkit-linear-gradient(#fff, #ccc);
background: -moz-linear-gradient(#fff, #ccc);
background: -o-linear-gradient(#fff, #ccc);
background: -ms-linear-gradient(#fff, #ccc);
background: linear-gradient(#fff, #ccc);
border: 1px solid #ccc;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
color: #000;
overflow: hidden;
padding: 4px;
top: 240px;
text-align: center;
position: absolute;
width: 60px;
}
#mycolorpickerpreview {
position: absolute;
left: 207px;
top: 240px;
width: 16px;
height: 16px;
padding: 4px;
overflow: hidden;
color: #fff;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
#myusernameform {
margin-left: 35px
}
#myusernameedit {
font-size: 1.3em;
color: #fff;
padding: 3px;
height: 18px;
margin: 0;
border: 0;
width: 117px;
background: transparent;
}
#myusernameform input.editable {
border: 1px solid #444
}
#myuser .myusernameedithoverable:hover {
background: white;
color: black;
}
#mystatusform {
margin-left: 35px;
margin-top: 5px;
}
#mystatusedit {
font-size: 1.2em;
color: #777;
font-style: italic;
display: none;
padding: 2px;
height: 14px;
margin: 0;
border: 1px solid #bbb;
width: 199px;
background: transparent;
}
#myusernameform .editactive,
#myusernameform .editempty {
background: white;
border-left: 1px solid #c3c3c3;
border-top: 1px solid #c3c3c3;
border-right: 1px solid #e6e6e6;
border-bottom: 1px solid #e6e6e6;
color: #000;
}
#myusernameform .editempty {
color: #333
}
#myswatchbox, #myusernameedit, #otheruserstable .swatch {
border: 1px solid #ccc !important;
color: #333;
}
table#otheruserstable {
display: none
}
#nootherusers {
padding: 10px;
font-size: 1.2em;
color: #eee;
font-weight: bold;
}
#nootherusers a {
color: #3C88FF
}
#otheruserstable td {
height: 26px;
vertical-align: middle;
padding: 0 2px;
color: #333;
}
#otheruserstable .swatch {
border: 1px solid #000;
width: 13px;
height: 13px;
overflow: hidden;
margin: 0 4px;
}
.usertdswatch {
width: 1%
}
.usertdname {
font-size: 1.3em;
color: #444;
}
.usertdstatus {
font-size: 1.1em;
font-style: italic;
color: #999;
}
.usertdactivity {
font-size: 1.1em;
color: #777;
}
.usertdname input {
border: 1px solid #bbb;
width: 80px;
padding: 2px;
}
.usertdname input.editactive,
.usertdname input.editempty {
background: white;
border-left: 1px solid #c3c3c3;
border-top: 1px solid #c3c3c3;
border-right: 1px solid #e6e6e6;
border-bottom: 1px solid #e6e6e6;
}
.usertdname input.editempty {
color: #888;
font-style: italic;
}
.modaldialog.cboxreconnecting .modaldialog-inner,
.modaldialog.cboxconnecting .modaldialog-inner {
background: url(../../static/img/connectingbar.gif) no-repeat center 60px;
height: 100px;
}
.modaldialog.cboxreconnecting,
.modaldialog.cboxconnecting,
.modaldialog.cboxdisconnected {
background: #8FCDE0
}
.cboxdisconnected #connectionboxinner div {
display: none
}
.cboxdisconnected_userdup #connectionboxinner #disconnected_userdup {
display: block
}
.cboxdisconnected_deleted #connectionboxinner #disconnected_deleted {
display: block
}
.cboxdisconnected_initsocketfail #connectionboxinner #disconnected_initsocketfail {
display: block
}
.cboxdisconnected_looping #connectionboxinner #disconnected_looping {
display: block
}
.cboxdisconnected_slowcommit #connectionboxinner #disconnected_slowcommit {
display: block
}
.cboxdisconnected_unauth #connectionboxinner #disconnected_unauth {
display: block
}
.cboxdisconnected_unknown #connectionboxinner #disconnected_unknown {
display: block
}
.cboxdisconnected_initsocketfail #connectionboxinner #reconnect_advise,
.cboxdisconnected_looping #connectionboxinner #reconnect_advise,
.cboxdisconnected_slowcommit #connectionboxinner #reconnect_advise,
.cboxdisconnected_unknown #connectionboxinner #reconnect_advise {
display: block
}
.cboxdisconnected div#reconnect_form {
display: block
}
.cboxdisconnected .disconnected h2 {
display: none
}
.cboxdisconnected .disconnected .h2_disconnect {
display: block
}
.cboxdisconnected_userdup .disconnected h2.h2_disconnect {
display: none
}
.cboxdisconnected_userdup .disconnected h2.h2_userdup {
display: block
}
.cboxdisconnected_unauth .disconnected h2.h2_disconnect {
display: none
}
.cboxdisconnected_unauth .disconnected h2.h2_unauth {
display: block
}
#connectionstatus {
position: absolute;
width: 37px;
height: 41px;
overflow: hidden;
right: 0;
z-index: 11;
}
#connectionboxinner .connecting {
margin-top: 20px;
font-size: 2.0em;
color: #555;
text-align: center;
display: none;
}
.cboxconnecting #connectionboxinner .connecting {
display: block
}
#connectionboxinner .disconnected h2 {
font-size: 1.8em;
color: #333;
text-align: left;
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 10px;
}
#connectionboxinner .disconnected p {
margin: 10px 10px;
font-size: 1.2em;
line-height: 1.1;
color: #333;
}
#connectionboxinner .disconnected {
display: none
}
.cboxdisconnected #connectionboxinner .disconnected {
display: block
}
#connectionboxinner .reconnecting {
margin-top: 20px;
font-size: 1.6em;
color: #555;
text-align: center;
display: none;
}
.cboxreconnecting #connectionboxinner .reconnecting {
display: block
}
#reconnect_form button {
font-size: 12pt;
padding: 5px;
}
#mainmodals {
z-index: 600; /* higher than the modals themselves: */
}
.modalfield {
font-size: 1.2em;
padding: 1px;
border: 1px solid #bbb;
}
#mainmodals .editempty {
color: #aaa
}
.modaldialog {
position: absolute;
top: 100px;
left: 50%;
margin-left: -243px;
width: 485px;
display: none;
z-index: 501;
zoom: 1;
overflow: hidden;
background: white;
border: 1px solid #999;
}
.modaldialog .modaldialog-inner {
padding: 10pt
}
.modaldialog .modaldialog-hide {
float: right;
background-repeat: no-repeat;
background-image: url(static/img/sharebox4.gif);
display: block;
width: 22px;
height: 22px;
background-position: -454px -6px;
margin-right: -5px;
margin-top: -5px;
}
.modaldialog label,
.modaldialog h1 {
color: #222222;
font-size: 125%;
font-weight: bold;
}
.modaldialog th {
vertical-align: top;
text-align: left;
}
#modaloverlay {
z-index: 500;
display: none;
background-repeat: repeat-both;
width: 100%;
position: absolute;
height: 100%;
left: 0;
top: 0;
}
* html #modaloverlay {
/* for IE 6+ */
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100);
opacity: 1; /* in case this is looked at */
background-image: none;
background-repeat: no-repeat; /* scale the image */
}
#chatbox {
position: absolute;
bottom: 0px;
right: 20px;
width: 180px;
height: 200px;
z-index: 400;
background-color: #f7f7f7;
border-left: 1px solid #999;
border-right: 1px solid #999;
border-top: 1px solid #999;
padding: 3px;
padding-bottom: 10px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
display: none;
}
#chattext {
background-color: white;
border: 1px solid white;
-ms-overflow-y: scroll;
overflow-y: scroll;
font-size: 12px;
position: absolute;
right: 0px;
left: 0px;
top: 25px;
bottom: 25px;
z-index: 1002;
}
#chattext p {
padding: 3px;
-ms-overflow-x: hidden;
overflow-x: hidden;
}
#chatinputbox {
padding: 3px 2px;
position: absolute;
bottom: 0px;
right: 0px;
left: 3px;
}
#chatlabel {
font-size: 13px;
font-weight: bold;
color: #555;
text-decoration: none;
margin-right: 3px;
vertical-align: middle;
}
#chatinput {
border: 1px solid #BBBBBB;
width: 100%;
float: right;
}
#chaticon {
z-index: 400;
position: fixed;
bottom: 0px;
right: 20px;
padding: 5px;
border-left: 1px solid #999;
border-right: 1px solid #999;
border-top: 1px solid #999;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: #fff;
cursor: pointer;
}
#chaticon a {
text-decoration: none
}
#chatcounter {
color: #777;
font-size: 10px;
vertical-align: middle;
}
#titlebar {
line-height: 16px;
font-weight: bold;
color: #555;
position: relative;
bottom: 2px;
}
#titlelabel {
font-size: 13px;
margin: 4px 0 0 4px;
position: absolute;
}
#titlecross {
font-size: 25px;
float: right;
text-align: right;
text-decoration: none;
cursor: pointer;
color: #555;
}
.time {
float: right;
color: #333;
font-style: italic;
font-size: 10px;
margin-left: 3px;
margin-right: 3px;
margin-top: 2px;
}
.exporttype {
margin-top: 4px;
background-repeat: no-repeat;
padding-left: 25px;
background-image: url("../../static/img/etherpad_lite_icons.png");
color: #333;
text-decoration: none;
}
#exporthtml {
background-position: 0px -299px
}
#exportplain {
background-position: 0px -395px
}
#exportword {
background-position: 0px -275px
}
#exportpdf {
background-position: 0px -371px
}
#exportopen {
background-position: 0px -347px
}
#exportdokuwiki {
background-position: 0px -459px
}
#importstatusball {
display: none
}
#importarrow {
display: none
}
#importmessagesuccess {
display: none
}
#importsubmitinput {
height: 25px;
width: 85px;
margin-top: 12px;
}
#importstatusball {
height: 50px
}
#chatthrob {
display: none;
position: absolute;
bottom: 40px;
font-size: 14px;
width: 150px;
height: 40px;
right: 20px;
z-index: 200;
background-color: #000;
color: white;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.7);
padding: 10px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
opacity: .8;
}
.buttonicon {
width: 16px;
height: 16px;
background-image: url('../../static/img/etherpad_lite_icons.png');
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle;
}
.buttonicon-bold {
background-position: 0px -116px
}
.buttonicon-italic {
background-position: 0px 0px
}
.buttonicon-underline {
background-position: 0px -236px
}
.buttonicon-strikethrough {
background-position: 0px -200px
}
.buttonicon-insertorderedlist {
background-position: 0px -477px
}
.buttonicon-insertunorderedlist {
background-position: 0px -34px
}
.buttonicon-indent {
background-position: 0px -52px
}
.buttonicon-outdent {
background-position: 0px -134px
}
.buttonicon-undo {
background-position: 0px -255px
}
.buttonicon-redo {
background-position: 0px -166px
}
.buttonicon-clearauthorship {
background-position: 0px -86px
}
.buttonicon-settings {
background-position: 0px -436px
}
.buttonicon-import_export {
background-position: 0px -68px
}
.buttonicon-embed {
background-position: 0px -18px
}
.buttonicon-history {
background-position: 0px -218px
}
.buttonicon-chat {
background-position: 0px -102px;
}
.buttonicon-showusers {
background-position: 0px -183px;
}
.buttonicon-savedRevision {
background-position: 0px -493px
}
#focusprotector {
z-index: 100;
position: absolute;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: white;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=1)";
filter: alpha(opacity=1);
opacity: 0.01;
display: none;
}
#online_count {
color: #888;
}
.rtl {
direction: RTL
}
#chattext p {
word-wrap: break-word
}
/* fix for misaligned checkboxes */
input[type=checkbox] {
vertical-align: -1px
}
.right {
float: right
}
.popup {
font-size: 14px;
width: 450px;
padding: 10px;
border-radius: 0 0 6px 6px;
border: 1px solid #ccc;
background: #f7f7f7;
background: -webkit-linear-gradient(#F7F7F7, #EEE);
background: -moz-linear-gradient(#F7F7F7, #EEE);
background: -ms-linear-gradient(#F7F7F7, #EEE);
background: -o-linear-gradient(#F7F7F7, #EEE);
background: linear-gradient(#F7F7F7, #EEE);
-webkit-box-shadow: 0 0 8px #888;
-moz-box-shadow: 0 0 8px #888;
box-shadow: 0 2px 4px #ddd;
color: #222;
}
.popup input[type=text] {
width: 100%;
padding: 5px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
display: block;
margin-top: 10px;
}
.popup input[type=text], #users input[type=text] {
outline: none;
}
.popup a {
text-decoration: none
}
.popup h1 {
color: #555;
font-size: 18px
}
.popup h2 {
color: #777;
font-size: 15px
}
.popup p {
margin: 5px 0
}
.column {
float: left;
width: 50%;
}
#settings,
#importexport,
#embed,
#users {
position: absolute;
top: 36px;
right: 20px;
display: none;
z-index: 500;
}
.stickyChat {
background-color: #f1f1f1 !important;
right: 0px !important;
top: 37px;
-webkit-border-radius: 0px !important;
-moz-border-radius: 0px !important;
border-radius: 0px !important;
height: auto !important;
border: none !important;
border-left: 1px solid #ccc !important;
width: 185px !important;
}
@media screen and (max-width: 960px) {
.modaldialog {
position: relative;
margin: 0 auto;
width: 80%;
top: 40px;
left: 0;
}
}
@media screen and (max-width: 600px) {
.toolbar ul li.separator {
display: none;
}
.toolbar ul li a {
padding: 4px 1px
}
}
@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
#users {
top: 36px;
bottom: 40px;
border-radius: none;
}
#mycolorpicker {
left: -73px;
/* #mycolorpicker: width -#users: width */;
}
#editorcontainer {
margin-bottom: 33px
}
.toolbar ul.menu_right {
background: #f7f7f7;
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
width: 100%;
overflow: hidden;
height: 32px;
position: fixed;
bottom: 0;
border-top: 1px solid #ccc;
}
.toolbar ul.menu_right > li:last-child {
float: right;
}
.toolbar ul.menu_right > li a {
border-radius: 0;
border: none;
background: none;
margin: 0;
padding: 8px;
}
.toolbar ul li a.selected {
background: none !important
}
#chaticon, #timesliderlink {
display: none !important
}
.popup {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
}
#settings,
#importexport,
#embed {
left: 0;
top: 0;
bottom: 33px;
right: 0;
}
.toolbar ul li .separator {
display: none
}
}

View File

@ -0,0 +1,288 @@
#editorcontainerbox {
overflow: auto;
top: 40px;
position: static;
}
#padcontent {
font-size: 12px;
padding: 10px;
}
#timeslider-wrapper {
left: 0;
position: relative;
right: 0;
top: 0;
}
#timeslider-left {
background-image: url(../../static/img/timeslider_left.png);
height: 63px;
left: 0;
position: absolute;
width: 134px;
}
#timeslider-right {
background-image: url(../../static/img/timeslider_right.png);
height: 63px;
position: absolute;
right: 0;
top: 0;
width: 155px;
}
#timeslider {
background-image: url(../../static/img/timeslider_background.png);
height: 63px;
margin: 0 9px;
}
#timeslider #timeslider-slider {
height: 61px;
left: 0;
position: absolute;
top: 1px;
width: 100%;
}
#ui-slider-handle {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
background-image: url(../../static/img/crushed_current_location.png);
cursor: pointer;
height: 61px;
left: 0;
position: absolute;
top: 0;
width: 13px;
}
#ui-slider-bar {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
cursor: pointer;
height: 35px;
margin-left: 5px;
margin-right: 148px;
position: relative;
top: 20px;
}
#playpause_button,
#playpause_button_icon {
height: 47px;
position: absolute;
width: 47px;
}
#playpause_button {
background-image: url(../../static/img/crushed_button_undepressed.png);
right: 77px;
top: 9px;
}
#playpause_button_icon {
background-image: url(../../static/img/play.png);
left: 0;
top: 0;
}
.pause#playpause_button_icon {
background-image: url(../../static/img/pause.png)
}
#leftstar,
#rightstar,
#leftstep,
#rightstep {
background: url(../../static/img/stepper_buttons.png) 0 0 no-repeat;
height: 21px;
overflow: hidden;
position: absolute;
}
#leftstar {
background-position: 0 -44px;
right: 34px;
top: 8px;
width: 30px;
}
#rightstar {
background-position: -29px -44px;
right: 5px;
top: 8px;
width: 29px;
}
#leftstep {
background-position: 0 -22px;
right: 34px;
top: 20px;
width: 30px;
}
#rightstep {
background-position: -29px -22px;
right: 5px;
top: 20px;
width: 29px;
}
#timeslider .star {
background-image: url(../../static/img/star.png);
cursor: pointer;
height: 16px;
position: absolute;
top: 40px;
width: 15px;
}
#timeslider #timer {
color: #fff;
font-family: Arial, sans-serif;
font-size: 11px;
left: 7px;
position: absolute;
text-align: center;
top: 9px;
width: 122px;
}
.topbarcenter,
#docbar {
display: none
}
#padmain {
top: 0px !important
}
#editbarright {
float: right
}
#returnbutton {
color: #222;
font-size: 16px;
line-height: 29px;
margin-top: 0;
padding-right: 6px;
}
#importexport .popup {
width: 185px
}
#importexport {
top: 118px;
width: 185px;
}
.timeslider-bar {
background: #f7f7f7;
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
overflow: hidden;
padding-top: 3px;
width: 100%;
}
.timeslider-bar #editbar {
border-bottom: none;
float: right;
width: 170px;
width: initial;
}
.timeslider-bar h1 {
margin: 5px
}
.timeslider-bar p {
margin: 5px
}
#timeslider-top {
width: 100%;
position: fixed;
z-index: 1;
}
#authorsList .author {
padding-left: 0.4em;
padding-right: 0.4em;
}
#authorsList .author-anonymous {
padding-left: 0.6em;
padding-right: 0.6em;
}
#padeditor {
position: static
}
/* lists */
.list-bullet2,
.list-indent2,
.list-number2 {
margin-left: 3em
}
.list-bullet3,
.list-indent3,
.list-number3 {
margin-left: 4.5em
}
.list-bullet4,
.list-indent4,
.list-number4 {
margin-left: 6em
}
.list-bullet5,
.list-indent5,
.list-number5 {
margin-left: 7.5em
}
.list-bullet6,
.list-indent6,
.list-number6 {
margin-left: 9em
}
.list-bullet7,
.list-indent7,
.list-number7 {
margin-left: 10.5em
}
.list-bullet8,
.list-indent8,
.list-number8 {
margin-left: 12em
}
/* unordered lists */
UL {
list-style-type: disc;
margin-left: 1.5em;
}
UL UL {
margin-left: 0 !important
}
.list-bullet2,
.list-bullet5,
.list-bullet8 {
list-style-type: circle
}
.list-bullet3,
.list-bullet6 {
list-style-type: square
}
.list-indent1,
.list-indent2,
.list-indent3,
.list-indent5,
.list-indent5,
.list-indent6,
.list-indent7,
.list-indent8 {
list-style-type: none
}
/* ordered lists */
OL {
list-style-type: decimal;
margin-left: 1.5em;
}
.list-number2,
.list-number5,
.list-number8 {
list-style-type: lower-latin
}
.list-number3,
.list-number6 {
list-style-type: lower-roman
}
/* IE 6/7 fixes */
* HTML #ui-slider-handle {
background-image: url(../../static/img/current_location.gif)
}
* HTML #timeslider .star {
background-image: url(../../static/img/star.gif)
}
* HTML #playpause_button_icon {
background-image: url(../../static/img/play.gif)
}
* HTML .pause#playpause_button_icon {
background-image: url(../../static/img/pause.gif)
}

3
src/static/custom/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!.gitignore
!*.template

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 697 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1009 B

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 494 B

View File

Before

Width:  |  Height:  |  Size: 658 B

After

Width:  |  Height:  |  Size: 658 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 123 B

After

Width:  |  Height:  |  Size: 123 B

View File

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 131 B

BIN
src/static/img/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 182 B

After

Width:  |  Height:  |  Size: 182 B

View File

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 686 B

View File

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,164 @@
var Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore');
var lineMarkerAttribute = 'lmkr';
// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list'];
/*
The Attribute manager builds changesets based on a document
representation for setting and removing range or line-based attributes.
@param rep the document representation to be used
@param applyChangesetCallback this callback will be called
once a changeset has been built.
A document representation contains
- an array `alines` containing 1 attributes string for each line
- an Attribute pool `apool`
- a SkipList `lines` containing the text lines of the document.
*/
var AttributeManager = function(rep, applyChangesetCallback)
{
this.rep = rep;
this.applyChangesetCallback = applyChangesetCallback;
this.author = '';
// If the first char in a line has one of the following attributes
// it will be considered as a line marker
};
AttributeManager.lineAttributes = lineAttributes;
AttributeManager.prototype = _(AttributeManager.prototype).extend({
applyChangeset: function(changeset){
if(!this.applyChangesetCallback) return changeset;
var cs = changeset.toString();
if (!Changeset.isIdentity(cs))
{
this.applyChangesetCallback(cs);
}
return changeset;
},
/*
Sets attributes on a range
@param start [row, col] tuple pointing to the start of the range
@param end [row, col] tuple pointing to the end of the range
@param attribute: an array of attributes
*/
setAttributesOnRange: function(start, end, attribs)
{
var builder = Changeset.builder(this.rep.lines.totalWidth());
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
return this.applyChangeset(builder);
},
/*
Returns if the line already has a line marker
@param lineNum: the number of the line
*/
lineHasMarker: function(lineNum){
var that = this;
return _.find(lineAttributes, function(attribute){
return that.getAttributeOnLine(lineNum, attribute) != '';
}) !== undefined;
},
/*
Gets a specified attribute on a line
@param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to get, e.g. list
*/
getAttributeOnLine: function(lineNum, attributeName){
// get `attributeName` attribute of first char of line
var aline = this.rep.alines[lineNum];
if (aline)
{
var opIter = Changeset.opIterator(aline);
if (opIter.hasNext())
{
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
}
}
return '';
},
/*
Sets a specified attribute on a line
@param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to set, e.g. list
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
*/
setAttributeOnLine: function(lineNum, attributeName, attributeValue){
var loc = [0,0];
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
if(hasMarker){
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
[attributeName, attributeValue]
], this.rep.apool);
}else{
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
[attributeName, attributeValue]
], this.rep.apool);
}
return this.applyChangeset(builder);
},
/*
Removes a specified attribute on a line
@param lineNum: the number of the affected line
@param attributeKey: the name of the attribute to remove, e.g. list
*/
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
var loc = [0,0];
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
if(hasMarker){
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1]));
}
return this.applyChangeset(builder);
},
/*
Sets a specified attribute on a line
@param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to set, e.g. list
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
*/
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
return this.getAttributeOnLine(attributeName) ?
this.removeAttributeOnLine(lineNum, attributeName) :
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
}
});
module.exports = AttributeManager;

View File

@ -0,0 +1,96 @@
/**
* This code represents the Attribute Pool Object of the original Etherpad.
* 90% of the code is still like in the original Etherpad
* Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
* You can find a explanation what a attribute pool is here:
* https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
*/
/*
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
An AttributePool maintains a mapping from [key,value] Pairs called
Attributes to Numbers (unsigened integers) and vice versa. These numbers are
used to reference Attributes in Changesets.
*/
var AttributePool = function () {
this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
this.attribToNum = {}; // e.g. {'foo,bar': 0}
this.nextNum = 0;
};
AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
var str = String(attrib);
if (str in this.attribToNum) {
return this.attribToNum[str];
}
if (dontAddIfAbsent) {
return -1;
}
var num = this.nextNum++;
this.attribToNum[str] = num;
this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
return num;
};
AttributePool.prototype.getAttrib = function (num) {
var pair = this.numToAttrib[num];
if (!pair) {
return pair;
}
return [pair[0], pair[1]]; // return a mutable copy
};
AttributePool.prototype.getAttribKey = function (num) {
var pair = this.numToAttrib[num];
if (!pair) return '';
return pair[0];
};
AttributePool.prototype.getAttribValue = function (num) {
var pair = this.numToAttrib[num];
if (!pair) return '';
return pair[1];
};
AttributePool.prototype.eachAttrib = function (func) {
for (var n in this.numToAttrib) {
var pair = this.numToAttrib[n];
func(pair[0], pair[1]);
}
};
AttributePool.prototype.toJsonable = function () {
return {
numToAttrib: this.numToAttrib,
nextNum: this.nextNum
};
};
AttributePool.prototype.fromJsonable = function (obj) {
this.numToAttrib = obj.numToAttrib;
this.nextNum = obj.nextNum;
this.attribToNum = {};
for (var n in this.numToAttrib) {
this.attribToNum[String(this.numToAttrib[n])] = Number(n);
}
return this;
};
module.exports = AttributePool;

View File

@ -25,7 +25,7 @@
* limitations under the License. * limitations under the License.
*/ */
var AttributePoolFactory = require("/AttributePoolFactory"); var AttributePool = require("./AttributePool");
var _opt = null; var _opt = null;
@ -1731,7 +1731,7 @@ exports.appendATextToAssembler = function (atext, assem) {
* @param pool {AtributePool} * @param pool {AtributePool}
*/ */
exports.prepareForWire = function (cs, pool) { exports.prepareForWire = function (cs, pool) {
var newPool = AttributePoolFactory.createAttributePool();; var newPool = new AttributePool();
var newCs = exports.moveOpsToNewPool(cs, pool, newPool); var newCs = exports.moveOpsToNewPool(cs, pool, newPool);
return { return {
translated: newCs, translated: newCs,

View File

@ -0,0 +1,60 @@
/**
* This module contains several helper Functions to build Changesets
* based on a SkipList
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.buildRemoveRange = function(rep, builder, start, end)
{
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0])
{
builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
builder.remove(end[1]);
}
else
{
builder.remove(end[1] - start[1]);
}
}
exports.buildKeepRange = function(rep, builder, start, end, attribs, pool)
{
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0])
{
builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
builder.keep(end[1], 0, attribs, pool);
}
else
{
builder.keep(end[1] - start[1], 0, attribs, pool);
}
}
exports.buildKeepToStartOfRange = function(rep, builder, start)
{
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
builder.keep(startLineOffset, start[0]);
builder.keep(start[1]);
}

View File

@ -28,7 +28,8 @@ Ace2Editor.registry = {
nextId: 1 nextId: 1
}; };
var plugins = require('/plugins').plugins; var hooks = require('./pluginfw/hooks');
var _ = require('./underscore');
function Ace2Editor() function Ace2Editor()
{ {
@ -70,7 +71,7 @@ function Ace2Editor()
function doActionsPendingInit() function doActionsPendingInit()
{ {
$.each(actionsPendingInit, function(i,fn){ _.each(actionsPendingInit, function(fn,i){
fn() fn()
}); });
actionsPendingInit = []; actionsPendingInit = [];
@ -87,7 +88,7 @@ function Ace2Editor()
'setUserChangeNotificationCallback', 'setAuthorInfo', 'setUserChangeNotificationCallback', 'setAuthorInfo',
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange']; 'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange'];
$.each(aceFunctionsPendingInit, function(i,fnName){ _.each(aceFunctionsPendingInit, function(fnName,i){
var prefix = 'ace_'; var prefix = 'ace_';
var name = prefix + fnName; var name = prefix + fnName;
editor[fnName] = pendingInit(function(){ editor[fnName] = pendingInit(function(){
@ -156,28 +157,38 @@ function Ace2Editor()
} }
function pushRequireScriptTo(buffer) { function pushRequireScriptTo(buffer) {
var KERNEL_SOURCE = '../static/js/require-kernel.js'; var KERNEL_SOURCE = '../static/js/require-kernel.js';
var KERNEL_BOOT = 'require.setRootURI("../minified/");\nrequire.setGlobalKeyPath("require");' var KERNEL_BOOT = '\
require.setRootURI("../javascripts/src");\n\
require.setLibraryURI("../javascripts/lib");\n\
require.setGlobalKeyPath("require");\n\
';
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) { if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
buffer.push('<script type="text/javascript">'); buffer.push('<script type="text/javascript">');
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. */
/* $$INCLUDE_JS("../minified/ace2_inner.js?callback=require.define"); */ /* $$INCLUDE_JS("../javascripts/lib/ep_etherpad-lite/static/js/ace2_inner.js?callback=require.define"); */
var ACE_SOURCE = '../minified/ace2_inner.js?callback=require.define'; /* $$INCLUDE_JS("../javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define"); */
var ACE_SOURCE = '../javascripts/lib/ep_etherpad-lite/static/js/ace2_inner.js?callback=require.define';
var ACE_COMMON = '../javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define';
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[ACE_SOURCE]) { if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[ACE_SOURCE]) {
buffer.push('<script type="text/javascript">'); buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[ACE_SOURCE]); buffer.push(Ace2Editor.EMBEDED[ACE_SOURCE]);
buffer.push('require("/ace2_inner");'); buffer.push(Ace2Editor.EMBEDED[ACE_COMMON]);
buffer.push('<\/script>'); buffer.push('<\/script>');
} else { } else {
buffer.push('<script type="application/javascript" src="' + ACE_SOURCE + '"><\/script>'); buffer.push('<script type="application/javascript" src="' + ACE_SOURCE + '"><\/script>');
buffer.push('<script type="text/javascript">'); buffer.push('<script type="application/javascript" src="' + ACE_COMMON + '"><\/script>');
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
} }
} }
function pushStyleTagsFor(buffer, files) { function pushStyleTagsFor(buffer, files) {
@ -228,16 +239,9 @@ function Ace2Editor()
iframeHTML.push(doctype); iframeHTML.push(doctype);
iframeHTML.push("<html><head>"); iframeHTML.push("<html><head>");
// For compatability's sake transform in and out. hooks.callAll("aceInitInnerdocbodyHead", {
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
plugins.callHook("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML iframeHTML: iframeHTML
}); });
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.parse(iframeHTML[i]);
}
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed // calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the // and compressed, putting the compressed code from the named file directly into the
@ -248,21 +252,29 @@ function Ace2Editor()
$$INCLUDE_CSS("../static/css/iframe_editor.css"); $$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css"); $$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css"); $$INCLUDE_CSS("../static/custom/pad.css");
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ return '../static/plugins/' + path });
includedCSS = includedCSS.concat(additionalCSS);
pushStyleTagsFor(iframeHTML, includedCSS); pushStyleTagsFor(iframeHTML, includedCSS);
var includedJS = []; var includedJS = [];
var $$INCLUDE_JS = function(filename) {includedJS.push(filename)};
pushRequireScriptTo(iframeHTML); pushRequireScriptTo(iframeHTML);
pushScriptsTo(iframeHTML);
// Inject my plugins into my child. // Inject my plugins into my child.
iframeHTML.push('\ iframeHTML.push('\
<script type="text/javascript">\ <script type="text/javascript">\
require.define("/plugins", null);\n\ parent_req = require("ep_etherpad-lite/static/js/pluginfw/parent_require");\
require.define("/plugins.js", function (require, exports, module) {\ parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/hooks");\
module.exports = parent.parent.require("/plugins");\ parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/plugins");\
});\
</script>\ </script>\
'); ');
pushScriptsTo(iframeHTML);
iframeHTML.push('<script type="text/javascript">');
iframeHTML.push('$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK');
iframeHTML.push('require("ep_etherpad-lite/static/js/ace2_inner");');
iframeHTML.push('<\/script>');
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>'); iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>'); iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>');
@ -271,7 +283,7 @@ function Ace2Editor()
var thisFunctionsName = "ChildAccessibleAce2Editor"; var thisFunctionsName = "ChildAccessibleAce2Editor";
(function () {return this}())[thisFunctionsName] = Ace2Editor; (function () {return this}())[thisFunctionsName] = Ace2Editor;
var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); ' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); iframe.name = "ace_inner";' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }'; 'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }';
var outerHTML = [doctype, '<html><head>'] var outerHTML = [doctype, '<html><head>']
@ -281,6 +293,11 @@ function Ace2Editor()
$$INCLUDE_CSS("../static/css/iframe_editor.css"); $$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css"); $$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css"); $$INCLUDE_CSS("../static/custom/pad.css");
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ return '../static/plugins/' + path });
includedCSS = includedCSS.concat(additionalCSS);
pushStyleTagsFor(outerHTML, includedCSS); pushStyleTagsFor(outerHTML, includedCSS);
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
@ -288,6 +305,7 @@ function Ace2Editor()
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>'); outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
var outerFrame = document.createElement("IFRAME"); var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer";
outerFrame.frameBorder = 0; // for IE outerFrame.frameBorder = 0; // for IE
info.frame = outerFrame; info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame); document.getElementById(containerId).appendChild(outerFrame);

View File

@ -20,7 +20,7 @@
* limitations under the License. * limitations under the License.
*/ */
var Security = require('/security'); var Security = require('./security');
function isNodeText(node) function isNodeText(node)
{ {
@ -29,58 +29,10 @@ function isNodeText(node)
function object(o) function object(o)
{ {
var f = function() var f = function(){};
{};
f.prototype = o; f.prototype = o;
return new f(); return new f();
} }
function extend(obj, props)
{
for (var p in props)
{
obj[p] = props[p];
}
return obj;
}
function forEach(array, func)
{
for (var i = 0; i < array.length; i++)
{
var result = func(array[i], i);
if (result) break;
}
}
function map(array, func)
{
var result = [];
// must remain compatible with "arguments" pseudo-array
for (var i = 0; i < array.length; i++)
{
if (func) result.push(func(array[i], i));
else result.push(array[i]);
}
return result;
}
function filter(array, func)
{
var result = [];
// must remain compatible with "arguments" pseudo-array
for (var i = 0; i < array.length; i++)
{
if (func(array[i], i)) result.push(array[i]);
}
return result;
}
function isArray(testObject)
{
return testObject && typeof testObject === 'object' && !(testObject.propertyIsEnumerable('length')) && typeof testObject.length === 'number';
}
var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase(); var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase();
// Figure out what browser is being used (stolen from jquery 1.2.1) // Figure out what browser is being used (stolen from jquery 1.2.1)
@ -142,21 +94,13 @@ function htmlPrettyEscape(str)
} }
var noop = function(){}; var noop = function(){};
var identity = function(x){return x};
exports.isNodeText = isNodeText; exports.isNodeText = isNodeText;
exports.object = object; exports.object = object;
exports.extend = extend;
exports.forEach = forEach;
exports.map = map;
exports.filter = filter;
exports.isArray = isArray;
exports.browser = browser; exports.browser = browser;
exports.getAssoc = getAssoc; exports.getAssoc = getAssoc;
exports.setAssoc = setAssoc; exports.setAssoc = setAssoc;
exports.binarySearch = binarySearch; exports.binarySearch = binarySearch;
exports.binarySearchInfinite = binarySearchInfinite; exports.binarySearchInfinite = binarySearchInfinite;
exports.htmlPrettyEscape = htmlPrettyEscape; exports.htmlPrettyEscape = htmlPrettyEscape;
exports.map = map;
exports.noop = noop; exports.noop = noop;
exports.identity = identity;

File diff suppressed because it is too large Load Diff

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

@ -20,16 +20,13 @@
* limitations under the License. * limitations under the License.
*/ */
var makeCSSManager = require('/cssmanager').makeCSSManager; var makeCSSManager = require('./cssmanager').makeCSSManager;
var domline = require('/domline').domline; var domline = require('./domline').domline;
var AttribPool = require('/AttributePoolFactory').createAttributePool; var AttribPool = require('./AttributePool');
var Changeset = require('/Changeset'); var Changeset = require('./Changeset');
var linestylefilter = require('/linestylefilter').linestylefilter; var linestylefilter = require('./linestylefilter').linestylefilter;
var colorutils = require('/colorutils').colorutils; var colorutils = require('./colorutils').colorutils;
var Ace2Common = require('./ace2_common'); var _ = require('./underscore');
var map = Ace2Common.map;
var forEach = Ace2Common.forEach;
// These parameters were global, now they are injected. A reference to the // These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate. // Timeslider controller would probably be more appropriate.
@ -155,7 +152,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// splice the lines // splice the lines
splice: function(start, numRemoved, newLinesVA) splice: function(start, numRemoved, newLinesVA)
{ {
var newLines = map(Array.prototype.slice.call(arguments, 2), function(s) { var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) {
return s; return s;
}); });
@ -278,7 +275,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
debugLog('Time Delta: ', timeDelta) debugLog('Time Delta: ', timeDelta)
updateTimer(); updateTimer();
var authors = map(padContents.getActiveAuthors(), function(name) var authors = _.map(padContents.getActiveAuthors(), function(name)
{ {
return authorData[name]; return authorData[name];
}); });
@ -384,7 +381,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
changesetLoader.queueUp(start, 1, update); changesetLoader.queueUp(start, 1, update);
} }
var authors = map(padContents.getActiveAuthors(), function(name){ var authors = _.map(padContents.getActiveAuthors(), function(name){
return authorData[name]; return authorData[name];
}); });
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
@ -527,7 +524,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
authorMap[obj.author] = obj.data; authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap); receiveAuthorData(authorMap);
var authors = map(padContents.getActiveAuthors(),function(name) { var authors = _.map(padContents.getActiveAuthors(), function(name) {
return authorData[name]; return authorData[name];
}); });
@ -607,10 +604,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
setChannelState("DISCONNECTED", reason); setChannelState("DISCONNECTED", reason);
} }
/// Since its not used, import 'forEach' has been dropped
/*window['onloadFuncts'] = []; /*window['onloadFuncts'] = [];
window.onload = function () window.onload = function ()
{ {
window['isloaded'] = true; window['isloaded'] = true;
forEach(window['onloadFuncts'],function (funct) forEach(window['onloadFuncts'],function (funct)
{ {
funct(); funct();

View File

@ -22,7 +22,7 @@
// These parameters were global, now they are injected. A reference to the // These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate. // Timeslider controller would probably be more appropriate.
var forEach = require('./ace2_common').forEach; var _ = require('./underscore');
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{ {
@ -162,49 +162,72 @@ 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();
} }
var fixPadHeight = _.throttle(function(){
var height = $('#timeslider-top').height();
$('#editorcontainerbox').css({marginTop: height});
}, 600);
function setAuthors(authors) function setAuthors(authors)
{ {
$("#authorstable").empty(); var authorsList = $("#authorsList");
authorsList.empty();
var numAnonymous = 0; var numAnonymous = 0;
var numNamed = 0; var numNamed = 0;
forEach(authors, function(author) var colorsAnonymous = [];
_.each(authors, function(author)
{ {
var authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
if (author.name) if (author.name)
{ {
if (numNamed !== 0) authorsList.append(', ');
$('<span />')
.text(author.name || "unnamed")
.css('background-color', authorColor)
.addClass('author')
.appendTo(authorsList);
numNamed++; numNamed++;
var tr = $('<tr></tr>');
var swatchtd = $('<td></td>');
var swatch = $('<div class="swatch"></div>');
swatch.css('background-color', clientVars.colorPalette[author.colorId]);
swatchtd.append(swatch);
tr.append(swatchtd);
var nametd = $('<td></td>');
nametd.text(author.name || "unnamed");
tr.append(nametd);
$("#authorstable").append(tr);
} }
else else
{ {
numAnonymous++; numAnonymous++;
if(authorColor) colorsAnonymous.push(authorColor);
} }
}); });
if (numAnonymous > 0) if (numAnonymous > 0)
{ {
var html = "<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">" + (numNamed > 0 ? "...and " : "") + numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + "</td></tr>"; var anonymousAuthorString = numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "")
$("#authorstable").append($(html)); if (numNamed !== 0){
authorsList.append(' + ' + anonymousAuthorString);
} else {
authorsList.append(anonymousAuthorString);
}
if(colorsAnonymous.length > 0){
authorsList.append(' (');
_.each(colorsAnonymous, function(color, i){
if( i > 0 ) authorsList.append(' ');
$('<span>&nbsp;</span>')
.css('background-color', color)
.addClass('author author-anonymous')
.appendTo(authorsList);
});
authorsList.append(')');
}
} }
if (authors.length == 0) if (authors.length == 0)
{ {
$("#authorstable").append($("<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">No Authors</td></tr>")) authorsList.append("No Authors");
} }
fixPadHeight();
} }
BroadcastSlider = { BroadcastSlider = {
@ -261,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();
@ -459,38 +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);
{ })
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
forEach(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

@ -20,8 +20,8 @@
* limitations under the License. * limitations under the License.
*/ */
var AttribPool = require('/AttributePoolFactory').createAttributePool; var AttributePool = require('./AttributePool');
var Changeset = require('/Changeset'); var Changeset = require('./Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
{ {
@ -83,7 +83,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
baseAText = Changeset.cloneAText(atext); baseAText = Changeset.cloneAText(atext);
if (apoolJsonObj) if (apoolJsonObj)
{ {
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool); baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
} }
submittedChangeset = null; submittedChangeset = null;
@ -117,7 +117,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
if (apoolJsonObj) if (apoolJsonObj)
{ {
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool); c = Changeset.moveOpsToNewPool(c, wireApool, apool);
} }

Some files were not shown because too many files have changed in this diff Show More