Merge branch 'develop' into ace2_refactoring

This commit is contained in:
Matthias Bartelmeß 2012-04-03 19:30:57 +02:00
commit 2dd60b0b6d
48 changed files with 1545 additions and 1779 deletions

3
.gitignore vendored
View File

@ -1,7 +1,5 @@
node_modules
settings.json
static/js/jquery.js
static/js/prefixfree.js
APIKEY.txt
bin/abiword.exe
bin/node.exe
@ -11,6 +9,5 @@ bin/convertSettings.json
*~
*.patch
src/static/js/jquery.js
src/static/js/prefixfree.js
npm-debug.log
*.DS_Store

16
README.plugins Normal file
View File

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

View File

@ -25,7 +25,8 @@
"hooks": {
"somehookname": "ep_fintest/otherpart:somehook",
"morehook": "ep_fintest/otherpart:morehook",
"expressCreateServer": "ep_fintest/otherpart:expressServer"
"expressCreateServer": "ep_fintest/otherpart:expressServer",
"eejsBlock_editbarMenuLeft": "ep_fintest/otherpart:eejsBlock_editbarMenuLeft"
},
"client_hooks": {
"somehookname": "ep_fintest/static/js/test:bar"

View File

@ -14,3 +14,12 @@ exports.expressServer = function (hook_name, args, cb) {
res.send("<em>Abra cadabra</em>");
});
}
exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) {
args.content = args.content + '\
<li id="testButton" onClick="window.pad&amp;&amp;pad.editbarClick(\'clearauthorship\');return false;">\
<a class="buttonicon buttonicon-test" title="Test test test"></a>\
</li>\
';
return cb();
}

View File

@ -81,21 +81,6 @@ if [ $DOWNLOAD_JQUERY = "true" ]; then
curl -lo src/static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
fi
echo "Ensure prefixfree is downloaded and up to date..."
DOWNLOAD_PREFIXFREE="true"
NEEDED_VERSION="1.0.4"
if [ -f "src/static/js/prefixfree.js" ]; then
VERSION=$(cat src/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 src/static/js/prefixfree.js -k https://raw.github.com/LeaVerou/prefixfree/master/prefixfree.js || exit 1
fi
#Remove all minified data to force node creating it new
echo "Clear minfified cache..."
rm -f var/minified*

View File

@ -50,6 +50,12 @@
/* This setting is used if you need http basic auth */
// "httpAuth" : "user:pass",
/* This setting is used for http basic auth for admin pages. If not set, the admin page won't be accessible from web*/
// "adminHttpAuth" : "user:pass",
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO"
"loglevel": "INFO",
/* cache 6 hours = 1000*60*60*6 */
"maxAge": 21600000
}

View File

@ -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
}

View File

@ -8,7 +8,9 @@
{ "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": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
{ "name": "adminplugins", "hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins:socketio" } }
]
}

View File

@ -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
@ -34,7 +39,7 @@ var Pad = function Pad(id) {
this.publicStatus = false;
this.passwordHash = null;
this.id = id;
this.savedRevisions = [];
};
exports.Pad = Pad;
@ -75,15 +80,28 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
newRevData.meta.atext = this.atext;
}
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
db.set("pad:"+this.id, {atext: this.atext,
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
publicStatus: this.publicStatus,
passwordHash: this.passwordHash});
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
this.saveToDatabase();
};
//save all attributes to the database
Pad.prototype.saveToDatabase = function saveToDatabase(){
var dbObject = {};
for(var attr in this){
if(typeof this[attr] === "function") continue;
if(attributeBlackList.indexOf(attr) !== -1) continue;
dbObject[attr] = this[attr];
if(jsonableList.indexOf(attr) !== -1){
dbObject[attr] = dbObject[attr].toJsonable();
}
}
db.set("pad:"+this.id, dbObject);
}
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
};
@ -200,11 +218,10 @@ Pad.prototype.setText = function setText(newText) {
};
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++;
//save the chat entry in the database
db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
//save the new chat head
db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
this.chatHead++;
//save the chat entry in the database
db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
this.saveToDatabase();
};
Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
@ -324,27 +341,14 @@ Pad.prototype.init = function init(text, callback) {
//if this pad exists, load it
if(value != null)
{
_this.head = value.head;
_this.atext = value.atext;
_this.pool = _this.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead != null)
_this.chatHead = value.chatHead;
else
_this.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
_this.publicStatus = value.publicStatus;
else
_this.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
_this.passwordHash = value.passwordHash;
else
_this.passwordHash = null;
//copy all attr. To a transfrom via fromJsonable if necassary
for(var attr in value){
if(jsonableList.indexOf(attr) !== -1){
_this[attr] = _this[attr].fromJsonable(value[attr]);
} else {
_this[attr] = value[attr];
}
}
}
//this pad doesn't exist, so create it
else
@ -452,12 +456,12 @@ Pad.prototype.remove = function remove(callback) {
//set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus;
db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
this.saveToDatabase();
};
Pad.prototype.setPassword = function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt());
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
this.saveToDatabase();
};
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
@ -468,6 +472,31 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
return this.passwordHash != null;
};
Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) {
//if this revision is already saved, return silently
for(var i in this.savedRevisions){
if(this.savedRevisions.revNum === revNum){
return;
}
}
//build the saved revision object
var savedRevision = {};
savedRevision.revNum = revNum;
savedRevision.savedById = savedById;
savedRevision.label = label || "Revision " + revNum;
savedRevision.timestamp = new Date().getTime();
savedRevision.id = randomString(10);
//save this new saved revision
this.savedRevisions.push(savedRevision);
this.saveToDatabase();
};
Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions;
};
/* Crypto helper methods */
function hash(password, salt)

View File

@ -115,7 +115,13 @@ exports.doesPadExists = function(padId, callback)
db.get("pad:"+padId, function(err, value)
{
if(ERR(err, callback)) return;
callback(null, value != null && value.atext);
if(value != null && value.atext){
callback(null, true);
}
else
{
callback(null, false);
}
});
}

View File

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

View File

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

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

