From 96b7b5e8d0c32aa09cc8d1b9fec66c68d239f92d Mon Sep 17 00:00:00 2001 From: Swen Date: Mon, 14 Jan 2013 21:12:40 +0100 Subject: [PATCH 01/32] Removed unnecessary return statement --- src/node/db/PadManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 8cd69a83e..76b2db84b 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -56,7 +56,6 @@ var padList = { }); } }); - return this; }, /** * Returns all pads in alphabetical order as array. From 5d416579ee3a0943ee258625cc0de4c7a56e49e3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 22 Jan 2013 22:33:51 +0000 Subject: [PATCH 02/32] bring in some padDiff stuff that doesnt suck --- src/node/db/API.js | 81 ++++++++++++++++++++++++++++++++++ src/node/handler/APIHandler.js | 38 ++++++++++++++++ src/node/utils/padDiff.js | 72 ++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 src/node/utils/padDiff.js diff --git a/src/node/db/API.js b/src/node/db/API.js index 9cad415d0..9cecdbd3b 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -568,6 +568,87 @@ exports.checkToken = function(callback) } +/** +createDiff(padID, startRev, endRev) returns an object of diffs from 2 points in a pad + +Example returns: + +TODO {"code":0,"message":"ok","data":null} +TODO {"code":4,"message":"no or wrong API Key","data":null} +*/ +exports.createDiff = function(padID, startRev, endRev, callback){ + //check if rev is a number + if(startRev !== undefined && typeof startRev != "number") + { + //try to parse the number + if(!isNaN(parseInt(startRev))) + { + startRev = parseInt(startRev, 10); + } + else + { + callback({stop: "startRev is not a number"}); + return; + } + } + + //check if rev is a number + if(endRev !== undefined && typeof endRev != "number") + { + //try to parse the number + if(!isNaN(parseInt(endRev))) + { + endRev = parseInt(endRev, 10); + } + else + { + callback({stop: "endRev is not a number"}); + return; + } + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(err){ + return callback(err); + } + + try { + var padDiff = new PadDiff(pad, startRev, endRev); + } catch(e) { + return callback({stop:e.message}); + } + + var html, authors; + + async.series([ + function(callback){ + padDiff.getHtml(function(err, _html){ + if(err){ + return callback(err); + } + + html = _html; + callback(); + }); + }, + function(callback){ + padDiff.getAuthors(function(err, _authors){ + if(err){ + return callback(err); + } + + authors = _authors; + callback(); + }); + } + ], function(err){ + callback(err, {html: html, authors: authors}) + }); + }); +} + /******************************/ /** INTERNAL HELPER FUNCTIONS */ /******************************/ diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index ae93e933f..ec7e9f8d0 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -24,6 +24,7 @@ var fs = require("fs"); var api = require("../db/API"); var padManager = require("../db/PadManager"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +var padDiff = require("../utils/padDiff"); //ensure we have an apikey var apikey = null; @@ -174,6 +175,43 @@ var version = , "listAllGroups" : [] , "checkToken" : [] } +, "1.2.2": + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "listAllPads" : [] + , "createDiff" : ["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"] + , "getRevisionsCount" : ["padID"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "getReadOnlyID" : ["padID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + , "getAuthorName" : ["authorID"] + , "padUsers" : ["padID"] + , "sendClientsMessage" : ["padID", "msg"] + , "listAllGroups" : [] + , "checkToken" : [] + } }; /** diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js new file mode 100644 index 000000000..15957b823 --- /dev/null +++ b/src/node/utils/padDiff.js @@ -0,0 +1,72 @@ +exports.createDiff = function(padID, startRev, endRev, callback){ + //check if rev is a number + if(startRev !== undefined && typeof startRev != "number") + { + //try to parse the number + if(!isNaN(parseInt(startRev))) + { + startRev = parseInt(startRev, 10); + } + else + { + callback({stop: "startRev is not a number"}); + return; + } + } + + //check if rev is a number + if(endRev !== undefined && typeof endRev != "number") + { + //try to parse the number + if(!isNaN(parseInt(endRev))) + { + endRev = parseInt(endRev, 10); + } + else + { + callback({stop: "endRev is not a number"}); + return; + } + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(err){ + return callback(err); + } + + try { + var padDiff = new PadDiff(pad, startRev, endRev); + } catch(e) { + return callback({stop:e.message}); + } + + var html, authors; + + async.series([ + function(callback){ + padDiff.getHtml(function(err, _html){ + if(err){ + return callback(err); + } + + html = _html; + callback(); + }); + }, + function(callback){ + padDiff.getAuthors(function(err, _authors){ + if(err){ + return callback(err); + } + + authors = _authors; + callback(); + }); + } + ], function(err){ + callback(err, {html: html, authors: authors}) + }); + }); +} From 205d9832253b9d62e7726178e2d6c5a14c3f64ac Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 22 Jan 2013 22:48:05 +0000 Subject: [PATCH 03/32] make it crash --- src/node/db/API.js | 1 + src/node/handler/APIHandler.js | 1 - src/node/utils/padDiff.js | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index 9cecdbd3b..72ca16328 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -30,6 +30,7 @@ var async = require("async"); var exportHtml = require("../utils/ExportHtml"); var importHtml = require("../utils/ImportHtml"); var cleanText = require("./Pad").cleanText; +var padDiff = require("../utils/padDiff"); /**********************/ /**GROUP FUNCTIONS*****/ diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index ec7e9f8d0..31b4b187c 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -24,7 +24,6 @@ var fs = require("fs"); var api = require("../db/API"); var padManager = require("../db/PadManager"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; -var padDiff = require("../utils/padDiff"); //ensure we have an apikey var apikey = null; diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 15957b823..230668eb3 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -1,4 +1,5 @@ exports.createDiff = function(padID, startRev, endRev, callback){ +console.warn("WTF"); //check if rev is a number if(startRev !== undefined && typeof startRev != "number") { From d21585b8807b831e51a689bab6769d0ebfc1544b Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 22 Jan 2013 23:06:52 +0000 Subject: [PATCH 04/32] mheh --- src/node/db/API.js | 8 ++++++-- src/node/utils/padDiff.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index 72ca16328..50c4a6c5d 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -30,7 +30,7 @@ var async = require("async"); var exportHtml = require("../utils/ExportHtml"); var importHtml = require("../utils/ImportHtml"); var cleanText = require("./Pad").cleanText; -var padDiff = require("../utils/padDiff"); +var PadDiff = require("../utils/padDiff"); /**********************/ /**GROUP FUNCTIONS*****/ @@ -611,16 +611,19 @@ exports.createDiff = function(padID, startRev, endRev, callback){ //get the pad getPadSafe(padID, true, function(err, pad) { +console.warn(padID); if(err){ return callback(err); } try { +console.warn(pad); var padDiff = new PadDiff(pad, startRev, endRev); +console.warn("AFTER"); } catch(e) { return callback({stop:e.message}); } - +/* var html, authors; async.series([ @@ -647,6 +650,7 @@ exports.createDiff = function(padID, startRev, endRev, callback){ ], function(err){ callback(err, {html: html, authors: authors}) }); + */ }); } diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 230668eb3..645c2e2d4 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -1,5 +1,6 @@ exports.createDiff = function(padID, startRev, endRev, callback){ console.warn("WTF"); + //check if rev is a number if(startRev !== undefined && typeof startRev != "number") { From 3fb2f02875bfd0da7097a3fa021c7959bde1811b Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 22 Jan 2013 23:16:49 +0000 Subject: [PATCH 05/32] semi working --- src/node/db/API.js | 6 +- src/node/utils/padDiff.js | 596 ++++++++++++++++++++++++++++++++++---- 2 files changed, 539 insertions(+), 63 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index 50c4a6c5d..2c8b91abb 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -611,19 +611,16 @@ exports.createDiff = function(padID, startRev, endRev, callback){ //get the pad getPadSafe(padID, true, function(err, pad) { -console.warn(padID); if(err){ return callback(err); } try { -console.warn(pad); var padDiff = new PadDiff(pad, startRev, endRev); -console.warn("AFTER"); } catch(e) { + // console.warn(e.stack); return callback({stop:e.message}); } -/* var html, authors; async.series([ @@ -650,7 +647,6 @@ console.warn("AFTER"); ], function(err){ callback(err, {html: html, authors: authors}) }); - */ }); } diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 645c2e2d4..f898cbe16 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -1,74 +1,554 @@ -exports.createDiff = function(padID, startRev, endRev, callback){ -console.warn("WTF"); - - //check if rev is a number - if(startRev !== undefined && typeof startRev != "number") +var Changeset = require("../../static/js/Changeset"); +var async = require("async"); +var exportHtml = require('./ExportHtml'); + +function PadDiff (pad, fromRev, toRev){ + //check parameters + if(!pad || !pad.id || !pad.atext || !pad.pool) { - //try to parse the number - if(!isNaN(parseInt(startRev))) - { - startRev = parseInt(startRev, 10); - } - else - { - callback({stop: "startRev is not a number"}); - return; - } + throw new Error('Invalid pad'); } - //check if rev is a number - if(endRev !== undefined && typeof endRev != "number") - { - //try to parse the number - if(!isNaN(parseInt(endRev))) - { - endRev = parseInt(endRev, 10); - } - else - { - callback({stop: "endRev is not a number"}); - return; - } + var range = pad.getValidRevisionRange(fromRev, toRev); + if(!range) { throw new Error('Invalid revision range.' + + ' startRev: ' + fromRev + + ' endRev: ' + toRev); } + + this._pad = pad; + this._fromRev = range.startRev; + this._toRev = range.endRev; + this._html = null; + this._authors = []; +} + +PadDiff.prototype._isClearAuthorship = function(changeset){ + //unpack + var unpacked = Changeset.unpack(changeset); + + //check if there is nothing in the charBank + if(unpacked.charBank !== "") + return false; + + //check if oldLength == newLength + if(unpacked.oldLen !== unpacked.newLen) + return false; + + //lets iterator over the operators + var iterator = Changeset.opIterator(unpacked.ops); + + //get the first operator, this should be a clear operator + var clearOperator = iterator.next(); + + //check if there is only one operator + if(iterator.hasNext() === true) + return false; + + //check if this operator doesn't change text + if(clearOperator.opcode !== "=") + return false; + + //check that this operator applys to the complete text + //if the text ends with a new line, its exactly one character less, else it has the same length + if(clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) + return false; + + var attributes = []; + Changeset.eachAttribNumber(changeset, function(attrNum){ + attributes.push(attrNum); + }); + + //check that this changeset uses only one attribute + if(attributes.length !== 1) + return false; + + var appliedAttribute = this._pad.pool.getAttrib(attributes[0]); + + //check if the applied attribute is an anonymous author attribute + if(appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") + return false; + + return true; +} + +PadDiff.prototype._createClearAuthorship = function(rev, callback){ + var self = this; + this._pad.getInternalRevisionAText(rev, function(err, atext){ + if(err){ + return callback(err); + } + + //build clearAuthorship changeset + var builder = Changeset.builder(atext.text.length); + builder.keepText(atext.text, [['author','']], self._pad.pool); + var changeset = builder.toString(); + + callback(null, changeset); + }); +} + +PadDiff.prototype._createClearStartAtext = function(rev, callback){ + var self = this; + + //get the atext of this revision + this._pad.getInternalRevisionAText(rev, function(err, atext){ + if(err){ + return callback(err); + } + + //create the clearAuthorship changeset + self._createClearAuthorship(rev, function(err, changeset){ + if(err){ + return callback(err); + } + + //apply the clearAuthorship changeset + var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); + + callback(null, newAText); + }); + }); +} + +PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { + var self = this; + + //find out which revisions we need + var revisions = []; + for(var i=startRev;i<(startRev+count) && i<=this._pad.head;i++){ + revisions.push(i); } + + var changesets = [], authors = []; + + //get all needed revisions + async.forEach(revisions, function(rev, callback){ + self._pad.getRevision(rev, function(err, revision){ + if(err){ + return callback(err) + } + + var arrayNum = rev-startRev; + + changesets[arrayNum] = revision.changeset; + authors[arrayNum] = revision.meta.author; + + callback(); + }); + }, function(err){ + callback(err, changesets, authors); + }); +} - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(err){ - return callback(err); +PadDiff.prototype._addAuthors = function(authors) { + var self = this; + //add to array if not in the array + authors.forEach(function(author){ + if(self._authors.indexOf(author) == -1){ + self._authors.push(author); } + }); +} - try { - var padDiff = new PadDiff(pad, startRev, endRev); - } catch(e) { - return callback({stop:e.message}); - } +PadDiff.prototype._createDiffAtext = function(callback) { + var self = this; + var bulkSize = 100; + + //get the cleaned startAText + self._createClearStartAtext(self._fromRev, function(err, atext){ + if(err) { return callback(err); } + + var superChangeset = null; + + var rev = self._fromRev + 1; - var html, authors; - - async.series([ - function(callback){ - padDiff.getHtml(function(err, _html){ - if(err){ - return callback(err); + //async while loop + async.whilst( + //loop condition + function () { return rev <= self._toRev; }, + + //loop body + function (callback) { + //get the bulk + self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors){ + var addedAuthors = []; + + //run trough all changesets + for(var i=0;i= curChar) { + curLineNextOp.chars -= (curChar - indexIntoLine); + done = true; + } else { + indexIntoLine += curLineNextOp.chars; + } + } + } + + while (numChars > 0) { + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + curLineOpIterLine = curLine; + curLineNextOp.chars = 0; + curLineOpIter = Changeset.opIterator(alines_get(curLine)); + } + if (!curLineNextOp.chars) { + curLineOpIter.next(curLineNextOp); + } + var charsToUse = Math.min(numChars, curLineNextOp.chars); + func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); + numChars -= charsToUse; + curLineNextOp.chars -= charsToUse; + curChar += charsToUse; + } + + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + } + } + + function skip(N, L) { + if (L) { + curLine += L; + curChar = 0; + } else { + if (curLineOpIter && curLineOpIterLine == curLine) { + consumeAttribRuns(N, function () {}); + } else { + curChar += N; + } + } + } + + function nextText(numChars) { + var len = 0; + var assem = Changeset.stringAssembler(); + var firstString = lines_get(curLine).substring(curChar); + len += firstString.length; + assem.append(firstString); + + var lineNum = curLine + 1; + while (len < numChars) { + var nextString = lines_get(lineNum); + len += nextString.length; + assem.append(nextString); + lineNum++; + } + + return assem.toString().substring(0, numChars); + } + + function cachedStrFunc(func) { + var cache = {}; + return function (s) { + if (!cache[s]) { + cache[s] = func(s); + } + return cache[s]; + }; + } + + var attribKeys = []; + var attribValues = []; + + //iterate over all operators of this changeset + while (csIter.hasNext()) { + var csOp = csIter.next(); + + if (csOp.opcode == '=') { + var textBank = nextText(csOp.chars); + + // decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set. + // If the text this operator applies to is only a star, than this is a false positive and should be ignored + if (csOp.attribs && textBank != "*") { + var deletedAttrib = apool.putAttrib(["removed", true]); + var authorAttrib = apool.putAttrib(["author", ""]);; + + attribKeys.length = 0; + attribValues.length = 0; + Changeset.eachAttribNumber(csOp.attribs, function (n) { + attribKeys.push(apool.getAttribKey(n)); + attribValues.push(apool.getAttribValue(n)); + + if(apool.getAttribKey(n) === "author"){ + authorAttrib = n; + }; + }); + + var undoBackToAttribs = cachedStrFunc(function (attribs) { + var backAttribs = []; + for (var i = 0; i < attribKeys.length; i++) { + var appliedKey = attribKeys[i]; + var appliedValue = attribValues[i]; + var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); + if (appliedValue != oldValue) { + backAttribs.push([appliedKey, oldValue]); + } + } + return Changeset.makeAttribsString('=', backAttribs, apool); + }); + + var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib); + + var textLeftToProcess = textBank; + + while(textLeftToProcess.length > 0){ + //process till the next line break or process only one line break + var lengthToProcess = textLeftToProcess.indexOf("\n"); + var lineBreak = false; + switch(lengthToProcess){ + case -1: + lengthToProcess=textLeftToProcess.length; + break; + case 0: + lineBreak = true; + lengthToProcess=1; + break; + } + + //get the text we want to procceed in this step + var processText = textLeftToProcess.substr(0, lengthToProcess); + textLeftToProcess = textLeftToProcess.substr(lengthToProcess); + + if(lineBreak){ + builder.keep(1, 1); //just skip linebreaks, don't do a insert + keep for a linebreak + + //consume the attributes of this linebreak + consumeAttribRuns(1, function(){}); + } else { + //add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it + var textBankIndex = 0; + consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { + //get the old attributes back + var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; + + builder.insert(processText.substr(textBankIndex, len), attribs); + textBankIndex += len; + }); + + builder.keep(lengthToProcess, 0); + } + } + } else { + skip(csOp.chars, csOp.lines); + builder.keep(csOp.chars, csOp.lines); + } + } else if (csOp.opcode == '+') { + builder.keep(csOp.chars, csOp.lines); + } else if (csOp.opcode == '-') { + var textBank = nextText(csOp.chars); + var textBankIndex = 0; + + consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); + textBankIndex += len; + }); + } + } + + return Changeset.checkRep(builder.toString()); +}; + +//export the constructor +module.exports = PadDiff; From f1b9c213eef910f7f4b94e40b526206aa9763b35 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 22 Jan 2013 23:37:53 +0000 Subject: [PATCH 06/32] and semi working --- src/node/db/Pad.js | 41 ++++++++++++++++++++++++++++++++++++ src/node/utils/ExportHtml.js | 2 +- src/node/utils/padDiff.js | 7 ++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index da1ce9e16..037886eac 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -213,6 +213,47 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe }); }; +Pad.prototype.getRevision = function getRevisionChangeset(revNum, callback) { + db.get("pad:"+this.id+":revs:"+revNum, callback); +}; + +Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback){ + var authors = this.getAllAuthors(); + var returnTable = {}; + var colorPalette = authorManager.getColorPalette(); + + async.forEach(authors, function(author, callback){ + authorManager.getAuthorColorId(author, function(err, colorId){ + if(err){ + return callback(err); + } + returnTable[author]=colorPalette[colorId]; + + callback(); + }); + }, function(err){ + callback(err, returnTable); + }); +}; + +Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) { + startRev = parseInt(startRev, 10); + var head = this.getHeadRevisionNumber(); + endRev = endRev ? parseInt(endRev, 10) : head; + if(isNaN(startRev) || startRev < 0 || startRev > head) { + startRev = null; + } + if(isNaN(endRev) || endRev < startRev) { + endRev = null; + } else if(endRev > head) { + endRev = head; + } + if(startRev !== null && endRev !== null) { + return { startRev: startRev , endRev: endRev } + } + return null; +}; + Pad.prototype.getKeyRevisionNumber = function getKeyRevisionNumber(revNum) { return Math.floor(revNum / 100) * 100; }; diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 354030133..f385d4704 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -92,7 +92,7 @@ function getPadHTML(pad, revNum, callback) exports.getPadHTML = getPadHTML; -function getHTMLFromAtext(pad, atext) +exports.getHTMLFromAtext = function(pad, atext) { var apool = pad.apool(); var textLines = atext.text.slice(0, -1).split('\n'); diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index f898cbe16..b1fa9277a 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -244,14 +244,17 @@ PadDiff.prototype.getHtml = function(callback){ }, //get the authorColor table function(callback){ - self._pad.getAllAuthorColors(function(err, _authorColors){ + /* + self._pad.getAllAuthorColors(function(err, _authorColors){ // TODO if(err){ return callback(err); } authorColors = _authorColors; - callback(); }); + */ + authorColors = {}; + callback(); }, //convert the atext to html function(callback){ From 7432b7aff94775419333c955044e62635cabd6b6 Mon Sep 17 00:00:00 2001 From: mluto Date: Sun, 27 Jan 2013 11:02:15 +0100 Subject: [PATCH 07/32] Rewrote getParams() to be more dynamic and shorter --- src/static/js/pad.js | 103 ++++++++++++------------------------------- 1 file changed, 28 insertions(+), 75 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 64d8b42b8..9f0fba7fa 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -101,86 +101,39 @@ function randomString() return "t." + randomstring; } +// This array represents all GET-parameters which can be used to change a setting. +// name: the parameter-name, eg `?noColors=true` => `noColors` +// checkVal: the callback is only executed when +// * the parameter was supplied and matches checkVal +// * the parameter was supplied and checkVal is null +// callback: the function to call when all above succeeds, `val` is the value supplied by the user +var paramSettings = [ + { name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } }, + { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').hide(); $('#editorcontainer').css({"top":"0px"}); } }, + { name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } }, + { name: "showLineNumbers", checkVal: "false", callback: function(val) { settings.LineNumbersDisabled = true; } }, + { name: "useMonospaceFont", checkVal: "true", callback: function(val) { settings.useMonospaceFontGlobal = true; } }, + // If the username is set as a parameter we should set a global value that we can call once we have initiated the pad. + { name: "userName", checkVal: null, callback: function(val) { settings.globalUserName = decodeURIComponent(val); } }, + // If the userColor is set as a parameter, set a global value to use once we have initiated the pad. + { name: "userColor", checkVal: null, callback: function(val) { settings.globalUserColor = decodeURIComponent(val); } }, + { name: "rtl", checkVal: "true", callback: function(val) { settings.rtlIsTrue = true } }, + { name: "alwaysShowChat", checkVal: "true", callback: function(val) { chat.stickToScreen(); } }, + { name: "lang", checkVal: null, callback: function(val) { window.html10n.localize([val, 'en']); } } +]; + function getParams() { var params = getUrlVars() - var showControls = params["showControls"]; - var showChat = params["showChat"]; - var userName = params["userName"]; - var userColor = params["userColor"]; - var showLineNumbers = params["showLineNumbers"]; - var useMonospaceFont = params["useMonospaceFont"]; - var IsnoColors = params["noColors"]; - var rtl = params["rtl"]; - var alwaysShowChat = params["alwaysShowChat"]; - var lang = params["lang"]; - - if(IsnoColors) + + for(var i = 0; i < paramSettings.length; i++) { - if(IsnoColors == "true") + var setting = paramSettings[i]; + var value = params[setting.name]; + + if(value && (value == setting.checkVal || setting.checkVal == null)) { - settings.noColors = true; - $('#clearAuthorship').hide(); - } - } - if(showControls) - { - if(showControls == "false") - { - $('#editbar').hide(); - $('#editorcontainer').css({"top":"0px"}); - } - } - if(showChat) - { - if(showChat == "false") - { - $('#chaticon').hide(); - } - } - if(showLineNumbers) - { - if(showLineNumbers == "false") - { - settings.LineNumbersDisabled = true; - } - } - if(useMonospaceFont) - { - if(useMonospaceFont == "true") - { - settings.useMonospaceFontGlobal = true; - } - } - if(userName) - { - // If the username is set as a parameter we should set a global value that we can call once we have initiated the pad. - settings.globalUserName = decodeURIComponent(userName); - } - if(userColor) - // If the userColor is set as a parameter, set a global value to use once we have initiated the pad. - { - settings.globalUserColor = decodeURIComponent(userColor); - } - if(rtl) - { - if(rtl == "true") - { - settings.rtlIsTrue = true - } - } - if(alwaysShowChat) - { - if(alwaysShowChat == "true") - { - chat.stickToScreen(); - } - } - if(lang) - { - if(lang !== "") - { - window.html10n.localize([lang, 'en']); + setting.callback(value); } } } From f4690dda9dec3ba9afce3f22b49f91a624d4fae4 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sun, 27 Jan 2013 15:40:05 +0000 Subject: [PATCH 08/32] fixed indent --- src/node/handler/APIHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 31b4b187c..9d017b280 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -180,7 +180,7 @@ var version = , "deleteGroup" : ["groupID"] , "listPads" : ["groupID"] , "listAllPads" : [] - , "createDiff" : ["padID", "startRev", "endRev"] + , "createDiff" : ["padID", "startRev", "endRev"] , "createPad" : ["padID", "text"] , "createGroupPad" : ["groupID", "padName", "text"] , "createAuthor" : ["name"] From dcfb1b2ea4bd3ee8783c6a01aeebc8c760aa7a06 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sun, 27 Jan 2013 15:40:37 +0000 Subject: [PATCH 09/32] Added missing functions to create pad diffs --- src/static/js/Changeset.js | 118 +++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index cfea43625..b16042126 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -2182,3 +2182,121 @@ exports.followAttributes = function (att1, att2, pool) { } return buf.toString(); }; + +exports.composeWithDeletions = function (cs1, cs2, pool) { + var unpacked1 = exports.unpack(cs1); + var unpacked2 = exports.unpack(cs2); + var len1 = unpacked1.oldLen; + var len2 = unpacked1.newLen; + exports.assert(len2 == unpacked2.oldLen, "mismatched composition"); + var len3 = unpacked2.newLen; + var bankIter1 = exports.stringIterator(unpacked1.charBank); + var bankIter2 = exports.stringIterator(unpacked2.charBank); + var bankAssem = exports.stringAssembler(); + + var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { + var op1code = op1.opcode; + var op2code = op2.opcode; + if (op1code == '+' && op2code == '-') { + bankIter1.skip(Math.min(op1.chars, op2.chars)); + } + exports._slicerZipperFuncWithDeletions(op1, op2, opOut, pool); + if (opOut.opcode == '+') { + if (op2code == '+') { + bankAssem.append(bankIter2.take(opOut.chars)); + } else { + bankAssem.append(bankIter1.take(opOut.chars)); + } + } + }); + + return exports.pack(len1, len3, newOps, bankAssem.toString()); +}; + +// This function is 95% like _slicerZipperFunc, we just changed two lines to ensure it merges the attribs of deletions properly. +// This is necassary for correct paddiff. But to ensure these changes doesn't affect anything else, we've created a seperate function only used for paddiffs +exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { + // attOp is the op from the sequence that is being operated on, either an + // attribution string or the earlier of two exportss being composed. + // pool can be null if definitely not needed. + //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + if (attOp.opcode == '-') { + exports.copyOp(attOp, opOut); + attOp.opcode = ''; + } else if (!attOp.opcode) { + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + } else { + switch (csOp.opcode) { + case '-': + { + if (csOp.chars <= attOp.chars) { + // delete or delete part + if (attOp.opcode == '=') { + opOut.opcode = '-'; + opOut.chars = csOp.chars; + opOut.lines = csOp.lines; + opOut.attribs = csOp.attribs; //changed by yammer + } + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + csOp.opcode = ''; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // delete and keep going + if (attOp.opcode == '=') { + opOut.opcode = '-'; + opOut.chars = attOp.chars; + opOut.lines = attOp.lines; + opOut.attribs = csOp.attribs; //changed by yammer + } + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; + attOp.opcode = ''; + } + break; + } + case '+': + { + // insert + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + break; + } + case '=': + { + if (csOp.chars <= attOp.chars) { + // keep or keep part + opOut.opcode = attOp.opcode; + opOut.chars = csOp.chars; + opOut.lines = csOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + csOp.opcode = ''; + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // keep and keep going + opOut.opcode = attOp.opcode; + opOut.chars = attOp.chars; + opOut.lines = attOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + attOp.opcode = ''; + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; + } + break; + } + case '': + { + exports.copyOp(attOp, opOut); + attOp.opcode = ''; + break; + } + } + } +}; From 07a267be7a372df4e2d7a899d4af5ba7b30057b8 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sun, 27 Jan 2013 16:45:09 +0000 Subject: [PATCH 10/32] Added colors to pad diff --- src/node/db/API.js | 1 - src/node/db/AuthorManager.js | 4 ++ src/node/db/Pad.js | 3 +- src/node/handler/PadMessageHandler.js | 2 +- src/node/utils/ExportHtml.js | 91 +++++++++++++++++++++++---- src/node/utils/padDiff.js | 9 +-- 6 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index 2c8b91abb..ef341bef9 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -618,7 +618,6 @@ exports.createDiff = function(padID, startRev, endRev, callback){ try { var padDiff = new PadDiff(pad, startRev, endRev); } catch(e) { - // console.warn(e.stack); return callback({stop:e.message}); } var html, authors; diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index 28b2dd91e..667e0605d 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -24,6 +24,10 @@ var db = require("./DB").db; var async = require("async"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +exports.getColorPalette = function(){ + return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"]; +}; + /** * Checks if the author exists */ diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 037886eac..4701e82a3 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -227,7 +227,8 @@ Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback){ if(err){ return callback(err); } - returnTable[author]=colorPalette[colorId]; + //colorId might be a hex color or an number out of the palette + returnTable[author]=colorPalette[colorId] || colorId; callback(); }); diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 434c25ad6..24f72c796 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1028,7 +1028,7 @@ function handleClientReady(client, message) "globalPadId": message.padId, "time": currentTime, }, - "colorPalette": ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"], + "colorPalette": authorManager.getColorPalette(), "clientIp": "127.0.0.1", "userIsGuest": true, "userColor": authorColorId, diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index f385d4704..d9ba3df48 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -1,12 +1,12 @@ /** * Copyright 2009 Google Inc. - * + * * 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. @@ -92,7 +92,7 @@ function getPadHTML(pad, revNum, callback) exports.getPadHTML = getPadHTML; -exports.getHTMLFromAtext = function(pad, atext) +exports.getHTMLFromAtext = function(pad, atext, authorColors) { var apool = pad.apool(); var textLines = atext.text.slice(0, -1).split('\n'); @@ -101,6 +101,42 @@ exports.getHTMLFromAtext = function(pad, atext) var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; var anumMap = {}; + var css = ""; + + var stripDotFromAuthorID = function(id){ + return id.replace(/\./g,'_'); + }; + + if(authorColors){ + css+=""; + } props.forEach(function (propName, i) { @@ -125,22 +161,53 @@ exports.getHTMLFromAtext = function(pad, atext) // Just bold Bold and italics Just italics var taker = Changeset.stringIterator(text); var assem = Changeset.stringAssembler(); - var openTags = []; + + function getSpanClassFor(i){ + //return if author colors are disabled + if (!authorColors) return false; + + var property = props[i]; + + if(property.substr(0,6) === "author"){ + return stripDotFromAuthorID(property); + } + + if(property === "removed"){ + return "removed"; + } + + return false; + } + function emitOpenTag(i) { openTags.unshift(i); - assem.append('<'); - assem.append(tags[i]); - assem.append('>'); + var spanClass = getSpanClassFor(i); + + if(spanClass){ + assem.append(''); + } else { + assem.append('<'); + assem.append(tags[i]); + assem.append('>'); + } } function emitCloseTag(i) { openTags.shift(); - assem.append(''); + var spanClass = getSpanClassFor(i); + + if(spanClass){ + assem.append(''); + } else { + assem.append(''); + } } function orderdCloseTags(tags2close) @@ -303,7 +370,7 @@ exports.getHTMLFromAtext = function(pad, atext) return _processSpaces(assem.toString()); } // end getLineHTML - var pieces = []; + var pieces = [css]; // Need to deal with constraints imposed on HTML lists; can // only gain one level of nesting at once, can't change type diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index b1fa9277a..1b3cf58f5 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -244,17 +244,14 @@ PadDiff.prototype.getHtml = function(callback){ }, //get the authorColor table function(callback){ - /* - self._pad.getAllAuthorColors(function(err, _authorColors){ // TODO + self._pad.getAllAuthorColors(function(err, _authorColors){ if(err){ return callback(err); } authorColors = _authorColors; + callback(); }); - */ - authorColors = {}; - callback(); }, //convert the atext to html function(callback){ @@ -265,7 +262,7 @@ PadDiff.prototype.getHtml = function(callback){ ], function(err){ callback(err, html); }); -} +}; PadDiff.prototype.getAuthors = function(callback){ var self = this; From 878fd7631c2f4df82620fa3b8642ba856519d584 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sun, 27 Jan 2013 17:25:50 +0000 Subject: [PATCH 11/32] Fixed HTML export --- src/node/utils/ExportHtml.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index d9ba3df48..069194880 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -91,8 +91,9 @@ function getPadHTML(pad, revNum, callback) } exports.getPadHTML = getPadHTML; +exports.getHTMLFromAtext = getHTMLFromAtext; -exports.getHTMLFromAtext = function(pad, atext, authorColors) +function getHTMLFromAtext(pad, atext, authorColors) { var apool = pad.apool(); var textLines = atext.text.slice(0, -1).split('\n'); From ed5644d4e5280c73db9a1862fd96dda3a5e8b132 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 27 Jan 2013 17:51:40 +0000 Subject: [PATCH 12/32] docs --- src/node/db/API.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index ef341bef9..0eb404e49 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -574,8 +574,8 @@ createDiff(padID, startRev, endRev) returns an object of diffs from 2 points in Example returns: -TODO {"code":0,"message":"ok","data":null} -TODO {"code":4,"message":"no or wrong API Key","data":null} +{"code":0,"message":"ok","data":{"html":"Welcome to Etherpad Lite!

This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!

Get involved with Etherpad at http://etherpad.org
aw

","authors":["a.HKIv23mEbachFYfH",""]}} +{"code":4,"message":"no or wrong API Key","data":null} */ exports.createDiff = function(padID, startRev, endRev, callback){ //check if rev is a number From f974136d0cf4e5555a81ebd655095efaa10b4643 Mon Sep 17 00:00:00 2001 From: mluto Date: Mon, 28 Jan 2013 10:13:37 +0100 Subject: [PATCH 13/32] Jump to revision given in URL, add current revision to URL --- src/static/js/broadcast_slider.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index 221666de0..821c92215 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -106,6 +106,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) function setSliderPosition(newpos) { newpos = Number(newpos); + window.location.hash = "#" + newpos; if (newpos < 0 || newpos > sliderLength) return; $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); $("a.tlink").map(function() @@ -481,6 +482,18 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) } $("#timeslider").show(); + + var startPos = clientVars.collab_client_vars.rev; + if(window.location.hash.length > 1) + { + var hashRev = Number(window.location.hash.substr(1)); + if(!isNaN(hashRev)) + { + // this is necessary because of the socket.io-event which loads the changesets + setTimeout(function() { setSliderPosition(hashRev); }, 1); + } + } + setSliderLength(clientVars.collab_client_vars.rev); setSliderPosition(clientVars.collab_client_vars.rev); From 437856188239e0f5d6b71c0de2083e9819e996b0 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 28 Jan 2013 16:52:23 +0000 Subject: [PATCH 14/32] change to createDiffHTML --- src/node/db/API.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index a700a4915..f99a43afd 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -658,14 +658,14 @@ exports.getChatHead = function(padID, callback) } /** -createDiff(padID, startRev, endRev) returns an object of diffs from 2 points in a pad +createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad Example returns: {"code":0,"message":"ok","data":{"html":"Welcome to Etherpad Lite!

This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!

Get involved with Etherpad at http://etherpad.org
aw

","authors":["a.HKIv23mEbachFYfH",""]}} {"code":4,"message":"no or wrong API Key","data":null} */ -exports.createDiff = function(padID, startRev, endRev, callback){ +exports.createDiffHTML = function(padID, startRev, endRev, callback){ //check if rev is a number if(startRev !== undefined && typeof startRev != "number") { From 51eff0d659a8364b4a1fb6712a072873b8dbbd59 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 28 Jan 2013 16:53:29 +0000 Subject: [PATCH 15/32] change to createDiffHTML --- src/node/handler/APIHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 1f91c737e..9f86277a0 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -180,7 +180,7 @@ var version = , "deleteGroup" : ["groupID"] , "listPads" : ["groupID"] , "listAllPads" : [] - , "createDiff" : ["padID", "startRev", "endRev"] + , "createDiffHTML" : ["padID", "startRev", "endRev"] , "createPad" : ["padID", "text"] , "createGroupPad" : ["groupID", "padName", "text"] , "createAuthor" : ["name"] From a239158b49159f660546d0f358c34e9ba1bfc199 Mon Sep 17 00:00:00 2001 From: mluto Date: Mon, 28 Jan 2013 20:17:34 +0100 Subject: [PATCH 16/32] Renamed paramSettings to getParameters, to cause less confusion --- src/static/js/pad.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 9f0fba7fa..1b244aea1 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -107,7 +107,7 @@ function randomString() // * the parameter was supplied and matches checkVal // * the parameter was supplied and checkVal is null // callback: the function to call when all above succeeds, `val` is the value supplied by the user -var paramSettings = [ +var getParameters = [ { name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } }, { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').hide(); $('#editorcontainer').css({"top":"0px"}); } }, { name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } }, @@ -126,9 +126,9 @@ function getParams() { var params = getUrlVars() - for(var i = 0; i < paramSettings.length; i++) + for(var i = 0; i < getParameters.length; i++) { - var setting = paramSettings[i]; + var setting = getParameters[i]; var value = params[setting.name]; if(value && (value == setting.checkVal || setting.checkVal == null)) From 3002807741175a7b4f508346bcac9fcb7cf46fc2 Mon Sep 17 00:00:00 2001 From: mluto Date: Mon, 28 Jan 2013 21:12:50 +0100 Subject: [PATCH 17/32] Added tests for revision-jumping --- tests/frontend/specs/timeslider_revisions.js | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/frontend/specs/timeslider_revisions.js b/tests/frontend/specs/timeslider_revisions.js index af59051ab..de8ca52f8 100644 --- a/tests/frontend/specs/timeslider_revisions.js +++ b/tests/frontend/specs/timeslider_revisions.js @@ -57,4 +57,68 @@ describe("timeslider", function(){ }, 6000); }, revs*timePerRev); }); + + it("changes the url when clicking on the timeslider", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + // make some changes to produce 7 revisions + var timePerRev = 900 + , revs = 7; + this.timeout(revs*timePerRev+10000); + for(var i=0; i < revs; i++) { + setTimeout(function() { + // enter 'a' in the first text element + inner$("div").first().sendkeys('a'); + }, timePerRev*i); + } + + setTimeout(function() { + // go to timeslider + $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); + + setTimeout(function() { + var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; + var $sliderBar = timeslider$('#ui-slider-bar'); + + var latestContents = timeslider$('#padcontent').text(); + + var oldUrl = $('#iframe-container iframe')[0].contentWindow.location.hash; + + // Click somewhere on the timeslider + var e = new jQuery.Event('mousedown'); + e.clientX = e.pageX = 150; + e.clientY = e.pageY = 60; + $sliderBar.trigger(e); + + helper.waitFor(function(){ + return $('#iframe-container iframe')[0].contentWindow.location.hash != oldUrl; + }, 6000).always(function(){ + expect( $('#iframe-container iframe')[0].contentWindow.location.hash ).not.to.eql( oldUrl ); + done(); + }); + }, 6000); + }, revs*timePerRev); + }); + + it("jumps to a revision given in the url", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + this.timeout(11000); + inner$("div").first().sendkeys('a'); + + setTimeout(function() { + // go to timeslider + $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); + var timeslider$; + + helper.waitFor(function(){ + timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; + return timeslider$ && timeslider$('#padcontent').text().length == 230; + }, 6000).always(function(){ + expect( timeslider$('#padcontent').text().length ).to.eql( 230 ); + done(); + }); + }, 2500); + }); }); From 0b90e5752be46113b3fcc9ae71a71c68c8a335f3 Mon Sep 17 00:00:00 2001 From: mluto Date: Mon, 28 Jan 2013 21:38:56 +0100 Subject: [PATCH 18/32] Added a test to check the export-url --- tests/frontend/specs/timeslider_revisions.js | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/frontend/specs/timeslider_revisions.js b/tests/frontend/specs/timeslider_revisions.js index de8ca52f8..52f487643 100644 --- a/tests/frontend/specs/timeslider_revisions.js +++ b/tests/frontend/specs/timeslider_revisions.js @@ -121,4 +121,31 @@ describe("timeslider", function(){ }); }, 2500); }); + + it("checks the export url", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + this.timeout(11000); + inner$("div").first().sendkeys('a'); + + setTimeout(function() { + // go to timeslider + $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); + var timeslider$; + var exportLink; + + helper.waitFor(function(){ + timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; + if(!timeslider$) + return false; + exportLink = timeslider$('#exportplaina').attr('href'); + if(!exportLink) + return false; + return exportLink.substr(exportLink.length - 12) == "0/export/txt"; + }, 6000).always(function(){ + expect( exportLink.substr(exportLink.length - 12) ).to.eql( "0/export/txt" ); + done(); + }); + }, 2500); + }); }); From 01fe5c183d0e123c5682ac5274df94103b6bd291 Mon Sep 17 00:00:00 2001 From: mluto Date: Mon, 28 Jan 2013 21:44:21 +0100 Subject: [PATCH 19/32] Only set url if the revsion will actually be loaded --- src/static/js/broadcast_slider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index 821c92215..08ac08b5a 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -106,8 +106,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) function setSliderPosition(newpos) { newpos = Number(newpos); - window.location.hash = "#" + newpos; if (newpos < 0 || newpos > sliderLength) return; + window.location.hash = "#" + newpos; $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); $("a.tlink").map(function() { From 10c2ac2a69e4b1b1d476f824ddeae42d4ff2d1de Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 28 Jan 2013 21:52:14 +0000 Subject: [PATCH 20/32] have a nice changelog makes it easier for when we release --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2cabd99c..ec377db6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 1.2.7 + * NEW: Visit a specific revision in the timeslider by suffixing #%revNumber% IE http://localhost/p/test/timeslider#12 + * NEW: Link to plugin on Admin page allows admins to easily see plugin details in a new window by clicking on the plugin name + * NEW: Automatically see plugins that require update and be able to one click update + * NEW: API endpoints for Chat .. getChatHistory, getChatHead + * NEW: API endpoint to see a pad diff in HTML format from revision x to revision y .. createPadDiffHTML + * NEW: Real time plugin search & unified menu UI for admin pages + * Fix: make docs + * Fix: Timeslider UI bug with slider not being in position + * Fix: IE8 language issue where it wouldn't load pads due to IE8 suckling on the bussum of hatrid + * Fix: Import timeout issue + * Fix: Import now works if Params are set in pad URL + * Fix: Convert script + * Other: Various new language strings + * Other: Clean up the getParams functionality + * Other: Various new EEJS blocks: index, timeslider, html etc. + # 1.2.6 * Fix: Package file UeberDB reference * New #users EEJS block for plugins From 2ae3dae492e294d415df9de23898346abc59dd5c Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 00:46:36 +0000 Subject: [PATCH 21/32] introduce gritter files and ensure its available --- src/node/utils/tar.json | 1 + src/static/js/gritter.js | 418 +++++++++++++++++++++++++++++++++++++++ src/static/js/pad.js | 1 + 3 files changed, 420 insertions(+) create mode 100644 src/static/js/gritter.js diff --git a/src/node/utils/tar.json b/src/node/utils/tar.json index 080da4428..b010f851b 100644 --- a/src/node/utils/tar.json +++ b/src/node/utils/tar.json @@ -14,6 +14,7 @@ , "pad_savedrevs.js" , "pad_connectionstatus.js" , "chat.js" + , "gritter.js" , "$tinycon/tinycon.js" , "excanvas.js" , "farbtastic.js" diff --git a/src/static/js/gritter.js b/src/static/js/gritter.js new file mode 100644 index 000000000..35076f174 --- /dev/null +++ b/src/static/js/gritter.js @@ -0,0 +1,418 @@ +/* + * Gritter for jQuery + * http://www.boedesign.com/ + * + * Copyright (c) 2012 Jordan Boesch + * Dual licensed under the MIT and GPL licenses. + * + * Date: February 24, 2012 + * Version: 1.7.4 + */ + +(function($){ + + /** + * Set it up as an object under the jQuery namespace + */ + $.gritter = {}; + + /** + * Set up global options that the user can over-ride + */ + $.gritter.options = { + position: '', + class_name: '', // could be set to 'gritter-light' to use white notifications + fade_in_speed: 'medium', // how fast notifications fade in + fade_out_speed: 1000, // how fast the notices fade out + time: 6000 // hang on the screen for... + } + + /** + * Add a gritter notification to the screen + * @see Gritter#add(); + */ + $.gritter.add = function(params){ + + try { + return Gritter.add(params || {}); + } catch(e) { + + var err = 'Gritter Error: ' + e; + (typeof(console) != 'undefined' && console.error) ? + console.error(err, params) : + alert(err); + + } + + } + + /** + * Remove a gritter notification from the screen + * @see Gritter#removeSpecific(); + */ + $.gritter.remove = function(id, params){ + Gritter.removeSpecific(id, params || {}); + } + + /** + * Remove all notifications + * @see Gritter#stop(); + */ + $.gritter.removeAll = function(params){ + Gritter.stop(params || {}); + } + + /** + * Big fat Gritter object + * @constructor (not really since its object literal) + */ + var Gritter = { + + // Public - options to over-ride with $.gritter.options in "add" + position: '', + fade_in_speed: '', + fade_out_speed: '', + time: '', + + // Private - no touchy the private parts + _custom_timer: 0, + _item_count: 0, + _is_setup: 0, + _tpl_close: '
', + _tpl_title: '[[title]]', + _tpl_item: '', + _tpl_wrap: '
', + + /** + * Add a gritter notification to the screen + * @param {Object} params The object that contains all the options for drawing the notification + * @return {Integer} The specific numeric id to that gritter notification + */ + add: function(params){ + // Handle straight text + if(typeof(params) == 'string'){ + params = {text:params}; + } + + // We might have some issues if we don't have a title or text! + if(!params.text){ + throw 'You must supply "text" parameter.'; + } + + // Check the options and set them once + if(!this._is_setup){ + this._runSetup(); + } + + // Basics + var title = params.title, + text = params.text, + image = params.image || '', + sticky = params.sticky || false, + item_class = params.class_name || $.gritter.options.class_name, + position = $.gritter.options.position, + time_alive = params.time || ''; + + this._verifyWrapper(); + + this._item_count++; + var number = this._item_count, + tmp = this._tpl_item; + + // Assign callbacks + $(['before_open', 'after_open', 'before_close', 'after_close']).each(function(i, val){ + Gritter['_' + val + '_' + number] = ($.isFunction(params[val])) ? params[val] : function(){} + }); + + // Reset + this._custom_timer = 0; + + // A custom fade time set + if(time_alive){ + this._custom_timer = time_alive; + } + + var image_str = (image != '') ? '' : '', + class_name = (image != '') ? 'gritter-with-image' : 'gritter-without-image'; + + // String replacements on the template + if(title){ + title = this._str_replace('[[title]]',title,this._tpl_title); + }else{ + title = ''; + } + + tmp = this._str_replace( + ['[[title]]', '[[text]]', '[[close]]', '[[image]]', '[[number]]', '[[class_name]]', '[[item_class]]'], + [title, text, this._tpl_close, image_str, this._item_count, class_name, item_class], tmp + ); + + // If it's false, don't show another gritter message + if(this['_before_open_' + number]() === false){ + return false; + } + + $('#gritter-notice-wrapper').addClass(position).append(tmp); + + var item = $('#gritter-item-' + this._item_count); + + item.fadeIn(this.fade_in_speed, function(){ + Gritter['_after_open_' + number]($(this)); + }); + + if(!sticky){ + this._setFadeTimer(item, number); + } + + // Bind the hover/unhover states + $(item).bind('mouseenter mouseleave', function(event){ + if(event.type == 'mouseenter'){ + if(!sticky){ + Gritter._restoreItemIfFading($(this), number); + } + } + else { + if(!sticky){ + Gritter._setFadeTimer($(this), number); + } + } + Gritter._hoverState($(this), event.type); + }); + + // Clicking (X) makes the perdy thing close + $(item).find('.gritter-close').click(function(){ + Gritter.removeSpecific(number, {}, null, true); + }); + + return number; + + }, + + /** + * If we don't have any more gritter notifications, get rid of the wrapper using this check + * @private + * @param {Integer} unique_id The ID of the element that was just deleted, use it for a callback + * @param {Object} e The jQuery element that we're going to perform the remove() action on + * @param {Boolean} manual_close Did we close the gritter dialog with the (X) button + */ + _countRemoveWrapper: function(unique_id, e, manual_close){ + + // Remove it then run the callback function + e.remove(); + this['_after_close_' + unique_id](e, manual_close); + + // Check if the wrapper is empty, if it is.. remove the wrapper + if($('.gritter-item-wrapper').length == 0){ + $('#gritter-notice-wrapper').remove(); + } + + }, + + /** + * Fade out an element after it's been on the screen for x amount of time + * @private + * @param {Object} e The jQuery element to get rid of + * @param {Integer} unique_id The id of the element to remove + * @param {Object} params An optional list of params to set fade speeds etc. + * @param {Boolean} unbind_events Unbind the mouseenter/mouseleave events if they click (X) + */ + _fade: function(e, unique_id, params, unbind_events){ + + var params = params || {}, + fade = (typeof(params.fade) != 'undefined') ? params.fade : true, + fade_out_speed = params.speed || this.fade_out_speed, + manual_close = unbind_events; + + this['_before_close_' + unique_id](e, manual_close); + + // If this is true, then we are coming from clicking the (X) + if(unbind_events){ + e.unbind('mouseenter mouseleave'); + } + + // Fade it out or remove it + if(fade){ + + e.animate({ + opacity: 0 + }, fade_out_speed, function(){ + e.animate({ height: 0 }, 300, function(){ + Gritter._countRemoveWrapper(unique_id, e, manual_close); + }) + }) + + } + else { + + this._countRemoveWrapper(unique_id, e); + + } + + }, + + /** + * Perform actions based on the type of bind (mouseenter, mouseleave) + * @private + * @param {Object} e The jQuery element + * @param {String} type The type of action we're performing: mouseenter or mouseleave + */ + _hoverState: function(e, type){ + + // Change the border styles and add the (X) close button when you hover + if(type == 'mouseenter'){ + + e.addClass('hover'); + + // Show close button + e.find('.gritter-close').show(); + + } + // Remove the border styles and hide (X) close button when you mouse out + else { + + e.removeClass('hover'); + + // Hide close button + e.find('.gritter-close').hide(); + + } + + }, + + /** + * Remove a specific notification based on an ID + * @param {Integer} unique_id The ID used to delete a specific notification + * @param {Object} params A set of options passed in to determine how to get rid of it + * @param {Object} e The jQuery element that we're "fading" then removing + * @param {Boolean} unbind_events If we clicked on the (X) we set this to true to unbind mouseenter/mouseleave + */ + removeSpecific: function(unique_id, params, e, unbind_events){ + + if(!e){ + var e = $('#gritter-item-' + unique_id); + } + + // We set the fourth param to let the _fade function know to + // unbind the "mouseleave" event. Once you click (X) there's no going back! + this._fade(e, unique_id, params || {}, unbind_events); + + }, + + /** + * If the item is fading out and we hover over it, restore it! + * @private + * @param {Object} e The HTML element to remove + * @param {Integer} unique_id The ID of the element + */ + _restoreItemIfFading: function(e, unique_id){ + + clearTimeout(this['_int_id_' + unique_id]); + e.stop().css({ opacity: '', height: '' }); + + }, + + /** + * Setup the global options - only once + * @private + */ + _runSetup: function(){ + + for(opt in $.gritter.options){ + this[opt] = $.gritter.options[opt]; + } + this._is_setup = 1; + + }, + + /** + * Set the notification to fade out after a certain amount of time + * @private + * @param {Object} item The HTML element we're dealing with + * @param {Integer} unique_id The ID of the element + */ + _setFadeTimer: function(e, unique_id){ + + var timer_str = (this._custom_timer) ? this._custom_timer : this.time; + this['_int_id_' + unique_id] = setTimeout(function(){ + Gritter._fade(e, unique_id); + }, timer_str); + + }, + + /** + * Bring everything to a halt + * @param {Object} params A list of callback functions to pass when all notifications are removed + */ + stop: function(params){ + + // callbacks (if passed) + var before_close = ($.isFunction(params.before_close)) ? params.before_close : function(){}; + var after_close = ($.isFunction(params.after_close)) ? params.after_close : function(){}; + + var wrap = $('#gritter-notice-wrapper'); + before_close(wrap); + wrap.fadeOut(function(){ + $(this).remove(); + after_close(); + }); + + }, + + /** + * An extremely handy PHP function ported to JS, works well for templating + * @private + * @param {String/Array} search A list of things to search for + * @param {String/Array} replace A list of things to replace the searches with + * @return {String} sa The output + */ + _str_replace: function(search, replace, subject, count){ + + var i = 0, j = 0, temp = '', repl = '', sl = 0, fl = 0, + f = [].concat(search), + r = [].concat(replace), + s = subject, + ra = r instanceof Array, sa = s instanceof Array; + s = [].concat(s); + + if(count){ + this.window[count] = 0; + } + + for(i = 0, sl = s.length; i < sl; i++){ + + if(s[i] === ''){ + continue; + } + + for (j = 0, fl = f.length; j < fl; j++){ + + temp = s[i] + ''; + repl = ra ? (r[j] !== undefined ? r[j] : '') : r[0]; + s[i] = (temp).split(f[j]).join(repl); + + if(count && s[i] !== temp){ + this.window[count] += (temp.length-s[i].length) / f[j].length; + } + + } + } + + return sa ? s : s[0]; + + }, + + /** + * A check to make sure we have something to wrap our notices with + * @private + */ + _verifyWrapper: function(){ + + if($('#gritter-notice-wrapper').length == 0){ + $('body').append(this._tpl_wrap); + } + + } + + } + +})(jQuery); diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 1b244aea1..6a115868e 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -48,6 +48,7 @@ var colorutils = require('./colorutils').colorutils; var createCookie = require('./pad_utils').createCookie; var readCookie = require('./pad_utils').readCookie; var randomString = require('./pad_utils').randomString; +var gritter = require('./gritter').gritter; var hooks = require('./pluginfw/hooks'); From 1c7810783cf8a4b4b1359266b2af7b87fda4160a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 01:55:36 +0000 Subject: [PATCH 22/32] gritter now implemented --- src/static/css/pad.css | 92 +++++++++++++++++++++++++++++++++++++ src/static/img/gritter.png | Bin 0 -> 4880 bytes src/static/js/chat.js | 34 ++++++++++++-- src/static/js/gritter.js | 1 - src/static/js/pad.js | 7 +++ src/templates/pad.html | 2 - 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/static/img/gritter.png diff --git a/src/static/css/pad.css b/src/static/css/pad.css index bbbadbc18..6034b5edc 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -925,3 +925,95 @@ input[type=checkbox] { #wrongPassword{ display:none; } + +/* gritter stuff */ +#gritter-notice-wrapper { + position:fixed; + top:20px; + right:20px; + width:301px; + z-index:9999; +} +#gritter-notice-wrapper.bottom-right { + top: auto; + left: auto; + bottom: 20px; + right: 20px; +} +.gritter-item-wrapper { + position:relative; + margin:0 0 10px 0; +} + +.gritter-top { + background:url(../../static/img/gritter.png) no-repeat left -30px; + height:10px; +} +.hover .gritter-top { + background-position:right -30px; +} +.gritter-bottom { + background:url(../../static/img/gritter.png) no-repeat left bottom; + height:8px; + margin:0; +} +.hover .gritter-bottom { + background-position: bottom right; +} +.gritter-item { + display:block; + background:url(../../static/img/gritter.png) no-repeat left -40px; + color:#eee; + padding:2px 11px 8px 11px; + font-size: 11px; + font-family:verdana; +} +.hover .gritter-item { + background-position:right -40px; +} +.gritter-item p { + padding:0; + margin:0; +} +.gritter-close { + display:none; + position:absolute; + top:5px; + left:3px; + background:url('../../static/img/gritter.png') no-repeat left top; + cursor:pointer; + width:30px; + height:30px; +} +.gritter-title { + font-size:14px; + font-weight:bold; + padding:0 0 7px 0; + display:block; + text-shadow:1px 1px 0 #000; /* Not supported by IE :( */ +} +.gritter-image { + width:48px; + height:48px; + float:left; +} +.gritter-with-image, +.gritter-without-image { + padding:0 0 5px 0; +} +.gritter-with-image { + width:220px; + float:right; +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-close { + color: #222; +} +.gritter-light .gritter-title { + text-shadow: none; +} + +/* End of gritter stuff */ diff --git a/src/static/img/gritter.png b/src/static/img/gritter.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca3bc0a0f8068194082db9719d6e20a8645985f GIT binary patch literal 4880 zcmeI0`8Qj6AIC49ZdA3?#VD1|W2S1WstH<4TGLvKmc|nMR$BX5QcFZTEuGdg?WDx)$W4Lb*_@?=xsSEf}j=ky@>l}G27U}m#5O6s#(m&{wO}JlhkW-Lf zU_$giL8bukW1Y>F%Qx?`7RFH-aDH*uia`wL`Mn!ygw7jo^Sf?_g;qYUK78Up;hi#z z0St?nllk&5`)cZ65uGlBg_2T|JrP}TgBkLo=tSJ@XAfU$&EL7YzxtA`LU^g^nKMq$ z{n8TJl2YE%rgwiJwF-ytCD8spQYde5nv?yk5tp=7swE19F~}KkJ3~`03NlI38_7@Ddgo5^^R>&8%f}dyS!(+urDQF{{E&=N15T)nOd>5$6YQ z7%x&j-XS>RCJLpj+;o?|{$i6S4=pA)F&K=6nukenKlZu(^Yi$oIPUOLrixKKO|;aC z5^nH0U6e!?b>G>WN-S)O+P%Y%U}Qfk2$CAd!l_gbFAW zN;*C+b zN?x%aY+OB~I~Dr+`sB5xQ#D?#dk(4^Kg}o3_b@jW$2Q)L2IEk6&d$yo`MMqg-gv7K zjaqoWx-DYAF5r8m!d$0ikQo;q9-hg;p(Rb#x;%=4g)6*Q9}f0oq$$dFloAT z?0oNW;G9J+g#FYUTC-g(}blG#-ka#OW_af#=An@unq4dw7g}TX+la z1^1irXuwtul!(F+4TrE|gW&$p>u>&4G#{a(M1s;rc~FJ!wD?6VPyI4=d+o#{Sz zYqRr-5)%G?m_-du{|~p4P%_^toGnVyq6afd%sN3k9c*uiog38Kv8eZs__ZjQq9Xa} z_nD!pu$1kl^(hlxTg*GhpCq$J^f|}cA{qEaX~@!c5X z=&6CRhXp<T` z%=Focl-W5NTbW$sb>6+;l7v~(`WxNG&rTJ|!0oN0Q-gTBR{B~oFV=Npvn1jv&LG%< zvpUn86w$WweyInN!X-5xv>Xa0(;9v?*}SsZK8Wsho&*ixEyS^J|C7C+&*w{x!O(jS z=E*$#nTDfeuoyWpy7nhz`@bCN>YwE1Sqvs!EwQ4stZaI*(wQLf2hTk6s~#cyRC^KR zWT*d1>PJODwRiAEo(qBU8$Pd!R$m{($WB==gU%^Nw#=2c9KD7Uq?e=1IT8^M(sn_T zW2Ke=%_fcuBR2?A%Q@)1>-*mZ0)gNs5-dKvQD(Vl!X?(<@{Au1 zf_D1rd(aa3>~%dmG}nV&B^)BBzR`_I|eO-JpJDZEduLt1ELPKR?r|E7oo!s!>Y&U}NMeN>4B|)ybwuu{}-yAsS zZ=qdz_9PYm#r85(%cLnP^K7T9v*x=V$9sBgYqtC2D#2im8fO45N2CNQ1Rl$dJG7IH z@}$*J&4xR+Rtb|@kT^WE7w2|u<&&#i)zafK!g`{x**!fsZ$K8X^bdizduP?Gl4sZu z3955Y|N3EgUNk?a>c^eWD3GKKwzx>nb|=a%P#pfvezH1^-7dsJj$qH8y57PtFhfS7 zQcMdVWK@sTCZ!i{fV<%hGO*Z$>I?CxXb$my>>klfD zo~=zUZ5Mt`mU(-r#aH|rEJdVW8rKcw&Jl3^YG9LvEwZSivwcOyW~Z*%+1VL}4im#B z-#i>OYG27DQmOn$Ok&S~t+R-pC*d9PHEuS8xu_mT{LUmQdEfn6B-x{|hwqZJNTL^=2JWQDfnaL12k|{h3MpBgAWF z9eDd02H#ek`~u}prbGI{UgG@=ynRn)dKWE@)CPRR@k*0rY85X8p1u| znO?{X>K(7@BH8T=jr5Z(N|I_#iUSgnTJ~;z6&fnu;`i@4r$h2={$po-^^m4a){jSe zn8fi`q=`z$D+<_{rJ*izBn06H%pP1c)#z++R>md6*uAVnO^`4`BV4Ng=(NM zF6$st2W0p8e~^7}{L-N($4^m!`)(AVboZfmFLv+KO+9s7_fm+=549mO;F?EQ<-afa zPVBp2zWe2STzpT<|EDaXC=Z5X9Ck(J-mOgTL%j06S1)tG&Msd^x#bpqfQ5k+6(9d{ zX4zASfd=*DE~KeML|LZ#;oIBITbJL+r`Nd8G#-?WSIVR{yRHQU%s{!UP;%e={5)3K zcjWFuZCBk9N7LwU82CXFVQqTDLGj!*{%n_?R=Zl;tv>7p1CLt&hxNM-$oJ;!_JY%6 zELi5Mh+V=JvaptUTQ;o}H{0cL%tz6yewKm5k-TXai=d(T^*3soJn?}z#RY}icK*0x*m$+nzzb@R!~_!{?)SFN|i*}yKK Zf3P5-;PGuo@D~?gbJgKWjfL<1{{eTAuA%?{ literal 0 HcmV?d00001 diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 205294a85..f868d869a 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -22,7 +22,6 @@ var padutils = require('./pad_utils').padutils; var padcookie = require('./pad_cookie').padcookie; - var Tinycon = require('tinycon/tinycon'); var chat = (function() @@ -36,6 +35,7 @@ var chat = (function() { $("#chaticon").hide(); $("#chatbox").show(); + $("#gritter-notice-wrapper").hide(); self.scrollDown(); chatMentions = 0; Tinycon.setBubble(0); @@ -62,6 +62,8 @@ var chat = (function() $("#chatcounter").text("0"); $("#chaticon").show(); $("#chatbox").hide(); + $.gritter.removeAll(); + $("#gritter-notice-wrapper").show(); }, scrollDown: function() { @@ -130,17 +132,41 @@ var chat = (function() // is the users focus already in the chatbox? var alreadyFocused = $("#chatinput").is(":focus"); + // does the user already have the chatbox open? + var chatOpen = $("#chatbox").is(":visible"); + $("#chatcounter").text(count); // chat throb stuff -- Just make it throw for twice as long - if(wasMentioned && !alreadyFocused && !isHistoryAdd) + if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { // If the user was mentioned show for twice as long and flash the browser window - $('#chatthrob').html(""+authorName+"" + ": " + text).show().delay(4000).hide(400); + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: authorName, + // (string | mandatory) the text inside the notification + text: text, + // (bool | optional) if you want it to fade out on its own or just sit there + sticky: true, + // (int | optional) the time you want it to be alive for before fading out + time: '2000' + }); + chatMentions++; Tinycon.setBubble(chatMentions); } else { - $('#chatthrob').html(""+authorName+"" + ": " + text).show().delay(2000).hide(400); + if(!chatOpen){ + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: authorName, + // (string | mandatory) the text inside the notification + text: text, + // (bool | optional) if you want it to fade out on its own or just sit there + sticky: false, + // (int | optional) the time you want it to be alive for before fading out + time: '4000' + }); + } } } // Clear the chat mentions when the user clicks on the chat input box diff --git a/src/static/js/gritter.js b/src/static/js/gritter.js index 35076f174..c32cc758e 100644 --- a/src/static/js/gritter.js +++ b/src/static/js/gritter.js @@ -10,7 +10,6 @@ */ (function($){ - /** * Set it up as an object under the jQuery namespace */ diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 6a115868e..93e785cdd 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -364,6 +364,13 @@ function handshake() }); } +$.extend($.gritter.options, { + position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1) + fade_in_speed: 'medium', // how fast notifications fade in (string or int) + fade_out_speed: 2000, // how fast the notices fade out + time: 6000 // hang on the screen for... +}); + var pad = { // don't access these directly from outside this file, except // for debugging diff --git a/src/templates/pad.html b/src/templates/pad.html index cb88c1c1a..76df5133c 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -365,8 +365,6 @@ <% e.end_block(); %> -
-
From fa7952523e2ca14e8f97d4085cfa006cc22eb25a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 02:08:37 +0000 Subject: [PATCH 23/32] spacing --- src/static/js/pad.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 93e785cdd..27dd3b737 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -365,10 +365,10 @@ function handshake() } $.extend($.gritter.options, { - position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1) - fade_in_speed: 'medium', // how fast notifications fade in (string or int) - fade_out_speed: 2000, // how fast the notices fade out - time: 6000 // hang on the screen for... + position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1) + fade_in_speed: 'medium', // how fast notifications fade in (string or int) + fade_out_speed: 2000, // how fast the notices fade out + time: 6000 // hang on the screen for... }); var pad = { From a7361f5ce0063cd343f0df1ea22e331aea717271 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 12:09:16 +0000 Subject: [PATCH 24/32] make tinycon update on all chat messages not just ones that mention your name --- src/static/js/chat.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/static/js/chat.js b/src/static/js/chat.js index f868d869a..5bd88e2e3 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -161,11 +161,14 @@ var chat = (function() title: authorName, // (string | mandatory) the text inside the notification text: text, + // (bool | optional) if you want it to fade out on its own or just sit there sticky: false, // (int | optional) the time you want it to be alive for before fading out time: '4000' }); + Tinycon.setBubble(count); + } } } From daaa650a1be95e38e95f6fedff7e39a63cd847fe Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 12:15:11 +0000 Subject: [PATCH 25/32] remove message about requiring comments --- src/static/js/chat.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 5bd88e2e3..56dcbfb0e 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -1,9 +1,3 @@ -/** - * This code is mostly from the old Etherpad. Please help us to comment this code. - * This helps other people to understand this code better and helps them to improve it. - * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED - */ - /** * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * From edd8b1204956e9e05b0bc29cfa60c2ec016637d6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 16:45:09 +0000 Subject: [PATCH 26/32] push authorID to author object and return via api... --- src/node/handler/PadMessageHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 24f72c796..ceef5de74 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1536,6 +1536,7 @@ exports.padUsers = function (padID, callback) { } var aid = sessioninfos[ix].author; authorManager.getAuthor( aid, function ( err, author ) { + author.id = aid; authors.push( author ); if ( authors.length === pad2sessions[padID].length ) { callback(null, {padUsers: authors}); From a19ad983f1440fbac395793931046a857bbb2dfd Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 29 Jan 2013 16:48:56 +0000 Subject: [PATCH 27/32] docs for api change --- doc/api/http_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/http_api.md b/doc/api/http_api.md index a7fc0bedd..0543ef71a 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -355,7 +355,7 @@ returns the number of user that are currently editing this pad returns the list of users that are currently editing this pad *Example returns:* - * `{code: 0, message:"ok", data: {padUsers: [{colorId:"#c1a9d9","name":"username1","timestamp":1345228793126},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042}]}}` + * `{code: 0, message:"ok", data: {padUsers: [{colorId:"#c1a9d9","name":"username1","timestamp":1345228793126,"id":"a.n4gEeMLsvg12452n"},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042,"id":"a.n4gEeMLsvg12452n"}]}}` * `{code: 0, message:"ok", data: {padUsers: []}}` #### deletePad(padID) From b5eeeb7dc20c407d3fc8d905b1e4184395ae66c3 Mon Sep 17 00:00:00 2001 From: mluto Date: Wed, 30 Jan 2013 12:16:32 +0100 Subject: [PATCH 28/32] Don't show notifications when loading the chat-messages. --- src/static/js/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 205294a85..30c38e214 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -122,7 +122,7 @@ var chat = (function() $("#chattext").append(html); //should we increment the counter?? - if(increment) + if(increment && !isHistoryAdd) { var count = Number($("#chatcounter").text()); count++; From f2742c5b63d62be5f6e6e677fed02f00b24d7981 Mon Sep 17 00:00:00 2001 From: mluto Date: Wed, 30 Jan 2013 15:21:25 +0100 Subject: [PATCH 29/32] Check for missing userInfo --- src/node/handler/PadMessageHandler.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 434c25ad6..f46279131 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -473,6 +473,11 @@ function handleSuggestUserName(client, message) function handleUserInfoUpdate(client, message) { //check if all ok + if(message.data.userInfo == null) + { + messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no userInfo!"); + return; + } if(message.data.userInfo.colorId == null) { messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!"); From 7e48e025c7df00ec80924bc1afabc6a8459ea444 Mon Sep 17 00:00:00 2001 From: mluto Date: Wed, 30 Jan 2013 15:28:54 +0100 Subject: [PATCH 30/32] Check for missing payload on CLIENT_MESSAGE --- src/node/handler/PadMessageHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index f46279131..8a1b8782c 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -210,6 +210,7 @@ exports.handleMessage = function(client, message) } else if (message.data.type == "SAVE_REVISION") { handleSaveRevisionMessage(client, message); } else if (message.data.type == "CLIENT_MESSAGE" && + message.data.payload != null && message.data.payload.type == "suggestUserName") { handleSuggestUserName(client, message); } else { From d669779eb878ed799661e0ed24ef2455154a14f5 Mon Sep 17 00:00:00 2001 From: mluto Date: Wed, 30 Jan 2013 15:45:48 +0100 Subject: [PATCH 31/32] Renamed "Saved Revisions" to "Save Revision" since it saves a revision.. --- src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index eea35cc53..bef6dfd05 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -14,7 +14,7 @@ "pad.toolbar.clearAuthorship.title": "Clear Authorship Colors", "pad.toolbar.import_export.title": "Import/Export from/to different file formats", "pad.toolbar.timeslider.title": "Timeslider", - "pad.toolbar.savedRevision.title": "Saved Revisions", + "pad.toolbar.savedRevision.title": "Save Revision", "pad.toolbar.settings.title": "Settings", "pad.toolbar.embed.title": "Embed this pad", "pad.toolbar.showusers.title": "Show the users on this pad", From 594d53ee8b768a77fdb4342113461cc8fc03c489 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 30 Jan 2013 14:58:23 +0000 Subject: [PATCH 32/32] changelog and package file --- CHANGELOG.md | 5 ++++- src/package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec377db6d..e0fde550e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,20 @@ # 1.2.7 + * NEW: notifications are now modularized and can be stacked * NEW: Visit a specific revision in the timeslider by suffixing #%revNumber% IE http://localhost/p/test/timeslider#12 * NEW: Link to plugin on Admin page allows admins to easily see plugin details in a new window by clicking on the plugin name * NEW: Automatically see plugins that require update and be able to one click update * NEW: API endpoints for Chat .. getChatHistory, getChatHead * NEW: API endpoint to see a pad diff in HTML format from revision x to revision y .. createPadDiffHTML * NEW: Real time plugin search & unified menu UI for admin pages + * Fix: MAJOR issue where server could be crashed by malformed client message + * Fix: AuthorID is now included in padUsers API response * Fix: make docs * Fix: Timeslider UI bug with slider not being in position * Fix: IE8 language issue where it wouldn't load pads due to IE8 suckling on the bussum of hatrid * Fix: Import timeout issue * Fix: Import now works if Params are set in pad URL * Fix: Convert script - * Other: Various new language strings + * Other: Various new language strings and update/bugfixes of others * Other: Clean up the getParams functionality * Other: Various new EEJS blocks: index, timeslider, html etc. diff --git a/src/package.json b/src/package.json index fd48bce6a..6d05e6a25 100644 --- a/src/package.json +++ b/src/package.json @@ -46,5 +46,5 @@ "engines" : { "node" : ">=0.6.0", "npm" : ">=1.0" }, - "version" : "1.2.6" + "version" : "1.2.7" }