mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-24 09:26:14 -04:00
Merge branch 'master' of git://github.com/ether/etherpad-lite into create_pad_special_characters
This commit is contained in:
commit
aa0d14c7d7
237 changed files with 6386 additions and 1927 deletions
|
@ -263,7 +263,7 @@ exports.getText = function(padID, rev, callback)
|
|||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
data = {text: atext.text};
|
||||
var data = {text: atext.text};
|
||||
|
||||
callback(null, data);
|
||||
})
|
||||
|
@ -368,7 +368,7 @@ exports.getHTML = function(padID, rev, callback)
|
|||
if(ERR(err, callback)) return;
|
||||
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
|
||||
html += "</body></html>";
|
||||
data = {html: html};
|
||||
var data = {html: html};
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
@ -380,7 +380,7 @@ exports.getHTML = function(padID, rev, callback)
|
|||
if(ERR(err, callback)) return;
|
||||
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
|
||||
html += "</body></html>";
|
||||
data = {html: html};
|
||||
var data = {html: html};
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
@ -410,11 +410,16 @@ exports.setHTML = function(padID, html, callback)
|
|||
if(ERR(err, callback)) return;
|
||||
|
||||
// add a new changeset with the new html to the pad
|
||||
importHtml.setPadHTML(pad, cleanText(html), callback);
|
||||
|
||||
//update the clients on the pad
|
||||
padMessageHandler.updatePadClients(pad, callback);
|
||||
|
||||
importHtml.setPadHTML(pad, cleanText(html), function(e){
|
||||
if(e){
|
||||
callback(new customError("HTML is malformed","apierror"));
|
||||
return;
|
||||
}else{
|
||||
//update the clients on the pad
|
||||
padMessageHandler.updatePadClients(pad, callback);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -427,8 +432,8 @@ getChatHistory(padId, start, end), returns a part of or the whole chat-history o
|
|||
|
||||
Example returns:
|
||||
|
||||
{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"},
|
||||
{"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}}
|
||||
{"code":0,"message":"ok","data":{"messages":[{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
|
||||
{"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}]}}
|
||||
|
||||
{code: 1, message:"start is higher or equal to the current chatHead", data: null}
|
||||
|
||||
|
@ -489,6 +494,33 @@ exports.getChatHistory = function(padID, start, end, callback)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.appendChatMessage = function(padID, text, authorID, time, callback)
|
||||
{
|
||||
//text is required
|
||||
if(typeof text != "string")
|
||||
{
|
||||
callback(new customError("text is no string","apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
pad.appendChatMessage(text, authorID, parseInt(time));
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
/*****************/
|
||||
/**PAD FUNCTIONS */
|
||||
/*****************/
|
||||
|
@ -512,6 +544,117 @@ exports.getRevisionsCount = function(padID, callback)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {savedRevisions: 42}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getSavedRevisionsCount = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
callback(null, {savedRevisions: pad.getSavedRevisionsNumber()});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
listSavedRevisions(padID) returns the list of saved revisions of this pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.listSavedRevisions = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
callback(null, {savedRevisions: pad.getSavedRevisionsList()});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
saveRevision(padID) returns the list of saved revisions of this pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.saveRevision = function(padID, rev, callback)
|
||||
{
|
||||
//check if rev is set
|
||||
if(typeof rev == "function")
|
||||
{
|
||||
callback = rev;
|
||||
rev = undefined;
|
||||
}
|
||||
|
||||
//check if rev is a number
|
||||
if(rev !== undefined && typeof rev != "number")
|
||||
{
|
||||
//try to parse the number
|
||||
if(!isNaN(parseInt(rev)))
|
||||
{
|
||||
rev = parseInt(rev);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(new customError("rev is not a number", "apierror"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure this is not a negativ number
|
||||
if(rev !== undefined && rev < 0)
|
||||
{
|
||||
callback(new customError("rev is a negativ number","apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//ensure this is not a float value
|
||||
if(rev !== undefined && !is_int(rev))
|
||||
{
|
||||
callback(new customError("rev is a float value","apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
//the client asked for a special revision
|
||||
if(rev !== undefined)
|
||||
{
|
||||
//check if this is a valid revision
|
||||
if(rev > pad.getHeadRevisionNumber())
|
||||
{
|
||||
callback(new customError("rev is higher than the head revision of the pad","apierror"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
||||
authorManager.createAuthor('API', function(err, author) {
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getLastEdited(padID) returns the timestamp of the last revision of the pad
|
||||
|
||||
|
@ -584,6 +727,117 @@ exports.deletePad = function(padID, callback)
|
|||
pad.remove(callback);
|
||||
});
|
||||
}
|
||||
/**
|
||||
restoreRevision(padID, [rev]) Restores revision from past as new changeset
|
||||
|
||||
Example returns:
|
||||
|
||||
{code:0, message:"ok", data:null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.restoreRevision = function (padID, rev, callback)
|
||||
{
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padMessage = require("ep_etherpad-lite/node/handler/PadMessageHandler.js");
|
||||
|
||||
//check if rev is a number
|
||||
if (rev !== undefined && typeof rev != "number")
|
||||
{
|
||||
//try to parse the number
|
||||
if (!isNaN(parseInt(rev)))
|
||||
{
|
||||
rev = parseInt(rev);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(new customError("rev is not a number", "apierror"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure this is not a negativ number
|
||||
if (rev !== undefined && rev < 0)
|
||||
{
|
||||
callback(new customError("rev is a negativ number", "apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//ensure this is not a float value
|
||||
if (rev !== undefined && !is_int(rev))
|
||||
{
|
||||
callback(new customError("rev is a float value", "apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function (err, pad)
|
||||
{
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
|
||||
//check if this is a valid revision
|
||||
if (rev > pad.getHeadRevisionNumber())
|
||||
{
|
||||
callback(new customError("rev is higher than the head revision of the pad", "apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
pad.getInternalRevisionAText(rev, function (err, atext)
|
||||
{
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
var oldText = pad.text();
|
||||
atext.text += "\n";
|
||||
function eachAttribRun(attribs, func)
|
||||
{
|
||||
var attribsIter = Changeset.opIterator(attribs);
|
||||
var textIndex = 0;
|
||||
var newTextStart = 0;
|
||||
var newTextEnd = atext.text.length;
|
||||
while (attribsIter.hasNext())
|
||||
{
|
||||
var op = attribsIter.next();
|
||||
var nextIndex = textIndex + op.chars;
|
||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd))
|
||||
{
|
||||
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||
}
|
||||
textIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
var builder = Changeset.builder(oldText.length);
|
||||
|
||||
// assemble each line into the builder
|
||||
eachAttribRun(atext.attribs, function (start, end, attribs)
|
||||
{
|
||||
builder.insert(atext.text.substring(start, end), attribs);
|
||||
});
|
||||
|
||||
var lastNewlinePos = oldText.lastIndexOf('\n');
|
||||
if (lastNewlinePos < 0)
|
||||
{
|
||||
builder.remove(oldText.length - 1, 0);
|
||||
} else
|
||||
{
|
||||
builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
|
||||
builder.remove(oldText.length - lastNewlinePos - 1, 0);
|
||||
}
|
||||
|
||||
var changeset = builder.toString();
|
||||
|
||||
//append the changeset
|
||||
pad.appendRevision(changeset);
|
||||
//
|
||||
padMessage.updatePadClients(pad, function ()
|
||||
{
|
||||
});
|
||||
callback(null, null);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
var ERR = require("async-stacktrace");
|
||||
var db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
|
||||
|
|
|
@ -54,6 +54,21 @@ Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() {
|
|||
return this.head;
|
||||
};
|
||||
|
||||
Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
|
||||
return this.savedRevisions.length;
|
||||
};
|
||||
|
||||
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
|
||||
var savedRev = new Array();
|
||||
for(var rev in this.savedRevisions){
|
||||
savedRev.push(this.savedRevisions[rev].revNum);
|
||||
}
|
||||
savedRev.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
return savedRev;
|
||||
};
|
||||
|
||||
Pad.prototype.getPublicStatus = function getPublicStatus() {
|
||||
return this.publicStatus;
|
||||
};
|
||||
|
@ -135,7 +150,7 @@ Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) {
|
|||
Pad.prototype.getAllAuthors = function getAllAuthors() {
|
||||
var authors = [];
|
||||
|
||||
for(key in this.pool.numToAttrib)
|
||||
for(var key in this.pool.numToAttrib)
|
||||
{
|
||||
if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "")
|
||||
{
|
||||
|
@ -461,7 +476,6 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
|
|||
// if the pad exists, we should abort, unless forced.
|
||||
function(callback)
|
||||
{
|
||||
console.log("destinationID", destinationID, force);
|
||||
padManager.doesPadExists(destinationID, function (err, exists)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
@ -470,9 +484,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
|
|||
{
|
||||
if (!force)
|
||||
{
|
||||
console.log("erroring out without force");
|
||||
console.error("erroring out without force");
|
||||
callback(new customError("destinationID already exists","apierror"));
|
||||
console.log("erroring out without force - after");
|
||||
console.error("erroring out without force - after");
|
||||
return;
|
||||
}
|
||||
else // exists and forcing
|
||||
|
@ -521,12 +535,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
|
|||
function(callback)
|
||||
{
|
||||
var revHead = _this.head;
|
||||
//console.log(revHead);
|
||||
for(var i=0;i<=revHead;i++)
|
||||
{
|
||||
db.get("pad:"+sourceID+":revs:"+i, function (err, rev) {
|
||||
//console.log("HERE");
|
||||
|
||||
if (ERR(err, callback)) return;
|
||||
db.set("pad:"+destinationID+":revs:"+i, rev);
|
||||
});
|
||||
|
@ -538,10 +549,8 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
|
|||
function(callback)
|
||||
{
|
||||
var authorIDs = _this.getAllAuthors();
|
||||
|
||||
authorIDs.forEach(function (authorID)
|
||||
{
|
||||
console.log("authors");
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
|
||||
|
@ -555,7 +564,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
|
|||
if(destGroupID) db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
|
||||
// Initialize the new pad (will update the listAllPads cache)
|
||||
padManager.getPad(destinationID, null, callback)
|
||||
setTimeout(function(){
|
||||
padManager.getPad(destinationID, null, callback) // this runs too early.
|
||||
},10);
|
||||
}
|
||||
// series
|
||||
], function(err)
|
||||
|
@ -690,7 +701,7 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
|
|||
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){
|
||||
if(this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
|
||||
var ERR = require("async-stacktrace");
|
||||
var db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var authorManager = require("./AuthorManager");
|
||||
var padManager = require("./PadManager");
|
||||
|
|
|
@ -351,7 +351,15 @@ function listSessionsWithDBKey (dbkey, callback)
|
|||
{
|
||||
exports.getSessionInfo(sessionID, function(err, sessionInfo)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
if (err == "apierror: sessionID does not exist")
|
||||
{
|
||||
console.warn("Found bad session " + sessionID + " in " + dbkey + ".");
|
||||
}
|
||||
else if(ERR(err, callback))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sessions[sessionID] = sessionInfo;
|
||||
callback();
|
||||
});
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
|
||||
var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'),
|
||||
utils = require('ep_etherpad-lite/node_modules/connect/lib/utils'),
|
||||
Session = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/session'),
|
||||
db = require('ep_etherpad-lite/node/db/DB').db,
|
||||
log4js = require('ep_etherpad-lite/node_modules/log4js'),
|
||||
messageLogger = log4js.getLogger("SessionStore");
|
||||
|
|
|
@ -71,7 +71,7 @@ exports.begin_define_block = function (name) {
|
|||
}
|
||||
|
||||
exports.end_define_block = function () {
|
||||
content = exports.end_capture();
|
||||
var content = exports.end_capture();
|
||||
return content;
|
||||
}
|
||||
|
||||
|
|
|
@ -345,10 +345,109 @@ var version =
|
|||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
, "1.2.11":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getAttributePool" : ["padID"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getSavedRevisionsCount" : ["padID"]
|
||||
, "listSavedRevisions" : ["padID"]
|
||||
, "saveRevision" : ["padID", "rev"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "getPadID" : ["roID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
, "checkToken" : []
|
||||
, "getChatHistory" : ["padID"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
, "1.2.12":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getAttributePool" : ["padID"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getSavedRevisionsCount" : ["padID"]
|
||||
, "listSavedRevisions" : ["padID"]
|
||||
, "saveRevision" : ["padID", "rev"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "getPadID" : ["roID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
, "checkToken" : []
|
||||
, "appendChatMessage" : ["padID", "text", "authorID", "time"]
|
||||
, "getChatHistory" : ["padID"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
};
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.2.10';
|
||||
exports.latestApiVersion = '1.2.12';
|
||||
|
||||
// exports the versions so it can be used by the new Swagger endpoint
|
||||
exports.version = version;
|
||||
|
@ -404,6 +503,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
|
|||
|
||||
if(fields["apikey"] != apikey.trim())
|
||||
{
|
||||
res.statusCode = 401;
|
||||
res.send({code: 4, message: "no or wrong API Key", data: null});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
|
||||
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,8 +22,7 @@
|
|||
var ERR = require("async-stacktrace");
|
||||
var exporthtml = require("../utils/ExportHtml");
|
||||
var exporttxt = require("../utils/ExportTxt");
|
||||
var exportdokuwiki = require("../utils/ExportDokuWiki");
|
||||
var padManager = require("../db/PadManager");
|
||||
var exportEtherpad = require("../utils/ExportEtherpad");
|
||||
var async = require("async");
|
||||
var fs = require("fs");
|
||||
var settings = require('../utils/Settings');
|
||||
|
@ -54,14 +54,20 @@ exports.doExport = function(req, res, padId, type)
|
|||
// if fileName is set then set it to the padId, note that fileName is returned as an array.
|
||||
if(hookFileName.length) fileName = hookFileName;
|
||||
|
||||
|
||||
//tell the browser that this is a downloadable file
|
||||
res.attachment(fileName + "." + type);
|
||||
|
||||
//if this is a plain text export, we can do this directly
|
||||
// We have to over engineer this because tabs are stored as attributes and not plain text
|
||||
|
||||
if(type == "txt")
|
||||
if(type == "etherpad"){
|
||||
exportEtherpad.getPadRaw(padId, function(err, pad){
|
||||
if(!err){
|
||||
res.send(pad);
|
||||
// return;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if(type == "txt")
|
||||
{
|
||||
var txt;
|
||||
var randNum;
|
||||
|
@ -129,26 +135,6 @@ exports.doExport = function(req, res, padId, type)
|
|||
if(err && err != "stop") ERR(err);
|
||||
})
|
||||
}
|
||||
else if(type == 'dokuwiki')
|
||||
{
|
||||
var randNum;
|
||||
var srcFile, destFile;
|
||||
|
||||
async.series([
|
||||
//render the dokuwiki document
|
||||
function(callback)
|
||||
{
|
||||
exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki)
|
||||
{
|
||||
res.send(dokuwiki);
|
||||
callback("stop");
|
||||
});
|
||||
},
|
||||
], function(err)
|
||||
{
|
||||
if(err && err != "stop") throw err;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var html;
|
||||
|
@ -172,8 +158,12 @@ exports.doExport = function(req, res, padId, type)
|
|||
//if this is a html export, we can send this from here directly
|
||||
if(type == "html")
|
||||
{
|
||||
res.send(html);
|
||||
callback("stop");
|
||||
// do any final changes the plugin might want to make cake
|
||||
hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML){
|
||||
if(newHTML.length) html = newHTML;
|
||||
res.send(html);
|
||||
callback("stop");
|
||||
});
|
||||
}
|
||||
else //write the html export to a file
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
/*
|
||||
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
|
||||
* 2012 Iván Eixarch
|
||||
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -29,6 +30,7 @@ var ERR = require("async-stacktrace")
|
|||
, formidable = require('formidable')
|
||||
, os = require("os")
|
||||
, importHtml = require("../utils/ImportHtml")
|
||||
, importEtherpad = require("../utils/ImportEtherpad")
|
||||
, log4js = require("log4js")
|
||||
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
|
||||
|
@ -53,7 +55,8 @@ exports.doImport = function(req, res, padId)
|
|||
var srcFile, destFile
|
||||
, pad
|
||||
, text
|
||||
, importHandledByPlugin;
|
||||
, importHandledByPlugin
|
||||
, directDatabaseAccess;
|
||||
|
||||
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
|
||||
|
@ -83,7 +86,7 @@ exports.doImport = function(req, res, padId)
|
|||
//this allows us to accept source code files like .c or .java
|
||||
function(callback) {
|
||||
var fileEnding = path.extname(srcFile).toLowerCase()
|
||||
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm"]
|
||||
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad"]
|
||||
, fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1);
|
||||
|
||||
//if the file ending is known, continue as normal
|
||||
|
@ -92,9 +95,14 @@ exports.doImport = function(req, res, padId)
|
|||
}
|
||||
//we need to rename this file with a .txt ending
|
||||
else {
|
||||
var oldSrcFile = srcFile;
|
||||
srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
|
||||
fs.rename(oldSrcFile, srcFile, callback);
|
||||
if(settings.allowUnknownFileEnds === true){
|
||||
var oldSrcFile = srcFile;
|
||||
srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
|
||||
fs.rename(oldSrcFile, srcFile, callback);
|
||||
}else{
|
||||
console.warn("Not allowing unknown file type to be imported", fileEnding);
|
||||
callback("uploadFailed");
|
||||
}
|
||||
}
|
||||
},
|
||||
function(callback){
|
||||
|
@ -111,11 +119,38 @@ exports.doImport = function(req, res, padId)
|
|||
}
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
var fileEnding = path.extname(srcFile).toLowerCase()
|
||||
var fileIsEtherpad = (fileEnding === ".etherpad");
|
||||
|
||||
if(fileIsEtherpad){
|
||||
// we do this here so we can see if the pad has quit ea few edits
|
||||
padManager.getPad(padId, function(err, _pad){
|
||||
var headCount = _pad.head;
|
||||
if(headCount >= 10){
|
||||
apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this")
|
||||
return callback("padHasData");
|
||||
}else{
|
||||
fs.readFile(srcFile, "utf8", function(err, _text){
|
||||
directDatabaseAccess = true;
|
||||
importEtherpad.setPadRaw(padId, _text, function(err){
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
},
|
||||
//convert file to html
|
||||
function(callback) {
|
||||
if(!importHandledByPlugin){
|
||||
if(!importHandledByPlugin || !directDatabaseAccess){
|
||||
var fileEnding = path.extname(srcFile).toLowerCase();
|
||||
var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
|
||||
var fileIsTXT = (fileEnding === ".txt");
|
||||
if (fileIsTXT) abiword = false; // Don't use abiword for text files
|
||||
// See https://github.com/ether/etherpad-lite/issues/2572
|
||||
if (abiword && !fileIsHTML) {
|
||||
abiword.convertFile(srcFile, destFile, "htm", function(err) {
|
||||
//catch convert errors
|
||||
|
@ -136,24 +171,28 @@ exports.doImport = function(req, res, padId)
|
|||
},
|
||||
|
||||
function(callback) {
|
||||
if (!abiword) {
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
fs.readFile(destFile, function(err, buf) {
|
||||
if (err) throw err;
|
||||
var isAscii = true;
|
||||
// Check if there are only ascii chars in the uploaded file
|
||||
for (var i=0, len=buf.length; i<len; i++) {
|
||||
if (buf[i] > 240) {
|
||||
isAscii=false;
|
||||
break;
|
||||
if (!abiword){
|
||||
if(!directDatabaseAccess) {
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
fs.readFile(destFile, function(err, buf) {
|
||||
if (err) throw err;
|
||||
var isAscii = true;
|
||||
// Check if there are only ascii chars in the uploaded file
|
||||
for (var i=0, len=buf.length; i<len; i++) {
|
||||
if (buf[i] > 240) {
|
||||
isAscii=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAscii) {
|
||||
callback();
|
||||
} else {
|
||||
callback("uploadFailed");
|
||||
}
|
||||
});
|
||||
if (isAscii) {
|
||||
callback();
|
||||
} else {
|
||||
callback("uploadFailed");
|
||||
}
|
||||
});
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
|
@ -170,66 +209,101 @@ exports.doImport = function(req, res, padId)
|
|||
|
||||
//read the text
|
||||
function(callback) {
|
||||
fs.readFile(destFile, "utf8", function(err, _text){
|
||||
if(ERR(err, callback)) return;
|
||||
text = _text;
|
||||
// Title needs to be stripped out else it appends it to the pad..
|
||||
text = text.replace("<title>", "<!-- <title>");
|
||||
text = text.replace("</title>","</title>-->");
|
||||
if(!directDatabaseAccess){
|
||||
fs.readFile(destFile, "utf8", function(err, _text){
|
||||
if(ERR(err, callback)) return;
|
||||
text = _text;
|
||||
// Title needs to be stripped out else it appends it to the pad..
|
||||
text = text.replace("<title>", "<!-- <title>");
|
||||
text = text.replace("</title>","</title>-->");
|
||||
|
||||
//node on windows has a delay on releasing of the file lock.
|
||||
//We add a 100ms delay to work around this
|
||||
if(os.type().indexOf("Windows") > -1){
|
||||
setTimeout(function() {callback();}, 100);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
//node on windows has a delay on releasing of the file lock.
|
||||
//We add a 100ms delay to work around this
|
||||
if(os.type().indexOf("Windows") > -1){
|
||||
setTimeout(function() {callback();}, 100);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
//change text of the pad and broadcast the changeset
|
||||
function(callback) {
|
||||
var fileEnding = path.extname(srcFile).toLowerCase();
|
||||
if (abiword || fileEnding == ".htm" || fileEnding == ".html") {
|
||||
try{
|
||||
importHtml.setPadHTML(pad, text);
|
||||
}catch(e){
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
if(!directDatabaseAccess){
|
||||
var fileEnding = path.extname(srcFile).toLowerCase();
|
||||
if (abiword || fileEnding == ".htm" || fileEnding == ".html") {
|
||||
importHtml.setPadHTML(pad, text, function(e){
|
||||
if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
});
|
||||
} else {
|
||||
pad.setText(text);
|
||||
}
|
||||
} else {
|
||||
pad.setText(text);
|
||||
}
|
||||
padMessageHandler.updatePadClients(pad, callback);
|
||||
|
||||
// Load the Pad into memory then brodcast updates to all clients
|
||||
padManager.unloadPad(padId);
|
||||
padManager.getPad(padId, function(err, _pad){
|
||||
var pad = _pad;
|
||||
padManager.unloadPad(padId);
|
||||
// direct Database Access means a pad user should perform a switchToPad
|
||||
// and not attempt to recieve updated pad data..
|
||||
if(!directDatabaseAccess){
|
||||
padMessageHandler.updatePadClients(pad, function(){
|
||||
callback();
|
||||
});
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
//clean up temporary files
|
||||
function(callback) {
|
||||
//for node < 0.7 compatible
|
||||
var fileExists = fs.exists || path.exists;
|
||||
async.parallel([
|
||||
function(callback){
|
||||
fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); });
|
||||
},
|
||||
function(callback){
|
||||
fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); });
|
||||
}
|
||||
], callback);
|
||||
if(!directDatabaseAccess){
|
||||
//for node < 0.7 compatible
|
||||
var fileExists = fs.exists || path.exists;
|
||||
async.parallel([
|
||||
function(callback){
|
||||
fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); });
|
||||
},
|
||||
function(callback){
|
||||
fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); });
|
||||
}
|
||||
], callback);
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
], function(err) {
|
||||
|
||||
var status = "ok";
|
||||
|
||||
//check for known errors and replace the status
|
||||
if(err == "uploadFailed" || err == "convertFailed")
|
||||
if(err == "uploadFailed" || err == "convertFailed" || err == "padHasData")
|
||||
{
|
||||
status = err;
|
||||
err = null;
|
||||
}
|
||||
|
||||
ERR(err);
|
||||
|
||||
|
||||
//close the connection
|
||||
res.send("<head><script type='text/javascript' src='../../static/js/jquery.js'></script><script type='text/javascript' src='../../static/js/jquery_browser.js'></script></head><script>$(window).load(function(){if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.parent.padimpexp.handleFrameCall('" + status + "');})</script>", 200);
|
||||
res.send(
|
||||
"<head> \
|
||||
<script type='text/javascript' src='../../static/js/jquery.js'></script> \
|
||||
</head> \
|
||||
<script> \
|
||||
$(window).load(function(){ \
|
||||
if(navigator.userAgent.indexOf('MSIE') === -1){ \
|
||||
document.domain = document.domain; \
|
||||
} \
|
||||
var impexp = window.parent.padimpexp.handleFrameCall('" + directDatabaseAccess +"', '" + status + "'); \
|
||||
}) \
|
||||
</script>"
|
||||
, 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ var _ = require('underscore');
|
|||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var channels = require("channels");
|
||||
var stats = require('../stats');
|
||||
var remoteAddress = require("../utils/RemoteAddress").remoteAddress;
|
||||
|
||||
/**
|
||||
* A associative array that saves informations about a session
|
||||
|
@ -93,8 +94,18 @@ exports.handleConnect = function(client)
|
|||
*/
|
||||
exports.kickSessionsFromPad = function(padID)
|
||||
{
|
||||
if(typeof socketio.sockets['clients'] !== 'function')
|
||||
return;
|
||||
|
||||
//skip if there is nobody on this pad
|
||||
if(socketio.sockets.clients(padID).length == 0)
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
if(roomClients.length == 0)
|
||||
return;
|
||||
|
||||
//disconnect everyone from this pad
|
||||
|
@ -115,14 +126,16 @@ exports.handleDisconnect = function(client)
|
|||
//if this connection was already etablished with a handshake, send a disconnect message to the others
|
||||
if(session && session.author)
|
||||
{
|
||||
client.get('remoteAddress', function(er, ip) {
|
||||
//Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad')
|
||||
})
|
||||
// Get the IP address from our persistant object
|
||||
var ip = remoteAddress[client.id];
|
||||
|
||||
// Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad')
|
||||
|
||||
//get the author color out of the db
|
||||
authorManager.getAuthorColorId(session.author, function(err, color)
|
||||
|
@ -220,6 +233,8 @@ exports.handleMessage = function(client, message)
|
|||
} else {
|
||||
messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type);
|
||||
}
|
||||
} else if(message.type == "SWITCH_TO_PAD") {
|
||||
handleSwitchToPad(client, message);
|
||||
} else {
|
||||
messageLogger.warn("Dropped message, unknown Message Type " + message.type);
|
||||
}
|
||||
|
@ -233,18 +248,7 @@ exports.handleMessage = function(client, message)
|
|||
{
|
||||
// client tried to auth for the first time (first msg from the client)
|
||||
if(message.type == "CLIENT_READY") {
|
||||
// Remember this information since we won't
|
||||
// have the cookie in further socket.io messages.
|
||||
// This information will be used to check if
|
||||
// the sessionId of this connection is still valid
|
||||
// since it could have been deleted by the API.
|
||||
sessioninfos[client.id].auth =
|
||||
{
|
||||
sessionID: message.sessionID,
|
||||
padID: message.padId,
|
||||
token : message.token,
|
||||
password: message.password
|
||||
};
|
||||
createSessionInfo(client, message);
|
||||
}
|
||||
|
||||
// Note: message.sessionID is an entirely different kind of
|
||||
|
@ -253,11 +257,10 @@ exports.handleMessage = function(client, message)
|
|||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
|
||||
// FIXME: A message might arrive but wont have an auth object, this is obviously bad so we should deny it
|
||||
// Simulate using the load testing tool
|
||||
if(!sessioninfos[client.id].auth){
|
||||
console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.")
|
||||
callback();
|
||||
return;
|
||||
}else{
|
||||
var auth = sessioninfos[client.id].auth;
|
||||
var checkAccessCallback = function(err, statusObject)
|
||||
|
@ -493,14 +496,19 @@ function handleSuggestUserName(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
var padId = sessioninfos[client.id].padId,
|
||||
clients = socketio.sockets.clients(padId);
|
||||
var padId = sessioninfos[client.id].padId;
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[padId];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
//search the author and send him this message
|
||||
for(var i = 0; i < clients.length; i++) {
|
||||
var session = sessioninfos[clients[i].id];
|
||||
for(var i = 0; i < roomClients.length; i++) {
|
||||
var session = sessioninfos[roomClients[i].id];
|
||||
if(session && session.author == message.data.payload.unnamedId) {
|
||||
clients[i].json.send(message);
|
||||
roomClients[i].json.send(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -648,12 +656,17 @@ function handleUserChanges(data, cb)
|
|||
, op
|
||||
while(iterator.hasNext()) {
|
||||
op = iterator.next()
|
||||
if(op.opcode != '+') continue;
|
||||
|
||||
//+ can add text with attribs
|
||||
//= can change or add attribs
|
||||
//- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool
|
||||
|
||||
op.attribs.split('*').forEach(function(attr) {
|
||||
if(!attr) return
|
||||
attr = wireApool.getAttrib(attr)
|
||||
if(!attr) return
|
||||
if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset);
|
||||
//the empty author is used in the clearAuthorship functionality so this should be the only exception
|
||||
if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -694,6 +707,14 @@ function handleUserChanges(data, cb)
|
|||
// and can be applied after "c".
|
||||
try
|
||||
{
|
||||
// a changeset can be based on an old revision with the same changes in it
|
||||
// prevent eplite from accepting it TODO: better send the client a NEW_CHANGES
|
||||
// of that revision
|
||||
if(baseRev+1 == r && c == changeset) {
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
stats.meter('failedChangesets').mark();
|
||||
return callback(new Error("Won't apply USER_CHANGES, because it contains an already accepted changeset"));
|
||||
}
|
||||
changeset = Changeset.follow(c, changeset, false, apool);
|
||||
}catch(e){
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
|
@ -724,7 +745,16 @@ function handleUserChanges(data, cb)
|
|||
return callback(new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length));
|
||||
}
|
||||
|
||||
pad.appendRevision(changeset, thisSession.author);
|
||||
try
|
||||
{
|
||||
pad.appendRevision(changeset, thisSession.author);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
stats.meter('failedChangesets').mark();
|
||||
return callback(e)
|
||||
}
|
||||
|
||||
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
|
||||
if (correctionChangeset) {
|
||||
|
@ -753,7 +783,13 @@ function handleUserChanges(data, cb)
|
|||
exports.updatePadClients = function(pad, callback)
|
||||
{
|
||||
//skip this step if noone is on this pad
|
||||
var roomClients = socketio.sockets.clients(pad.id);
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
if(roomClients.length==0)
|
||||
return callback();
|
||||
|
||||
|
@ -766,10 +802,8 @@ exports.updatePadClients = function(pad, callback)
|
|||
var revCache = {};
|
||||
|
||||
//go trough all sessions on this pad
|
||||
async.forEach(roomClients, function(client, callback)
|
||||
{
|
||||
async.forEach(roomClients, function(client, callback){
|
||||
var sid = client.id;
|
||||
|
||||
//https://github.com/caolan/async#whilst
|
||||
//send them all new changesets
|
||||
async.whilst(
|
||||
|
@ -816,10 +850,10 @@ exports.updatePadClients = function(pad, callback)
|
|||
|
||||
client.json.send(wireMsg);
|
||||
}
|
||||
|
||||
sessioninfos[sid].time = currentTime;
|
||||
sessioninfos[sid].rev = r;
|
||||
|
||||
if(sessioninfos[sid]){
|
||||
sessioninfos[sid].time = currentTime;
|
||||
sessioninfos[sid].rev = r;
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
], callback);
|
||||
|
@ -875,6 +909,48 @@ function _correctMarkersInPad(atext, apool) {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
function handleSwitchToPad(client, message)
|
||||
{
|
||||
// clear the session and leave the room
|
||||
var currentSession = sessioninfos[client.id];
|
||||
var padId = currentSession.padId;
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[padId];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < roomClients.length; i++) {
|
||||
var sinfo = sessioninfos[roomClients[i].id];
|
||||
if(sinfo && sinfo.author == currentSession.author) {
|
||||
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
||||
sessioninfos[roomClients[i].id] = {};
|
||||
roomClients[i].leave(padId);
|
||||
}
|
||||
}
|
||||
|
||||
// start up the new pad
|
||||
createSessionInfo(client, message);
|
||||
handleClientReady(client, message);
|
||||
}
|
||||
|
||||
function createSessionInfo(client, message)
|
||||
{
|
||||
// Remember this information since we won't
|
||||
// have the cookie in further socket.io messages.
|
||||
// This information will be used to check if
|
||||
// the sessionId of this connection is still valid
|
||||
// since it could have been deleted by the API.
|
||||
sessioninfos[client.id].auth =
|
||||
{
|
||||
sessionID: message.sessionID,
|
||||
padID: message.padId,
|
||||
token : message.token,
|
||||
password: message.password
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
|
||||
* and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
|
||||
|
@ -998,6 +1074,11 @@ function handleClientReady(client, message)
|
|||
{
|
||||
authorManager.getAuthor(authorId, function(err, author)
|
||||
{
|
||||
if(!author && !err)
|
||||
{
|
||||
messageLogger.error("There is no author for authorId:", authorId);
|
||||
return callback();
|
||||
}
|
||||
if(ERR(err, callback)) return;
|
||||
historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients)
|
||||
callback();
|
||||
|
@ -1015,7 +1096,13 @@ function handleClientReady(client, message)
|
|||
return callback();
|
||||
|
||||
//Check if this author is already on the pad, if yes, kick the other sessions!
|
||||
var roomClients = socketio.sockets.clients(padIds.padId);
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < roomClients.length; i++) {
|
||||
var sinfo = sessioninfos[roomClients[i].id];
|
||||
if(sinfo && sinfo.author == author) {
|
||||
|
@ -1032,19 +1119,19 @@ function handleClientReady(client, message)
|
|||
sessioninfos[client.id].readonly = padIds.readonly;
|
||||
|
||||
//Log creation/(re-)entering of a pad
|
||||
client.get('remoteAddress', function(er, ip) {
|
||||
//Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
var ip = remoteAddress[client.id];
|
||||
|
||||
if(pad.head > 0) {
|
||||
accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
|
||||
}
|
||||
else if(pad.head == 0) {
|
||||
accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad');
|
||||
}
|
||||
})
|
||||
//Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
if(pad.head > 0) {
|
||||
accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
|
||||
}
|
||||
else if(pad.head == 0) {
|
||||
accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad');
|
||||
}
|
||||
|
||||
//If this is a reconnect, we don't have to send the client the ClientVars again
|
||||
if(message.reconnect == true)
|
||||
|
@ -1165,7 +1252,14 @@ function handleClientReady(client, message)
|
|||
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
|
||||
|
||||
//Run trough all sessions of this pad
|
||||
async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback)
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
async.forEach(roomClients, function(roomClient, callback)
|
||||
{
|
||||
var author;
|
||||
|
||||
|
@ -1540,10 +1634,15 @@ function composePadChangesets(padId, startNum, endNum, callback)
|
|||
changeset = changesets[startNum];
|
||||
var pool = pad.apool();
|
||||
|
||||
for(var r=startNum+1;r<endNum;r++)
|
||||
{
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
try {
|
||||
for(var r=startNum+1;r<endNum;r++) {
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
}
|
||||
} catch(e){
|
||||
// r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3
|
||||
console.warn("failed to compose cs in pad:",padId," startrev:",startNum," current rev:",r);
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
|
@ -1561,8 +1660,16 @@ function composePadChangesets(padId, startNum, endNum, callback)
|
|||
* Get the number of users in a pad
|
||||
*/
|
||||
exports.padUsersCount = function (padID, callback) {
|
||||
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
padUsersCount: socketio.sockets.clients(padID).length
|
||||
padUsersCount: roomClients.length
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1572,7 +1679,14 @@ exports.padUsersCount = function (padID, callback) {
|
|||
exports.padUsers = function (padID, callback) {
|
||||
var result = [];
|
||||
|
||||
async.forEach(socketio.sockets.clients(padID), function(roomClient, callback) {
|
||||
var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
|
||||
if (room) {
|
||||
for (var id in room) {
|
||||
roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
|
||||
}
|
||||
}
|
||||
|
||||
async.forEach(roomClients, function(roomClient, callback) {
|
||||
var s = sessioninfos[roomClient.id];
|
||||
if(s) {
|
||||
authorManager.getAuthor(s.author, function(err, author) {
|
||||
|
|
|
@ -24,6 +24,7 @@ var log4js = require('log4js');
|
|||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
var readOnlyManager = require("../db/ReadOnlyManager");
|
||||
var remoteAddress = require("../utils/RemoteAddress").remoteAddress;
|
||||
var settings = require('../utils/Settings');
|
||||
|
||||
/**
|
||||
|
@ -56,11 +57,15 @@ exports.setSocketIO = function(_socket) {
|
|||
|
||||
socket.sockets.on('connection', function(client)
|
||||
{
|
||||
|
||||
// Broken: See http://stackoverflow.com/questions/4647348/send-message-to-specific-client-with-socket-io-and-node-js
|
||||
// Fixed by having a persistant object, ideally this would actually be in the database layer
|
||||
// TODO move to database layer
|
||||
if(settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined){
|
||||
client.set('remoteAddress', client.handshake.headers['x-forwarded-for']);
|
||||
remoteAddress[client.id] = client.handshake.headers['x-forwarded-for'];
|
||||
}
|
||||
else{
|
||||
client.set('remoteAddress', client.handshake.address.address);
|
||||
remoteAddress[client.id] = client.handshake.address;
|
||||
}
|
||||
var clientAuthorized = false;
|
||||
|
||||
|
|
|
@ -10,24 +10,11 @@ var server;
|
|||
var serverName;
|
||||
|
||||
exports.createServer = function () {
|
||||
//try to get the git version
|
||||
var version = "";
|
||||
try
|
||||
{
|
||||
var rootPath = path.resolve(npm.dir, '..');
|
||||
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
|
||||
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
|
||||
version = fs.readFileSync(refPath, "utf-8");
|
||||
version = version.substring(0, 7);
|
||||
console.log("Your Etherpad git version is " + version);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.warn("Can't get git version for server header\n" + e.message)
|
||||
}
|
||||
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
|
||||
|
||||
serverName = "Etherpad " + version + " (http://etherpad.org)";
|
||||
serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)";
|
||||
|
||||
console.log("Your Etherpad version is " + settings.getEpVersion() + " (" + settings.getGitCommit() + ")");
|
||||
|
||||
exports.restartServer();
|
||||
|
||||
|
@ -38,7 +25,6 @@ exports.createServer = function () {
|
|||
else{
|
||||
console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.restartServer = function () {
|
||||
|
@ -56,7 +42,7 @@ exports.restartServer = function () {
|
|||
console.log( "SSL -- server key file: " + settings.ssl.key );
|
||||
console.log( "SSL -- Certificate Authority's certificate file: " + settings.ssl.cert );
|
||||
|
||||
options = {
|
||||
var options = {
|
||||
key: fs.readFileSync( settings.ssl.key ),
|
||||
cert: fs.readFileSync( settings.ssl.cert )
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
var path = require('path');
|
||||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
|
||||
var _ = require('underscore');
|
||||
var semver = require('semver');
|
||||
var async = require('async');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin/plugins', function(req, res) {
|
||||
|
@ -14,18 +13,25 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
search_results: {},
|
||||
errors: [],
|
||||
};
|
||||
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) );
|
||||
});
|
||||
args.app.get('/admin/plugins/info', function(req, res) {
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) );
|
||||
var gitCommit = settings.getGitCommit();
|
||||
var epVersion = settings.getEpVersion();
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html",
|
||||
{
|
||||
gitCommit: gitCommit,
|
||||
epVersion: epVersion
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
var io = args.io.of("/pluginfw/installer");
|
||||
io.on('connection', function (socket) {
|
||||
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
|
||||
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("getInstalled", function (query) {
|
||||
// send currently installed plugins
|
||||
|
@ -85,7 +91,7 @@ exports.socketio = function (hook_name, args, cb) {
|
|||
socket.on("install", function (plugin_name) {
|
||||
installer.install(plugin_name, function (er) {
|
||||
if(er) console.warn(er)
|
||||
socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null});
|
||||
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -107,4 +113,4 @@ function sortPluginList(plugins, property, /*ASC?*/dir) {
|
|||
// a must be equal to b
|
||||
return 0;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
var path = require('path');
|
||||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var fs = require('fs');
|
||||
|
||||
|
@ -22,7 +20,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
exports.socketio = function (hook_name, args, cb) {
|
||||
var io = args.io.of("/settings");
|
||||
io.on('connection', function (socket) {
|
||||
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
|
||||
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("load", function (query) {
|
||||
fs.readFile('settings.json', 'utf8', function (err,data) {
|
||||
|
|
|
@ -5,7 +5,7 @@ var importHandler = require('../../handler/ImportHandler');
|
|||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) {
|
||||
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
|
||||
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
|
||||
//send a 404 if we don't support this filetype
|
||||
if (types.indexOf(req.params.type) == -1) {
|
||||
next();
|
||||
|
|
|
@ -10,7 +10,6 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
{
|
||||
var html;
|
||||
var padId;
|
||||
var pad;
|
||||
|
||||
async.series([
|
||||
//translate the read only pad to a padId
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var log4js = require('log4js');
|
||||
var socketio = require('socket.io');
|
||||
var settings = require('../../utils/Settings');
|
||||
var socketio = require('socket.io');
|
||||
var socketIORouter = require("../../handler/SocketIORouter");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess");
|
||||
|
@ -11,14 +10,25 @@ var connect = require('connect');
|
|||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
//init socket.io and redirect all requests to the MessageHandler
|
||||
var io = socketio.listen(args.server);
|
||||
// there shouldn't be a browser that isn't compatible to all
|
||||
// transports in this list at once
|
||||
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
|
||||
var io = socketio({
|
||||
transports: settings.socketTransportProtocols
|
||||
}).listen(args.server);
|
||||
|
||||
/* Require an express session cookie to be present, and load the
|
||||
* session. See http://www.danielbaulig.de/socket-ioexpress for more
|
||||
* info */
|
||||
io.set('authorization', function (data, accept) {
|
||||
if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
|
||||
|
||||
io.use(function(socket, accept) {
|
||||
var data = socket.request;
|
||||
// Use a setting if we want to allow load Testing
|
||||
if(!data.headers.cookie && settings.loadTest){
|
||||
accept(null, true);
|
||||
}else{
|
||||
if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
|
||||
}
|
||||
// Use connect's cookie parser, because it knows how to parse signed cookies
|
||||
connect.cookieParser(webaccess.secret)(data, {}, function(err){
|
||||
if(err) {
|
||||
|
@ -36,35 +46,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
});
|
||||
});
|
||||
|
||||
// there shouldn't be a browser that isn't compatible to all
|
||||
// transports in this list at once
|
||||
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
|
||||
io.set('transports', settings.socketTransportProtocols );
|
||||
|
||||
var socketIOLogger = log4js.getLogger("socket.io");
|
||||
io.set('logger', {
|
||||
debug: function (str)
|
||||
{
|
||||
socketIOLogger.debug.apply(socketIOLogger, arguments);
|
||||
},
|
||||
info: function (str)
|
||||
{
|
||||
socketIOLogger.info.apply(socketIOLogger, arguments);
|
||||
},
|
||||
warn: function (str)
|
||||
{
|
||||
socketIOLogger.warn.apply(socketIOLogger, arguments);
|
||||
},
|
||||
error: function (str)
|
||||
{
|
||||
socketIOLogger.error.apply(socketIOLogger, arguments);
|
||||
},
|
||||
});
|
||||
// var socketIOLogger = log4js.getLogger("socket.io");
|
||||
// Debug logging now has to be set at an environment level, this is stupid.
|
||||
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
|
||||
// This debug logging environment is set in Settings.js
|
||||
|
||||
//minify socket.io javascript
|
||||
if(settings.minify)
|
||||
io.enable('browser client minification');
|
||||
|
||||
// Due to a shitty decision by the SocketIO team minification is
|
||||
// no longer available, details available at:
|
||||
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
|
||||
// if(settings.minify) io.enable('browser client minification');
|
||||
|
||||
//Initalize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent("pad", padMessageHandler);
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
var path = require('path');
|
||||
var minify = require('../../utils/Minify');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
var CachingMiddleware = require('../../utils/caching_middleware');
|
||||
var settings = require("../../utils/Settings");
|
||||
var Yajsml = require('yajsml');
|
||||
var fs = require("fs");
|
||||
var ERR = require("async-stacktrace");
|
||||
var Yajsml = require('etherpad-yajsml');
|
||||
var _ = require("underscore");
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
var log4js = require('log4js');
|
||||
var express = require('express');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
var apiCaller = require('./apicalls').apiCaller;
|
||||
|
@ -285,6 +284,10 @@ var API = {
|
|||
}
|
||||
},
|
||||
"response": {"chatHead":{"type":"Message"}}
|
||||
},
|
||||
"appendChatMessage": {
|
||||
"func": "appendChatMessage",
|
||||
"description": "appends a chat message"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -356,7 +359,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
args.app.use(basePath, subpath);
|
||||
|
||||
swagger.setAppHandler(subpath);
|
||||
//hack!
|
||||
var swagger_temp = swagger
|
||||
swagger = swagger.createNew(subpath);
|
||||
swagger.params = swagger_temp.params
|
||||
swagger.queryParam = swagger_temp.queryParam
|
||||
swagger.pathParam = swagger_temp.pathParam
|
||||
swagger.bodyParam = swagger_temp.bodyParam
|
||||
swagger.formParam = swagger_temp.formParam
|
||||
swagger.headerParam = swagger_temp.headerParam
|
||||
swagger.error = swagger_temp.error
|
||||
//swagger.setAppHandler(subpath);
|
||||
|
||||
swagger.addModels(swaggerModels);
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
});
|
||||
|
||||
|
||||
// path.join seems to normalize by default, but we'll just be explicit
|
||||
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
|
||||
|
||||
var url2FilePath = function(url){
|
||||
var subPath = url.substr("/tests/frontend".length);
|
||||
if (subPath == ""){
|
||||
|
@ -30,8 +34,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
}
|
||||
subPath = subPath.split("?")[0];
|
||||
|
||||
var filePath = path.normalize(npm.root + "/../tests/frontend/")
|
||||
filePath += subPath.replace("..", "");
|
||||
var filePath = path.normalize(path.join(rootTestFolder, subPath));
|
||||
// make sure we jail the paths to the test folder, otherwise serve index
|
||||
if (filePath.indexOf(rootTestFolder) !== 0) {
|
||||
filePath = path.join(rootTestFolder, "index.html");
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ var express = require('express');
|
|||
var log4js = require('log4js');
|
||||
var httpLogger = log4js.getLogger("http");
|
||||
var settings = require('../../utils/Settings');
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var ueberStore = require('../../db/SessionStore');
|
||||
var stats = require('ep_etherpad-lite/node/stats')
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
var languages = require('languages4translatewiki')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, express = require('express')
|
||||
, _ = require('underscore')
|
||||
, npm = require('npm')
|
||||
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins.js').plugins
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var util = require('util');
|
||||
var spawn = require('child_process').spawn;
|
||||
var async = require("async");
|
||||
var settings = require("./Settings");
|
||||
|
|
|
@ -1,350 +0,0 @@
|
|||
/**
|
||||
* Copyright 2011 Adrian Lang
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
|
||||
function getPadDokuWiki(pad, revNum, callback)
|
||||
{
|
||||
var atext = pad.atext;
|
||||
var dokuwiki;
|
||||
async.waterfall([
|
||||
// fetch revision atext
|
||||
|
||||
|
||||
function (callback)
|
||||
{
|
||||
if (revNum != undefined)
|
||||
{
|
||||
pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
|
||||
{
|
||||
atext = revisionAtext;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
|
||||
// convert atext to dokuwiki text
|
||||
|
||||
function (callback)
|
||||
{
|
||||
dokuwiki = getDokuWikiFromAtext(pad, atext);
|
||||
callback(null);
|
||||
}],
|
||||
// run final callback
|
||||
|
||||
|
||||
function (err)
|
||||
{
|
||||
callback(err, dokuwiki);
|
||||
});
|
||||
}
|
||||
|
||||
function getDokuWikiFromAtext(pad, atext)
|
||||
{
|
||||
var apool = pad.apool();
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
|
||||
var tags = ['======', '=====', '**', '//', '__', 'del>'];
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
var anumMap = {};
|
||||
|
||||
props.forEach(function (propName, i)
|
||||
{
|
||||
var propTrueNum = apool.putAttrib([propName, true], true);
|
||||
if (propTrueNum >= 0)
|
||||
{
|
||||
anumMap[propTrueNum] = i;
|
||||
}
|
||||
});
|
||||
|
||||
function getLineDokuWiki(text, attribs)
|
||||
{
|
||||
var propVals = [false, false, false];
|
||||
var ENTER = 1;
|
||||
var STAY = 2;
|
||||
var LEAVE = 0;
|
||||
|
||||
// Use order of tags (b/i/u) as order of nesting, for simplicity
|
||||
// and decent nesting. For example,
|
||||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
var taker = Changeset.stringIterator(text);
|
||||
var assem = Changeset.stringAssembler();
|
||||
|
||||
function emitOpenTag(i)
|
||||
{
|
||||
if (tags[i].indexOf('>') !== -1) {
|
||||
assem.append('<');
|
||||
}
|
||||
assem.append(tags[i]);
|
||||
}
|
||||
|
||||
function emitCloseTag(i)
|
||||
{
|
||||
if (tags[i].indexOf('>') !== -1) {
|
||||
assem.append('</');
|
||||
}
|
||||
assem.append(tags[i]);
|
||||
}
|
||||
|
||||
var urls = _findURLs(text);
|
||||
|
||||
var idx = 0;
|
||||
|
||||
function processNextChars(numChars)
|
||||
{
|
||||
if (numChars <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
idx += numChars;
|
||||
|
||||
while (iter.hasNext())
|
||||
{
|
||||
var o = iter.next();
|
||||
var propChanged = false;
|
||||
Changeset.eachAttribNumber(o.attribs, function (a)
|
||||
{
|
||||
if (a in anumMap)
|
||||
{
|
||||
var i = anumMap[a]; // i = 0 => bold, etc.
|
||||
if (!propVals[i])
|
||||
{
|
||||
propVals[i] = ENTER;
|
||||
propChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
propVals[i] = STAY;
|
||||
}
|
||||
}
|
||||
});
|
||||
for (var i = 0; i < propVals.length; i++)
|
||||
{
|
||||
if (propVals[i] === true)
|
||||
{
|
||||
propVals[i] = LEAVE;
|
||||
propChanged = true;
|
||||
}
|
||||
else if (propVals[i] === STAY)
|
||||
{
|
||||
propVals[i] = true; // set it back
|
||||
}
|
||||
}
|
||||
// now each member of propVal is in {false,LEAVE,ENTER,true}
|
||||
// according to what happens at start of span
|
||||
if (propChanged)
|
||||
{
|
||||
// leaving bold (e.g.) also leaves italics, etc.
|
||||
var left = false;
|
||||
for (var i = 0; i < propVals.length; i++)
|
||||
{
|
||||
var v = propVals[i];
|
||||
if (!left)
|
||||
{
|
||||
if (v === LEAVE)
|
||||
{
|
||||
left = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v === true)
|
||||
{
|
||||
propVals[i] = STAY; // tag will be closed and re-opened
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = propVals.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (propVals[i] === LEAVE)
|
||||
{
|
||||
emitCloseTag(i);
|
||||
propVals[i] = false;
|
||||
}
|
||||
else if (propVals[i] === STAY)
|
||||
{
|
||||
emitCloseTag(i);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < propVals.length; i++)
|
||||
{
|
||||
if (propVals[i] === ENTER || propVals[i] === STAY)
|
||||
{
|
||||
emitOpenTag(i);
|
||||
propVals[i] = true;
|
||||
}
|
||||
}
|
||||
// propVals is now all {true,false} again
|
||||
} // end if (propChanged)
|
||||
var chars = o.chars;
|
||||
if (o.lines)
|
||||
{
|
||||
chars--; // exclude newline at end of line, if present
|
||||
}
|
||||
var s = taker.take(chars);
|
||||
|
||||
assem.append(_escapeDokuWiki(s));
|
||||
} // end iteration over spans in line
|
||||
for (var i = propVals.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (propVals[i])
|
||||
{
|
||||
emitCloseTag(i);
|
||||
propVals[i] = false;
|
||||
}
|
||||
}
|
||||
} // end processNextChars
|
||||
if (urls)
|
||||
{
|
||||
urls.forEach(function (urlData)
|
||||
{
|
||||
var startIndex = urlData[0];
|
||||
var url = urlData[1];
|
||||
var urlLength = url.length;
|
||||
processNextChars(startIndex - idx);
|
||||
assem.append('[[');
|
||||
|
||||
// Do not use processNextChars since a link does not contain syntax and
|
||||
// needs no escaping
|
||||
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + urlLength));
|
||||
idx += urlLength;
|
||||
assem.append(taker.take(iter.next().chars));
|
||||
|
||||
assem.append(']]');
|
||||
});
|
||||
}
|
||||
processNextChars(text.length - idx);
|
||||
|
||||
return assem.toString() + "\n";
|
||||
} // end getLineDokuWiki
|
||||
var pieces = [];
|
||||
|
||||
for (var i = 0; i < textLines.length; i++)
|
||||
{
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
var lineContent = getLineDokuWiki(line.text, line.aline);
|
||||
|
||||
if (line.listLevel && lineContent)
|
||||
{
|
||||
if (line.listTypeName == "number")
|
||||
{
|
||||
pieces.push(new Array(line.listLevel + 1).join(' ') + ' - ');
|
||||
} else {
|
||||
pieces.push(new Array(line.listLevel + 1).join(' ') + '* ');
|
||||
}
|
||||
}
|
||||
pieces.push(lineContent);
|
||||
}
|
||||
|
||||
return pieces.join('');
|
||||
}
|
||||
|
||||
function _analyzeLine(text, aline, apool)
|
||||
{
|
||||
var line = {};
|
||||
|
||||
// identify list
|
||||
var lineMarker = 0;
|
||||
line.listLevel = 0;
|
||||
if (aline)
|
||||
{
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext())
|
||||
{
|
||||
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType)
|
||||
{
|
||||
lineMarker = 1;
|
||||
listType = /([a-z]+)([12345678])/.exec(listType);
|
||||
if (listType)
|
||||
{
|
||||
line.listTypeName = listType[1];
|
||||
line.listLevel = Number(listType[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineMarker)
|
||||
{
|
||||
line.text = text.substring(1);
|
||||
line.aline = Changeset.subattribution(aline, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
line.text = text;
|
||||
line.aline = aline;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
exports.getPadDokuWikiDocument = function (padId, revNum, callback)
|
||||
{
|
||||
padManager.getPad(padId, function (err, pad)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
getPadDokuWiki(pad, revNum, callback);
|
||||
});
|
||||
};
|
||||
|
||||
function _escapeDokuWiki(s)
|
||||
{
|
||||
s = s.replace(/(\/\/|\*\*|__)/g, '%%$1%%');
|
||||
return s;
|
||||
}
|
||||
|
||||
// copied from ACE
|
||||
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
var _REGEX_SPACE = /\s/;
|
||||
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
|
||||
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
|
||||
|
||||
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
|
||||
|
||||
|
||||
function _findURLs(text)
|
||||
{
|
||||
_REGEX_URL.lastIndex = 0;
|
||||
var urls = null;
|
||||
var execResult;
|
||||
while ((execResult = _REGEX_URL.exec(text)))
|
||||
{
|
||||
urls = (urls || []);
|
||||
var startIndex = execResult.index;
|
||||
var url = execResult[0];
|
||||
urls.push([startIndex, url]);
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
79
src/node/utils/ExportEtherpad.js
Normal file
79
src/node/utils/ExportEtherpad.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
var async = require("async");
|
||||
var db = require("../db/DB").db;
|
||||
var ERR = require("async-stacktrace");
|
||||
|
||||
exports.getPadRaw = function(padId, callback){
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
|
||||
// Get the Pad
|
||||
db.findKeys("pad:"+padId, null, function(err,padcontent){
|
||||
if(!err){
|
||||
cb(err, padcontent);
|
||||
}
|
||||
})
|
||||
},
|
||||
function(padcontent,cb){
|
||||
|
||||
// Get the Pad available content keys
|
||||
db.findKeys("pad:"+padId+":*", null, function(err,records){
|
||||
if(!err){
|
||||
for (var key in padcontent) { records.push(padcontent[key]);}
|
||||
cb(err, records);
|
||||
}
|
||||
})
|
||||
},
|
||||
function(records, cb){
|
||||
var data = {};
|
||||
|
||||
async.forEachSeries(Object.keys(records), function(key, r){
|
||||
|
||||
// For each piece of info about a pad.
|
||||
db.get(records[key], function(err, entry){
|
||||
data[records[key]] = entry;
|
||||
|
||||
// Get the Pad Authors
|
||||
if(entry.pool && entry.pool.numToAttrib){
|
||||
var authors = entry.pool.numToAttrib;
|
||||
async.forEachSeries(Object.keys(authors), function(k, c){
|
||||
if(authors[k][0] === "author"){
|
||||
var authorId = authors[k][1];
|
||||
|
||||
// Get the author info
|
||||
db.get("globalAuthor:"+authorId, function(e, authorEntry){
|
||||
if(authorEntry && authorEntry.padIDs) authorEntry.padIDs = padId;
|
||||
if(!e) data["globalAuthor:"+authorId] = authorEntry;
|
||||
});
|
||||
|
||||
}
|
||||
// console.log("authorsK", authors[k]);
|
||||
c(null);
|
||||
});
|
||||
}
|
||||
r(null); // callback;
|
||||
});
|
||||
}, function(err){
|
||||
cb(err, data);
|
||||
})
|
||||
}
|
||||
], function(err, data){
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
|
@ -18,12 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var ERR = require("async-stacktrace");
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
exports.getPadPlainText = function(pad, revNum){
|
||||
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
|
||||
|
@ -60,7 +55,7 @@ exports._analyzeLine = function(text, aline, apool){
|
|||
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType){
|
||||
lineMarker = 1;
|
||||
listType = /([a-z]+)([12345678])/.exec(listType);
|
||||
listType = /([a-z]+)([0-9]+)/.exec(listType);
|
||||
if (listType){
|
||||
line.listTypeName = listType[1];
|
||||
line.listLevel = Number(listType[2]);
|
||||
|
|
|
@ -30,8 +30,6 @@ function getPadHTML(pad, revNum, callback)
|
|||
var html;
|
||||
async.waterfall([
|
||||
// fetch revision atext
|
||||
|
||||
|
||||
function (callback)
|
||||
{
|
||||
if (revNum != undefined)
|
||||
|
@ -78,6 +76,14 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
|
||||
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
|
||||
hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){
|
||||
newProps.forEach(function (propName, i){
|
||||
tags.push(propName);
|
||||
props.push(propName);
|
||||
});
|
||||
});
|
||||
|
||||
// holds a map of used styling attributes (*1, *2, etc) in the apool
|
||||
// and maps them to an index in props
|
||||
// *3:2 -> the attribute *3 means strong
|
||||
|
@ -297,10 +303,12 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
// want to deal gracefully with blank lines.
|
||||
// => keeps track of the parents level of indentation
|
||||
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
|
||||
var listLevels = []
|
||||
for (var i = 0; i < textLines.length; i++)
|
||||
{
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
var lineContent = getLineHTML(line.text, line.aline);
|
||||
listLevels.push(line.listLevel)
|
||||
|
||||
if (line.listLevel)//If we are inside a list
|
||||
{
|
||||
|
@ -320,13 +328,27 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
|
||||
if (whichList >= lists.length)//means we are on a deeper level of indentation than the previous line
|
||||
{
|
||||
if(lists.length > 0){
|
||||
pieces.push('</li>')
|
||||
}
|
||||
lists.push([line.listLevel, line.listTypeName]);
|
||||
|
||||
// if there is a previous list we need to open x tags, where x is the difference of the levels
|
||||
// if there is no previous list we need to open x tags, where x is the wanted level
|
||||
var toOpen = lists.length > 1 ? line.listLevel - lists[lists.length - 2][0] - 1 : line.listLevel - 1
|
||||
|
||||
if(line.listTypeName == "number")
|
||||
{
|
||||
if(toOpen > 0){
|
||||
pieces.push(new Array(toOpen + 1).join('<ol>'))
|
||||
}
|
||||
pieces.push('<ol class="'+line.listTypeName+'"><li>', lineContent || '<br>');
|
||||
}
|
||||
else
|
||||
{
|
||||
if(toOpen > 0){
|
||||
pieces.push(new Array(toOpen + 1).join('<ul>'))
|
||||
}
|
||||
pieces.push('<ul class="'+line.listTypeName+'"><li>', lineContent || '<br>');
|
||||
}
|
||||
}
|
||||
|
@ -355,44 +377,50 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
pieces.push('<br><br>');
|
||||
}
|
||||
}*/
|
||||
else//means we are getting closer to the lowest level of indentation
|
||||
else//means we are getting closer to the lowest level of indentation or are at the same level
|
||||
{
|
||||
while (whichList < lists.length - 1)
|
||||
{
|
||||
var toClose = lists.length > 0 ? listLevels[listLevels.length - 2] - line.listLevel : 0
|
||||
if( toClose > 0){
|
||||
pieces.push('</li>')
|
||||
if(lists[lists.length - 1][1] == "number")
|
||||
{
|
||||
pieces.push('</li></ol>');
|
||||
pieces.push(new Array(toClose+1).join('</ol>'))
|
||||
pieces.push('<li>', lineContent || '<br>');
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push('</li></ul>');
|
||||
pieces.push(new Array(toClose+1).join('</ul>'))
|
||||
pieces.push('<li>', lineContent || '<br>');
|
||||
}
|
||||
lists.length--;
|
||||
lists = lists.slice(0,whichList+1)
|
||||
} else {
|
||||
pieces.push('</li><li>', lineContent || '<br>');
|
||||
}
|
||||
pieces.push('</li><li>', lineContent || '<br>');
|
||||
}
|
||||
}
|
||||
else//outside any list
|
||||
else//outside any list, need to close line.listLevel of lists
|
||||
{
|
||||
while (lists.length > 0)//if was in a list: close it before
|
||||
{
|
||||
if(lists[lists.length - 1][1] == "number")
|
||||
{
|
||||
if(lists.length > 0){
|
||||
if(lists[lists.length - 1][1] == "number"){
|
||||
pieces.push('</li></ol>');
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ol>'))
|
||||
} else {
|
||||
pieces.push('</li></ul>');
|
||||
pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ul>'))
|
||||
}
|
||||
lists.length--;
|
||||
}
|
||||
var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
|
||||
{
|
||||
lists = []
|
||||
|
||||
var context = {
|
||||
line: line,
|
||||
lineContent: lineContent,
|
||||
apool: apool,
|
||||
attribLine: attribLines[i],
|
||||
text: textLines[i]
|
||||
}, " ", " ", "");
|
||||
}
|
||||
|
||||
var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", context, " ", " ", "");
|
||||
|
||||
if (lineContentFromHook)
|
||||
{
|
||||
pieces.push(lineContentFromHook, '');
|
||||
|
@ -425,37 +453,120 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
|
|||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
var head =
|
||||
(noDocType ? '' : '<!doctype html>\n') +
|
||||
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
|
||||
'<title>' + Security.escapeHTML(padId) + '</title>\n' +
|
||||
'<meta charset="utf-8">\n' +
|
||||
'<style> * { font-family: arial, sans-serif;\n' +
|
||||
'font-size: 13px;\n' +
|
||||
'line-height: 17px; }' +
|
||||
'ul.indent { list-style-type: none; }' +
|
||||
'ol { list-style-type: decimal; }' +
|
||||
'ol ol { list-style-type: lower-latin; }' +
|
||||
'ol ol ol { list-style-type: lower-roman; }' +
|
||||
'ol ol ol ol { list-style-type: decimal; }' +
|
||||
'ol ol ol ol ol { list-style-type: lower-latin; }' +
|
||||
'ol ol ol ol ol ol{ list-style-type: lower-roman; }' +
|
||||
'ol ol ol ol ol ol ol { list-style-type: decimal; }' +
|
||||
'ol ol ol ol ol ol ol ol{ list-style-type: lower-latin; }' +
|
||||
'</style>\n' + '</head>\n') +
|
||||
'<body>';
|
||||
var stylesForExportCSS = "";
|
||||
// Include some Styles into the Head for Export
|
||||
hooks.aCallAll("stylesForExport", padId, function(err, stylesForExport){
|
||||
stylesForExport.forEach(function(css){
|
||||
stylesForExportCSS += css;
|
||||
});
|
||||
// Core inclusion of head etc.
|
||||
var head =
|
||||
(noDocType ? '' : '<!doctype html>\n') +
|
||||
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
|
||||
'<title>' + Security.escapeHTML(padId) + '</title>\n' +
|
||||
'<meta charset="utf-8">\n' +
|
||||
'<style> * { font-family: arial, sans-serif;\n' +
|
||||
'font-size: 13px;\n' +
|
||||
'line-height: 17px; }' +
|
||||
'ul.indent { list-style-type: none; }' +
|
||||
|
||||
var foot = '</body>\n</html>\n';
|
||||
'ol { list-style-type: none; padding-left:0;}' +
|
||||
'body > ol { counter-reset: first second third fourth fifth sixth seventh eigth ninth tenth eleventh twelth thirteenth fourteenth fifteenth sixteenth; }' +
|
||||
'ol > li:before {' +
|
||||
'content: counter(first) ". " ;'+
|
||||
'counter-increment: first;}' +
|
||||
|
||||
getPadHTML(pad, revNum, function (err, html)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, head + html + foot);
|
||||
'ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) ". " ;'+
|
||||
'counter-increment: second;}' +
|
||||
|
||||
'ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) ". ";'+
|
||||
'counter-increment: third;}' +
|
||||
|
||||
'ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) ". ";'+
|
||||
'counter-increment: fourth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) ". ";'+
|
||||
'counter-increment: fifth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) ". ";'+
|
||||
'counter-increment: sixth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) ". ";'+
|
||||
'counter-increment: seventh;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) ". ";'+
|
||||
'counter-increment: eigth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) ". ";'+
|
||||
'counter-increment: ninth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) ". ";'+
|
||||
'counter-increment: tenth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". ";'+
|
||||
'counter-increment: eleventh;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) ". ";'+
|
||||
'counter-increment: twelth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) ". ";'+
|
||||
'counter-increment: thirteenth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) ". ";'+
|
||||
'counter-increment: fourteenth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". ";'+
|
||||
'counter-increment: fifteenth;}' +
|
||||
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
|
||||
'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixthteenth) ". ";'+
|
||||
'counter-increment: sixthteenth;}' +
|
||||
|
||||
'ol{ text-indent: 0px; }' +
|
||||
'ol > ol{ text-indent: 10px; }' +
|
||||
'ol > ol > ol{ text-indent: 20px; }' +
|
||||
'ol > ol > ol > ol{ text-indent: 30px; }' +
|
||||
'ol > ol > ol > ol > ol{ text-indent: 40px; }' +
|
||||
'ol > ol > ol > ol > ol > ol{ text-indent: 50px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol{ text-indent: 60px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 70px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 80px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 90px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 100px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 110px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol { text-indent: 120px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 130px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 140px; }' +
|
||||
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 150px; }' +
|
||||
|
||||
stylesForExportCSS +
|
||||
'</style>\n' + '</head>\n') +
|
||||
'<body>';
|
||||
var foot = '</body>\n</html>\n';
|
||||
|
||||
getPadHTML(pad, revNum, function (err, html)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, head + html + foot);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// copied from ACE
|
||||
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
var _REGEX_SPACE = /\s/;
|
||||
|
|
|
@ -22,9 +22,6 @@ var async = require("async");
|
|||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var ERR = require("async-stacktrace");
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var getPadPlainText = require('./ExportHelper').getPadPlainText;
|
||||
var _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
|
||||
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
|
||||
|
@ -82,7 +79,6 @@ function getTXTFromAtext(pad, atext, authorColors)
|
|||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
|
||||
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
var anumMap = {};
|
||||
var css = "";
|
||||
|
@ -110,7 +106,6 @@ function getTXTFromAtext(pad, atext, authorColors)
|
|||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
var taker = Changeset.stringIterator(text);
|
||||
var assem = Changeset.stringAssembler();
|
||||
var openTags = [];
|
||||
|
||||
var idx = 0;
|
||||
|
||||
|
@ -250,7 +245,6 @@ function getTXTFromAtext(pad, atext, authorColors)
|
|||
// so we want to do something reasonable there. We also
|
||||
// want to deal gracefully with blank lines.
|
||||
// => keeps track of the parents level of indentation
|
||||
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
|
||||
for (var i = 0; i < textLines.length; i++)
|
||||
{
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
|
|
83
src/node/utils/ImportEtherpad.js
Normal file
83
src/node/utils/ImportEtherpad.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var async = require("async");
|
||||
var db = require("../db/DB").db;
|
||||
|
||||
exports.setPadRaw = function(padId, records, callback){
|
||||
records = JSON.parse(records);
|
||||
|
||||
// !! HACK !!
|
||||
// If you have a really large pad it will cause a Maximum Range Stack crash
|
||||
// This is a temporary patch for that so things are kept stable.
|
||||
var recordCount = Object.keys(records).length;
|
||||
if(recordCount >= 50000){
|
||||
console.warn("Etherpad file is too large to import.. We need to fix this. See https://github.com/ether/etherpad-lite/issues/2524");
|
||||
return callback("tooLarge", false);
|
||||
}
|
||||
|
||||
async.eachSeries(Object.keys(records), function(key, cb){
|
||||
var value = records[key]
|
||||
|
||||
if(!value){
|
||||
cb(); // null values are bad.
|
||||
}
|
||||
|
||||
// Author data
|
||||
if(value.padIDs){
|
||||
// rewrite author pad ids
|
||||
value.padIDs[padId] = 1;
|
||||
var newKey = key;
|
||||
|
||||
// Does this author already exist?
|
||||
db.get(key, function(err, author){
|
||||
if(author){
|
||||
// Yes, add the padID to the author..
|
||||
if( Object.prototype.toString.call(author) === '[object Array]'){
|
||||
author.padIDs.push(padId);
|
||||
}
|
||||
value = author;
|
||||
}else{
|
||||
// No, create a new array with the author info in
|
||||
value.padIDs = [padId];
|
||||
}
|
||||
});
|
||||
|
||||
// Not author data, probably pad data
|
||||
}else{
|
||||
// we can split it to look to see if its pad data
|
||||
var oldPadId = key.split(":");
|
||||
|
||||
// we know its pad data..
|
||||
if(oldPadId[0] === "pad"){
|
||||
|
||||
// so set the new pad id for the author
|
||||
oldPadId[1] = padId;
|
||||
|
||||
// and create the value
|
||||
var newKey = oldPadId.join(":"); // create the new key
|
||||
}
|
||||
|
||||
}
|
||||
// Write the value to the server
|
||||
db.set(newKey, value);
|
||||
|
||||
cb();
|
||||
}, function(){
|
||||
callback(null, true);
|
||||
});
|
||||
}
|
|
@ -14,23 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var jsdom = require('jsdom-nocontextifiy').jsdom;
|
||||
var log4js = require('log4js');
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var contentcollector = require("ep_etherpad-lite/static/js/contentcollector");
|
||||
var cheerio = require("cheerio");
|
||||
|
||||
function setPadHTML(pad, html, callback)
|
||||
{
|
||||
var apiLogger = log4js.getLogger("ImportHtml");
|
||||
|
||||
// Parse the incoming HTML with jsdom
|
||||
try{
|
||||
var doc = jsdom(html.replace(/>\n+</g, '><'));
|
||||
}catch(e){
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
var doc = jsdom("<html><body><div>Error during import, possibly malformed HTML</div></body></html>");
|
||||
}
|
||||
var $ = cheerio.load(html);
|
||||
|
||||
// Appends a line break, used by Etherpad to ensure a caret is available
|
||||
// below the last line of an import
|
||||
$('body').append("<p></p>");
|
||||
|
||||
var doc = $('html')[0];
|
||||
apiLogger.debug('html:');
|
||||
apiLogger.debug(html);
|
||||
|
||||
|
@ -38,10 +37,10 @@ function setPadHTML(pad, html, callback)
|
|||
// using the content collector object
|
||||
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
|
||||
try{ // we use a try here because if the HTML is bad it will blow up
|
||||
cc.collectContent(doc.childNodes[0]);
|
||||
cc.collectContent(doc);
|
||||
}catch(e){
|
||||
apiLogger.warn("HTML was not properly formed", e);
|
||||
return; // We don't process the HTML because it was bad..
|
||||
return callback(e); // We don't process the HTML because it was bad..
|
||||
}
|
||||
|
||||
var result = cc.finish();
|
||||
|
@ -92,6 +91,7 @@ function setPadHTML(pad, html, callback)
|
|||
apiLogger.debug('The changeset: ' + theChangeset);
|
||||
pad.setText("");
|
||||
pad.appendRevision(theChangeset);
|
||||
callback(null);
|
||||
}
|
||||
|
||||
exports.setPadHTML = setPadHTML;
|
||||
|
|
|
@ -23,12 +23,12 @@ var ERR = require("async-stacktrace");
|
|||
var settings = require('./Settings');
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
var cleanCSS = require('clean-css');
|
||||
var CleanCSS = require('clean-css');
|
||||
var jsp = require("uglify-js").parser;
|
||||
var pro = require("uglify-js").uglify;
|
||||
var path = require('path');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
var RequireKernel = require('require-kernel');
|
||||
var RequireKernel = require('etherpad-require-kernel');
|
||||
var urlutil = require('url');
|
||||
|
||||
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
|
||||
|
@ -145,7 +145,6 @@ function minify(req, res, next)
|
|||
filename = path.normalize(path.join(ROOT_DIR, filename));
|
||||
if (filename.indexOf(ROOT_DIR) == 0) {
|
||||
filename = filename.slice(ROOT_DIR.length);
|
||||
filename = filename.replace(/\\/g, '/'); // Windows (safe generally?)
|
||||
} else {
|
||||
res.writeHead(404, {});
|
||||
res.end();
|
||||
|
@ -261,7 +260,6 @@ function getAceFile(callback) {
|
|||
// them into the file.
|
||||
async.forEach(founds, function (item, callback) {
|
||||
var filename = item.match(/"([^"]*)"/)[1];
|
||||
var request = require('request');
|
||||
|
||||
var baseURI = 'http://localhost:' + settings.port;
|
||||
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
|
||||
|
@ -411,7 +409,8 @@ function compressJS(values)
|
|||
function compressCSS(values)
|
||||
{
|
||||
var complete = values.join("\n");
|
||||
return cleanCSS.process(complete);
|
||||
var minimized = new CleanCSS().minify(complete).styles;
|
||||
return minimized;
|
||||
}
|
||||
|
||||
exports.minify = minify;
|
||||
|
|
1
src/node/utils/RemoteAddress.js
Normal file
1
src/node/utils/RemoteAddress.js
Normal file
|
@ -0,0 +1 @@
|
|||
exports.remoteAddress = {};
|
|
@ -27,7 +27,7 @@ var npm = require("npm/lib/npm.js");
|
|||
var jsonminify = require("jsonminify");
|
||||
var log4js = require("log4js");
|
||||
var randomString = require("./randomstring");
|
||||
|
||||
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
|
||||
|
||||
/* Root path of the installation */
|
||||
exports.root = path.normalize(path.join(npm.dir, ".."));
|
||||
|
@ -54,6 +54,11 @@ exports.ip = "0.0.0.0";
|
|||
*/
|
||||
exports.port = process.env.PORT || 9001;
|
||||
|
||||
/**
|
||||
* Should we suppress Error messages from being in Pad Contents
|
||||
*/
|
||||
exports.suppressErrorsInPadText = false;
|
||||
|
||||
/**
|
||||
* The SSL signed server key and the Certificate Authority's own certificate
|
||||
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
|
||||
|
@ -95,7 +100,7 @@ exports.toolbar = {
|
|||
["showusers"]
|
||||
],
|
||||
timeslider: [
|
||||
["timeslider_export", "timeslider_returnToPad"]
|
||||
["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -129,6 +134,11 @@ exports.minify = true;
|
|||
*/
|
||||
exports.abiword = null;
|
||||
|
||||
/**
|
||||
* Should we support none natively supported file types on import?
|
||||
*/
|
||||
exports.allowUnknownFileEnds = true;
|
||||
|
||||
/**
|
||||
* The log level of log4js
|
||||
*/
|
||||
|
@ -139,6 +149,11 @@ exports.loglevel = "INFO";
|
|||
*/
|
||||
exports.disableIPlogging = false;
|
||||
|
||||
/**
|
||||
* Disable Load Testing
|
||||
*/
|
||||
exports.loadTest = false;
|
||||
|
||||
/*
|
||||
* log4js appender configuration
|
||||
*/
|
||||
|
@ -174,6 +189,29 @@ exports.abiwordAvailable = function()
|
|||
}
|
||||
};
|
||||
|
||||
// Provide git version if available
|
||||
exports.getGitCommit = function() {
|
||||
var version = "";
|
||||
try
|
||||
{
|
||||
var rootPath = path.resolve(npm.dir, '..');
|
||||
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
|
||||
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
|
||||
version = fs.readFileSync(refPath, "utf-8");
|
||||
version = version.substring(0, 7);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.warn("Can't get git version for server header\n" + e.message)
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
// Return etherpad version from package.json
|
||||
exports.getEpVersion = function() {
|
||||
return require('ep_etherpad-lite/package.json').version;
|
||||
}
|
||||
|
||||
exports.reloadSettings = function reloadSettings() {
|
||||
// Discover where the settings file lives
|
||||
var settingsFilename = argv.settings || "settings.json";
|
||||
|
@ -228,17 +266,45 @@ exports.reloadSettings = function reloadSettings() {
|
|||
|
||||
log4js.configure(exports.logconfig);//Configure the logging appenders
|
||||
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
|
||||
process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug
|
||||
log4js.replaceConsole();
|
||||
|
||||
if(exports.abiword){
|
||||
// Check abiword actually exists
|
||||
if(exports.abiword != null)
|
||||
{
|
||||
fs.exists(exports.abiword, function(exists) {
|
||||
if (!exists) {
|
||||
var abiwordError = "Abiword does not exist at this path, check your settings file";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
|
||||
}
|
||||
console.error(abiwordError);
|
||||
exports.abiword = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!exports.sessionKey){ // If the secretKey isn't set we also create yet another unique value here
|
||||
exports.sessionKey = randomString(32);
|
||||
console.warn("You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts");
|
||||
var sessionWarning = "You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + sessionWarning + suppressDisableMsg;
|
||||
}
|
||||
console.warn(sessionWarning);
|
||||
}
|
||||
|
||||
if(exports.dbType === "dirty"){
|
||||
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.");
|
||||
var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
|
||||
}
|
||||
console.warn(dirtyWarning);
|
||||
}
|
||||
};
|
||||
|
||||
// initially load settings
|
||||
exports.reloadSettings();
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ var Buffer = require('buffer').Buffer;
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var zlib = require('zlib');
|
||||
var util = require('util');
|
||||
var settings = require('./Settings');
|
||||
var semver = require('semver');
|
||||
|
||||
|
|
|
@ -101,8 +101,12 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
//apply the clearAuthorship changeset
|
||||
var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool);
|
||||
} catch(err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
callback(null, newAText);
|
||||
});
|
||||
|
@ -209,10 +213,14 @@ PadDiff.prototype._createDiffAtext = function(callback) {
|
|||
if(superChangeset){
|
||||
var deletionChangeset = self._createDeletionChangeset(superChangeset,atext,self._pad.pool);
|
||||
|
||||
//apply the superChangeset, which includes all addings
|
||||
atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool);
|
||||
//apply the deletionChangeset, which adds a deletions
|
||||
atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool);
|
||||
try {
|
||||
//apply the superChangeset, which includes all addings
|
||||
atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool);
|
||||
//apply the deletionChangeset, which adds a deletions
|
||||
atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool);
|
||||
} catch(err) {
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
|
||||
callback(err, atext);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"pad.js": [
|
||||
"pad.js"
|
||||
, "pad_utils.js"
|
||||
, "browser.js"
|
||||
, "pad_cookie.js"
|
||||
, "pad_editor.js"
|
||||
, "pad_editbar.js"
|
||||
|
@ -24,6 +25,7 @@
|
|||
, "colorutils.js"
|
||||
, "draggable.js"
|
||||
, "pad_utils.js"
|
||||
, "browser.js"
|
||||
, "pad_cookie.js"
|
||||
, "pad_editor.js"
|
||||
, "pad_editbar.js"
|
||||
|
@ -42,6 +44,7 @@
|
|||
]
|
||||
, "ace2_inner.js": [
|
||||
"ace2_inner.js"
|
||||
, "browser.js"
|
||||
, "AttributePool.js"
|
||||
, "Changeset.js"
|
||||
, "ChangesetUtils.js"
|
||||
|
@ -58,6 +61,7 @@
|
|||
]
|
||||
, "ace2_common.js": [
|
||||
"ace2_common.js"
|
||||
, "browser.js"
|
||||
, "jquery.js"
|
||||
, "rjquery.js"
|
||||
, "$async.js"
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
var _ = require("underscore")
|
||||
, tagAttributes
|
||||
, tag
|
||||
, defaultButtons
|
||||
, Button
|
||||
, ButtonsGroup
|
||||
, Separator
|
||||
|
@ -100,12 +99,14 @@ _.extend(Button.prototype, {
|
|||
};
|
||||
return tag("li", liAttributes,
|
||||
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
|
||||
tag("span", { "class": " "+ this.attributes.class })
|
||||
tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId })
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
SelectButton = function (attributes) {
|
||||
this.attributes = attributes;
|
||||
this.options = [];
|
||||
|
@ -122,8 +123,7 @@ _.extend(SelectButton.prototype, Button.prototype, {
|
|||
},
|
||||
|
||||
select: function (attributes) {
|
||||
var self = this
|
||||
, options = [];
|
||||
var options = [];
|
||||
|
||||
_.each(this.options, function (opt) {
|
||||
var a = _.extend({
|
||||
|
@ -210,6 +210,12 @@ module.exports = {
|
|||
class: "buttonicon buttonicon-import_export"
|
||||
},
|
||||
|
||||
timeslider_settings: {
|
||||
command: "settings",
|
||||
localizationId: "pad.toolbar.settings.title",
|
||||
class: "buttonicon buttonicon-settings"
|
||||
},
|
||||
|
||||
timeslider_returnToPad: {
|
||||
command: "timeslider_returnToPad",
|
||||
localizationId: "timeslider.toolbar.returnbutton",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue