mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Merge pull request #557 from redhog/master
Templating system built on top of EJS and plugin installer
This commit is contained in:
commit
9ecd864ac6
46 changed files with 1526 additions and 1774 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,5 @@
|
|||
node_modules
|
||||
settings.json
|
||||
static/js/jquery.js
|
||||
static/js/prefixfree.js
|
||||
APIKEY.txt
|
||||
bin/abiword.exe
|
||||
bin/node.exe
|
||||
|
@ -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
16
README.plugins
Normal file
|
@ -0,0 +1,16 @@
|
|||
So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json
|
||||
require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded
|
||||
A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
|
||||
require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name
|
||||
That is the basis.
|
||||
Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored).
|
||||
This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order
|
||||
Ordering between plugins is undefined, only parts are ordered.
|
||||
|
||||
A plugin usually has one part, but it van have multiple.
|
||||
This is so that it can insert some hook registration before that of another plugin, and another one after.
|
||||
This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps
|
||||
So, that's basically it... apart from client-side hooks
|
||||
which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client...
|
||||
One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json...
|
||||
was that clear?
|
|
@ -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"
|
||||
|
|
|
@ -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&&pad.editbarClick(\'clearauthorship\');return false;">\
|
||||
<a class="buttonicon buttonicon-test" title="Test test test"></a>\
|
||||
</li>\
|
||||
';
|
||||
return cb();
|
||||
}
|
||||
|
|
|
@ -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*
|
||||
|
|
|
@ -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 */
|
||||
"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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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" } }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
9
src/node/eejs/examples/bar.ejs
Normal file
9
src/node/eejs/examples/bar.ejs
Normal file
|
@ -0,0 +1,9 @@
|
|||
a
|
||||
<% e.begin_block("bar"); %>
|
||||
A
|
||||
<% e.begin_block("foo"); %>
|
||||
XX
|
||||
<% e.end_block(); %>
|
||||
B
|
||||
<% e.end_block(); %>
|
||||
b
|
7
src/node/eejs/examples/foo.ejs
Normal file
7
src/node/eejs/examples/foo.ejs
Normal file
|
@ -0,0 +1,7 @@
|
|||
<% e.inherit("./bar.ejs"); %>
|
||||
|
||||
<% e.begin_define_block("foo"); %>
|
||||
YY
|
||||
<% e.super(); %>
|
||||
ZZ
|
||||
<% e.end_define_block(); %>
|
115
src/node/eejs/index.js
Normal file
115
src/node/eejs/index.js
Normal 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));
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
51
src/node/hooks/express/adminplugins.js
Normal file
51
src/node/hooks/express/adminplugins.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 });
|
||||
});
|
||||
|
||||
}
|
|
@ -6,11 +6,19 @@ var settings = require('../../utils/Settings');
|
|||
|
||||
//checks for basic http auth
|
||||
exports.basicAuth = function (req, res, next) {
|
||||
var pass = settings.httpAuth;
|
||||
if (req.path.indexOf('/admin') == 0) {
|
||||
var pass = settings.adminHttpAuth;
|
||||
}
|
||||
// Just pass if not activated in Activate http basic auth if it has been defined in settings.json
|
||||
if (!pass) {
|
||||
return 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;
|
||||
if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() == pass) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +33,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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
16
src/node/utils/randomstring.js
Normal file
16
src/node/utils/randomstring.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
|
||||
*/
|
||||
var randomString = function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
};
|
||||
|
||||
module.exports = randomString;
|
|
@ -22,7 +22,6 @@
|
|||
, "chat.js"
|
||||
, "excanvas.js"
|
||||
, "farbtastic.js"
|
||||
, "prefixfree.js"
|
||||
]
|
||||
, "timeslider.js": [
|
||||
"jquery.js"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
BIN
src/static/img/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -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");\
|
||||
|
|
|
@ -844,7 +844,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);
|
||||
|
@ -854,7 +854,7 @@ function Ace2Inner(){
|
|||
|
||||
function replaceRange(start, end, text)
|
||||
{
|
||||
inCallStack('replaceRange', function()
|
||||
inCallStackIfNecessary('replaceRange', function()
|
||||
{
|
||||
fastIncorp(9);
|
||||
performDocumentReplaceRange(start, end, text);
|
||||
|
@ -1155,7 +1155,7 @@ function Ace2Inner(){
|
|||
return;
|
||||
}
|
||||
|
||||
inCallStack("idleWorkTimer", function()
|
||||
inCallStackIfNecessary("idleWorkTimer", function()
|
||||
{
|
||||
|
||||
var isTimeUp = newTimeLimit(250);
|
||||
|
@ -2043,6 +2043,7 @@ function Ace2Inner(){
|
|||
return [lineNum, col];
|
||||
}
|
||||
}
|
||||
editorInfo.ace_getLineAndCharForPoint = getLineAndCharForPoint;
|
||||
|
||||
function createDomLineEntry(lineString)
|
||||
{
|
||||
|
@ -2328,6 +2329,7 @@ function Ace2Inner(){
|
|||
var cs = builder.toString();
|
||||
performDocumentApplyChangeset(cs);
|
||||
}
|
||||
editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange;
|
||||
|
||||
function buildKeepToStartOfRange(builder, start)
|
||||
{
|
||||
|
@ -2853,6 +2855,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.
|
||||
|
@ -3280,7 +3283,7 @@ function Ace2Inner(){
|
|||
|
||||
function handleClick(evt)
|
||||
{
|
||||
inCallStack("handleClick", function()
|
||||
inCallStackIfNecessary("handleClick", function()
|
||||
{
|
||||
idleWorkTimer.atMost(200);
|
||||
});
|
||||
|
@ -3602,7 +3605,7 @@ function Ace2Inner(){
|
|||
|
||||
var stopped = false;
|
||||
|
||||
inCallStack("handleKeyEvent", function()
|
||||
inCallStackIfNecessary("handleKeyEvent", function()
|
||||
{
|
||||
|
||||
if (type == "keypress" || (isTypeForSpecialKey && keyCode == 13 /*return*/ ))
|
||||
|
@ -4689,7 +4692,7 @@ function Ace2Inner(){
|
|||
}
|
||||
|
||||
// click below the body
|
||||
inCallStack("handleOuterClick", function()
|
||||
inCallStackIfNecessary("handleOuterClick", function()
|
||||
{
|
||||
// put caret at bottom of doc
|
||||
fastIncorp(11);
|
||||
|
@ -4726,6 +4729,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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}());
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
76
src/static/js/pluginfw/installer.js
Normal file
76
src/static/js/pluginfw/installer.js
Normal 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
|
||||
);
|
||||
};
|
37
src/static/js/pluginfw/parent_require.js
Normal file
37
src/static/js/pluginfw/parent_require.js
Normal 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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
324
src/static/js/pluginfw/read-installed.js
Normal file
324
src/static/js/pluginfw/read-installed.js
Normal 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
|
||||
}
|
||||
}
|
|
@ -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 shoudn’t 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);
|
|
@ -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&&pad.editbarClick('bold');return false">
|
||||
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
|
||||
</li>
|
||||
<li id="italic" onClick="window.pad&&pad.editbarClick('italic'); return false;">
|
||||
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
|
||||
</li>
|
||||
<li id="underline" onClick="window.pad&&pad.editbarClick('underline');return false;" >
|
||||
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
|
||||
</li>
|
||||
<li id="strikethrough" onClick="window.pad&&pad.editbarClick('strikethrough');return false;">
|
||||
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="oderedlist" onClick="window.pad&&pad.editbarClick('insertorderedlist');return false;">
|
||||
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
|
||||
</li>
|
||||
<li id="unoderedlist" onClick="window.pad&&pad.editbarClick('insertunorderedlist');return false;">
|
||||
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
|
||||
</li>
|
||||
<li id="indent" onClick="window.pad&&pad.editbarClick('indent');return false;">
|
||||
<a class="buttonicon buttonicon-indent" title="Indent"></a>
|
||||
</li>
|
||||
<li id="outdent" onClick="window.pad&&pad.editbarClick('outdent');return false;">
|
||||
<a class="buttonicon buttonicon-outdent" title="Unindent"></a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="undo" onClick="window.pad&&pad.editbarClick('undo');return false;">
|
||||
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
|
||||
</li>
|
||||
<li id="redo" onClick="window.pad&&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&&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&&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&&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&&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&&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;">- </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"> </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>
|
217
src/templates/admin/plugins.html
Normal file
217
src/templates/admin/plugins.html
Normal 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
307
src/templates/pad.html
Normal 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&&pad.editbarClick('bold');return false">
|
||||
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
|
||||
</li>
|
||||
<li id="italic" onClick="window.pad&&pad.editbarClick('italic'); return false;">
|
||||
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
|
||||
</li>
|
||||
<li id="underline" onClick="window.pad&&pad.editbarClick('underline');return false;" >
|
||||
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
|
||||
</li>
|
||||
<li id="strikethrough" onClick="window.pad&&pad.editbarClick('strikethrough');return false;">
|
||||
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="oderedlist" onClick="window.pad&&pad.editbarClick('insertorderedlist');return false;">
|
||||
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
|
||||
</li>
|
||||
<li id="unoderedlist" onClick="window.pad&&pad.editbarClick('insertunorderedlist');return false;">
|
||||
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
|
||||
</li>
|
||||
<li id="indent" onClick="window.pad&&pad.editbarClick('indent');return false;">
|
||||
<a class="buttonicon buttonicon-indent" title="Indent"></a>
|
||||
</li>
|
||||
<li id="outdent" onClick="window.pad&&pad.editbarClick('outdent');return false;">
|
||||
<a class="buttonicon buttonicon-outdent" title="Unindent"></a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="undo" onClick="window.pad&&pad.editbarClick('undo');return false;">
|
||||
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
|
||||
</li>
|
||||
<li id="redo" onClick="window.pad&&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&&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&&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&&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&&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&&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&&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;">- </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"> </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>
|
|
@ -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>
|
|
@ -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 shoudn’t 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);
|
Loading…
Reference in a new issue