@ -0,0 +1,115 @@
/*
* 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");
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) {
if (args == undefined) args = {};
if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) {
name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path), name);
}
var ejspath = require.resolve(name)
args.e = exports;
args.require = require;
var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
exports.info.file_stack.push({path: ejspath, inherit: []});
var res = ejs.render(template, args);
exports.info.file_stack.pop();
return res;
}
exports._require = function (name, args) {
exports.info.buf.push(exports.require(name, args));
}

View File

@ -190,6 +190,11 @@ exports.handleMessage = function(client, message)
{
handleChatMessage(client, message);
}
else if(message.type == "COLLABROOM" &&
message.data.type == "SAVE_REVISION")
{
handleSaveRevisionMessage(client, message);
}
else if(message.type == "COLLABROOM" &&
message.data.type == "CLIENT_MESSAGE" &&
message.data.payload.type == "suggestUserName")
@ -203,6 +208,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

View File

@ -166,6 +166,7 @@ function createTimesliderClientVars (padId, callback)
hooks: [],
initialStyledContents: {}
};
var pad;
var initialChangesets = [];
@ -180,6 +181,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)
{

View File

@ -0,0 +1,51 @@
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) {
socket.on("load", function (query) {
socket.emit("installed-results", {results: plugins.plugins});
});
socket.on("search", function (query) {
socket.emit("progress", {progress:0, message:'Fetching results...'});
installer.search(query, function (progress) {
if (progress.results)
socket.emit("search-result", progress);
socket.emit("progress", progress);
});
});
socket.on("install", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
installer.install(plugin_name, function (progress) {
socket.emit("progress", progress);
});
});
socket.on("uninstall", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
installer.uninstall(plugin_name, function (progress) {
socket.emit("progress", progress);
});
});
});
}

View File

@ -1,12 +1,12 @@
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)
{
var filePath = path.normalize(__dirname + "/../../../static/index.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
res.send(eejs.require("ep_etherpad-lite/templates/index.html"), { maxAge: exports.maxAge });
});
//serve robots.txt
@ -34,15 +34,13 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next)
{
var filePath = path.normalize(__dirname + "/../../../static/pad.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
res.send(eejs.require("ep_etherpad-lite/templates/pad.html"), { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', function(req, res, next)
{
var filePath = path.normalize(__dirname + "/../../../static/timeslider.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"), { maxAge: exports.maxAge });
});
}

View File

@ -6,14 +6,30 @@ var settings = require('../../utils/Settings');
//checks for basic http auth
exports.basicAuth = function (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;
// When handling HTTP-Auth, an undefined password will lead to no authorization at all
var pass = settings.httpAuth || '';
if (req.path.indexOf('/admin') == 0) {
var pass = settings.adminHttpAuth;
}
// Just pass if password is an empty string
if (pass === '') {
return next();
}
// If a password has been set and auth headers are present...
if (pass && req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
// ...check login and password
if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() === pass) {
return next();
}
}
// Otherwise return Auth required Headers, delayed for 1 second, if auth failed.
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
@ -25,8 +41,7 @@ exports.basicAuth = function (req, res, next) {
}
exports.expressConfigure = function (hook_name, args, cb) {
// Activate http basic auth if it has been defined in settings.json
if(settings.httpAuth != null) args.app.use(exports.basicAuth);
args.app.use(exports.basicAuth);
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.

View File

@ -29,7 +29,6 @@ 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');
@ -109,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) {
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);
}
}

View File

@ -85,6 +85,11 @@ exports.loglevel = "INFO";
*/
exports.httpAuth = null;
/**
* Http basic auth, with "user:password" format
*/
exports.adminHttpAuth = null;
//checks if abiword is avaiable
exports.abiwordAvailable = function()
{

View File

@ -18,13 +18,11 @@ 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 = path.normalize(ROOT_DIR + '../../var/');
console.log(CACHE_DIR)
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {};

View File

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

View File

@ -22,7 +22,6 @@
, "chat.js"
, "excanvas.js"
, "farbtastic.js"
, "prefixfree.js"
]
, "timeslider.js": [
"jquery.js"

View File

@ -23,7 +23,12 @@
"log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2",
"npm" : "1.1",
"npm" : "1.1",
"ejs" : "0.6.1",
"node.extend" : "1.0.0",
"graceful-fs" : "1.1.5",
"slide" : "1.1.3",
"semver" : "1.0.13",
"underscore" : "1.3.1"
},
"devDependencies": {

View File

@ -24,7 +24,7 @@ a img
}
/* menu */
#editbar ul
.toolbar ul
{
position: relative;
list-style: none;
@ -35,18 +35,20 @@ a img
}
#editbar
.toolbar
{
background: #f7f7f7;
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
border-bottom: 1px solid #ccc;
height: 32px;
overflow: hidden;
padding-top: 3px;
width: 100%;
position: absolute;
left: 0;
right: 0;
height: 32px;
}
#editbar ul li
.toolbar ul li
{
background: #fff;
background: linear-gradient(#fff, #f0f0f0);
@ -61,52 +63,52 @@ a img
width: 18px;
}
#editbar ul li a
.toolbar ul li a
{
text-decoration: none;
color: #ccc;
position: absolute;
}
#editbar ul li a span
.toolbar ul li a span
{
position: relative;
top:-2px
}
#editbar ul li:hover {
.toolbar ul li:hover {
background: #fff;
background: linear-gradient(#f4f4f4, #e4e4e4);
}
#editbar ul li:active {
.toolbar ul li:active {
background: #eee;
background: linear-gradient(#ddd, #fff);
box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
}
#editbar ul li.separator
.toolbar ul li.separator
{
border: inherit;
background: inherit;
visibility:hidden;
width: 0px;
}
#editbar ul li a
.toolbar ul li a
{
display: block;
}
#editbar ul li a img
.toolbar ul li a img
{
padding: 1px;
}
#editbar ul
.toolbar ul
{
float: left;
}
#editbar ul#menu_right
.toolbar ul.menu_right
{
float: right;
}
@ -320,7 +322,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
z-index: 10;
}
#editbarsavetable
.toolbarsavetable
{
position:absolute;
top: 6px;
@ -328,7 +330,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
height: 24px;
}
#editbarsavetable td, #editbartable td
.toolbarsavetable td, .toolbartable td
{
white-space: nowrap;
}
@ -688,14 +690,15 @@ a#topbarmaximize {
background: url(static/img/maximize_maximized.png);
}
#editbarinner h1 {
.toolbarinner h1 {
line-height: 29px;
font-size: 16px;
padding-left: 6pt;
margin-top: 0;
white-space: nowrap;
}
#editbarinner h1 a {
.toolbarinner h1 a {
font-size: 12px;
}
@ -1034,6 +1037,9 @@ margin-top: 1px;
background-position: 0px -183px;
display: inline-block;
}
.buttonicon-savedRevision {
background-position: 0px -493px
}
#usericon
{
@ -1173,13 +1179,13 @@ input[type=checkbox] {
}
@media screen and (max-width: 600px) {
#editbar ul li {
.toolbar ul li {
padding: 4px 1px;
}
}
@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
#editbar ul li {
.toolbar ul li {
padding: 4px 3px;
}
#users {
@ -1194,7 +1200,7 @@ input[type=checkbox] {
#editorcontainer {
margin-bottom: 33px;
}
#editbar ul#menu_right {
.toolbar ul.menu_right {
background: #f7f7f7;
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
width: 100%;
@ -1204,7 +1210,7 @@ input[type=checkbox] {
bottom: 0;
border-top: 1px solid #ccc;
}
#editbar ul#menu_right li:last-child {
.toolbar ul.menu_right li:last-child {
height: 24px;
border-radius: 0;
margin-top: 0;
@ -1226,7 +1232,7 @@ input[type=checkbox] {
border-top-right-radius: 0;
border-right: none;
}
#editbar ul li a span {
.toolbar ul li a span {
top: -3px;
}
#usericonback {
@ -1235,10 +1241,10 @@ input[type=checkbox] {
#qrcode {
display: none;
}
#editbar ul#menu_right li:not(:last-child) {
.toolbar ul.menu_right li:not(:last-child) {
display: block;
}
#editbar ul#menu_right > li {
.toolbar ul.menu_right > li {
background: none;
border: none;
margin-top: 4px;
@ -1267,4 +1273,4 @@ input[type=checkbox] {
#online_count {
line-height: 24px;
}
}
}

View File

@ -42,10 +42,10 @@
#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;}
#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);
@ -71,8 +71,11 @@
#padmain {top:30px;}
#editbarright {float:right;}
#returnbutton {color:#222; font-size:16px; line-height:29px; margin-top:0; padding-right:6px;}
#importexport {top:118px;}
#importexport .popup {width:185px;}
#importexport{
top:118px;
width:185px;
}
/* lists */
.list-bullet2, .list-indent2, .list-number2 {margin-left:3em;}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -233,14 +233,16 @@ require.setGlobalKeyPath("require");\n\
iframeHTML.push(doctype);
iframeHTML.push("<html><head>");
iframeHTML.push('<script type="text/javascript" src="../static/js/jquery.js"></script>');
hooks.callAll("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
});
// For compatability's sake transform in and out.
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
hooks.callAll("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
});
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.parse(iframeHTML[i]);
}
@ -262,6 +264,11 @@ require.setGlobalKeyPath("require");\n\
// Inject my plugins into my child.
iframeHTML.push('\
<script type="text/javascript">\
parent_req = require("./pluginfw/parent_require.js");\
parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/hooks");\
parent_req.getRequirementFromParent(require, "ep_etherpad-lite/static/js/pluginfw/plugins");\
parent_req.getRequirementFromParent(require, "./pluginfw/hooks");\
parent_req.getRequirementFromParent(require, "./pluginfw/plugins");\
require.define("/plugins", null);\n\
require.define("/plugins.js", function (require, exports, module) {\
module.exports = require("ep_etherpad-lite/static/js/plugins");\

View File

@ -841,7 +841,7 @@ function Ace2Inner(){
var cmdArgs = Array.prototype.slice.call(arguments, 1);
if (CMDS[cmd])
{
inCallStack(cmd, function()
inCallStackIfNecessary(cmd, function()
{
fastIncorp(9);
CMDS[cmd].apply(CMDS, cmdArgs);
@ -851,7 +851,7 @@ function Ace2Inner(){
function replaceRange(start, end, text)
{
inCallStack('replaceRange', function()
inCallStackIfNecessary('replaceRange', function()
{
fastIncorp(9);
performDocumentReplaceRange(start, end, text);
@ -1152,7 +1152,7 @@ function Ace2Inner(){
return;
}
inCallStack("idleWorkTimer", function()
inCallStackIfNecessary("idleWorkTimer", function()
{
var isTimeUp = newTimeLimit(250);
@ -1991,6 +1991,7 @@ function Ace2Inner(){
return [lineNum, col];
}
}
editorInfo.ace_getLineAndCharForPoint = getLineAndCharForPoint;
function createDomLineEntry(lineString)
{
@ -2276,6 +2277,7 @@ function Ace2Inner(){
var cs = builder.toString();
performDocumentApplyChangeset(cs);
}
editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange;
function buildKeepToStartOfRange(builder, start)
{
@ -2801,6 +2803,7 @@ function Ace2Inner(){
currentCallStack.selectionAffected = true;
}
}
editorInfo.ace_performSelectionChange = performSelectionChange;
// Change the abstract representation of the document to have a different selection.
// Should not rely on the line representation. Should not affect the DOM.
@ -3228,7 +3231,7 @@ function Ace2Inner(){
function handleClick(evt)
{
inCallStack("handleClick", function()
inCallStackIfNecessary("handleClick", function()
{
idleWorkTimer.atMost(200);
});
@ -3550,7 +3553,7 @@ function Ace2Inner(){
var stopped = false;
inCallStack("handleKeyEvent", function()
inCallStackIfNecessary("handleKeyEvent", function()
{
if (type == "keypress" || (isTypeForSpecialKey && keyCode == 13 /*return*/ ))
@ -4628,7 +4631,7 @@ function Ace2Inner(){
}
// click below the body
inCallStack("handleOuterClick", function()
inCallStackIfNecessary("handleOuterClick", function()
{
// put caret at bottom of doc
fastIncorp(11);
@ -4665,6 +4668,54 @@ function Ace2Inner(){
else $(elem).removeClass(elem, className);
}
function setup()
{
doc = document; // defined as a var in scope outside
inCallStackIfNecessary("setup", function()
{
var body = doc.getElementById("innerdocbody");
root = body; // defined as a var in scope outside
if (browser.mozilla) addClass(root, "mozilla");
if (browser.safari) addClass(root, "safari");
if (browser.msie) addClass(root, "msie");
if (browser.msie)
{
// cache CSS background images
try
{
doc.execCommand("BackgroundImageCache", false, true);
}
catch (e)
{ /* throws an error in some IE 6 but not others! */
}
}
setClassPresence(root, "authorColors", true);
setClassPresence(root, "doesWrap", doesWrap);
initDynamicCSS();
enforceEditability();
// set up dom and rep
while (root.firstChild) root.removeChild(root.firstChild);
var oneEntry = createDomLineEntry("");
doRepLineSplice(0, rep.lines.length(), [oneEntry]);
insertDomLines(null, [oneEntry.domInfo], null);
rep.alines = Changeset.splitAttributionLines(
Changeset.makeAttribution("\n"), "\n");
bindTheEventHandlers();
});
scheduler.setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe
}, 0);
isSetUp = true;
}
function focus()
{
window.focus();

View File

@ -465,7 +465,7 @@ if (!JSON)
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
throw new SyntaxError('JSON.parse: ' + text);
};
}
}());

View File

@ -31,7 +31,6 @@ require('./farbtastic');
require('./excanvas');
JSON = require('./json2');
require('./undo-xpopup');
require('./prefixfree');
var chat = require('./chat').chat;
var getCollabClient = require('./collab_client').getCollabClient;
@ -42,7 +41,7 @@ var padeditbar = require('./pad_editbar').padeditbar;
var padeditor = require('./pad_editor').padeditor;
var padimpexp = require('./pad_impexp').padimpexp;
var padmodals = require('./pad_modals').padmodals;
var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
var padsavedrevs = require('./pad_savedrevs');
var paduserlist = require('./pad_userlist').paduserlist;
var padutils = require('./pad_utils').padutils;
@ -50,6 +49,50 @@ var createCookie = require('./pad_utils').createCookie;
var readCookie = require('./pad_utils').readCookie;
var randomString = require('./pad_utils').randomString;
var hooks = require('./pluginfw/hooks');
function createCookie(name, value, days, path)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
if(!path)
path = "/";
document.cookie = name + "=" + value + expires + "; path=" + path;
}
function readCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function randomString()
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var string_length = 20;
var randomstring = '';
for (var i = 0; i < string_length; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return "t." + randomstring;
}
function getParams()
{
var params = getUrlVars()
@ -457,7 +500,7 @@ var pad = {
guestPolicy: pad.padOptions.guestPolicy
}, this);
padimpexp.init(this);
padsavedrevs.init(clientVars.initialRevisionList, this);
padsavedrevs.init(this);
padeditor.init(postAceInit, pad.padOptions.view || {}, this);
@ -491,6 +534,7 @@ var pad = {
if(padcookie.getPref("showAuthorshipColors") == false){
pad.changeViewOption('showAuthorColors', false);
}
hooks.aCallAll("postAceInit");
}
},
dispose: function()

View File

@ -449,7 +449,7 @@ var paddocbar = (function()
handleResizePage: function()
{
// Side-step circular reference. This should be injected.
var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
var padsavedrevs = require('./pad_savedrevs');
padsavedrevs.handleResizePage();
},
hideLaterIfNoOtherInteraction: function()

View File

@ -22,7 +22,7 @@
var padutils = require('./pad_utils').padutils;
var padeditor = require('./pad_editor').padeditor;
var padsavedrevs = require('./pad_savedrevs').padsavedrevs;
var padsavedrevs = require('./pad_savedrevs');
function indexOf(array, value) {
for (var i = 0, ii = array.length; i < ii; i++) {
@ -131,7 +131,7 @@ var padeditbar = (function()
{
self.toogleDropDown("importexport");
}
else if (cmd == 'save')
else if (cmd == 'savedRevision')
{
padsavedrevs.saveNow();
}

View File

@ -87,8 +87,6 @@ var padeditor = (function()
return defaultValue;
}
self.ace.setProperty("showsauthorcolors", !settings.noColors);
self.ace.setProperty("rtlIsTrue", settings.rtlIsTrue);
var v;
@ -100,6 +98,8 @@ var padeditor = (function()
v = getOption('showAuthorColors', true);
self.ace.setProperty("showsauthorcolors", v);
padutils.setCheckbox($("#options-colorscheck"), v);
// Override from parameters
self.ace.setProperty("showsauthorcolors", !settings.noColors);
v = getOption('useMonospaceFont', false);
self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));

View File

@ -1,11 +1,5 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
* Copyright 2012 Peter 'Pita' Martischka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,507 +14,13 @@
* limitations under the License.
*/
var padutils = require('./pad_utils').padutils;
var paddocbar = require('./pad_docbar').paddocbar;
var pad;
var padsavedrevs = (function()
{
exports.saveNow = function(){
pad.collabClient.sendMessage({"type": "SAVE_REVISION"});
alert("This revision is now marked as a saved revision");
}
function reversedCopy(L)
{
var L2 = L.slice();
L2.reverse();
return L2;
}
function makeRevisionBox(revisionInfo, rnum)
{
var box = $('<div class="srouterbox">' + '<div class="srinnerbox">' + '<a href="javascript:void(0)" class="srname"><!-- --></a>' + '<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>' + '<div class="srtime"><!-- --></div>' + '<div class="srauthor"><!-- --></div>' + '<img class="srtwirly" src="static/img/misc/status-ball.gif">' + '</div></div>');
setBoxLabel(box, revisionInfo.label);
setBoxTimestamp(box, revisionInfo.timestamp);
box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy));
var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id;
box.find(".srview").attr('href', viewLink);
var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');';
box.find(".srrestore").attr('href', restoreLink);
box.find(".srname").click(function(evt)
{
editRevisionLabel(rnum, box);
});
return box;
}
function setBoxLabel(box, label)
{
box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
}
function setBoxTimestamp(box, timestamp)
{
box.find(".srtime").html(padutils.escapeHtml(
padutils.timediff(new Date(timestamp))));
}
function getNthBox(n)
{
return $("#savedrevisions .srouterbox").eq(n);
}
function editRevisionLabel(rnum, box)
{
var input = $('<input type="text" class="srnameedit"/>');
box.find(".srnameedit").remove(); // just in case
var label = box.find(".srname");
input.width(label.width());
input.height(label.height());
input.css('top', label.position().top);
input.css('left', label.position().left);
label.after(input);
label.css('opacity', 0);
function endEdit()
{
input.remove();
label.css('opacity', 1);
}
var rev = currentRevisionList[rnum];
var oldLabel = rev.label;
input.blur(function()
{
var newLabel = input.val();
if (newLabel && newLabel != oldLabel)
{
relabelRevision(rnum, newLabel);
}
endEdit();
});
input.val(rev.label).focus().select();
padutils.bindEnterAndEscape(input, function onEnter()
{
input.blur();
}, function onEscape()
{
input.val('').blur();
});
}
function relabelRevision(rnum, newLabel)
{
var rev = currentRevisionList[rnum];
$.ajax(
{
type: 'post',
url: '/ep/pad/saverevisionlabel',
data: {
userId: pad.getUserId(),
padId: pad.getPadId(),
revId: rev.id,
newLabel: newLabel
},
success: success,
error: error
});
function success(text)
{
var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList);
pad.sendClientMessage(
{
type: 'revisionLabel',
revisionList: reversedCopy(currentRevisionList),
savedBy: pad.getUserName(),
newLabel: newLabel
});
}
function error(e)
{
alert("Oops! There was an error saving that revision label. Please try again later.");
}
}
var currentRevisionList = [];
function setRevisionList(newRevisionList, noAnimation)
{
// deals with changed labels and new added revisions
for (var i = 0; i < currentRevisionList.length; i++)
{
var a = currentRevisionList[i];
var b = newRevisionList[i];
if (b.label != a.label)
{
setBoxLabel(getNthBox(i), b.label);
}
}
for (var j = currentRevisionList.length; j < newRevisionList.length; j++)
{
var newBox = makeRevisionBox(newRevisionList[j], j);
$("#savedrevs-scrollinner").append(newBox);
newBox.css('left', j * REVISION_BOX_WIDTH);
}
var newOnes = (newRevisionList.length > currentRevisionList.length);
currentRevisionList = newRevisionList;
if (newOnes)
{
setDesiredScroll(getMaxScroll());
if (noAnimation)
{
setScroll(desiredScroll);
}
if (!noAnimation)
{
var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label;
displaySavedTip(nameOfLast);
}
}
}
function refreshRevisionList()
{
for (var i = 0; i < currentRevisionList.length; i++)
{
var r = currentRevisionList[i];
var box = getNthBox(i);
setBoxTimestamp(box, r.timestamp);
}
}
var savedTipAnimator = padutils.makeShowHideAnimator(function(state)
{
if (state == -1)
{
$("#revision-notifier").css('opacity', 0).css('display', 'block');
}
else if (state == 0)
{
$("#revision-notifier").css('opacity', 1);
}
else if (state == 1)
{
$("#revision-notifier").css('opacity', 0).css('display', 'none');
}
else if (state < 0)
{
$("#revision-notifier").css('opacity', 1);
}
else if (state > 0)
{
$("#revision-notifier").css('opacity', 1 - state);
}
}, false, 25, 300);
function displaySavedTip(text)
{
$("#revision-notifier .name").html(padutils.escapeHtml(text));
savedTipAnimator.show();
padutils.cancelActions("hide-revision-notifier");
var hideLater = padutils.getCancellableAction("hide-revision-notifier", function()
{
savedTipAnimator.hide();
});
window.setTimeout(hideLater, 3000);
}
var REVISION_BOX_WIDTH = 120;
var curScroll = 0; // distance between left of revisions and right of view
var desiredScroll = 0;
function getScrollWidth()
{
return REVISION_BOX_WIDTH * currentRevisionList.length;
}
function getViewportWidth()
{
return $("#savedrevs-scrollouter").width();
}
function getMinScroll()
{
return Math.min(getViewportWidth(), getScrollWidth());
}
function getMaxScroll()
{
return getScrollWidth();
}
function setScroll(newScroll)
{
curScroll = newScroll;
$("#savedrevs-scrollinner").css('right', newScroll);
updateScrollArrows();
}
function setDesiredScroll(newDesiredScroll, dontUpdate)
{
desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), newDesiredScroll));
if (!dontUpdate)
{
updateScroll();
}
}
function updateScroll()
{
updateScrollArrows();
scrollAnimator.scheduleAnimation();
}
function updateScrollArrows()
{
$("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll());
$("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll());
}
var scrollAnimator = padutils.makeAnimationScheduler(function()
{
setDesiredScroll(desiredScroll, true); // re-clamp
if (Math.abs(desiredScroll - curScroll) < 1)
{
setScroll(desiredScroll);
return false;
}
else
{
setScroll(curScroll + (desiredScroll - curScroll) * 0.5);
return true;
}
}, 50, 2);
var isSaving = false;
function setIsSaving(v)
{
isSaving = v;
rerenderButton();
}
function haveReachedRevLimit()
{
var mv = pad.getPrivilege('maxRevisions');
return (!(mv < 0 || mv > currentRevisionList.length));
}
function rerenderButton()
{
if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit())
{
$("#savedrevs-savenow").css('opacity', 0.75);
}
else
{
$("#savedrevs-savenow").css('opacity', 1);
}
}
var scrollRepeatTimer = null;
var scrollStartTime = 0;
function setScrollRepeatTimer(dir)
{
clearScrollRepeatTimer();
scrollStartTime = +new Date;
scrollRepeatTimer = window.setTimeout(function f()
{
if (!scrollRepeatTimer)
{
return;
}
self.scroll(dir);
var scrollTime = (+new Date) - scrollStartTime;
var delay = (scrollTime > 2000 ? 50 : 300);
scrollRepeatTimer = window.setTimeout(f, delay);
}, 300);
$(document).bind('mouseup', clearScrollRepeatTimer);
}
function clearScrollRepeatTimer()
{
if (scrollRepeatTimer)
{
window.clearTimeout(scrollRepeatTimer);
scrollRepeatTimer = null;
}
$(document).unbind('mouseup', clearScrollRepeatTimer);
}
var pad = undefined;
var self = {
init: function(initialRevisions, _pad)
{
pad = _pad;
self.newRevisionList(initialRevisions, true);
$("#savedrevs-savenow").click(function()
{
self.saveNow();
});
$("#savedrevs-scrollleft").mousedown(function()
{
self.scroll('left');
setScrollRepeatTimer('left');
});
$("#savedrevs-scrollright").mousedown(function()
{
self.scroll('right');
setScrollRepeatTimer('right');
});
$("#savedrevs-close").click(function()
{
paddocbar.setShownPanel(null);
});
// update "saved n minutes ago" times
window.setInterval(function()
{
refreshRevisionList();
}, 60 * 1000);
},
restoreRevision: function(rnum)
{
var rev = currentRevisionList[rnum];
var warning = ("Restoring this revision will overwrite the current" + " text of the pad. " + "Are you sure you want to continue?");
var hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
var box = getNthBox(rnum);
if (confirm(warning))
{
box.find(".srtwirly").show();
$.ajax(
{
type: 'get',
url: '/ep/pad/getrevisionatext',
data: {
padId: pad.getPadId(),
revId: rev.id
},
success: success,
error: error
});
}
function success(resultJson)
{
untwirl();
var result = JSON.parse(resultJson);
padeditor.restoreRevisionText(result);
window.setTimeout(function()
{
hidePanel();
}, 0);
}
function error(e)
{
untwirl();
alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId());
}
function untwirl()
{
box.find(".srtwirly").hide();
}
},
showReachedLimit: function()
{
alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions.");
},
newRevisionList: function(lst, noAnimation)
{
// server gives us list with newest first;
// we want chronological order
var L = reversedCopy(lst);
setRevisionList(L, noAnimation);
rerenderButton();
},
saveNow: function()
{
if (isSaving)
{
return;
}
if (!pad.isFullyConnected())
{
return;
}
if (haveReachedRevLimit())
{
self.showReachedLimit();
return;
}
setIsSaving(true);
var savedBy = pad.getUserName() || "unnamed";
pad.callWhenNotCommitting(submitSave);
function submitSave()
{
$.ajax(
{
type: 'post',
url: '/ep/pad/saverevision',
data: {
padId: pad.getPadId(),
savedBy: savedBy,
savedById: pad.getUserId(),
revNum: pad.getCollabRevisionNumber()
},
success: success,
error: error
});
}
function success(text)
{
setIsSaving(false);
var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList);
pad.sendClientMessage(
{
type: 'newRevisionList',
revisionList: newRevisionList,
savedBy: savedBy
});
}
function error(e)
{
setIsSaving(false);
alert("Oops! The server failed to save the revision. Please try again later.");
}
},
handleResizePage: function()
{
updateScrollArrows();
},
handleIsFullyConnected: function(isConnected)
{
rerenderButton();
},
scroll: function(dir)
{
var minScroll = getMinScroll();
var maxScroll = getMaxScroll();
if (dir == 'left')
{
if (desiredScroll > minScroll)
{
var n = Math.floor((desiredScroll - 1 - minScroll) / REVISION_BOX_WIDTH);
setDesiredScroll(Math.max(0, n) * REVISION_BOX_WIDTH + minScroll);
}
}
else if (dir == 'right')
{
if (desiredScroll < maxScroll)
{
var n = Math.floor((maxScroll - desiredScroll - 1) / REVISION_BOX_WIDTH);
setDesiredScroll(maxScroll - Math.max(0, n) * REVISION_BOX_WIDTH);
}
}
}
};
return self;
}());
exports.padsavedrevs = padsavedrevs;
exports.init = function(_pad){
pad = _pad;
}

View File

@ -10,12 +10,18 @@ if (plugins.isClient) {
_ = require("underscore");
}
exports.bubbleExceptions = true
var hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; };
try {
if (exports.bubbleExceptions) {
return hook.hook_fn(hook_name, args, cb);
} catch (ex) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
} else {
try {
return hook.hook_fn(hook_name, args, cb);
} catch (ex) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
}
}
}
@ -36,6 +42,7 @@ exports.flatten = function (lst) {
}
exports.callAll = function (hook_name, args) {
if (!args) args = {};
if (plugins.hooks[hook_name] === undefined) return [];
return exports.flatten(_.map(plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args);
@ -43,26 +50,31 @@ exports.callAll = function (hook_name, args) {
}
exports.aCallAll = function (hook_name, args, cb) {
if (plugins.hooks[hook_name] === undefined) cb([]);
if (!args) args = {};
if (!cb) cb = function () {};
if (plugins.hooks[hook_name] === undefined) return cb(null, []);
async.map(
plugins.hooks[hook_name],
function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
function (err, res) {
cb(exports.flatten(res));
cb(null, exports.flatten(res));
}
);
}
exports.callFirst = function (hook_name, args) {
if (!args) args = {};
if (plugins.hooks[hook_name][0] === undefined) return [];
return exports.flatten(hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args));
}
exports.aCallFirst = function (hook_name, args, cb) {
if (plugins.hooks[hook_name][0] === undefined) cb([]);
hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(exports.flatten(res)); });
if (!args) args = {};
if (!cb) cb = function () {};
if (plugins.hooks[hook_name][0] === undefined) return cb(null, []);
hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(null, exports.flatten(res)); });
}
exports.callAllStr = function(hook_name, args, sep, pre, post) {

View File

@ -0,0 +1,76 @@
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm");
var registry = require("npm/lib/utils/npm-registry-client/index.js");
var withNpm = function (npmfn, cb) {
npm.load({}, function (er) {
if (er) return cb({progress:1, error:er});
npm.on("log", function (message) {
cb({progress: 0.5, message:message.msg + ": " + message.pref});
});
npmfn(function (er, data) {
if (er) return cb({progress:1, error:er.code + ": " + er.path});
if (!data) data = {};
data.progress = 1;
data.message = "Done.";
cb(data);
});
});
}
// All these functions call their callback multiple times with
// {progress:[0,1], message:STRING, error:object}. They will call it
// with progress = 1 at least once, and at all times will either
// message or error be present, not both. It can be called multiple
// times for all values of propgress except for 1.
exports.uninstall = function(plugin_name, cb) {
withNpm(
function (cb) {
npm.commands.uninstall([plugin_name], function (er) {
if (er) return cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
},
cb
);
};
exports.install = function(plugin_name, cb) {
withNpm(
function (cb) {
npm.commands.install([plugin_name], function (er) {
if (er) return cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
},
cb
);
};
exports.search = function(pattern, cb) {
withNpm(
function (cb) {
registry.get(
"/-/all", null, 600, false, true,
function (er, data) {
if (er) return cb(er);
var res = {};
for (key in data) {
if (key.indexOf(plugins.prefix) == 0 && key.indexOf(pattern) != -1)
res[key] = data[key];
}
cb(null, {results:res});
}
);
},
cb
);
};

View File

@ -0,0 +1,37 @@
/**
* This module allows passing require modules instances to
* embedded iframes in a page.
* For example, if a page has the "plugins" module initialized,
* it is important to use exactly the same "plugins" instance
* inside iframes as well. Otherwise, plugins cannot save any
* state.
*/
/**
* Instructs the require object that when a reqModuleName module
* needs to be loaded, that it iterates through the parents of the
* current window until it finds one who can execute "require"
* statements and asks it to perform require on reqModuleName.
*
* @params requireDefObj Require object which supports define
* statements. This object is accessible after loading require-kernel.
* @params reqModuleName Module name e.g. (ep_etherpad-lite/static/js/plugins)
*/
exports.getRequirementFromParent = function(requireDefObj, reqModuleName) {
requireDefObj.define(reqModuleName, function(require, exports, module) {
var t = parent;
var max = 0; // make sure I don't go up more than 10 times
while (typeof(t) != "undefined") {
max++;
if (max==10)
break;
if (typeof(t.require) != "undefined") {
module.exports = t.require(reqModuleName);
return;
}
t = t.parent;
}
});
}

View File

@ -4,7 +4,7 @@ var _;
if (!exports.isClient) {
var npm = require("npm/lib/npm.js");
var readInstalled = require("npm/lib/utils/read-installed.js");
var readInstalled = require("./read-installed.js");
var relativize = require("npm/lib/utils/relativize.js");
var readJson = require("npm/lib/utils/read-json.js");
var path = require("path");
@ -12,12 +12,12 @@ if (!exports.isClient) {
var fs = require("fs");
var tsort = require("./tsort");
var util = require("util");
var extend = require("node.extend");
_ = require("underscore");
}else{
var $, jQuery
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
_ = require("ep_etherpad-lite/static/js/underscore");
}
exports.prefix = 'ep_';
@ -123,14 +123,19 @@ exports.getPackages = function (cb) {
function flatten(deps) {
_.chain(deps).keys().each(function (name) {
if (name.indexOf(exports.prefix) == 0) {
packages[name] = deps[name];
packages[name] = extend({}, deps[name]);
// Delete anything that creates loops so that the plugin
// list can be sent as JSON to the web client
delete packages[name].dependencies;
delete packages[name].parent;
}
if (deps[name].dependencies !== undefined)
flatten(deps[name].dependencies);
delete deps[name].dependencies;
});
}
flatten([data]);
var tmp = {};
tmp[data.name] = data;
flatten(tmp);
cb(null, packages);
});
}

View File

@ -0,0 +1,324 @@
// A copy of npm/lib/utils/read-installed.js
// that is hacked to not cache everything :)
// Walk through the file-system "database" of installed
// packages, and create a data object related to the
// installed versions of each package.
/*
This will traverse through all node_modules folders,
resolving the dependencies object to the object corresponding to
the package that meets that dep, or just the version/range if
unmet.
Assuming that you had this folder structure:
/path/to
+-- package.json { name = "root" }
`-- node_modules
+-- foo {bar, baz, asdf}
| +-- node_modules
| +-- bar { baz }
| `-- baz
`-- asdf
where "foo" depends on bar, baz, and asdf, bar depends on baz,
and bar and baz are bundled with foo, whereas "asdf" is at
the higher level (sibling to foo), you'd get this object structure:
{ <package.json data>
, path: "/path/to"
, parent: null
, dependencies:
{ foo :
{ version: "1.2.3"
, path: "/path/to/node_modules/foo"
, parent: <Circular: root>
, dependencies:
{ bar:
{ parent: <Circular: foo>
, path: "/path/to/node_modules/foo/node_modules/bar"
, version: "2.3.4"
, dependencies: { baz: <Circular: foo.dependencies.baz> }
}
, baz: { ... }
, asdf: <Circular: asdf>
}
}
, asdf: { ... }
}
}
Unmet deps are left as strings.
Extraneous deps are marked with extraneous:true
deps that don't meet a requirement are marked with invalid:true
to READ(packagefolder, parentobj, name, reqver)
obj = read package.json
installed = ./node_modules/*
if parentobj is null, and no package.json
obj = {dependencies:{<installed>:"*"}}
deps = Object.keys(obj.dependencies)
obj.path = packagefolder
obj.parent = parentobj
if name, && obj.name !== name, obj.invalid = true
if reqver, && obj.version !satisfies reqver, obj.invalid = true
if !reqver && parentobj, obj.extraneous = true
for each folder in installed
obj.dependencies[folder] = READ(packagefolder+node_modules+folder,
obj, folder, obj.dependencies[folder])
# walk tree to find unmet deps
for each dep in obj.dependencies not in installed
r = obj.parent
while r
if r.dependencies[dep]
if r.dependencies[dep].verion !satisfies obj.dependencies[dep]
WARN
r.dependencies[dep].invalid = true
obj.dependencies[dep] = r.dependencies[dep]
r = null
else r = r.parent
return obj
TODO:
1. Find unmet deps in parent directories, searching as node does up
as far as the left-most node_modules folder.
2. Ignore anything in node_modules that isn't a package folder.
*/
var npm = require("npm/lib/npm.js")
, fs = require("graceful-fs")
, path = require("path")
, asyncMap = require("slide").asyncMap
, semver = require("semver")
, readJson = require("npm/lib/utils/read-json.js")
, log = require("npm/lib/utils/log.js")
module.exports = readInstalled
function readInstalled (folder, cb) {
/* This is where we clear the cache, these three lines are all the
* new code there is */
rpSeen = {};
riSeen = [];
var fuSeen = [];
var d = npm.config.get("depth")
readInstalled_(folder, null, null, null, 0, d, function (er, obj) {
if (er) return cb(er)
// now obj has all the installed things, where they're installed
// figure out the inheritance links, now that the object is built.
resolveInheritance(obj)
cb(null, obj)
})
}
var rpSeen = {}
function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
//console.error(folder, name)
var installed
, obj
, real
, link
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
// error indicates that nothing is installed here
if (er) i = []
installed = i.filter(function (f) { return f.charAt(0) !== "." })
next()
})
readJson(path.resolve(folder, "package.json"), function (er, data) {
obj = copy(data)
if (!parent) {
obj = obj || true
er = null
}
return next(er)
})
fs.lstat(folder, function (er, st) {
if (er) {
if (!parent) real = true
return next(er)
}
fs.realpath(folder, function (er, rp) {
//console.error("realpath(%j) = %j", folder, rp)
real = rp
if (st.isSymbolicLink()) link = rp
next(er)
})
})
var errState = null
, called = false
function next (er) {
if (errState) return
if (er) {
errState = er
return cb(null, [])
}
//console.error('next', installed, obj && typeof obj, name, real)
if (!installed || !obj || !real || called) return
called = true
if (rpSeen[real]) return cb(null, rpSeen[real])
if (obj === true) {
obj = {dependencies:{}, path:folder}
installed.forEach(function (i) { obj.dependencies[i] = "*" })
}
if (name && obj.name !== name) obj.invalid = true
obj.realName = name || obj.name
obj.dependencies = obj.dependencies || {}
// "foo":"http://blah" is always presumed valid
if (reqver
&& semver.validRange(reqver)
&& !semver.satisfies(obj.version, reqver)) {
obj.invalid = true
}
if (parent
&& !(name in parent.dependencies)
&& !(name in (parent.devDependencies || {}))) {
obj.extraneous = true
}
obj.path = obj.path || folder
obj.realPath = real
obj.link = link
if (parent && !obj.link) obj.parent = parent
rpSeen[real] = obj
obj.depth = depth
if (depth >= maxDepth) return cb(null, obj)
asyncMap(installed, function (pkg, cb) {
var rv = obj.dependencies[pkg]
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
readInstalled_( path.resolve(folder, "node_modules/"+pkg)
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
, cb )
}, function (er, installedData) {
if (er) return cb(er)
installedData.forEach(function (dep) {
obj.dependencies[dep.realName] = dep
})
// any strings here are unmet things. however, if it's
// optional, then that's fine, so just delete it.
if (obj.optionalDependencies) {
Object.keys(obj.optionalDependencies).forEach(function (dep) {
if (typeof obj.dependencies[dep] === "string") {
delete obj.dependencies[dep]
}
})
}
return cb(null, obj)
})
}
}
// starting from a root object, call findUnmet on each layer of children
var riSeen = []
function resolveInheritance (obj) {
if (typeof obj !== "object") return
if (riSeen.indexOf(obj) !== -1) return
riSeen.push(obj)
if (typeof obj.dependencies !== "object") {
obj.dependencies = {}
}
Object.keys(obj.dependencies).forEach(function (dep) {
findUnmet(obj.dependencies[dep])
})
Object.keys(obj.dependencies).forEach(function (dep) {
resolveInheritance(obj.dependencies[dep])
})
}
// find unmet deps by walking up the tree object.
// No I/O
var fuSeen = []
function findUnmet (obj) {
if (fuSeen.indexOf(obj) !== -1) return
fuSeen.push(obj)
//console.error("find unmet", obj.name, obj.parent && obj.parent.name)
var deps = obj.dependencies = obj.dependencies || {}
//console.error(deps)
Object.keys(deps)
.filter(function (d) { return typeof deps[d] === "string" })
.forEach(function (d) {
//console.error("find unmet", obj.name, d, deps[d])
var r = obj.parent
, found = null
while (r && !found && typeof deps[d] === "string") {
// if r is a valid choice, then use that.
found = r.dependencies[d]
if (!found && r.realName === d) found = r
if (!found) {
r = r.link ? null : r.parent
continue
}
if ( typeof deps[d] === "string"
&& !semver.satisfies(found.version, deps[d])) {
// the bad thing will happen
log.warn(obj.path + " requires "+d+"@'"+deps[d]
+"' but will load\n"
+found.path+",\nwhich is version "+found.version
,"unmet dependency")
found.invalid = true
}
deps[d] = found
}
})
log.verbose([obj._id], "returning")
return obj
}
function copy (obj) {
if (!obj || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(copy)
var o = {}
for (var i in obj) o[i] = copy(obj[i])
return o
}
if (module === require.main) {
var util = require("util")
console.error("testing")
var called = 0
readInstalled(process.cwd(), function (er, map) {
console.error(called ++)
if (er) return console.error(er.stack || er.message)
cleanup(map)
console.error(util.inspect(map, true, 10, true))
})
var seen = []
function cleanup (map) {
if (seen.indexOf(map) !== -1) return
seen.push(map)
for (var i in map) switch (i) {
case "_id":
case "path":
case "extraneous": case "invalid":
case "dependencies": case "name":
continue
default: delete map[i]
}
var dep = map.dependencies
// delete map.dependencies
if (dep) {
// map.dependencies = dep
for (var i in dep) if (typeof dep[i] === "object") {
cleanup(dep[i])
}
}
return map
}
}

View File

@ -1,419 +0,0 @@
/**
* StyleFix 1.0.2
* @author Lea Verou
* MIT license
*/
(function(){
if(!window.addEventListener) {
return;
}
var self = window.StyleFix = {
link: function(link) {
try {
// Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
if(link.rel !== 'stylesheet' || link.hasAttribute('data-noprefix')) {
return;
}
}
catch(e) {
return;
}
var url = link.href || link.getAttribute('data-href'),
base = url.replace(/[^\/]+$/, ''),
parent = link.parentNode,
xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
var css = xhr.responseText;
if(css && link.parentNode) {
css = self.fix(css, true, link);
// Convert relative URLs to absolute, if needed
if(base) {
css = css.replace(/url\(('?|"?)(.+?)\1\)/gi, function($0, quote, url) {
if(!/^([a-z]{3,10}:|\/|#)/i.test(url)) { // If url not absolute & not a hash
// May contain sequences like /../ and /./ but those DO work
return 'url("' + base + url + '")';
}
return $0;
});
// behavior URLs shoudnt be converted (Issue #19)
css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + base, 'gi'), '$1');
}
var style = document.createElement('style');
style.textContent = css;
style.media = link.media;
style.disabled = link.disabled;
style.setAttribute('data-href', link.getAttribute('href'));
parent.insertBefore(style, link);
parent.removeChild(link);
}
}
};
xhr.send(null);
link.setAttribute('data-inprogress', '');
},
styleElement: function(style) {
var disabled = style.disabled;
style.textContent = self.fix(style.textContent, true, style);
style.disabled = disabled;
},
styleAttribute: function(element) {
var css = element.getAttribute('style');
css = self.fix(css, false, element);
element.setAttribute('style', css);
},
process: function() {
// Linked stylesheets
$('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);
// Inline stylesheets
$('style').forEach(StyleFix.styleElement);
// Inline styles
$('[style]').forEach(StyleFix.styleAttribute);
},
register: function(fixer, index) {
(self.fixers = self.fixers || [])
.splice(index === undefined? self.fixers.length : index, 0, fixer);
},
fix: function(css, raw) {
for(var i=0; i<self.fixers.length; i++) {
css = self.fixers[i](css, raw) || css;
}
return css;
},
camelCase: function(str) {
return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-','');
},
deCamelCase: function(str) {
return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
}
};
/**************************************
* Process styles
**************************************/
(function(){
setTimeout(function(){
$('link[rel="stylesheet"]').forEach(StyleFix.link);
}, 10);
document.addEventListener('DOMContentLoaded', StyleFix.process, false);
})();
function $(expr, con) {
return [].slice.call((con || document).querySelectorAll(expr));
}
})();
/**
* PrefixFree 1.0.4
* @author Lea Verou
* MIT license
*/
(function(root, undefined){
if(!window.StyleFix || !window.getComputedStyle) {
return;
}
var self = window.PrefixFree = {
prefixCSS: function(css, raw) {
var prefix = self.prefix;
function fix(what, before, after, replacement) {
what = self[what];
if(what.length) {
var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi');
css = css.replace(regex, replacement);
}
}
fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(');
fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3');
fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:');
// Prefix properties *inside* values (issue #8)
if (self.properties.length) {
var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi');
fix('valueProperties', '\\b', ':(.+?);', function($0) {
return $0.replace(regex, prefix + "$1")
});
}
if(raw) {
fix('selectors', '', '\\b', self.prefixSelector);
fix('atrules', '@', '\\b', '@' + prefix + '$1');
}
// Fix double prefixing
css = css.replace(RegExp('-' + prefix, 'g'), '-');
return css;
},
// Warning: prefixXXX functions prefix no matter what, even if the XXX is supported prefix-less
prefixSelector: function(selector) {
return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix })
},
prefixProperty: function(property, camelCase) {
var prefixed = self.prefix + property;
return camelCase? StyleFix.camelCase(prefixed) : prefixed;
}
};
/**************************************
* Properties
**************************************/
(function() {
var prefixes = {},
properties = [],
shorthands = {},
style = getComputedStyle(document.documentElement, null),
dummy = document.createElement('div').style;
// Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
var iterate = function(property) {
if(property.charAt(0) === '-') {
properties.push(property);
var parts = property.split('-'),
prefix = parts[1];
// Count prefix uses
prefixes[prefix] = ++prefixes[prefix] || 1;
// This helps determining shorthands
while(parts.length > 3) {
parts.pop();
var shorthand = parts.join('-');
if(supported(shorthand) && properties.indexOf(shorthand) === -1) {
properties.push(shorthand);
}
}
}
},
supported = function(property) {
return StyleFix.camelCase(property) in dummy;
}
// Some browsers have numerical indices for the properties, some don't
if(style.length > 0) {
for(var i=0; i<style.length; i++) {
iterate(style[i])
}
}
else {
for(var property in style) {
iterate(StyleFix.deCamelCase(property));
}
}
// Find most frequently used prefix
var highest = {uses:0};
for(var prefix in prefixes) {
var uses = prefixes[prefix];
if(highest.uses < uses) {
highest = {prefix: prefix, uses: uses};
}
}
self.prefix = '-' + highest.prefix + '-';
self.Prefix = StyleFix.camelCase(self.prefix);
self.properties = [];
// Get properties ONLY supported with a prefix
for(var i=0; i<properties.length; i++) {
var property = properties[i];
if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
var unprefixed = property.slice(self.prefix.length);
if(!supported(unprefixed)) {
self.properties.push(unprefixed);
}
}
}
// IE fix
if(self.Prefix == 'Ms'
&& !('transform' in dummy)
&& !('MsTransform' in dummy)
&& ('msTransform' in dummy)) {
self.properties.push('transform', 'transform-origin');
}
self.properties.sort();
})();
/**************************************
* Values
**************************************/
(function() {
// Values that might need prefixing
var functions = {
'linear-gradient': {
property: 'backgroundImage',
params: 'red, teal'
},
'calc': {
property: 'width',
params: '1px + 5%'
},
'element': {
property: 'backgroundImage',
params: '#foo'
}
};
functions['repeating-linear-gradient'] =
functions['repeating-radial-gradient'] =
functions['radial-gradient'] =
functions['linear-gradient'];
var keywords = {
'initial': 'color',
'zoom-in': 'cursor',
'zoom-out': 'cursor',
'box': 'display',
'flexbox': 'display',
'inline-flexbox': 'display'
};
self.functions = [];
self.keywords = [];
var style = document.createElement('div').style;
function supported(value, property) {
style[property] = '';
style[property] = value;
return !!style[property];
}
for (var func in functions) {
var test = functions[func],
property = test.property,
value = func + '(' + test.params + ')';
if (!supported(value, property)
&& supported(self.prefix + value, property)) {
// It's supported, but with a prefix
self.functions.push(func);
}
}
for (var keyword in keywords) {
var property = keywords[keyword];
if (!supported(keyword, property)
&& supported(self.prefix + keyword, property)) {
// It's supported, but with a prefix
self.keywords.push(keyword);
}
}
})();
/**************************************
* Selectors and @-rules
**************************************/
(function() {
var
selectors = {
':read-only': null,
':read-write': null,
':any-link': null,
'::selection': null
},
atrules = {
'keyframes': 'name',
'viewport': null,
'document': 'regexp(".")'
};
self.selectors = [];
self.atrules = [];
var style = root.appendChild(document.createElement('style'));
function supported(selector) {
style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
return !!style.sheet.cssRules.length;
}
for(var selector in selectors) {
var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : '');
if(!supported(test) && supported(self.prefixSelector(test))) {
self.selectors.push(selector);
}
}
for(var atrule in atrules) {
var test = atrule + ' ' + (atrules[atrule] || '');
if(!supported('@' + test) && supported('@' + self.prefix + test)) {
self.atrules.push(atrule);
}
}
root.removeChild(style);
})();
// Properties that accept properties as their value
self.valueProperties = [
'transition',
'transition-property'
]
// Add class for current prefix
root.className += ' ' + self.prefix;
StyleFix.register(self.prefixCSS);
})(document.documentElement);

View File

@ -1,284 +0,0 @@
<!doctype html>
<html>
<title>Etherpad Lite</title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<link href="../static/css/pad.css" rel="stylesheet">
<link href="../static/custom/pad.css" rel="stylesheet">
<style title="dynamicsyntax"></style>
<!-- head and body had been removed intentionally -->
<div id="editbar">
<ul id="menu_left">
<li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
</li>
<li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
</li>
<li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
</li>
<li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
</li>
<li class="separator"></li>
<li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
</li>
<li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
</li>
<li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
<a class="buttonicon buttonicon-indent" title="Indent"></a>
</li>
<li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
<a class="buttonicon buttonicon-outdent" title="Unindent"></a>
</li>
<li class="separator"></li>
<li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
</li>
<li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
<a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
</li>
<li class="separator"></li>
<li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
<a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
</li>
</ul>
<ul id="menu_right">
<li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
<a class="buttonicon buttonicon-settings" id="settingslink" title="Settings of this pad"></a>
</li>
<li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
<a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a>
</li>
<li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;" >
<a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a>
</li>
<li class="separator"></li>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
<a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
</li>
<li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
<span class="buttonicon buttonicon-showusers" id="usericonback"></span>
<span id="online_count">1</span>
</li>
</ul>
</div>
<div id="users">
<div id="connectionstatus"></div>
<div id="myuser">
<div id="mycolorpicker">
<div id="colorpicker"></div>
<button id="mycolorpickersave">Save</button>
<button id="mycolorpickercancel">Cancel</button>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
</div>
<div id="myswatchbox"><div id="myswatch"></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div>
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
</div>
<div id="otherusers">
<div id="guestprompts"></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr><td></td></tr>
</table>
<div id="nootherusers"></div>
</div>
<div id="userlistbuttonarea"></div>
</div>
<div id="editorcontainerbox">
<div id="editorcontainer"></div>
<div id="editorloadingbox">Loading...</div>
</div>
<div id="settingsmenu" class="popup">
<h1>Pad settings</h1>
<div class="column">
<h2>My view</h2>
<p>
<input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
<label for="options-stickychat">Chat always on screen</label>
</p>
<p>
<input type="checkbox" id="options-colorscheck">
<label for="options-colorscheck">Authorship colors</label>
</p>
<p>
<input type="checkbox" id="options-linenoscheck" checked>
<label for="options-linenoscheck">Line numbers</label>
</p>
<p>
Font type:
<select id="viewfontmenu">
<option value="normal">Normal</option>
<option value="monospace">Monospaced</option>
</select>
</p>
</div>
<div class="column">
<h2>Global view</h2>
<p>Currently nothing.</p>
<p class="note">These options affect everyone viewing this pad.</p>
</div>
</div>
<div id="importexport" class="popup">
<div class="column">
<h2>Import from text file, HTML, PDF, Word, ODT or RTF</h2><br>
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="15" id="importfileinput">
<div class="importmessage" id="importmessagefail"></div>
</div>
<div id="import"></div>
<div class="importmessage" id="importmessagesuccess">Successful!</div>
<div class="importformdiv" id="importformsubmitdiv">
<input type="hidden" name="padId" value="blpmaXT35R">
<span class="nowrap">
<input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
<img alt="" id="importstatusball" src="../static/img/loading.gif" align="top">
<img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
</span>
</div>
</form>
</div>
<div class="column">
<h2>Export current pad as</h2>
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
<a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
</div>
</div>
<div id="embed" class="popup">
<div id="embedreadonly" class="right">
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
<label for="readonlyinput">Read only</label>
</div>
<h1>Share this pad</h1>
<div id="linkcode">
<h2>Link</h2>
<input id="linkinput" type="text" value="">
</div>
<br>
<div id="embedcode">
<h2>Embed URL</h2>
<input id="embedinput" type="text" value="">
</div>
<br>
<div id="qrcode">
<h2>QR code</h2>
<div id="qr_center"><img id="embedreadonlyqr"></div>
</div>
</div>
<div id="chatthrob"></div>
<div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
<span id="chatlabel">Chat</span>
<span class="buttonicon buttonicon-chat"></span>
<span id="chatcounter">0</span>
</div>
<div id="chatbox">
<div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="chattext" class="authorColors"></div>
<div id="chatinputbox">
<form>
<input id="chatinput" type="text" maxlength="140">
</form>
</div>
</div>
<div id="focusprotector">&nbsp;</div>
<div id="modaloverlay">
<div id="modaloverlay-inner"></div>
</div>
<div id="mainmodals">
<div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div>
<div id="disconnected_initsocketfail">
<p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
</div>
<div id="disconnected_userdup">
<p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
</div>
<div id="disconnected_unknown">
<p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div>
<div id="disconnected_slowcommit">
<p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div>
<div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div>
<div id="disconnected_deleted">
<p>This pad was deleted.</p>
</div>
<div id="reconnect_advise">
<p>If this continues to happen, please let us know</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button>
</div>
</div>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
</div>
</div>
<script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../static/js/jquery.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
<script type="text/javascript">
var clientVars = {};
(function () {
require.setRootURI("../javascripts/src");
require.setLibraryURI("../javascripts/lib");
require.setGlobalKeyPath("require");
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
plugins.update(function () {
require('ep_etherpad-lite/static/js/pad').init();
});
/* TODO: These globals shouldn't exist. */
pad = require('ep_etherpad-lite/static/js/pad').pad;
chat = require('ep_etherpad-lite/static/js/chat').chat;
padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;
padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp;
}());
</script>
</html>

View File

@ -0,0 +1,217 @@
<html>
<head>
<title>Plugin manager</title>
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid black;
padding-left: 10px;
padding-right: 10px;
padding-top: 2px;
padding-bottom: 2px;
}
.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 #999999;
background: #eeeeee;
}
.dialog .title {
margin: 0;
padding: 2px;
border-bottom: 3px solid #999999;
font-size: 24px;
line-height: 24px;
height: 24px;
overflow: hidden;
}
.dialog .title .close {
float: right;
}
.dialog .history {
background: #222222;
color: #eeeeee;
position: absolute;
top: 41px;
bottom: 10px;
left: 10px;
right: 10px;
padding: 2px;
overflow: auto;
}
</style>
<script src="../../static/js/jquery.js"></script>
<script src="../../socket.io/socket.io.js"></script>
<script>
$(document).ready(function () {
var socket = io.connect().of("/pluginfw/installer");
var doUpdate = false;
function updateHandlers() {
$("#progress.dialog .close").unbind('click').click(function () {
$("#progress.dialog").hide();
});
$("#do-search").unbind('click').click(function () {
if ($("#search-query")[0].value != "")
socket.emit("search", $("#search-query")[0].value);
});
$(".do-install").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("install", row.find(".name").html());
});
$(".do-uninstall").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("uninstall", row.find(".name").html());
});
}
updateHandlers();
socket.on('progress', function (data) {
$("#progress.dialog .close").hide();
$("#progress.dialog").show();
var message = "Unknown status";
if (data.message) {
message = "<span class='status'>" + data.message.toString() + "</span>";
}
if (data.error) {
message = "<span class='error'>" + data.error.toString() + "<span>";
}
$("#progress.dialog .message").html(message);
$("#progress.dialog .history").append("<div>" + message + "</div>");
if (data.progress >= 1) {
if (data.error) {
$("#progress.dialog .close").show();
} else {
if (doUpdate) {
doUpdate = false;
socket.emit("load");
}
$("#progress.dialog").hide();
}
}
});
socket.on('search-result', function (data) {
$("#search-results *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#search-result-template").clone();
for (attr in plugin) {
row.find("." + attr).html(plugin[attr]);
}
$("#search-results").append(row);
}
updateHandlers();
});
socket.on('installed-results', function (data) {
$("#installed-plugins *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone();
for (attr in plugin.package) {
row.find("." + attr).html(plugin.package[attr]);
}
$("#installed-plugins").append(row);
}
updateHandlers();
});
socket.emit("load");
});
</script>
</head>
<body>
<% if (errors.length) { %>
<div class="errors">
<% errors.forEach(function (item) { %>
<div class="error"><%= item.toString() %></div>
<% }) %>
</div>
<% } %>
<h1>Installed plugins</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<td></td>
</tr>
</thead>
<tbody class="template">
<tr id="installed-plugin-template">
<td class="name"></td>
<td class="description"></td>
<td class="actions">
<input type="button" value="I" class="do-uninstall">
</td>
</tr>
</tbody>
<tbody id="installed-plugins">
</tbody>
</table>
<h1>Search for plugins to install</h1>
<form>
<input type="text" name="search" value="" id="search-query">
<input type="button" value="S" id="do-search">
</form>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<td></td>
</tr>
</thead>
<tbody class="template">
<tr id="search-result-template">
<td class="name"></td>
<td class="description"></td>
<td class="actions">
<input type="button" value="I" class="do-install">
</td>
</tr>
</tbody>
<tbody id="search-results">
</tbody>
</table>
<div id="progress" class="dialog">
<h1 class="title">
Please wait: <span class="message"></span>
<input type="button" class="close" value="Close">
</h1>
<div class="history"></div>
</div>
</body>
</html>

