Merge branch 'develop'
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -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
 | 
			
		||||
src/static/js/jquery.js
 | 
			
		||||
npm-debug.log
 | 
			
		||||
*.DS_Store
 | 
			
		||||
.ep_initialized
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								README.plugins
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
			
		||||
So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json
 | 
			
		||||
require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded
 | 
			
		||||
A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
 | 
			
		||||
require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name
 | 
			
		||||
That is the basis.
 | 
			
		||||
Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored).
 | 
			
		||||
This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order
 | 
			
		||||
Ordering between plugins is undefined, only parts are ordered.
 | 
			
		||||
 | 
			
		||||
A plugin usually has one part, but it van have multiple.
 | 
			
		||||
This is so that it can insert some hook registration before that of another plugin, and another one after.
 | 
			
		||||
This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps
 | 
			
		||||
So, that's basically it... apart from client-side hooks
 | 
			
		||||
which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client...
 | 
			
		||||
One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json...
 | 
			
		||||
was that clear?
 | 
			
		||||
							
								
								
									
										7
									
								
								available_plugins/ep_fintest/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
			
		||||
.git*
 | 
			
		||||
docs/
 | 
			
		||||
examples/
 | 
			
		||||
support/
 | 
			
		||||
test/
 | 
			
		||||
testing.js
 | 
			
		||||
