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
settings.json
static/js/jquery.js
static/js/prefixfree.js
APIKEY.txt
bin/abiword.exe
bin/node.exe
@ -10,4 +8,7 @@ var/dirty.db
bin/convertSettings.json
*~
*.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)
{
console.error("Use: node checkPad.js $PADID");
console.error("Use: node bin/checkPad.js $PADID");
process.exit(1);
}
//get the padID
var padId = process.argv[2];
//initalize the database
var log4js = require("log4js");
var log4js = require("../src/node_modules/log4js");
log4js.setGlobalLogLevel("INFO");
var async = require("async");
var db = require('../node/db/DB');
var CommonCode = require('../node/utils/common_code');
var Changeset = CommonCode.require("/Changeset");
var async = require("../src/node_modules/async");
var db = require('../src/node/db/DB');
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager;
async.series([
@ -28,7 +28,7 @@ async.series([
//get the pad
function (callback)
{
padManager = require('../node/db/PadManager');
padManager = require('../src/node/db/PadManager');
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 fs = require("fs");
var ueberDB = require("ueberDB");
var mysql = require("mysql");
var async = require("async");
var Changeset = CommonCode.require("/Changeset");
var randomString = CommonCode.require('/pad_utils').randomString;
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var settingsFile = process.argv[2];
var sqlOutputFile = process.argv[3];
@ -384,7 +384,7 @@ function convertPad(padId, callback)
}
//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 atext = changesetsMeta[keyRev].atext;
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"
cd "node"
node --debug server.js
node --debug node_modules/ep_etherpad-lite/node/server.js $*
#kill node-inspector before ending
kill $!

View File

@ -55,7 +55,13 @@ if [ ! -f $settings ]; then
fi
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
exit 1
}
@ -63,8 +69,8 @@ npm install || {
echo "Ensure jQuery is downloaded and up to date..."
DOWNLOAD_JQUERY="true"
NEEDED_VERSION="1.7.1"
if [ -f "static/js/jquery.js" ]; then
VERSION=$(cat static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");
if [ -f "src/static/js/jquery.js" ]; then
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
DOWNLOAD_JQUERY="false"
@ -72,22 +78,7 @@ if [ -f "static/js/jquery.js" ]; then
fi
if [ $DOWNLOAD_JQUERY = "true" ]; then
curl -lo 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
curl -lo src/static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
fi
#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"
do
if [ ! -f "static/custom/$f.js" ]; then
cp -v "static/custom/js.template" "static/custom/$f.js" || exit 1
if [ ! -f "src/static/custom/$f.js" ]; then
cp -v "src/static/custom/js.template" "src/static/custom/$f.js" || exit 1
fi
if [ ! -f "static/custom/$f.css" ]; then
cp -v "static/custom/css.template" "static/custom/$f.css" || exit 1
if [ ! -f "src/static/custom/$f.css" ]; then
cp -v "src/static/custom/css.template" "src/static/custom/$f.css" || exit 1
fi
done

View File

@ -25,5 +25,4 @@ bin/installDeps.sh $* || exit 1
#Move to the node folder and start
echo "start..."
cd "node"
node server.js $*
node node_modules/ep_etherpad-lite/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",
//the database specific settings
"dbSettings" : {
"filename" : "../var/dirty.db"
"filename" : "var/dirty.db"
},
/* An Example of MySQL Configuration
@ -39,16 +39,35 @@
but makes it impossible to debug the javascript/css */
"minify" : true,
/* How long may clients use served javascript code? Without versioning this
is may cause problems during deployment. */
"maxAge" : 21600000, // 6 hours
/* How long may clients use served javascript code (in seconds)? Without versioning this
may cause problems during deployment. Set to 0 to disable caching */
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/
"abiword" : null,
/* This setting is used if you need http basic auth */
// "httpAuth" : "user:pass",
/* This setting is used if you require authentication of all users.
Note: /admin always requires authentication. */
"requireAuthentication": false,
/* Require authorization by a module, or a user with is_admin set, see below. */
"requireAuthorization": false,
/* Users for basic authentication. is_admin = true gives access to /admin.
If you do not uncomment this, /admin will not be available! */
/*
"users": {
"admin": {
"password": "changeme1",
"is_admin": true
},
"user": {
"password": "changeme1",
"is_admin": false
}
},
*/
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO"

View File

@ -12,7 +12,7 @@
"dbType" : "dirty",
//the database specific settings
"dbSettings" : {
"filename" : "../var/dirty.db"
"filename" : "var/dirty.db"
},
/* An Example of MySQL Configuration
@ -40,5 +40,9 @@
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/
"abiword" : null
"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;
//set the password
pad.setPassword(password);
pad.setPassword(password == "" ? null : password);
callback();
});

View File

@ -18,11 +18,11 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
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

View File

@ -18,10 +18,10 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
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 async = require("async");
var padManager = require("./PadManager");

View File

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

View File

@ -18,11 +18,11 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
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

View File

@ -18,7 +18,7 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
@ -26,7 +26,7 @@ var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
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.

View File

@ -18,10 +18,10 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
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 async = require("async");
var groupMangager = require("./GroupManager");

View File

@ -20,9 +20,9 @@
* limitations under the License.
*/
var CommonCode = require('./utils/common_code');
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
function random() {
this.nextInt = function (maxValue) {
@ -227,7 +227,7 @@ function runTests() {
return attribs; // it's already an attrib pool
} else {
// 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) {
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"]);
var testPoolWithChars = (function () {
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
p.putAttrib(['char', 'newline']);
for (var i = 1; i < 36; i++) {
p.putAttrib(['char', Changeset.numToString(i)]);
@ -560,7 +560,7 @@ function runTests() {
var rand = new random();
print("> testCompose#" + randomSeed);
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
var startText = randomMultiline(10, 20, rand) + '\n';
@ -594,7 +594,7 @@ function runTests() {
(function simpleComposeAttributesTest() {
print("> simpleComposeAttributesTest");
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
p.putAttrib(['bold', '']);
p.putAttrib(['bold', 'true']);
var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x");
@ -604,7 +604,7 @@ function runTests() {
})();
(function followAttributesTest() {
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
p.putAttrib(['x', '']);
p.putAttrib(['x', 'abc']);
p.putAttrib(['x', 'def']);
@ -633,7 +633,7 @@ function runTests() {
var rand = new random();
print("> testFollow#" + randomSeed);
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
var startText = randomMultiline(10, 20, rand) + '\n';
@ -682,8 +682,8 @@ function runTests() {
(function testMoveOpsToNewPool() {
print("> testMoveOpsToNewPool");
var pool1 = AttributePoolFactory.createAttributePool();
var pool2 = AttributePoolFactory.createAttributePool();
var pool1 = new AttributePool();
var pool2 = new AttributePool();
pool1.putAttrib(['baz', 'qux']);
pool1.putAttrib(['foo', 'bar']);
@ -738,7 +738,7 @@ function runTests() {
(function testOpAttributeValue() {
print("> testOpAttributeValue");
var p = AttributePoolFactory.createAttributePool();
var p = new AttributePool();
p.putAttrib(['name', 'david']);
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.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var fs = require("fs");
var api = require("../db/API");
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
var apikey = null;
try
{
apikey = fs.readFileSync("../APIKEY.txt","utf8");
apikey = fs.readFileSync("./APIKEY.txt","utf8");
}
catch(e)
{
apikey = randomString(32);
fs.writeFileSync("../APIKEY.txt",apikey,"utf8");
fs.writeFileSync("./APIKEY.txt",apikey,"utf8");
}
//a list of all functions

View File

@ -196,6 +196,6 @@ exports.doImport = function(req, res, padId)
ERR(err);
//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.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var AttributeManager = require("ep_etherpad-lite/static/js/AttributeManager");
var authorManager = require("../db/AuthorManager");
var readOnlyManager = require("../db/ReadOnlyManager");
var settings = require('../utils/Settings');
var securityManager = require("../db/SecurityManager");
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins.js");
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var _ = require('underscore');
/**
* 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
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
* @param client the client that send this message
@ -366,7 +390,7 @@ function handleUserChanges(client, message)
//get all Vars we need
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 r, apool, pad;
@ -563,8 +587,12 @@ function _correctMarkersInPad(atext, apool) {
var offset = 0;
while (iter.hasNext()) {
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++) {
if (offset > 0 && text.charAt(offset-1) != '\n') {
badMarkers.push(offset);
@ -736,9 +764,10 @@ function handleClientReady(client, message)
{
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
},
"abiwordAvailable": settings.abiwordAvailable(),
"hooks": {}
"plugins": {
"plugins": plugins.plugins,
"parts": plugins.parts,
}
}
//Add a username to the clientVars if one avaiable
if(authorName != null)
{

View File

@ -18,12 +18,12 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var settings = require('../utils/Settings');
var authorManager = require("../db/AuthorManager");
var log4js = require('log4js');
@ -155,8 +155,6 @@ function createTimesliderClientVars (padId, callback)
var clientVars = {
viewId: padId,
colorPalette: ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"],
sliderEnabled : true,
supportsSlider: true,
savedRevisions: [],
padIdForUrl: padId,
fullWidth: false,
@ -166,6 +164,7 @@ function createTimesliderClientVars (padId, callback)
hooks: [],
initialStyledContents: {}
};
var pad;
var initialChangesets = [];
@ -180,6 +179,12 @@ function createTimesliderClientVars (padId, callback)
callback();
});
},
//get all saved revisions and add them
function(callback)
{
clientVars.savedRevisions = pad.getSavedRevisions();
callback();
},
//get all authors and add them to
function(callback)
{
@ -265,7 +270,7 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
var forwardsChangesets = [];
var backwardsChangesets = [];
var timeDeltas = [];
var apool = AttributePoolFactory.createAttributePool();
var apool = new AttributePool();
var pad;
var composedChangesets = {};
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 CommonCode = require('./common_code');
var Changeset = CommonCode.require("/Changeset");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
function getPadDokuWiki(pad, revNum, callback)

View File

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

View File

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

View File

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

View File

@ -23,6 +23,10 @@ var fs = require("fs");
var os = require("os");
var path = require('path');
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
@ -40,7 +44,7 @@ exports.dbType = "dirty";
/**
* 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
*/
@ -76,10 +80,12 @@ exports.abiword = null;
*/
exports.loglevel = "INFO";
/**
* Http basic auth, with "user:password" format
*/
exports.httpAuth = null;
/* This setting is used if you need authentication and/or
* authorization. Note: /admin always requires authentication, and
* either authorization by a module, or a user with is_admin set */
exports.requireAuthentication = false;
exports.requireAuthorization = false;
exports.users = {};
//checks if abiword is avaiable
exports.abiwordAvailable = function()
@ -96,11 +102,19 @@ exports.abiwordAvailable = function()
// Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json";
var settingsPath = settingsFilename.charAt(0) == '/' ? '' : path.normalize(__dirname + "/../../");
//read the settings sync
var settingsStr = fs.readFileSync(settingsPath + settingsFilename).toString();
if (settingsFilename.charAt(0) != '/') {
settingsFilename = path.normalize(path.join(root, settingsFilename));
}
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
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");
}
}
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 fs = require('fs');
var path = require('path');
var server = require('../server');
var zlib = require('zlib');
var util = require('util');
var settings = require('./Settings');
var ROOT_DIR = path.normalize(__dirname + "/../");
var CACHE_DIR = ROOT_DIR + '../var/';
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {};
@ -37,7 +37,7 @@ function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
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);
}
@ -73,6 +73,9 @@ CachingMiddleware.prototype = new function () {
var _headers = {};
old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) {
// Don't set cookies, see issue #707
if (key.toLowerCase() === 'set-cookie') return;
_headers[key.toLowerCase()] = 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": [
"jquery.js"
, "security.js"
, "pad.js"
, "ace2_common.js"
"pad.js"
, "pad_utils.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
, "pad_cookie.js"
, "pad_editor.js"
, "pad_editbar.js"
@ -22,17 +16,11 @@
, "chat.js"
, "excanvas.js"
, "farbtastic.js"
, "prefixfree.js"
]
, "timeslider.js": [
"jquery.js"
, "security.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
"timeslider.js"
, "colorutils.js"
, "draggable.js"
, "ace2_common.js"
, "pad_utils.js"
, "pad_cookie.js"
, "pad_editor.js"
@ -41,7 +29,7 @@
, "pad_modals.js"
, "pad_savedrevs.js"
, "pad_impexp.js"
, "AttributePoolFactory.js"
, "AttributePool.js"
, "Changeset.js"
, "domline.js"
, "linestylefilter.js"
@ -49,13 +37,12 @@
, "broadcast.js"
, "broadcast_slider.js"
, "broadcast_revisions.js"
, "timeslider.js"
]
, "ace2_inner.js": [
"ace2_common.js"
, "AttributePoolFactory.js"
"ace2_inner.js"
, "AttributePool.js"
, "Changeset.js"
, "security.js"
, "ChangesetUtils.js"
, "skiplist.js"
, "virtual_lines.js"
, "cssmanager.js"
@ -65,6 +52,18 @@
, "changesettracker.js"
, "linestylefilter.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",
"homepage" : "https://github.com/Pita/etherpad-lite",
"keywords" : ["etherpad", "realtime", "collaborative", "editor"],
@ -10,20 +10,29 @@
"name": "Robin Buse" }
],
"dependencies" : {
"yajsml" : "1.1.2",
"yajsml" : "1.1.3",
"request" : "2.9.100",
"require-kernel" : "1.0.3",
"socket.io" : "0.8.7",
"require-kernel" : "1.0.5",
"resolve" : "0.2.1",
"socket.io" : "0.9.6",
"ueberDB" : "0.1.7",
"async" : "0.1.18",
"express" : "2.5.8",
"connect" : "1.8.7",
"clean-css" : "0.3.2",
"uglify-js" : "1.2.5",
"formidable" : "1.0.9",
"log4js" : "0.4.1",
"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": {
"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) */
span { cursor: auto; }
::selection {
background: #acf;
}
::-moz-selection {
background: #acf;
}
a { cursor: pointer !important; }
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.
*/
var AttributePoolFactory = require("/AttributePoolFactory");
var AttributePool = require("./AttributePool");
var _opt = null;
@ -1731,7 +1731,7 @@ exports.appendATextToAssembler = function (atext, assem) {
* @param pool {AtributePool}
*/
exports.prepareForWire = function (cs, pool) {
var newPool = AttributePoolFactory.createAttributePool();;
var newPool = new AttributePool();
var newCs = exports.moveOpsToNewPool(cs, pool, newPool);
return {
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
};
var plugins = require('/plugins').plugins;
var hooks = require('./pluginfw/hooks');
var _ = require('./underscore');
function Ace2Editor()
{
@ -70,7 +71,7 @@ function Ace2Editor()
function doActionsPendingInit()
{
$.each(actionsPendingInit, function(i,fn){
_.each(actionsPendingInit, function(fn,i){
fn()
});
actionsPendingInit = [];
@ -87,7 +88,7 @@ function Ace2Editor()
'setUserChangeNotificationCallback', 'setAuthorInfo',
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange'];
$.each(aceFunctionsPendingInit, function(i,fnName){
_.each(aceFunctionsPendingInit, function(fnName,i){
var prefix = 'ace_';
var name = prefix + fnName;
editor[fnName] = pendingInit(function(){
@ -156,28 +157,38 @@ function Ace2Editor()
}
function pushRequireScriptTo(buffer) {
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]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
}
} else {
file = KERNEL_SOURCE;
buffer.push('<script type="application/javascript" src="' + KERNEL_SOURCE + '"><\/script>');
buffer.push('<script type="text/javascript">');
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
}
}
function pushScriptsTo(buffer) {
/* Folling is for packaging regular expression. */
/* $$INCLUDE_JS("../minified/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_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]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[ACE_SOURCE]);
buffer.push('require("/ace2_inner");');
buffer.push(Ace2Editor.EMBEDED[ACE_COMMON]);
buffer.push('<\/script>');
} else {
buffer.push('<script type="application/javascript" src="' + ACE_SOURCE + '"><\/script>');
buffer.push('<script type="text/javascript">');
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
buffer.push('<script type="application/javascript" src="' + ACE_COMMON + '"><\/script>');
}
}
function pushStyleTagsFor(buffer, files) {
@ -228,16 +239,9 @@ function Ace2Editor()
iframeHTML.push(doctype);
iframeHTML.push("<html><head>");
// For compatability's sake transform in and out.
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
plugins.callHook("aceInitInnerdocbodyHead", {
hooks.callAll("aceInitInnerdocbodyHead", {
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
// 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/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);
var includedJS = [];
var $$INCLUDE_JS = function(filename) {includedJS.push(filename)};
pushRequireScriptTo(iframeHTML);
pushScriptsTo(iframeHTML);
// Inject my plugins into my child.
iframeHTML.push('\
<script type="text/javascript">\
require.define("/plugins", null);\n\
require.define("/plugins.js", function (require, exports, module) {\
module.exports = parent.parent.require("/plugins");\
});\
parent_req = require("ep_etherpad-lite/static/js/pluginfw/parent_require");\
parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/hooks");\
parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/plugins");\
</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('</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>');
@ -271,7 +283,7 @@ function Ace2Editor()
var thisFunctionsName = "ChildAccessibleAce2Editor";
(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); }';
var outerHTML = [doctype, '<html><head>']
@ -281,6 +293,11 @@ function Ace2Editor()
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/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);
// 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>');
var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer";
outerFrame.frameBorder = 0; // for IE
info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame);

View File

@ -20,7 +20,7 @@
* limitations under the License.
*/
var Security = require('/security');
var Security = require('./security');
function isNodeText(node)
{
@ -29,58 +29,10 @@ function isNodeText(node)
function object(o)
{
var f = function()
{};
var f = function(){};
f.prototype = o;
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();
// Figure out what browser is being used (stolen from jquery 1.2.1)
@ -142,21 +94,13 @@ function htmlPrettyEscape(str)
}
var noop = function(){};
var identity = function(x){return x};
exports.isNodeText = isNodeText;
exports.object = object;
exports.extend = extend;
exports.forEach = forEach;
exports.map = map;
exports.filter = filter;
exports.isArray = isArray;
exports.browser = browser;
exports.getAssoc = getAssoc;
exports.setAssoc = setAssoc;
exports.binarySearch = binarySearch;
exports.binarySearchInfinite = binarySearchInfinite;
exports.htmlPrettyEscape = htmlPrettyEscape;
exports.map = map;
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.
*/
var makeCSSManager = require('/cssmanager').makeCSSManager;
var domline = require('/domline').domline;
var AttribPool = require('/AttributePoolFactory').createAttributePool;
var Changeset = require('/Changeset');
var linestylefilter = require('/linestylefilter').linestylefilter;
var colorutils = require('/colorutils').colorutils;
var Ace2Common = require('./ace2_common');
var map = Ace2Common.map;
var forEach = Ace2Common.forEach;
var makeCSSManager = require('./cssmanager').makeCSSManager;
var domline = require('./domline').domline;
var AttribPool = require('./AttributePool');
var Changeset = require('./Changeset');
var linestylefilter = require('./linestylefilter').linestylefilter;
var colorutils = require('./colorutils').colorutils;
var _ = require('./underscore');
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
@ -155,7 +152,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// splice the lines
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;
});
@ -278,7 +275,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
debugLog('Time Delta: ', timeDelta)
updateTimer();
var authors = map(padContents.getActiveAuthors(), function(name)
var authors = _.map(padContents.getActiveAuthors(), function(name)
{
return authorData[name];
});
@ -384,7 +381,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
changesetLoader.queueUp(start, 1, update);
}
var authors = map(padContents.getActiveAuthors(), function(name){
var authors = _.map(padContents.getActiveAuthors(), function(name){
return authorData[name];
});
BroadcastSlider.setAuthors(authors);
@ -527,7 +524,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
var authors = map(padContents.getActiveAuthors(),function(name) {
var authors = _.map(padContents.getActiveAuthors(), function(name) {
return authorData[name];
});
@ -607,10 +604,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
setChannelState("DISCONNECTED", reason);
}
/// Since its not used, import 'forEach' has been dropped
/*window['onloadFuncts'] = [];
window.onload = function ()
{
window['isloaded'] = true;
forEach(window['onloadFuncts'],function (funct)
{
funct();

View File

@ -22,7 +22,7 @@
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
var forEach = require('./ace2_common').forEach;
var _ = require('./underscore');
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{
@ -162,49 +162,72 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
function showReconnectUI()
{
if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
{
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
}
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
$('#error').show();
}
var fixPadHeight = _.throttle(function(){
var height = $('#timeslider-top').height();
$('#editorcontainerbox').css({marginTop: height});
}, 600);
function setAuthors(authors)
{
$("#authorstable").empty();
var authorsList = $("#authorsList");
authorsList.empty();
var numAnonymous = 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 (numNamed !== 0) authorsList.append(', ');
$('<span />')
.text(author.name || "unnamed")
.css('background-color', authorColor)
.addClass('author')
.appendTo(authorsList);
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
{
numAnonymous++;
if(authorColor) colorsAnonymous.push(authorColor);
}
});
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>";
$("#authorstable").append($(html));
var anonymousAuthorString = numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "")
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)
{
$("#authorstable").append($("<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">No Authors</td></tr>"))
authorsList.append("No Authors");
}
fixPadHeight();
}
BroadcastSlider = {
@ -261,55 +284,52 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{
disableSelection($("#playpause_button")[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;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var code = -1;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
if (code == 37)
{ // left
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 (code == 37)
{ // left
if (!e.shiftKey)
{
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);
}
setSliderPosition(getSliderPosition() - 1);
}
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()
{
updateSliderElements();
@ -459,38 +479,16 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
$("#revision").css('right', "20px");
$("#revision").css('top', "20px");
}
if (clientVars.sliderEnabled)
$("#timeslider").show();
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
_.each(clientVars.savedRevisions, function(revision)
{
if (clientVars.supportsSlider)
{
$("#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);
}
}
addSavedRevision(revision.revNum, revision);
})
}
});
})();

View File

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

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