307
src/templates/pad.html Normal file
View File

@ -0,0 +1,307 @@
<%
var settings = require("ep_etherpad-lite/node/utils/Settings");
%>
<!doctype html>
<html>
<title>Etherpad Lite</title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<% e.begin_block("styles"); %>
<link href="../static/css/pad.css" rel="stylesheet">
<link href="../static/custom/pad.css" rel="stylesheet">
<style title="dynamicsyntax"></style>
<% e.end_block(); %>
<!-- head and body had been removed intentionally -->
<div id="editbar" class="toolbar">
<ul class="menu_left">
<% e.begin_block("editbarMenuLeft"); %>
<li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
</li>
<li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
</li>
<li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
</li>
<li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
</li>
<li class="separator"></li>
<li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
</li>
<li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
</li>
<li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
<a class="buttonicon buttonicon-indent" title="Indent"></a>
</li>
<li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
<a class="buttonicon buttonicon-outdent" title="Unindent"></a>
</li>
<li class="separator"></li>
<li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
</li>
<li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
<a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
</li>
<li class="separator"></li>
<li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
<a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
</li>
<% e.end_block(); %>
</ul>
<ul class="menu_right">
<% e.begin_block("editbarMenuRight"); %>
<li onClick="window.pad&amp;&amp;pad.editbarClick('savedRevision');return false;">
<a id="settingslink" title="Mark this revision as a saved revision">
<div class="buttonicon buttonicon-savedRevision"></div>
</a>
</li>
<li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
<a class="buttonicon buttonicon-settings" id="settingslink" title="Settings of this pad"></a>
</li>
<li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
<a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a>
</li>
<li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;" >
<a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a>
</li>
<li class="separator"></li>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
<a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
</li>
<li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
<span class="buttonicon buttonicon-showusers" id="usericonback"></span>
<span id="online_count">1</span>
</li>
<% e.end_block(); %>
</ul>
</div>
<div id="users">
<div id="connectionstatus"></div>
<div id="myuser">
<div id="mycolorpicker">
<div id="colorpicker"></div>
<button id="mycolorpickersave">Save</button>
<button id="mycolorpickercancel">Cancel</button>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
</div>
<div id="myswatchbox"><div id="myswatch"></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div>
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
</div>
<div id="otherusers">
<div id="guestprompts"></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr><td></td></tr>
</table>
<div id="nootherusers"></div>
</div>
<div id="userlistbuttonarea"></div>
</div>
<div id="editorcontainerbox">
<div id="editorcontainer"></div>
<div id="editorloadingbox">Loading...</div>
</div>
<div id="settingsmenu" class="popup">
<h1>Pad settings</h1>
<div class="column">
<h2>My view</h2>
<p>
<input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
<label for="options-stickychat">Chat always on screen</label>
</p>
<p>
<input type="checkbox" id="options-colorscheck">
<label for="options-colorscheck">Authorship colors</label>
</p>
<p>
<input type="checkbox" id="options-linenoscheck" checked>
<label for="options-linenoscheck">Line numbers</label>
</p>
<p>
Font type:
<select id="viewfontmenu">
<option value="normal">Normal</option>
<option value="monospace">Monospaced</option>
</select>
</p>
</div>
<div class="column">
<h2>Global view</h2>
<p>Currently nothing.</p>
<p class="note">These options affect everyone viewing this pad.</p>
</div>
</div>
<div id="importexport" class="popup">
<div class="column">
<h2>Import from text file, HTML, PDF, Word, ODT or RTF</h2><br>
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="15" id="importfileinput">
<div class="importmessage" id="importmessagefail"></div>
</div>
<div id="import"></div>
<div class="importmessage" id="importmessagesuccess">Successful!</div>
<div class="importformdiv" id="importformsubmitdiv">
<input type="hidden" name="padId" value="blpmaXT35R">
<span class="nowrap">
<input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
<img alt="" id="importstatusball" src="../static/img/loading.gif" align="top">
<img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
</span>
</div>
</form>
</div>
<div class="column">
<h2>Export current pad as</h2>
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
<a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
</div>
</div>
<div id="embed" class="popup">
<div id="embedreadonly" class="right">
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
<label for="readonlyinput">Read only</label>
</div>
<h1>Share this pad</h1>
<div id="linkcode">
<h2>Link</h2>
<input id="linkinput" type="text" value="">
</div>
<br>
<div id="embedcode">
<h2>Embed URL</h2>
<input id="embedinput" type="text" value="">
</div>
<br>
<div id="qrcode">
<h2>QR code</h2>
<div id="qr_center"><img id="embedreadonlyqr"></div>
</div>
</div>
<div id="chatthrob"></div>
<div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
<span id="chatlabel">Chat</span>
<span class="buttonicon buttonicon-chat"></span>
<span id="chatcounter">0</span>
</div>
<div id="chatbox">
<div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="chattext" class="authorColors"></div>
<div id="chatinputbox">
<form>
<input id="chatinput" type="text" maxlength="140">
</form>
</div>
</div>
<div id="focusprotector">&nbsp;</div>
<div id="modaloverlay">
<div id="modaloverlay-inner"></div>
</div>
<div id="mainmodals">
<% e.begin_block("modals"); %>
<div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div>
<div id="disconnected_initsocketfail">
<p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
</div>
<div id="disconnected_userdup">
<p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
</div>
<div id="disconnected_unknown">
<p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div>
<div id="disconnected_slowcommit">
<p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div>
<div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div>
<div id="disconnected_deleted">
<p>This pad was deleted.</p>
</div>
<div id="reconnect_advise">
<p>If this continues to happen, please let us know</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button>
</div>
</div>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
</div>
<% e.end_block(); %>
</div>
<% e.begin_block("scripts"); %>
<script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../static/js/jquery.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script>
<% if (settings.minify) { %>
<script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script>
<% } %>
<script type="text/javascript">
var clientVars = {};
(function () {
<% if (settings.minify) { %>
require.setRootURI("../javascripts/src");
require.setLibraryURI("../javascripts/lib");
require.setGlobalKeyPath("require");
<% } else { %>
require.setRootURI("../static/js");
require.setLibraryURI("../static/plugins");
<% } %>
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
plugins.update(function () {
require('ep_etherpad-lite/static/js/pad').init();
});
/* TODO: These globals shouldn't exist. */
pad = require('ep_etherpad-lite/static/js/pad').pad;
chat = require('ep_etherpad-lite/static/js/chat').chat;
padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar;
padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp;
}());
</script>
<% e.end_block(); %>
</html>

