Merge branch 'develop' of github.com:Pita/etherpad-lite into feature/frontend-tests

This commit is contained in:
johnyma22 2012-10-04 18:55:57 +01:00
commit c74aed986e
17 changed files with 331 additions and 168 deletions

View file

@ -45,3 +45,26 @@ Returns the `rep` object.
## editorInfo.ace_doInsertUnorderedList(?) ## editorInfo.ace_doInsertUnorderedList(?)
## editorInfo.ace_doInsertOrderedList(?) ## editorInfo.ace_doInsertOrderedList(?)
## editorInfo.ace_performDocumentApplyAttributesToRange() ## editorInfo.ace_performDocumentApplyAttributesToRange()
## editorInfo.ace_getAuthorInfos()
Returns an info object about the author. Object key = author_id and info includes author's bg color value.
Use to define your own authorship.
## editorInfo.ace_performDocumentReplaceRange(start, end, newText)
This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`.
## editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText)
This function replaces a range (from y1 to y2) with `newText`.
## editorInfo.ace_renumberList(lineNum)
If you delete a line, calling this method will fix the line numbering.
## editorInfo.ace_doReturnKey()
Forces a return key at the current carret position.
## editorInfo.ace_isBlockElement(element)
Returns true if your passed elment is registered as a block element
## editorInfo.ace_getLineListType(lineNum)
Returns the line's html list type.
## editorInfo.ace_caretLine()
Returns X position of the caret.
## editorInfo.ace_caretColumn()
Returns Y position of the caret.
## editorInfo.ace_caretDocChar()
Returns the Y offset starting from [x=0,y=0]
## editorInfo.ace_isWordChar(?)

View file

@ -174,3 +174,71 @@ Things in context:
This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types.
`collab_client.js` has a pretty extensive list of message types, if you want to take a look. `collab_client.js` has a pretty extensive list of message types, if you want to take a look.
##aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
Called from: src/static/js/ace2_inner.js
Things in context:
1. callstack - a bunch of information about the current action
2. editorInfo - information about the user who is making the change
3. rep - information about where the change is being made
4. root - the span element of the current line
5. point - the starting/ending element where the cursor highlights
6. documentAttributeManager - information about attributes in the document
This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection.
The return value should be an array of [line,char]
##aceKeyEvent
Called from: src/static/js/ace2_inner.js
Things in context:
1. callstack - a bunch of information about the current action
2. editorInfo - information about the user who is making the change
3. rep - information about where the change is being made
4. documentAttributeManager - information about attributes in the document
5. evt - the fired event
This hook is provided to allow a plugin to handle key events.
The return value should be true if you have handled the event.
##collectContentLineText
Called from: src/static/js/contentcollector.js
Things in context:
1. cc - the contentcollector object
2. state - the current state of the change being made
3. tname - the tag name of this node currently being processed
4. text - the text for that line
This hook allows you to validate/manipulate the text before it's sent to the server side.
The return value should be the validated/manipulated text.
##collectContentLineBreak
Called from: src/static/js/contentcollector.js
Things in context:
1. cc - the contentcollector object
2. state - the current state of the change being made
3. tname - the tag name of this node currently being processed
This hook is provided to allow whether the br tag should induce a new magic domline or not.
The return value should be either true(break the line) or false.
##disableAuthorColorsForThisLine
Called from: src/static/js/linestylefilter.js
Things in context:
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
2. text - the line text
3. class - line class
This hook is provided to allow whether a given line should be deliniated with multiple authors.
Multiple authors in one line cause the creation of magic span lines. This might not suit you and
now you can disable it and handle your own deliniation.
The return value should be either true(disable) or false.

View file

@ -49,7 +49,8 @@ Called from: src/node/server.js
Things in context: Things in context:
1. app - the main application object (helpful for adding new paths and such) 1. app - the main express application object (helpful for adding new paths and such)
2. server - the http server object
This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables.
@ -71,6 +72,7 @@ Things in context:
1. app - the application object 1. app - the application object
2. io - the socketio object 2. io - the socketio object
3. server - the http server object
I have no idea what this is useful for, someone else will have to add this description. I have no idea what this is useful for, someone else will have to add this description.
@ -136,3 +138,16 @@ function handleMessage ( hook, context, callback ) {
} }
}; };
``` ```
## getLineHTMLForExport
Called from: src/node/utils/ExportHtml.js
Things in context:
1. apool - pool object
2. attribLine - line attributes
3. text - line text
This hook will allow a plug-in developer to re-write each line when exporting to HTML.

View file

@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message)
} }
/** /**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations * Handles a USER_CHANGES message, where the client submits its local
* This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly * edits as a changeset.
* Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() *
* This handler's job is to update the incoming changeset so that it applies
* to the latest revision, then add it to the pad, broadcast the changes
* to all other clients, and send a confirmation to the submitting client.
*
* This function is based on a similar one in the original Etherpad.
* See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
*
* @param client the client that send this message * @param client the client that send this message
* @param message the message from the client * @param message the message from the client
*/ */
function handleUserChanges(client, message) function handleUserChanges(client, message)
{ {
//check if all ok // Make sure all required fields are present
if(message.data.baseRev == null) if(message.data.baseRev == null)
{ {
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
@ -487,22 +494,23 @@ function handleUserChanges(client, message)
{ {
//ex. _checkChangesetAndPool //ex. _checkChangesetAndPool
//Copied from Etherpad, don't know what it does exactly
try try
{ {
//this looks like a changeset check, it throws errors sometimes // Verify that the changeset has valid syntax and is in canonical form
Changeset.checkRep(changeset); Changeset.checkRep(changeset);
// Verify that the attribute indexes used in the changeset are all
// defined in the accompanying attribute pool.
Changeset.eachAttribNumber(changeset, function(n) { Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) { if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
} }
}); });
} }
//there is an error in this changeset, so just refuse it
catch(e) catch(e)
{ {
console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep"); // There is an error in this changeset, so just refuse it
console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
client.json.send({disconnect:"badChangeset"}); client.json.send({disconnect:"badChangeset"});
return; return;
} }
@ -516,6 +524,9 @@ function handleUserChanges(client, message)
apool = pad.pool; apool = pad.pool;
r = baseRev; r = baseRev;
// The client's changeset might not be based on the latest revision,
// since other clients are sending changes at the same time.
// Update the changeset so that it can be applied to the latest revision.
//https://github.com/caolan/async#whilst //https://github.com/caolan/async#whilst
async.whilst( async.whilst(
function() { return r < pad.getHeadRevisionNumber(); }, function() { return r < pad.getHeadRevisionNumber(); },
@ -527,7 +538,12 @@ function handleUserChanges(client, message)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
// At this point, both "c" (from the pad) and "changeset" (from the
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// and can be applied after "c".
changeset = Changeset.follow(c, changeset, false, apool); changeset = Changeset.follow(c, changeset, false, apool);
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
async.nextTick(callback); async.nextTick(callback);
} else { } else {
@ -559,6 +575,7 @@ function handleUserChanges(client, message)
pad.appendRevision(correctionChangeset); pad.appendRevision(correctionChangeset);
} }
// Make sure the pad always ends with an empty line.
if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
pad.appendRevision(nlChangeset); pad.appendRevision(nlChangeset);
@ -865,6 +882,13 @@ function handleClientReady(client, message)
}, },
function(callback) function(callback)
{ {
//Check that the client is still here. It might have disconnected between callbacks.
if(sessioninfos[client.id] === undefined)
{
callback();
return;
}
//Check if this author is already on the pad, if yes, kick the other sessions! //Check if this author is already on the pad, if yes, kick the other sessions!
if(pad2sessions[padIds.padId]) if(pad2sessions[padIds.padId])
{ {
@ -879,10 +903,9 @@ function handleClientReady(client, message)
} }
//Save in sessioninfos that this session belonges to this pad //Save in sessioninfos that this session belonges to this pad
var sessionId=String(client.id); sessioninfos[client.id].padId = padIds.padId;
sessioninfos[sessionId].padId = padIds.padId; sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId; sessioninfos[client.id].readonly = padIds.readonly;
sessioninfos[sessionId].readonly = padIds.readonly;
//check if there is already a pad2sessions entry, if not, create one //check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[padIds.padId]) if(!pad2sessions[padIds.padId])
@ -891,7 +914,7 @@ function handleClientReady(client, message)
} }
//Saves in pad2sessions that this session belongs to this pad //Saves in pad2sessions that this session belongs to this pad
pad2sessions[padIds.padId].push(sessionId); pad2sessions[padIds.padId].push(client.id);
//prepare all values for the wire //prepare all values for the wire
var atext = Changeset.cloneAText(pad.atext); var atext = Changeset.cloneAText(pad.atext);
@ -956,26 +979,22 @@ function handleClientReady(client, message)
clientVars.userName = authorName; clientVars.userName = authorName;
} }
if(sessioninfos[client.id] !== undefined) //If this is a reconnect, we don't have to send the client the ClientVars again
if(message.reconnect == true)
{ {
//This is a reconnect, so we don't have to send the client the ClientVars again //Save the revision in sessioninfos, we take the revision from the info the client send to us
if(message.reconnect == true) sessioninfos[client.id].rev = message.client_rev;
{
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the revision in sessioninfos
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
//Save the revision and the author id in sessioninfos
sessioninfos[client.id].author = author;
} }
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the current revision in sessioninfos, should be the same as in clientVars
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
sessioninfos[client.id].author = author;
//prepare the notification for the other users on the pad, that this user joined //prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = { var messageToTheOtherUsers = {

View file

@ -1,4 +1,5 @@
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var http = require('http');
var express = require('express'); var express = require('express');
var settings = require('../utils/Settings'); var settings = require('../utils/Settings');
var fs = require('fs'); var fs = require('fs');
@ -42,22 +43,24 @@ exports.createServer = function () {
} }
exports.restartServer = function () { exports.restartServer = function () {
if (server) { if (server) {
console.log("Restarting express server"); console.log("Restarting express server");
server.close(); server.close();
} }
server = express.createServer(); var app = express(); // New syntax for express v3
server = http.createServer(app);
server.use(function (req, res, next) { app.use(function (req, res, next) {
res.header("Server", serverName); res.header("Server", serverName);
next(); next();
}); });
server.configure(function() { app.configure(function() {
hooks.callAll("expressConfigure", {"app": server}); hooks.callAll("expressConfigure", {"app": app});
}); });
hooks.callAll("expressCreateServer", {"app": server}); hooks.callAll("expressCreateServer", {"app": app, "server": server});
server.listen(settings.port, settings.ip); server.listen(settings.port, settings.ip);
} }

View file

@ -12,14 +12,10 @@ exports.expressCreateServer = function (hook_name, args, cb) {
errors: [], errors: [],
}; };
res.send(eejs.require( res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) );
"ep_etherpad-lite/templates/admin/plugins.html",
render_args), {});
}); });
args.app.get('/admin/plugins/info', function(req, res) { args.app.get('/admin/plugins/info', function(req, res) {
res.send(eejs.require( res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) );
"ep_etherpad-lite/templates/admin/plugins-info.html",
{}), {});
}); });
} }

View file

@ -16,9 +16,6 @@ exports.gracefulShutdown = function(err) {
console.log("graceful shutdown..."); console.log("graceful shutdown...");
//stop the http server
exports.app.close();
//do the db shutdown //do the db shutdown
db.db.doShutdown(function() { db.db.doShutdown(function() {
console.log("db sucessfully closed."); console.log("db sucessfully closed.");
@ -35,11 +32,14 @@ exports.gracefulShutdown = function(err) {
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app; exports.app = args.app;
args.app.error(function(err, req, res, next){ // Handle errors
res.send(500); args.app.use(function(err, req, res, next){
console.error(err.stack ? err.stack : err.toString()); // if an error occurs Connect will pass it down
exports.gracefulShutdown(); // through these "error-handling" middleware
}); // allowing you to respond however you like
res.send(500, { error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
})
//connect graceful shutdown with sigint and uncaughtexception //connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1) { if(os.type().indexOf("Windows") == -1) {

View file

@ -56,7 +56,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
ERR(err); ERR(err);
if(err == "notfound") if(err == "notfound")
res.send('404 - Not Found', 404); res.send(404, '404 - Not Found');
else else
res.send(html); res.send(html);
}); });

View file

@ -7,7 +7,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//ensure the padname is valid and the url doesn't end with a / //ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url)) if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{ {
res.send('Such a padname is forbidden', 404); res.send(404, 'Such a padname is forbidden');
} }
else else
{ {
@ -19,7 +19,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
var query = url.parse(req.url).query; var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query; if ( query ) real_url += '?' + query;
res.header('Location', real_url); res.header('Location', real_url);
res.send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>', 302); res.send(302, 'You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
} }
//the pad id was fine, so just render it //the pad id was fine, so just render it
else else

View file

@ -3,6 +3,7 @@ var socketio = require('socket.io');
var settings = require('../../utils/Settings'); var settings = require('../../utils/Settings');
var socketIORouter = require("../../handler/SocketIORouter"); var socketIORouter = require("../../handler/SocketIORouter");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess");
var padMessageHandler = require("../../handler/PadMessageHandler"); var padMessageHandler = require("../../handler/PadMessageHandler");
@ -10,19 +11,28 @@ var connect = require('connect');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler //init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(args.app); var io = socketio.listen(args.server);
/* Require an express session cookie to be present, and load the /* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more * session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */ * info */
io.set('authorization', function (data, accept) { io.set('authorization', function (data, accept) {
if (!data.headers.cookie) return accept('No session cookie transmitted.', false); if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
data.cookie = connect.utils.parseCookie(data.headers.cookie);
data.sessionID = data.cookie.express_sid; // Use connect's cookie parser, because it knows how to parse signed cookies
args.app.sessionStore.get(data.sessionID, function (err, session) { connect.cookieParser(webaccess.secret)(data, {}, function(err){
if (err || !session) return accept('Bad session / session has expired', false); if(err) {
data.session = new connect.middleware.session.Session(data, session); console.error(err);
accept(null, true); accept("Couldn't parse request cookies. ", false);
return;
}
data.sessionID = data.signedCookies.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
accept(null, true);
});
}); });
}); });
@ -62,5 +72,5 @@ exports.expressCreateServer = function (hook_name, args, cb) {
socketIORouter.setSocketIO(io); socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler); socketIORouter.addComponent("pad", padMessageHandler);
hooks.callAll("socketio", {"app": args.app, "io": io}); hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server});
} }

View file

@ -56,10 +56,10 @@ exports.basicAuth = function (req, res, next) {
res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) { if (req.headers.authorization) {
setTimeout(function () { setTimeout(function () {
res.send('Authentication required', 401); res.send(401, 'Authentication required');
}, 1000); }, 1000);
} else { } else {
res.send('Authentication required', 401); res.send(401, 'Authentication required');
} }
})); }));
} }
@ -88,14 +88,13 @@ exports.basicAuth = function (req, res, next) {
}); });
} }
var secret = null; exports.secret = null;
exports.expressConfigure = function (hook_name, args, cb) { exports.expressConfigure = function (hook_name, args, cb) {
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // 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. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(express.cookieParser());
/* Do not let express create the session, so that we can retain a /* Do not let express create the session, so that we can retain a
* reference to it for socket.io to use. Also, set the key (cookie * reference to it for socket.io to use. Also, set the key (cookie
@ -104,13 +103,14 @@ exports.expressConfigure = function (hook_name, args, cb) {
if (!exports.sessionStore) { if (!exports.sessionStore) {
exports.sessionStore = new express.session.MemoryStore(); exports.sessionStore = new express.session.MemoryStore();
secret = randomString(32); exports.secret = randomString(32);
} }
args.app.use(express.cookieParser(exports.secret));
args.app.sessionStore = exports.sessionStore; args.app.sessionStore = exports.sessionStore;
args.app.use(express.session({store: args.app.sessionStore, args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid', key: 'express_sid' }));
secret: secret}));
args.app.use(exports.basicAuth); args.app.use(exports.basicAuth);
} }

View file

@ -15,7 +15,7 @@ module.exports = function (req, res, callback) {
callback(); callback();
//no access //no access
} else { } else {
res.send("403 - Can't touch this", 403); res.send(403, "403 - Can't touch this");
} }
}); });
} }

View file

@ -21,8 +21,15 @@
* limitations under the License. * limitations under the License.
*/ */
// set up logger
var log4js = require('log4js'); var log4js = require('log4js');
log4js.replaceConsole();
var settings = require('./utils/Settings'); var settings = require('./utils/Settings');
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
var db = require('./db/DB'); var db = require('./db/DB');
var async = require('async'); var async = require('async');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
@ -31,9 +38,6 @@ var npm = require("npm/lib/npm.js");
hooks.plugins = plugins; hooks.plugins = plugins;
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
async.waterfall([ async.waterfall([
//initalize the database //initalize the database
function (callback) function (callback)

View file

@ -10,19 +10,19 @@
"name": "Robin Buse" } "name": "Robin Buse" }
], ],
"dependencies" : { "dependencies" : {
"yajsml" : "1.1.5", "yajsml" : "1.1.6",
"request" : "2.9.100", "request" : "2.9.100",
"require-kernel" : "1.0.5", "require-kernel" : "1.0.5",
"resolve" : "0.2.x", "resolve" : "0.2.x",
"socket.io" : "0.9.x", "socket.io" : "0.9.x",
"ueberDB" : "0.1.7", "ueberDB" : "0.1.7",
"async" : "0.1.x", "async" : "0.1.22",
"express" : "2.5.x", "express" : "3.x",
"connect" : "1.x", "connect" : "2.4.x",
"clean-css" : "0.3.2", "clean-css" : "0.3.2",
"uglify-js" : "1.2.5", "uglify-js" : "1.2.5",
"formidable" : "1.0.9", "formidable" : "1.0.9",
"log4js" : "0.4.1", "log4js" : "0.5.x",
"jsdom-nocontextifiy" : "0.2.10", "jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2", "async-stacktrace" : "0.0.2",
"npm" : "1.1.24", "npm" : "1.1.24",

View file

@ -24,6 +24,8 @@
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var KERNEL_SOURCE = '../static/js/require-kernel.js';
Ace2Editor.registry = { Ace2Editor.registry = {
nextId: 1 nextId: 1
}; };
@ -31,6 +33,14 @@ Ace2Editor.registry = {
var hooks = require('./pluginfw/hooks'); var hooks = require('./pluginfw/hooks');
var _ = require('./underscore'); var _ = require('./underscore');
function scriptTag(source) {
return (
'<script type="text/javascript">\n'
+ source.replace(/<\//g, '<\\/') +
'</script>'
)
}
function Ace2Editor() function Ace2Editor()
{ {
var ace2 = Ace2Editor; var ace2 = Ace2Editor;
@ -155,24 +165,6 @@ function Ace2Editor()
return {embeded: embededFiles, remote: remoteFiles}; return {embeded: embededFiles, remote: remoteFiles};
} }
function pushRequireScriptTo(buffer) {
var KERNEL_SOURCE = '../static/js/require-kernel.js';
var KERNEL_BOOT = '\
require.setRootURI("../javascripts/src");\n\
require.setLibraryURI("../javascripts/lib");\n\
require.setGlobalKeyPath("require");\n\
';
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
} else {
// Remotely src'd script tag will not work in IE; it must be embedded, so
// throw an error if it is not.
throw new Error("Require script could not be embedded.");
}
}
function pushStyleTagsFor(buffer, files) { function pushStyleTagsFor(buffer, files) {
var sorted = sortFilesByEmbeded(files); var sorted = sortFilesByEmbeded(files);
var embededFiles = sorted.embeded; var embededFiles = sorted.embeded;
@ -236,23 +228,30 @@ require.setGlobalKeyPath("require");\n\
pushStyleTagsFor(iframeHTML, includedCSS); pushStyleTagsFor(iframeHTML, includedCSS);
var includedJS = []; if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
pushRequireScriptTo(iframeHTML); // Remotely src'd script tag will not work in IE; it must be embedded, so
// throw an error if it is not.
throw new Error("Require kernel could not be found.");
}
// Inject my plugins into my child. iframeHTML.push(scriptTag(
iframeHTML.push('\ Ace2Editor.EMBEDED[KERNEL_SOURCE] + '\n\
<script type="text/javascript">\n\ require.setRootURI("../javascripts/src");\n\
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");\n\ require.setLibraryURI("../javascripts/lib");\n\
var plugins = require("ep_etherpad-lite/static/js/pluginfw/client_plugins");\n\ require.setGlobalKeyPath("require");\n\
hooks.plugins = plugins;\n\ \n\
plugins.adoptPluginsFromAncestorsOf(window);\n\ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");\n\
</script>\ var plugins = require("ep_etherpad-lite/static/js/pluginfw/client_plugins");\n\
'); hooks.plugins = plugins;\n\
plugins.adoptPluginsFromAncestorsOf(window);\n\
iframeHTML.push('<script type="text/javascript">'); \n\
iframeHTML.push('$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK'); $ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK\n\
iframeHTML.push('require("ep_etherpad-lite/static/js/ace2_inner");'); var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\
iframeHTML.push('<\/script>'); \n\
plugins.ensure(function () {\n\
Ace2Inner.init();\n\
});\n\
'));
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>'); iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
@ -266,8 +265,32 @@ require.setGlobalKeyPath("require");\n\
var thisFunctionsName = "ChildAccessibleAce2Editor"; var thisFunctionsName = "ChildAccessibleAce2Editor";
(function () {return this}())[thisFunctionsName] = Ace2Editor; (function () {return this}())[thisFunctionsName] = Ace2Editor;
var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); iframe.name = "ace_inner";' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE var outerScript = '\
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }'; editorId = ' + JSON.stringify(info.id) + ';\n\
editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\
window.onload = function () {\n\
window.onload = null;\n\
setTimeout(function () {\n\
var iframe = document.createElement("IFRAME");\n\
iframe.name = "ace_inner";\n\
iframe.scrolling = "no";\n\
var outerdocbody = document.getElementById("outerdocbody");\n\
iframe.frameBorder = 0;\n\
iframe.allowTransparency = true; // for IE\n\
outerdocbody.insertBefore(iframe, outerdocbody.firstChild);\n\
iframe.ace_outerWin = window;\n\
readyFunc = function () {\n\
editorInfo.onEditorReady();\n\
readyFunc = null;\n\
editorInfo = null;\n\
};\n\
var doc = iframe.contentWindow.document;\n\
doc.open();\n\
var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\
doc.write(text);\n\
doc.close();\n\
}, 0);\n\
}';
var outerHTML = [doctype, '<html><head>'] var outerHTML = [doctype, '<html><head>']
@ -285,7 +308,7 @@ require.setGlobalKeyPath("require");\n\
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing) // (throbs busy while typing)
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>'); outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
var outerFrame = document.createElement("IFRAME"); var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer"; outerFrame.name = "ace_outer";

View file

@ -19,7 +19,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
var editor, _, $, jQuery, plugins, Ace2Common; var _, $, jQuery, plugins, Ace2Common;
Ace2Common = require('./ace2_common'); Ace2Common = require('./ace2_common');
@ -5430,62 +5430,64 @@ function Ace2Inner(){
return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments); return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments);
}; };
$(document).ready(function(){ this.init = function () {
doc = document; // defined as a var in scope outside $(document).ready(function(){
inCallStack("setup", function() doc = document; // defined as a var in scope outside
{ inCallStack("setup", function()
var body = doc.getElementById("innerdocbody");
root = body; // defined as a var in scope outside
if (browser.mozilla) $(root).addClass("mozilla");
if (browser.safari) $(root).addClass("safari");
if (browser.msie) $(root).addClass("msie");
if (browser.msie)
{ {
// cache CSS background images var body = doc.getElementById("innerdocbody");
try root = body; // defined as a var in scope outside
if (browser.mozilla) $(root).addClass("mozilla");
if (browser.safari) $(root).addClass("safari");
if (browser.msie) $(root).addClass("msie");
if (browser.msie)
{ {
doc.execCommand("BackgroundImageCache", false, true); // cache CSS background images
try
{
doc.execCommand("BackgroundImageCache", false, true);
}
catch (e)
{ /* throws an error in some IE 6 but not others! */
}
} }
catch (e) setClassPresence(root, "authorColors", true);
{ /* throws an error in some IE 6 but not others! */ setClassPresence(root, "doesWrap", doesWrap);
}
}
setClassPresence(root, "authorColors", true);
setClassPresence(root, "doesWrap", doesWrap);
initDynamicCSS(); initDynamicCSS();
enforceEditability(); enforceEditability();
// set up dom and rep // set up dom and rep
while (root.firstChild) root.removeChild(root.firstChild); while (root.firstChild) root.removeChild(root.firstChild);
var oneEntry = createDomLineEntry(""); var oneEntry = createDomLineEntry("");
doRepLineSplice(0, rep.lines.length(), [oneEntry]); doRepLineSplice(0, rep.lines.length(), [oneEntry]);
insertDomLines(null, [oneEntry.domInfo], null); insertDomLines(null, [oneEntry.domInfo], null);
rep.alines = Changeset.splitAttributionLines( rep.alines = Changeset.splitAttributionLines(
Changeset.makeAttribution("\n"), "\n"); Changeset.makeAttribution("\n"), "\n");
bindTheEventHandlers(); bindTheEventHandlers();
});
hooks.callAll('aceInitialized', {
editorInfo: editorInfo,
rep: rep,
documentAttributeManager: documentAttributeManager
});
scheduler.setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe
}, 0);
isSetUp = true;
}); });
}
hooks.callAll('aceInitialized', {
editorInfo: editorInfo,
rep: rep,
documentAttributeManager: documentAttributeManager
});
scheduler.setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe
}, 0);
isSetUp = true;
});
} }
// Ensure that plugins are loaded before initializing the editor exports.init = function () {
plugins.ensure(function () { var editor = new Ace2Inner()
var editor = new Ace2Inner(); editor.init();
}); };

View file

@ -249,13 +249,13 @@ var padeditbar = (function()
{ {
var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/")); var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/"));
var readonlyLink = basePath + "/p/" + clientVars.readOnlyId; var readonlyLink = basePath + "/p/" + clientVars.readOnlyId;
$('#embedinput').val("<iframe name='embed_readonly' src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>"); $('#embedinput').val("<iframe name='embed_readonly' src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400></iframe>");
$('#linkinput').val(readonlyLink); $('#linkinput').val(readonlyLink);
} }
else else
{ {
var padurl = window.location.href.split("?")[0]; var padurl = window.location.href.split("?")[0];
$('#embedinput').val("<iframe name='embed_readwrite' src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>"); $('#embedinput').val("<iframe name='embed_readwrite' src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400></iframe>");
$('#linkinput').val(padurl); $('#linkinput').val(padurl);
} }
} }