.DS_Store
 | 
			
		||||
							
								
								
									
										36
									
								
								available_plugins/ep_fintest/ep.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,36 @@
 | 
			
		||||
{
 | 
			
		||||
  "parts": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "somepart",
 | 
			
		||||
      "pre": [],
 | 
			
		||||
      "post": ["ep_onemoreplugin/partone"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "partlast",
 | 
			
		||||
      "pre": ["ep_fintest/otherpart"],
 | 
			
		||||
      "post": [],
 | 
			
		||||
      "hooks": {
 | 
			
		||||
	"somehookname": "ep_fintest/partlast:somehook"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "partfirst",
 | 
			
		||||
      "pre": [],
 | 
			
		||||
      "post": ["ep_onemoreplugin/somepart"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "otherpart",
 | 
			
		||||
      "pre": ["ep_fintest/somepart", "ep_otherplugin/main"],
 | 
			
		||||
      "post": [],
 | 
			
		||||
      "hooks": {
 | 
			
		||||
	"somehookname": "ep_fintest/otherpart:somehook",
 | 
			
		||||
	"morehook": "ep_fintest/otherpart:morehook",
 | 
			
		||||
	"expressCreateServer": "ep_fintest/otherpart:expressServer",
 | 
			
		||||
	"eejsBlock_editbarMenuLeft": "ep_fintest/otherpart:eejsBlock_editbarMenuLeft"
 | 
			
		||||
      },
 | 
			
		||||
      "client_hooks": {
 | 
			
		||||
        "somehookname": "ep_fintest/static/js/test:bar"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								available_plugins/ep_fintest/otherpart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,25 @@
 | 
			
		||||
test = require("ep_fintest/static/js/test.js");
 | 
			
		||||
console.log("FOOO:", test.foo);
 | 
			
		||||
 | 
			
		||||
exports.somehook = function (hook_name, args, cb) {
 | 
			
		||||
  return cb(["otherpart:somehook was here"]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.morehook = function (hook_name, args, cb) {
 | 
			
		||||
  return cb(["otherpart:morehook was here"]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.expressServer = function (hook_name, args, cb) {
 | 
			
		||||
  args.app.get('/otherpart', function(req, res) { 
 | 
			
		||||
      res.send("<em>Abra cadabra</em>");
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) {
 | 
			
		||||
  args.content = args.content + '\
 | 
			
		||||
		    <li id="testButton" onClick="window.pad&&pad.editbarClick(\'clearauthorship\');return false;">\
 | 
			
		||||
			<a class="buttonicon buttonicon-test" title="Test test test"></a>\
 | 
			
		||||
		    </li>\
 | 
			
		||||
  ';
 | 
			
		||||
  return cb();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								available_plugins/ep_fintest/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "ep_fintest",
 | 
			
		||||
  "description": "A test plugin",
 | 
			
		||||
  "version": "0.0.1",
 | 
			
		||||
  "author": "RedHog (Egil Moeller) <egil.moller@freecode.no>",
 | 
			
		||||
  "contributors": [],
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "engines": { "node": ">= 0.4.1 < 0.7.0" }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								available_plugins/ep_fintest/partlast.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
			
		||||
exports.somehook = function (hook_name, args, cb) {
 | 
			
		||||
  return cb(["partlast:somehook was here"]);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								available_plugins/ep_fintest/static/js/test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
			
		||||
exports.foo = 42;
 | 
			
		||||
 | 
			
		||||
exports.bar = function (hook_name, args, cb) {
 | 
			
		||||
 return cb(["FOOOO"]);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								available_plugins/ep_fintest/static/test.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								src/ep.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
  "parts": [
 | 
			
		||||
    { "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } },
 | 
			
		||||
    { "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
 | 
			
		||||
    { "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },
 | 
			
		||||
    { "name": "padreadonly", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padreadonly:expressCreateServer" } },
 | 
			
		||||
    { "name": "webaccess", "hooks": { "expressConfigure": "ep_etherpad-lite/node/hooks/express/webaccess:expressConfigure" } },
 | 
			
		||||
    { "name": "apicalls", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/apicalls:expressCreateServer" } },
 | 
			
		||||
    { "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
 | 
			
		||||
    { "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
 | 
			
		||||
    { "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
 | 
			
		||||
    { "name": "adminplugins", "hooks": {
 | 
			
		||||
      "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
 | 
			
		||||
      "socketio": "ep_etherpad-lite/node/hooks/express/adminplugins:socketio" } }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
@ -76,14 +81,27 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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});
 | 
			
		||||
  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);
 | 
			
		||||
};
 | 
			
		||||
@ -203,8 +221,7 @@ 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.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']);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/node/eejs/examples/bar.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,9 @@
 | 
			
		||||
a
 | 
			
		||||
<% e.begin_block("bar"); %>
 | 
			
		||||
  A
 | 
			
		||||
  <% e.begin_block("foo"); %>
 | 
			
		||||
  XX
 | 
			
		||||
  <% e.end_block(); %>
 | 
			
		||||
  B
 | 
			
		||||
<% e.end_block(); %>
 | 
			
		||||
b
 | 
			
		||||
							
								
								
									
										7
									
								
								src/node/eejs/examples/foo.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
			
		||||
<% e.inherit("./bar.ejs"); %>
 | 
			
		||||
 | 
			
		||||
<% e.begin_define_block("foo"); %>
 | 
			
		||||
  YY
 | 
			
		||||
  <% e.super(); %>
 | 
			
		||||
  ZZ
 | 
			
		||||
<% e.end_define_block(); %>
 | 
			
		||||
							
								
								
									
										131
									
								
								src/node/eejs/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -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,7 +828,10 @@ 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
 | 
			
		||||
@ -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 = [];
 | 
			
		||||
							
								
								
									
										53
									
								
								src/node/hooks/express/adminplugins.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,53 @@
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var eejs = require('ep_etherpad-lite/node/eejs');
 | 
			
		||||
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
 | 
			
		||||
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  args.app.get('/admin/plugins', function(req, res) {
 | 
			
		||||
    var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
 | 
			
		||||
    var render_args = {
 | 
			
		||||
      plugins: plugins.plugins,
 | 
			
		||||
      search_results: {},
 | 
			
		||||
      errors: [],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    res.send(eejs.require(
 | 
			
		||||
      "ep_etherpad-lite/templates/admin/plugins.html",
 | 
			
		||||
      render_args), {});
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.socketio = function (hook_name, args, cb) {
 | 
			
		||||
  var io = args.io.of("/pluginfw/installer");
 | 
			
		||||
  io.on('connection', function (socket) {
 | 
			
		||||
    if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
 | 
			
		||||
 | 
			
		||||
    socket.on("load", function (query) {
 | 
			
		||||
      socket.emit("installed-results", {results: plugins.plugins});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    socket.on("search", function (query) {
 | 
			
		||||
      socket.emit("progress", {progress:0, message:'Fetching results...'});
 | 
			
		||||
        installer.search(query, true, function (progress) {
 | 
			
		||||
        if (progress.results)
 | 
			
		||||
          socket.emit("search-result", progress);
 | 
			
		||||
        socket.emit("progress", progress);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    socket.on("install", function (plugin_name) {
 | 
			
		||||
      socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
 | 
			
		||||
      installer.install(plugin_name, function (progress) {
 | 
			
		||||
        socket.emit("progress", progress);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    socket.on("uninstall", function (plugin_name) {
 | 
			
		||||
      socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
 | 
			
		||||
      installer.uninstall(plugin_name, function (progress) {
 | 
			
		||||
        socket.emit("progress", progress);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/node/hooks/express/apicalls.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,60 @@
 | 
			
		||||
var log4js = require('log4js');
 | 
			
		||||
var apiLogger = log4js.getLogger("API");
 | 
			
		||||
var formidable = require('formidable');
 | 
			
		||||
var apiHandler = require('../../handler/APIHandler');
 | 
			
		||||
 | 
			
		||||
//This is for making an api call, collecting all post information and passing it to the apiHandler
 | 
			
		||||
var apiCaller = function(req, res, fields) {
 | 
			
		||||
  res.header("Content-Type", "application/json; charset=utf-8");
 | 
			
		||||
 | 
			
		||||
  apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
 | 
			
		||||
 | 
			
		||||
  //wrap the send function so we can log the response
 | 
			
		||||
  //note: res._send seems to be already in use, so better use a "unique" name
 | 
			
		||||
  res._____send = res.send;
 | 
			
		||||
  res.send = function (response) {
 | 
			
		||||
    response = JSON.stringify(response);
 | 
			
		||||
    apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
 | 
			
		||||
 | 
			
		||||
    //is this a jsonp call, if yes, add the function call
 | 
			
		||||
    if(req.query.jsonp)
 | 
			
		||||
      response = req.query.jsonp + "(" + response + ")";
 | 
			
		||||
 | 
			
		||||
    res._____send(response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //call the api handler
 | 
			
		||||
  apiHandler.handle(req.params.func, fields, req, res);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.apiCaller = apiCaller;
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  //This is a api GET call, collect all post informations and pass it to the apiHandler
 | 
			
		||||
  args.app.get('/api/1/:func', function (req, res) {
 | 
			
		||||
    apiCaller(req, res, req.query)
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //This is a api POST call, collect all post informations and pass it to the apiHandler
 | 
			
		||||
  args.app.post('/api/1/:func', function(req, res) {
 | 
			
		||||
    new formidable.IncomingForm().parse(req, function (err, fields, files) {
 | 
			
		||||
      apiCaller(req, res, fields)
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //The Etherpad client side sends information about how a disconnect happen
 | 
			
		||||
  args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
 | 
			
		||||
    new formidable.IncomingForm().parse(req, function(err, fields, files) { 
 | 
			
		||||
      console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
 | 
			
		||||
      res.end("OK");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //The Etherpad client side sends information about client side javscript errors
 | 
			
		||||
  args.app.post('/jserror', function(req, res) {
 | 
			
		||||
    new formidable.IncomingForm().parse(req, function(err, fields, files) { 
 | 
			
		||||
      console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
 | 
			
		||||
      res.end("OK");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/node/hooks/express/errorhandling.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,52 @@
 | 
			
		||||
var os = require("os");
 | 
			
		||||
var db = require('../../db/DB');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
exports.onShutdown = false;
 | 
			
		||||
exports.gracefulShutdown = function(err) {
 | 
			
		||||
  if(err && err.stack) {
 | 
			
		||||
    console.error(err.stack);
 | 
			
		||||
  } else if(err) {
 | 
			
		||||
    console.error(err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //ensure there is only one graceful shutdown running
 | 
			
		||||
  if(exports.onShutdown) return;
 | 
			
		||||
  exports.onShutdown = true;
 | 
			
		||||
 | 
			
		||||
  console.log("graceful shutdown...");
 | 
			
		||||
 | 
			
		||||
  //stop the http server
 | 
			
		||||
  exports.app.close();
 | 
			
		||||
 | 
			
		||||
  //do the db shutdown
 | 
			
		||||
  db.db.doShutdown(function() {
 | 
			
		||||
    console.log("db sucessfully closed.");
 | 
			
		||||
 | 
			
		||||
    process.exit(0);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  setTimeout(function(){
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }, 3000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  exports.app = args.app;
 | 
			
		||||
 | 
			
		||||
  args.app.error(function(err, req, res, next){
 | 
			
		||||
    res.send(500);
 | 
			
		||||
    console.error(err.stack ? err.stack : err.toString());
 | 
			
		||||
    exports.gracefulShutdown();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //connect graceful shutdown with sigint and uncaughtexception
 | 
			
		||||
  if(os.type().indexOf("Windows") == -1) {
 | 
			
		||||
    //sigint is so far not working on windows
 | 
			
		||||
    //https://github.com/joyent/node/issues/1553
 | 
			
		||||
    process.on('SIGINT', exports.gracefulShutdown);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  process.on('uncaughtException', exports.gracefulShutdown);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/node/hooks/express/importexport.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,41 @@
 | 
			
		||||
var hasPadAccess = require("../../padaccess");
 | 
			
		||||
var settings = require('../../utils/Settings');
 | 
			
		||||
var exportHandler = require('../../handler/ExportHandler');
 | 
			
		||||
var importHandler = require('../../handler/ImportHandler');
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) {
 | 
			
		||||
    var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
 | 
			
		||||
    //send a 404 if we don't support this filetype
 | 
			
		||||
    if (types.indexOf(req.params.type) == -1) {
 | 
			
		||||
      next();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //if abiword is disabled, and this is a format we only support with abiword, output a message
 | 
			
		||||
    if (settings.abiword == null &&
 | 
			
		||||
       ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
 | 
			
		||||
      res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.header("Access-Control-Allow-Origin", "*");
 | 
			
		||||
 | 
			
		||||
    hasPadAccess(req, res, function() {
 | 
			
		||||
      exportHandler.doExport(req, res, req.params.pad, req.params.type);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //handle import requests
 | 
			
		||||
  args.app.post('/p/:pad/import', function(req, res, next) {
 | 
			
		||||
    //if abiword is disabled, skip handling this request
 | 
			
		||||
    if(settings.abiword == null) {
 | 
			
		||||
      next();
 | 
			
		||||
      return; 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasPadAccess(req, res, function() {
 | 
			
		||||
      importHandler.doImport(req, res, req.params.pad);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/node/hooks/express/padreadonly.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,65 @@
 | 
			
		||||
var async = require('async');
 | 
			
		||||
var ERR = require("async-stacktrace");
 | 
			
		||||
var readOnlyManager = require("../../db/ReadOnlyManager");
 | 
			
		||||
var hasPadAccess = require("../../padaccess");
 | 
			
		||||
var exporthtml = require("../../utils/ExportHtml");
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  //serve read only pad
 | 
			
		||||
  args.app.get('/ro/:id', function(req, res)
 | 
			
		||||
  { 
 | 
			
		||||
    var html;
 | 
			
		||||
    var padId;
 | 
			
		||||
    var pad;
 | 
			
		||||
 | 
			
		||||
    async.series([
 | 
			
		||||
      //translate the read only pad to a padId
 | 
			
		||||
      function(callback)
 | 
			
		||||
      {
 | 
			
		||||
	readOnlyManager.getPadId(req.params.id, function(err, _padId)
 | 
			
		||||
	{
 | 
			
		||||
	  if(ERR(err, callback)) return;
 | 
			
		||||
 | 
			
		||||
	  padId = _padId;
 | 
			
		||||
 | 
			
		||||
	  //we need that to tell hasPadAcess about the pad  
 | 
			
		||||
	  req.params.pad = padId; 
 | 
			
		||||
 | 
			
		||||
	  callback();
 | 
			
		||||
	});
 | 
			
		||||
      },
 | 
			
		||||
      //render the html document
 | 
			
		||||
      function(callback)
 | 
			
		||||
      {
 | 
			
		||||
	//return if the there is no padId
 | 
			
		||||
	if(padId == null)
 | 
			
		||||
	{
 | 
			
		||||
	  callback("notfound");
 | 
			
		||||
	  return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasPadAccess(req, res, function()
 | 
			
		||||
	{
 | 
			
		||||
	  //render the html document
 | 
			
		||||
	  exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
 | 
			
		||||
	  {
 | 
			
		||||
	    if(ERR(err, callback)) return;
 | 
			
		||||
	    html = _html;
 | 
			
		||||
	    callback();
 | 
			
		||||
	  });
 | 
			
		||||
	});
 | 
			
		||||
      }
 | 
			
		||||
    ], function(err)
 | 
			
		||||
    {
 | 
			
		||||
      //throw any unexpected error
 | 
			
		||||
      if(err && err != "notfound")
 | 
			
		||||
	ERR(err);
 | 
			
		||||
 | 
			
		||||
      if(err == "notfound")
 | 
			
		||||
	res.send('404 - Not Found', 404);
 | 
			
		||||
      else
 | 
			
		||||
	res.send(html);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/node/hooks/express/padurlsanitize.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,29 @@
 | 
			
		||||
var padManager = require('../../db/PadManager');
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  //redirects browser to the pad's sanitized url if needed. otherwise, renders the html
 | 
			
		||||
  args.app.param('pad', function (req, res, next, padId) {
 | 
			
		||||
    //ensure the padname is valid and the url doesn't end with a /
 | 
			
		||||
    if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
 | 
			
		||||
    {
 | 
			
		||||
      res.send('Such a padname is forbidden', 404);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      padManager.sanitizePadId(padId, function(sanitizedPadId) {
 | 
			
		||||
	//the pad id was sanitized, so we redirect to the sanitized version
 | 
			
		||||
	if(sanitizedPadId != padId)
 | 
			
		||||
	{
 | 
			
		||||
	  var real_path = req.path.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId);
 | 
			
		||||
	  res.header('Location', real_path);
 | 
			
		||||
	  res.send('You should be redirected to <a href="' + real_path + '">' + real_path + '</a>', 302);
 | 
			
		||||
	}
 | 
			
		||||
	//the pad id was fine, so just render it
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
	  next();
 | 
			
		||||
	}
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/node/hooks/express/socketio.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,65 @@
 | 
			
		||||
var log4js = require('log4js');
 | 
			
		||||
var socketio = require('socket.io');
 | 
			
		||||
var settings = require('../../utils/Settings');
 | 
			
		||||
var socketIORouter = require("../../handler/SocketIORouter");
 | 
			
		||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
 | 
			
		||||
 | 
			
		||||
var padMessageHandler = require("../../handler/PadMessageHandler");
 | 
			
		||||
var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler");
 | 
			
		||||
 | 
			
		||||
var connect = require('connect');
 | 
			
		||||
 
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  //init socket.io and redirect all requests to the MessageHandler
 | 
			
		||||
  var io = socketio.listen(args.app);
 | 
			
		||||
 | 
			
		||||
  /* Require an express session cookie to be present, and load the
 | 
			
		||||
   * session. See http://www.danielbaulig.de/socket-ioexpress for more
 | 
			
		||||
   * info */
 | 
			
		||||
  io.set('authorization', function (data, accept) {
 | 
			
		||||
    if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
 | 
			
		||||
    data.cookie = connect.utils.parseCookie(data.headers.cookie);
 | 
			
		||||
    data.sessionID = data.cookie.express_sid;
 | 
			
		||||
    args.app.sessionStore.get(data.sessionID, function (err, session) {
 | 
			
		||||
      if (err || !session) return accept('Bad session / session has expired', false);
 | 
			
		||||
      data.session = new connect.middleware.session.Session(data, session);
 | 
			
		||||
      accept(null, true);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //this is only a workaround to ensure it works with all browers behind a proxy
 | 
			
		||||
  //we should remove this when the new socket.io version is more stable
 | 
			
		||||
  io.set('transports', ['xhr-polling']);
 | 
			
		||||
 | 
			
		||||
  var socketIOLogger = log4js.getLogger("socket.io");
 | 
			
		||||
  io.set('logger', {
 | 
			
		||||
    debug: function (str)
 | 
			
		||||
    {
 | 
			
		||||
      socketIOLogger.debug.apply(socketIOLogger, arguments);
 | 
			
		||||
    }, 
 | 
			
		||||
    info: function (str)
 | 
			
		||||
    {
 | 
			
		||||
      socketIOLogger.info.apply(socketIOLogger, arguments);
 | 
			
		||||
    },
 | 
			
		||||
    warn: function (str)
 | 
			
		||||
    {
 | 
			
		||||
      socketIOLogger.warn.apply(socketIOLogger, arguments);
 | 
			
		||||
    },
 | 
			
		||||
    error: function (str)
 | 
			
		||||
    {
 | 
			
		||||
      socketIOLogger.error.apply(socketIOLogger, arguments);
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //minify socket.io javascript
 | 
			
		||||
  if(settings.minify)
 | 
			
		||||
    io.enable('browser client minification');
 | 
			
		||||
 | 
			
		||||
  //Initalize the Socket.IO Router
 | 
			
		||||
  socketIORouter.setSocketIO(io);
 | 
			
		||||
  socketIORouter.addComponent("pad", padMessageHandler);
 | 
			
		||||
  socketIORouter.addComponent("timeslider", timesliderMessageHandler);
 | 
			
		||||
 | 
			
		||||
  hooks.callAll("socketio", {"app": args.app, "io": io});
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/node/hooks/express/specialpages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,46 @@
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var eejs = require('ep_etherpad-lite/node/eejs');
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
 | 
			
		||||
  //serve index.html under /
 | 
			
		||||
  args.app.get('/', function(req, res)
 | 
			
		||||
  {
 | 
			
		||||
    res.send(eejs.require("ep_etherpad-lite/templates/index.html"));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //serve robots.txt
 | 
			
		||||
  args.app.get('/robots.txt', function(req, res)
 | 
			
		||||
  {
 | 
			
		||||
    var filePath = path.normalize(__dirname + "/../../../static/robots.txt");
 | 
			
		||||
    res.sendfile(filePath);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //serve favicon.ico
 | 
			
		||||
  args.app.get('/favicon.ico', function(req, res)
 | 
			
		||||
  {
 | 
			
		||||
    var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico");
 | 
			
		||||
    res.sendfile(filePath, function(err)
 | 
			
		||||
    {
 | 
			
		||||
      //there is no custom favicon, send the default favicon
 | 
			
		||||
      if(err)
 | 
			
		||||
      {
 | 
			
		||||
	filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
 | 
			
		||||
	res.sendfile(filePath);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //serve pad.html under /p
 | 
			
		||||
  args.app.get('/p/:pad', function(req, res, next)
 | 
			
		||||
  {    
 | 
			
		||||
    res.send(eejs.require("ep_etherpad-lite/templates/pad.html"));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //serve timeslider.html under /p/$padname/timeslider
 | 
			
		||||
  args.app.get('/p/:pad/timeslider', function(req, res, next)
 | 
			
		||||
  {
 | 
			
		||||
    res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								src/node/hooks/express/static.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,57 @@
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var minify = require('../../utils/Minify');
 | 
			
		||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
 | 
			
		||||
var CachingMiddleware = require('../../utils/caching_middleware');
 | 
			
		||||
var settings = require("../../utils/Settings");
 | 
			
		||||
var Yajsml = require('yajsml');
 | 
			
		||||
var fs = require("fs");
 | 
			
		||||
var ERR = require("async-stacktrace");
 | 
			
		||||
var _ = require("underscore");
 | 
			
		||||
 | 
			
		||||
exports.expressCreateServer = function (hook_name, args, cb) {
 | 
			
		||||
  // Cache both minified and static.
 | 
			
		||||
  var assetCache = new CachingMiddleware;
 | 
			
		||||
  args.app.all('/(javascripts|static)/*', assetCache.handle);
 | 
			
		||||
 | 
			
		||||
  // Minify will serve static files compressed (minify enabled). It also has
 | 
			
		||||
  // file-specific hacks for ace/require-kernel/etc.
 | 
			
		||||
  args.app.all('/static/:filename(*)', minify.minify);
 | 
			
		||||
 | 
			
		||||
  // Setup middleware that will package JavaScript files served by minify for
 | 
			
		||||
  // CommonJS loader on the client-side.
 | 
			
		||||
  var jsServer = new (Yajsml.Server)({
 | 
			
		||||
    rootPath: 'javascripts/src/'
 | 
			
		||||
  , rootURI: 'http://localhost:' + settings.port + '/static/js/'
 | 
			
		||||
  , libraryPath: 'javascripts/lib/'
 | 
			
		||||
  , libraryURI: 'http://localhost:' + settings.port + '/static/plugins/'
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  var StaticAssociator = Yajsml.associators.StaticAssociator;
 | 
			
		||||
  var associations =
 | 
			
		||||
    Yajsml.associators.associationsForSimpleMapping(minify.tar);
 | 
			
		||||
  var associator = new StaticAssociator(associations);
 | 
			
		||||
  jsServer.setAssociator(associator);
 | 
			
		||||
  args.app.use(jsServer);
 | 
			
		||||
 | 
			
		||||
  // serve plugin definitions
 | 
			
		||||
  // not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
 | 
			
		||||
  args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
 | 
			
		||||
 | 
			
		||||
    var clientParts = _(plugins.parts)
 | 
			
		||||
      .filter(function(part){ return _(part).has('client_hooks') });
 | 
			
		||||
      
 | 
			
		||||
    var clientPlugins = {};
 | 
			
		||||
    
 | 
			
		||||
    _(clientParts).chain()
 | 
			
		||||
      .map(function(part){ return part.plugin })
 | 
			
		||||
      .uniq()
 | 
			
		||||
      .each(function(name){
 | 
			
		||||
        clientPlugins[name] = _(plugins.plugins[name]).clone();
 | 
			
		||||
        delete clientPlugins[name]['package'];
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
    res.header("Content-Type","application/json; charset=utf-8");
 | 
			
		||||
    res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
 | 
			
		||||
    res.end();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										109
									
								
								src/node/hooks/express/webaccess.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,109 @@
 | 
			
		||||
var express = require('express');
 | 
			
		||||
var log4js = require('log4js');
 | 
			
		||||
var httpLogger = log4js.getLogger("http");
 | 
			
		||||
var settings = require('../../utils/Settings');
 | 
			
		||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
 | 
			
		||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//checks for basic http auth
 | 
			
		||||
exports.basicAuth = function (req, res, next) {
 | 
			
		||||
  var hookResultMangle = function (cb) {
 | 
			
		||||
    return function (err, data) {
 | 
			
		||||
      return cb(!err && data.length && data[0]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var authorize = function (cb) {
 | 
			
		||||
    // Do not require auth for static paths...this could be a bit brittle
 | 
			
		||||
    if (req.path.match(/^\/(static|javascripts|pluginfw)/)) return cb(true);
 | 
			
		||||
 | 
			
		||||
    if (req.path.indexOf('/admin') != 0) {
 | 
			
		||||
      if (!settings.requireAuthentication) return cb(true);
 | 
			
		||||
      if (!settings.requireAuthorization && req.session && req.session.user) return cb(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.session && req.session.user && req.session.user.is_admin) return cb(true);
 | 
			
		||||
 | 
			
		||||
    hooks.aCallFirst("authorize", {req: req, res:res, next:next, resource: req.path}, hookResultMangle(cb));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var authenticate = function (cb) {
 | 
			
		||||
    // If auth headers are present use them to authenticate...
 | 
			
		||||
    if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
 | 
			
		||||
      var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
 | 
			
		||||
      var username = userpass[0];
 | 
			
		||||
      var password = userpass[1];
 | 
			
		||||
 | 
			
		||||
      if (settings.users[username] != undefined && settings.users[username].password == password) {
 | 
			
		||||
        settings.users[username].username = username;
 | 
			
		||||
        req.session.user = settings.users[username];
 | 
			
		||||
        return cb(true);
 | 
			
		||||
      }
 | 
			
		||||
        return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(cb));
 | 
			
		||||
    }
 | 
			
		||||
    hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /* Authentication OR authorization failed. */
 | 
			
		||||
  var failure = function () {
 | 
			
		||||
    return hooks.aCallFirst("authFailure", {req: req, res:res, next:next}, hookResultMangle(function (ok) {
 | 
			
		||||
    if (ok) return;
 | 
			
		||||
      /* No plugin handler for invalid auth. Return Auth required
 | 
			
		||||
       * Headers, delayed for 1 second, if authentication failed
 | 
			
		||||
       * before. */
 | 
			
		||||
      res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
 | 
			
		||||
      if (req.headers.authorization) {
 | 
			
		||||
        setTimeout(function () {
 | 
			
		||||
          res.send('Authentication required', 401);
 | 
			
		||||
        }, 1000);
 | 
			
		||||
      } else {
 | 
			
		||||
        res.send('Authentication required', 401);
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /* This is the actual authentication/authorization hoop. It is done in four steps:
 | 
			
		||||
 | 
			
		||||
     1) Try to just access the thing
 | 
			
		||||
     2) If not allowed using whatever creds are in the current session already, try to authenticate
 | 
			
		||||
     3) If authentication using already supplied credentials succeeds, try to access the thing again
 | 
			
		||||
     4) If all els fails, give the user a 401 to request new credentials
 | 
			
		||||
 | 
			
		||||
     Note that the process could stop already in step 3 with a redirect to login page.
 | 
			
		||||
 | 
			
		||||
  */
 | 
			
		||||
 
 | 
			
		||||
  authorize(function (ok) {
 | 
			
		||||
    if (ok) return next();
 | 
			
		||||
    authenticate(function (ok) {
 | 
			
		||||
      if (!ok) return failure();
 | 
			
		||||
      authorize(function (ok) {
 | 
			
		||||
        if (ok) return next();
 | 
			
		||||
        failure();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.expressConfigure = function (hook_name, args, cb) {
 | 
			
		||||
  // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
 | 
			
		||||
  // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
 | 
			
		||||
  if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
 | 
			
		||||
    args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
 | 
			
		||||
  args.app.use(express.cookieParser());
 | 
			
		||||
 | 
			
		||||
  /* Do not let express create the session, so that we can retain a
 | 
			
		||||
   * reference to it for socket.io to use. Also, set the key (cookie
 | 
			
		||||
   * name) to a javascript identifier compatible string. Makes code
 | 
			
		||||
   * handling it cleaner :) */
 | 
			
		||||
 | 
			
		||||
  args.app.sessionStore = new express.session.MemoryStore();
 | 
			
		||||
  args.app.use(express.session({store: args.app.sessionStore,
 | 
			
		||||
                                key: 'express_sid',
 | 
			
		||||
                                secret: apikey = randomString(32)}));
 | 
			
		||||
 | 
			
		||||
  args.app.use(exports.basicAuth);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/node/padaccess.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,21 @@
 | 
			
		||||
var ERR = require("async-stacktrace");
 | 
			
		||||
var securityManager = require('./db/SecurityManager');
 | 
			
		||||
 | 
			
		||||
//checks for padAccess
 | 
			
		||||
module.exports = function (req, res, callback) {
 | 
			
		||||
 | 
			
		||||
  // FIXME: Why is this ever undefined??
 | 
			
		||||
  if (req.cookies === undefined) req.cookies = {};
 | 
			
		||||
 | 
			
		||||
  securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj) {
 | 
			
		||||
    if(ERR(err, callback)) return;
 | 
			
		||||
 | 
			
		||||
    //there is access, continue
 | 
			
		||||
    if(accessObj.accessStatus == "grant") {
 | 
			
		||||
      callback();
 | 
			
		||||
    //no access
 | 
			
		||||
    } else {
 | 
			
		||||
      res.send("403 - Can't touch this", 403);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								src/node/server.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						@ -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,10 +102,18 @@ exports.abiwordAvailable = function()
 | 
			
		||||
 | 
			
		||||
// Discover where the settings file lives
 | 
			
		||||
var settingsFilename = argv.settings || "settings.json";
 | 
			
		||||
var settingsPath = settingsFilename.charAt(0) == '/' ? '' : path.normalize(__dirname + "/../../");
 | 
			
		||||
if (settingsFilename.charAt(0) != '/') {
 | 
			
		||||
    settingsFilename = path.normalize(path.join(root, settingsFilename));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var settingsStr
 | 
			
		||||
try{
 | 
			
		||||
  //read the settings sync
 | 
			
		||||
var settingsStr = fs.readFileSync(settingsPath + settingsFilename).toString();
 | 
			
		||||
  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);
 | 
			
		||||
      };
 | 
			
		||||
							
								
								
									
										16
									
								
								src/node/utils/randomstring.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
 | 
			
		||||
 */
 | 
			
		||||
var randomString = function randomString(len)
 | 
			
		||||
{
 | 
			
		||||
  var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 | 
			
		||||
  var randomstring = '';
 | 
			
		||||
  for (var i = 0; i < len; i++)
 | 
			
		||||
  {
 | 
			
		||||
    var rnum = Math.floor(Math.random() * chars.length);
 | 
			
		||||
    randomstring += chars.substring(rnum, rnum + 1);
 | 
			
		||||
  }
 | 
			
		||||
  return randomstring;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = randomString;
 | 
			
		||||
@ -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"  : "*"
 | 
			
		||||
                     },
 | 
			
		||||
							
								
								
									
										122
									
								
								src/static/css/admin.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,122 @@
 | 
			
		||||
body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  font: 14px helvetica, sans-serif;
 | 
			
		||||
  background: #ddd;
 | 
			
		||||
  background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
 | 
			
		||||
  background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
 | 
			
		||||
  background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
 | 
			
		||||
  background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
 | 
			
		||||
  border-top: 8px solid rgba(51,51,51,.8);
 | 
			
		||||
}
 | 
			
		||||
#wrapper {
 | 
			
		||||
  margin-top: 160px;
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  opacity: .9;
 | 
			
		||||
  box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
 | 
			
		||||
  max-width: 700px;
 | 
			
		||||
  margin: auto;
 | 
			
		||||
  border-radius: 0 0 7px 7px;
 | 
			
		||||
}
 | 
			
		||||
h1 {
 | 
			
		||||
  font-size: 29px;
 | 
			
		||||
}
 | 
			
		||||
h2 {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
.separator {
 | 
			
		||||
  margin: 10px 0;
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  background: #aaa;
 | 
			
		||||
  background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
 | 
			
		||||
  background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
 | 
			
		||||
  background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
 | 
			
		||||
  background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
 | 
			
		||||
}
 | 
			
		||||
form {
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
#inner {
 | 
			
		||||
  width: 300px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
input {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
}
 | 
			
		||||
input[type="button"] {
 | 
			
		||||
  padding: 4px 6px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
input[type="button"].do-install, input[type="button"].do-uninstall {
 | 
			
		||||
  float: right;
 | 
			
		||||
  width: 100px;
 | 
			
		||||
}
 | 
			
		||||
input[type="button"]#do-search {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
input[type="text"] {
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  -moz-box-sizing: border-box;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  *padding: 0; /* IE7 hack */
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  margin: 0 0 5px 0;
 | 
			
		||||
  max-width: 500px;
 | 
			
		||||
}
 | 
			
		||||
table {
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  border-spacing: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
table thead tr {
 | 
			
		||||
  background: #eee;
 | 
			
		||||
}
 | 
			
		||||
td, th {
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
.template {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.dialog {
 | 
			
		||||
  display: none;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  width: 700px;
 | 
			
		||||
  height: 500px;
 | 
			
		||||
  margin-left: -350px;
 | 
			
		||||
  margin-top: -250px;
 | 
			
		||||
  border: 3px solid #999;
 | 
			
		||||
  background: #eee;
 | 
			
		||||
}
 | 
			
		||||
.dialog .title {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 2px;
 | 
			
		||||
  border-bottom: 3px solid #999;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  height: 24px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.dialog .title .close {
 | 
			
		||||
  float: right;
 | 
			
		||||
  padding: 1px 10px;
 | 
			
		||||
}
 | 
			
		||||
.dialog .history {
 | 
			
		||||
  background: #222;
 | 
			
		||||
  color: #eee;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 41px;
 | 
			
		||||
  bottom: 10px;
 | 
			
		||||
  left: 10px;
 | 
			
		||||
  right: 10px;
 | 
			
		||||
  padding: 2px;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,13 @@
 | 
			
		||||
html { cursor: text; } /* in Safari, produces text cursor for whole doc (inc. below body) */
 | 
			
		||||
span { cursor: auto; }
 | 
			
		||||
 | 
			
		||||
::selection {
 | 
			
		||||
  background: #acf;
 | 
			
		||||
}
 | 
			
		||||
::-moz-selection {
 | 
			
		||||
  background: #acf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a { cursor: pointer !important; }
 | 
			
		||||
 | 
			
		||||
ul, ol, li {
 | 
			
		||||
							
								
								
									
										995
									
								
								src/static/css/pad.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										288
									
								
								src/static/css/timeslider.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,288 @@
 | 
			
		||||
#editorcontainerbox {
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
  top: 40px;
 | 
			
		||||
  position: static;
 | 
			
		||||
}
 | 
			
		||||
#padcontent {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider-wrapper {
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
}
 | 
			
		||||
#timeslider-left {
 | 
			
		||||
  background-image: url(../../static/img/timeslider_left.png);
 | 
			
		||||
  height: 63px;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 134px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider-right {
 | 
			
		||||
  background-image: url(../../static/img/timeslider_right.png);
 | 
			
		||||
  height: 63px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  width: 155px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider {
 | 
			
		||||
  background-image: url(../../static/img/timeslider_background.png);
 | 
			
		||||
  height: 63px;
 | 
			
		||||
  margin: 0 9px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider #timeslider-slider {
 | 
			
		||||
  height: 61px;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 1px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
#ui-slider-handle {
 | 
			
		||||
  -webkit-user-select: none;
 | 
			
		||||
  -moz-user-select: none;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  background-image: url(../../static/img/crushed_current_location.png);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  height: 61px;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  width: 13px;
 | 
			
		||||
}
 | 
			
		||||
#ui-slider-bar {
 | 
			
		||||
  -webkit-user-select: none;
 | 
			
		||||
  -moz-user-select: none;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  height: 35px;
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  margin-right: 148px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: 20px;
 | 
			
		||||
}
 | 
			
		||||
#playpause_button,
 | 
			
		||||
#playpause_button_icon {
 | 
			
		||||
  height: 47px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 47px;
 | 
			
		||||
}
 | 
			
		||||
#playpause_button {
 | 
			
		||||
  background-image: url(../../static/img/crushed_button_undepressed.png);
 | 
			
		||||
  right: 77px;
 | 
			
		||||
  top: 9px;
 | 
			
		||||
}
 | 
			
		||||
#playpause_button_icon {
 | 
			
		||||
  background-image: url(../../static/img/play.png);
 | 
			
		||||
  left: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
}
 | 
			
		||||
.pause#playpause_button_icon {
 | 
			
		||||
  background-image: url(../../static/img/pause.png)
 | 
			
		||||
}
 | 
			
		||||
#leftstar,
 | 
			
		||||
#rightstar,
 | 
			
		||||
#leftstep,
 | 
			
		||||
#rightstep {
 | 
			
		||||
  background: url(../../static/img/stepper_buttons.png) 0 0 no-repeat;
 | 
			
		||||
  height: 21px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
#leftstar {
 | 
			
		||||
  background-position: 0 -44px;
 | 
			
		||||
  right: 34px;
 | 
			
		||||
  top: 8px;
 | 
			
		||||
  width: 30px;
 | 
			
		||||
}
 | 
			
		||||
#rightstar {
 | 
			
		||||
  background-position: -29px -44px;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
  top: 8px;
 | 
			
		||||
  width: 29px;
 | 
			
		||||
}
 | 
			
		||||
#leftstep {
 | 
			
		||||
  background-position: 0 -22px;
 | 
			
		||||
  right: 34px;
 | 
			
		||||
  top: 20px;
 | 
			
		||||
  width: 30px;
 | 
			
		||||
}
 | 
			
		||||
#rightstep {
 | 
			
		||||
  background-position: -29px -22px;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
  top: 20px;
 | 
			
		||||
  width: 29px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider .star {
 | 
			
		||||
  background-image: url(../../static/img/star.png);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  height: 16px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 40px;
 | 
			
		||||
  width: 15px;
 | 
			
		||||
}
 | 
			
		||||
#timeslider #timer {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  font-family: Arial, sans-serif;
 | 
			
		||||
  font-size: 11px;
 | 
			
		||||
  left: 7px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  top: 9px;
 | 
			
		||||
  width: 122px;
 | 
			
		||||
}
 | 
			
		||||
.topbarcenter,
 | 
			
		||||
#docbar {
 | 
			
		||||
  display: none
 | 
			
		||||
}
 | 
			
		||||
#padmain {
 | 
			
		||||
  top: 0px !important
 | 
			
		||||
}
 | 
			
		||||
#editbarright {
 | 
			
		||||
  float: right
 | 
			
		||||
}
 | 
			
		||||
#returnbutton {
 | 
			
		||||
  color: #222;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  line-height: 29px;
 | 
			
		||||
  margin-top: 0;
 | 
			
		||||
  padding-right: 6px;
 | 
			
		||||
}
 | 
			
		||||
#importexport .popup {
 | 
			
		||||
  width: 185px
 | 
			
		||||
}
 | 
			
		||||
#importexport {
 | 
			
		||||
  top: 118px;
 | 
			
		||||
  width: 185px;
 | 
			
		||||
}
 | 
			
		||||
.timeslider-bar {
 | 
			
		||||
  background: #f7f7f7;
 | 
			
		||||
  background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
 | 
			
		||||
  background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
 | 
			
		||||
  background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
 | 
			
		||||
  background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
 | 
			
		||||
  background: linear-gradient(#f7f7f7, #f1f1f1 80%);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  padding-top: 3px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.timeslider-bar #editbar {
 | 
			
		||||
  border-bottom: none;
 | 
			
		||||
  float: right;
 | 
			
		||||
  width: 170px;
 | 
			
		||||
  width: initial;
 | 
			
		||||
}
 | 
			
		||||
.timeslider-bar h1 {
 | 
			
		||||
  margin: 5px
 | 
			
		||||
}
 | 
			
		||||
.timeslider-bar p {
 | 
			
		||||
  margin: 5px
 | 
			
		||||
}
 | 
			
		||||
#timeslider-top {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
#authorsList .author {
 | 
			
		||||
  padding-left: 0.4em;
 | 
			
		||||
  padding-right: 0.4em;
 | 
			
		||||
}
 | 
			
		||||
#authorsList .author-anonymous {
 | 
			
		||||
  padding-left: 0.6em;
 | 
			
		||||
  padding-right: 0.6em;
 | 
			
		||||
}
 | 
			
		||||
#padeditor {
 | 
			
		||||
  position: static
 | 
			
		||||
}
 | 
			
		||||
/* lists */
 | 
			
		||||
.list-bullet2,
 | 
			
		||||
.list-indent2,
 | 
			
		||||
.list-number2 {
 | 
			
		||||
  margin-left: 3em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet3,
 | 
			
		||||
.list-indent3,
 | 
			
		||||
.list-number3 {
 | 
			
		||||
  margin-left: 4.5em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet4,
 | 
			
		||||
.list-indent4,
 | 
			
		||||
.list-number4 {
 | 
			
		||||
  margin-left: 6em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet5,
 | 
			
		||||
.list-indent5,
 | 
			
		||||
.list-number5 {
 | 
			
		||||
  margin-left: 7.5em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet6,
 | 
			
		||||
.list-indent6,
 | 
			
		||||
.list-number6 {
 | 
			
		||||
  margin-left: 9em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet7,
 | 
			
		||||
.list-indent7,
 | 
			
		||||
.list-number7 {
 | 
			
		||||
  margin-left: 10.5em
 | 
			
		||||
}
 | 
			
		||||
.list-bullet8,
 | 
			
		||||
.list-indent8,
 | 
			
		||||
.list-number8 {
 | 
			
		||||
  margin-left: 12em
 | 
			
		||||
}
 | 
			
		||||
/* unordered lists */
 | 
			
		||||
UL {
 | 
			
		||||
  list-style-type: disc;
 | 
			
		||||
  margin-left: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
UL UL {
 | 
			
		||||
  margin-left: 0 !important
 | 
			
		||||
}
 | 
			
		||||
.list-bullet2,
 | 
			
		||||
.list-bullet5,
 | 
			
		||||
.list-bullet8 {
 | 
			
		||||
  list-style-type: circle
 | 
			
		||||
}
 | 
			
		||||
.list-bullet3,
 | 
			
		||||
.list-bullet6 {
 | 
			
		||||
  list-style-type: square
 | 
			
		||||
}
 | 
			
		||||
.list-indent1,
 | 
			
		||||
.list-indent2,
 | 
			
		||||
.list-indent3,
 | 
			
		||||
.list-indent5,
 | 
			
		||||
.list-indent5,
 | 
			
		||||
.list-indent6,
 | 
			
		||||
.list-indent7,
 | 
			
		||||
.list-indent8 {
 | 
			
		||||
  list-style-type: none
 | 
			
		||||
}
 | 
			
		||||
/* ordered lists */
 | 
			
		||||
OL {
 | 
			
		||||
  list-style-type: decimal;
 | 
			
		||||
  margin-left: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
.list-number2,
 | 
			
		||||
.list-number5,
 | 
			
		||||
.list-number8 {
 | 
			
		||||
  list-style-type: lower-latin
 | 
			
		||||
}
 | 
			
		||||
.list-number3,
 | 
			
		||||
.list-number6 {
 | 
			
		||||
  list-style-type: lower-roman
 | 
			
		||||
}
 | 
			
		||||
/* IE 6/7 fixes */
 | 
			
		||||
* HTML #ui-slider-handle {
 | 
			
		||||
  background-image: url(../../static/img/current_location.gif)
 | 
			
		||||
}
 | 
			
		||||
* HTML #timeslider .star {
 | 
			
		||||
  background-image: url(../../static/img/star.gif)
 | 
			
		||||
}
 | 
			
		||||
* HTML #playpause_button_icon {
 | 
			
		||||
  background-image: url(../../static/img/play.gif)
 | 
			
		||||
}
 | 
			
		||||
* HTML .pause#playpause_button_icon {
 | 
			
		||||
  background-image: url(../../static/img/pause.gif)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								src/static/custom/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -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  | 
							
								
								
									
										
											BIN
										
									
								
								src/static/img/etherpad_lite_icons.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 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  | 
							
								
								
									
										
											BIN
										
									
								
								src/static/img/star.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 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  | 
							
								
								
									
										164
									
								
								src/static/js/AttributeManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,164 @@
 | 
			
		||||
var Changeset = require('./Changeset');
 | 
			
		||||
var ChangesetUtils = require('./ChangesetUtils');
 | 
			
		||||
var _ = require('./underscore');
 | 
			
		||||
 | 
			
		||||
var lineMarkerAttribute = 'lmkr';
 | 
			
		||||
 | 
			
		||||
// If one of these attributes are set to the first character of a 
 | 
			
		||||
// line it is considered as a line attribute marker i.e. attributes
 | 
			
		||||
// set on this marker are applied to the whole line. 
 | 
			
		||||
// The list attribute is only maintained for compatibility reasons
 | 
			
		||||
var lineAttributes = [lineMarkerAttribute,'list'];
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  The Attribute manager builds changesets based on a document 
 | 
			
		||||
  representation for setting and removing range or line-based attributes.
 | 
			
		||||
  
 | 
			
		||||
  @param rep the document representation to be used
 | 
			
		||||
  @param applyChangesetCallback this callback will be called 
 | 
			
		||||
    once a changeset has been built.
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
  A document representation contains 
 | 
			
		||||
  - an array `alines` containing 1 attributes string for each line 
 | 
			
		||||
  - an Attribute pool `apool`
 | 
			
		||||
  - a SkipList `lines` containing the text lines of the document.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var AttributeManager = function(rep, applyChangesetCallback)
 | 
			
		||||
{
 | 
			
		||||
  this.rep = rep;
 | 
			
		||||
  this.applyChangesetCallback = applyChangesetCallback;
 | 
			
		||||
  this.author = '';
 | 
			
		||||
  
 | 
			
		||||
  // If the first char in a line has one of the following attributes
 | 
			
		||||
  // it will be considered as a line marker
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributeManager.lineAttributes = lineAttributes;
 | 
			
		||||
 | 
			
		||||
AttributeManager.prototype = _(AttributeManager.prototype).extend({
 | 
			
		||||
  
 | 
			
		||||
  applyChangeset: function(changeset){
 | 
			
		||||
    if(!this.applyChangesetCallback) return changeset;
 | 
			
		||||
    
 | 
			
		||||
    var cs = changeset.toString();
 | 
			
		||||
    if (!Changeset.isIdentity(cs))
 | 
			
		||||
    {
 | 
			
		||||
      this.applyChangesetCallback(cs);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return changeset;
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  /*
 | 
			
		||||
    Sets attributes on a range
 | 
			
		||||
    @param start [row, col] tuple pointing to the start of the range
 | 
			
		||||
    @param end [row, col] tuple pointing to the end of the range
 | 
			
		||||
    @param attribute: an array of attributes
 | 
			
		||||
  */
 | 
			
		||||
  setAttributesOnRange: function(start, end, attribs)
 | 
			
		||||
  {
 | 
			
		||||
    var builder = Changeset.builder(this.rep.lines.totalWidth());
 | 
			
		||||
    ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
 | 
			
		||||
    ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
 | 
			
		||||
    return this.applyChangeset(builder);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /* 
 | 
			
		||||
    Returns if the line already has a line marker
 | 
			
		||||
    @param lineNum: the number of the line
 | 
			
		||||
  */
 | 
			
		||||
  lineHasMarker: function(lineNum){
 | 
			
		||||
    var that = this;
 | 
			
		||||
    
 | 
			
		||||
    return _.find(lineAttributes, function(attribute){
 | 
			
		||||
      return that.getAttributeOnLine(lineNum, attribute) != ''; 
 | 
			
		||||
    }) !== undefined;
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  /*
 | 
			
		||||
    Gets a specified attribute on a line
 | 
			
		||||
    @param lineNum: the number of the line to set the attribute for
 | 
			
		||||
    @param attributeKey: the name of the attribute to get, e.g. list  
 | 
			
		||||
  */
 | 
			
		||||
  getAttributeOnLine: function(lineNum, attributeName){
 | 
			
		||||
    // get  `attributeName` attribute of first char of line
 | 
			
		||||
    var aline = this.rep.alines[lineNum];
 | 
			
		||||
    if (aline)
 | 
			
		||||
    {
 | 
			
		||||
      var opIter = Changeset.opIterator(aline);
 | 
			
		||||
      if (opIter.hasNext())
 | 
			
		||||
      {
 | 
			
		||||
        return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return '';
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  /*
 | 
			
		||||
    Sets a specified attribute on a line
 | 
			
		||||
    @param lineNum: the number of the line to set the attribute for
 | 
			
		||||
    @param attributeKey: the name of the attribute to set, e.g. list
 | 
			
		||||
    @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
 | 
			
		||||
  
 | 
			
		||||
  */
 | 
			
		||||
  setAttributeOnLine: function(lineNum, attributeName, attributeValue){
 | 
			
		||||
    var loc = [0,0];
 | 
			
		||||
    var builder = Changeset.builder(this.rep.lines.totalWidth());
 | 
			
		||||
    var hasMarker = this.lineHasMarker(lineNum);
 | 
			
		||||
    
 | 
			
		||||
    ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
 | 
			
		||||
 | 
			
		||||
    if(hasMarker){
 | 
			
		||||
      ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
 | 
			
		||||
        [attributeName, attributeValue]
 | 
			
		||||
      ], this.rep.apool);
 | 
			
		||||
    }else{      
 | 
			
		||||
        // add a line marker
 | 
			
		||||
        builder.insert('*', [
 | 
			
		||||
          ['author', this.author],
 | 
			
		||||
          ['insertorder', 'first'],
 | 
			
		||||
          [lineMarkerAttribute, '1'],
 | 
			
		||||
          [attributeName, attributeValue]
 | 
			
		||||
        ], this.rep.apool);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return this.applyChangeset(builder);
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  /*
 | 
			
		||||
     Removes a specified attribute on a line
 | 
			
		||||
     @param lineNum: the number of the affected line
 | 
			
		||||
     @param attributeKey: the name of the attribute to remove, e.g. list
 | 
			
		||||
 | 
			
		||||
   */
 | 
			
		||||
   removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
 | 
			
		||||
     
 | 
			
		||||
     var loc = [0,0];
 | 
			
		||||
     var builder = Changeset.builder(this.rep.lines.totalWidth());
 | 
			
		||||
     var hasMarker = this.lineHasMarker(lineNum);
 | 
			
		||||
     
 | 
			
		||||
     if(hasMarker){
 | 
			
		||||
       ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
 | 
			
		||||
       ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1]));
 | 
			
		||||
     }
 | 
			
		||||
     
 | 
			
		||||
     return this.applyChangeset(builder);
 | 
			
		||||
   },
 | 
			
		||||
  
 | 
			
		||||
   /*
 | 
			
		||||
     Sets a specified attribute on a line
 | 
			
		||||
     @param lineNum: the number of the line to set the attribute for
 | 
			
		||||
     @param attributeKey: the name of the attribute to set, e.g. list
 | 
			
		||||
     @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
 | 
			
		||||
  */
 | 
			
		||||
  toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
 | 
			
		||||
    return this.getAttributeOnLine(attributeName) ?
 | 
			
		||||
      this.removeAttributeOnLine(lineNum, attributeName) :
 | 
			
		||||
      this.setAttributeOnLine(lineNum, attributeName, attributeValue);
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = AttributeManager;
 | 
			
		||||
							
								
								
									
										96
									
								
								src/static/js/AttributePool.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,96 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This code represents the Attribute Pool Object of the original Etherpad.
 | 
			
		||||
 * 90% of the code is still like in the original Etherpad
 | 
			
		||||
 * Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
 | 
			
		||||
 * You can find a explanation what a attribute pool is here:
 | 
			
		||||
 * https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS-IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  An AttributePool maintains a mapping from [key,value] Pairs called
 | 
			
		||||
  Attributes to Numbers (unsigened integers) and vice versa. These numbers are
 | 
			
		||||
  used to reference Attributes in Changesets.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var AttributePool = function () {
 | 
			
		||||
  this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
 | 
			
		||||
  this.attribToNum = {}; // e.g. {'foo,bar': 0}
 | 
			
		||||
  this.nextNum = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
 | 
			
		||||
  var str = String(attrib);
 | 
			
		||||
  if (str in this.attribToNum) {
 | 
			
		||||
    return this.attribToNum[str];
 | 
			
		||||
  }
 | 
			
		||||
  if (dontAddIfAbsent) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
  var num = this.nextNum++;
 | 
			
		||||
  this.attribToNum[str] = num;
 | 
			
		||||
  this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
 | 
			
		||||
  return num;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.getAttrib = function (num) {
 | 
			
		||||
  var pair = this.numToAttrib[num];
 | 
			
		||||
  if (!pair) {
 | 
			
		||||
    return pair;
 | 
			
		||||
  }
 | 
			
		||||
  return [pair[0], pair[1]]; // return a mutable copy
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.getAttribKey = function (num) {
 | 
			
		||||
  var pair = this.numToAttrib[num];
 | 
			
		||||
  if (!pair) return '';
 | 
			
		||||
  return pair[0];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.getAttribValue = function (num) {
 | 
			
		||||
  var pair = this.numToAttrib[num];
 | 
			
		||||
  if (!pair) return '';
 | 
			
		||||
  return pair[1];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.eachAttrib = function (func) {
 | 
			
		||||
  for (var n in this.numToAttrib) {
 | 
			
		||||
    var pair = this.numToAttrib[n];
 | 
			
		||||
    func(pair[0], pair[1]);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.toJsonable = function () {
 | 
			
		||||
  return {
 | 
			
		||||
    numToAttrib: this.numToAttrib,
 | 
			
		||||
    nextNum: this.nextNum
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributePool.prototype.fromJsonable = function (obj) {
 | 
			
		||||
  this.numToAttrib = obj.numToAttrib;
 | 
			
		||||
  this.nextNum = obj.nextNum;
 | 
			
		||||
  this.attribToNum = {};
 | 
			
		||||
  for (var n in this.numToAttrib) {
 | 
			
		||||
    this.attribToNum[String(this.numToAttrib[n])] = Number(n);
 | 
			
		||||
  }
 | 
			
		||||
  return this;
 | 
			
		||||
};
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
module.exports = AttributePool;
 | 
			
		||||
@ -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,
 | 
			
		||||
							
								
								
									
										60
									
								
								src/static/js/ChangesetUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,60 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This module contains several helper Functions to build Changesets
 | 
			
		||||
 * based on a SkipList
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright 2009 Google Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS-IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
exports.buildRemoveRange = function(rep, builder, start, end)
 | 
			
		||||
{
 | 
			
		||||
  var startLineOffset = rep.lines.offsetOfIndex(start[0]);
 | 
			
		||||
  var endLineOffset = rep.lines.offsetOfIndex(end[0]);
 | 
			
		||||
 | 
			
		||||
  if (end[0] > start[0])
 | 
			
		||||
  {
 | 
			
		||||
    builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
 | 
			
		||||
    builder.remove(end[1]);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    builder.remove(end[1] - start[1]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.buildKeepRange = function(rep, builder, start, end, attribs, pool)
 | 
			
		||||
{
 | 
			
		||||
  var startLineOffset = rep.lines.offsetOfIndex(start[0]);
 | 
			
		||||
  var endLineOffset = rep.lines.offsetOfIndex(end[0]);
 | 
			
		||||
 | 
			
		||||
  if (end[0] > start[0])
 | 
			
		||||
  {
 | 
			
		||||
    builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
 | 
			
		||||
    builder.keep(end[1], 0, attribs, pool);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    builder.keep(end[1] - start[1], 0, attribs, pool);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.buildKeepToStartOfRange = function(rep, builder, start)
 | 
			
		||||
{
 | 
			
		||||
  var startLineOffset = rep.lines.offsetOfIndex(start[0]);
 | 
			
		||||
 | 
			
		||||
  builder.keep(startLineOffset, start[0]);
 | 
			
		||||
  builder.keep(start[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
							
								
								
									
										143
									
								
								src/static/js/admin/plugins.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,143 @@
 | 
			
		||||
$(document).ready(function () {
 | 
			
		||||
  
 | 
			
		||||
  var socket,
 | 
			
		||||
    loc = document.location,
 | 
			
		||||
    port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
 | 
			
		||||
    url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
 | 
			
		||||
    pathComponents = location.pathname.split('/'),
 | 
			
		||||
    // Strip admin/plugins
 | 
			
		||||
    baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
 | 
			
		||||
    resource = baseURL.substring(1) + "socket.io";
 | 
			
		||||
 | 
			
		||||
  //connect
 | 
			
		||||
  socket = io.connect(url, {resource : resource}).of("/pluginfw/installer");
 | 
			
		||||
 | 
			
		||||
  $('.search-results').data('query', {
 | 
			
		||||
    pattern: '',
 | 
			
		||||
    offset: 0,
 | 
			
		||||
    limit: 4,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  var doUpdate = false;
 | 
			
		||||
 | 
			
		||||
  var search = function () {
 | 
			
		||||
    socket.emit("search", $('.search-results').data('query'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function updateHandlers() {
 | 
			
		||||
    $("#progress.dialog .close").unbind('click').click(function () {
 | 
			
		||||
      $("#progress.dialog").hide();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#do-search").unbind('click').click(function () {
 | 
			
		||||
      var query = $('.search-results').data('query');
 | 
			
		||||
      query.pattern = $("#search-query")[0].value;
 | 
			
		||||
      query.offset = 0;
 | 
			
		||||
      search();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(".do-install").unbind('click').click(function (e) {
 | 
			
		||||
      var row = $(e.target).closest("tr");
 | 
			
		||||
      doUpdate = true;
 | 
			
		||||
      socket.emit("install", row.find(".name").html());
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(".do-uninstall").unbind('click').click(function (e) {
 | 
			
		||||
      var row = $(e.target).closest("tr");
 | 
			
		||||
      doUpdate = true;
 | 
			
		||||
      socket.emit("uninstall", row.find(".name").html());
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(".do-prev-page").unbind('click').click(function (e) {
 | 
			
		||||
      var query = $('.search-results').data('query');
 | 
			
		||||
      query.offset -= query.limit;
 | 
			
		||||
      if (query.offset < 0) {
 | 
			
		||||
        query.offset = 0;
 | 
			
		||||
      }
 | 
			
		||||
      search();
 | 
			
		||||
    });
 | 
			
		||||
    $(".do-next-page").unbind('click').click(function (e) {
 | 
			
		||||
      var query = $('.search-results').data('query');
 | 
			
		||||
      var total = $('.search-results').data('total');
 | 
			
		||||
      if (query.offset + query.limit < total) {
 | 
			
		||||
        query.offset += query.limit;
 | 
			
		||||
      }
 | 
			
		||||
      search();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateHandlers();
 | 
			
		||||
 | 
			
		||||
  socket.on('progress', function (data) {
 | 
			
		||||
    if (data.progress > 0 && $('#progress.dialog').data('progress') > data.progress) return;
 | 
			
		||||
 | 
			
		||||
    $("#progress.dialog .close").hide();
 | 
			
		||||
    $("#progress.dialog").show();
 | 
			
		||||
 | 
			
		||||
    $('#progress.dialog').data('progress', data.progress);
 | 
			
		||||
 | 
			
		||||
    var message = "Unknown status";
 | 
			
		||||
    if (data.message) {
 | 
			
		||||
      message = "<span class='status'>" + data.message.toString() + "</span>";
 | 
			
		||||
    }
 | 
			
		||||
    if (data.error) {
 | 
			
		||||
      message = "<span class='error'>" + data.error.toString() + "<span>";            
 | 
			
		||||
    }
 | 
			
		||||
    $("#progress.dialog .message").html(message);
 | 
			
		||||
    $("#progress.dialog .history").append("<div>" + message + "</div>");
 | 
			
		||||
 | 
			
		||||
    if (data.progress >= 1) {
 | 
			
		||||
      if (data.error) {
 | 
			
		||||
        $("#progress.dialog .close").show();
 | 
			
		||||
      } else {
 | 
			
		||||
        if (doUpdate) {
 | 
			
		||||
          doUpdate = false;
 | 
			
		||||
          socket.emit("load");
 | 
			
		||||
        }
 | 
			
		||||
        $("#progress.dialog").hide();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  socket.on('search-result', function (data) {
 | 
			
		||||
    var widget=$(".search-results");
 | 
			
		||||
 | 
			
		||||
    widget.data('query', data.query);
 | 
			
		||||
    widget.data('total', data.total);
 | 
			
		||||
 | 
			
		||||
    widget.find('.offset').html(data.query.offset);
 | 
			
		||||
    widget.find('.limit').html(data.query.offset + data.query.limit);
 | 
			
		||||
    widget.find('.total').html(data.total);
 | 
			
		||||
 | 
			
		||||
    widget.find(".results *").remove();
 | 
			
		||||
    for (plugin_name in data.results) {
 | 
			
		||||
      var plugin = data.results[plugin_name];
 | 
			
		||||
      var row = widget.find(".template tr").clone();
 | 
			
		||||
 | 
			
		||||
      for (attr in plugin) {
 | 
			
		||||
        row.find("." + attr).html(plugin[attr]);
 | 
			
		||||
      }
 | 
			
		||||
      widget.find(".results").append(row);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateHandlers();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  socket.on('installed-results', function (data) {
 | 
			
		||||
    $("#installed-plugins *").remove();
 | 
			
		||||
    for (plugin_name in data.results) {
 | 
			
		||||
      var plugin = data.results[plugin_name];
 | 
			
		||||
      var row = $("#installed-plugin-template").clone();
 | 
			
		||||
 | 
			
		||||
      for (attr in plugin.package) {
 | 
			
		||||
        row.find("." + attr).html(plugin.package[attr]);
 | 
			
		||||
      }
 | 
			
		||||
      $("#installed-plugins").append(row);
 | 
			
		||||
    }
 | 
			
		||||
    updateHandlers();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  socket.emit("load");
 | 
			
		||||
  search();
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@ -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)
 | 
			
		||||
{
 | 
			
		||||
@ -161,50 +161,73 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
 | 
			
		||||
    // just take over the whole slider screen with a reconnect message
 | 
			
		||||
 | 
			
		||||
    function showReconnectUI()
 | 
			
		||||
    {
 | 
			
		||||
      if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
 | 
			
		||||
    {
 | 
			
		||||
      $("#padmain, #rightbars").css('top', "130px");
 | 
			
		||||
      $("#timeslider").show();
 | 
			
		||||
      }
 | 
			
		||||
      $('#error').show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 = {
 | 
			
		||||
@ -262,8 +285,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
 | 
			
		||||
      disableSelection($("#playpause_button")[0]);
 | 
			
		||||
      disableSelection($("#timeslider")[0]);
 | 
			
		||||
      
 | 
			
		||||
      if (clientVars.sliderEnabled && clientVars.supportsSlider)
 | 
			
		||||
      {
 | 
			
		||||
      $(document).keyup(function(e)
 | 
			
		||||
      {
 | 
			
		||||
        var code = -1;
 | 
			
		||||
@ -308,7 +329,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
 | 
			
		||||
        else if (code == 32) playpause();
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      $(window).resize(function()
 | 
			
		||||
      {
 | 
			
		||||
@ -460,37 +480,15 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
 | 
			
		||||
          $("#revision").css('top', "20px");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (clientVars.sliderEnabled)
 | 
			
		||||
        {
 | 
			
		||||
          if (clientVars.supportsSlider)
 | 
			
		||||
          {
 | 
			
		||||
            $("#padmain, #rightbars").css('top', "130px");
 | 
			
		||||
        $("#timeslider").show();
 | 
			
		||||
        setSliderLength(clientVars.totalRevs);
 | 
			
		||||
        setSliderPosition(clientVars.revNum);
 | 
			
		||||
            forEach(clientVars.savedRevisions, function(revision)
 | 
			
		||||
        
 | 
			
		||||
        _.each(clientVars.savedRevisions, function(revision)
 | 
			
		||||
        {
 | 
			
		||||
          addSavedRevision(revision.revNum, revision);
 | 
			
		||||
        })
 | 
			
		||||
          }
 | 
			
		||||
          else
 | 
			
		||||
          {
 | 
			
		||||
            // slider is not supported
 | 
			
		||||
            $("#padmain, #rightbars").css('top', "130px");
 | 
			
		||||
            $("#timeslider").show();
 | 
			
		||||
            $("#error").html("The timeslider feature is not supported on this pad. <a href=\"/ep/about/faq#disabledslider\">Why not?</a>");
 | 
			
		||||
            $("#error").show();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
          if (clientVars.supportsSlider)
 | 
			
		||||
          {
 | 
			
		||||
            setSliderLength(clientVars.totalRevs);
 | 
			
		||||
            setSliderPosition(clientVars.revNum);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  })();
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||