Merge branch 'develop'
|
@ -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
|
||||
|
|
|
@ -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?
|
|
@ -0,0 +1,7 @@
|
|||
.git*
|
||||
docs/
|
||||
examples/
|
||||
support/
|
||||
test/
|
||||
testing.js
|
||||
.DS_Store
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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&&pad.editbarClick(\'clearauthorship\');return false;">\
|
||||
<a class="buttonicon buttonicon-test" title="Test test test"></a>\
|
||||
</li>\
|
||||
';
|
||||
return cb();
|
||||
}
|
|
@ -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" }
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
exports.somehook = function (hook_name, args, cb) {
|
||||
return cb(["partlast:somehook was here"]);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
exports.foo = 42;
|
||||
|
||||
exports.bar = function (hook_name, args, cb) {
|
||||
return cb(["FOOOO"]);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<em>Test bla bla</em>
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 $!
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 $*
|
||||
|
|
500
node/server.js
|
@ -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);
|
||||
}
|
||||
]);
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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" } }
|
||||
]
|
||||
}
|
|
@ -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();
|
||||
});
|
|
@ -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
|
|
@ -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");
|
|
@ -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)
|
|
@ -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
|
|
@ -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.
|
|
@ -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");
|
|
@ -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']);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
a
|
||||
<% e.begin_block("bar"); %>
|
||||
A
|
||||
<% e.begin_block("foo"); %>
|
||||
XX
|
||||
<% e.end_block(); %>
|
||||
B
|
||||
<% e.end_block(); %>
|
||||
b
|
|
@ -0,0 +1,7 @@
|
|||
<% e.inherit("./bar.ejs"); %>
|
||||
|
||||
<% e.begin_define_block("foo"); %>
|
||||
YY
|
||||
<% e.super(); %>
|
||||
ZZ
|
||||
<% e.end_define_block(); %>
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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)
|
||||
{
|
|
@ -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 = [];
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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});
|
||||
}
|
|
@ -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"));
|
||||
});
|
||||
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
]);
|
|
@ -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)
|
|
@ -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)
|
||||
{
|
|
@ -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)
|
||||
{
|
|
@ -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);
|
|
@ -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.")
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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" : "*"
|
||||
},
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
!*.template
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 697 B After Width: | Height: | Size: 697 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1009 B After Width: | Height: | Size: 1009 B |
After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 658 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 123 B After Width: | Height: | Size: 123 B |
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 131 B |
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 182 B After Width: | Height: | Size: 182 B |
Before Width: | Height: | Size: 686 B After Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
|
@ -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;
|
|
@ -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;
|
|
@ -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,
|
|
@ -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]);
|
||||
}
|
||||
|
|
@ -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"> </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);
|
|
@ -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;
|
|
@ -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();
|
||||
|
||||
});
|
|
@ -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();
|
|
@ -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> </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);
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -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);
|
||||
}
|
||||
|