diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md
index e4322e9e..05656bce 100644
--- a/doc/api/editorInfo.md
+++ b/doc/api/editorInfo.md
@@ -45,3 +45,26 @@ Returns the `rep` object.
## editorInfo.ace_doInsertUnorderedList(?)
## editorInfo.ace_doInsertOrderedList(?)
## 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(?)
diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md
index f706f6a1..55d1da00 100644
--- a/doc/api/hooks_client-side.md
+++ b/doc/api/hooks_client-side.md
@@ -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.
`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.
diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md
index 06ec7374..854b4339 100644
--- a/doc/api/hooks_server-side.md
+++ b/doc/api/hooks_server-side.md
@@ -136,3 +136,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.
+
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js
index 8a5a92bb..10b259ae 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -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
- * This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly
- * Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
+ * Handles a USER_CHANGES message, where the client submits its local
+ * edits as a changeset.
+ *
+ * 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 message the message from the client
*/
function handleUserChanges(client, message)
{
- //check if all ok
+ // Make sure all required fields are present
if(message.data.baseRev == null)
{
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
@@ -487,22 +494,23 @@ function handleUserChanges(client, message)
{
//ex. _checkChangesetAndPool
- //Copied from Etherpad, don't know what it does exactly
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);
-
+
+ // Verify that the attribute indexes used in the changeset are all
+ // defined in the accompanying attribute pool.
Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
}
});
}
- //there is an error in this changeset, so just refuse it
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"});
return;
}
@@ -515,7 +523,10 @@ function handleUserChanges(client, message)
//ex. applyUserChanges
apool = pad.pool;
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
async.whilst(
function() { return r < pad.getHeadRevisionNumber(); },
@@ -526,8 +537,13 @@ function handleUserChanges(client, message)
pad.getRevisionChangeset(r, function(err, c)
{
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);
+
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
async.nextTick(callback);
} else {
@@ -558,7 +574,8 @@ function handleUserChanges(client, message)
if (correctionChangeset) {
pad.appendRevision(correctionChangeset);
}
-
+
+ // Make sure the pad always ends with an empty line.
if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
pad.appendRevision(nlChangeset);
@@ -865,6 +882,13 @@ function handleClientReady(client, message)
},
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!
if(pad2sessions[padIds.padId])
{
@@ -879,10 +903,9 @@ function handleClientReady(client, message)
}
//Save in sessioninfos that this session belonges to this pad
- var sessionId=String(client.id);
- sessioninfos[sessionId].padId = padIds.padId;
- sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId;
- sessioninfos[sessionId].readonly = padIds.readonly;
+ sessioninfos[client.id].padId = padIds.padId;
+ sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
+ sessioninfos[client.id].readonly = padIds.readonly;
//check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[padIds.padId])
@@ -891,7 +914,7 @@ function handleClientReady(client, message)
}
//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
var atext = Changeset.cloneAText(pad.atext);
@@ -956,26 +979,22 @@ function handleClientReady(client, message)
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
- if(message.reconnect == true)
- {
- //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;
+ //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 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
var messageToTheOtherUsers = {
diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js
index cb8c5898..4f5dad4f 100644
--- a/src/node/hooks/express/errorhandling.js
+++ b/src/node/hooks/express/errorhandling.js
@@ -38,7 +38,6 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.error(function(err, req, res, next){
res.send(500);
console.error(err.stack ? err.stack : err.toString());
- exports.gracefulShutdown();
});
//connect graceful shutdown with sigint and uncaughtexception
diff --git a/src/package.json b/src/package.json
index 2285f0ca..e2f9c774 100644
--- a/src/package.json
+++ b/src/package.json
@@ -10,7 +10,7 @@
"name": "Robin Buse" }
],
"dependencies" : {
- "yajsml" : "1.1.5",
+ "yajsml" : "1.1.6",
"request" : "2.9.100",
"require-kernel" : "1.0.5",
"resolve" : "0.2.x",
diff --git a/src/static/js/ace.js b/src/static/js/ace.js
index db62deb4..e50f75c7 100644
--- a/src/static/js/ace.js
+++ b/src/static/js/ace.js
@@ -24,6 +24,8 @@
// requires: plugins
// requires: undefined
+var KERNEL_SOURCE = '../static/js/require-kernel.js';
+
Ace2Editor.registry = {
nextId: 1
};
@@ -31,6 +33,14 @@ Ace2Editor.registry = {
var hooks = require('./pluginfw/hooks');
var _ = require('./underscore');
+function scriptTag(source) {
+ return (
+ ''
+ )
+}
+
function Ace2Editor()
{
var ace2 = Ace2Editor;
@@ -155,24 +165,6 @@ function Ace2Editor()
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('\
-');
-
- iframeHTML.push('