View File

@ -119,8 +119,8 @@
<div id="padmain">
<div id="padeditor">
<div id="editbar" class="editbar disabledtoolbar">
<div id="editbarinner" class="editbarinner">
<div id="editbar" class="toolbar disabledtoolbar">
<div id="editbarinner" class="toolbarinner">
<div id="editbarleft" class="editbarleft">
<!-- -->
</div>

View File

@ -1,421 +0,0 @@
/**
* StyleFix 1.0.1
* @author Lea Verou
* MIT license
*/
(function(){
if(!window.addEventListener) {
return;
}
var self = window.StyleFix = {
link: function(link) {
try {
// Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
if(link.rel !== 'stylesheet' || !link.sheet.cssRules || link.hasAttribute('data-noprefix')) {
return;
}
}
catch(e) {
return;
}
if(link.href == "data:text/css,"){
return false;
}
var url = link.href || link.getAttribute('data-href'),
base = url.replace(/[^\/]+$/, ''),
parent = link.parentNode,
xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
var css = xhr.responseText;
if(css && link.parentNode) {
css = self.fix(css, true, link);
// Convert relative URLs to absolute, if needed
if(base) {
css = css.replace(/url\((?:'|")?(.+?)(?:'|")?\)/gi, function($0, url) {
if(!/^([a-z]{3,10}:|\/|#)/i.test(url)) { // If url not absolute & not a hash
// May contain sequences like /../ and /./ but those DO work
return 'url("' + base + url + '")';
}
return $0;
});
// behavior URLs shoudnt be converted (Issue #19)
css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + base, 'gi'), '$1');
}
var style = document.createElement('style');
style.textContent = css;
style.media = link.media;
style.disabled = link.disabled;
style.setAttribute('data-href', link.getAttribute('href'));
parent.insertBefore(style, link);
parent.removeChild(link);
}
}
};
xhr.send(null);
link.setAttribute('data-inprogress', '');
},
styleElement: function(style) {
var disabled = style.disabled;
style.textContent = self.fix(style.textContent, true, style);
style.disabled = disabled;
},
styleAttribute: function(element) {
var css = element.getAttribute('style');
css = self.fix(css, false, element);
element.setAttribute('style', css);
},
process: function() {
// Linked stylesheets
$('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);
// Inline stylesheets
$('style').forEach(StyleFix.styleElement);
// Inline styles
$('[style]').forEach(StyleFix.styleAttribute);
},
register: function(fixer, index) {
(self.fixers = self.fixers || [])
.splice(index === undefined? self.fixers.length : index, 0, fixer);
},
fix: function(css, raw) {
for(var i=0; i<self.fixers.length; i++) {
css = self.fixers[i](css, raw) || css;
}
return css;
},
camelCase: function(str) {
return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-','');
},
deCamelCase: function(str) {
return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
}
};
/**************************************
* Process styles
**************************************/
(function(){
setTimeout(function(){
$('link[rel="stylesheet"]').forEach(StyleFix.link);
}, 10);
document.addEventListener('DOMContentLoaded', StyleFix.process, false);
})();
function $(expr, con) {
return [].slice.call((con || document).querySelectorAll(expr));
}
})();
/**
* PrefixFree 1.0.4
* @author Lea Verou
* MIT license
*/
(function(root, undefined){
if(!window.StyleFix || !window.getComputedStyle) {
return;
}
var self = window.PrefixFree = {
prefixCSS: function(css, raw) {
var prefix = self.prefix;
function fix(what, before, after, replacement) {
what = self[what];
if(what.length) {
var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi');
css = css.replace(regex, replacement);
}
}
fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(');
fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3');
fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:');
// Prefix properties *inside* values (issue #8)
if (self.properties.length) {
var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi');
fix('valueProperties', '\\b', ':(.+?);', function($0) {
return $0.replace(regex, prefix + "$1")
});
}
if(raw) {
fix('selectors', '', '\\b', self.prefixSelector);
fix('atrules', '@', '\\b', '@' + prefix + '$1');
}
// Fix double prefixing
css = css.replace(RegExp('-' + prefix, 'g'), '-');
return css;
},
// Warning: prefixXXX functions prefix no matter what, even if the XXX is supported prefix-less
prefixSelector: function(selector) {
return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix })
},
prefixProperty: function(property, camelCase) {
var prefixed = self.prefix + property;
return camelCase? StyleFix.camelCase(prefixed) : prefixed;
}
};
/**************************************
* Properties
**************************************/
(function() {
var prefixes = {},
properties = [],
shorthands = {},
style = getComputedStyle(document.documentElement, null),
dummy = document.createElement('div').style;
// Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
var iterate = function(property) {
if(property.charAt(0) === '-') {
properties.push(property);
var parts = property.split('-'),
prefix = parts[1];
// Count prefix uses
prefixes[prefix] = ++prefixes[prefix] || 1;
// This helps determining shorthands
while(parts.length > 3) {
parts.pop();
var shorthand = parts.join('-');
if(supported(shorthand) && properties.indexOf(shorthand) === -1) {
properties.push(shorthand);
}
}
}
},
supported = function(property) {
return StyleFix.camelCase(property) in dummy;
}
// Some browsers have numerical indices for the properties, some don't
if(style.length > 0) {
for(var i=0; i<style.length; i++) {
iterate(style[i])
}
}
else {
for(var property in style) {
iterate(StyleFix.deCamelCase(property));
}
}
// Find most frequently used prefix
var highest = {uses:0};
for(var prefix in prefixes) {
var uses = prefixes[prefix];
if(highest.uses < uses) {
highest = {prefix: prefix, uses: uses};
}
}
self.prefix = '-' + highest.prefix + '-';
self.Prefix = StyleFix.camelCase(self.prefix);
self.properties = [];
// Get properties ONLY supported with a prefix
for(var i=0; i<properties.length; i++) {
var property = properties[i];
if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
var unprefixed = property.slice(self.prefix.length);
if(!supported(unprefixed)) {
self.properties.push(unprefixed);
}
}
}
// IE fix
if(self.Prefix == 'Ms'
&& !('transform' in dummy)
&& !('MsTransform' in dummy)
&& ('msTransform' in dummy)) {
self.properties.push('transform', 'transform-origin');
}
self.properties.sort();
})();
/**************************************
* Values
**************************************/
(function() {
// Values that might need prefixing
var functions = {
'linear-gradient': {
property: 'backgroundImage',
params: 'red, teal'
},
'calc': {
property: 'width',
params: '1px + 5%'
},
'element': {
property: 'backgroundImage',
params: '#foo'
}
};
functions['repeating-linear-gradient'] =
functions['repeating-radial-gradient'] =
functions['radial-gradient'] =
functions['linear-gradient'];
var keywords = {
'initial': 'color',
'zoom-in': 'cursor',
'zoom-out': 'cursor',
'box': 'display',
'flexbox': 'display',
'inline-flexbox': 'display'
};
self.functions = [];
self.keywords = [];
var style = document.createElement('div').style;
function supported(value, property) {
style[property] = '';
style[property] = value;
return !!style[property];
}
for (var func in functions) {
var test = functions[func],
property = test.property,
value = func + '(' + test.params + ')';
if (!supported(value, property)
&& supported(self.prefix + value, property)) {
// It's supported, but with a prefix
self.functions.push(func);
}
}
for (var keyword in keywords) {
var property = keywords[keyword];
if (!supported(keyword, property)
&& supported(self.prefix + keyword, property)) {
// It's supported, but with a prefix
self.keywords.push(keyword);
}
}
})();
/**************************************
* Selectors and @-rules
**************************************/
(function() {
var
selectors = {
':read-only': null,
':read-write': null,
':any-link': null,
'::selection': null
},
atrules = {
'keyframes': 'name',
'viewport': null,
'document': 'regexp(".")'
};
self.selectors = [];
self.atrules = [];
var style = root.appendChild(document.createElement('style'));
function supported(selector) {
style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
return !!style.sheet.cssRules.length;
}
for(var selector in selectors) {
var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : '');
if(!supported(test) && supported(self.prefixSelector(test))) {
self.selectors.push(selector);
}
}
for(var atrule in atrules) {
var test = atrule + ' ' + (atrules[atrule] || '');
if(!supported('@' + test) && supported('@' + self.prefix + test)) {
self.atrules.push(atrule);
}
}
root.removeChild(style);
})();
// Properties that accept properties as their value
self.valueProperties = [
'transition',
'transition-property'
]
// Add class for current prefix
root.className += ' ' + self.prefix;
StyleFix.register(self.prefixCSS);
})(document.documentElement);