From 67a07307100e588adf68ca075c307a293b7ac177 Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 5 Mar 2013 21:28:38 +0100 Subject: [PATCH 001/104] added nodemailer to package.json - fix #1586 --- src/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/package.json b/src/package.json index 44d03d809..38085650e 100644 --- a/src/package.json +++ b/src/package.json @@ -24,6 +24,7 @@ "uglify-js" : "1.2.5", "formidable" : "1.0.9", "log4js" : "0.5.x", + "nodemailer" : "0.3.x", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", "npm" : "1.1.x", From d8ca1b9139c95fa47e9a55220061aef7d3e29b2e Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 5 Mar 2013 21:29:44 +0100 Subject: [PATCH 002/104] Update settings.json.template --- settings.json.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.json.template b/settings.json.template index ec0e6f837..40b7289f6 100644 --- a/settings.json.template +++ b/settings.json.template @@ -123,7 +123,7 @@ }*/ /* , { "type": "logLevelFilter" - , "level": "error" // filters out all log messages that have a lower level than "error" + , "level": "error" // filters out all log messages that have a lower or same level than "error" , "appender": { "type": "smtp" , "subject": "An error occured in your EPL instance!" From d464ea9817045dc742205a5c03aeff59e9d2ace3 Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 5 Mar 2013 22:36:51 +0100 Subject: [PATCH 003/104] Update settings.json.template --- settings.json.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.json.template b/settings.json.template index 40b7289f6..ec0e6f837 100644 --- a/settings.json.template +++ b/settings.json.template @@ -123,7 +123,7 @@ }*/ /* , { "type": "logLevelFilter" - , "level": "error" // filters out all log messages that have a lower or same level than "error" + , "level": "error" // filters out all log messages that have a lower level than "error" , "appender": { "type": "smtp" , "subject": "An error occured in your EPL instance!" From 83a820b720cb6659dcd2e11ea1103459563839d8 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 12 Mar 2013 16:59:15 +0000 Subject: [PATCH 004/104] new function for handling custom messages, allows objects to be sent, before we only allowed strings --- src/node/handler/PadMessageHandler.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index c046f130a..076f31a58 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -254,6 +254,23 @@ function handleSaveRevisionMessage(client, message){ }); } +/** + * Handles a custom message, different to the function below as it handles objects not strings and you can direct the message to specific sessionID + * + * @param msg {Object} the message we're sending + * @param sessionID {string} the socketIO session to which we're sending this message + */ +exports.handleCustomObjectMessage = function (msg, sessionID, cb) { + if(sessionID){ // If a sessionID is targeted then send directly to this sessionID + io.sockets.socket(sessionID).emit(msg); // send a targeted message + }else{ + socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad + } + + cb(null, {}); +} + + /** * Handles a custom message (sent via HTTP API request) * From 5a9393d5da5b50232fdf055c5d335e6b0ce252cc Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 16 Mar 2013 09:46:35 +0100 Subject: [PATCH 005/104] Update version checks --- bin/installDeps.sh | 4 ++-- bin/installOnWindows.bat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/installDeps.sh b/bin/installDeps.sh index 9763f41ba..2f97090b7 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -44,8 +44,8 @@ fi #check node version NODE_VERSION=$(node --version) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) -if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ]; then - echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x or v0.8.x" >&2 +if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" && [ ! $NODE_V_MINOR = "v0.10" ]; then + echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x, v0.8.x or v0.10.x" >&2 exit 1 fi diff --git a/bin/installOnWindows.bat b/bin/installOnWindows.bat index f678672b1..f74529829 100644 --- a/bin/installOnWindows.bat +++ b/bin/installOnWindows.bat @@ -8,7 +8,7 @@ cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && ex echo _ echo Checking node version... -set check_version="if(['6','8'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.6.x or v0.8.x'); process.exit(1) }" +set check_version="if(['6','8','10'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.6.x, v0.8.x or v0.10.x'); process.exit(1) }" cmd /C node -e %check_version% || exit /B 1 echo _ From cd9c78998e84568d85bb850d892adee5ea5710b6 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 16 Mar 2013 09:47:10 +0100 Subject: [PATCH 006/104] Fix path.join in Settings.js --- src/node/utils/Settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 45f81aa5f..901ac5146 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -142,7 +142,7 @@ exports.abiwordAvailable = function() exports.reloadSettings = function reloadSettings() { // Discover where the settings file lives var settingsFilename = argv.settings || "settings.json"; - settingsFilename = path.resolve(path.join(root, settingsFilename)); + settingsFilename = path.resolve(path.join(exports.root, settingsFilename)); var settingsStr; try{ From 13ad46aa67cf87d2c5ee86372a101d44f4de7b65 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 16 Mar 2013 13:19:12 +0000 Subject: [PATCH 007/104] a safer approach I think but still be careful --- src/node/handler/PadMessageHandler.js | 11 ++++++----- src/static/js/collab_client.js | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index e44ced051..b254053d9 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -261,12 +261,13 @@ function handleSaveRevisionMessage(client, message){ * @param sessionID {string} the socketIO session to which we're sending this message */ exports.handleCustomObjectMessage = function (msg, sessionID, cb) { - if(sessionID){ // If a sessionID is targeted then send directly to this sessionID - io.sockets.socket(sessionID).emit(msg); // send a targeted message - }else{ - socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad + if(msg.type === "CUSTOM"){ + if(sessionID){ // If a sessionID is targeted then send directly to this sessionID + io.sockets.socket(sessionID).emit(msg); // send a targeted message + }else{ + socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad + } } - cb(null, {}); } diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index 941491237..ff3604192 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -278,8 +278,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) if (!getSocket()) return; if (!evt.data) return; var wrapper = evt; - if (wrapper.type != "COLLABROOM") return; + if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return; var msg = wrapper.data; + if (msg.type == "NEW_CHANGES") { var newRev = msg.newRev; @@ -390,6 +391,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) callbacks.onUserLeave(userInfo); } } + else if (msg.type == "DISCONNECT_REASON") { appLevelDisconnectReason = msg.reason; From f972d2a92d574648d20bd5d41c3115c3563a83d6 Mon Sep 17 00:00:00 2001 From: accolade Date: Sat, 16 Mar 2013 17:05:23 +0100 Subject: [PATCH 008/104] (( 2 typos --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a69a3b65..4e59afbfc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,8 @@ To make sure everybody is going in the same direction: * easy to install for admins and easy to use for people * easy to integrate into other apps, but also usable as standalone * using less resources on server side -* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core -Also, keep it maintainable. We don't wanna end ob as the monster Etherpad was! +* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core. +Also, keep it maintainable. We don't wanna end up as the monster Etherpad was! ## How to work with git? * Don't work in your master branch. @@ -62,4 +62,4 @@ The docs are in the `doc/` folder in the git repository, so people can easily fi Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request. -You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet. \ No newline at end of file +You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet. From 69a4ab76cfb4de059d3c5bc34f3d803bac718899 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 16 Mar 2013 17:50:53 +0000 Subject: [PATCH 009/104] hide modal once reconnect is good --- src/static/js/pad_modals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/pad_modals.js b/src/static/js/pad_modals.js index 0292e048f..0013e8999 100644 --- a/src/static/js/pad_modals.js +++ b/src/static/js/pad_modals.js @@ -49,7 +49,7 @@ var padmodals = (function() }, duration); }, hideOverlay: function(duration) { - $("#overlay").animate( + $("#overlay").hide().css( { 'opacity': 0 }, duration, function() From a1d9d27cde3bee416355222ec1988debfdc3d4c2 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 16 Mar 2013 17:57:23 +0000 Subject: [PATCH 010/104] much cleaner way of showing / hiding overlay --- src/static/js/pad_connectionstatus.js | 1 - src/static/js/pad_modals.js | 16 ++-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/static/js/pad_connectionstatus.js b/src/static/js/pad_connectionstatus.js index 2d9354ab1..862d5fd13 100644 --- a/src/static/js/pad_connectionstatus.js +++ b/src/static/js/pad_connectionstatus.js @@ -76,7 +76,6 @@ var padconnectionstatus = (function() }, isFullyConnected: function() { - padmodals.hideOverlay(); return status.what == 'connected'; }, getStatus: function() diff --git a/src/static/js/pad_modals.js b/src/static/js/pad_modals.js index 0013e8999..39094a7ea 100644 --- a/src/static/js/pad_modals.js +++ b/src/static/js/pad_modals.js @@ -40,22 +40,10 @@ var padmodals = (function() }); }, showOverlay: function(duration) { - $("#overlay").show().css( - { - 'opacity': 0 - }).animate( - { - 'opacity': 1 - }, duration); + $("#overlay").show(); }, hideOverlay: function(duration) { - $("#overlay").hide().css( - { - 'opacity': 0 - }, duration, function() - { - $("#modaloverlay").hide(); - }); + $("#overlay").hide(); } }; return self; From 693b9b9b9449252fea4bc6f4117beb88da48b5bc Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Mar 2013 01:23:31 +0000 Subject: [PATCH 011/104] better mobile support for gritter messages, before it was awful --- src/static/css/pad.css | 39 ++++++++++++++++++++++++++++++++++++++- src/static/js/chat.js | 1 - src/static/js/gritter.js | 2 -- src/static/js/pad.js | 4 ++-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/static/css/pad.css b/src/static/css/pad.css index e536b9258..320a47202 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -828,7 +828,44 @@ input[type=checkbox] { padding: 4px 1px } } -@media screen and (max-width: 400px){ +@media all and (max-width: 400px){ + #gritter-notice-wrapper{ + max-height:172px; + overflow:hidden; + width:100% !important; + background-color: #ccc; + bottom:20px; + left:0px; + right:0px; + color:#000; + } + .gritter-close { + display:block !important; + left: auto !important; + right:5px; + } + #gritter-notice-wrapper.bottom-right{ + left:0px !important; + bottom:30px !important; + right:0px !important; + } + .gritter-item p{ + color:black; + font-size:16px; + } + .gritter-title{ + text-shadow: none !important; + color:black; + } + .gritter-item{ + padding:2px 11px 8px 4px; + } + .gritter-item-wrapper{ + margin:0; + } + .gritter-item-wrapper > div{ + background: none; + } #editorcontainer { top: 68px; } diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 83a487dee..6ef11fa01 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -155,7 +155,6 @@ 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 diff --git a/src/static/js/gritter.js b/src/static/js/gritter.js index c32cc758e..9778707ef 100644 --- a/src/static/js/gritter.js +++ b/src/static/js/gritter.js @@ -21,8 +21,6 @@ $.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... } diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 4b0526208..8b53a6fbd 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -365,8 +365,7 @@ 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 + fade: false, // dont fade, too jerky on mobile time: 6000 // hang on the screen for... }); @@ -615,6 +614,7 @@ var pad = { }, handleClientMessage: function(msg) { + console.log("as"); if (msg.type == 'suggestUserName') { if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && !pad.myUserInfo.name) From 3e0a80cb7431e99b8bb3743d37e4b22ea3443224 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Mar 2013 15:17:36 +0000 Subject: [PATCH 012/104] remove console log --- src/static/js/pad.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 8b53a6fbd..0a81192f6 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -614,7 +614,6 @@ var pad = { }, handleClientMessage: function(msg) { - console.log("as"); if (msg.type == 'suggestUserName') { if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && !pad.myUserInfo.name) From b084cf4f28bb1fe766984dfbeb73d3505eb8951e Mon Sep 17 00:00:00 2001 From: maixithn Date: Sun, 17 Mar 2013 21:57:14 +0100 Subject: [PATCH 013/104] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 91410b873..574740730 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,11 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind [Next steps](#next-steps). -## Linux +## Linux/Unix You'll need gzip, git, curl, libssl develop libraries, python and gcc. *For Debian/Ubuntu*: `apt-get install gzip git-core curl python libssl-dev pkg-config build-essential` *For Fedora/CentOS*: `yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"` +*For FreeBSD*: `portinstall node node, npm and git (optional)` Additionally, you'll need [node.js](http://nodejs.org) installed, Ideally the latest stable version, be careful of installing nodejs from apt. From 81f0ef73abfb492841e83a4b87a293a2d2d71ebc Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Mar 2013 22:15:18 +0000 Subject: [PATCH 014/104] beginning of FE tests for caret tracking which is easily broken when you add weird line heights to pads --- tests/frontend/specs/caret.js | 114 ++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/frontend/specs/caret.js diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js new file mode 100644 index 000000000..fd479d5db --- /dev/null +++ b/tests/frontend/specs/caret.js @@ -0,0 +1,114 @@ +describe("As the caret is moved is the UI properly updated?", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + /* Tests to do + * Keystroke up (38), down (40), left (37), right (39) with and without special keys IE control / shift + * Page up (33) / down (34) with and without special keys + */ + + it("Creates N rows, changes height of rows, updates UI by caret key events", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var numberOfRows = 50; + + //ace creates a new dom element when you press a keystroke, so just get the first text element again + var $newFirstTextElement = inner$("div").first(); + var originalDivHeight = inner$("div").first().css("height"); + + prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target + + helper.waitFor(function(){ // Wait for the DOM to register the new items + return inner$("div").first().text().length == 6; + }).done(function(){ // Once the DOM has registered the items + inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc) + var random = Math.floor(Math.random() * (50)) + 20; + $(this).css("height", random+"px"); + }); + + var newDivHeight = inner$("div").first().css("height"); + var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height + expect(heightHasChanged).to.be(true); // expect the first line to be blank + }); + + // Is this Element now visible to the pad user? + helper.waitFor(function(){ // Wait for the DOM to register the new items + return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place + }).done(function(){ // Once the DOM has registered the items + inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc) + var random = Math.floor(Math.random() * (80 - 20 + 1)) + 20; + $(this).css("height", random+"px"); + }); + + var newDivHeight = inner$("div").first().css("height"); + var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height + expect(heightHasChanged).to.be(true); // expect the first line to be blank + }); + + // Does scrolling back up the pad with the up arrow show the correct contents? + helper.waitFor(function(){ // Wait for the new position to be in place + return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place + }).done(function(){ // Once the DOM has registered the items + var i = 0; + while(i < numberOfRows){ // press up arrow N times + keyEvent(inner$, 38, false, false); + i++; + } + + helper.waitFor(function(){ // Wait for the new position to be in place + return isScrolledIntoView(inner$("div:nth-child(0)"), inner$); // Wait for the DOM to scroll into place + }).done(function(){ // Once we're at the top of the document + expect(true).to.be(true); + done(); + }); + }); + + }); +}); + +function prepareDocument(n, target){ // generates a random document with random content on n lines + var i = 0; + while(i < n){ // for each line + target.sendkeys(makeStr()); // generate a random string and send that to the editor + target.sendkeys('{enter}'); // generator an enter keypress + i++; // rinse n times + } +} + +function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window + if(target.browser.mozilla){ // if it's a mozilla browser + var evtType = "keypress"; + }else{ + var evtType = "keydown"; + } + var e = target.Event(evtType); + if(ctrl){ + e.ctrlKey = true; // Control key + } + if(shift){ + e.shiftKey = true; // Shift Key + } + e.which = charCode; + target("#innerdocbody").trigger(e); +} + + +function makeStr(){ // from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ) + text += possible.charAt(Math.floor(Math.random() * possible.length)); + return text; +} + +function isScrolledIntoView(elem, $){ // from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling + var docViewTop = $(window).scrollTop(); + var docViewBottom = docViewTop + $(window).height(); + var elemTop = $(elem).offset().top; + var elemBottom = elemTop + $(elem).height(); + return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); +} From 99ac407f089e83db3b1608bb4a9c7869f438c4c4 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Mar 2013 23:16:23 +0000 Subject: [PATCH 015/104] working caret position function --- tests/frontend/specs/caret.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index fd479d5db..56964885a 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -10,8 +10,14 @@ describe("As the caret is moved is the UI properly updated?", function(){ * Page up (33) / down (34) with and without special keys */ + /* Challenges + * How do we keep the authors focus on a line if the lines above the author are modified? We should only redraw the user to a location if they are typing and make sure shift and arrow keys aren't redrawing the UI else highlight - copy/paste would get broken + * How the fsk do I get + * + */ + it("Creates N rows, changes height of rows, updates UI by caret key events", function(done) { - var inner$ = helper.padInner$; + var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; var numberOfRows = 50; @@ -29,6 +35,7 @@ describe("As the caret is moved is the UI properly updated?", function(){ $(this).css("height", random+"px"); }); + console.log(caretPosition(inner$)); var newDivHeight = inner$("div").first().css("height"); var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height expect(heightHasChanged).to.be(true); // expect the first line to be blank @@ -112,3 +119,12 @@ function isScrolledIntoView(elem, $){ // from http://stackoverflow.com/questions var elemBottom = elemTop + $(elem).height(); return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); } + +function caretPosition($){ + var doc = $.window.document; + var pos = doc.getSelection(); + pos.y = pos.anchorNode.parentElement.offsetTop; + pos.x = pos.anchorNode.parentElement.offsetLeft; + console.log(pos); + return pos; +} From 83ed9303dacf75f870378e91b9ca165fdc3247cc Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Mon, 18 Mar 2013 00:43:57 +0000 Subject: [PATCH 016/104] Localisation updates from http://translatewiki.net. --- src/locales/ast.json | 3 +- src/locales/be-tarask.json | 61 ++++++++++++++++++++++++++++++++++++++ src/locales/br.json | 1 + src/locales/ca.json | 1 + src/locales/da.json | 12 ++++---- src/locales/de.json | 1 + src/locales/fa.json | 2 +- src/locales/fr.json | 3 +- src/locales/gl.json | 3 +- src/locales/he.json | 3 +- src/locales/ia.json | 1 + src/locales/it.json | 3 +- src/locales/ja.json | 1 + src/locales/ko.json | 3 +- src/locales/mk.json | 3 +- src/locales/ms.json | 3 +- src/locales/nl.json | 1 + src/locales/ru.json | 3 +- src/locales/sl.json | 3 +- src/locales/sv.json | 3 +- src/locales/te.json | 2 +- src/locales/zh-hant.json | 1 + 22 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 src/locales/be-tarask.json diff --git a/src/locales/ast.json b/src/locales/ast.json index 7beb706ad..7d471860f 100644 --- a/src/locales/ast.json +++ b/src/locales/ast.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Llimpiar los colores d'autor\u00eda", "pad.toolbar.import_export.title": "Importar\/Esportar ente distintos formatos de ficheru", "pad.toolbar.timeslider.title": "Eslizador de tiempu", - "pad.toolbar.savedRevision.title": "Revisiones guardaes", + "pad.toolbar.savedRevision.title": "Guardar revisi\u00f3n", "pad.toolbar.settings.title": "Configuraci\u00f3n", "pad.toolbar.embed.title": "Incrustar esti bloc", "pad.toolbar.showusers.title": "Amosar los usuarios d'esti bloc", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Alderique en pantalla siempres", "pad.settings.colorcheck": "Colores d'autor\u00eda", "pad.settings.linenocheck": "N\u00famberos de llinia", + "pad.settings.rtlcheck": "\u00bfLleer el conten\u00edu de drecha a izquierda?", "pad.settings.fontType": "Tipograf\u00eda:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monoespaciada", diff --git a/src/locales/be-tarask.json b/src/locales/be-tarask.json new file mode 100644 index 000000000..dda412895 --- /dev/null +++ b/src/locales/be-tarask.json @@ -0,0 +1,61 @@ +{ + "index.newPad": "\u0421\u0442\u0432\u0430\u0440\u044b\u0446\u044c", + "index.createOpenPad": "\u0446\u0456 \u0442\u0432\u0430\u0440\u044b\u0446\u044c\/\u0430\u0434\u043a\u0440\u044b\u0446\u044c \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442 \u0437 \u043d\u0430\u0437\u0432\u0430\u0439:", + "pad.toolbar.bold.title": "\u0422\u043e\u045e\u0441\u0442\u044b (Ctrl-B)", + "pad.toolbar.italic.title": "\u041a\u0443\u0440\u0441\u0456\u045e (Ctrl-I)", + "pad.toolbar.underline.title": "\u041f\u0430\u0434\u043a\u0440\u044d\u0441\u044c\u043b\u0456\u0432\u0430\u043d\u044c\u043d\u0435 (Ctrl-U)", + "pad.toolbar.strikethrough.title": "\u0417\u0430\u043a\u0440\u044d\u0441\u044c\u043b\u0456\u0432\u0430\u043d\u044c\u043d\u0435", + "pad.toolbar.ol.title": "\u0423\u043f\u0430\u0440\u0430\u0434\u043a\u0430\u0432\u0430\u043d\u044b \u0441\u044c\u043f\u0456\u0441", + "pad.toolbar.ul.title": "\u041d\u0435\u045e\u043f\u0430\u0440\u0430\u0434\u043a\u0430\u0432\u0430\u043d\u044b \u0441\u044c\u043f\u0456\u0441", + "pad.toolbar.indent.title": "\u0412\u043e\u0434\u0441\u0442\u0443\u043f", + "pad.toolbar.unindent.title": "\u0412\u044b\u0441\u0442\u0443\u043f", + "pad.toolbar.undo.title": "\u0421\u043a\u0430\u0441\u0430\u0432\u0430\u0446\u044c(Ctrl-Z)", + "pad.toolbar.redo.title": "\u0412\u044f\u0440\u043d\u0443\u0446\u044c (Ctrl-Y)", + "pad.toolbar.clearAuthorship.title": "\u041f\u0440\u044b\u0431\u0440\u0430\u0446\u044c \u043a\u043e\u043b\u0435\u0440 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0443", + "pad.toolbar.import_export.title": "\u0406\u043c\u043f\u0430\u0440\u0442\/\u042d\u043a\u0441\u043f\u0430\u0440\u0442 \u0437 \u0432\u044b\u043a\u0430\u0440\u044b\u0441\u0442\u0430\u043d\u044c\u043d\u0435 \u0440\u043e\u0437\u043d\u044b\u0445 \u0444\u0430\u0440\u043c\u0430\u0442\u0430\u045e \u0444\u0430\u0439\u043b\u0430\u045e", + "pad.toolbar.timeslider.title": "\u0428\u043a\u0430\u043b\u0430 \u0447\u0430\u0441\u0443", + "pad.toolbar.savedRevision.title": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c \u0432\u044d\u0440\u0441\u0456\u044e", + "pad.toolbar.settings.title": "\u041d\u0430\u043b\u0430\u0434\u044b", + "pad.toolbar.embed.title": "\u0423\u0431\u0443\u0434\u0430\u0432\u0430\u0446\u044c \u0433\u044d\u0442\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442", + "pad.toolbar.showusers.title": "\u041f\u0430\u043a\u0430\u0437\u0430\u0446\u044c \u043a\u0430\u0440\u044b\u0441\u0442\u0430\u043b\u044c\u043d\u0456\u043a\u0430\u045e \u0443 \u0433\u044d\u0442\u044b\u043c \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0446\u0435", + "pad.colorpicker.save": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c", + "pad.colorpicker.cancel": "\u0421\u043a\u0430\u0441\u0430\u0432\u0430\u0446\u044c", + "pad.loading": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430...", + "pad.passwordRequired": "\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u0430 \u0433\u044d\u0442\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430 \u043f\u0430\u0442\u0440\u044d\u0431\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u044c", + "pad.permissionDenied": "\u0412\u044b \u043d\u044f \u043c\u0430\u0435\u0446\u0435 \u0434\u0430\u0437\u0432\u043e\u043b\u0443 \u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u0430 \u0433\u044d\u0442\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430", + "pad.wrongPassword": "\u0412\u044b \u045e\u0432\u044f\u043b\u0456 \u043d\u044f\u0441\u043b\u0443\u0448\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u044c", + "pad.settings.padSettings": "\u041d\u0430\u043b\u0430\u0434\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430", + "pad.settings.myView": "\u041c\u043e\u0439 \u0432\u044b\u0433\u043b\u044f\u0434", + "pad.settings.stickychat": "\u0417\u0430\u045e\u0441\u0451\u0434\u044b \u043f\u0430\u043a\u0430\u0437\u0432\u0430\u0446\u044c \u0447\u0430\u0442", + "pad.settings.colorcheck": "\u041a\u043e\u043b\u0435\u0440\u044b \u0430\u045e\u0442\u0430\u0440\u0441\u0442\u0432\u0430", + "pad.settings.linenocheck": "\u041d\u0443\u043c\u0430\u0440\u044b \u0440\u0430\u0434\u043a\u043e\u045e", + "pad.settings.rtlcheck": "\u0422\u044d\u043a\u0441\u0442 \u0441\u043f\u0440\u0430\u0432\u0430-\u043d\u0430\u043b\u0435\u0432\u0430", + "pad.settings.fontType": "\u0422\u044b\u043f \u0448\u0440\u044b\u0444\u0442\u0443:", + "pad.settings.fontType.normal": "\u0417\u0432\u044b\u0447\u0430\u0439\u043d\u044b", + "pad.settings.fontType.monospaced": "\u041c\u043e\u043d\u0430\u0448\u044b\u0440\u044b\u043d\u043d\u044b", + "pad.settings.globalView": "\u0410\u0433\u0443\u043b\u044c\u043d\u044b \u0432\u044b\u0433\u043b\u044f\u0434", + "pad.settings.language": "\u041c\u043e\u0432\u0430:", + "pad.importExport.import_export": "\u0406\u043c\u043f\u0430\u0440\u0442\/\u042d\u043a\u0441\u043f\u0430\u0440\u0442", + "pad.importExport.import": "\u0417\u0430\u0433\u0440\u0443\u0437\u0456\u0436\u0430\u0439\u0446\u0435 \u043b\u044e\u0431\u044b\u044f \u0442\u044d\u043a\u0441\u0442\u0430\u0432\u044b\u044f \u0444\u0430\u0439\u043b\u044b \u0430\u0431\u043e \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u044b", + "pad.importExport.importSuccessful": "\u041f\u0430\u0441\u044c\u043f\u044f\u0445\u043e\u0432\u0430!", + "pad.importExport.export": "\u042d\u043a\u0441\u043f\u0430\u0440\u0442\u0430\u0432\u0430\u0446\u044c \u0431\u044f\u0433\u0443\u0447\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442 \u044f\u043a:", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "\u041f\u0440\u043e\u0441\u0442\u044b \u0442\u044d\u043a\u0441\u0442", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.modals.connected": "\u041f\u0430\u0434\u043b\u0443\u0447\u044b\u043b\u0456\u0441\u044f.", + "pad.modals.reconnecting": "\u041f\u0435\u0440\u0430\u043f\u0430\u0434\u043b\u0443\u0447\u044d\u043d\u044c\u043d\u0435 \u0434\u0430 \u0432\u0430\u0448\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430...", + "pad.modals.forcereconnect": "\u041f\u0440\u044b\u043c\u0443\u0441\u043e\u0432\u0430\u0435 \u043f\u0435\u0440\u0430\u043f\u0430\u0434\u043b\u0443\u0447\u044d\u043d\u044c\u043d\u0435", + "pad.share": "\u041f\u0430\u0434\u0437\u044f\u043b\u0456\u0446\u0446\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430\u043c", + "pad.share.readonly": "\u0422\u043e\u043b\u044c\u043a\u0456 \u0434\u043b\u044f \u0447\u044b\u0442\u0430\u043d\u044c\u043d\u044f", + "pad.share.link": "\u0421\u043f\u0430\u0441\u044b\u043b\u043a\u0430", + "pad.chat": "\u0427\u0430\u0442", + "@metadata": { + "authors": [ + "Jim-by", + "Wizardist" + ] + } +} \ No newline at end of file diff --git a/src/locales/br.json b/src/locales/br.json index 1197f0d65..f844eb054 100644 --- a/src/locales/br.json +++ b/src/locales/br.json @@ -37,6 +37,7 @@ "pad.settings.stickychat": "Diskwel ar flap bepred", "pad.settings.colorcheck": "Livio\u00f9 anaout", "pad.settings.linenocheck": "Niverenno\u00f9 linenno\u00f9", + "pad.settings.rtlcheck": "Lenn an danvez a-zehou da gleiz ?", "pad.settings.fontType": "Seurt font :", "pad.settings.fontType.normal": "Reizh", "pad.settings.fontType.monospaced": "Monospas", diff --git a/src/locales/ca.json b/src/locales/ca.json index ec521ecac..e736fd3c1 100644 --- a/src/locales/ca.json +++ b/src/locales/ca.json @@ -28,6 +28,7 @@ "pad.settings.stickychat": "Xateja sempre a la pantalla", "pad.settings.colorcheck": "Colors d'autoria", "pad.settings.linenocheck": "N\u00fameros de l\u00ednia", + "pad.settings.rtlcheck": "Llegir el contingut de dreta a esquerra?", "pad.settings.fontType": "Tipus de lletra:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "D'amplada fixa", diff --git a/src/locales/da.json b/src/locales/da.json index e7cde1f55..b0aef7139 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -1,9 +1,10 @@ { "@metadata": { - "authors": [ - "Christian List", - "Peter Alberti" - ] + "authors": { + "0": "Christian List", + "1": "Peter Alberti", + "3": "Steenth" + } }, "index.newPad": "Ny Pad", "index.createOpenPad": "eller opret\/\u00e5bn en Pad med navnet:", @@ -20,7 +21,7 @@ "pad.toolbar.clearAuthorship.title": "Fjern farver for forfatterskab", "pad.toolbar.import_export.title": "Import\/eksport fra\/til forskellige filformater", "pad.toolbar.timeslider.title": "Timeslider", - "pad.toolbar.savedRevision.title": "Gemte revisioner", + "pad.toolbar.savedRevision.title": "Gem Revision", "pad.toolbar.settings.title": "Indstillinger", "pad.toolbar.embed.title": "Integrer denne pad", "pad.toolbar.showusers.title": "Vis brugere p\u00e5 denne pad", @@ -35,6 +36,7 @@ "pad.settings.stickychat": "Chat altid p\u00e5 sk\u00e6rmen", "pad.settings.colorcheck": "Forfatterskabsfarver", "pad.settings.linenocheck": "Linjenumre", + "pad.settings.rtlcheck": "L\u00e6se indhold fra h\u00f8jre mod venstre?", "pad.settings.fontType": "Skrifttype:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Fastbredde", diff --git a/src/locales/de.json b/src/locales/de.json index 209384222..fdd016753 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -37,6 +37,7 @@ "pad.settings.stickychat": "Chat immer anzeigen", "pad.settings.colorcheck": "Autorenfarben anzeigen", "pad.settings.linenocheck": "Zeilennummern", + "pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?", "pad.settings.fontType": "Schriftart:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/fa.json b/src/locales/fa.json index bccc353c2..8e0fd1388 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -21,7 +21,7 @@ "pad.toolbar.clearAuthorship.title": "\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u06cc", "pad.toolbar.import_export.title": "\u062f\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc\/\u0628\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc \u0627\u0632\/\u0628\u0647 \u0642\u0627\u0644\u0628\u200c\u0647\u0627\u06cc \u0645\u062e\u062a\u0644\u0641", "pad.toolbar.timeslider.title": "\u0627\u0633\u0644\u0627\u06cc\u062f\u0631 \u0632\u0645\u0627\u0646", - "pad.toolbar.savedRevision.title": "\u0630\u062e\u06cc\u0631\u0647\u200c\u0633\u0627\u0632\u06cc \u0646\u0633\u062e\u0647", + "pad.toolbar.savedRevision.title": "\u0630\u062e\u06cc\u0631\u0647\u200c\u06cc \u0628\u0627\u0632\u0646\u0648\u06cc\u0633\u06cc", "pad.toolbar.settings.title": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a", "pad.toolbar.embed.title": "\u062c\u0627\u0633\u0627\u0632\u06cc \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a", "pad.toolbar.showusers.title": "\u0646\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631\u0627\u0646 \u062f\u0631 \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a", diff --git a/src/locales/fr.json b/src/locales/fr.json index 4131c723a..6e1e79b44 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -28,7 +28,7 @@ "pad.toolbar.clearAuthorship.title": "Effacer les couleurs identifiant les auteurs", "pad.toolbar.import_export.title": "Importer\/Exporter de\/vers un format de fichier diff\u00e9rent", "pad.toolbar.timeslider.title": "Historique dynamique", - "pad.toolbar.savedRevision.title": "Versions enregistr\u00e9es", + "pad.toolbar.savedRevision.title": "Enregistrer la r\u00e9vision", "pad.toolbar.settings.title": "Param\u00e8tres", "pad.toolbar.embed.title": "Int\u00e9grer ce Pad", "pad.toolbar.showusers.title": "Afficher les utilisateurs du Pad", @@ -43,6 +43,7 @@ "pad.settings.stickychat": "Toujours afficher le chat", "pad.settings.colorcheck": "Couleurs d\u2019identification", "pad.settings.linenocheck": "Num\u00e9ros de lignes", + "pad.settings.rtlcheck": "Lire le contenu de la droite vers la gauche?", "pad.settings.fontType": "Type de police :", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/gl.json b/src/locales/gl.json index 261d28ef7..6b9c2b549 100644 --- a/src/locales/gl.json +++ b/src/locales/gl.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Limpar as cores de identificaci\u00f3n dos autores", "pad.toolbar.import_export.title": "Importar\/Exportar desde\/a diferentes formatos de ficheiro", "pad.toolbar.timeslider.title": "Li\u00f1a do tempo", - "pad.toolbar.savedRevision.title": "Revisi\u00f3ns gardadas", + "pad.toolbar.savedRevision.title": "Gardar a revisi\u00f3n", "pad.toolbar.settings.title": "Configuraci\u00f3ns", "pad.toolbar.embed.title": "Incorporar este documento", "pad.toolbar.showusers.title": "Mostrar os usuarios deste documento", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Chat sempre visible", "pad.settings.colorcheck": "Cores de identificaci\u00f3n", "pad.settings.linenocheck": "N\u00fameros de li\u00f1a", + "pad.settings.rtlcheck": "Quere ler o contido da dereita \u00e1 esquerda?", "pad.settings.fontType": "Tipo de letra:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monoespazada", diff --git a/src/locales/he.json b/src/locales/he.json index 7e5f3b048..77e68e63d 100644 --- a/src/locales/he.json +++ b/src/locales/he.json @@ -20,7 +20,7 @@ "pad.toolbar.clearAuthorship.title": "\u05e0\u05d9\u05e7\u05d5\u05d9 \u05e6\u05d1\u05e2\u05d9\u05dd", "pad.toolbar.import_export.title": "\u05d9\u05d9\u05d1\u05d5\u05d0\/\u05d9\u05d9\u05e6\u05d0 \u05d1\u05ea\u05e1\u05d3\u05d9\u05e8\u05d9 \u05e7\u05d1\u05e6\u05d9\u05dd \u05e9\u05d5\u05e0\u05d9\u05dd", "pad.toolbar.timeslider.title": "\u05d2\u05d5\u05dc\u05dc \u05d6\u05de\u05df", - "pad.toolbar.savedRevision.title": "\u05d2\u05e8\u05e1\u05d0\u05d5\u05ea \u05e9\u05de\u05d5\u05e8\u05d5\u05ea", + "pad.toolbar.savedRevision.title": "\u05e9\u05de\u05d9\u05e8\u05ea \u05d2\u05e8\u05e1\u05d4", "pad.toolbar.settings.title": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea", "pad.toolbar.embed.title": "\u05d4\u05d8\u05de\u05e2\u05ea \u05d4\u05e4\u05e0\u05e7\u05e1 \u05d4\u05d6\u05d4", "pad.toolbar.showusers.title": "\u05d4\u05e6\u05d2\u05ea \u05d4\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d1\u05e4\u05e0\u05e7\u05e1 \u05d4\u05d6\u05d4", @@ -35,6 +35,7 @@ "pad.settings.stickychat": "\u05d4\u05e9\u05d9\u05d7\u05d4 \u05ea\u05de\u05d9\u05d3 \u05e2\u05dc \u05d4\u05de\u05e1\u05da", "pad.settings.colorcheck": "\u05e6\u05d1\u05d9\u05e2\u05d4 \u05dc\u05e4\u05d9 \u05de\u05d7\u05d1\u05e8", "pad.settings.linenocheck": "\u05de\u05e1\u05e4\u05e8\u05d9 \u05e9\u05d5\u05e8\u05d5\u05ea", + "pad.settings.rtlcheck": "\u05dc\u05e7\u05e8\u05d5\u05d0 \u05d0\u05ea \u05d4\u05ea\u05d5\u05db\u05df \u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc?", "pad.settings.fontType": "\u05e1\u05d5\u05d2 \u05d2\u05d5\u05e4\u05df:", "pad.settings.fontType.normal": "\u05e8\u05d2\u05d9\u05dc", "pad.settings.fontType.monospaced": "\u05d1\u05e8\u05d5\u05d7\u05d1 \u05e7\u05d1\u05d5\u05e2", diff --git a/src/locales/ia.json b/src/locales/ia.json index e6c5dde11..3c57a8f03 100644 --- a/src/locales/ia.json +++ b/src/locales/ia.json @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Chat sempre visibile", "pad.settings.colorcheck": "Colores de autor", "pad.settings.linenocheck": "Numeros de linea", + "pad.settings.rtlcheck": "Leger le contento de dextra a sinistra?", "pad.settings.fontType": "Typo de litteras:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monospatial", diff --git a/src/locales/it.json b/src/locales/it.json index 05569a322..c80b2a390 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -22,7 +22,7 @@ "pad.toolbar.clearAuthorship.title": "Elimina i colori che indicano gli autori", "pad.toolbar.import_export.title": "Importa\/esporta da\/a diversi formati di file", "pad.toolbar.timeslider.title": "Presentazione cronologia", - "pad.toolbar.savedRevision.title": "Revisioni salvate", + "pad.toolbar.savedRevision.title": "Versione salvata", "pad.toolbar.settings.title": "Impostazioni", "pad.toolbar.embed.title": "Incorpora questo Pad", "pad.toolbar.showusers.title": "Visualizza gli utenti su questo Pad", @@ -37,6 +37,7 @@ "pad.settings.stickychat": "Chat sempre sullo schermo", "pad.settings.colorcheck": "Colori che indicano gli autori", "pad.settings.linenocheck": "Numeri di riga", + "pad.settings.rtlcheck": "Leggere il contenuto da destra a sinistra?", "pad.settings.fontType": "Tipo di carattere:", "pad.settings.fontType.normal": "Normale", "pad.settings.fontType.monospaced": "A larghezza fissa", diff --git a/src/locales/ja.json b/src/locales/ja.json index f7173dd4f..2464f02ce 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -34,6 +34,7 @@ "pad.settings.stickychat": "\u753b\u9762\u306b\u30c1\u30e3\u30c3\u30c8\u3092\u5e38\u306b\u8868\u793a", "pad.settings.colorcheck": "\u4f5c\u8005\u306e\u8272\u5206\u3051", "pad.settings.linenocheck": "\u884c\u756a\u53f7", + "pad.settings.rtlcheck": "\u53f3\u6a2a\u66f8\u304d\u306b\u3059\u308b", "pad.settings.fontType": "\u30d5\u30a9\u30f3\u30c8\u306e\u7a2e\u985e:", "pad.settings.fontType.normal": "\u901a\u5e38", "pad.settings.fontType.monospaced": "\u56fa\u5b9a\u5e45", diff --git a/src/locales/ko.json b/src/locales/ko.json index ccd7705ce..ac12f0b41 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "\uc800\uc790\uc758 \uc0c9 \uc9c0\uc6b0\uae30", "pad.toolbar.import_export.title": "\ub2e4\ub978 \ud30c\uc77c \ud615\uc2dd\uc73c\ub85c \uac00\uc838\uc624\uae30\/\ub0b4\ubcf4\ub0b4\uae30", "pad.toolbar.timeslider.title": "\uc2dc\uac04\uc2ac\ub77c\uc774\ub354", - "pad.toolbar.savedRevision.title": "\uc800\uc7a5\ud55c \ud310", + "pad.toolbar.savedRevision.title": "\ud310 \uc800\uc7a5", "pad.toolbar.settings.title": "\uc124\uc815", "pad.toolbar.embed.title": "\uc774 \ud328\ub4dc \ud3ec\ud568\ud558\uae30", "pad.toolbar.showusers.title": "\uc774 \ud328\ub4dc\uc5d0 \uc0ac\uc6a9\uc790 \ubcf4\uae30", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "\ud654\uba74\uc5d0 \ud56d\uc0c1 \ub300\ud654 \ubcf4\uae30", "pad.settings.colorcheck": "\uc800\uc790 \uc0c9", "pad.settings.linenocheck": "\uc904 \ubc88\ud638", + "pad.settings.rtlcheck": "\uc6b0\ud6a1\uc11c(\uc624\ub978\ucabd\uc5d0\uc11c \uc67c\ucabd\uc73c\ub85c)\uc785\ub2c8\uae4c?", "pad.settings.fontType": "\uae00\uaf34 \uc885\ub958:", "pad.settings.fontType.normal": "\ubcf4\ud1b5", "pad.settings.fontType.monospaced": "\uace0\uc815 \ud3ed", diff --git a/src/locales/mk.json b/src/locales/mk.json index 94d73bd85..1ba1fb70f 100644 --- a/src/locales/mk.json +++ b/src/locales/mk.json @@ -20,7 +20,7 @@ "pad.toolbar.clearAuthorship.title": "\u041f\u043e\u043d\u0438\u0448\u0442\u0438 \u0433\u0438 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u0438\u0442\u0435 \u0431\u043e\u0438", "pad.toolbar.import_export.title": "\u0423\u0432\u043e\u0437\/\u0418\u0437\u0432\u043e\u0437 \u043e\u0434\/\u0432\u043e \u0440\u0430\u0437\u043d\u0438 \u043f\u043e\u0434\u0430\u0442\u043e\u0442\u0435\u0447\u043d\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0438", "pad.toolbar.timeslider.title": "\u0418\u0441\u0442\u043e\u0440\u0438\u0441\u043a\u0438 \u043f\u0440\u0435\u0433\u043b\u0435\u0434", - "pad.toolbar.savedRevision.title": "\u0417\u0430\u0447\u0443\u0432\u0430\u043d\u0438 \u0440\u0435\u0432\u0438\u0437\u0438\u0438", + "pad.toolbar.savedRevision.title": "\u0417\u0430\u0447\u0443\u0432\u0430\u0458 \u0440\u0435\u0432\u0438\u0437\u0438\u0458\u0430", "pad.toolbar.settings.title": "\u041f\u043e\u0441\u0442\u0430\u0432\u043a\u0438", "pad.toolbar.embed.title": "\u0412\u043c\u0435\u0442\u043d\u0438 \u0458\u0430 \u0442\u0435\u0442\u0440\u0430\u0442\u043a\u0430\u0432\u0430", "pad.toolbar.showusers.title": "\u041f\u0440\u0438\u043a\u0430\u0436. \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u0446\u0438\u0442\u0435 \u043d\u0430 \u0442\u0435\u0442\u0440\u0430\u0442\u043a\u0430\u0432\u0430", @@ -35,6 +35,7 @@ "pad.settings.stickychat": "\u0420\u0430\u0437\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0435 \u0441\u0435\u043a\u043e\u0433\u0430\u0448 \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u043e\u0442", "pad.settings.colorcheck": "\u0410\u0432\u0442\u043e\u0440\u0441\u043a\u0438 \u0431\u043e\u0438", "pad.settings.linenocheck": "\u0411\u0440\u043e\u0435\u0432\u0438 \u043d\u0430 \u0440\u0435\u0434\u043e\u0432\u0438\u0442\u0435", + "pad.settings.rtlcheck": "\u0421\u043e\u0434\u0440\u0436\u0438\u043d\u0438\u0442\u0435 \u0434\u0430 \u0441\u0435 \u0447\u0438\u0442\u0430\u0430\u0442 \u043e\u0434 \u0434\u0435\u0441\u043d\u043e \u043d\u0430 \u043b\u0435\u0432\u043e?", "pad.settings.fontType": "\u0422\u0438\u043f \u043d\u0430 \u0444\u043e\u043d\u0442:", "pad.settings.fontType.normal": "\u041d\u043e\u0440\u043c\u0430\u043b\u0435\u043d", "pad.settings.fontType.monospaced": "\u041d\u0435\u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u0435\u043d", diff --git a/src/locales/ms.json b/src/locales/ms.json index 04055d26e..732a7759e 100644 --- a/src/locales/ms.json +++ b/src/locales/ms.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Padamkan Warna Pengarang", "pad.toolbar.import_export.title": "Import\/Eksport dari\/ke format-format fail berbeza", "pad.toolbar.timeslider.title": "Gelangsar masa", - "pad.toolbar.savedRevision.title": "Semakan Tersimpan", + "pad.toolbar.savedRevision.title": "Simpan Semakan", "pad.toolbar.settings.title": "Tetapan", "pad.toolbar.embed.title": "Benamkan pad ini", "pad.toolbar.showusers.title": "Tunjukkan pengguna pada pad ini", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Sentiasa bersembang pada skrin", "pad.settings.colorcheck": "Warna pengarang", "pad.settings.linenocheck": "Nombor baris", + "pad.settings.rtlcheck": "Membaca dari kanan ke kiri?", "pad.settings.fontType": "Jenis fon:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/nl.json b/src/locales/nl.json index 4142cc339..3f4a0cab6 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Chat altijd zichtbaar", "pad.settings.colorcheck": "Kleuren auteurs", "pad.settings.linenocheck": "Regelnummers", + "pad.settings.rtlcheck": "Inhoud van rechts naar links lezen?", "pad.settings.fontType": "Lettertype:", "pad.settings.fontType.normal": "Normaal", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/ru.json b/src/locales/ru.json index 4e4c40506..8cd82a5f6 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -22,7 +22,7 @@ "pad.toolbar.clearAuthorship.title": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0446\u0432\u0435\u0442\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", "pad.toolbar.import_export.title": "\u0418\u043c\u043f\u043e\u0440\u0442\/\u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432 \u0444\u0430\u0439\u043b\u043e\u0432", "pad.toolbar.timeslider.title": "\u0428\u043a\u0430\u043b\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438", - "pad.toolbar.savedRevision.title": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438", + "pad.toolbar.savedRevision.title": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e", "pad.toolbar.settings.title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", "pad.toolbar.embed.title": "\u0412\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", "pad.toolbar.showusers.title": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435", @@ -37,6 +37,7 @@ "pad.settings.stickychat": "\u0412\u0441\u0435\u0433\u0434\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0447\u0430\u0442", "pad.settings.colorcheck": "\u0426\u0432\u0435\u0442\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430", "pad.settings.linenocheck": "\u041d\u043e\u043c\u0435\u0440\u0430 \u0441\u0442\u0440\u043e\u043a", + "pad.settings.rtlcheck": "\u0427\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0441\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0435\u0432\u043e?", "pad.settings.fontType": "\u0422\u0438\u043f \u0448\u0440\u0438\u0444\u0442\u0430:", "pad.settings.fontType.normal": "\u041e\u0431\u044b\u0447\u043d\u044b\u0439", "pad.settings.fontType.monospaced": "\u041c\u043e\u043d\u043e\u0448\u0438\u0440\u0438\u043d\u043d\u044b\u0439", diff --git a/src/locales/sl.json b/src/locales/sl.json index edfa68c00..98cd92b67 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Po\u010disti barvo avtorstva", "pad.toolbar.import_export.title": "Izvozi\/Uvozi razli\u010dne oblike zapisov", "pad.toolbar.timeslider.title": "Drsnik zgodovine", - "pad.toolbar.savedRevision.title": "Shranjene predelave", + "pad.toolbar.savedRevision.title": "Shrani predelavo", "pad.toolbar.settings.title": "Nastavitve", "pad.toolbar.embed.title": "Vstavi dokument", "pad.toolbar.showusers.title": "Poka\u017ei uporabnike dokumenta", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Vsebina klepeta je vedno na zaslonu.", "pad.settings.colorcheck": "Barve avtorstva", "pad.settings.linenocheck": "\u0160tevilke vrstic", + "pad.settings.rtlcheck": "Ali naj se vsebina prebira od desne proti levi?", "pad.settings.fontType": "Vrsta pisave:", "pad.settings.fontType.normal": "Obi\u010dajno", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/sv.json b/src/locales/sv.json index 8c6c2d86e..5e37d02a8 100644 --- a/src/locales/sv.json +++ b/src/locales/sv.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Rensa f\u00f6rfattarf\u00e4rger", "pad.toolbar.import_export.title": "Importera\/exportera fr\u00e5n\/till olika filformat", "pad.toolbar.timeslider.title": "Tidsreglage", - "pad.toolbar.savedRevision.title": "Sparade revisioner", + "pad.toolbar.savedRevision.title": "Spara revision", "pad.toolbar.settings.title": "Inst\u00e4llningar", "pad.toolbar.embed.title": "B\u00e4dda in detta block", "pad.toolbar.showusers.title": "Visa anv\u00e4ndarna p\u00e5 detta block", @@ -34,6 +34,7 @@ "pad.settings.stickychat": "Chatten alltid p\u00e5 sk\u00e4rmen", "pad.settings.colorcheck": "F\u00f6rfattarskapsf\u00e4rger", "pad.settings.linenocheck": "Radnummer", + "pad.settings.rtlcheck": "Vill du l\u00e4sa inneh\u00e5llet fr\u00e5n h\u00f6ger till v\u00e4nster?", "pad.settings.fontType": "Typsnitt:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Fast breddsteg", diff --git a/src/locales/te.json b/src/locales/te.json index 955b263ab..898b40fd1 100644 --- a/src/locales/te.json +++ b/src/locales/te.json @@ -26,7 +26,7 @@ "pad.colorpicker.save": "\u0c2d\u0c26\u0c4d\u0c30\u0c2a\u0c30\u0c1a\u0c41", "pad.colorpicker.cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41\u0c1a\u0c47\u0c2f\u0c3f", "pad.loading": "\u0c32\u0c4b\u0c21\u0c35\u0c41\u0c24\u0c4b\u0c02\u0c26\u0c3f...", - "pad.wrongPassword": "\u0c2e\u0c40 \u0c30\u0c39\u0c38\u0c4d\u0c2f\u0c2a\u0c26\u0c02 \u0c24\u0c2a\u0c41", + "pad.wrongPassword": "\u0c2e\u0c40 \u0c38\u0c02\u0c15\u0c47\u0c24\u0c2a\u0c26\u0c02 \u0c24\u0c2a\u0c4d\u0c2a\u0c41", "pad.settings.padSettings": "\u0c2a\u0c32\u0c15 \u0c05\u0c2e\u0c30\u0c3f\u0c15\u0c32\u0c41", "pad.settings.myView": "\u0c28\u0c3e \u0c09\u0c26\u0c4d\u0c26\u0c47\u0c36\u0c4d\u0c2f\u0c2e\u0c41", "pad.settings.stickychat": "\u0c24\u0c46\u0c30\u0c2a\u0c48\u0c28\u0c47 \u0c2e\u0c3e\u0c1f\u0c3e\u0c2e\u0c02\u0c24\u0c3f\u0c28\u0c3f \u0c0e\u0c32\u0c4d\u0c32\u0c2a\u0c41\u0c21\u0c41 \u0c1a\u0c47\u0c2f\u0c41\u0c2e\u0c41", diff --git a/src/locales/zh-hant.json b/src/locales/zh-hant.json index efe4da617..7b5c725cd 100644 --- a/src/locales/zh-hant.json +++ b/src/locales/zh-hant.json @@ -35,6 +35,7 @@ "pad.settings.stickychat": "\u6c38\u9060\u5728\u5c4f\u5e55\u4e0a\u986f\u793a\u804a\u5929", "pad.settings.colorcheck": "\u4f5c\u8005\u984f\u8272", "pad.settings.linenocheck": "\u884c\u865f", + "pad.settings.rtlcheck": "\u5f9e\u53f3\u81f3\u5de6\u8b80\u53d6\u5167\u5bb9\uff1f", "pad.settings.fontType": "\u5b57\u9ad4\u985e\u578b\uff1a", "pad.settings.fontType.normal": "\u6b63\u5e38", "pad.settings.fontType.monospaced": "\u7b49\u5bec", From 9f54a65c88176abb850e39dcbff07fda159538b6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 17:40:18 +0000 Subject: [PATCH 017/104] refactored arrow keys now work after paste in chrome --- src/static/js/ace2_inner.js | 88 ++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 2dc6408b1..905a73314 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3722,29 +3722,44 @@ function Ace2Inner(){ var isPageUp = evt.which === 33; scheduler.setTimeout(function(){ - var newVisibleLineRange = getVisibleLineRange(); - var linesCount = rep.lines.length(); + var newVisibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10 + var linesCount = rep.lines.length(); // total count of lines in pad IE 10 + var numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now? + + top.console.log(rep); + top.console.log("old vis", oldVisibleLineRange); - var newCaretRow = rep.selStart[0]; if(isPageUp){ - newCaretRow = oldVisibleLineRange[0]; + if(rep.selStart[0] == oldVisibleLineRange[0]+1 || rep.selStart[0] == oldVisibleLineRange[0] || rep.selStart[0] == oldVisibleLineRange[0] -1){ // if we're at the top of the document + rep.selEnd[0] = oldVisibleLineRange[0] - numberOfLinesInViewport; + } + else if(rep.selEnd[0] < (oldVisibleLineRange[0]+1)){ // If it's mostly near the bottom of a document + rep.selEnd[0] = oldVisibleLineRange[0]; // dont go further in the page up than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + rep.selStart[0] = oldVisibleLineRange[0]; // dont go further in the page up than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + } } - if(isPageDown){ - newCaretRow = newVisibleLineRange[0] + topOffset; + if(isPageDown){ // if we hit page down + if(rep.selEnd[0] > oldVisibleLineRange[0]){ + // top.console.log("new bottom", oldVisibleLineRange[1]); + rep.selStart[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + rep.selEnd[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + } } //ensure min and max - if(newCaretRow < 0){ - newCaretRow = 0; + if(rep.selEnd[0] < 0){ + rep.selEnd[0] = 0; } - if(newCaretRow >= linesCount){ - newCaretRow = linesCount-1; + if(rep.selEnd[0] >= linesCount){ + rep.selEnd[0] = linesCount-1; } - - rep.selStart[0] = newCaretRow; - rep.selEnd[0] = newCaretRow; +top.console.log(rep) updateBrowserSelectionFromRep(); + var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current + var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214 + setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document + }, 200); } @@ -3752,32 +3767,43 @@ function Ace2Inner(){ We have to do this the way we do because rep. doesn't hold the value for keyheld events IE if the user presses and holds the arrow key */ if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){ - - var newVisibleLineRange = getVisibleLineRange(); // get the current visible range -- This works great. - var lineHeight = textLineHeight(); // what Is the height of each line? + var viewport = getViewPortTopBottom(); var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214 + var lineHeight = $(myselection.focusNode.parentNode).parent().height(); // get the line height of the caret line + var caretOffsetTopBottom = caretOffsetTop + lineHeight; + var visibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10 if(caretOffsetTop){ // sometimes caretOffsetTop bugs out and returns 0, not sure why, possible Chrome bug? Either way if it does we don't wanna mess with it - var lineNum = Math.round(caretOffsetTop / lineHeight) ; // Get the current Line Number IE 84 - newVisibleLineRange[1] = newVisibleLineRange[1]-1; - var caretIsVisible = (lineNum > newVisibleLineRange[0] && lineNum < newVisibleLineRange[1]); // Is the cursor in the visible Range IE ie 84 > 14 and 84 < 90? - - if(!caretIsVisible){ // is the cursor no longer visible to the user? + var caretIsNotVisible = (caretOffsetTop <= viewport.top || caretOffsetTopBottom >= viewport.bottom); // Is the Caret Visible to the user? + if(caretIsNotVisible){ // is the cursor no longer visible to the user? // Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum. - // Get the new Y by getting the line number and multiplying by the height of each line. - if(evt.which == 37 || evt.which == 38){ // If left or up - var newY = lineHeight * (lineNum -1); // -1 to go to the line above - }else if(evt.which == 39 || evt.which == 40){ // if down or right - var newY = getScrollY() + (lineHeight*3); // the offset and one additional line + if(evt.which == 37 || evt.which == 38){ // If left or up arrow + var newY = caretOffsetTop; // That was easy! + } + if(evt.which == 39 || evt.which == 40){ // if down or right arrow + // only move the viewport if we're at the bottom of the viewport, if we hit down any other time the viewport shouldn't change + // NOTE: This behavior only fires if Chrome decides to break the page layout after a paste, it's annoying but nothing I can do + var selection = getSelection(); + // top.console.log("line #", rep.selStart[0]); // the line our caret is on + // top.console.log("firstvisible", visibleLineRange[0]); // the first visiblel ine + // top.console.log("lastVisible", visibleLineRange[1]); // the last visible line + + // Holding down arrow after a paste can lose the cursor -- This is the best fix I can find + if(rep.selStart[0] >= visibleLineRange[1] || rep.selStart[0] < visibleLineRange[0] ){ // if we're not at the bottom of the viewport + // top.console.log(viewport, lineHeight, myselection); + var newY = caretOffsetTop; + }else{ // we're at the bottom of the viewport so snap to a "new viewport" + // top.console.log(viewport, lineHeight, myselection); + var newY = caretOffsetTopBottom; // Allow continuous holding of down arrow to redraw the screen so we can see what we are going to highlight + } + } + if(newY){ + setScrollY(newY); // set the scrollY offset of the viewport on the document } - setScrollY(newY); // set the scroll height of the browser } - } - } - } if (type == "keydown") @@ -5109,7 +5135,7 @@ function Ace2Inner(){ setLineListType(mod[0], mod[1]); }); } - + function doInsertUnorderedList(){ doInsertList('bullet'); } From 27e9f918640aebb3771ee9f212a6e0f66a456061 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 18:03:37 +0000 Subject: [PATCH 018/104] page up, down etc all working, still no shift page up/down for highlight but that never worked anyways --- src/static/js/ace2_inner.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 905a73314..f8ed758d2 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3712,6 +3712,9 @@ function Ace2Inner(){ specialHandled = true; } if((evt.which == 33 || evt.which == 34) && type == 'keydown'){ + + evt.preventDefault(); // This is required, browsers will try to do normal default behavior on page up / down and the default behavior SUCKS + var oldVisibleLineRange = getVisibleLineRange(); var topOffset = rep.selStart[0] - oldVisibleLineRange[0]; if(topOffset < 0 ){ @@ -3726,22 +3729,13 @@ function Ace2Inner(){ var linesCount = rep.lines.length(); // total count of lines in pad IE 10 var numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now? - top.console.log(rep); - top.console.log("old vis", oldVisibleLineRange); - if(isPageUp){ - if(rep.selStart[0] == oldVisibleLineRange[0]+1 || rep.selStart[0] == oldVisibleLineRange[0] || rep.selStart[0] == oldVisibleLineRange[0] -1){ // if we're at the top of the document - rep.selEnd[0] = oldVisibleLineRange[0] - numberOfLinesInViewport; - } - else if(rep.selEnd[0] < (oldVisibleLineRange[0]+1)){ // If it's mostly near the bottom of a document - rep.selEnd[0] = oldVisibleLineRange[0]; // dont go further in the page up than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content - rep.selStart[0] = oldVisibleLineRange[0]; // dont go further in the page up than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content - } + rep.selEnd[0] = rep.selEnd[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page) + rep.selStart[0] = rep.selStart[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page) } if(isPageDown){ // if we hit page down - if(rep.selEnd[0] > oldVisibleLineRange[0]){ - // top.console.log("new bottom", oldVisibleLineRange[1]); + if(rep.selEnd[0] >= oldVisibleLineRange[0]){ // If the new viewpoint position is actually further than where we are right now rep.selStart[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content rep.selEnd[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content } @@ -3754,10 +3748,10 @@ function Ace2Inner(){ if(rep.selEnd[0] >= linesCount){ rep.selEnd[0] = linesCount-1; } -top.console.log(rep) updateBrowserSelectionFromRep(); var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current - var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214 + var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 + // top.console.log(caretOffsetTop); setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document }, 200); @@ -3792,6 +3786,7 @@ top.console.log(rep) // Holding down arrow after a paste can lose the cursor -- This is the best fix I can find if(rep.selStart[0] >= visibleLineRange[1] || rep.selStart[0] < visibleLineRange[0] ){ // if we're not at the bottom of the viewport // top.console.log(viewport, lineHeight, myselection); + // TODO: Make it so chrome doesnt need to redraw the page by only applying this technique if required var newY = caretOffsetTop; }else{ // we're at the bottom of the viewport so snap to a "new viewport" // top.console.log(viewport, lineHeight, myselection); From 3562672a757d4b0db85cda4113b5687183e67513 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 18:44:01 +0000 Subject: [PATCH 019/104] stop start point going negative --- src/static/js/ace2_inner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index f8ed758d2..f091dc0c0 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3745,6 +3745,9 @@ function Ace2Inner(){ if(rep.selEnd[0] < 0){ rep.selEnd[0] = 0; } + if(rep.selStart[0] < 0){ + rep.selStart[0] = 0; + } if(rep.selEnd[0] >= linesCount){ rep.selEnd[0] = linesCount-1; } From fb9d46fc51a4f8f2e2d9a4b3e01fc1821a1aac03 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 20:08:58 +0000 Subject: [PATCH 020/104] document the required tests --- tests/frontend/specs/caret.js | 75 ++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index 56964885a..9622df8a6 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -8,15 +8,18 @@ describe("As the caret is moved is the UI properly updated?", function(){ /* Tests to do * Keystroke up (38), down (40), left (37), right (39) with and without special keys IE control / shift * Page up (33) / down (34) with and without special keys + * Page up on the first line shouldn't move the viewport + * Down down on the last line shouldn't move the viewport + * Down arrow on any other line except the last lines shouldn't move the viewport + * Do all of the above tests after a copy/paste event */ /* Challenges * How do we keep the authors focus on a line if the lines above the author are modified? We should only redraw the user to a location if they are typing and make sure shift and arrow keys aren't redrawing the UI else highlight - copy/paste would get broken - * How the fsk do I get - * + * How can we simulate an edit event in the test framework? */ - it("Creates N rows, changes height of rows, updates UI by caret key events", function(done) { + it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; var numberOfRows = 50; @@ -24,9 +27,9 @@ describe("As the caret is moved is the UI properly updated?", function(){ //ace creates a new dom element when you press a keystroke, so just get the first text element again var $newFirstTextElement = inner$("div").first(); var originalDivHeight = inner$("div").first().css("height"); - prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target + /* helper.waitFor(function(){ // Wait for the DOM to register the new items return inner$("div").first().text().length == 6; }).done(function(){ // Once the DOM has registered the items @@ -40,7 +43,9 @@ describe("As the caret is moved is the UI properly updated?", function(){ var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height expect(heightHasChanged).to.be(true); // expect the first line to be blank }); + */ + /* // Is this Element now visible to the pad user? helper.waitFor(function(){ // Wait for the DOM to register the new items return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place @@ -54,25 +59,64 @@ describe("As the caret is moved is the UI properly updated?", function(){ var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height expect(heightHasChanged).to.be(true); // expect the first line to be blank }); + */ +/* + var i = 0; + while(i < numberOfRows){ // press down arrow +console.log("dwn"); + keyEvent(inner$, 40, false, false); + i++; + } +*/ +/* // Does scrolling back up the pad with the up arrow show the correct contents? helper.waitFor(function(){ // Wait for the new position to be in place - return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place + try{ + return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place + }catch(e){ + return false; + } }).done(function(){ // Once the DOM has registered the items + var i = 0; - while(i < numberOfRows){ // press up arrow N times - keyEvent(inner$, 38, false, false); + while(i < numberOfRows){ // press down arrow + keyEvent(inner$, 33, false, false); // doesn't work + i++; + } + + // Does scrolling back up the pad with the up arrow show the correct contents? + helper.waitFor(function(){ // Wait for the new position to be in place + try{ + return isScrolledIntoView(inner$("div:nth-child(0)"), inner$); // Wait for the DOM to scroll into place + }catch(e){ + return false; + } + }).done(function(){ // Once the DOM has registered the items + + + + }); + }); + +*/ + + var i = 0; + while(i < numberOfRows){ // press down arrow + keyEvent(inner$, 33, false, false); // doesn't work i++; } - helper.waitFor(function(){ // Wait for the new position to be in place - return isScrolledIntoView(inner$("div:nth-child(0)"), inner$); // Wait for the DOM to scroll into place - }).done(function(){ // Once we're at the top of the document - expect(true).to.be(true); - done(); - }); + + // Does scrolling back up the pad with the up arrow show the correct contents? + helper.waitFor(function(){ // Wait for the new position to be in place + return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place + }).done(function(){ // Once the DOM has registered the items + expect(true).to.be(true); + done(); }); + }); }); @@ -115,8 +159,9 @@ function makeStr(){ // from http://stackoverflow.com/questions/1349404/generate- function isScrolledIntoView(elem, $){ // from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling var docViewTop = $(window).scrollTop(); var docViewBottom = docViewTop + $(window).height(); - var elemTop = $(elem).offset().top; - var elemBottom = elemTop + $(elem).height(); + var elemTop = $(elem).offset().top; // how far the element is from the top of it's container + var elemBottom = elemTop + $(elem).height(); // how far plus the height of the elem.. IE is it all in? + elemBottom = elemBottom - 16; // don't ask, sorry but this is needed.. return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); } From 2bc45de1062cd4afcb8cc53e6a89b480a05f77b6 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 18 Mar 2013 22:09:47 +0100 Subject: [PATCH 021/104] Fix #1639 by removing bodyParser middleware introduced with swagger REST API --- src/node/hooks/express/swagger.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js index f4fc5cffa..3a437a51c 100644 --- a/src/node/hooks/express/swagger.js +++ b/src/node/hooks/express/swagger.js @@ -354,7 +354,6 @@ exports.expressCreateServer = function (hook_name, args, cb) { // Let's put this under /rest for now var subpath = express(); - args.app.use(express.bodyParser()); args.app.use(basePath, subpath); swagger.setAppHandler(subpath); From b3dbf1c995d16bb3c2ebbe630b00e5ef4b60dc1f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 18 Mar 2013 22:29:42 +0100 Subject: [PATCH 022/104] Update html10n.js --- src/static/js/html10n.js | 230 +++++++++++++++++++-------------------- 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index e1c025c43..aa53a2668 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -23,27 +23,27 @@ window.html10n = (function(window, document, undefined) { // fix console - var console = window.console; + var console = window.console function interceptConsole(method){ - if (!console) return function() {}; + if (!console) return function() {} - var original = console[method]; + var original = console[method] // do sneaky stuff if (original.bind){ // Do this for normal browsers - return original.bind(console); + return original.bind(console) }else{ return function() { // Do this for IE - var message = Array.prototype.slice.apply(arguments).join(' '); - original(message); + var message = Array.prototype.slice.apply(arguments).join(' ') + original(message) } } } var consoleLog = interceptConsole('log') , consoleWarn = interceptConsole('warn') - , consoleError = interceptConsole('warn'); + , consoleError = interceptConsole('warn') // fix Array.prototype.instanceOf in, guess what, IE! <3 @@ -84,14 +84,14 @@ window.html10n = (function(window, document, undefined) { * MicroEvent - to make any js object an event emitter (server or browser) */ - var MicroEvent = function(){} + var MicroEvent = function(){} MicroEvent.prototype = { - bind : function(event, fct){ + bind : function(event, fct){ this._events = this._events || {}; this._events[event] = this._events[event] || []; this._events[event].push(fct); }, - unbind : function(event, fct){ + unbind : function(event, fct){ this._events = this._events || {}; if( event in this._events === false ) return; this._events[event].splice(this._events[event].indexOf(fct), 1); @@ -100,7 +100,7 @@ window.html10n = (function(window, document, undefined) { this._events = this._events || {}; if( event in this._events === false ) return; for(var i = 0; i < this._events[event].length; i++){ - this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)) } } }; @@ -122,50 +122,50 @@ window.html10n = (function(window, document, undefined) { * and caching all necessary resources */ function Loader(resources) { - this.resources = resources; - this.cache = {}; // file => contents - this.langs = {}; // lang => strings + this.resources = resources + this.cache = {} // file => contents + this.langs = {} // lang => strings } Loader.prototype.load = function(lang, cb) { - if(this.langs[lang]) return cb(); + if(this.langs[lang]) return cb() if (this.resources.length > 0) { var reqs = 0; for (var i=0, n=this.resources.length; i < n; i++) { this.fetch(this.resources[i], lang, function(e) { reqs++; - if(e) return setTimeout(function(){ throw e }, 0); + if(e) console.warn(e) if (reqs < n) return;// Call back once all reqs are completed - cb && cb(); + cb && cb() }) } } } Loader.prototype.fetch = function(href, lang, cb) { - var that = this; + var that = this if (this.cache[href]) { this.parse(lang, href, this.cache[href], cb) return; } - var xhr = new XMLHttpRequest(); - xhr.open('GET', href, /*async: */true); + var xhr = new XMLHttpRequest() + xhr.open('GET', href, /*async: */true) if (xhr.overrideMimeType) { xhr.overrideMimeType('application/json; charset=utf-8'); } xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200 || xhr.status === 0) { - var data = JSON.parse(xhr.responseText); - that.cache[href] = data; + var data = JSON.parse(xhr.responseText) + that.cache[href] = data // Pass on the contents for parsing - that.parse(lang, href, data, cb); + that.parse(lang, href, data, cb) } else { - cb(new Error('Failed to load '+href)); + cb(new Error('Failed to load '+href)) } } }; @@ -174,39 +174,39 @@ window.html10n = (function(window, document, undefined) { Loader.prototype.parse = function(lang, currHref, data, cb) { if ('object' != typeof data) { - cb(new Error('A file couldn\'t be parsed as json.')); - return; + cb(new Error('A file couldn\'t be parsed as json.')) + return } - if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-')); + if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-')) if (!data[lang]) { - cb(new Error('Couldn\'t find translations for '+lang)); - return; + cb(new Error('Couldn\'t find translations for '+lang)) + return } if ('string' == typeof data[lang]) { // Import rule // absolute path - var importUrl = data[lang]; + var importUrl = data[lang] // relative path if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) { - importUrl = currHref+"/../"+data[lang]; + importUrl = currHref+"/../"+data[lang] } - this.fetch(importUrl, lang, cb); - return; + this.fetch(importUrl, lang, cb) + return } if ('object' != typeof data[lang]) { - cb(new Error('Translations should be specified as JSON objects!')); - return; + cb(new Error('Translations should be specified as JSON objects!')) + return } - this.langs[lang] = data[lang]; + this.langs[lang] = data[lang] // TODO: Also store accompanying langs - cb(); + cb() } @@ -216,11 +216,11 @@ window.html10n = (function(window, document, undefined) { var html10n = { language : null } - MicroEvent.mixin(html10n); + MicroEvent.mixin(html10n) - html10n.macros = {}; + html10n.macros = {} - html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]; + html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"] /** * Get rules for plural forms (shared with JetPack), see: @@ -664,14 +664,14 @@ window.html10n = (function(window, document, undefined) { * @param langs An array of lang codes defining fallbacks */ html10n.localize = function(langs) { - var that = this; + var that = this // if only one string => create an array - if ('string' == typeof langs) langs = [langs]; + if ('string' == typeof langs) langs = [langs] this.build(langs, function(er, translations) { - html10n.translations = translations; - html10n.translateElement(translations); - that.trigger('localized'); + html10n.translations = translations + html10n.translateElement(translations) + that.trigger('localized') }) } @@ -682,78 +682,78 @@ window.html10n = (function(window, document, undefined) { * @param element A DOM element, if omitted, the document element will be used */ html10n.translateElement = function(translations, element) { - element = element || document.documentElement; + element = element || document.documentElement var children = element? getTranslatableChildren(element) : document.childNodes; for (var i=0, n=children.length; i < n; i++) { - this.translateNode(translations, children[i]); + this.translateNode(translations, children[i]) } // translate element itself if necessary - this.translateNode(translations, element); + this.translateNode(translations, element) } function asyncForEach(list, iterator, cb) { var i = 0 - , n = list.length; + , n = list.length iterator(list[i], i, function each(err) { - if(err) consoleLog(err); - i++; + if(err) consoleLog(err) + i++ if (i < n) return iterator(list[i],i, each); - cb(); + cb() }) } function getTranslatableChildren(element) { if(!document.querySelectorAll) { - if (!element) return []; + if (!element) return [] var nodes = element.getElementsByTagName('*') - , l10nElements = []; + , l10nElements = [] for (var i=0, n=nodes.length; i < n; i++) { if (nodes[i].getAttribute('data-l10n-id')) l10nElements.push(nodes[i]); } - return l10nElements; + return l10nElements } - return element.querySelectorAll('*[data-l10n-id]'); + return element.querySelectorAll('*[data-l10n-id]') } html10n.get = function(id, args) { - var translations = html10n.translations; - if(!translations) return consoleWarn('No translations available (yet)'); - if(!translations[id]) return consoleWarn('Could not find string '+id); + var translations = html10n.translations + if(!translations) return consoleWarn('No translations available (yet)') + if(!translations[id]) return consoleWarn('Could not find string '+id) // apply args - var str = substArguments(translations[id], args); + var str = substArguments(translations[id], args) // apply macros - return substMacros(id, str, args); + return substMacros(id, str, args) // replace {{arguments}} with their values or the // associated translation string (based on its key) function substArguments(str, args) { var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/ - , match; + , match while (match = reArgs.exec(str)) { if (!match || match.length < 2) - return str; // argument key not found + return str // argument key not found var arg = match[1] - , sub = ''; + , sub = '' if (arg in args) { - sub = args[arg]; + sub = args[arg] } else if (arg in translations) { - sub = translations[arg]; + sub = translations[arg] } else { - consoleWarn('Could not find argument {{' + arg + '}}'); - return str; + consoleWarn('Could not find argument {{' + arg + '}}') + return str } - str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length); + str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length) } - return str; + return str } // replace {[macros]} with their values @@ -766,21 +766,21 @@ window.html10n = (function(window, document, undefined) { // a macro has been found // Note: at the moment, only one parameter is supported var macroName = reMatch[1] - , paramName = reMatch[2]; + , paramName = reMatch[2] - if (!(macroName in gMacros)) return str; + if (!(macroName in gMacros)) return str - var param; + var param if (args && paramName in args) { - param = args[paramName]; + param = args[paramName] } else if (paramName in translations) { - param = translations[paramName]; + param = translations[paramName] } // there's no macro parser yet: it has to be defined in gMacros - var macro = html10n.macros[macroName]; - str = macro(translations, key, str, param); - return str; + var macro = html10n.macros[macroName] + str = macro(translations, key, str, param) + return str } } @@ -788,26 +788,26 @@ window.html10n = (function(window, document, undefined) { * Applies translations to a DOM node (recursive) */ html10n.translateNode = function(translations, node) { - var str = {}; + var str = {} // get id - str.id = node.getAttribute('data-l10n-id'); - if (!str.id) return; + str.id = node.getAttribute('data-l10n-id') + if (!str.id) return - if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id); + if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id) // get args if(window.JSON) { - str.args = JSON.parse(node.getAttribute('data-l10n-args')); + str.args = JSON.parse(node.getAttribute('data-l10n-args')) }else{ try{ - str.args = eval(node.getAttribute('data-l10n-args')); + str.args = eval(node.getAttribute('data-l10n-args')) }catch(e) { - consoleWarn('Couldn\'t parse args for '+str.id); + consoleWarn('Couldn\'t parse args for '+str.id) } } - str.str = html10n.get(str.id, str.args); + str.str = html10n.get(str.id, str.args) // get attribute name to apply str to var prop @@ -817,31 +817,31 @@ window.html10n = (function(window, document, undefined) { , "innerHTML": 1 , "alt": 1 , "textContent": 1 - }; + } if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified - prop = str.id.substr(index + 1); + prop = str.id.substr(index + 1) } else { // no attribute: assuming text content by default - prop = document.body.textContent ? 'textContent' : 'innerText'; + prop = document.body.textContent ? 'textContent' : 'innerText' } // Apply translation if (node.children.length === 0 || prop != 'textContent') { - node[prop] = str.str; + node[prop] = str.str } else { var children = node.childNodes, - found = false; + found = false for (var i=0, n=children.length; i < n; i++) { if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) { if (!found) { - children[i].nodeValue = str.str; - found = true; + children[i].nodeValue = str.str + found = true } else { - children[i].nodeValue = ''; + children[i].nodeValue = '' } } } if (!found) { - consoleWarn('Unexpected error: could not translate element content for key '+str.id, node); + consoleWarn('Unexpected error: could not translate element content for key '+str.id, node) } } } @@ -852,32 +852,32 @@ window.html10n = (function(window, document, undefined) { */ html10n.build = function(langs, cb) { var that = this - , build = {}; + , build = {} asyncForEach(langs, function (lang, i, next) { if(!lang) return next(); - that.loader.load(lang, next); + that.loader.load(lang, next) }, function() { - var lang; - langs.reverse(); + var lang + langs.reverse() // loop through priority array... for (var i=0, n=langs.length; i < n; i++) { - lang = langs[i]; + lang = langs[i] if(!lang || !(lang in that.loader.langs)) continue; // ... and apply all strings of the current lang in the list // to our build object for (var string in that.loader.langs[lang]) { - build[string] = that.loader.langs[lang][string]; + build[string] = that.loader.langs[lang][string] } // the last applied lang will be exposed as the // lang the page was translated to - that.language = lang; + that.language = lang } - cb(null, build); + cb(null, build) }) } @@ -893,8 +893,8 @@ window.html10n = (function(window, document, undefined) { * Returns the direction of the language returned be html10n#getLanguage */ html10n.getDirection = function() { - var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-')); - return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'; + var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-')) + return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl' } /** @@ -903,28 +903,28 @@ window.html10n = (function(window, document, undefined) { html10n.index = function () { // Find all s var links = document.getElementsByTagName('link') - , resources = []; + , resources = [] for (var i=0, n=links.length; i < n; i++) { if (links[i].type != 'application/l10n+json') continue; - resources.push(links[i].href); + resources.push(links[i].href) } - this.loader = new Loader(resources); - this.trigger('indexed'); + this.loader = new Loader(resources) + this.trigger('indexed') } if (document.addEventListener) // modern browsers and IE9+ document.addEventListener('DOMContentLoaded', function() { - html10n.index(); - }, false); + html10n.index() + }, false) else if (window.attachEvent) window.attachEvent('onload', function() { - html10n.index(); - }, false); + html10n.index() + }, false) // gettext-like shortcut if (window._ === undefined) window._ = html10n.get; - return html10n; -})(window, document); + return html10n +})(window, document) \ No newline at end of file From e49620ea077117179d3e5faccf9974080cdbb5b3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 21:36:50 +0000 Subject: [PATCH 023/104] update ueber for pg --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index a7147cf2a..f2150b5d9 100644 --- a/src/package.json +++ b/src/package.json @@ -16,7 +16,7 @@ "require-kernel" : "1.0.5", "resolve" : "0.2.x", "socket.io" : "0.9.x", - "ueberDB" : "0.1.94", + "ueberDB" : "0.1.95", "async" : "0.1.x", "express" : "3.x", "connect" : "2.4.x", From ee6a7d0b0c93232ef94de134387a2238062c7b00 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 22:09:51 +0000 Subject: [PATCH 024/104] most test pass but important ones failed --- tests/frontend/specs/caret.js | 100 +++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index 9622df8a6..7432fda13 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -1,7 +1,9 @@ describe("As the caret is moved is the UI properly updated?", function(){ - //create a new pad before each test run - beforeEach(function(cb){ - helper.newPad(cb); + var padName; + var numberOfRows = 50; + + it("creates a pad", function(done) { + padName = helper.newPad(done); this.timeout(60000); }); @@ -19,6 +21,87 @@ describe("As the caret is moved is the UI properly updated?", function(){ * How can we simulate an edit event in the test framework? */ + // THIS DOESNT WORK AS IT DOESNT MOVE THE CURSOR! + it("down arrow", function(done){ + var inner$ = helper.padInner$; + keyEvent(inner$, 40, false, false); // arrow up + done(); + }); + + it("Creates N lines", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var $newFirstTextElement = inner$("div").first(); + + prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target + helper.waitFor(function(){ // Wait for the DOM to register the new items + return inner$("div").first().text().length == 6; + }).done(function(){ // Once the DOM has registered the items + done(); + }); + }); + + it("Moves caret up a line", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.y; + var newCaretPos; + keyEvent(inner$, 38, false, false); // arrow up + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.y; + return (newCaretPos < originalPos); + }).done(function(){ + expect(newCaretPos).to.be.lessThan(originalPos); + done(); + }); + }); + + it("Moves caret down a line", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.y; + var newCaretPos; + keyEvent(inner$, 40, false, false); // arrow down + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.y; + return (newCaretPos > originalPos); + }).done(function(){ + expect(newCaretPos).to.be.moreThan(originalPos); + done(); + }); + }); + + it("Moves caret to top of doc", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.y; + var newCaretPos; + + var i = 0; + while(i < numberOfRows){ // press pageup key N times + keyEvent(inner$, 33, false, false); + i++; + } + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.y; + return (newCaretPos < originalPos); + }).done(function(){ + expect(newCaretPos).to.be.lessThan(originalPos); + done(); + }); + }); + + +/* it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -29,7 +112,6 @@ describe("As the caret is moved is the UI properly updated?", function(){ var originalDivHeight = inner$("div").first().css("height"); prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target - /* helper.waitFor(function(){ // Wait for the DOM to register the new items return inner$("div").first().text().length == 6; }).done(function(){ // Once the DOM has registered the items @@ -43,9 +125,7 @@ describe("As the caret is moved is the UI properly updated?", function(){ var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height expect(heightHasChanged).to.be(true); // expect the first line to be blank }); - */ - /* // Is this Element now visible to the pad user? helper.waitFor(function(){ // Wait for the DOM to register the new items return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place @@ -59,16 +139,12 @@ describe("As the caret is moved is the UI properly updated?", function(){ var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height expect(heightHasChanged).to.be(true); // expect the first line to be blank }); - */ -/* var i = 0; while(i < numberOfRows){ // press down arrow console.log("dwn"); keyEvent(inner$, 40, false, false); i++; } -*/ -/* // Does scrolling back up the pad with the up arrow show the correct contents? helper.waitFor(function(){ // Wait for the new position to be in place @@ -99,7 +175,6 @@ console.log("dwn"); }); }); -*/ var i = 0; while(i < numberOfRows){ // press down arrow @@ -115,9 +190,8 @@ console.log("dwn"); expect(true).to.be(true); done(); }); +*/ - - }); }); function prepareDocument(n, target){ // generates a random document with random content on n lines From 13ee96dce0adc1a479d1237e7b5aeee406937200 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Mar 2013 22:14:41 +0000 Subject: [PATCH 025/104] more tests but still fundamental flaw with arrow keys --- tests/frontend/specs/caret.js | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index 7432fda13..9d9da4609 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -100,6 +100,85 @@ describe("As the caret is moved is the UI properly updated?", function(){ }); }); + it("Moves caret right a position", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.x; + var newCaretPos; + keyEvent(inner$, 39, false, false); // arrow right + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.x; + return (newCaretPos > originalPos); + }).done(function(){ + expect(newCaretPos).to.be.moreThan(originalPos); + done(); + }); + }); + + it("Moves caret left a position", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.x; + var newCaretPos; + keyEvent(inner$, 33, false, false); // arrow left + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.x; + return (newCaretPos < originalPos); + }).done(function(){ + expect(newCaretPos).to.be.lessThan(originalPos); + done(); + }); + }); + + it("Moves caret to the next line using right arrow", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.y; + var newCaretPos; + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + keyEvent(inner$, 39, false, false); // arrow right + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.y; + return (newCaretPos > originalPos); + }).done(function(){ + expect(newCaretPos).to.be.moreThan(originalPos); + done(); + }); + }); + + it("Moves caret to the previous line using left arrow", function(done){ + var inner$ = helper.padInner$; + var $newFirstTextElement = inner$("div").first(); + var originalCaretPosition = caretPosition(inner$); + var originalPos = originalCaretPosition.y; + var newCaretPos; + keyEvent(inner$, 33, false, false); // arrow left + + helper.waitFor(function(){ // Wait for the DOM to register the new items + var newCaretPosition = caretPosition(inner$); + newCaretPos = newCaretPosition.y; + return (newCaretPos < originalPos); + }).done(function(){ + expect(newCaretPos).to.be.lessThan(originalPos); + done(); + }); + }); + + /* it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){ From 7741f762e2b743116ef9d90224c3db348402a34a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 02:21:53 +0000 Subject: [PATCH 026/104] hook for chat msg --- src/static/js/chat.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 83a487dee..bba3d87e5 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -17,6 +17,7 @@ var padutils = require('./pad_utils').padutils; var padcookie = require('./pad_cookie').padcookie; var Tinycon = require('tinycon/tinycon'); +var hooks = require('./pluginfw/hooks'); var chat = (function() { @@ -162,7 +163,18 @@ var chat = (function() time: '4000' }); Tinycon.setBubble(count); - + var msg = { + "authorName" : authorName, + "text" : text, + "sticky" : false, + "time" : timeStr + }; + hooks.aCallAll("chatNewMessage", { + "authorName" : authorName, + "text" : text, + "sticky" : false, + "time" : timeStr + }); } } } From 8406cc95ab0c050138e0cc0411fdc8d9618e7a0a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 03:43:38 +0000 Subject: [PATCH 027/104] docs for hook --- doc/api/hooks_client-side.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 7f376defa..a84ccdeb0 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -143,6 +143,15 @@ Things in context: This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. +## chatNewMessage +Called from: src/static/js/chat.js + +Things in context: + +1. msg - The message object + +This hook is called on the client side whenever a user recieves a chat message from the server. This can be used to create different notifications for chat messages. + ## collectContentPre Called from: src/static/js/contentcollector.js From 11341eb0954d319e127b80ead980d035ecbc637a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 12:52:14 +0000 Subject: [PATCH 028/104] add a test to show weird behavior --- tests/frontend/specs/caret.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index 9d9da4609..b33f5168c 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -24,10 +24,14 @@ describe("As the caret is moved is the UI properly updated?", function(){ // THIS DOESNT WORK AS IT DOESNT MOVE THE CURSOR! it("down arrow", function(done){ var inner$ = helper.padInner$; - keyEvent(inner$, 40, false, false); // arrow up + var $newFirstTextElement = inner$("div").first(); + $newFirstTextElement.focus(); + keyEvent(inner$, 37, false, false); // arrow down + keyEvent(inner$, 37, false, false); // arrow down + done(); }); - +/* it("Creates N lines", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -289,6 +293,7 @@ function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the win var evtType = "keydown"; } var e = target.Event(evtType); + console.log(e); if(ctrl){ e.ctrlKey = true; // Control key } @@ -296,6 +301,7 @@ function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the win e.shiftKey = true; // Shift Key } e.which = charCode; + e.keyCode = charCode; target("#innerdocbody").trigger(e); } From 2916b39c24f469c50e090b1cb1462e9a9d384e5b Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 16:21:04 +0000 Subject: [PATCH 029/104] make sure the sessionID target is right --- src/node/handler/PadMessageHandler.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index bcc9023d0..4b7abb6b5 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -156,7 +156,6 @@ exports.handleMessage = function(client, message) // handleMessage will be called, even if the client is not authorized hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) { if(ERR(err, callback)) return; - _.each(messages, function(newMessage){ if ( newMessage === null ) { dropMessage = true; @@ -193,6 +192,7 @@ exports.handleMessage = function(client, message) handleSuggestUserName(client, message); } else { messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type); +console.warn(message); } } else { messageLogger.warn("Dropped message, unknown Message Type " + message.type); @@ -261,9 +261,12 @@ function handleSaveRevisionMessage(client, message){ * @param sessionID {string} the socketIO session to which we're sending this message */ exports.handleCustomObjectMessage = function (msg, sessionID, cb) { - if(msg.type === "CUSTOM"){ + if(msg.data.type === "CUSTOM"){ if(sessionID){ // If a sessionID is targeted then send directly to this sessionID - io.sockets.socket(sessionID).emit(msg); // send a targeted message + console.warn("Sent msg", msg); + console.warn("to sessionID", sessionID); + // socketio.clients[sessionID].send(msg); + socketio.sockets.socket(sessionID).emit(msg); // send a targeted message }else{ socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad } @@ -1496,3 +1499,5 @@ exports.padUsers = function (padID, callback) { callback(null, {padUsers: result}); }); } + +exports.sessioninfos = sessioninfos; From 9bb05874475a1dfab177dc42a003e91b5724ea00 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 16:40:51 +0000 Subject: [PATCH 030/104] working and jsonify obj --- src/node/handler/PadMessageHandler.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 4b7abb6b5..9fa9c39c3 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -255,7 +255,8 @@ function handleSaveRevisionMessage(client, message){ } /** - * Handles a custom message, different to the function below as it handles objects not strings and you can direct the message to specific sessionID + * Handles a custom message, different to the function below as it handles objects not strings and you can + * direct the message to specific sessionID * * @param msg {Object} the message we're sending * @param sessionID {string} the socketIO session to which we're sending this message @@ -263,10 +264,7 @@ function handleSaveRevisionMessage(client, message){ exports.handleCustomObjectMessage = function (msg, sessionID, cb) { if(msg.data.type === "CUSTOM"){ if(sessionID){ // If a sessionID is targeted then send directly to this sessionID - console.warn("Sent msg", msg); - console.warn("to sessionID", sessionID); - // socketio.clients[sessionID].send(msg); - socketio.sockets.socket(sessionID).emit(msg); // send a targeted message + socketio.sockets.socket(sessionID).json.send(msg); // send a targeted message }else{ socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad } From a9bd081a44ea4a338a025420457f55248e012b0f Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 19 Mar 2013 16:55:42 +0000 Subject: [PATCH 031/104] more clean up --- src/node/handler/PadMessageHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 9fa9c39c3..954c116d2 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -192,7 +192,6 @@ exports.handleMessage = function(client, message) handleSuggestUserName(client, message); } else { messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type); -console.warn(message); } } else { messageLogger.warn("Dropped message, unknown Message Type " + message.type); From a628317b555743a1de5772e0af9e5ff1b09ada67 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 19 Mar 2013 18:34:21 +0100 Subject: [PATCH 032/104] Log http on debug log level ... and additionally log the response time --- src/node/hooks/express/webaccess.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index c39f91da6..944fd98f7 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -94,7 +94,7 @@ exports.expressConfigure = function (hook_name, args, cb) { // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) - args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); + args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.DEBUG, format: ':status, :method :url -- :response-timems'})); /* Do not let express create the session, so that we can retain a * reference to it for socket.io to use. Also, set the key (cookie From c30697cb0771882a6077b40e4d6f62105fafbf0b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 19 Mar 2013 18:40:39 +0100 Subject: [PATCH 033/104] Don't break the whole server if an import failed because no files were uploaded Fixes #1611 --- src/node/handler/ImportHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index ac856a604..7bb9c5db9 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -60,7 +60,7 @@ exports.doImport = function(req, res, padId) form.parse(req, function(err, fields, files) { //the upload failed, stop at this point if(err || files.file === undefined) { - console.warn("Uploading Error: " + err.stack); + if(err) console.warn("Uploading Error: " + err.stack); callback("uploadFailed"); } //everything ok, continue From bcb92f25a63da45fbe328e43a4a3d0311675a2ba Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 19 Mar 2013 20:21:27 +0100 Subject: [PATCH 034/104] Refactor chat notifications and the chatNewMessage hook --- doc/api/hooks_client-side.md | 9 ++- src/static/js/chat.js | 113 +++++++++++++++-------------------- 2 files changed, 55 insertions(+), 67 deletions(-) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index a84ccdeb0..919859417 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -148,9 +148,14 @@ Called from: src/static/js/chat.js Things in context: -1. msg - The message object +1. authorName - The user that wrote this message +2. author - The authorID of the user that wrote the message +2. text - the message text +3. sticky (boolean) - if you want the gritter notification bubble to fade out on its own or just sit there +3. timestamp - the timestamp of the chat message +4. timeStr - the timestamp as a formatted string -This hook is called on the client side whenever a user recieves a chat message from the server. This can be used to create different notifications for chat messages. +This hook is called on the client side whenever a chat message is received from the server. It can be used to create different notifications for chat messages. ## collectContentPre Called from: src/static/js/contentcollector.js diff --git a/src/static/js/chat.js b/src/static/js/chat.js index bba3d87e5..38d6f38d3 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -78,7 +78,7 @@ var chat = (function() $("#chatinput").val(""); }, addMessage: function(msg, increment, isHistoryAdd) - { + { //correct the time msg.time += this._pad.clientTimeOffset; @@ -100,85 +100,68 @@ var chat = (function() var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank"); - /* Performs an action if your name is mentioned */ - var myName = $('#myusernameedit').val(); - myName = myName.toLowerCase(); - var chatText = text.toLowerCase(); - var wasMentioned = false; - if (chatText.indexOf(myName) !== -1 && myName != "undefined"){ - wasMentioned = true; + var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); + + // the hook args + var ctx = { + "authorName" : authorName, + "author" : msg.userId, + "text" : text, + "sticky" : false, + "timestamp" : msg.time, + "timeStr" : timeStr } - /* End of new action */ - var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); - - var html = "

" + authorName + ":" + timeStr + " " + text + "

"; - if(isHistoryAdd) - $(html).insertAfter('#chatloadmessagesbutton'); - else - $("#chattext").append(html); - - //should we increment the counter?? - if(increment && !isHistoryAdd) - { - var count = Number($("#chatcounter").text()); - count++; - - // 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"); + // is the users focus already in the chatbox? + var alreadyFocused = $("#chatinput").is(":focus"); - $("#chatcounter").text(count); - // chat throb stuff -- Just make it throw for twice as long - if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) - { // If the user was mentioned show for twice as long and flash the browser window - $.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' - }); + // does the user already have the chatbox open? + var chatOpen = $("#chatbox").is(":visible"); - chatMentions++; - Tinycon.setBubble(chatMentions); - } + // does this message contain this user's name? (is the curretn user mentioned?) + var myName = $('#myusernameedit').val(); + var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined"); + + if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) + { // If the user was mentioned show for twice as long and flash the browser window + chatMentions++; + Tinycon.setBubble(chatMentions); + ctx.sticky = true; + } + + // Call chat message hook + hooks.aCallAll("chatNewMessage", ctx, function() { + + var html = "

" + authorName + ":" + ctx.timeStr + " " + ctx.text + "

"; + if(isHistoryAdd) + $(html).insertAfter('#chatloadmessagesbutton'); else + $("#chattext").append(html); + + //should we increment the counter?? + if(increment && !isHistoryAdd) { - if(!chatOpen){ + // Update the counter of unread messages + var count = Number($("#chatcounter").text()); + count++; + $("#chatcounter").text(count); + + if(!chatOpen) { $.gritter.add({ // (string | mandatory) the heading of the notification - title: authorName, + title: ctx.authorName, // (string | mandatory) the text inside the notification - text: text, - + text: ctx.text, // (bool | optional) if you want it to fade out on its own or just sit there - sticky: false, + sticky: ctx.sticky, // (int | optional) the time you want it to be alive for before fading out time: '4000' }); - Tinycon.setBubble(count); - var msg = { - "authorName" : authorName, - "text" : text, - "sticky" : false, - "time" : timeStr - }; - hooks.aCallAll("chatNewMessage", { - "authorName" : authorName, - "text" : text, - "sticky" : false, - "time" : timeStr - }); } } - } - // Clear the chat mentions when the user clicks on the chat input box + }); + + // Clear the chat mentions when the user clicks on the chat input box $('#chatinput').click(function(){ chatMentions = 0; Tinycon.setBubble(0); From 2a1859cc6dd902715567d25c95e4fba66705938f Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 20 Mar 2013 01:14:04 +0000 Subject: [PATCH 035/104] mm k --- out/doc/api/api.html | 1819 ----------------------- out/doc/api/changeset_library.html | 175 --- out/doc/api/editorInfo.html | 161 -- out/doc/api/embed_parameters.html | 130 -- out/doc/api/hooks_client-side.html | 393 ----- out/doc/api/hooks_overview.html | 44 - out/doc/api/hooks_server-side.html | 329 ----- out/doc/api/http_api.html | 712 --------- out/doc/api/pluginfw.html | 51 - out/doc/assets/style.css | 44 - out/doc/custom_static.html | 41 - out/doc/database.html | 132 -- out/doc/documentation.html | 43 - out/doc/easysync/README.html | 29 - out/doc/index.html | 2202 ---------------------------- out/doc/localization.html | 130 -- out/doc/plugins.html | 169 --- 17 files changed, 6604 deletions(-) delete mode 100644 out/doc/api/api.html delete mode 100644 out/doc/api/changeset_library.html delete mode 100644 out/doc/api/editorInfo.html delete mode 100644 out/doc/api/embed_parameters.html delete mode 100644 out/doc/api/hooks_client-side.html delete mode 100644 out/doc/api/hooks_overview.html delete mode 100644 out/doc/api/hooks_server-side.html delete mode 100644 out/doc/api/http_api.html delete mode 100644 out/doc/api/pluginfw.html delete mode 100644 out/doc/assets/style.css delete mode 100644 out/doc/custom_static.html delete mode 100644 out/doc/database.html delete mode 100644 out/doc/documentation.html delete mode 100644 out/doc/easysync/README.html delete mode 100644 out/doc/index.html delete mode 100644 out/doc/localization.html delete mode 100644 out/doc/plugins.html diff --git a/out/doc/api/api.html b/out/doc/api/api.html deleted file mode 100644 index fa3490aa7..000000000 --- a/out/doc/api/api.html +++ /dev/null @@ -1,1819 +0,0 @@ - - - - - Embed parameters - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

Embed parameters#

-

You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters. - -

-

Example: - -

-

Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers. - -

-
<iframe src='http://pad.test.de/p/PAD_NAME?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
-

showLineNumbers#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showControls#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showChat#

-
    -
  • Boolean
  • -
-

Default: true - -

-

useMonospaceFont#

-
    -
  • Boolean
  • -
-

Default: false - -

-

userName#

-
    -
  • String
  • -
-

Default: "unnamed" - -

-

Example: userName=Etherpad%20User - -

-

userColor#

-
    -
  • String (css hex color value)
  • -
-

Default: randomly chosen by pad server - -

-

Example: userColor=%23ff9900 - -

-

noColors#

-
    -
  • Boolean
  • -
-

Default: false - -

-

alwaysShowChat#

-
    -
  • Boolean
  • -
-

Default: false - -

-

lang#

-
    -
  • String
  • -
-

Default: en - -

-

Example: lang=ar (translates the interface into Arabic) - -

-

rtl#

-
    -
  • Boolean
  • -
-

Default: true -Displays pad text from right to left. - - -

-

HTTP API#

-

What can I do with this API?#

-

The API gives another web application control of the pads. The basic functions are - -

-
    -
  • create/delete pads
  • -
  • grant/forbid access to pads
  • -
  • get/set pad content
  • -
-

The API is designed in a way, so you can reuse your existing user system with their permissions, and map it to etherpad lite. Means: Your web application still has to do authentication, but you can tell etherpad lite via the api, which visitors should get which permissions. This allows etherpad lite to fit into any web application and extend it with real-time functionality. You can embed the pads via an iframe into your website. - -

-

Take a look at HTTP API client libraries to see if a library in your favorite language. - -

-

Examples#

-

Example 1#

-

A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. - -

-

Portal maps the internal userid to an etherpad author. - -

-
-

Request: http://pad.domain/api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 - -

-

Response: {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-
-

Portal maps the internal userid to an etherpad group: - -

-
-

Request: http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7 - -

-

Response: {code: 0, message:"ok", data: {groupID: "g.s8oes9dhwrvt0zif"}} - -

-
-

Portal creates a pad in the userGroup - -

-
-

Request: http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad - -

-

Response: {code: 0, message:"ok", data: null} - -

-
-

Portal starts the session for the user on the group: - -

-
-

Request: http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246 - -

-

Response: {"data":{"sessionID": "s.s8oes9dhwrvt0zif"}} - -

-
-

Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad. - -

-

Example 2#

-

A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post. - -

-

Portal retrieves the contents of the pad for entry into the db as a blog post: - -

-
-

Request: http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123 - -

-

Response: {code: 0, message:"ok", data: {text:"Welcome Text"}} - -

-
-

Portal submits content into new blog post - -

-
-

Portal.AddNewBlog(content) - - -

-
-

Usage#

-

API version#

-

The latest version is 1.2.7 - -

-

The current version can be queried via /api. - -

-

Request Format#

-

The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION depends on the endpoints you want to use. - -

-

Response Format#

-

Responses are valid JSON in the following format: - -

-
{
-  "code": number,
-  "message": string,
-  "data": obj
-}
-
    -
  • code a return code
      -
    • 0 everything ok
    • -
    • 1 wrong parameters
    • -
    • 2 internal error
    • -
    • 3 no such function
    • -
    • 4 no or wrong API Key
    • -
    -
  • -
  • message a status message. Its ok if everything is fine, else it contains an error message
  • -
  • data the payload
  • -
-

Overview#

-

API Overview - -

-

Data Types#

-
    -
  • groupID a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
  • -
  • sessionID a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
  • -
  • authorID a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif
  • -
  • readOnlyID a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
  • -
  • padID a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test
  • -
-

Authentication#

-

Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad-Lite deployment. This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. - -

-

Node Interoperability#

-

All functions will also be available through a node module accessable from other node.js applications. - -

-

JSONP#

-

The API provides JSONP support to allow requests from a server in a different domain. -Simply add &jsonp=? to the API call. - -

-

Example usage: http://api.jquery.com/jQuery.getJSON/ - -

-

API Methods#

-

Groups#

-

Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test - -

-

createGroup()#

-
    -
  • API >= 1
  • -
-

creates a new group - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

createGroupIfNotExistsFor(groupMapper)#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application group ids to etherpad lite group ids - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

deleteGroup(groupID)#

-
    -
  • API >= 1
  • -
-

deletes a group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"groupID does not exist", data: null} - -

-

listPads(groupID)#

-
    -
  • API >= 1
  • -
-

returns all pads of this group - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]} - {code: 1, message:"groupID does not exist", data: null} - -

-

createGroupPad(groupID, padName [, text])#

-
    -
  • API >= 1
  • -
-

creates a new pad in this group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - * {code: 1, message:"groupID does not exist", data: null} - -

-

listAllGroups()#

-
    -
  • API >= 1.1
  • -
-

lists all existing groups - -

-

Example returns: - {code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}} - {code: 0, message:"ok", data: {groupIDs: []}} - -

-

Author#

-

These authors are bound to the attributes the users choose (color and name). - -

-

createAuthor([name])#

-
    -
  • API >= 1
  • -
-

creates a new author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

createAuthorIfNotExistsFor(authorMapper [, name])#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application author ids to etherpad lite author ids - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

listPadsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns an array of all pads this author contributed to - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs: ["g.s8oes9dhwrvt0zif$test", "g.s8oejklhwrvt0zif$foo"]}} - {code: 1, message:"authorID does not exist", data: null} - -

-

getAuthorName(authorID)#

-
    -
  • API >= 1.1
  • -
-

Returns the Author Name of the author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorName: "John McLear"}} - -

-

-> can't be deleted cause this would involve scanning all the pads where this author was - -

-

Session#

-

Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-seperated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out. - -

-

createSession(groupID, authorID, validUntil)#

-
    -
  • API >= 1
  • -
-

creates a new session. validUntil is an unix timestamp in seconds - -

-

Example returns: - {code: 0, message:"ok", data: {sessionID: "s.s8oes9dhwrvt0zif"}} - {code: 1, message:"groupID doesn't exist", data: null} - {code: 1, message:"authorID doesn't exist", data: null} - {code: 1, message:"validUntil is in the past", data: null} - -

-

deleteSession(sessionID)#

-
    -
  • API >= 1
  • -
-

deletes a session - -

-

Example returns: - {code: 1, message:"ok", data: null} - {code: 1, message:"sessionID does not exist", data: null} - -

-

getSessionInfo(sessionID)#

-
    -
  • API >= 1
  • -
-

returns informations about a session - -

-

Example returns: - {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}} - {code: 1, message:"sessionID does not exist", data: null} - -

-

listSessionsOfGroup(groupID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of a group - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"groupID does not exist", data: null} - -

-

listSessionsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of an author - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"authorID does not exist", data: null} - -

-

Pad Content#

-

Pad content can be updated and retrieved through the API - -

-

getText(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {text:"Welcome Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setText(padID, text)#

-
    -
  • API >= 1
  • -
-

sets the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - * {code: 1, message:"text too long", data: null} - -

-

getHTML(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad formatted as HTML - -

-

Example returns: - {code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

Chat#

-

getChatHistory(padID, [start, end])#

-
    -
  • API >= 1.2.7
  • -
-

returns - -

-
    -
  • a part of the chat history, when start and end are given
  • -
  • the whole chat histroy, when no extra parameters are given
  • -
-

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: 1, message:"start is higher or equal to the current chatHead", data: null}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

getChatHead(padID)#

-
    -
  • API >= 1.2.7
  • -
-

returns the chatHead (last number of the last chat-message) of the pad - - -

-

Example returns: - -

-
    -
  • {code: 0, message:"ok", data: {chatHead: 42}}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

Pad#

-

Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. - -

-

createPad(padID [, text])#

-
    -
  • API >= 1
  • -
-

creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad. - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - -

-

getRevisionsCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of revisions of this pad - -

-

Example returns: - {code: 0, message:"ok", data: {revisions: 56}} - {code: 1, message:"padID does not exist", data: null} - -

-

padUsersCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of user that are currently editing this pad - -

-

Example returns: - * {code: 0, message:"ok", data: {padUsersCount: 5}} - -

-

padUsers(padID)#

-
    -
  • API >= 1.1
  • -
-

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,"id":"a.n4gEeMLsvg12452n"},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042,"id":"a.n4gEeMLsvg12452n"}]}} - {code: 0, message:"ok", data: {padUsers: []}} - -

-

deletePad(padID)#

-
    -
  • API >= 1
  • -
-

deletes a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getReadOnlyID(padID)#

-
    -
  • API >= 1
  • -
-

returns the read only link of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPublicStatus(padID, publicStatus)#

-
    -
  • API >= 1
  • -
-

sets a boolean for the public status of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getPublicStatus(padID)#

-
    -
  • API >= 1
  • -
-

return true of false - -

-

Example returns: - {code: 0, message:"ok", data: {publicStatus: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPassword(padID, password)#

-
    -
  • API >= 1
  • -
-

returns ok or a error message - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

isPasswordProtected(padID)#

-
    -
  • API >= 1
  • -
-

returns true or false - -

-

Example returns: - {code: 0, message:"ok", data: {passwordProtection: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

listAuthorsOfPad(padID)#

-
    -
  • API >= 1
  • -
-

returns an array of authors who contributed to this pad - -

-

Example returns: - {code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]} - {code: 1, message:"padID does not exist", data: null} - -

-

getLastEdited(padID)#

-
    -
  • API >= 1
  • -
-

returns the timestamp of the last revision of the pad - -

-

Example returns: - {code: 0, message:"ok", data: {lastEdited: 1340815946602}} - {code: 1, message:"padID does not exist", data: null} - -

-

sendClientsMessage(padID, msg)#

-
    -
  • API >= 1.1
  • -
-

sends a custom message of type msg to the pad - -

-

Example returns: - {code: 0, message:"ok", data: {}} - {code: 1, message:"padID does not exist", data: null} - -

-

checkToken()#

-
    -
  • API >= 1.2
  • -
-

returns ok when the current api token is valid - -

-

Example returns: - {"code":0,"message":"ok","data":null} - {"code":4,"message":"no or wrong API Key","data":null} - -

-

Pads#

-

listAllPads()#

-
    -
  • API >= 1.2.1
  • -
-

lists all pads on this epl instance - -

-

Example returns: - * {code: 0, message:"ok", data: ["testPad", "thePadsOfTheOthers"]} - -

-

Hooks#

-

All hooks are called with two arguments: - -

-
    -
  1. name - the name of the hook being called
  2. -
  3. context - an object with some relevant information about the context of the call
  4. -
-

Return values#

-

A hook should always return a list or undefined. Returning undefined is equivalent to returning an empty list. -All the returned lists are appended to each other, so if the return values where [1, 2], undefined, [3, 4,], undefined and [5], the value returned by callHook would be [1, 2, 3, 4, 5]. - -

-

This is, because it should never matter if you have one plugin or several plugins doing some work - a single plugin should be able to make callHook return the same value a set of plugins are able to return collectively. So, any plugin can return a list of values, of any length, not just one value. -

-

Client-side hooks#

-

Most of these hooks are called during or in order to set up the formatting process. - -

-

documentReady#

-

Called from: src/templates/pad.html - -

-

Things in context: - -

-

nothing - -

-

This hook proxies the functionality of jQuery's $(document).ready event. - -

-

aceDomLineProcessLineAttributes#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - The current DOM line being processed
  2. -
  3. cls - The class of the current block element (useful for styling)
  4. -
-

This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. - -

-

The return value of this hook should have the following structure: - -

-

{ preHtml: String, postHtml: String, processedMarker: Boolean } - -

-

The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more. - -

-

aceCreateDomLine#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - the current DOM line being processed
  2. -
  3. cls - The class of the current element (useful for styling)
  4. -
-

This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped. - -

-

The return value of this hook should have the following structure: - -

-

{ extraOpenTags: String, extraCloseTags: String, cls: String } - -

-

extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward. - -

-

acePostWriteDomLineHTML#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. node - the DOM node that just got written to the page
  2. -
-

This hook is for right after a node has been fully formatted and written to the page. - -

-

aceAttribsToClasses#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. key - the current attribute being processed
  4. -
  5. value - the value of the attribute being processed
  6. -
-

This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM. - -

-

The return value for this function should be a list of classes, which will then be parsed into a valid class string. - -

-

aceGetFilterStack#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. browser - an object indicating which browser is accessing the page
  4. -
-

This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale [[ ]] syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above). - -

-

aceEditorCSS#

-

Called from: src/static/js/ace.js - -

-

Things in context: None - -

-

This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory. - -

-

aceInitInnerdocbodyHead#

-

Called from: src/static/js/ace.js - -

-

Things in context: - -

-
    -
  1. iframeHTML - the HTML of the editor iframe up to this point, in array format
  2. -
-

This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the <head> element of the editor HTML document. - -

-

aceEditEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document (this is a mystery to me)
  8. -
-

This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event. - -

-

aceRegisterBlockElements#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: None - -

-

The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements. - -

-

aceInitialized#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings)
  2. -
  3. rep - information about where the user's cursor is
  4. -
  5. documentAttributeManager - some kind of magic
  6. -
-

This hook is for inserting further information into the ace engine, for later use in formatting hooks. - -

-

postAceInit#

-

Called from: src/static/js/pad.js - -

-

Things in context: - -

-
    -
  1. ace - the ace object that is applied to this editor.
  2. -
-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up. - -

-

postTimesliderInit#

-

Called from: src/static/js/timeslider.js - -

-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up. - -

-

userJoinOrUpdate#

-

Called from: src/static/js/pad_userlist.js - -

-

Things in context: - -

-
    -
  1. info - the user information
  2. -
-

This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. - -

-

collectContentPre#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

collectContentPost#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

handleClientMessage_name#

-

Called from: src/static/js/collab_client.js - -

-

Things in context: - -

-
    -
  1. payload - the data that got sent with the message (use it for custom message content)
  2. -
-

This hook gets called every time the client receives a message of type name. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. - -

-

collab_client.js has a pretty extensive list of message types, if you want to take a look. - -

-

aceStartLineAndCharForPoint-aceEndLineAndCharForPoint#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. root - the span element of the current line
  8. -
  9. point - the starting/ending element where the cursor highlights
  10. -
  11. documentAttributeManager - information about attributes in the document
  12. -
-

This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection. -The return value should be an array of [line,char] - -

-

aceKeyEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document
  8. -
  9. evt - the fired event
  10. -
-

This hook is provided to allow a plugin to handle key events. -The return value should be true if you have handled the event. - -

-

collectContentLineText#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. text - the text for that line
  8. -
-

This hook allows you to validate/manipulate the text before it's sent to the server side. -The return value should be the validated/manipulated text. - -

-

collectContentLineBreak#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
-

This hook is provided to allow whether the br tag should induce a new magic domline or not. -The return value should be either true(break the line) or false. - -

-

disableAuthorColorsForThisLine#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. text - the line text
  4. -
  5. class - line class
  6. -
-

This hook is provided to allow whether a given line should be deliniated with multiple authors. -Multiple authors in one line cause the creation of magic span lines. This might not suit you and -now you can disable it and handle your own deliniation. -The return value should be either true(disable) or false. - -

-

Server-side hooks#

-

These hooks are called on server-side. - -

-

loadSettings#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. settings - the settings object
  2. -
-

Use this hook to receive the global settings in your plugin. - -

-

pluginUninstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool! - -

-

pluginInstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed. - -

-

init_<plugin name>#

-

Called from: src/static/js/pluginfw/plugins.js - -

-

Things in context: None - -

-

This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin. - -

-

expressConfigure#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main application object
  2. -
-

This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured. - -

-

expressCreateServer#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main express application object (helpful for adding new paths and such)
  2. -
  3. server - the http server object
  4. -
-

This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. - -

-

eejsBlock_<name>#

-

Called from: src/node/eejs/index.js - -

-

Things in context: - -

-
    -
  1. content - the content of the block
  2. -
-

This hook gets called upon the rendering of an ejs template block. For any specific kind of block, you can change how that block gets rendered by modifying the content object passed in. - -

-

Have a look at src/templates/pad.html and src/templates/timeslider.html to see which blocks are available. - -

-

padCreate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when a new pad was created. - -

-

padLoad#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an pad was loaded. If a new pad was created and loaded this event will be emitted too. - -

-

padUpdate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an existing pad was updated. - -

-

padRemove#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. padID
  2. -
-

This hook gets called when an existing pad was removed/deleted. - -

-

socketio#

-

Called from: src/node/hooks/express/socketio.js - -

-

Things in context: - -

-
    -
  1. app - the application object
  2. -
  3. io - the socketio object
  4. -
  5. server - the http server object
  6. -
-

I have no idea what this is useful for, someone else will have to add this description. - -

-

authorize#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. resource - the path being accessed
  8. -
-

This is useful for modifying the way authentication is done, especially for specific paths. - -

-

authenticate#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. username - the username used (optional)
  8. -
  9. password - the password used (optional)
  10. -
-

This is useful for modifying the way authentication is done. - -

-

authFailure#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
-

This is useful for modifying the way authentication is done. - -

-

handleMessage#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. message - the message being handled
  2. -
  3. client - the client object from socket.io
  4. -
-

This hook will be called once a message arrive. If a plugin calls callback(null) the message will be dropped. However it is not possible to modify the message. - -

-

Plugins may also decide to implement custom behavior once a message arrives. - -

-

WARNING: handleMessage will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions. - -

-

Example: - -

-
function handleMessage ( hook, context, callback ) {
-  if ( context.message.type == 'USERINFO_UPDATE' ) {
-    // If the message type is USERINFO_UPDATE, drop the message
-    callback(null);
-  }else{
-    callback();
-  }
-};
-

clientVars#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. clientVars - the basic clientVars built by the core
  2. -
  3. pad - the pad this session is about
  4. -
-

This hook will be called once a client connects and the clientVars are being sent. Plugins can use this hook to give the client a initial configuriation, like the tracking-id of an external analytics-tool that is used on the client-side. You can also overwrite values from the original clientVars. - -

-

Example: - -

-
exports.clientVars = function(hook, context, callback)
-{
-  // tell the client which year we are in
-  return callback({ "currentYear": new Date().getFullYear() });
-};
-

This can be accessed on the client-side using clientVars.currentYear. - -

-

getLineHTMLForExport#

-

Called from: src/node/utils/ExportHtml.js - -

-

Things in context: - -

-
    -
  1. apool - pool object
  2. -
  3. attribLine - line attributes
  4. -
  5. text - line text
  6. -
-

This hook will allow a plug-in developer to re-write each line when exporting to HTML. - - -

-

editorInfo#

-

editorInfo.ace_replaceRange(start, end, text)#

-

This function replaces a range (from start to end) with text. - -

-

editorInfo.ace_getRep()#

-

Returns the rep object. - -

-

editorInfo.ace_getAuthor()#

-

editorInfo.ace_inCallStack()#

-

editorInfo.ace_inCallStackIfNecessary(?)#

-

editorInfo.ace_focus(?)#

-

editorInfo.ace_importText(?)#

-

editorInfo.ace_importAText(?)#

-

editorInfo.ace_exportText(?)#

-

editorInfo.ace_editorChangedSize(?)#

-

editorInfo.ace_setOnKeyPress(?)#

-

editorInfo.ace_setOnKeyDown(?)#

-

editorInfo.ace_setNotifyDirty(?)#

-

editorInfo.ace_dispose(?)#

-

editorInfo.ace_getFormattedCode(?)#

-

editorInfo.ace_setEditable(bool)#

-

editorInfo.ace_execCommand(?)#

-

editorInfo.ace_callWithAce(fn, callStack, normalize)#

-

editorInfo.ace_setProperty(key, value)#

-

editorInfo.ace_setBaseText(txt)#

-

editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj)#

-

editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj)#

-

editorInfo.ace_prepareUserChangeset()#

-

editorInfo.ace_applyPreparedChangesetToBase()#

-

editorInfo.ace_setUserChangeNotificationCallback(f)#

-

editorInfo.ace_setAuthorInfo(author, info)#

-

editorInfo.ace_setAuthorSelectionRange(author, start, end)#

-

editorInfo.ace_getUnhandledErrors()#

-

editorInfo.ace_getDebugProperty(prop)#

-

editorInfo.ace_fastIncorp(?)#

-

editorInfo.ace_isCaret(?)#

-

editorInfo.ace_getLineAndCharForPoint(?)#

-

editorInfo.ace_performDocumentApplyAttributesToCharRange(?)#

-

editorInfo.ace_setAttributeOnSelection(?)#

-

editorInfo.ace_toggleAttributeOnSelection(?)#

-

editorInfo.ace_performSelectionChange(?)#

-

editorInfo.ace_doIndentOutdent(?)#

-

editorInfo.ace_doUndoRedo(?)#

-

editorInfo.ace_doInsertUnorderedList(?)#

-

editorInfo.ace_doInsertOrderedList(?)#

-

editorInfo.ace_performDocumentApplyAttributesToRange()#

-

editorInfo.ace_getAuthorInfos()#

-

Returns an info object about the author. Object key = author_id and info includes author's bg color value. -Use to define your own authorship. -

-

editorInfo.ace_performDocumentReplaceRange(start, end, newText)#

-

This function replaces a range (from [x1,y1] to [x2,y2]) with newText. -

-

editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText)#

-

This function replaces a range (from y1 to y2) with newText. -

-

editorInfo.ace_renumberList(lineNum)#

-

If you delete a line, calling this method will fix the line numbering. -

-

editorInfo.ace_doReturnKey()#

-

Forces a return key at the current carret position. -

-

editorInfo.ace_isBlockElement(element)#

-

Returns true if your passed elment is registered as a block element -

-

editorInfo.ace_getLineListType(lineNum)#

-

Returns the line's html list type. -

-

editorInfo.ace_caretLine()#

-

Returns X position of the caret. -

-

editorInfo.ace_caretColumn()#

-

Returns Y position of the caret. -

-

editorInfo.ace_caretDocChar()#

-

Returns the Y offset starting from [x=0,y=0] -

-

editorInfo.ace_isWordChar(?)#

-

Changeset Library#

-
"Z:z>1|2=m=b*0|1+1$\n"
-

This is a Changeset. Its just a string and its very difficult to read in this form. But the Changeset Library gives us some tools to read it. - -

-

A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. - -

-

Changeset.unpack(changeset)#

-
    -
  • changeset String
  • -
-

This functions returns an object representaion of the changeset, similar to this: - -

-
{ oldLen: 35, newLen: 36, ops: '|2=m=b*0|1+1', charBank: '\n' }
-
    -
  • oldLen {Number} the original length of the document.
  • -
  • newLen {Number} the length of the document after the changeset is applied.
  • -
  • ops {String} the actual changes, introduced by this changeset.
  • -
  • charBank {String} All characters that are added by this changeset.
  • -
-

Changeset.opIterator(ops)#

-
    -
  • ops String The operators, returned by Changeset.unpack()
  • -
-

Returns an operator iterator. This iterator allows us to iterate over all operators that are in the changeset. - -

-

You can iterate with an opIterator using its next() and hasNext() methods. Next returns the next() operator object and hasNext() indicates, whether there are any operators left. - -

-

The Operator object#

-

There are 3 types of operators: +,- and =. These operators describe different changes to the document, beginning with the first character of the document. A = operator doesn't change the text, but it may add or remove text attributes. A - operator removes text. And a + Operator adds text and optionally adds some attributes to it. - -

-
    -
  • opcode {String} the operator type
  • -
  • chars {Number} the length of the text changed by this operator.
  • -
  • lines {Number} the number of lines changed by this operator.
  • -
  • attribs {attribs} attributes set on this text.
  • -
-

Example#

-
{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-

APool#

-
> var AttributePoolFactory = require("./utils/AttributePoolFactory");
-> var apool = AttributePoolFactory.createAttributePool();
-> console.log(apool)
-{ numToAttrib: {},
-  attribToNum: {},
-  nextNum: 0,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

This creates an empty apool. A apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Lets fill this apool with some values - -

-
> apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
-> console.log(apool)
-{ numToAttrib: 
-   { '0': [ 'author', 'a.kVnWeomPADAT2pn9' ],
-     '1': [ 'bold', 'true' ],
-     '2': [ 'italic', 'true' ] },
-  attribToNum: 
-   { 'author,a.kVnWeomPADAT2pn9': 0,
-     'bold,true': 1,
-     'italic,true': 2 },
-  nextNum: 3,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. A attribute is always a key value pair. For stuff like bold and italic its just 'italic':'true'. For authors its author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors - -

-
> apool.getAttrib(1)
-[ 'bold', 'true' ]
-

Simple example of how to get the key value pair for the attribute 1 - -

-

AText#

-
> var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
-> console.log(atext)
-{ text: 'bold text\nitalic text\nnormal text\n\n',
-  attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
-

This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps - -

-
> var opiterator = Changeset.opIterator(atext.attribs)
-> console.log(opiterator)
-{ next: [Function: next],
-  hasNext: [Function: hasNext],
-  lastIndex: [Function: lastIndex] }
-> opiterator.next()
-{ opcode: '+',
-  chars: 9,
-  lines: 0,
-  attribs: '*0*1' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0*1*2' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 2,
-  lines: 2,
-  attribs: '' }
-

The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes - -

-

For more information see /doc/easysync/easysync-notes.txt in the source. - -

-

Plugin Framework#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins") - -

-

plugins.update#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files, registering the contained hooks. -A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name) - -

-

hooks.callAll#

-

require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name with {argname:value}. - -

-

hooks.aCallAll#

-

? - -

-

...#

- -
- - - - diff --git a/out/doc/api/changeset_library.html b/out/doc/api/changeset_library.html deleted file mode 100644 index 3459ee7bc..000000000 --- a/out/doc/api/changeset_library.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - Changeset Library - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Changeset Library#

-
"Z:z>1|2=m=b*0|1+1$\n"
-

This is a Changeset. Its just a string and its very difficult to read in this form. But the Changeset Library gives us some tools to read it. - -

-

A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. - -

-

Changeset.unpack(changeset)#

-
    -
  • changeset String
  • -
-

This functions returns an object representaion of the changeset, similar to this: - -

-
{ oldLen: 35, newLen: 36, ops: '|2=m=b*0|1+1', charBank: '\n' }
-
    -
  • oldLen {Number} the original length of the document.
  • -
  • newLen {Number} the length of the document after the changeset is applied.
  • -
  • ops {String} the actual changes, introduced by this changeset.
  • -
  • charBank {String} All characters that are added by this changeset.
  • -
-

Changeset.opIterator(ops)#

-
    -
  • ops String The operators, returned by Changeset.unpack()
  • -
-

Returns an operator iterator. This iterator allows us to iterate over all operators that are in the changeset. - -

-

You can iterate with an opIterator using its next() and hasNext() methods. Next returns the next() operator object and hasNext() indicates, whether there are any operators left. - -

-

The Operator object#

-

There are 3 types of operators: +,- and =. These operators describe different changes to the document, beginning with the first character of the document. A = operator doesn't change the text, but it may add or remove text attributes. A - operator removes text. And a + Operator adds text and optionally adds some attributes to it. - -

-
    -
  • opcode {String} the operator type
  • -
  • chars {Number} the length of the text changed by this operator.
  • -
  • lines {Number} the number of lines changed by this operator.
  • -
  • attribs {attribs} attributes set on this text.
  • -
-

Example#

-
{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-

APool#

-
> var AttributePoolFactory = require("./utils/AttributePoolFactory");
-> var apool = AttributePoolFactory.createAttributePool();
-> console.log(apool)
-{ numToAttrib: {},
-  attribToNum: {},
-  nextNum: 0,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

This creates an empty apool. A apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Lets fill this apool with some values - -

-
> apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
-> console.log(apool)
-{ numToAttrib: 
-   { '0': [ 'author', 'a.kVnWeomPADAT2pn9' ],
-     '1': [ 'bold', 'true' ],
-     '2': [ 'italic', 'true' ] },
-  attribToNum: 
-   { 'author,a.kVnWeomPADAT2pn9': 0,
-     'bold,true': 1,
-     'italic,true': 2 },
-  nextNum: 3,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. A attribute is always a key value pair. For stuff like bold and italic its just 'italic':'true'. For authors its author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors - -

-
> apool.getAttrib(1)
-[ 'bold', 'true' ]
-

Simple example of how to get the key value pair for the attribute 1 - -

-

AText#

-
> var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
-> console.log(atext)
-{ text: 'bold text\nitalic text\nnormal text\n\n',
-  attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
-

This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps - -

-
> var opiterator = Changeset.opIterator(atext.attribs)
-> console.log(opiterator)
-{ next: [Function: next],
-  hasNext: [Function: hasNext],
-  lastIndex: [Function: lastIndex] }
-> opiterator.next()
-{ opcode: '+',
-  chars: 9,
-  lines: 0,
-  attribs: '*0*1' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0*1*2' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 2,
-  lines: 2,
-  attribs: '' }
-

The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes - -

-

For more information see /doc/easysync/easysync-notes.txt in the source. -

- -
- - - - diff --git a/out/doc/api/editorInfo.html b/out/doc/api/editorInfo.html deleted file mode 100644 index 864b06751..000000000 --- a/out/doc/api/editorInfo.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - editorInfo - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

editorInfo#

-

editorInfo.ace_replaceRange(start, end, text)#

-

This function replaces a range (from start to end) with text. - -

-

editorInfo.ace_getRep()#

-

Returns the rep object. - -

-

editorInfo.ace_getAuthor()#

-

editorInfo.ace_inCallStack()#

-

editorInfo.ace_inCallStackIfNecessary(?)#

-

editorInfo.ace_focus(?)#

-

editorInfo.ace_importText(?)#

-

editorInfo.ace_importAText(?)#

-

editorInfo.ace_exportText(?)#

-

editorInfo.ace_editorChangedSize(?)#

-

editorInfo.ace_setOnKeyPress(?)#

-

editorInfo.ace_setOnKeyDown(?)#

-

editorInfo.ace_setNotifyDirty(?)#

-

editorInfo.ace_dispose(?)#

-

editorInfo.ace_getFormattedCode(?)#

-

editorInfo.ace_setEditable(bool)#

-

editorInfo.ace_execCommand(?)#

-

editorInfo.ace_callWithAce(fn, callStack, normalize)#

-

editorInfo.ace_setProperty(key, value)#

-

editorInfo.ace_setBaseText(txt)#

-

editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj)#

-

editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj)#

-

editorInfo.ace_prepareUserChangeset()#

-

editorInfo.ace_applyPreparedChangesetToBase()#

-

editorInfo.ace_setUserChangeNotificationCallback(f)#

-

editorInfo.ace_setAuthorInfo(author, info)#

-

editorInfo.ace_setAuthorSelectionRange(author, start, end)#

-

editorInfo.ace_getUnhandledErrors()#

-

editorInfo.ace_getDebugProperty(prop)#

-

editorInfo.ace_fastIncorp(?)#

-

editorInfo.ace_isCaret(?)#

-

editorInfo.ace_getLineAndCharForPoint(?)#

-

editorInfo.ace_performDocumentApplyAttributesToCharRange(?)#

-

editorInfo.ace_setAttributeOnSelection(?)#

-

editorInfo.ace_toggleAttributeOnSelection(?)#

-

editorInfo.ace_performSelectionChange(?)#

-

editorInfo.ace_doIndentOutdent(?)#

-

editorInfo.ace_doUndoRedo(?)#

-

editorInfo.ace_doInsertUnorderedList(?)#

-

editorInfo.ace_doInsertOrderedList(?)#

-

editorInfo.ace_performDocumentApplyAttributesToRange()#

-

editorInfo.ace_getAuthorInfos()#

-

Returns an info object about the author. Object key = author_id and info includes author's bg color value. -Use to define your own authorship. -

-

editorInfo.ace_performDocumentReplaceRange(start, end, newText)#

-

This function replaces a range (from [x1,y1] to [x2,y2]) with newText. -

-

editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText)#

-

This function replaces a range (from y1 to y2) with newText. -

-

editorInfo.ace_renumberList(lineNum)#

-

If you delete a line, calling this method will fix the line numbering. -

-

editorInfo.ace_doReturnKey()#

-

Forces a return key at the current carret position. -

-

editorInfo.ace_isBlockElement(element)#

-

Returns true if your passed elment is registered as a block element -

-

editorInfo.ace_getLineListType(lineNum)#

-

Returns the line's html list type. -

-

editorInfo.ace_caretLine()#

-

Returns X position of the caret. -

-

editorInfo.ace_caretColumn()#

-

Returns Y position of the caret. -

-

editorInfo.ace_caretDocChar()#

-

Returns the Y offset starting from [x=0,y=0] -

-

editorInfo.ace_isWordChar(?)#

- -
- - - - diff --git a/out/doc/api/embed_parameters.html b/out/doc/api/embed_parameters.html deleted file mode 100644 index da521a3d6..000000000 --- a/out/doc/api/embed_parameters.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Embed parameters - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Embed parameters#

-

You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters. - -

-

Example: - -

-

Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers. - -

-
<iframe src='http://pad.test.de/p/PAD_NAME?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
-

showLineNumbers#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showControls#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showChat#

-
    -
  • Boolean
  • -
-

Default: true - -

-

useMonospaceFont#

-
    -
  • Boolean
  • -
-

Default: false - -

-

userName#

-
    -
  • String
  • -
-

Default: "unnamed" - -

-

Example: userName=Etherpad%20User - -

-

userColor#

-
    -
  • String (css hex color value)
  • -
-

Default: randomly chosen by pad server - -

-

Example: userColor=%23ff9900 - -

-

noColors#

-
    -
  • Boolean
  • -
-

Default: false - -

-

alwaysShowChat#

-
    -
  • Boolean
  • -
-

Default: false - -

-

lang#

-
    -
  • String
  • -
-

Default: en - -

-

Example: lang=ar (translates the interface into Arabic) - -

-

rtl#

-
    -
  • Boolean
  • -
-

Default: true -Displays pad text from right to left. - -

- -
- - - - diff --git a/out/doc/api/hooks_client-side.html b/out/doc/api/hooks_client-side.html deleted file mode 100644 index 46b17f1e9..000000000 --- a/out/doc/api/hooks_client-side.html +++ /dev/null @@ -1,393 +0,0 @@ - - - - - Client-side hooks - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Client-side hooks#

-

Most of these hooks are called during or in order to set up the formatting process. - -

-

documentReady#

-

Called from: src/templates/pad.html - -

-

Things in context: - -

-

nothing - -

-

This hook proxies the functionality of jQuery's $(document).ready event. - -

-

aceDomLineProcessLineAttributes#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - The current DOM line being processed
  2. -
  3. cls - The class of the current block element (useful for styling)
  4. -
-

This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. - -

-

The return value of this hook should have the following structure: - -

-

{ preHtml: String, postHtml: String, processedMarker: Boolean } - -

-

The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more. - -

-

aceCreateDomLine#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - the current DOM line being processed
  2. -
  3. cls - The class of the current element (useful for styling)
  4. -
-

This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped. - -

-

The return value of this hook should have the following structure: - -

-

{ extraOpenTags: String, extraCloseTags: String, cls: String } - -

-

extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward. - -

-

acePostWriteDomLineHTML#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. node - the DOM node that just got written to the page
  2. -
-

This hook is for right after a node has been fully formatted and written to the page. - -

-

aceAttribsToClasses#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. key - the current attribute being processed
  4. -
  5. value - the value of the attribute being processed
  6. -
-

This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM. - -

-

The return value for this function should be a list of classes, which will then be parsed into a valid class string. - -

-

aceGetFilterStack#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. browser - an object indicating which browser is accessing the page
  4. -
-

This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale [[ ]] syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above). - -

-

aceEditorCSS#

-

Called from: src/static/js/ace.js - -

-

Things in context: None - -

-

This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory. - -

-

aceInitInnerdocbodyHead#

-

Called from: src/static/js/ace.js - -

-

Things in context: - -

-
    -
  1. iframeHTML - the HTML of the editor iframe up to this point, in array format
  2. -
-

This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the <head> element of the editor HTML document. - -

-

aceEditEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document (this is a mystery to me)
  8. -
-

This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event. - -

-

aceRegisterBlockElements#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: None - -

-

The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements. - -

-

aceInitialized#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings)
  2. -
  3. rep - information about where the user's cursor is
  4. -
  5. documentAttributeManager - some kind of magic
  6. -
-

This hook is for inserting further information into the ace engine, for later use in formatting hooks. - -

-

postAceInit#

-

Called from: src/static/js/pad.js - -

-

Things in context: - -

-
    -
  1. ace - the ace object that is applied to this editor.
  2. -
-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up. - -

-

postTimesliderInit#

-

Called from: src/static/js/timeslider.js - -

-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up. - -

-

userJoinOrUpdate#

-

Called from: src/static/js/pad_userlist.js - -

-

Things in context: - -

-
    -
  1. info - the user information
  2. -
-

This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. - -

-

collectContentPre#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

collectContentPost#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

handleClientMessage_name#

-

Called from: src/static/js/collab_client.js - -

-

Things in context: - -

-
    -
  1. payload - the data that got sent with the message (use it for custom message content)
  2. -
-

This hook gets called every time the client receives a message of type name. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. - -

-

collab_client.js has a pretty extensive list of message types, if you want to take a look. - -

-

aceStartLineAndCharForPoint-aceEndLineAndCharForPoint#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. root - the span element of the current line
  8. -
  9. point - the starting/ending element where the cursor highlights
  10. -
  11. documentAttributeManager - information about attributes in the document
  12. -
-

This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection. -The return value should be an array of [line,char] - -

-

aceKeyEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document
  8. -
  9. evt - the fired event
  10. -
-

This hook is provided to allow a plugin to handle key events. -The return value should be true if you have handled the event. - -

-

collectContentLineText#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. text - the text for that line
  8. -
-

This hook allows you to validate/manipulate the text before it's sent to the server side. -The return value should be the validated/manipulated text. - -

-

collectContentLineBreak#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
-

This hook is provided to allow whether the br tag should induce a new magic domline or not. -The return value should be either true(break the line) or false. - -

-

disableAuthorColorsForThisLine#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. text - the line text
  4. -
  5. class - line class
  6. -
-

This hook is provided to allow whether a given line should be deliniated with multiple authors. -Multiple authors in one line cause the creation of magic span lines. This might not suit you and -now you can disable it and handle your own deliniation. -The return value should be either true(disable) or false. -

- -
- - - - diff --git a/out/doc/api/hooks_overview.html b/out/doc/api/hooks_overview.html deleted file mode 100644 index de50a7f0c..000000000 --- a/out/doc/api/hooks_overview.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Hooks - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

Hooks#

-

All hooks are called with two arguments: - -

-
    -
  1. name - the name of the hook being called
  2. -
  3. context - an object with some relevant information about the context of the call
  4. -
-

Return values#

-

A hook should always return a list or undefined. Returning undefined is equivalent to returning an empty list. -All the returned lists are appended to each other, so if the return values where [1, 2], undefined, [3, 4,], undefined and [5], the value returned by callHook would be [1, 2, 3, 4, 5]. - -

-

This is, because it should never matter if you have one plugin or several plugins doing some work - a single plugin should be able to make callHook return the same value a set of plugins are able to return collectively. So, any plugin can return a list of values, of any length, not just one value.

- -
- - - - diff --git a/out/doc/api/hooks_server-side.html b/out/doc/api/hooks_server-side.html deleted file mode 100644 index bc19b85cf..000000000 --- a/out/doc/api/hooks_server-side.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - Server-side hooks - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Server-side hooks#

-

These hooks are called on server-side. - -

-

loadSettings#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. settings - the settings object
  2. -
-

Use this hook to receive the global settings in your plugin. - -

-

pluginUninstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool! - -

-

pluginInstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed. - -

-

init_<plugin name>#

-

Called from: src/static/js/pluginfw/plugins.js - -

-

Things in context: None - -

-

This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin. - -

-

expressConfigure#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main application object
  2. -
-

This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured. - -

-

expressCreateServer#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main express application object (helpful for adding new paths and such)
  2. -
  3. server - the http server object
  4. -
-

This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. - -

-

eejsBlock_<name>#

-

Called from: src/node/eejs/index.js - -

-

Things in context: - -

-
    -
  1. content - the content of the block
  2. -
-

This hook gets called upon the rendering of an ejs template block. For any specific kind of block, you can change how that block gets rendered by modifying the content object passed in. - -

-

Have a look at src/templates/pad.html and src/templates/timeslider.html to see which blocks are available. - -

-

padCreate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when a new pad was created. - -

-

padLoad#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an pad was loaded. If a new pad was created and loaded this event will be emitted too. - -

-

padUpdate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an existing pad was updated. - -

-

padRemove#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. padID
  2. -
-

This hook gets called when an existing pad was removed/deleted. - -

-

socketio#

-

Called from: src/node/hooks/express/socketio.js - -

-

Things in context: - -

-
    -
  1. app - the application object
  2. -
  3. io - the socketio object
  4. -
  5. server - the http server object
  6. -
-

I have no idea what this is useful for, someone else will have to add this description. - -

-

authorize#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. resource - the path being accessed
  8. -
-

This is useful for modifying the way authentication is done, especially for specific paths. - -

-

authenticate#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. username - the username used (optional)
  8. -
  9. password - the password used (optional)
  10. -
-

This is useful for modifying the way authentication is done. - -

-

authFailure#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
-

This is useful for modifying the way authentication is done. - -

-

handleMessage#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. message - the message being handled
  2. -
  3. client - the client object from socket.io
  4. -
-

This hook will be called once a message arrive. If a plugin calls callback(null) the message will be dropped. However it is not possible to modify the message. - -

-

Plugins may also decide to implement custom behavior once a message arrives. - -

-

WARNING: handleMessage will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions. - -

-

Example: - -

-
function handleMessage ( hook, context, callback ) {
-  if ( context.message.type == 'USERINFO_UPDATE' ) {
-    // If the message type is USERINFO_UPDATE, drop the message
-    callback(null);
-  }else{
-    callback();
-  }
-};
-

clientVars#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. clientVars - the basic clientVars built by the core
  2. -
  3. pad - the pad this session is about
  4. -
-

This hook will be called once a client connects and the clientVars are being sent. Plugins can use this hook to give the client a initial configuriation, like the tracking-id of an external analytics-tool that is used on the client-side. You can also overwrite values from the original clientVars. - -

-

Example: - -

-
exports.clientVars = function(hook, context, callback)
-{
-  // tell the client which year we are in
-  return callback({ "currentYear": new Date().getFullYear() });
-};
-

This can be accessed on the client-side using clientVars.currentYear. - -

-

getLineHTMLForExport#

-

Called from: src/node/utils/ExportHtml.js - -

-

Things in context: - -

-
    -
  1. apool - pool object
  2. -
  3. attribLine - line attributes
  4. -
  5. text - line text
  6. -
-

This hook will allow a plug-in developer to re-write each line when exporting to HTML. - -

- -
- - - - diff --git a/out/doc/api/http_api.html b/out/doc/api/http_api.html deleted file mode 100644 index cf27680ca..000000000 --- a/out/doc/api/http_api.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - HTTP API - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

HTTP API#

-

What can I do with this API?#

-

The API gives another web application control of the pads. The basic functions are - -

-
    -
  • create/delete pads
  • -
  • grant/forbid access to pads
  • -
  • get/set pad content
  • -
-

The API is designed in a way, so you can reuse your existing user system with their permissions, and map it to etherpad lite. Means: Your web application still has to do authentication, but you can tell etherpad lite via the api, which visitors should get which permissions. This allows etherpad lite to fit into any web application and extend it with real-time functionality. You can embed the pads via an iframe into your website. - -

-

Take a look at HTTP API client libraries to see if a library in your favorite language. - -

-

Examples#

-

Example 1#

-

A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. - -

-

Portal maps the internal userid to an etherpad author. - -

-
-

Request: http://pad.domain/api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 - -

-

Response: {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-
-

Portal maps the internal userid to an etherpad group: - -

-
-

Request: http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7 - -

-

Response: {code: 0, message:"ok", data: {groupID: "g.s8oes9dhwrvt0zif"}} - -

-
-

Portal creates a pad in the userGroup - -

-
-

Request: http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad - -

-

Response: {code: 0, message:"ok", data: null} - -

-
-

Portal starts the session for the user on the group: - -

-
-

Request: http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246 - -

-

Response: {"data":{"sessionID": "s.s8oes9dhwrvt0zif"}} - -

-
-

Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad. - -

-

Example 2#

-

A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post. - -

-

Portal retrieves the contents of the pad for entry into the db as a blog post: - -

-
-

Request: http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123 - -

-

Response: {code: 0, message:"ok", data: {text:"Welcome Text"}} - -

-
-

Portal submits content into new blog post - -

-
-

Portal.AddNewBlog(content) - - -

-
-

Usage#

-

API version#

-

The latest version is 1.2.7 - -

-

The current version can be queried via /api. - -

-

Request Format#

-

The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION depends on the endpoints you want to use. - -

-

Response Format#

-

Responses are valid JSON in the following format: - -

-
{
-  "code": number,
-  "message": string,
-  "data": obj
-}
-
    -
  • code a return code
      -
    • 0 everything ok
    • -
    • 1 wrong parameters
    • -
    • 2 internal error
    • -
    • 3 no such function
    • -
    • 4 no or wrong API Key
    • -
    -
  • -
  • message a status message. Its ok if everything is fine, else it contains an error message
  • -
  • data the payload
  • -
-

Overview#

-

API Overview - -

-

Data Types#

-
    -
  • groupID a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
  • -
  • sessionID a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
  • -
  • authorID a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif
  • -
  • readOnlyID a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
  • -
  • padID a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test
  • -
-

Authentication#

-

Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad-Lite deployment. This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. - -

-

Node Interoperability#

-

All functions will also be available through a node module accessable from other node.js applications. - -

-

JSONP#

-

The API provides JSONP support to allow requests from a server in a different domain. -Simply add &jsonp=? to the API call. - -

-

Example usage: http://api.jquery.com/jQuery.getJSON/ - -

-

API Methods#

-

Groups#

-

Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test - -

-

createGroup()#

-
    -
  • API >= 1
  • -
-

creates a new group - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

createGroupIfNotExistsFor(groupMapper)#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application group ids to etherpad lite group ids - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

deleteGroup(groupID)#

-
    -
  • API >= 1
  • -
-

deletes a group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"groupID does not exist", data: null} - -

-

listPads(groupID)#

-
    -
  • API >= 1
  • -
-

returns all pads of this group - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]} - {code: 1, message:"groupID does not exist", data: null} - -

-

createGroupPad(groupID, padName [, text])#

-
    -
  • API >= 1
  • -
-

creates a new pad in this group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - * {code: 1, message:"groupID does not exist", data: null} - -

-

listAllGroups()#

-
    -
  • API >= 1.1
  • -
-

lists all existing groups - -

-

Example returns: - {code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}} - {code: 0, message:"ok", data: {groupIDs: []}} - -

-

Author#

-

These authors are bound to the attributes the users choose (color and name). - -

-

createAuthor([name])#

-
    -
  • API >= 1
  • -
-

creates a new author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

createAuthorIfNotExistsFor(authorMapper [, name])#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application author ids to etherpad lite author ids - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

listPadsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns an array of all pads this author contributed to - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs: ["g.s8oes9dhwrvt0zif$test", "g.s8oejklhwrvt0zif$foo"]}} - {code: 1, message:"authorID does not exist", data: null} - -

-

getAuthorName(authorID)#

-
    -
  • API >= 1.1
  • -
-

Returns the Author Name of the author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorName: "John McLear"}} - -

-

-> can't be deleted cause this would involve scanning all the pads where this author was - -

-

Session#

-

Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-seperated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out. - -

-

createSession(groupID, authorID, validUntil)#

-
    -
  • API >= 1
  • -
-

creates a new session. validUntil is an unix timestamp in seconds - -

-

Example returns: - {code: 0, message:"ok", data: {sessionID: "s.s8oes9dhwrvt0zif"}} - {code: 1, message:"groupID doesn't exist", data: null} - {code: 1, message:"authorID doesn't exist", data: null} - {code: 1, message:"validUntil is in the past", data: null} - -

-

deleteSession(sessionID)#

-
    -
  • API >= 1
  • -
-

deletes a session - -

-

Example returns: - {code: 1, message:"ok", data: null} - {code: 1, message:"sessionID does not exist", data: null} - -

-

getSessionInfo(sessionID)#

-
    -
  • API >= 1
  • -
-

returns informations about a session - -

-

Example returns: - {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}} - {code: 1, message:"sessionID does not exist", data: null} - -

-

listSessionsOfGroup(groupID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of a group - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"groupID does not exist", data: null} - -

-

listSessionsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of an author - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"authorID does not exist", data: null} - -

-

Pad Content#

-

Pad content can be updated and retrieved through the API - -

-

getText(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {text:"Welcome Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setText(padID, text)#

-
    -
  • API >= 1
  • -
-

sets the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - * {code: 1, message:"text too long", data: null} - -

-

getHTML(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad formatted as HTML - -

-

Example returns: - {code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

Chat#

-

getChatHistory(padID, [start, end])#

-
    -
  • API >= 1.2.7
  • -
-

returns - -

-
    -
  • a part of the chat history, when start and end are given
  • -
  • the whole chat histroy, when no extra parameters are given
  • -
-

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: 1, message:"start is higher or equal to the current chatHead", data: null}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

getChatHead(padID)#

-
    -
  • API >= 1.2.7
  • -
-

returns the chatHead (last number of the last chat-message) of the pad - - -

-

Example returns: - -

-
    -
  • {code: 0, message:"ok", data: {chatHead: 42}}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

Pad#

-

Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. - -

-

createPad(padID [, text])#

-
    -
  • API >= 1
  • -
-

creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad. - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - -

-

getRevisionsCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of revisions of this pad - -

-

Example returns: - {code: 0, message:"ok", data: {revisions: 56}} - {code: 1, message:"padID does not exist", data: null} - -

-

padUsersCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of user that are currently editing this pad - -

-

Example returns: - * {code: 0, message:"ok", data: {padUsersCount: 5}} - -

-

padUsers(padID)#

-
    -
  • API >= 1.1
  • -
-

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,"id":"a.n4gEeMLsvg12452n"},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042,"id":"a.n4gEeMLsvg12452n"}]}} - {code: 0, message:"ok", data: {padUsers: []}} - -

-

deletePad(padID)#

-
    -
  • API >= 1
  • -
-

deletes a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getReadOnlyID(padID)#

-
    -
  • API >= 1
  • -
-

returns the read only link of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPublicStatus(padID, publicStatus)#

-
    -
  • API >= 1
  • -
-

sets a boolean for the public status of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getPublicStatus(padID)#

-
    -
  • API >= 1
  • -
-

return true of false - -

-

Example returns: - {code: 0, message:"ok", data: {publicStatus: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPassword(padID, password)#

-
    -
  • API >= 1
  • -
-

returns ok or a error message - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

isPasswordProtected(padID)#

-
    -
  • API >= 1
  • -
-

returns true or false - -

-

Example returns: - {code: 0, message:"ok", data: {passwordProtection: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

listAuthorsOfPad(padID)#

-
    -
  • API >= 1
  • -
-

returns an array of authors who contributed to this pad - -

-

Example returns: - {code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]} - {code: 1, message:"padID does not exist", data: null} - -

-

getLastEdited(padID)#

-
    -
  • API >= 1
  • -
-

returns the timestamp of the last revision of the pad - -

-

Example returns: - {code: 0, message:"ok", data: {lastEdited: 1340815946602}} - {code: 1, message:"padID does not exist", data: null} - -

-

sendClientsMessage(padID, msg)#

-
    -
  • API >= 1.1
  • -
-

sends a custom message of type msg to the pad - -

-

Example returns: - {code: 0, message:"ok", data: {}} - {code: 1, message:"padID does not exist", data: null} - -

-

checkToken()#

-
    -
  • API >= 1.2
  • -
-

returns ok when the current api token is valid - -

-

Example returns: - {"code":0,"message":"ok","data":null} - {"code":4,"message":"no or wrong API Key","data":null} - -

-

Pads#

-

listAllPads()#

-
    -
  • API >= 1.2.1
  • -
-

lists all pads on this epl instance - -

-

Example returns: - * {code: 0, message:"ok", data: ["testPad", "thePadsOfTheOthers"]} -

- -
- - - - diff --git a/out/doc/api/pluginfw.html b/out/doc/api/pluginfw.html deleted file mode 100644 index 7c0ab4ed4..000000000 --- a/out/doc/api/pluginfw.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Plugin Framework - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

Plugin Framework#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins") - -

-

plugins.update#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files, registering the contained hooks. -A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name) - -

-

hooks.callAll#

-

require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name with {argname:value}. - -

-

hooks.aCallAll#

-

? - -

-

...#

- -
- - - - diff --git a/out/doc/assets/style.css b/out/doc/assets/style.css deleted file mode 100644 index fe1343af4..000000000 --- a/out/doc/assets/style.css +++ /dev/null @@ -1,44 +0,0 @@ -body.apidoc { - width: 60%; - min-width: 10cm; - margin: 0 auto; -} - -#header { - background-color: #5a5; - padding: 10px; - color: #111; -} - -a, -a:active { - color: #272; -} -a:focus, -a:hover { - color: #050; -} - -#apicontent a.mark, -#apicontent a.mark:active { - float: right; - color: #BBB; - font-size: 0.7cm; - text-decoration: none; -} -#apicontent a.mark:focus, -#apicontent a.mark:hover { - color: #AAA; -} - -#apicontent code { - padding: 1px; - background-color: #EEE; - border-radius: 4px; - border: 1px solid #DDD; -} -#apicontent pre>code { - display: block; - overflow: auto; - padding: 5px; -} \ No newline at end of file diff --git a/out/doc/custom_static.html b/out/doc/custom_static.html deleted file mode 100644 index eb37d3bca..000000000 --- a/out/doc/custom_static.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Custom static files - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

Custom static files#

-

Etherpad Lite allows you to include your own static files in the browser, by modifying the files in static/custom. - -

-
    -
  • index.js Javascript that'll be run in /
  • -
  • index.css Stylesheet affecting /
  • -
  • pad.js Javascript that'll be run in /p/:padid
  • -
  • pad.css Stylesheet affecting /p/:padid
  • -
  • timeslider.js Javascript that'll be run in /p/:padid/timeslider
  • -
  • timeslider.css Stylesheet affecting /p/:padid/timeslider
  • -
  • favicon.ico Overrides the default favicon.
  • -
  • robots.txt Overrides the default robots.txt.
  • -
- -
- - - - diff --git a/out/doc/database.html b/out/doc/database.html deleted file mode 100644 index 940d5b398..000000000 --- a/out/doc/database.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - Database structure - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Database structure#

-

Keys and their values#

-

groups#

-

A list of all existing groups (a JSON object with groupIDs as keys and 1 as values). - -

-

pad:$PADID#

-

Saves all informations about pads - -

-
    -
  • atext - the latest attributed text
  • -
  • pool - the attribute pool
  • -
  • head - the number of the latest revision
  • -
  • chatHead - the number of the latest chat entry
  • -
  • public - flag that disables security for this pad
  • -
  • passwordHash - string that contains a bcrypt hashed password for this pad
  • -
-

pad:$PADID:revs:$REVNUM#

-

Saves a revision $REVNUM of pad $PADID - -

-
    -
  • meta
      -
    • author - the autorID of this revision
    • -
    • timestamp - the timestamp of when this revision was created
    • -
    -
  • -
  • changeset - the changeset of this revision
  • -
-

pad:$PADID:chat:$CHATNUM#

-

Saves a chatentry with num $CHATNUM of pad $PADID - -

-
    -
  • text - the text of this chat entry
  • -
  • userId - the autorID of this chat entry
  • -
  • time - the timestamp of this chat entry
  • -
-

pad2readonly:$PADID#

-

Translates a padID to a readonlyID -

-

readonly2pad:$READONLYID#

-

Translates a readonlyID to a padID -

-

token2author:$TOKENID#

-

Translates a token to an authorID -

-

globalAuthor:$AUTHORID#

-

Information about an author - -

-
    -
  • name - the name of this author as shown in the pad
  • -
  • colorID - the colorID of this author as shown in the pad
  • -
-

mapper2group:$MAPPER#

-

Maps an external application identifier to an internal group -

-

mapper2author:$MAPPER#

-

Maps an external application identifier to an internal author -

-

group:$GROUPID#

-

a group of pads - -

-
    -
  • pads - object with pad names in it, values are 1

    session:$SESSIONID#

    -a session between an author and a group
  • -
-
    -
  • groupID - the groupID the session belongs too
  • -
  • authorID - the authorID the session belongs too
  • -
  • validUntil - the timestamp until this session is valid
  • -
-

author2sessions:$AUTHORID#

-

saves the sessions of an author - -

-
    -
  • sessionsIDs - object with sessionIDs in it, values are 1
  • -
-

group2sessions:$GROUPID#

-
    -
  • sessionsIDs - object with sessionIDs in it, values are 1
  • -
- -
- - - - diff --git a/out/doc/documentation.html b/out/doc/documentation.html deleted file mode 100644 index bcbf1ff0d..000000000 --- a/out/doc/documentation.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - About this Documentation - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

About this Documentation#

- - -

The goal of this documentation is to comprehensively explain Etherpad-Lite, -both from a reference as well as a conceptual point of view. - -

-

Where appropriate, property types, method arguments, and the arguments -provided to event handlers are detailed in a list underneath the topic -heading. - -

-

Every .html file is generated based on the corresponding -.markdown file in the doc/api/ folder in the source tree. The -documentation is generated using the tools/doc/generate.js program. -The HTML template is located at doc/template.html.

- -
- - - - diff --git a/out/doc/easysync/README.html b/out/doc/easysync/README.html deleted file mode 100644 index ba387b43b..000000000 --- a/out/doc/easysync/README.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - About this folder - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

About this folder#

-

We put all documentations we found about the old Etherpad together in this folder. Most of this is still valid for Etherpad Lite

- -
- - - - diff --git a/out/doc/index.html b/out/doc/index.html deleted file mode 100644 index 84a723340..000000000 --- a/out/doc/index.html +++ /dev/null @@ -1,2202 +0,0 @@ - - - - - About this Documentation - Etherpad Lite v1.2.81 Manual & Documentation - - - - - -
-

Table of Contents

- - -
- -
-

About this Documentation#

- - -

The goal of this documentation is to comprehensively explain Etherpad-Lite, -both from a reference as well as a conceptual point of view. - -

-

Where appropriate, property types, method arguments, and the arguments -provided to event handlers are detailed in a list underneath the topic -heading. - -

-

Every .html file is generated based on the corresponding -.markdown file in the doc/api/ folder in the source tree. The -documentation is generated using the tools/doc/generate.js program. -The HTML template is located at doc/template.html. -

-

Localization#

-

Etherpad lite provides a multi-language user interface, that's apart from your users' content, so users from different countries can collaborate on a single document, while still having the user interface displayed in their mother tongue. - - -

-

Translating#

-

We rely on http://translatewiki.net to handle the translation process for us, so if you'd like to help... - -

-
    -
  1. sign up at http://translatewiki.net
  2. -
  3. Visit our TWN project page
  4. -
  5. Click on Translate Etherpad lite interface
  6. -
  7. Choose a target language, you'd like to translate our interface to, and hit Fetch
  8. -
  9. Start translating!
  10. -
-

Translations will be send back to us regularly and will eventually appear in the next release. - -

-

Implementation#

-

Server-side#

-

/src/locales contains files for all supported languages which contain the translated strings. Translation files are simple *.json files and look like this: - -

-
{ "pad.modals.connected": "Connect�."
-, "pad.modals.uderdup": "Ouvrir dans une nouvelle fen�tre."
-, "pad.toolbar.unindent.title": "D�sindenter"
-, "pad.toolbar.undo.title": "Annuler (Ctrl-Z)"
-, "timeslider.pageTitle": "{{appTitle}} Curseur temporel"
-, ...
-}
-

Each translation consists of a key (the id of the string that is to be translated) and the translated string. Terms in curly braces must not be touched but left as they are, since they represent a dynamically changing part of the string like a variable. Imagine a message welcoming a user: Welcome, {{userName}}! would be translated as Ahoy, {{userName}}! in pirate. - -

-

Client-side#

-

We use a language cookie to save your language settings if you change them. If you don't, we autodetect your locale using information from your browser. Now, that we know your preferred language this information is feeded into a very nice library called html10n.js, which loads the appropriate translations and applies them to our templates, providing translation params, pluralization, include rules and even a nice javascript API along the way. - - - -

-

Localizing plugins#

-

1. Mark the strings to translate#

-

In the template files of your plugin, change all hardcoded messages/strings... - -

-

from: -

-
<option value="0">Heading 1</option>
-

to: -

-
<option data-l10n-id="ep_heading.h1" value="0"></option>
-

In the javascript files of your plugin, chaneg all hardcoded messages/strings... - -

-

from: -

-
alert ('Chat');
-

to: -

-
alert(window._('pad.chat'));
-

2. Create translate files in the locales directory of your plugin#

-
    -
  • The name of the file must be the language code of the language it contains translations for (see supported lang codes; e.g. en ? English, es ? Spanish...)
  • -
  • The extension of the file must be .json
  • -
  • The default language is English, so your plugin should always provide en.json
  • -
  • In order to avoid naming conflicts, your message keys should start with the name of your plugin followed by a dot (see below)

    -
  • -
  • ep_your-plugin/locales/en.json*

    -
    <span class="type"> "ep_your-plugin.h1": "Heading 1"
    -</span>
    -
  • -
  • ep_your-plugin/locales/es.json*

    -
    <span class="type"> "ep_your-plugin.h1": "T�tulo 1"
    -</span>
    -
  • -
-

Everytime the http server is started, it will auto-detect your messages and merge them automatically with the core messages. - -

-

Overwrite core messages#

-

You can overwrite Etherpad Lite's core messages in your plugin's locale files. -For example, if you want to replace Chat with Notes, simply add... - -

-

ep_your-plugin/locales/en.json -

-
{ "ep_your-plugin.h1": "Heading 1"
-, "pad.chat": "Notes"
-}
-

Custom static files#

-

Etherpad Lite allows you to include your own static files in the browser, by modifying the files in static/custom. - -

-
    -
  • index.js Javascript that'll be run in /
  • -
  • index.css Stylesheet affecting /
  • -
  • pad.js Javascript that'll be run in /p/:padid
  • -
  • pad.css Stylesheet affecting /p/:padid
  • -
  • timeslider.js Javascript that'll be run in /p/:padid/timeslider
  • -
  • timeslider.css Stylesheet affecting /p/:padid/timeslider
  • -
  • favicon.ico Overrides the default favicon.
  • -
  • robots.txt Overrides the default robots.txt.

    Embed parameters#

    -You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters.
  • -
-

Example: - -

-

Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers. - -

-
<iframe src='http://pad.test.de/p/PAD_NAME?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
-

showLineNumbers#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showControls#

-
    -
  • Boolean
  • -
-

Default: true - -

-

showChat#

-
    -
  • Boolean
  • -
-

Default: true - -

-

useMonospaceFont#

-
    -
  • Boolean
  • -
-

Default: false - -

-

userName#

-
    -
  • String
  • -
-

Default: "unnamed" - -

-

Example: userName=Etherpad%20User - -

-

userColor#

-
    -
  • String (css hex color value)
  • -
-

Default: randomly chosen by pad server - -

-

Example: userColor=%23ff9900 - -

-

noColors#

-
    -
  • Boolean
  • -
-

Default: false - -

-

alwaysShowChat#

-
    -
  • Boolean
  • -
-

Default: false - -

-

lang#

-
    -
  • String
  • -
-

Default: en - -

-

Example: lang=ar (translates the interface into Arabic) - -

-

rtl#

-
    -
  • Boolean
  • -
-

Default: true -Displays pad text from right to left. - - -

-

HTTP API#

-

What can I do with this API?#

-

The API gives another web application control of the pads. The basic functions are - -

-
    -
  • create/delete pads
  • -
  • grant/forbid access to pads
  • -
  • get/set pad content
  • -
-

The API is designed in a way, so you can reuse your existing user system with their permissions, and map it to etherpad lite. Means: Your web application still has to do authentication, but you can tell etherpad lite via the api, which visitors should get which permissions. This allows etherpad lite to fit into any web application and extend it with real-time functionality. You can embed the pads via an iframe into your website. - -

-

Take a look at HTTP API client libraries to see if a library in your favorite language. - -

-

Examples#

-

Example 1#

-

A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. - -

-

Portal maps the internal userid to an etherpad author. - -

-
-

Request: http://pad.domain/api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 - -

-

Response: {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-
-

Portal maps the internal userid to an etherpad group: - -

-
-

Request: http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7 - -

-

Response: {code: 0, message:"ok", data: {groupID: "g.s8oes9dhwrvt0zif"}} - -

-
-

Portal creates a pad in the userGroup - -

-
-

Request: http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad - -

-

Response: {code: 0, message:"ok", data: null} - -

-
-

Portal starts the session for the user on the group: - -

-
-

Request: http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246 - -

-

Response: {"data":{"sessionID": "s.s8oes9dhwrvt0zif"}} - -

-
-

Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad. - -

-

Example 2#

-

A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post. - -

-

Portal retrieves the contents of the pad for entry into the db as a blog post: - -

-
-

Request: http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123 - -

-

Response: {code: 0, message:"ok", data: {text:"Welcome Text"}} - -

-
-

Portal submits content into new blog post - -

-
-

Portal.AddNewBlog(content) - - -

-
-

Usage#

-

API version#

-

The latest version is 1.2.7 - -

-

The current version can be queried via /api. - -

-

Request Format#

-

The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION depends on the endpoints you want to use. - -

-

Response Format#

-

Responses are valid JSON in the following format: - -

-
{
-  "code": number,
-  "message": string,
-  "data": obj
-}
-
    -
  • code a return code
      -
    • 0 everything ok
    • -
    • 1 wrong parameters
    • -
    • 2 internal error
    • -
    • 3 no such function
    • -
    • 4 no or wrong API Key
    • -
    -
  • -
  • message a status message. Its ok if everything is fine, else it contains an error message
  • -
  • data the payload
  • -
-

Overview#

-

API Overview - -

-

Data Types#

-
    -
  • groupID a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
  • -
  • sessionID a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
  • -
  • authorID a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif
  • -
  • readOnlyID a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
  • -
  • padID a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test
  • -
-

Authentication#

-

Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad-Lite deployment. This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. - -

-

Node Interoperability#

-

All functions will also be available through a node module accessable from other node.js applications. - -

-

JSONP#

-

The API provides JSONP support to allow requests from a server in a different domain. -Simply add &jsonp=? to the API call. - -

-

Example usage: http://api.jquery.com/jQuery.getJSON/ - -

-

API Methods#

-

Groups#

-

Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test - -

-

createGroup()#

-
    -
  • API >= 1
  • -
-

creates a new group - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

createGroupIfNotExistsFor(groupMapper)#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application group ids to etherpad lite group ids - -

-

Example returns: - * {code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}} - -

-

deleteGroup(groupID)#

-
    -
  • API >= 1
  • -
-

deletes a group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"groupID does not exist", data: null} - -

-

listPads(groupID)#

-
    -
  • API >= 1
  • -
-

returns all pads of this group - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]} - {code: 1, message:"groupID does not exist", data: null} - -

-

createGroupPad(groupID, padName [, text])#

-
    -
  • API >= 1
  • -
-

creates a new pad in this group - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - * {code: 1, message:"groupID does not exist", data: null} - -

-

listAllGroups()#

-
    -
  • API >= 1.1
  • -
-

lists all existing groups - -

-

Example returns: - {code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}} - {code: 0, message:"ok", data: {groupIDs: []}} - -

-

Author#

-

These authors are bound to the attributes the users choose (color and name). - -

-

createAuthor([name])#

-
    -
  • API >= 1
  • -
-

creates a new author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

createAuthorIfNotExistsFor(authorMapper [, name])#

-
    -
  • API >= 1
  • -
-

this functions helps you to map your application author ids to etherpad lite author ids - -

-

Example returns: - * {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}} - -

-

listPadsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns an array of all pads this author contributed to - -

-

Example returns: - {code: 0, message:"ok", data: {padIDs: ["g.s8oes9dhwrvt0zif$test", "g.s8oejklhwrvt0zif$foo"]}} - {code: 1, message:"authorID does not exist", data: null} - -

-

getAuthorName(authorID)#

-
    -
  • API >= 1.1
  • -
-

Returns the Author Name of the author - -

-

Example returns: - * {code: 0, message:"ok", data: {authorName: "John McLear"}} - -

-

-> can't be deleted cause this would involve scanning all the pads where this author was - -

-

Session#

-

Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-seperated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out. - -

-

createSession(groupID, authorID, validUntil)#

-
    -
  • API >= 1
  • -
-

creates a new session. validUntil is an unix timestamp in seconds - -

-

Example returns: - {code: 0, message:"ok", data: {sessionID: "s.s8oes9dhwrvt0zif"}} - {code: 1, message:"groupID doesn't exist", data: null} - {code: 1, message:"authorID doesn't exist", data: null} - {code: 1, message:"validUntil is in the past", data: null} - -

-

deleteSession(sessionID)#

-
    -
  • API >= 1
  • -
-

deletes a session - -

-

Example returns: - {code: 1, message:"ok", data: null} - {code: 1, message:"sessionID does not exist", data: null} - -

-

getSessionInfo(sessionID)#

-
    -
  • API >= 1
  • -
-

returns informations about a session - -

-

Example returns: - {code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}} - {code: 1, message:"sessionID does not exist", data: null} - -

-

listSessionsOfGroup(groupID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of a group - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"groupID does not exist", data: null} - -

-

listSessionsOfAuthor(authorID)#

-
    -
  • API >= 1
  • -
-

returns all sessions of an author - -

-

Example returns: - {"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}} - {code: 1, message:"authorID does not exist", data: null} - -

-

Pad Content#

-

Pad content can be updated and retrieved through the API - -

-

getText(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {text:"Welcome Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setText(padID, text)#

-
    -
  • API >= 1
  • -
-

sets the text of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - * {code: 1, message:"text too long", data: null} - -

-

getHTML(padID, [rev])#

-
    -
  • API >= 1
  • -
-

returns the text of a pad formatted as HTML - -

-

Example returns: - {code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}} - {code: 1, message:"padID does not exist", data: null} - -

-

Chat#

-

getChatHistory(padID, [start, end])#

-
    -
  • API >= 1.2.7
  • -
-

returns - -

-
    -
  • a part of the chat history, when start and end are given
  • -
  • the whole chat histroy, when no extra parameters are given
  • -
-

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: 1, message:"start is higher or equal to the current chatHead", data: null}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

getChatHead(padID)#

-
    -
  • API >= 1.2.7
  • -
-

returns the chatHead (last number of the last chat-message) of the pad - - -

-

Example returns: - -

-
    -
  • {code: 0, message:"ok", data: {chatHead: 42}}
  • -
  • {code: 1, message:"padID does not exist", data: null}
  • -
-

Pad#

-

Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. - -

-

createPad(padID [, text])#

-
    -
  • API >= 1
  • -
-

creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad. - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"pad does already exist", data: null} - -

-

getRevisionsCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of revisions of this pad - -

-

Example returns: - {code: 0, message:"ok", data: {revisions: 56}} - {code: 1, message:"padID does not exist", data: null} - -

-

padUsersCount(padID)#

-
    -
  • API >= 1
  • -
-

returns the number of user that are currently editing this pad - -

-

Example returns: - * {code: 0, message:"ok", data: {padUsersCount: 5}} - -

-

padUsers(padID)#

-
    -
  • API >= 1.1
  • -
-

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,"id":"a.n4gEeMLsvg12452n"},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042,"id":"a.n4gEeMLsvg12452n"}]}} - {code: 0, message:"ok", data: {padUsers: []}} - -

-

deletePad(padID)#

-
    -
  • API >= 1
  • -
-

deletes a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getReadOnlyID(padID)#

-
    -
  • API >= 1
  • -
-

returns the read only link of a pad - -

-

Example returns: - {code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPublicStatus(padID, publicStatus)#

-
    -
  • API >= 1
  • -
-

sets a boolean for the public status of a pad - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

getPublicStatus(padID)#

-
    -
  • API >= 1
  • -
-

return true of false - -

-

Example returns: - {code: 0, message:"ok", data: {publicStatus: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

setPassword(padID, password)#

-
    -
  • API >= 1
  • -
-

returns ok or a error message - -

-

Example returns: - {code: 0, message:"ok", data: null} - {code: 1, message:"padID does not exist", data: null} - -

-

isPasswordProtected(padID)#

-
    -
  • API >= 1
  • -
-

returns true or false - -

-

Example returns: - {code: 0, message:"ok", data: {passwordProtection: true}} - {code: 1, message:"padID does not exist", data: null} - -

-

listAuthorsOfPad(padID)#

-
    -
  • API >= 1
  • -
-

returns an array of authors who contributed to this pad - -

-

Example returns: - {code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]} - {code: 1, message:"padID does not exist", data: null} - -

-

getLastEdited(padID)#

-
    -
  • API >= 1
  • -
-

returns the timestamp of the last revision of the pad - -

-

Example returns: - {code: 0, message:"ok", data: {lastEdited: 1340815946602}} - {code: 1, message:"padID does not exist", data: null} - -

-

sendClientsMessage(padID, msg)#

-
    -
  • API >= 1.1
  • -
-

sends a custom message of type msg to the pad - -

-

Example returns: - {code: 0, message:"ok", data: {}} - {code: 1, message:"padID does not exist", data: null} - -

-

checkToken()#

-
    -
  • API >= 1.2
  • -
-

returns ok when the current api token is valid - -

-

Example returns: - {"code":0,"message":"ok","data":null} - {"code":4,"message":"no or wrong API Key","data":null} - -

-

Pads#

-

listAllPads()#

-
    -
  • API >= 1.2.1
  • -
-

lists all pads on this epl instance - -

-

Example returns: - * {code: 0, message:"ok", data: ["testPad", "thePadsOfTheOthers"]} - -

-

Hooks#

-

All hooks are called with two arguments: - -

-
    -
  1. name - the name of the hook being called
  2. -
  3. context - an object with some relevant information about the context of the call
  4. -
-

Return values#

-

A hook should always return a list or undefined. Returning undefined is equivalent to returning an empty list. -All the returned lists are appended to each other, so if the return values where [1, 2], undefined, [3, 4,], undefined and [5], the value returned by callHook would be [1, 2, 3, 4, 5]. - -

-

This is, because it should never matter if you have one plugin or several plugins doing some work - a single plugin should be able to make callHook return the same value a set of plugins are able to return collectively. So, any plugin can return a list of values, of any length, not just one value. -

-

Client-side hooks#

-

Most of these hooks are called during or in order to set up the formatting process. - -

-

documentReady#

-

Called from: src/templates/pad.html - -

-

Things in context: - -

-

nothing - -

-

This hook proxies the functionality of jQuery's $(document).ready event. - -

-

aceDomLineProcessLineAttributes#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - The current DOM line being processed
  2. -
  3. cls - The class of the current block element (useful for styling)
  4. -
-

This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. - -

-

The return value of this hook should have the following structure: - -

-

{ preHtml: String, postHtml: String, processedMarker: Boolean } - -

-

The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more. - -

-

aceCreateDomLine#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. domline - the current DOM line being processed
  2. -
  3. cls - The class of the current element (useful for styling)
  4. -
-

This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped. - -

-

The return value of this hook should have the following structure: - -

-

{ extraOpenTags: String, extraCloseTags: String, cls: String } - -

-

extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward. - -

-

acePostWriteDomLineHTML#

-

Called from: src/static/js/domline.js - -

-

Things in context: - -

-
    -
  1. node - the DOM node that just got written to the page
  2. -
-

This hook is for right after a node has been fully formatted and written to the page. - -

-

aceAttribsToClasses#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. key - the current attribute being processed
  4. -
  5. value - the value of the attribute being processed
  6. -
-

This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM. - -

-

The return value for this function should be a list of classes, which will then be parsed into a valid class string. - -

-

aceGetFilterStack#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. browser - an object indicating which browser is accessing the page
  4. -
-

This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale [[ ]] syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above). - -

-

aceEditorCSS#

-

Called from: src/static/js/ace.js - -

-

Things in context: None - -

-

This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory. - -

-

aceInitInnerdocbodyHead#

-

Called from: src/static/js/ace.js - -

-

Things in context: - -

-
    -
  1. iframeHTML - the HTML of the editor iframe up to this point, in array format
  2. -
-

This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the <head> element of the editor HTML document. - -

-

aceEditEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document (this is a mystery to me)
  8. -
-

This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event. - -

-

aceRegisterBlockElements#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: None - -

-

The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements. - -

-

aceInitialized#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings)
  2. -
  3. rep - information about where the user's cursor is
  4. -
  5. documentAttributeManager - some kind of magic
  6. -
-

This hook is for inserting further information into the ace engine, for later use in formatting hooks. - -

-

postAceInit#

-

Called from: src/static/js/pad.js - -

-

Things in context: - -

-
    -
  1. ace - the ace object that is applied to this editor.
  2. -
-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up. - -

-

postTimesliderInit#

-

Called from: src/static/js/timeslider.js - -

-

There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up. - -

-

userJoinOrUpdate#

-

Called from: src/static/js/pad_userlist.js - -

-

Things in context: - -

-
    -
  1. info - the user information
  2. -
-

This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. - -

-

collectContentPre#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

collectContentPost#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. style - the style applied to the node (probably CSS)
  8. -
  9. cls - the HTML class string of the node
  10. -
-

This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. - -

-

handleClientMessage_name#

-

Called from: src/static/js/collab_client.js - -

-

Things in context: - -

-
    -
  1. payload - the data that got sent with the message (use it for custom message content)
  2. -
-

This hook gets called every time the client receives a message of type name. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. - -

-

collab_client.js has a pretty extensive list of message types, if you want to take a look. - -

-

aceStartLineAndCharForPoint-aceEndLineAndCharForPoint#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. root - the span element of the current line
  8. -
  9. point - the starting/ending element where the cursor highlights
  10. -
  11. documentAttributeManager - information about attributes in the document
  12. -
-

This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection. -The return value should be an array of [line,char] - -

-

aceKeyEvent#

-

Called from: src/static/js/ace2_inner.js - -

-

Things in context: - -

-
    -
  1. callstack - a bunch of information about the current action
  2. -
  3. editorInfo - information about the user who is making the change
  4. -
  5. rep - information about where the change is being made
  6. -
  7. documentAttributeManager - information about attributes in the document
  8. -
  9. evt - the fired event
  10. -
-

This hook is provided to allow a plugin to handle key events. -The return value should be true if you have handled the event. - -

-

collectContentLineText#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
  7. text - the text for that line
  8. -
-

This hook allows you to validate/manipulate the text before it's sent to the server side. -The return value should be the validated/manipulated text. - -

-

collectContentLineBreak#

-

Called from: src/static/js/contentcollector.js - -

-

Things in context: - -

-
    -
  1. cc - the contentcollector object
  2. -
  3. state - the current state of the change being made
  4. -
  5. tname - the tag name of this node currently being processed
  6. -
-

This hook is provided to allow whether the br tag should induce a new magic domline or not. -The return value should be either true(break the line) or false. - -

-

disableAuthorColorsForThisLine#

-

Called from: src/static/js/linestylefilter.js - -

-

Things in context: - -

-
    -
  1. linestylefilter - the JavaScript object that's currently processing the ace attributes
  2. -
  3. text - the line text
  4. -
  5. class - line class
  6. -
-

This hook is provided to allow whether a given line should be deliniated with multiple authors. -Multiple authors in one line cause the creation of magic span lines. This might not suit you and -now you can disable it and handle your own deliniation. -The return value should be either true(disable) or false. - -

-

Server-side hooks#

-

These hooks are called on server-side. - -

-

loadSettings#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. settings - the settings object
  2. -
-

Use this hook to receive the global settings in your plugin. - -

-

pluginUninstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool! - -

-

pluginInstall#

-

Called from: src/static/js/pluginfw/installer.js - -

-

Things in context: - -

-
    -
  1. plugin_name - self-explanatory
  2. -
-

If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed. - -

-

init_<plugin name>#

-

Called from: src/static/js/pluginfw/plugins.js - -

-

Things in context: None - -

-

This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin. - -

-

expressConfigure#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main application object
  2. -
-

This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured. - -

-

expressCreateServer#

-

Called from: src/node/server.js - -

-

Things in context: - -

-
    -
  1. app - the main express application object (helpful for adding new paths and such)
  2. -
  3. server - the http server object
  4. -
-

This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. - -

-

eejsBlock_<name>#

-

Called from: src/node/eejs/index.js - -

-

Things in context: - -

-
    -
  1. content - the content of the block
  2. -
-

This hook gets called upon the rendering of an ejs template block. For any specific kind of block, you can change how that block gets rendered by modifying the content object passed in. - -

-

Have a look at src/templates/pad.html and src/templates/timeslider.html to see which blocks are available. - -

-

padCreate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when a new pad was created. - -

-

padLoad#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an pad was loaded. If a new pad was created and loaded this event will be emitted too. - -

-

padUpdate#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. pad - the pad instance
  2. -
-

This hook gets called when an existing pad was updated. - -

-

padRemove#

-

Called from: src/node/db/Pad.js - -

-

Things in context: - -

-
    -
  1. padID
  2. -
-

This hook gets called when an existing pad was removed/deleted. - -

-

socketio#

-

Called from: src/node/hooks/express/socketio.js - -

-

Things in context: - -

-
    -
  1. app - the application object
  2. -
  3. io - the socketio object
  4. -
  5. server - the http server object
  6. -
-

I have no idea what this is useful for, someone else will have to add this description. - -

-

authorize#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. resource - the path being accessed
  8. -
-

This is useful for modifying the way authentication is done, especially for specific paths. - -

-

authenticate#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
  7. username - the username used (optional)
  8. -
  9. password - the password used (optional)
  10. -
-

This is useful for modifying the way authentication is done. - -

-

authFailure#

-

Called from: src/node/hooks/express/webaccess.js - -

-

Things in context: - -

-
    -
  1. req - the request object
  2. -
  3. res - the response object
  4. -
  5. next - ?
  6. -
-

This is useful for modifying the way authentication is done. - -

-

handleMessage#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. message - the message being handled
  2. -
  3. client - the client object from socket.io
  4. -
-

This hook will be called once a message arrive. If a plugin calls callback(null) the message will be dropped. However it is not possible to modify the message. - -

-

Plugins may also decide to implement custom behavior once a message arrives. - -

-

WARNING: handleMessage will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions. - -

-

Example: - -

-
function handleMessage ( hook, context, callback ) {
-  if ( context.message.type == 'USERINFO_UPDATE' ) {
-    // If the message type is USERINFO_UPDATE, drop the message
-    callback(null);
-  }else{
-    callback();
-  }
-};
-

clientVars#

-

Called from: src/node/handler/PadMessageHandler.js - -

-

Things in context: - -

-
    -
  1. clientVars - the basic clientVars built by the core
  2. -
  3. pad - the pad this session is about
  4. -
-

This hook will be called once a client connects and the clientVars are being sent. Plugins can use this hook to give the client a initial configuriation, like the tracking-id of an external analytics-tool that is used on the client-side. You can also overwrite values from the original clientVars. - -

-

Example: - -

-
exports.clientVars = function(hook, context, callback)
-{
-  // tell the client which year we are in
-  return callback({ "currentYear": new Date().getFullYear() });
-};
-

This can be accessed on the client-side using clientVars.currentYear. - -

-

getLineHTMLForExport#

-

Called from: src/node/utils/ExportHtml.js - -

-

Things in context: - -

-
    -
  1. apool - pool object
  2. -
  3. attribLine - line attributes
  4. -
  5. text - line text
  6. -
-

This hook will allow a plug-in developer to re-write each line when exporting to HTML. - - -

-

editorInfo#

-

editorInfo.ace_replaceRange(start, end, text)#

-

This function replaces a range (from start to end) with text. - -

-

editorInfo.ace_getRep()#

-

Returns the rep object. - -

-

editorInfo.ace_getAuthor()#

-

editorInfo.ace_inCallStack()#

-

editorInfo.ace_inCallStackIfNecessary(?)#

-

editorInfo.ace_focus(?)#

-

editorInfo.ace_importText(?)#

-

editorInfo.ace_importAText(?)#

-

editorInfo.ace_exportText(?)#

-

editorInfo.ace_editorChangedSize(?)#

-

editorInfo.ace_setOnKeyPress(?)#

-

editorInfo.ace_setOnKeyDown(?)#

-

editorInfo.ace_setNotifyDirty(?)#

-

editorInfo.ace_dispose(?)#

-

editorInfo.ace_getFormattedCode(?)#

-

editorInfo.ace_setEditable(bool)#

-

editorInfo.ace_execCommand(?)#

-

editorInfo.ace_callWithAce(fn, callStack, normalize)#

-

editorInfo.ace_setProperty(key, value)#

-

editorInfo.ace_setBaseText(txt)#

-

editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj)#

-

editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj)#

-

editorInfo.ace_prepareUserChangeset()#

-

editorInfo.ace_applyPreparedChangesetToBase()#

-

editorInfo.ace_setUserChangeNotificationCallback(f)#

-

editorInfo.ace_setAuthorInfo(author, info)#

-

editorInfo.ace_setAuthorSelectionRange(author, start, end)#

-

editorInfo.ace_getUnhandledErrors()#

-

editorInfo.ace_getDebugProperty(prop)#

-

editorInfo.ace_fastIncorp(?)#

-

editorInfo.ace_isCaret(?)#

-

editorInfo.ace_getLineAndCharForPoint(?)#

-

editorInfo.ace_performDocumentApplyAttributesToCharRange(?)#

-

editorInfo.ace_setAttributeOnSelection(?)#

-

editorInfo.ace_toggleAttributeOnSelection(?)#

-

editorInfo.ace_performSelectionChange(?)#

-

editorInfo.ace_doIndentOutdent(?)#

-

editorInfo.ace_doUndoRedo(?)#

-

editorInfo.ace_doInsertUnorderedList(?)#

-

editorInfo.ace_doInsertOrderedList(?)#

-

editorInfo.ace_performDocumentApplyAttributesToRange()#

-

editorInfo.ace_getAuthorInfos()#

-

Returns an info object about the author. Object key = author_id and info includes author's bg color value. -Use to define your own authorship. -

-

editorInfo.ace_performDocumentReplaceRange(start, end, newText)#

-

This function replaces a range (from [x1,y1] to [x2,y2]) with newText. -

-

editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText)#

-

This function replaces a range (from y1 to y2) with newText. -

-

editorInfo.ace_renumberList(lineNum)#

-

If you delete a line, calling this method will fix the line numbering. -

-

editorInfo.ace_doReturnKey()#

-

Forces a return key at the current carret position. -

-

editorInfo.ace_isBlockElement(element)#

-

Returns true if your passed elment is registered as a block element -

-

editorInfo.ace_getLineListType(lineNum)#

-

Returns the line's html list type. -

-

editorInfo.ace_caretLine()#

-

Returns X position of the caret. -

-

editorInfo.ace_caretColumn()#

-

Returns Y position of the caret. -

-

editorInfo.ace_caretDocChar()#

-

Returns the Y offset starting from [x=0,y=0] -

-

editorInfo.ace_isWordChar(?)#

-

Changeset Library#

-
"Z:z>1|2=m=b*0|1+1$\n"
-

This is a Changeset. Its just a string and its very difficult to read in this form. But the Changeset Library gives us some tools to read it. - -

-

A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. - -

-

Changeset.unpack(changeset)#

-
    -
  • changeset String
  • -
-

This functions returns an object representaion of the changeset, similar to this: - -

-
{ oldLen: 35, newLen: 36, ops: '|2=m=b*0|1+1', charBank: '\n' }
-
    -
  • oldLen {Number} the original length of the document.
  • -
  • newLen {Number} the length of the document after the changeset is applied.
  • -
  • ops {String} the actual changes, introduced by this changeset.
  • -
  • charBank {String} All characters that are added by this changeset.
  • -
-

Changeset.opIterator(ops)#

-
    -
  • ops String The operators, returned by Changeset.unpack()
  • -
-

Returns an operator iterator. This iterator allows us to iterate over all operators that are in the changeset. - -

-

You can iterate with an opIterator using its next() and hasNext() methods. Next returns the next() operator object and hasNext() indicates, whether there are any operators left. - -

-

The Operator object#

-

There are 3 types of operators: +,- and =. These operators describe different changes to the document, beginning with the first character of the document. A = operator doesn't change the text, but it may add or remove text attributes. A - operator removes text. And a + Operator adds text and optionally adds some attributes to it. - -

-
    -
  • opcode {String} the operator type
  • -
  • chars {Number} the length of the text changed by this operator.
  • -
  • lines {Number} the number of lines changed by this operator.
  • -
  • attribs {attribs} attributes set on this text.
  • -
-

Example#

-
{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-

APool#

-
> var AttributePoolFactory = require("./utils/AttributePoolFactory");
-> var apool = AttributePoolFactory.createAttributePool();
-> console.log(apool)
-{ numToAttrib: {},
-  attribToNum: {},
-  nextNum: 0,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

This creates an empty apool. A apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Lets fill this apool with some values - -

-
> apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
-> console.log(apool)
-{ numToAttrib: 
-   { '0': [ 'author', 'a.kVnWeomPADAT2pn9' ],
-     '1': [ 'bold', 'true' ],
-     '2': [ 'italic', 'true' ] },
-  attribToNum: 
-   { 'author,a.kVnWeomPADAT2pn9': 0,
-     'bold,true': 1,
-     'italic,true': 2 },
-  nextNum: 3,
-  putAttrib: [Function],
-  getAttrib: [Function],
-  getAttribKey: [Function],
-  getAttribValue: [Function],
-  eachAttrib: [Function],
-  toJsonable: [Function],
-  fromJsonable: [Function] }
-

We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. A attribute is always a key value pair. For stuff like bold and italic its just 'italic':'true'. For authors its author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors - -

-
> apool.getAttrib(1)
-[ 'bold', 'true' ]
-

Simple example of how to get the key value pair for the attribute 1 - -

-

AText#

-
> var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
-> console.log(atext)
-{ text: 'bold text\nitalic text\nnormal text\n\n',
-  attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
-

This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps - -

-
> var opiterator = Changeset.opIterator(atext.attribs)
-> console.log(opiterator)
-{ next: [Function: next],
-  hasNext: [Function: hasNext],
-  lastIndex: [Function: lastIndex] }
-> opiterator.next()
-{ opcode: '+',
-  chars: 9,
-  lines: 0,
-  attribs: '*0*1' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0*1*2' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 1,
-  lines: 1,
-  attribs: '' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 11,
-  lines: 0,
-  attribs: '*0' }
-> opiterator.next()
-{ opcode: '+',
-  chars: 2,
-  lines: 2,
-  attribs: '' }
-

The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes - -

-

For more information see /doc/easysync/easysync-notes.txt in the source. - -

-

Plugin Framework#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins") - -

-

plugins.update#

-

require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files, registering the contained hooks. -A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name) - -

-

hooks.callAll#

-

require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name with {argname:value}. - -

-

hooks.aCallAll#

-

? - -

-

...#

-

Plugins#

-

Etherpad-Lite allows you to extend its functionality with plugins. A plugin registers hooks (functions) for certain events (thus certain features) in Etherpad-lite to execute its own functionality based on these events. - -

-

Publicly available plugins can be found in the npm registry (see http://npmjs.org). Etherpad-lite's naming convention for plugins is to prefix your plugins with ep_. So, e.g. it's ep_flubberworms. Thus you can install plugins from npm, using npm install ep_flubberworm in etherpad-lite's root directory. - -

-

You can also browse to http://yourEtherpadInstan.ce/admin/plugins, which will list all installed plugins and those available on npm. It even provides functionality to search through all available plugins. - -

-

Folder structure#

-

A basic plugin usually has the following folder structure: -

-
ep_<plugin>/
- | static/
- | templates/
- | locales/
- + ep.json
- + package.json
-

If your plugin includes client-side hooks, put them in static/js/. If you're adding in CSS or image files, you should put those files in static/css/ and static/image/, respectively, and templates go into templates/. Translations go into locales/ - -

-

A Standard directory structure like this makes it easier to navigate through your code. That said, do note, that this is not actually required to make your plugin run. If you want to make use of our i18n system, you need to put your translations into locales/, though, in order to have them intergated. (See "Localization" for more info on how to localize your plugin) - -

-

Plugin definition#

-

Your plugin definition goes into ep.json. In this file you register your hooks, indicate the parts of your plugin and the order of execution. (A documentation of all available events to hook into can be found in chapter hooks.) - -

-

A hook registration is a pairs of a hook name and a function reference (filename to require() + exported function name) - -

-
{
-  "parts": [
-    {
-      "name": "nameThisPartHoweverYouWant",
-      "hooks": {
-        "authenticate" : "ep_<plugin>/<file>:FUNCTIONNAME1",
-        "expressCreateServer": "ep_<plugin>/<file>:FUNCTIONNAME2"
-      },
-      "client_hooks": {
-        "acePopulateDOMLine": "ep_plugin/<file>:FUNCTIONNAME3"
-      }
-    }
-  ]
-}
-

Etherpad-lite will expect the part of the hook definition before the colon to be a javascript file and will try to require it. The part after the colon is expected to be a valid function identifier of that module. So, you have to export your hooks, using module.exports and register it in ep.json as ep_<plugin>/path/to/<file>:FUNCTIONNAME. -You can omit the FUNCTIONNAME part, if the exported function has got the same name as the hook. So "authorize" : "ep_flubberworm/foo" will call the function exports.authorize in ep_flubberworm/foo.js - -

-

Client hooks and server hooks#

-

There are server hooks, which will be executed on the server (e.g. expressCreateServer), and there are client hooks, which are executed on the client (e.g. acePopulateDomLine). Be sure to not make assumptions about the environment your code is running in, e.g. don't try to access process, if you know your code will be run on the client, and likewise, don't try to access window on the server... - -

-

Parts#

-

As your plugins become more and more complex, you will find yourself in the need to manage dependencies between plugins. E.g. you want the hooks of a certain plugin to be executed before (or after) yours. You can also manage these dependencies in your plugin definition file ep.json: - -

-
{
-  "parts": [
-    {
-      "name": "onepart",
-      "pre": [],
-      "post": ["ep_onemoreplugin/partone"]
-      "hooks": {
-        "storeBar": "ep_monospace/plugin:storeBar",
-        "getFoo": "ep_monospace/plugin:getFoo",
-      }
-    },
-    {
-      "name": "otherpart",
-      "pre": ["ep_my_example/somepart", "ep_otherplugin/main"],
-      "post": [],
-      "hooks": {
-        "someEvent": "ep_my_example/otherpart:someEvent",
-        "another": "ep_my_example/otherpart:another"
-      }
-    }
-  ]
-}
-

Usually a plugin will add only one functionality at a time, so it will probably only use one part definition to register its hooks. However, sometimes you have to put different (unrelated) functionalities into one plugin. For this you will want use parts, so other plugins can depend on them. - -

-

pre/post#

-

The "pre" and "post" definitions, affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order. - -

-

"pre" lists parts that must be executed before the defining part. "post" lists parts that must be executed after the defining part. - -

-

You can, on a basic level, think of this as double-ended dependency listing. If you have a dependency on another plugin, you can make sure it loads before yours by putting it in "pre". If you are setting up things that might need to be used by a plugin later, you can ensure proper order by putting it in "post". - -

-

Note that it would be far more sane to use "pre" in almost any case, but if you want to change config variables for another plugin, or maybe modify its environment, "post" could definitely be useful. - -

-

Also, note that dependencies should also be listed in your package.json, so they can be npm install'd automagically when your plugin gets installed. - -

-

Package definition#

-

Your plugin must also contain a package definition file, called package.json, in the project root - this file contains various metadata relevant to your plugin, such as the name and version number, author, project hompage, contributors, a short description, etc. If you publish your plugin on npm, these metadata are used for package search etc., but it's necessary for Etherpad-lite plugins, even if you don't publish your plugin. - -

-
{
-  "name": "ep_PLUGINNAME",
-  "version": "0.0.1",
-  "description": "DESCRIPTION",
-  "author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
-  "contributors": [],
-  "dependencies": {"MODULE": "0.3.20"},
-  "engines": { "node": ">= 0.6.0"}
-}
-

Templates#

-

If your plugin adds or modifies the front end HTML (e.g. adding buttons or changing their functions), you should put the necessary HTML code for such operations in templates/, in files of type ".ejs", since Etherpad-Lite uses EJS for HTML templating. See the following link for more information about EJS: https://github.com/visionmedia/ejs. - -

-

Writing and running front-end tests for your plugin#

-

Etherpad allows you to easily create front-end tests for plugins. - -

-
    -
  1. Create a new folder
    %your_plugin%/static/tests/frontend/specs
    -
  2. -
  3. Put your spec file in here (Example spec files are visible in %etherpad_root_folder%/frontend/tests/specs)

    -
  4. -
  5. Visit http://yourserver.com/frontend/tests your front-end tests will run.

    -
  6. -
-

Database structure#

-

Keys and their values#

-

groups#

-

A list of all existing groups (a JSON object with groupIDs as keys and 1 as values). - -

-

pad:$PADID#

-

Saves all informations about pads - -

-
    -
  • atext - the latest attributed text
  • -
  • pool - the attribute pool
  • -
  • head - the number of the latest revision
  • -
  • chatHead - the number of the latest chat entry
  • -
  • public - flag that disables security for this pad
  • -
  • passwordHash - string that contains a bcrypt hashed password for this pad
  • -
-

pad:$PADID:revs:$REVNUM#

-

Saves a revision $REVNUM of pad $PADID - -

-
    -
  • meta
      -
    • author - the autorID of this revision
    • -
    • timestamp - the timestamp of when this revision was created
    • -
    -
  • -
  • changeset - the changeset of this revision
  • -
-

pad:$PADID:chat:$CHATNUM#

-

Saves a chatentry with num $CHATNUM of pad $PADID - -

-
    -
  • text - the text of this chat entry
  • -
  • userId - the autorID of this chat entry
  • -
  • time - the timestamp of this chat entry
  • -
-

pad2readonly:$PADID#

-

Translates a padID to a readonlyID -

-

readonly2pad:$READONLYID#

-

Translates a readonlyID to a padID -

-

token2author:$TOKENID#

-

Translates a token to an authorID -

-

globalAuthor:$AUTHORID#

-

Information about an author - -

-
    -
  • name - the name of this author as shown in the pad
  • -
  • colorID - the colorID of this author as shown in the pad
  • -
-

mapper2group:$MAPPER#

-

Maps an external application identifier to an internal group -

-

mapper2author:$MAPPER#

-

Maps an external application identifier to an internal author -

-

group:$GROUPID#

-

a group of pads - -

-
    -
  • pads - object with pad names in it, values are 1

    session:$SESSIONID#

    -a session between an author and a group
  • -
-
    -
  • groupID - the groupID the session belongs too
  • -
  • authorID - the authorID the session belongs too
  • -
  • validUntil - the timestamp until this session is valid
  • -
-

author2sessions:$AUTHORID#

-

saves the sessions of an author - -

-
    -
  • sessionsIDs - object with sessionIDs in it, values are 1
  • -
-

group2sessions:$GROUPID#

-
    -
  • sessionsIDs - object with sessionIDs in it, values are 1
  • -
- -
- - - - diff --git a/out/doc/localization.html b/out/doc/localization.html deleted file mode 100644 index 58574984e..000000000 --- a/out/doc/localization.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Localization - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Localization#

-

Etherpad lite provides a multi-language user interface, that's apart from your users' content, so users from different countries can collaborate on a single document, while still having the user interface displayed in their mother tongue. - - -

-

Translating#

-

We rely on http://translatewiki.net to handle the translation process for us, so if you'd like to help... - -

-
    -
  1. sign up at http://translatewiki.net
  2. -
  3. Visit our TWN project page
  4. -
  5. Click on Translate Etherpad lite interface
  6. -
  7. Choose a target language, you'd like to translate our interface to, and hit Fetch
  8. -
  9. Start translating!
  10. -
-

Translations will be send back to us regularly and will eventually appear in the next release. - -

-

Implementation#

-

Server-side#

-

/src/locales contains files for all supported languages which contain the translated strings. Translation files are simple *.json files and look like this: - -

-
{ "pad.modals.connected": "Connect�."
-, "pad.modals.uderdup": "Ouvrir dans une nouvelle fen�tre."
-, "pad.toolbar.unindent.title": "D�sindenter"
-, "pad.toolbar.undo.title": "Annuler (Ctrl-Z)"
-, "timeslider.pageTitle": "{{appTitle}} Curseur temporel"
-, ...
-}
-

Each translation consists of a key (the id of the string that is to be translated) and the translated string. Terms in curly braces must not be touched but left as they are, since they represent a dynamically changing part of the string like a variable. Imagine a message welcoming a user: Welcome, {{userName}}! would be translated as Ahoy, {{userName}}! in pirate. - -

-

Client-side#

-

We use a language cookie to save your language settings if you change them. If you don't, we autodetect your locale using information from your browser. Now, that we know your preferred language this information is feeded into a very nice library called html10n.js, which loads the appropriate translations and applies them to our templates, providing translation params, pluralization, include rules and even a nice javascript API along the way. - - - -

-

Localizing plugins#

-

1. Mark the strings to translate#

-

In the template files of your plugin, change all hardcoded messages/strings... - -

-

from: -

-
<option value="0">Heading 1</option>
-

to: -

-
<option data-l10n-id="ep_heading.h1" value="0"></option>
-

In the javascript files of your plugin, chaneg all hardcoded messages/strings... - -

-

from: -

-
alert ('Chat');
-

to: -

-
alert(window._('pad.chat'));
-

2. Create translate files in the locales directory of your plugin#

-
    -
  • The name of the file must be the language code of the language it contains translations for (see supported lang codes; e.g. en ? English, es ? Spanish...)
  • -
  • The extension of the file must be .json
  • -
  • The default language is English, so your plugin should always provide en.json
  • -
  • In order to avoid naming conflicts, your message keys should start with the name of your plugin followed by a dot (see below)

    -
  • -
  • ep_your-plugin/locales/en.json*

    -
    <span class="type"> "ep_your-plugin.h1": "Heading 1"
    -</span>
    -
  • -
  • ep_your-plugin/locales/es.json*

    -
    <span class="type"> "ep_your-plugin.h1": "T�tulo 1"
    -</span>
    -
  • -
-

Everytime the http server is started, it will auto-detect your messages and merge them automatically with the core messages. - -

-

Overwrite core messages#

-

You can overwrite Etherpad Lite's core messages in your plugin's locale files. -For example, if you want to replace Chat with Notes, simply add... - -

-

ep_your-plugin/locales/en.json -

-
{ "ep_your-plugin.h1": "Heading 1"
-, "pad.chat": "Notes"
-}
- -
- - - - diff --git a/out/doc/plugins.html b/out/doc/plugins.html deleted file mode 100644 index e51e2070b..000000000 --- a/out/doc/plugins.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - Plugins - Etherpad Lite v1.2.81 Manual & Documentation - - - - - - - -
-

Plugins#

-

Etherpad-Lite allows you to extend its functionality with plugins. A plugin registers hooks (functions) for certain events (thus certain features) in Etherpad-lite to execute its own functionality based on these events. - -

-

Publicly available plugins can be found in the npm registry (see http://npmjs.org). Etherpad-lite's naming convention for plugins is to prefix your plugins with ep_. So, e.g. it's ep_flubberworms. Thus you can install plugins from npm, using npm install ep_flubberworm in etherpad-lite's root directory. - -

-

You can also browse to http://yourEtherpadInstan.ce/admin/plugins, which will list all installed plugins and those available on npm. It even provides functionality to search through all available plugins. - -

-

Folder structure#

-

A basic plugin usually has the following folder structure: -

-
ep_<plugin>/
- | static/
- | templates/
- | locales/
- + ep.json
- + package.json
-

If your plugin includes client-side hooks, put them in static/js/. If you're adding in CSS or image files, you should put those files in static/css/ and static/image/, respectively, and templates go into templates/. Translations go into locales/ - -

-

A Standard directory structure like this makes it easier to navigate through your code. That said, do note, that this is not actually required to make your plugin run. If you want to make use of our i18n system, you need to put your translations into locales/, though, in order to have them intergated. (See "Localization" for more info on how to localize your plugin) - -

-

Plugin definition#

-

Your plugin definition goes into ep.json. In this file you register your hooks, indicate the parts of your plugin and the order of execution. (A documentation of all available events to hook into can be found in chapter hooks.) - -

-

A hook registration is a pairs of a hook name and a function reference (filename to require() + exported function name) - -

-
{
-  "parts": [
-    {
-      "name": "nameThisPartHoweverYouWant",
-      "hooks": {
-        "authenticate" : "ep_<plugin>/<file>:FUNCTIONNAME1",
-        "expressCreateServer": "ep_<plugin>/<file>:FUNCTIONNAME2"
-      },
-      "client_hooks": {
-        "acePopulateDOMLine": "ep_plugin/<file>:FUNCTIONNAME3"
-      }
-    }
-  ]
-}
-

Etherpad-lite will expect the part of the hook definition before the colon to be a javascript file and will try to require it. The part after the colon is expected to be a valid function identifier of that module. So, you have to export your hooks, using module.exports and register it in ep.json as ep_<plugin>/path/to/<file>:FUNCTIONNAME. -You can omit the FUNCTIONNAME part, if the exported function has got the same name as the hook. So "authorize" : "ep_flubberworm/foo" will call the function exports.authorize in ep_flubberworm/foo.js - -

-

Client hooks and server hooks#

-

There are server hooks, which will be executed on the server (e.g. expressCreateServer), and there are client hooks, which are executed on the client (e.g. acePopulateDomLine). Be sure to not make assumptions about the environment your code is running in, e.g. don't try to access process, if you know your code will be run on the client, and likewise, don't try to access window on the server... - -

-

Parts#

-

As your plugins become more and more complex, you will find yourself in the need to manage dependencies between plugins. E.g. you want the hooks of a certain plugin to be executed before (or after) yours. You can also manage these dependencies in your plugin definition file ep.json: - -

-
{
-  "parts": [
-    {
-      "name": "onepart",
-      "pre": [],
-      "post": ["ep_onemoreplugin/partone"]
-      "hooks": {
-        "storeBar": "ep_monospace/plugin:storeBar",
-        "getFoo": "ep_monospace/plugin:getFoo",
-      }
-    },
-    {
-      "name": "otherpart",
-      "pre": ["ep_my_example/somepart", "ep_otherplugin/main"],
-      "post": [],
-      "hooks": {
-        "someEvent": "ep_my_example/otherpart:someEvent",
-        "another": "ep_my_example/otherpart:another"
-      }
-    }
-  ]
-}
-

Usually a plugin will add only one functionality at a time, so it will probably only use one part definition to register its hooks. However, sometimes you have to put different (unrelated) functionalities into one plugin. For this you will want use parts, so other plugins can depend on them. - -

-

pre/post#

-

The "pre" and "post" definitions, affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order. - -

-

"pre" lists parts that must be executed before the defining part. "post" lists parts that must be executed after the defining part. - -

-

You can, on a basic level, think of this as double-ended dependency listing. If you have a dependency on another plugin, you can make sure it loads before yours by putting it in "pre". If you are setting up things that might need to be used by a plugin later, you can ensure proper order by putting it in "post". - -

-

Note that it would be far more sane to use "pre" in almost any case, but if you want to change config variables for another plugin, or maybe modify its environment, "post" could definitely be useful. - -

-

Also, note that dependencies should also be listed in your package.json, so they can be npm install'd automagically when your plugin gets installed. - -

-

Package definition#

-

Your plugin must also contain a package definition file, called package.json, in the project root - this file contains various metadata relevant to your plugin, such as the name and version number, author, project hompage, contributors, a short description, etc. If you publish your plugin on npm, these metadata are used for package search etc., but it's necessary for Etherpad-lite plugins, even if you don't publish your plugin. - -

-
{
-  "name": "ep_PLUGINNAME",
-  "version": "0.0.1",
-  "description": "DESCRIPTION",
-  "author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
-  "contributors": [],
-  "dependencies": {"MODULE": "0.3.20"},
-  "engines": { "node": ">= 0.6.0"}
-}
-

Templates#

-

If your plugin adds or modifies the front end HTML (e.g. adding buttons or changing their functions), you should put the necessary HTML code for such operations in templates/, in files of type ".ejs", since Etherpad-Lite uses EJS for HTML templating. See the following link for more information about EJS: https://github.com/visionmedia/ejs. - -

-

Writing and running front-end tests for your plugin#

-

Etherpad allows you to easily create front-end tests for plugins. - -

-
    -
  1. Create a new folder
    %your_plugin%/static/tests/frontend/specs
    -
  2. -
  3. Put your spec file in here (Example spec files are visible in %etherpad_root_folder%/frontend/tests/specs)

    -
  4. -
  5. Visit http://yourserver.com/frontend/tests your front-end tests will run.

    -
  6. -
- -
- - - - From 23abafb3cb61d8948a2392aa4beccff9e02e875d Mon Sep 17 00:00:00 2001 From: Sahil Amoli Date: Wed, 20 Mar 2013 15:39:10 -0700 Subject: [PATCH 036/104] Issue #1648 - Long lines without any spaces don't wrap on Firefox, the text ends up going off screen --- src/static/css/iframe_editor.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index 3e19cbbea..1d9b61bea 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -78,6 +78,7 @@ ul.list-indent8 { list-style-type: none; } body { margin: 0; white-space: nowrap; + word-wrap: normal; } #outerdocbody { @@ -93,6 +94,7 @@ body.grayedout { background-color: #eee !important } body.doesWrap { white-space: normal; + word-wrap: break-word; /* fix for issue #1648 - firefox not wrapping long lines (without spaces) correctly */ } #innerdocbody { From cbde18945cf3ce8b6f99d284b73b8a11285a0018 Mon Sep 17 00:00:00 2001 From: Simon Gaeremynck Date: Fri, 22 Mar 2013 15:15:45 +0000 Subject: [PATCH 037/104] Bumped the ueberDB package version to 0.1.96 to add in Cassandra support. --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 8278734f2..822676a38 100644 --- a/src/package.json +++ b/src/package.json @@ -16,7 +16,7 @@ "require-kernel" : "1.0.5", "resolve" : "0.2.x", "socket.io" : "0.9.x", - "ueberDB" : "0.1.95", + "ueberDB" : "0.1.96", "async" : "0.1.x", "express" : "3.x", "connect" : "2.4.x", From e050ad57e48916ca72a9378a5c4c641b45ec7f06 Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 22 Mar 2013 17:39:22 +0000 Subject: [PATCH 038/104] fix typo --- bin/installDeps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/installDeps.sh b/bin/installDeps.sh index 2f97090b7..fc3133c16 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -44,7 +44,7 @@ fi #check node version NODE_VERSION=$(node --version) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) -if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" && [ ! $NODE_V_MINOR = "v0.10" ]; then +if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ] && [ ! $NODE_V_MINOR = "v0.10" ]; then echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x, v0.8.x or v0.10.x" >&2 exit 1 fi From 0063933041ba7f5ac2c284e05e2520d53fd1835a Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 23 Mar 2013 02:59:12 +0000 Subject: [PATCH 039/104] fix cookies --- src/static/js/pad.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index e75d09b2d..e5e6e95f3 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -441,6 +441,7 @@ var pad = { //initialize the chat chat.init(this); + padcookie.init(); // initialize the cookies pad.initTime = +(new Date()); pad.padOptions = clientVars.initialOptions; From 35e489121797aebc265db6d9a56ad0545cfdf609 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Sat, 23 Mar 2013 13:26:38 +0000 Subject: [PATCH 040/104] Localisation updates from http://translatewiki.net. --- src/locales/be-tarask.json | 14 +++++++------- src/locales/el.json | 3 ++- src/locales/fa.json | 4 +++- src/locales/fi.json | 1 + src/locales/ml.json | 6 +++++- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/locales/be-tarask.json b/src/locales/be-tarask.json index dda412895..0b44115ca 100644 --- a/src/locales/be-tarask.json +++ b/src/locales/be-tarask.json @@ -1,4 +1,10 @@ { + "@metadata": { + "authors": [ + "Jim-by", + "Wizardist" + ] + }, "index.newPad": "\u0421\u0442\u0432\u0430\u0440\u044b\u0446\u044c", "index.createOpenPad": "\u0446\u0456 \u0442\u0432\u0430\u0440\u044b\u0446\u044c\/\u0430\u0434\u043a\u0440\u044b\u0446\u044c \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442 \u0437 \u043d\u0430\u0437\u0432\u0430\u0439:", "pad.toolbar.bold.title": "\u0422\u043e\u045e\u0441\u0442\u044b (Ctrl-B)", @@ -51,11 +57,5 @@ "pad.share": "\u041f\u0430\u0434\u0437\u044f\u043b\u0456\u0446\u0446\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430\u043c", "pad.share.readonly": "\u0422\u043e\u043b\u044c\u043a\u0456 \u0434\u043b\u044f \u0447\u044b\u0442\u0430\u043d\u044c\u043d\u044f", "pad.share.link": "\u0421\u043f\u0430\u0441\u044b\u043b\u043a\u0430", - "pad.chat": "\u0427\u0430\u0442", - "@metadata": { - "authors": [ - "Jim-by", - "Wizardist" - ] - } + "pad.chat": "\u0427\u0430\u0442" } \ No newline at end of file diff --git a/src/locales/el.json b/src/locales/el.json index f33865e6b..52b4b7d69 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -22,7 +22,7 @@ "pad.toolbar.clearAuthorship.title": "\u039a\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03a7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd \u03a3\u03c5\u03bd\u03c4\u03b1\u03ba\u03c4\u03ce\u03bd", "pad.toolbar.import_export.title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03b1\u03c0\u03cc\/\u03c3\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03bf\u03cd\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c5\u03c2 \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd", "pad.toolbar.timeslider.title": "\u03a7\u03c1\u03bf\u03bd\u03bf\u03b4\u03b9\u03ac\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1", - "pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b5\u03c2 \u0391\u03bd\u03b1\u03b8\u03b5\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2", + "pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u0391\u03bd\u03b1\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2", "pad.toolbar.settings.title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", "pad.toolbar.embed.title": "\u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 pad", "pad.toolbar.showusers.title": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 pad", @@ -37,6 +37,7 @@ "pad.settings.stickychat": "\u0397 \u03a3\u03c5\u03bd\u03bf\u03bc\u03b9\u03bb\u03af\u03b1 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03ac\u03bd\u03c4\u03b1 \u03bf\u03c1\u03b1\u03c4\u03ae", "pad.settings.colorcheck": "\u03a7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ac\u03ba\u03c4\u03b7", "pad.settings.linenocheck": "\u0391\u03c1\u03b9\u03b8\u03bc\u03bf\u03af \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2", + "pad.settings.rtlcheck": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03b1\u03c0\u03cc \u03b4\u03b5\u03be\u03b9\u03ac \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b1 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac;", "pad.settings.fontType": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac\u03c2:", "pad.settings.fontType.normal": "\u039a\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ae", "pad.settings.fontType.monospaced": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c5 \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2", diff --git a/src/locales/fa.json b/src/locales/fa.json index 8e0fd1388..6495ace59 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -3,7 +3,8 @@ "authors": { "0": "BMRG14", "1": "Dalba", - "3": "ZxxZxxZ" + "3": "ZxxZxxZ", + "4": "\u0627\u0644\u0646\u0627\u0632" } }, "index.newPad": "\u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a \u062a\u0627\u0632\u0647", @@ -36,6 +37,7 @@ "pad.settings.stickychat": "\u06af\u0641\u062a\u06af\u0648 \u0647\u0645\u06cc\u0634\u0647 \u0631\u0648\u06cc \u0635\u0641\u062d\u0647 \u0646\u0645\u0627\u06cc\u0634 \u0628\u0627\u0634\u062f", "pad.settings.colorcheck": "\u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u06cc", "pad.settings.linenocheck": "\u0634\u0645\u0627\u0631\u0647\u200c\u06cc \u062e\u0637\u0648\u0637", + "pad.settings.rtlcheck": "\u062e\u0648\u0627\u0646\u062f\u0646 \u0645\u062d\u062a\u0648\u0627 \u0627\u0632 \u0631\u0627\u0633\u062a \u0628\u0647 \u0686\u067e\u061f", "pad.settings.fontType": "\u0646\u0648\u0639 \u0642\u0644\u0645:", "pad.settings.fontType.normal": "\u0633\u0627\u062f\u0647", "pad.settings.fontType.monospaced": "Monospace", diff --git a/src/locales/fi.json b/src/locales/fi.json index eeb4cb160..38190f143 100644 --- a/src/locales/fi.json +++ b/src/locales/fi.json @@ -39,6 +39,7 @@ "pad.settings.stickychat": "Keskustelu aina n\u00e4kyviss\u00e4", "pad.settings.colorcheck": "Kirjoittajav\u00e4rit", "pad.settings.linenocheck": "Rivinumerot", + "pad.settings.rtlcheck": "Luetaanko sis\u00e4lt\u00f6 oikealta vasemmalle?", "pad.settings.fontType": "Kirjasintyyppi:", "pad.settings.fontType.normal": "normaali", "pad.settings.fontType.monospaced": "tasalevyinen", diff --git a/src/locales/ml.json b/src/locales/ml.json index e82504348..2ffbee2f6 100644 --- a/src/locales/ml.json +++ b/src/locales/ml.json @@ -21,7 +21,7 @@ "pad.toolbar.clearAuthorship.title": "\u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7e\u0d15\u0d4d\u0d15\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d31\u0d02 \u0d15\u0d33\u0d2f\u0d41\u0d15", "pad.toolbar.import_export.title": "\u0d35\u0d4d\u0d2f\u0d24\u0d4d\u0d2f\u0d38\u0d4d\u0d24 \u0d2b\u0d2f\u0d7d \u0d24\u0d30\u0d19\u0d4d\u0d19\u0d33\u0d3f\u0d32\u0d47\u0d15\u0d4d\u0d15\u0d4d\/\u0d24\u0d30\u0d19\u0d4d\u0d19\u0d33\u0d3f\u0d7d \u0d28\u0d3f\u0d28\u0d4d\u0d28\u0d4d \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f\/\u0d15\u0d2f\u0d31\u0d4d\u0d31\u0d41\u0d2e\u0d24\u0d3f \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", "pad.toolbar.timeslider.title": "\u0d38\u0d2e\u0d2f\u0d30\u0d47\u0d16", - "pad.toolbar.savedRevision.title": "\u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d24\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d41\u0d15\u0d7e", + "pad.toolbar.savedRevision.title": "\u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d4d \u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", "pad.toolbar.settings.title": "\u0d38\u0d1c\u0d4d\u0d1c\u0d40\u0d15\u0d30\u0d23\u0d19\u0d4d\u0d19\u0d7e", "pad.toolbar.embed.title": "\u0d08 \u0d2a\u0d3e\u0d21\u0d4d \u0d0e\u0d02\u0d2c\u0d46\u0d21\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", "pad.toolbar.showusers.title": "\u0d08 \u0d2a\u0d3e\u0d21\u0d3f\u0d32\u0d41\u0d33\u0d4d\u0d33 \u0d09\u0d2a\u0d2f\u0d4b\u0d15\u0d4d\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d33\u0d46 \u0d2a\u0d4d\u0d30\u0d26\u0d7c\u0d36\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", @@ -36,6 +36,7 @@ "pad.settings.stickychat": "\u0d24\u0d24\u0d4d\u0d38\u0d2e\u0d2f\u0d02 \u0d38\u0d02\u0d35\u0d3e\u0d26\u0d02 \u0d0e\u0d2a\u0d4d\u0d2a\u0d4b\u0d34\u0d41\u0d02 \u0d38\u0d4d\u0d15\u0d4d\u0d30\u0d40\u0d28\u0d3f\u0d7d \u0d15\u0d3e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", "pad.settings.colorcheck": "\u0d0e\u0d34\u0d41\u0d24\u0d4d\u0d24\u0d41\u0d15\u0d3e\u0d7c\u0d15\u0d4d\u0d15\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d31\u0d19\u0d4d\u0d19\u0d7e", "pad.settings.linenocheck": "\u0d35\u0d30\u0d3f\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d15\u0d4d\u0d30\u0d2e\u0d38\u0d02\u0d16\u0d4d\u0d2f", + "pad.settings.rtlcheck": "\u0d09\u0d33\u0d4d\u0d33\u0d1f\u0d15\u0d4d\u0d15\u0d02 \u0d35\u0d32\u0d24\u0d4d\u0d24\u0d41\u0d28\u0d3f\u0d28\u0d4d\u0d28\u0d4d \u0d07\u0d1f\u0d24\u0d4d\u0d24\u0d4b\u0d1f\u0d4d\u0d1f\u0d3e\u0d23\u0d4b \u0d35\u0d3e\u0d2f\u0d3f\u0d15\u0d4d\u0d15\u0d47\u0d23\u0d4d\u0d1f\u0d24\u0d4d?", "pad.settings.fontType": "\u0d2b\u0d4b\u0d23\u0d4d\u0d1f\u0d4d \u0d24\u0d30\u0d02:", "pad.settings.fontType.normal": "\u0d38\u0d3e\u0d27\u0d3e\u0d30\u0d23\u0d02", "pad.settings.fontType.monospaced": "\u0d2e\u0d4b\u0d23\u0d4b\u0d38\u0d4d\u0d2a\u0d47\u0d38\u0d4d", @@ -51,6 +52,7 @@ "pad.importExport.exportpdf": "\u0d2a\u0d3f.\u0d21\u0d3f.\u0d0e\u0d2b\u0d4d.", "pad.importExport.exportopen": "\u0d12.\u0d21\u0d3f.\u0d0e\u0d2b\u0d4d. (\u0d13\u0d2a\u0d4d\u0d2a\u0d7a \u0d21\u0d4b\u0d15\u0d4d\u0d2f\u0d41\u0d2e\u0d46\u0d28\u0d4d\u0d31\u0d4d \u0d2b\u0d4b\u0d7c\u0d2e\u0d3e\u0d31\u0d4d\u0d31\u0d4d)", "pad.importExport.exportdokuwiki": "\u0d21\u0d4b\u0d15\u0d41\u0d35\u0d3f\u0d15\u0d4d\u0d15\u0d3f", + "pad.importExport.abiword.innerHTML": "\u0d2a\u0d4d\u0d32\u0d46\u0d2f\u0d3f\u0d7b \u0d1f\u0d46\u0d15\u0d4d\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4b \u0d0e\u0d1a\u0d4d\u0d1a\u0d4d.\u0d31\u0d4d\u0d31\u0d3f.\u0d0e\u0d02.\u0d0e\u0d7d. \u0d24\u0d30\u0d2e\u0d4b \u0d2e\u0d3e\u0d24\u0d4d\u0d30\u0d2e\u0d47 \u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d7e\u0d15\u0d4d\u0d15\u0d4d \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d28\u0d3e\u0d35\u0d42. \u0d15\u0d42\u0d1f\u0d41\u0d24\u0d7d \u0d35\u0d3f\u0d2a\u0d41\u0d32\u0d40\u0d15\u0d43\u0d24 \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f \u0d38\u0d57\u0d15\u0d30\u0d4d\u0d2f\u0d19\u0d4d\u0d19\u0d7e\u0d15\u0d4d\u0d15\u0d3e\u0d2f\u0d3f \u0d26\u0d2f\u0d35\u0d3e\u0d2f\u0d3f \u0d05\u0d2c\u0d3f\u0d35\u0d47\u0d21\u0d4d \u0d07\u0d7b\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4b\u0d7e \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15<\/a>.", "pad.modals.connected": "\u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d1a\u0d4d\u0d1a\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41.", "pad.modals.reconnecting": "\u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d3e\u0d21\u0d3f\u0d32\u0d47\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d4d \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41...", "pad.modals.forcereconnect": "\u0d0e\u0d28\u0d4d\u0d24\u0d3e\u0d2f\u0d3e\u0d32\u0d41\u0d02 \u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15", @@ -101,6 +103,8 @@ "timeslider.month.october": "\u0d12\u0d15\u0d4d\u0d1f\u0d4b\u0d2c\u0d7c", "timeslider.month.november": "\u0d28\u0d35\u0d02\u0d2c\u0d7c", "timeslider.month.december": "\u0d21\u0d3f\u0d38\u0d02\u0d2c\u0d7c", + "timeslider.unnamedauthor": "{{num}} \u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24 \u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d35\u0d4d", + "timeslider.unnamedauthors": "{{num}} \u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24 \u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7e", "pad.savedrevs.marked": "\u0d08 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d4d \u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d24\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d3e\u0d2f\u0d3f \u0d05\u0d1f\u0d2f\u0d3e\u0d33\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d41\u0d24\u0d4d\u0d24\u0d3f\u0d2f\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41", "pad.userlist.entername": "\u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d47\u0d30\u0d4d \u0d28\u0d7d\u0d15\u0d41\u0d15", "pad.userlist.unnamed": "\u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24", From ab2e805aa0143ffc04d2875672ba8ac3193c9460 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 23 Mar 2013 14:50:00 +0000 Subject: [PATCH 041/104] changelog --- CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 642846a6e..152af7f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.2.91 + * NEW: Authors can now send custom object messages to other Authors making 3 way conversations possible. This introduces WebRTC plugin support. + * NEW: Hook for Chat Messages Allows for Desktop Notification support + * NEW: FreeBSD installation docs + * Fix: Cookies inside of plugins + * Fix: Long lines in Firefox now wrap properly + * Fix: Log HTTP on DEBUG log level + * Fix: Server wont crash on import fails on 0 file import. + * Fix: Import no longer fails consistantly + * Fix: Language support for non existing languages + * Fix: Mobile support for chat notifications are now usable + * Fix: Re-Enable Editbar buttons on reconnect + * Fix: Clearing authorship colors no longer disconnects all clients + # 1.2.9 * Fix: MAJOR Security issue, where a hacker could submit content as another user * Fix: security issue due to unescaped user input @@ -6,7 +20,7 @@ * Fix: PadUsers API endpoint * NEW: A script to import data to all dbms * NEW: Add authorId to chat and userlist as a data attribute - * NEW Refactor and fix our frontend tests + * NEW: Refactor and fix our frontend tests * NEW: Localisation updates From af80e37ac752e1004752cff275db730daf6c2292 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 23 Mar 2013 15:03:56 +0000 Subject: [PATCH 042/104] missed this one.. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 152af7f79..ca5b078fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * NEW: Hook for Chat Messages Allows for Desktop Notification support * NEW: FreeBSD installation docs * Fix: Cookies inside of plugins + * Fix: Refactor Caret navigation with Arrow and Pageup/down keys stops cursor being lost * Fix: Long lines in Firefox now wrap properly * Fix: Log HTTP on DEBUG log level * Fix: Server wont crash on import fails on 0 file import. From b3988e30d54ceccbf16a2586701e490fd037980c Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 23 Mar 2013 17:55:34 +0000 Subject: [PATCH 043/104] pump isdeprecated --- src/node/utils/caching_middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index c6b237139..a970bfc95 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -168,7 +168,7 @@ CachingMiddleware.prototype = new function () { } else if (req.method == 'GET') { var readStream = fs.createReadStream(pathStr); res.writeHead(statusCode, headers); - util.pump(readStream, res); + readableStream.pipe(readStream, res); } else { res.writeHead(statusCode, headers); res.end(); From d515acae96dd9a74fb398412cc93d22a18329ade Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 23 Mar 2013 18:01:44 +0000 Subject: [PATCH 044/104] expires was never defined --- src/node/db/SessionStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js index 09ea73330..52a504f10 100644 --- a/src/node/db/SessionStore.js +++ b/src/node/db/SessionStore.js @@ -22,7 +22,7 @@ SessionStore.prototype.get = function(sid, fn){ { if (sess) { sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires; - if (!sess.cookie.expires || new Date() < expires) { + if (!sess.cookie.expires || new Date() < sess.cookie.expires) { fn(null, sess); } else { self.destroy(sid, fn); From c78aad16eaf888c47844e3f36db9a9cde4539844 Mon Sep 17 00:00:00 2001 From: disy-mk Date: Sun, 24 Mar 2013 01:18:44 +0100 Subject: [PATCH 045/104] adds missing semicolons in src/node/utils folder --- src/node/utils/Abiword.js | 6 +++--- src/node/utils/ExportDokuWiki.js | 2 +- src/node/utils/ExportHelper.js | 8 ++++---- src/node/utils/ExportHtml.js | 5 ++--- src/node/utils/ExportTxt.js | 3 +-- src/node/utils/Minify.js | 10 +++++----- src/node/utils/Settings.js | 8 ++++---- src/node/utils/caching_middleware.js | 4 ++-- src/node/utils/padDiff.js | 22 +++++++++++----------- 9 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js index 27138e648..925203433 100644 --- a/src/node/utils/Abiword.js +++ b/src/node/utils/Abiword.js @@ -63,7 +63,7 @@ if(os.type().indexOf("Windows") > -1) callback(); }); - } + }; exports.convertFile = function(srcFile, destFile, type, callback) { @@ -121,7 +121,7 @@ else firstPrompt = false; } }); - } + }; spawnAbiword(); doConvertTask = function(task, callback) @@ -135,7 +135,7 @@ else console.log("queue continue"); task.callback(err); }; - } + }; //Queue with the converts we have to do var queue = async.queue(doConvertTask, 1); diff --git a/src/node/utils/ExportDokuWiki.js b/src/node/utils/ExportDokuWiki.js index d2f71236b..f5d2d177f 100644 --- a/src/node/utils/ExportDokuWiki.js +++ b/src/node/utils/ExportDokuWiki.js @@ -316,7 +316,7 @@ exports.getPadDokuWikiDocument = function (padId, revNum, callback) getPadDokuWiki(pad, revNum, callback); }); -} +}; function _escapeDokuWiki(s) { diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index a939a8b6e..136896f06 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -45,7 +45,7 @@ exports.getPadPlainText = function(pad, revNum){ } return pieces.join(''); -} +}; exports._analyzeLine = function(text, aline, apool){ @@ -77,11 +77,11 @@ exports._analyzeLine = function(text, aline, apool){ line.aline = aline; } return line; -} +}; exports._encodeWhitespace = function(s){ return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ - return "&#" +c.charCodeAt(0) + ";" + return "&#" +c.charCodeAt(0) + ";"; }); -} +}; diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 585694d4b..7b94310a3 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -21,7 +21,7 @@ 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 getPadPlainText = require('./ExportHelper').getPadPlainText; var _analyzeLine = require('./ExportHelper')._analyzeLine; var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; @@ -515,7 +515,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) callback(null, head + html + foot); }); }); -} +}; // copied from ACE @@ -595,4 +595,3 @@ function _processSpaces(s){ } return parts.join(''); } - diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index c57424f1d..f0b62743a 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -289,5 +289,4 @@ exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) callback(null, html); }); }); -} - +}; diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 5fc8accbb..58d08b30e 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -125,11 +125,11 @@ function requestURIs(locations, method, headers, callback) { } function completed() { - var statuss = responses.map(function (x) {return x[0]}); - var headerss = responses.map(function (x) {return x[1]}); - var contentss = responses.map(function (x) {return x[2]}); + var statuss = responses.map(function (x) {return x[0];}); + var headerss = responses.map(function (x) {return x[1];}); + var contentss = responses.map(function (x) {return x[2];}); callback(statuss, headerss, contentss); - }; + } } /** @@ -263,7 +263,7 @@ function getAceFile(callback) { var filename = item.match(/"([^"]*)"/)[1]; var request = require('request'); - var baseURI = 'http://localhost:' + settings.port + var baseURI = 'http://localhost:' + settings.port; var resourceURI = baseURI + path.normalize(path.join('/static/', filename)); resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?) diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 45f81aa5f..a6c71a85e 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -137,7 +137,7 @@ exports.abiwordAvailable = function() { return "no"; } -} +}; exports.reloadSettings = function reloadSettings() { // Discover where the settings file lives @@ -157,7 +157,7 @@ exports.reloadSettings = function reloadSettings() { try { if(settingsStr) { settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json"); - settings = JSON.parse(JSON.stringify(settings)) // fix objects having constructors of other vm.context + settings = JSON.parse(JSON.stringify(settings)); // fix objects having constructors of other vm.context } }catch(e){ console.error('There was an error processing your settings.json file: '+e.message); @@ -196,9 +196,9 @@ exports.reloadSettings = function reloadSettings() { } if(exports.dbType === "dirty"){ - console.warn("DirtyDB is used. This is fine for testing but not recommended for production.") + console.warn("DirtyDB is used. This is fine for testing but not recommended for production."); } -} +}; // initially load settings exports.reloadSettings(); diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index c6b237139..1d103ffd6 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -23,7 +23,7 @@ var util = require('util'); var settings = require('./Settings'); var semver = require('semver'); -var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync +var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync; var CACHE_DIR = path.normalize(path.join(settings.root, 'var/')); CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined; @@ -133,7 +133,7 @@ CachingMiddleware.prototype = new function () { old_res.write = res.write; old_res.end = res.end; res.write = function(data, encoding) {}; - res.end = function(data, encoding) { respond() }; + res.end = function(data, encoding) { respond(); }; } else { res.writeHead(status, headers); } diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 1b3cf58f5..c53540417 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -68,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function(changeset){ return false; return true; -} +}; PadDiff.prototype._createClearAuthorship = function(rev, callback){ var self = this; @@ -84,7 +84,7 @@ PadDiff.prototype._createClearAuthorship = function(rev, callback){ callback(null, changeset); }); -} +}; PadDiff.prototype._createClearStartAtext = function(rev, callback){ var self = this; @@ -107,7 +107,7 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){ callback(null, newAText); }); }); -} +}; PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { var self = this; @@ -124,7 +124,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { async.forEach(revisions, function(rev, callback){ self._pad.getRevision(rev, function(err, revision){ if(err){ - return callback(err) + return callback(err); } var arrayNum = rev-startRev; @@ -137,7 +137,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { }, function(err){ callback(err, changesets, authors); }); -} +}; PadDiff.prototype._addAuthors = function(authors) { var self = this; @@ -147,7 +147,7 @@ PadDiff.prototype._addAuthors = function(authors) { self._authors.push(author); } }); -} +}; PadDiff.prototype._createDiffAtext = function(callback) { var self = this; @@ -219,7 +219,7 @@ PadDiff.prototype._createDiffAtext = function(callback) { } ); }); -} +}; PadDiff.prototype.getHtml = function(callback){ //cache the html @@ -279,7 +279,7 @@ PadDiff.prototype.getAuthors = function(callback){ } else { callback(null, self._authors); } -} +}; PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) { //unpack @@ -312,7 +312,7 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool //return the modified changeset return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank); -} +}; //this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext. PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { @@ -463,7 +463,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { // 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", ""]);; + var authorAttrib = apool.putAttrib(["author", ""]); attribKeys.length = 0; attribValues.length = 0; @@ -473,7 +473,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { if(apool.getAttribKey(n) === "author"){ authorAttrib = n; - }; + } }); var undoBackToAttribs = cachedStrFunc(function (attribs) { From 2e7a9796ded7c66a14639e261d54b7bf6c4496ec Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 24 Mar 2013 01:12:01 +0000 Subject: [PATCH 046/104] option to show sticky chat on screen, note i use a literal string, how am i supposed to add a l10n title? --- src/static/css/pad.css | 9 +++++++++ src/templates/pad.html | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 320a47202..dafb77ef4 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -559,6 +559,15 @@ table#otheruserstable { margin: 4px 0 0 4px; position: absolute; } +#titlesticky{ + font-size: 10px; + padding-top:2px; + float: right; + text-align: right; + text-decoration: none; + cursor: pointer; + color: #555; +} #titlecross { font-size: 25px; float: right; diff --git a/src/templates/pad.html b/src/templates/pad.html index 1d8c069a5..16599e175 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -369,14 +369,17 @@ <% e.end_block(); %> -
+
0
- +
+ + █   +
loading.. From ef7fb5c7f0e233268530eb8cd6eb02224751dae5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 12:18:06 +0100 Subject: [PATCH 047/104] Update npm --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 822676a38..ff6625753 100644 --- a/src/package.json +++ b/src/package.json @@ -27,7 +27,7 @@ "nodemailer" : "0.3.x", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", - "npm" : "1.1.x", + "npm" : "1.2.x", "npm-registry-client" : "0.2.10", "ejs" : "0.6.1", "graceful-fs" : "1.1.5", From 0070eab4164ef473c706fe7f1f5c8064e7c819b9 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 12:45:23 +0100 Subject: [PATCH 048/104] Fix caching of npm search results and only make one registry request on /admin/plugins fixes #1488 --- src/node/hooks/express/adminplugins.js | 4 ++-- src/static/js/admin/plugins.js | 6 ++++-- src/static/js/pluginfw/installer.js | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 7e221cf1e..85bf2108b 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -36,7 +36,7 @@ exports.socketio = function (hook_name, args, cb) { socket.on("checkUpdates", function() { socket.emit("progress", {progress:0, message:'Checking for plugin updates...'}); // Check plugins for updates - installer.search({offset: 0, pattern: '', limit: 500}, /*useCache:*/true, function(data) { // hacky + installer.search({offset: 0, pattern: '', limit: 500}, /*maxCacheAge:*/60*10, function(data) { // hacky if (!data.results) return; var updatable = _(plugins.plugins).keys().filter(function(plugin) { if(!data.results[plugin]) return false; @@ -51,7 +51,7 @@ exports.socketio = function (hook_name, args, cb) { socket.on("search", function (query) { socket.emit("progress", {progress:0, message:'Fetching results...'}); - installer.search(query, true, function (progress) { + installer.search(query, /*maxCacheAge:*/60*10, function (progress) { if (progress.results) socket.emit("search-result", progress); socket.emit("progress", progress); diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index a973875ce..d7f94d2cf 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -163,8 +163,10 @@ $(document).ready(function () { } updateHandlers(); - socket.emit('checkUpdates'); - tasks++; + setTimeout(function() { + socket.emit('checkUpdates'); + tasks++; + }, 5000) }); socket.on('updatable', function(data) { diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 15d879409..14a3b7be5 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -71,12 +71,13 @@ exports.install = function(plugin_name, cb) { }; exports.searchCache = null; +var cacheTimestamp = 0; -exports.search = function(query, cache, cb) { +exports.search = function(query, maxCacheAge, cb) { withNpm( function (cb) { var getData = function (cb) { - if (cache && exports.searchCache) { + if (maxCacheAge && exports.searchCache && Math.round(+new Date/1000)-cacheTimestamp < maxCacheAge) { cb(null, exports.searchCache); } else { registry.get( @@ -84,6 +85,7 @@ exports.search = function(query, cache, cb) { function (er, data) { if (er) return cb(er); exports.searchCache = data; + cacheTimestamp = Math.round(+new Date/1000) cb(er, data); } ); From b297784288a5c93e329509deca45a78fc81057d3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 16:51:12 +0100 Subject: [PATCH 049/104] Make npm registry access code more sane --- src/package.json | 1 - src/static/js/pluginfw/installer.js | 139 +++++++++------------------- 2 files changed, 46 insertions(+), 94 deletions(-) diff --git a/src/package.json b/src/package.json index ff6625753..24f15ca9b 100644 --- a/src/package.json +++ b/src/package.json @@ -28,7 +28,6 @@ "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", "npm" : "1.2.x", - "npm-registry-client" : "0.2.10", "ejs" : "0.6.1", "graceful-fs" : "1.1.5", "slide" : "1.1.3", diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 14a3b7be5..949fdae82 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -1,120 +1,73 @@ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var npm = require("npm"); -var RegClient = require("npm-registry-client") -var registry = new RegClient( -{ registry: "http://registry.npmjs.org" -, cache: npm.cache } -); - -var withNpm = function (npmfn, final, cb) { +var withNpm = function (npmfn, cb) { npm.load({}, function (er) { - if (er) return cb({progress:1, error:er}); + if (er) return cb(er); npm.on("log", function (message) { - cb({progress: 0.5, message:message.msg + ": " + message.pref}); - }); - npmfn(function (er, data) { - if (er) { - console.error(er); - return cb({progress:1, error: er.message}); - } - if (!data) data = {}; - data.progress = 1; - data.message = "Done."; - cb(data); - final(); + console.log('npm: ',message) }); + npmfn(); }); } -// All these functions call their callback multiple times with -// {progress:[0,1], message:STRING, error:object}. They will call it -// with progress = 1 at least once, and at all times will either -// message or error be present, not both. It can be called multiple -// times for all values of propgress except for 1. - exports.uninstall = function(plugin_name, cb) { - withNpm( - function (cb) { - npm.commands.uninstall([plugin_name], function (er) { + withNpm(function () { + npm.commands.uninstall([plugin_name], function (er) { + if (er) return cb && cb(er); + hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { if (er) return cb(er); - hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { - if (er) return cb(er); - plugins.update(cb); - }); + plugins.update(cb); + cb && cb(); + hooks.aCallAll("restartServer", {}, function () {}); }); - }, - function () { - hooks.aCallAll("restartServer", {}, function () {}); - }, - cb - ); + }); + }); }; exports.install = function(plugin_name, cb) { - withNpm( - function (cb) { + withNpm(function () { npm.commands.install([plugin_name], function (er) { - if (er) return cb(er); + if (er) return cb && cb(er); hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { if (er) return cb(er); plugins.update(cb); + cb && cb(); + hooks.aCallAll("restartServer", {}, function () {}); }); }); - }, - function () { - hooks.aCallAll("restartServer", {}, function () {}); - }, - cb - ); + }); }; -exports.searchCache = null; +exports.availablePlugins = null; var cacheTimestamp = 0; -exports.search = function(query, maxCacheAge, cb) { - withNpm( - function (cb) { - var getData = function (cb) { - if (maxCacheAge && exports.searchCache && Math.round(+new Date/1000)-cacheTimestamp < maxCacheAge) { - cb(null, exports.searchCache); - } else { - registry.get( - "/-/all", 600, false, true, - function (er, data) { - if (er) return cb(er); - exports.searchCache = data; - cacheTimestamp = Math.round(+new Date/1000) - cb(er, data); - } - ); - } - } - getData( - function (er, data) { - if (er) return cb(er); - var res = {}; - var i = 0; - var pattern = query.pattern.toLowerCase(); - for (key in data) { // for every plugin in the data from npm - if ( key.indexOf(plugins.prefix) == 0 - && key.indexOf(pattern) != -1 - || key.indexOf(plugins.prefix) == 0 - && data[key].description.indexOf(pattern) != -1 - ) { // If the name contains ep_ and the search string is in the name or description - i++; - if (i > query.offset - && i <= query.offset + query.limit) { - res[key] = data[key]; - } - } - } - cb(null, {results:res, query: query, total:i}); - } - ); - }, - function () { }, - cb - ); +exports.getAvailablePlugins = function(maxCacheAge, cb) { + withNpm(function () { + if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) { + return cb && cb(null, exports.availablePlugins) + } + npm.commands.search(['ep_'], function(er, results) { + if(er) return cb && cb(er); + exports.availablePlugins = results; + cacheTimestamp = Math.round(+new Date/1000); + cb && cb(null, results) + }) + }); +}; + + +exports.search = function(searchTerm, maxCacheAge, cb) { + exports.getAvailablePlugins(maxCacheAge, function(er, results) { + if(er) return cb && cb(er); + var res = {}; + searchTerm = searchTerm.toLowerCase(); + for (var pluginName in results) { // for every available plugin + if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here! + if(pluginName.indexOf(searchTerm) < 0 && results[pluginName].description.indexOf(searchTerm) < 0) continue; + res[pluginName] = results[pluginName]; + } + cb && cb(null, res) + }) }; From 079fdf0f38160481836b6fc881c60741f90a414b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 17:20:10 +0100 Subject: [PATCH 050/104] Revamp /admin/plugins - dry up the client-side code - use the new saner API of pluginfw/installer.js on the server - Improve UX: allow user to infinitely scroll to display their results --- src/node/hooks/express/adminplugins.js | 62 +++++--- src/static/js/admin/plugins.js | 207 ++++++++----------------- src/templates/admin/plugins.html | 5 +- 3 files changed, 105 insertions(+), 169 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 85bf2108b..6b7c01e04 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -27,48 +27,66 @@ exports.socketio = function (hook_name, args, cb) { io.on('connection', function (socket) { if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return; - socket.on("load", function (query) { + socket.on("getInstalled", function (query) { // send currently installed plugins - socket.emit("installed-results", {results: plugins.plugins}); - socket.emit("progress", {progress:1}); + var installed = Object.keys(plugins.plugins).map(function(plugin) { + return plugins.plugins[plugin].package + }) + socket.emit("results:installed", {installed: installed}); }); - socket.on("checkUpdates", function() { - socket.emit("progress", {progress:0, message:'Checking for plugin updates...'}); + socket.on("getUpdatable", function() { // Check plugins for updates - installer.search({offset: 0, pattern: '', limit: 500}, /*maxCacheAge:*/60*10, function(data) { // hacky - if (!data.results) return; + installer.getAvailable(/*maxCacheAge:*/60*10, function(er, results) { + if(er) { + console.warn(er); + socket.emit("results:updatable", {updatable: {}}); + return; + } var updatable = _(plugins.plugins).keys().filter(function(plugin) { - if(!data.results[plugin]) return false; - var latestVersion = data.results[plugin]['dist-tags'].latest + if(!results[plugin]) return false; + var latestVersion = results[plugin].version var currentVersion = plugins.plugins[plugin].package.version return semver.gt(latestVersion, currentVersion) }); - socket.emit("updatable", {updatable: updatable}); - socket.emit("progress", {progress:1}); + socket.emit("results:updatable", {updatable: updatable}); }); }) + + socket.on("getAvailable", function (query) { + installer.getAvailablePlugins(/*maxCacheAge:*/false, function (er, results) { + if(er) { + console.error(er) + results = {} + } + socket.emit("results:available", results); + }); + }); socket.on("search", function (query) { - socket.emit("progress", {progress:0, message:'Fetching results...'}); - installer.search(query, /*maxCacheAge:*/60*10, function (progress) { - if (progress.results) - socket.emit("search-result", progress); - socket.emit("progress", progress); + installer.search(query.searchTerm, /*maxCacheAge:*/60*10, function (er, results) { + if(er) { + console.error(er) + results = {} + } + var res = Object.keys(results) + .slice(query.offset, query.offset+query.length) + .map(function(pluginName) { + return results[pluginName] + }); + socket.emit("results:search", {results: res, query: query}); }); }); socket.on("install", function (plugin_name) { - socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."}); - installer.install(plugin_name, function (progress) { - socket.emit("progress", progress); + installer.install(plugin_name, function (er) { + socket.emit("finished:install", {error: er}); }); }); socket.on("uninstall", function (plugin_name) { - socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."}); - installer.uninstall(plugin_name, function (progress) { - socket.emit("progress", progress); + installer.uninstall(plugin_name, function (er) { + socket.emit("finished:uninstall", {error: er}); }); }); }); diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index d7f94d2cf..8aff0f68f 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -12,165 +12,86 @@ $(document).ready(function () { //connect socket = io.connect(url, {resource : resource}).of("/pluginfw/installer"); - $('.search-results').data('query', { - pattern: '', - offset: 0, - limit: 12, - }); - - var doUpdate = false; - - var search = function () { - socket.emit("search", $('.search-results').data('query')); - tasks++; + function search(searchTerm) { + if(search.searchTerm != searchTerm) { + search.offset = 0 + search.results = [] + search.end = false + } + search.searchTerm = searchTerm; + socket.emit("search", {searchTerm: searchTerm, offset:search.offset, length: search.limit}); + search.offset += search.limit; } + search.offset = 0 + search.limit = 12 + search.results = [] + search.end = true// have we received all results already? - function updateHandlers() { - $("form").submit(function(){ - var query = $('.search-results').data('query'); - query.pattern = $("#search-query").val(); - query.offset = 0; - search(); - return false; - }); - - $("#search-query").unbind('keyup').keyup(function () { - var query = $('.search-results').data('query'); - query.pattern = $("#search-query").val(); - query.offset = 0; - search(); - }); - - $(".do-install, .do-update").unbind('click').click(function (e) { - var row = $(e.target).closest("tr"); - doUpdate = true; - socket.emit("install", row.find(".name").text()); - tasks++; - }); - - $(".do-uninstall").unbind('click').click(function (e) { - var row = $(e.target).closest("tr"); - doUpdate = true; - socket.emit("uninstall", row.find(".name").text()); - tasks++; - }); - - $(".do-prev-page").unbind('click').click(function (e) { - var query = $('.search-results').data('query'); - query.offset -= query.limit; - if (query.offset < 0) { - query.offset = 0; - } - search(); - }); - $(".do-next-page").unbind('click').click(function (e) { - var query = $('.search-results').data('query'); - var total = $('.search-results').data('total'); - if (query.offset + query.limit < total) { - query.offset += query.limit; - } - search(); - }); - } - - updateHandlers(); - - var tasks = 0; - socket.on('progress', function (data) { - $("#progress").show(); - $('#progress').data('progress', data.progress); - - var message = "Unknown status"; - if (data.message) { - message = data.message.toString(); - } - if (data.error) { - data.progress = 1; - } - - $("#progress .message").html(message); - - if (data.progress >= 1) { - tasks--; - if (tasks <= 0) { - // Hide the activity indicator once all tasks are done - $("#progress").hide(); - tasks = 0; - } - - if (data.error) { - alert('An error occurred: '+data.error+' -- the server log might know more...'); - }else { - if (doUpdate) { - doUpdate = false; - socket.emit("load"); - tasks++; - } - } - } - }); - - socket.on('search-result', function (data) { - var widget=$(".search-results"); - - widget.data('query', data.query); - widget.data('total', data.total); - - widget.find('.offset').html(data.query.offset); - if (data.query.offset + data.query.limit > data.total){ - widget.find('.limit').html(data.total); - }else{ - widget.find('.limit').html(data.query.offset + data.query.limit); - } - widget.find('.total').html(data.total); - - widget.find(".results *").remove(); - for (plugin_name in data.results) { - var plugin = data.results[plugin_name]; - var row = widget.find(".template tr").clone(); + function displayPluginList(plugins, container, template) { + plugins.forEach(function(plugin) { + var row = template.clone(); for (attr in plugin) { if(attr == "name"){ // Hack to rewrite URLS into name - row.find(".name").html(""+plugin[attr]+""); + row.find(".name").html(""+plugin['name']+""); }else{ row.find("." + attr).html(plugin[attr]); } } - row.find(".version").html( data.results[plugin_name]['dist-tags'].latest ); + row.find(".version").html( plugin.version ); - widget.find(".results").append(row); - } - + container.append(row); + }) updateHandlers(); + } + + function updateHandlers() { + // Search + $("#search-query").unbind('keyup').keyup(function () { + search($("#search-query").val()); + }); + + // update & install + $(".do-install, .do-update").unbind('click').click(function (e) { + var row = $(e.target).closest("tr"); + socket.emit("install", row.find(".name").text()); + }); + + // uninstall + $(".do-uninstall").unbind('click').click(function (e) { + var row = $(e.target).closest("tr"); + socket.emit("uninstall", row.find(".name").text()); + }); + + // Infinite scroll + $(window).unbind('scroll').scroll(function() { + if(search.end) return;// don't keep requesting if there are no more results + var top = $('.search-results .results > tr').last().offset().top + if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm) + }) + } + + socket.on('results:search', function (data) { + console.log('got search results', data) + search.results = search.results.concat(data.results); + if(!data.results.length) search.end = true; + + var searchWidget = $(".search-results"); + searchWidget.find(".results *").remove(); + displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) }); - socket.on('installed-results', function (data) { + socket.on('results:installed', function (data) { $("#installed-plugins *").remove(); - - for (plugin_name in data.results) { - if (plugin_name == "ep_etherpad-lite") continue; // Hack... - var plugin = data.results[plugin_name]; - var row = $("#installed-plugin-template").clone(); - - for (attr in plugin.package) { - if(attr == "name"){ // Hack to rewrite URLS into name - row.find(".name").html(""+plugin.package[attr]+""); - }else{ - row.find("." + attr).html(plugin.package[attr]); - } - } - $("#installed-plugins").append(row); - } - updateHandlers(); + displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")) setTimeout(function() { socket.emit('checkUpdates'); - tasks++; }, 5000) }); - socket.on('updatable', function(data) { - $('#installed-plugins>tr').each(function(i,tr) { + socket.on('results:updatable', function(data) { + $('#installed-plugins > tr').each(function(i, tr) { var pluginName = $(tr).find('.name').text() if (data.updatable.indexOf(pluginName) >= 0) { @@ -182,8 +103,8 @@ $(document).ready(function () { updateHandlers(); }) - socket.emit("load"); - tasks++; - - search(); + // init + updateHandlers(); + socket.emit("getInstalled"); + search(''); }); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 7c2a7abf2..d6fff9f00 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -86,11 +86,8 @@ - - .. of . -
- +
From 1ebbcd2f30895e4e0ec54a49c2e49905ab52f626 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 17:22:51 +0100 Subject: [PATCH 051/104] Don't leak event listeners in pluginfw/installer.js fixes #921 --- src/static/js/pluginfw/installer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 949fdae82..1ef34733f 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -2,9 +2,12 @@ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var npm = require("npm"); -var withNpm = function (npmfn, cb) { +var npmIsLoaded = false; +var withNpm = function (npmfn) { + if(npmIsLoaded) return npmfn(); npm.load({}, function (er) { if (er) return cb(er); + npmIsLoaded = true; npm.on("log", function (message) { console.log('npm: ',message) }); From 773293991b2ad4877d2c360cef59ccc4cd7562f5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 23:09:03 +0100 Subject: [PATCH 052/104] admin/plugins: Allow people to sort search results --- src/node/hooks/express/adminplugins.js | 14 +++++- src/static/css/admin.css | 13 +++++ src/static/js/admin/plugins.js | 66 +++++++++++++++++++++----- src/templates/admin/plugins.html | 6 +-- 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 6b7c01e04..70ace317f 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -70,10 +70,11 @@ exports.socketio = function (hook_name, args, cb) { results = {} } var res = Object.keys(results) - .slice(query.offset, query.offset+query.length) .map(function(pluginName) { return results[pluginName] }); + res = sortPluginList(res, query.sortBy, query.sortDir) + .slice(query.offset, query.offset+query.limit); socket.emit("results:search", {results: res, query: query}); }); }); @@ -91,3 +92,14 @@ exports.socketio = function (hook_name, args, cb) { }); }); } + +function sortPluginList(plugins, property, /*ASC?*/dir) { + return plugins.sort(function(a, b) { + if (a[property] < b[property]) + return dir? -1 : 1; + if (a[property] > b[property]) + return dir? 1 : -1; + // a must be equal to b + return 0; + }) +} \ No newline at end of file diff --git a/src/static/css/admin.css b/src/static/css/admin.css index b68238425..8160ee98a 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -102,6 +102,19 @@ input[type="text"] { max-width: 500px; } +.sort { + cursor: pointer; +} +.sort:after { + content: '▲▼' +} +.sort.up:after { + content:'▲' +} +.sort.down:after { + content:'▼' +} + table { border: 1px solid #ddd; border-radius: 3px; diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 8aff0f68f..308413bb7 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -12,20 +12,23 @@ $(document).ready(function () { //connect socket = io.connect(url, {resource : resource}).of("/pluginfw/installer"); - function search(searchTerm) { + function search(searchTerm, limit) { if(search.searchTerm != searchTerm) { search.offset = 0 search.results = [] search.end = false } + limit = limit? limit : search.limit search.searchTerm = searchTerm; - socket.emit("search", {searchTerm: searchTerm, offset:search.offset, length: search.limit}); - search.offset += search.limit; + socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir}); + search.offset += limit; } - search.offset = 0 - search.limit = 12 - search.results = [] - search.end = true// have we received all results already? + search.offset = 0; + search.limit = 12; + search.results = []; + search.sortBy = 'name'; + search.sortDir = /*DESC?*/true; + search.end = true;// have we received all results already? function displayPluginList(plugins, container, template) { plugins.forEach(function(plugin) { @@ -44,6 +47,17 @@ $(document).ready(function () { }) updateHandlers(); } + + function sortPluginList(plugins, property, /*ASC?*/dir) { + return plugins.sort(function(a, b) { + if (a[property] < b[property]) + return dir? -1 : 1; + if (a[property] > b[property]) + return dir? 1 : -1; + // a must be equal to b + return 0; + }) + } function updateHandlers() { // Search @@ -66,24 +80,54 @@ $(document).ready(function () { // Infinite scroll $(window).unbind('scroll').scroll(function() { if(search.end) return;// don't keep requesting if there are no more results - var top = $('.search-results .results > tr').last().offset().top + var top = $('.search-results .results > tr:last').offset().top if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm) }) + + // Sort + $('.sort.up').unbind('click').click(function() { + search.sortBy = $(this).text().toLowerCase(); + search.sortDir = false; + search.offset = 0; + search.results = []; + search(search.searchTerm, search.results.length); + }) + $('.sort.down, .sort.none').unbind('click').click(function() { + search.sortBy = $(this).text().toLowerCase(); + search.sortDir = true; + search.offset = 0; + search.results = []; + search(search.searchTerm); + }) } socket.on('results:search', function (data) { - console.log('got search results', data) - search.results = search.results.concat(data.results); if(!data.results.length) search.end = true; + console.log('got search results', data) + + // add to results + search.results = search.results.concat(data.results); + + // Update sorting head + $('.sort') + .removeClass('up down') + .addClass('none'); + $('.search-results thead th[data-label='+data.query.sortBy+']') + .removeClass('none') + .addClass(data.query.sortDir? 'up' : 'down'); + + // re-render search results var searchWidget = $(".search-results"); searchWidget.find(".results *").remove(); displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) }); socket.on('results:installed', function (data) { + sortPluginList(data.installed, 'name', /*ASC?*/true); + $("#installed-plugins *").remove(); - displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")) + displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")); setTimeout(function() { socket.emit('checkUpdates'); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index d6fff9f00..5b90aeabe 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -67,9 +67,9 @@ - - - + + + From b35d9c14fda2904d4a3dd1e55a6fdb6ea52f1416 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 25 Mar 2013 23:52:10 +0100 Subject: [PATCH 053/104] /admin/plugins:Hide ep_etherpad-lite in the list of installed plugins --- src/static/js/admin/plugins.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 308413bb7..1c00bbfa4 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -127,6 +127,9 @@ $(document).ready(function () { sortPluginList(data.installed, 'name', /*ASC?*/true); $("#installed-plugins *").remove(); + data.installed = data.installed.filter(function(plugin) { + return plugin.name != 'ep_etherpad-lite' + }) displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")); setTimeout(function() { From 6b55d13370f1057e117178c46807a59bd679de29 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 26 Mar 2013 01:54:01 +0000 Subject: [PATCH 054/104] expose ace document, reqjired for various plugins --- src/static/js/ace2_inner.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 2dc6408b1..fc69d5921 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1013,6 +1013,11 @@ function Ace2Inner(){ return caughtErrors.slice(); }; + editorInfo.ace_getDocument = function() + { + return doc; + }; + editorInfo.ace_getDebugProperty = function(prop) { if (prop == "debugger") From e8bae61cf5cfb568a237be4932460626409a056f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 11:19:36 +0100 Subject: [PATCH 055/104] /admin/plugins: Add progress indicators and report errors --- src/node/hooks/express/adminplugins.js | 6 +- src/static/css/admin.css | 22 +++++-- src/static/js/admin/plugins.js | 39 ++++++++++-- src/templates/admin/plugins.html | 87 ++++++++++++++------------ 4 files changed, 101 insertions(+), 53 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 70ace317f..bf796ee7b 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -81,13 +81,15 @@ exports.socketio = function (hook_name, args, cb) { socket.on("install", function (plugin_name) { installer.install(plugin_name, function (er) { - socket.emit("finished:install", {error: er}); + if(er) console.warn(er) + socket.emit("finished:install", {error: er? er.message : null}); }); }); socket.on("uninstall", function (plugin_name) { installer.uninstall(plugin_name, function (er) { - socket.emit("finished:uninstall", {error: er}); + if(er) console.warn(er) + socket.emit("finished:uninstall", {error: er? er.message : null}); }); }); }); diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 8160ee98a..d1c86ac59 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -35,7 +35,8 @@ div.menu li:last-child { div.innerwrapper { padding: 15px; - padding-left: 265px; + margin-left: 265px; + position:relative; /* Allows us to position the loading indicator relative to this div */ } #wrapper { @@ -121,6 +122,7 @@ table { border-spacing: 0; width: 100%; margin: 20px 0; + position:relative; /* Allows us to position the loading indicator relative to the table */ } table thead tr { @@ -135,13 +137,23 @@ td, th { display: none; } -#progress { +.progress { position: absolute; - bottom: 50px; + top: 0; left: 0; bottom:0; right:0; + padding: auto; + padding-top: 20%; + + background: rgba(255,255,255,0.85); + display: none; + /*border-radius: 7px; + border: 1px solid #ddd;*/ } -#progress img { - vertical-align: top; +.progress * { + display: block; + margin: 0 auto; + text-align: center; + color: #999; } .settings { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 1c00bbfa4..b43f26bb1 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -22,6 +22,7 @@ $(document).ready(function () { search.searchTerm = searchTerm; socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir}); search.offset += limit; + $('#search-progress').show() } search.offset = 0; search.limit = 12; @@ -59,6 +60,18 @@ $(document).ready(function () { }) } + var progress = { + show: function(msg) { + $('#progress').show() + $('#progress .message').text(msg) + $(window).scrollTop(0) + }, + hide: function() { + $('#progress').hide() + $('#progress .message').text('') + } + } + function updateHandlers() { // Search $("#search-query").unbind('keyup').keyup(function () { @@ -67,14 +80,18 @@ $(document).ready(function () { // update & install $(".do-install, .do-update").unbind('click').click(function (e) { - var row = $(e.target).closest("tr"); - socket.emit("install", row.find(".name").text()); + var row = $(e.target).closest("tr") + , plugin = row.find(".name").text(); + socket.emit("install", plugin); + progress.show('Installing plugin '+plugin+'...') }); // uninstall $(".do-uninstall").unbind('click').click(function (e) { - var row = $(e.target).closest("tr"); - socket.emit("uninstall", row.find(".name").text()); + var row = $(e.target).closest("tr") + , plugin = row.find(".name").text(); + socket.emit("uninstall", plugin); + progress.show('Uninstalling plugin '+plugin+'...') }); // Infinite scroll @@ -121,16 +138,18 @@ $(document).ready(function () { var searchWidget = $(".search-results"); searchWidget.find(".results *").remove(); displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) + $('#search-progress').hide() }); socket.on('results:installed', function (data) { sortPluginList(data.installed, 'name', /*ASC?*/true); - $("#installed-plugins *").remove(); data.installed = data.installed.filter(function(plugin) { return plugin.name != 'ep_etherpad-lite' }) + $("#installed-plugins *").remove(); displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")); + progress.hide() setTimeout(function() { socket.emit('checkUpdates'); @@ -150,6 +169,16 @@ $(document).ready(function () { updateHandlers(); }) + socket.on('finished:install', function(data) { + if(data.error) alert('An error occured while installing the plugin. \n'+data.error) + socket.emit("getInstalled"); + }) + + socket.on('finished:uninstall', function(data) { + if(data.error) alert('An error occured while uninstalling the plugin. \n'+data.error) + socket.emit("getInstalled"); + }) + // init updateHandlers(); socket.emit("getInstalled"); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 5b90aeabe..1e7984321 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -28,65 +28,70 @@
  • Troubleshooting information
  • <% e.end_block(); %> -
      
    -

    Installed plugins

    -
    NameDescriptionVersionNameDescriptionVersion
    - - - - - - - - - - - - - - - - - - -
    NameDescriptionVersion
    - -
    - -
    -
    - -

    Available plugins

    -
    - -
    - +

    Installed plugins

    - - - + + + - + + + - +
    NameDescriptionVersionNameDescriptionVersion
    - -
    -
    + +
    +
    + +

    Available plugins

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionVersion
    + +
    +
    +
    +
    +

    From 9109bd206ee44afd390db85ec0e7e8839ff8579a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 11:20:12 +0100 Subject: [PATCH 056/104] Catch all errors in pluginfw/installer.js --- src/static/js/pluginfw/installer.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 1ef34733f..56f18e10f 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -6,7 +6,7 @@ var npmIsLoaded = false; var withNpm = function (npmfn) { if(npmIsLoaded) return npmfn(); npm.load({}, function (er) { - if (er) return cb(er); + if (er) return npmfn(er); npmIsLoaded = true; npm.on("log", function (message) { console.log('npm: ',message) @@ -16,7 +16,8 @@ var withNpm = function (npmfn) { } exports.uninstall = function(plugin_name, cb) { - withNpm(function () { + withNpm(function (er) { + if (er) return cb && cb(er); npm.commands.uninstall([plugin_name], function (er) { if (er) return cb && cb(er); hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { @@ -30,16 +31,17 @@ exports.uninstall = function(plugin_name, cb) { }; exports.install = function(plugin_name, cb) { - withNpm(function () { - npm.commands.install([plugin_name], function (er) { - if (er) return cb && cb(er); - hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { - if (er) return cb(er); - plugins.update(cb); - cb && cb(); - hooks.aCallAll("restartServer", {}, function () {}); - }); + withNpm(function (er) { + if (er) return cb && cb(er); + npm.commands.install([plugin_name], function (er) { + if (er) return cb && cb(er); + hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { + if (er) return cb(er); + plugins.update(cb); + cb && cb(); + hooks.aCallAll("restartServer", {}, function () {}); }); + }); }); }; @@ -47,7 +49,8 @@ exports.availablePlugins = null; var cacheTimestamp = 0; exports.getAvailablePlugins = function(maxCacheAge, cb) { - withNpm(function () { + withNpm(function (er) { + if (er) return cb && cb(er); if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) { return cb && cb(null, exports.availablePlugins) } From 5d7a8adcb77ea53dea1109f74cf601c465909bfe Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 11:33:04 +0100 Subject: [PATCH 057/104] Silence npm when using npm.commands.search --- src/static/js/pluginfw/installer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 56f18e10f..9723ec671 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -54,7 +54,7 @@ exports.getAvailablePlugins = function(maxCacheAge, cb) { if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) { return cb && cb(null, exports.availablePlugins) } - npm.commands.search(['ep_'], function(er, results) { + npm.commands.search(['ep_'], /*silent?*/true, function(er, results) { if(er) return cb && cb(er); exports.availablePlugins = results; cacheTimestamp = Math.round(+new Date/1000); From 511407241a920a32746c5c1a97d1629ec99ffb01 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 11:38:51 +0100 Subject: [PATCH 058/104] /admin/plugins: Make it display the same amount of plugins after sorting --- src/static/js/admin/plugins.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index b43f26bb1..8f03f3b95 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -106,15 +106,15 @@ $(document).ready(function () { search.sortBy = $(this).text().toLowerCase(); search.sortDir = false; search.offset = 0; - search.results = []; search(search.searchTerm, search.results.length); + search.results = []; }) $('.sort.down, .sort.none').unbind('click').click(function() { search.sortBy = $(this).text().toLowerCase(); search.sortDir = true; search.offset = 0; + search(search.searchTerm, search.results.length); search.results = []; - search(search.searchTerm); }) } From aca5d150e48810ccc98fb0c284c35b1cca1f2070 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 11:58:31 +0100 Subject: [PATCH 059/104] /admin/plugins: Don't list installed plugins as available --- src/node/hooks/express/adminplugins.js | 3 +++ src/static/js/admin/plugins.js | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index bf796ee7b..3ef341154 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -72,6 +72,9 @@ exports.socketio = function (hook_name, args, cb) { var res = Object.keys(results) .map(function(pluginName) { return results[pluginName] + }) + .filter(function(plugin) { + return !plugins.plugins[plugin.name] }); res = sortPluginList(res, query.sortBy, query.sortDir) .slice(query.offset, query.offset+query.limit); diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 8f03f3b95..494776782 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -80,16 +80,17 @@ $(document).ready(function () { // update & install $(".do-install, .do-update").unbind('click').click(function (e) { - var row = $(e.target).closest("tr") - , plugin = row.find(".name").text(); + var $row = $(e.target).closest("tr") + , plugin = $row.find(".name").text(); socket.emit("install", plugin); progress.show('Installing plugin '+plugin+'...') }); // uninstall $(".do-uninstall").unbind('click').click(function (e) { - var row = $(e.target).closest("tr") - , plugin = row.find(".name").text(); + var $row = $(e.target).closest("tr") + , plugin = $row.find(".name").text(); + $row.remove() socket.emit("uninstall", plugin); progress.show('Uninstalling plugin '+plugin+'...') }); @@ -172,11 +173,21 @@ $(document).ready(function () { socket.on('finished:install', function(data) { if(data.error) alert('An error occured while installing the plugin. \n'+data.error) socket.emit("getInstalled"); + + // update search results + search.offset = 0; + search(search.searchTerm, search.results.length); + search.results = []; }) socket.on('finished:uninstall', function(data) { if(data.error) alert('An error occured while uninstalling the plugin. \n'+data.error) socket.emit("getInstalled"); + + // update search results + search.offset = 0; + search(search.searchTerm, search.results.length); + search.results = []; }) // init From 981a33f01ef1c1b66a03abdc971d9bd35669b412 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 14:40:19 +0100 Subject: [PATCH 060/104] pluginfw/installer.js fire callbacks only once --- src/static/js/pluginfw/installer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 9723ec671..377be35e9 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -23,7 +23,6 @@ exports.uninstall = function(plugin_name, cb) { hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { if (er) return cb(er); plugins.update(cb); - cb && cb(); hooks.aCallAll("restartServer", {}, function () {}); }); }); @@ -38,7 +37,6 @@ exports.install = function(plugin_name, cb) { hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { if (er) return cb(er); plugins.update(cb); - cb && cb(); hooks.aCallAll("restartServer", {}, function () {}); }); }); From 638cea5fd65cb0845cbb290bcaf6630ffea76a24 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 15:11:30 +0100 Subject: [PATCH 061/104] Install and uninstall plugins with style - Don't block the whole page when installing a plugin - allow people to search and install other plugins meanwhile Why? http://i.imgur.com/XoX6uYS.jpg --- src/node/hooks/express/adminplugins.js | 4 +- src/static/css/admin.css | 18 ++++-- src/static/js/admin/plugins.js | 77 ++++++++++++++++---------- src/templates/admin/plugins.html | 11 +++- 4 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 3ef341154..611a6b149 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -85,14 +85,14 @@ 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", {error: er? er.message : null}); + socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null}); }); }); socket.on("uninstall", function (plugin_name) { installer.uninstall(plugin_name, function (er) { if(er) console.warn(er) - socket.emit("finished:uninstall", {error: er? er.message : null}); + socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null}); }); }); }); diff --git a/src/static/css/admin.css b/src/static/css/admin.css index d1c86ac59..c65aafd0b 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -36,7 +36,6 @@ div.menu li:last-child { div.innerwrapper { padding: 15px; margin-left: 265px; - position:relative; /* Allows us to position the loading indicator relative to this div */ } #wrapper { @@ -137,23 +136,30 @@ td, th { display: none; } +#installed-plugins td>div { + position: relative;/* Allows us to position the loading indicator relative to this row */ + display: inline-block; /*make this fill the whole cell*/ +} + .progress { position: absolute; top: 0; left: 0; bottom:0; right:0; padding: auto; - padding-top: 20%; - background: rgba(255,255,255,0.85); + background: rgba(255,255,255,0.95); display: none; - /*border-radius: 7px; - border: 1px solid #ddd;*/ +} + +#search-progress.progress { + padding-top: 20%; + background: rgba(255,255,255,0.7); } .progress * { display: block; margin: 0 auto; text-align: center; - color: #999; + color: #666; } .settings { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 494776782..ed854b7d6 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -31,6 +31,20 @@ $(document).ready(function () { search.sortDir = /*DESC?*/true; search.end = true;// have we received all results already? + var installed = { + progress: { + show: function(plugin, msg) { + $('#installed-plugins .'+plugin+' .progress').show() + $('#installed-plugins .'+plugin+' .progress .message').text(msg) + }, + hide: function(plugin) { + $('#installed-plugins .'+plugin+' .progress').hide() + $('#installed-plugins .'+plugin+' .progress .message').text('') + } + }, + list: [] + } + function displayPluginList(plugins, container, template) { plugins.forEach(function(plugin) { var row = template.clone(); @@ -43,7 +57,7 @@ $(document).ready(function () { } } row.find(".version").html( plugin.version ); - + row.addClass(plugin.name) container.append(row); }) updateHandlers(); @@ -60,18 +74,6 @@ $(document).ready(function () { }) } - var progress = { - show: function(msg) { - $('#progress').show() - $('#progress .message').text(msg) - $(window).scrollTop(0) - }, - hide: function() { - $('#progress').hide() - $('#progress .message').text('') - } - } - function updateHandlers() { // Search $("#search-query").unbind('keyup').keyup(function () { @@ -82,17 +84,20 @@ $(document).ready(function () { $(".do-install, .do-update").unbind('click').click(function (e) { var $row = $(e.target).closest("tr") , plugin = $row.find(".name").text(); + $row.remove().appendTo('#installed-plugins') socket.emit("install", plugin); - progress.show('Installing plugin '+plugin+'...') + installed.progress.show(plugin, 'Installing') }); // uninstall $(".do-uninstall").unbind('click').click(function (e) { var $row = $(e.target).closest("tr") - , plugin = $row.find(".name").text(); - $row.remove() - socket.emit("uninstall", plugin); - progress.show('Uninstalling plugin '+plugin+'...') + , pluginName = $row.find(".name").text(); + socket.emit("uninstall", pluginName); + installed.progress.show(pluginName, 'Uninstalling') + installed.list = installed.list.filter(function(plugin) { + return plugin.name != pluginName + }) }); // Infinite scroll @@ -143,18 +148,19 @@ $(document).ready(function () { }); socket.on('results:installed', function (data) { - sortPluginList(data.installed, 'name', /*ASC?*/true); + installed.list = data.installed + sortPluginList(installed.list, 'name', /*ASC?*/true); - data.installed = data.installed.filter(function(plugin) { + // filter out epl + installed.list = installed.list.filter(function(plugin) { return plugin.name != 'ep_etherpad-lite' }) - $("#installed-plugins *").remove(); - displayPluginList(data.installed, $("#installed-plugins"), $("#installed-plugin-template")); - progress.hide() - setTimeout(function() { - socket.emit('checkUpdates'); - }, 5000) + // remove all installed plugins (leave all plugins that are still being installed) + installed.list.forEach(function(plugin) { + $('#installed-plugins .'+plugin.name).remove() + }) + displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); }); socket.on('results:updatable', function(data) { @@ -171,7 +177,11 @@ $(document).ready(function () { }) socket.on('finished:install', function(data) { - if(data.error) alert('An error occured while installing the plugin. \n'+data.error) + if(data.error) { + alert('An error occured while installing '+data.plugin+' \n'+data.error) + $('#installed-plugins .'+data.plugin).remove() + } + socket.emit("getInstalled"); // update search results @@ -181,9 +191,13 @@ $(document).ready(function () { }) socket.on('finished:uninstall', function(data) { - if(data.error) alert('An error occured while uninstalling the plugin. \n'+data.error) - socket.emit("getInstalled"); + if(data.error) alert('An error occured while uninstalling the '+data.plugin+' \n'+data.error) + + // remove plugin from installed list + $('#installed-plugins .'+data.plugin).remove() + socket.emit("getInstalled"); + // update search results search.offset = 0; search(search.searchTerm, search.results.length); @@ -194,4 +208,9 @@ $(document).ready(function () { updateHandlers(); socket.emit("getInstalled"); search(''); + + // check for updates every 15mins + setInterval(function() { + socket.emit('checkUpdates'); + }, 1000*60*15) }); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 1e7984321..e9c34917e 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -47,7 +47,10 @@ - +
    + +

    +
    @@ -78,7 +81,10 @@ - +
    + +

    +
    @@ -91,7 +97,6 @@ -

    From 7edfff75743a876d94411e9a1313a8f19777502c Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 16:23:47 +0100 Subject: [PATCH 062/104] /admin/plugins: Show some text if nothing is display otherwise --- src/static/css/admin.css | 9 +++++++++ src/static/js/admin/plugins.js | 21 ++++++++++++++++++--- src/templates/admin/plugins.html | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/static/css/admin.css b/src/static/css/admin.css index c65aafd0b..7a2e00f4a 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -141,6 +141,15 @@ td, th { display: inline-block; /*make this fill the whole cell*/ } +.messages * { + text-align: center; + display: none; +} + +.messages .fetching { + display: block; +} + .progress { position: absolute; top: 0; left: 0; bottom:0; right:0; diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index ed854b7d6..f640ed83a 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -87,6 +87,7 @@ $(document).ready(function () { $row.remove().appendTo('#installed-plugins') socket.emit("install", plugin); installed.progress.show(plugin, 'Installing') + $(".installed-results .nothing-installed").hide() }); // uninstall @@ -126,6 +127,8 @@ $(document).ready(function () { socket.on('results:search', function (data) { if(!data.results.length) search.end = true; + $(".search-results .nothing-found").hide() + $(".search-results .fetching").hide() console.log('got search results', data) @@ -143,11 +146,18 @@ $(document).ready(function () { // re-render search results var searchWidget = $(".search-results"); searchWidget.find(".results *").remove(); - displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) + if(search.results.length > 0) { + displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) + }else { + $(".search-results .nothing-found").show() + } $('#search-progress').hide() }); socket.on('results:installed', function (data) { + $(".installed-results .nothing-installed").hide() + $(".installed-results .fetching").hide() + installed.list = data.installed sortPluginList(installed.list, 'name', /*ASC?*/true); @@ -156,11 +166,16 @@ $(document).ready(function () { return plugin.name != 'ep_etherpad-lite' }) - // remove all installed plugins (leave all plugins that are still being installed) + // remove all installed plugins (leave plugins that are still being installed) installed.list.forEach(function(plugin) { $('#installed-plugins .'+plugin.name).remove() }) - displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); + + if(installed.list.length > 0) { + displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); + }else { + $(".installed-results .nothing-installed").show() + } }); socket.on('results:updatable', function(data) { diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index e9c34917e..8ca24644f 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -32,7 +32,7 @@

    Installed plugins

    - +
    @@ -56,6 +56,12 @@ + + +
    Name
    +

    You haven't installed any plugins yet.

    +

    Fetching installed plugins...

    +
    @@ -91,9 +97,11 @@ - -
    - + +
    +

    No plugins found.

    +

    Fetching catalogue...

    +
    From e96baf6ef1fde0e47e0a4afaf32078dc39e441ff Mon Sep 17 00:00:00 2001 From: neurolit Date: Tue, 26 Mar 2013 16:37:57 +0100 Subject: [PATCH 063/104] API documentation: listAllPads() returns {padIDs: [...]} instead of [...] --- 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 7e05ff86d..f71e0a5d0 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -458,4 +458,4 @@ returns ok when the current api token is valid lists all pads on this epl instance *Example returns:* - * `{code: 0, message:"ok", data: ["testPad", "thePadsOfTheOthers"]}` + * `{code: 0, message:"ok", data: {padIDs: ["testPad", "thePadsOfTheOthers"]}}` From f75a839cd093480684f2a0790b67dd9bd7324197 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 18:39:46 +0100 Subject: [PATCH 064/104] Remove plugin prefix in pluin lists and make links to plugins more clear --- src/static/css/admin.css | 22 ++++++++++++++++++++-- src/static/js/admin/plugins.js | 7 ++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 7a2e00f4a..560191291 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -155,7 +155,7 @@ td, th { top: 0; left: 0; bottom:0; right:0; padding: auto; - background: rgba(255,255,255,0.95); + background: rgb(255,255,255); display: none; } @@ -187,7 +187,25 @@ a:link, a:visited, a:hover, a:focus { } a:focus, a:hover { - border-bottom: #333333 1px solid; + text-decoration: underline; +} + +.installed-results a:link, +.search-results a:link, +.installed-results a:visited, +.search-results a:visited, +.installed-results a:hover, +.search-results a:hover, +.installed-results a:focus, +.search-results a:focus { + text-decoration: underline; +} + +.installed-results a:focus, +.search-results a:focus, +.installed-results a:hover, +.search-results a:hover { + text-decoration: none; } pre { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index f640ed83a..21edee6ba 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -51,13 +51,14 @@ $(document).ready(function () { for (attr in plugin) { if(attr == "name"){ // Hack to rewrite URLS into name - row.find(".name").html(""+plugin['name']+""); + row.find(".name").html(""+plugin['name'].substr(3)+""); // remove 'ep_' }else{ row.find("." + attr).html(plugin[attr]); } } row.find(".version").html( plugin.version ); row.addClass(plugin.name) + row.data('plugin', plugin.name) container.append(row); }) updateHandlers(); @@ -83,7 +84,7 @@ $(document).ready(function () { // update & install $(".do-install, .do-update").unbind('click').click(function (e) { var $row = $(e.target).closest("tr") - , plugin = $row.find(".name").text(); + , plugin = $row.data('plugin'); $row.remove().appendTo('#installed-plugins') socket.emit("install", plugin); installed.progress.show(plugin, 'Installing') @@ -93,7 +94,7 @@ $(document).ready(function () { // uninstall $(".do-uninstall").unbind('click').click(function (e) { var $row = $(e.target).closest("tr") - , pluginName = $row.find(".name").text(); + , pluginName = $row.data('plugin'); socket.emit("uninstall", pluginName); installed.progress.show(pluginName, 'Uninstalling') installed.list = installed.list.filter(function(plugin) { From 2393dcd65259e232006ad5c956f19eeee60370e7 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 19:22:04 +0100 Subject: [PATCH 065/104] Disable search until registry is loaded and fix sorting by version ... and always display a scrollbar. --- src/static/css/admin.css | 2 +- src/static/js/admin/plugins.js | 1 + src/templates/admin/plugins.html | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 560191291..3108655e4 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -43,7 +43,7 @@ div.innerwrapper { box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2); margin: auto; max-width: 1150px; - min-height: 100%; + min-height: 101%;/*always display a scrollbar*/ } h1 { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 21edee6ba..53e16ff30 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -130,6 +130,7 @@ $(document).ready(function () { if(!data.results.length) search.end = true; $(".search-results .nothing-found").hide() $(".search-results .fetching").hide() + $("#search-query").removeAttr('disabled') console.log('got search results', data) diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 8ca24644f..fe1f607aa 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -69,7 +69,7 @@

    Available plugins

    - +
    @@ -77,7 +77,7 @@ - + From 4edb3b7ab3a7f2aeb24d510dd103b079d67c31a1 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 19:32:15 +0100 Subject: [PATCH 066/104] /admin/plugins: Fix infinite scroll for larger screens --- src/static/js/admin/plugins.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 53e16ff30..440b5879f 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -75,6 +75,16 @@ $(document).ready(function () { }) } + // Infinite scroll + $(window).scroll(checkInfiniteScroll) + function checkInfiniteScroll() { + if(search.end) return;// don't keep requesting if there are no more results + try{ + var top = $('.search-results .results > tr:last').offset().top + if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm) + }catch(e){} + } + function updateHandlers() { // Search $("#search-query").unbind('keyup').keyup(function () { @@ -102,13 +112,6 @@ $(document).ready(function () { }) }); - // Infinite scroll - $(window).unbind('scroll').scroll(function() { - if(search.end) return;// don't keep requesting if there are no more results - var top = $('.search-results .results > tr:last').offset().top - if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm) - }) - // Sort $('.sort.up').unbind('click').click(function() { search.sortBy = $(this).text().toLowerCase(); @@ -154,6 +157,7 @@ $(document).ready(function () { $(".search-results .nothing-found").show() } $('#search-progress').hide() + checkInfiniteScroll() }); socket.on('results:installed', function (data) { From 806926d0f6013369096577e7ad9389e0579a8b98 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 19:54:23 +0100 Subject: [PATCH 067/104] /admin/plugins: If a user installs sth scroll up to the loading indicator --- src/static/js/admin/plugins.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 440b5879f..2ffb55b0d 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -36,6 +36,7 @@ $(document).ready(function () { show: function(plugin, msg) { $('#installed-plugins .'+plugin+' .progress').show() $('#installed-plugins .'+plugin+' .progress .message').text(msg) + $(window).scrollTop(0) }, hide: function(plugin) { $('#installed-plugins .'+plugin+' .progress').hide() From 76c879bb4705212747b9d2e0d546a5db6d97bc68 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 20:41:24 +0100 Subject: [PATCH 068/104] /admin/plugins: Fix for smaller screens --- src/static/css/admin.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 3108655e4..322fe41c3 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -35,7 +35,7 @@ div.menu li:last-child { div.innerwrapper { padding: 15px; - margin-left: 265px; + padding-left: 265px; } #wrapper { From d01a209cbf3377bc9c03719b00f2a3adbbb5b386 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 21:04:21 +0100 Subject: [PATCH 069/104] /admin/plugins: Dry up displaying of info messages --- src/static/css/admin.css | 4 +-- src/static/js/admin/plugins.js | 42 +++++++++++++++++++++++--------- src/templates/admin/plugins.html | 8 +++--- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 322fe41c3..31b16b9c6 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -141,9 +141,9 @@ td, th { display: inline-block; /*make this fill the whole cell*/ } -.messages * { - text-align: center; +.messages td>* { display: none; + text-align: center; } .messages .fetching { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 2ffb55b0d..3937e6665 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -30,17 +30,37 @@ $(document).ready(function () { search.sortBy = 'name'; search.sortDir = /*DESC?*/true; search.end = true;// have we received all results already? + search.messages = { + show: function(msg) { + $('.search-results .messages').show() + $('.search-results .messages .'+msg+'').show() + }, + hide: function(msg) { + $('.search-results .messages').hide() + $('.search-results .messages .'+msg+'').hide() + } + } var installed = { progress: { show: function(plugin, msg) { - $('#installed-plugins .'+plugin+' .progress').show() - $('#installed-plugins .'+plugin+' .progress .message').text(msg) + $('.installed-results .'+plugin+' .progress').show() + $('.installed-results .'+plugin+' .progress .message').text(msg) $(window).scrollTop(0) }, hide: function(plugin) { - $('#installed-plugins .'+plugin+' .progress').hide() - $('#installed-plugins .'+plugin+' .progress .message').text('') + $('.installed-results .'+plugin+' .progress').hide() + $('.installed-results .'+plugin+' .progress .message').text('') + } + }, + messages: { + show: function(msg) { + $('.installed-results .messages').show() + $('.installed-results .messages .'+msg+'').show() + }, + hide: function(msg) { + $('.installed-results .messages').hide() + $('.installed-results .messages .'+msg+'').hide() } }, list: [] @@ -99,7 +119,7 @@ $(document).ready(function () { $row.remove().appendTo('#installed-plugins') socket.emit("install", plugin); installed.progress.show(plugin, 'Installing') - $(".installed-results .nothing-installed").hide() + installed.messages.hide("nothing-installed") }); // uninstall @@ -132,8 +152,8 @@ $(document).ready(function () { socket.on('results:search', function (data) { if(!data.results.length) search.end = true; - $(".search-results .nothing-found").hide() - $(".search-results .fetching").hide() + search.messages.hide('nothing-found') + search.messages.hide('fetching') $("#search-query").removeAttr('disabled') console.log('got search results', data) @@ -155,15 +175,15 @@ $(document).ready(function () { if(search.results.length > 0) { displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) }else { - $(".search-results .nothing-found").show() + search.messages.show('nothing-found') } $('#search-progress').hide() checkInfiniteScroll() }); socket.on('results:installed', function (data) { - $(".installed-results .nothing-installed").hide() - $(".installed-results .fetching").hide() + installed.messages.hide("fetching") + installed.messages.hide("nothing-installed") installed.list = data.installed sortPluginList(installed.list, 'name', /*ASC?*/true); @@ -181,7 +201,7 @@ $(document).ready(function () { if(installed.list.length > 0) { displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); }else { - $(".installed-results .nothing-installed").show() + installed.messages.show("nothing-installed") } }); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index fe1f607aa..270fbfd7a 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -56,8 +56,8 @@ - - + @@ -96,8 +96,8 @@ - - + - -
    Name DescriptionVersionVersion
    +

    You haven't installed any plugins yet.

    Fetching installed plugins...

    +

    No plugins found.

    Fetching catalogue...

    From ac0018cdfafa8b8b5de594ff9127c190d20f3f63 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 26 Mar 2013 21:19:09 +0100 Subject: [PATCH 070/104] Don't leak event listeners for process:uncaughtException --- src/node/hooks/express/errorhandling.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 3c5956835..825c5f3b4 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -28,6 +28,7 @@ exports.gracefulShutdown = function(err) { }, 3000); } +process.on('uncaughtException', exports.gracefulShutdown); exports.expressCreateServer = function (hook_name, args, cb) { exports.app = args.app; @@ -47,6 +48,4 @@ exports.expressCreateServer = function (hook_name, args, cb) { //https://github.com/joyent/node/issues/1553 process.on('SIGINT', exports.gracefulShutdown); } - - process.on('uncaughtException', exports.gracefulShutdown); -} +} \ No newline at end of file From c4d9a71156c39d84350230e88ef9ebab148f1a2a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 27 Mar 2013 12:02:19 +0100 Subject: [PATCH 071/104] /admin/plugins: Fix update check --- src/node/hooks/express/adminplugins.js | 4 ++-- src/static/css/admin.css | 1 + src/static/js/admin/plugins.js | 27 +++++++++++++------------- src/templates/admin/plugins.html | 8 ++++---- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 611a6b149..d8f19bba9 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -35,9 +35,9 @@ exports.socketio = function (hook_name, args, cb) { socket.emit("results:installed", {installed: installed}); }); - socket.on("getUpdatable", function() { + socket.on("checkUpdates", function() { // Check plugins for updates - installer.getAvailable(/*maxCacheAge:*/60*10, function(er, results) { + installer.getAvailablePlugins(/*maxCacheAge:*/60*10, function(er, results) { if(er) { console.warn(er); socket.emit("results:updatable", {updatable: {}}); diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 31b16b9c6..2260e27df 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -139,6 +139,7 @@ td, th { #installed-plugins td>div { position: relative;/* Allows us to position the loading indicator relative to this row */ display: inline-block; /*make this fill the whole cell*/ + width:100%; } .messages td>* { diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 3937e6665..bc7a02082 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -46,7 +46,7 @@ $(document).ready(function () { show: function(plugin, msg) { $('.installed-results .'+plugin+' .progress').show() $('.installed-results .'+plugin+' .progress .message').text(msg) - $(window).scrollTop(0) + $(window).scrollTop($('.'+plugin).offset().top-100) }, hide: function(plugin) { $('.installed-results .'+plugin+' .progress').hide() @@ -116,9 +116,13 @@ $(document).ready(function () { $(".do-install, .do-update").unbind('click').click(function (e) { var $row = $(e.target).closest("tr") , plugin = $row.data('plugin'); - $row.remove().appendTo('#installed-plugins') + if($(this).hasClass('do-install')) { + $row.remove().appendTo('#installed-plugins') + installed.progress.show(plugin, 'Installing') + }else{ + installed.progress.show(plugin, 'Updating') + } socket.emit("install", plugin); - installed.progress.show(plugin, 'Installing') installed.messages.hide("nothing-installed") }); @@ -200,20 +204,17 @@ $(document).ready(function () { if(installed.list.length > 0) { displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); + socket.emit('checkUpdates'); }else { installed.messages.show("nothing-installed") } }); socket.on('results:updatable', function(data) { - $('#installed-plugins > tr').each(function(i, tr) { - var pluginName = $(tr).find('.name').text() - - if (data.updatable.indexOf(pluginName) >= 0) { - var actions = $(tr).find('.actions') - actions.append('') - actions.css('width', 200) - } + data.updatable.forEach(function(pluginName) { + var $row = $('#installed-plugins > tr.'+pluginName) + , actions = $row.find('.actions') + actions.append('') }) updateHandlers(); }) @@ -251,8 +252,8 @@ $(document).ready(function () { socket.emit("getInstalled"); search(''); - // check for updates every 15mins + // check for updates every 5mins setInterval(function() { socket.emit('checkUpdates'); - }, 1000*60*15) + }, 1000*60*5) }); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 270fbfd7a..1b69549c4 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -46,8 +46,8 @@
    -
    +
    +

    @@ -86,8 +86,8 @@
    -
    +
    +

    From bc8d6d4c45f443716d3334e1606cf7664b9a97ac Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 27 Mar 2013 12:20:50 +0100 Subject: [PATCH 072/104] /admin/plugins: Add a loading indicator to some messages --- src/static/js/admin/plugins.js | 2 +- src/templates/admin/plugins.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index bc7a02082..e2e07e967 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -46,7 +46,7 @@ $(document).ready(function () { show: function(plugin, msg) { $('.installed-results .'+plugin+' .progress').show() $('.installed-results .'+plugin+' .progress .message').text(msg) - $(window).scrollTop($('.'+plugin).offset().top-100) + if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100) }, hide: function(plugin) { $('.installed-results .'+plugin+' .progress').hide() diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 1b69549c4..44e6f7a51 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -59,7 +59,7 @@

    You haven't installed any plugins yet.

    -

    Fetching installed plugins...

    +


    Fetching installed plugins...

    @@ -100,7 +100,7 @@

    No plugins found.

    -

    Fetching catalogue...

    +


    Fetching catalogue...

    From cbee50d42d8a528c524ad5a76a54bf59e8978689 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 27 Mar 2013 12:28:54 +0100 Subject: [PATCH 073/104] /admin/plugins: Display a tooltip when hovering the plugin details link --- src/static/js/admin/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index e2e07e967..41affa745 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -72,7 +72,7 @@ $(document).ready(function () { for (attr in plugin) { if(attr == "name"){ // Hack to rewrite URLS into name - row.find(".name").html(""+plugin['name'].substr(3)+""); // remove 'ep_' + row.find(".name").html(""+plugin['name'].substr(3)+""); // remove 'ep_' }else{ row.find("." + attr).html(plugin[attr]); } From 40cbe55507fdf9eb45e092d3f9321de4f72dce22 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 27 Mar 2013 14:11:20 +0000 Subject: [PATCH 074/104] Update en.json --- src/locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 920a2b003..d08ebe657 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -16,7 +16,7 @@ "pad.toolbar.timeslider.title": "Timeslider", "pad.toolbar.savedRevision.title": "Save Revision", "pad.toolbar.settings.title": "Settings", - "pad.toolbar.embed.title": "Embed this pad", + "pad.toolbar.embed.title": "Share and Embed this pad", "pad.toolbar.showusers.title": "Show the users on this pad", "pad.colorpicker.save": "Save", "pad.colorpicker.cancel": "Cancel", @@ -113,4 +113,4 @@ "pad.impexp.importfailed": "Import failed", "pad.impexp.copypaste": "Please copy paste", "pad.impexp.exportdisabled": "Exporting as {{type}} format is disabled. Please contact your system administrator for details." -} \ No newline at end of file +} From c48917720699fb1e4b304a464978f016f1967531 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 28 Mar 2013 02:24:59 +0000 Subject: [PATCH 075/104] show light yellow for .5 secs on save revision keypress --- src/static/js/ace2_inner.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index fc69d5921..e00d2ee72 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3649,6 +3649,11 @@ function Ace2Inner(){ if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey)) /* Do a saved revision on ctrl S */ { evt.preventDefault(); + var originalBackground = parent.parent.$('#revisionlink').css("background") + parent.parent.$('#revisionlink').css({"background":"lightyellow"}); + setTimeout(function(){ + parent.parent.$('#revisionlink').css({"background":originalBackground}); + }, 500); parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ specialHandled = true; } From 59a9ff404d1be02ccb051eabdd487117e789a04b Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 28 Mar 2013 13:18:55 +0000 Subject: [PATCH 076/104] more settimeout to top window --- src/static/js/ace2_inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index e00d2ee72..1ab026efb 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3651,7 +3651,7 @@ function Ace2Inner(){ evt.preventDefault(); var originalBackground = parent.parent.$('#revisionlink').css("background") parent.parent.$('#revisionlink').css({"background":"lightyellow"}); - setTimeout(function(){ + top.setTimeout(function(){ parent.parent.$('#revisionlink').css({"background":originalBackground}); }, 500); parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ From 0ff5137da3b1b6396e1b489cee90525664b9188d Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 28 Mar 2013 16:39:33 +0100 Subject: [PATCH 077/104] Make revision button glow on ctrl-s and increase duration --- src/static/js/ace2_inner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 1ab026efb..cbae0be90 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3650,10 +3650,10 @@ function Ace2Inner(){ { evt.preventDefault(); var originalBackground = parent.parent.$('#revisionlink').css("background") - parent.parent.$('#revisionlink').css({"background":"lightyellow"}); + parent.parent.$('#revisionlink').css({"background":"lightyellow", 'box-shadow': '0 0 50px yellow'}); top.setTimeout(function(){ - parent.parent.$('#revisionlink').css({"background":originalBackground}); - }, 500); + parent.parent.$('#revisionlink').css({"background":originalBackground, 'box-shadow': 'none'}); + }, 1000); parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ specialHandled = true; } From d73ea4e334ed3345ceaf110db4b1d71737d7db8e Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 29 Mar 2013 02:24:15 +0000 Subject: [PATCH 078/104] Loading blocks --- src/templates/pad.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/templates/pad.html b/src/templates/pad.html index 1d8c069a5..ef621545b 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -195,7 +195,9 @@

    Your password was wrong

    + <% e.begin_block("loading"); %>

    Loading...

    + <% e.end_block(); %>
    From c67c7ca746fda220b15d05d913a2b74a00332377 Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 29 Mar 2013 03:09:10 +0000 Subject: [PATCH 079/104] remove messy bits --- src/static/js/ace2_inner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index cbae0be90..46b308ce1 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3650,9 +3650,9 @@ function Ace2Inner(){ { evt.preventDefault(); var originalBackground = parent.parent.$('#revisionlink').css("background") - parent.parent.$('#revisionlink').css({"background":"lightyellow", 'box-shadow': '0 0 50px yellow'}); + parent.parent.$('#revisionlink').css({"background":"lightyellow"}); top.setTimeout(function(){ - parent.parent.$('#revisionlink').css({"background":originalBackground, 'box-shadow': 'none'}); + parent.parent.$('#revisionlink').css({"background":originalBackground}); }, 1000); parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ specialHandled = true; From 358b07390e28338ddf6fa6e077fdbeb117431c99 Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Sat, 30 Mar 2013 15:42:10 +0100 Subject: [PATCH 080/104] fix "util.pump() is deprecated. Use readableStream.pipe()" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix error introduced by b3988e3  --- src/node/utils/caching_middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index a970bfc95..ab3d8edde 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -168,7 +168,7 @@ CachingMiddleware.prototype = new function () { } else if (req.method == 'GET') { var readStream = fs.createReadStream(pathStr); res.writeHead(statusCode, headers); - readableStream.pipe(readStream, res); + readStream.pipe(res); } else { res.writeHead(statusCode, headers); res.end(); From 253a8e37fd6f7a81bd0e7659919890a36dad3f23 Mon Sep 17 00:00:00 2001 From: mluto Date: Sat, 30 Mar 2013 20:28:46 +0100 Subject: [PATCH 081/104] Added debug-output to SecurityManager.checkAccess to indicate *why* an auth-try failed. --- src/node/db/SecurityManager.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 4289e39ca..074728b58 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -134,12 +134,14 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //is it for this group? if(sessionInfo.groupID != groupID) { + console.debug("Auth failed: wrong group"); callback(); return; } //is validUntil still ok? if(sessionInfo.validUntil <= now){ + console.debug("Auth failed: validUntil"); callback(); return; } @@ -234,7 +236,11 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //--> grant access statusObject = {accessStatus: "grant", authorID: sessionAuthor}; //--> deny access if user isn't allowed to create the pad - if(settings.editOnly) statusObject.accessStatus = "deny"; + if(settings.editOnly) + { + console.debug("Auth failed: valid session & pad does not exist"); + statusObject.accessStatus = "deny"; + } } // there is no valid session avaiable AND pad exists else if(!validSession && padExists) @@ -266,6 +272,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //- its not public else if(!isPublic) { + console.debug("Auth failed: invalid session & pad is not public"); //--> deny access statusObject = {accessStatus: "deny"}; } @@ -277,6 +284,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // there is no valid session avaiable AND pad doesn't exists else { + console.debug("Auth failed: invalid session & pad does not exist"); //--> deny access statusObject = {accessStatus: "deny"}; } From 7e3b288afc53e8908c0550c9d0944af6183c161a Mon Sep 17 00:00:00 2001 From: mluto Date: Sat, 30 Mar 2013 20:46:56 +0100 Subject: [PATCH 082/104] log things the log4js-way; put all the brackets on a new line --- src/node/db/SecurityManager.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 074728b58..d3a98446d 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -27,6 +27,8 @@ var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); var settings = require("../utils/Settings"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +var log4js = require('log4js'); +var authLogger = log4js.getLogger("auth"); /** * This function controlls the access to a pad, it checks if the user can access a pad. @@ -117,14 +119,17 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //get information about all sessions contained in this cookie function(callback) { - if (!sessionCookie) { + if (!sessionCookie) + { callback(); return; } var sessionIDs = sessionCookie.split(','); - async.forEach(sessionIDs, function(sessionID, callback) { - sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { + async.forEach(sessionIDs, function(sessionID, callback) + { + sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) + { //skip session if it doesn't exist if(err && err.message == "sessionID does not exist") return; @@ -133,15 +138,17 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) var now = Math.floor(new Date().getTime()/1000); //is it for this group? - if(sessionInfo.groupID != groupID) { - console.debug("Auth failed: wrong group"); + if(sessionInfo.groupID != groupID) + { + authLogger.debug("Auth failed: wrong group"); callback(); return; } //is validUntil still ok? - if(sessionInfo.validUntil <= now){ - console.debug("Auth failed: validUntil"); + if(sessionInfo.validUntil <= now) + { + authLogger.debug("Auth failed: validUntil"); callback(); return; } @@ -238,7 +245,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //--> deny access if user isn't allowed to create the pad if(settings.editOnly) { - console.debug("Auth failed: valid session & pad does not exist"); + authLogger.debug("Auth failed: valid session & pad does not exist"); statusObject.accessStatus = "deny"; } } @@ -272,7 +279,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //- its not public else if(!isPublic) { - console.debug("Auth failed: invalid session & pad is not public"); + authLogger.debug("Auth failed: invalid session & pad is not public"); //--> deny access statusObject = {accessStatus: "deny"}; } @@ -284,7 +291,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // there is no valid session avaiable AND pad doesn't exists else { - console.debug("Auth failed: invalid session & pad does not exist"); + authLogger.debug("Auth failed: invalid session & pad does not exist"); //--> deny access statusObject = {accessStatus: "deny"}; } From 30cae9097f2979048a2491b2ec032aa9548121a2 Mon Sep 17 00:00:00 2001 From: mluto Date: Sun, 31 Mar 2013 10:27:21 +0200 Subject: [PATCH 083/104] When there is just one session and this one is invalid SecurityManager.checkAccess would cause the request to hang forever because the callback is omitted for each invalid session, this fixes this issue. validSession still remains false so this does not cause issues further down. --- src/node/db/SecurityManager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index d3a98446d..410204e01 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -131,7 +131,11 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { //skip session if it doesn't exist - if(err && err.message == "sessionID does not exist") return; + if(err && err.message == "sessionID does not exist") + { + callback(); + return; + } if(ERR(err, callback)) return; From 911bfb30e4d1f11ea569cc0d922085821dd23158 Mon Sep 17 00:00:00 2001 From: mluto Date: Sun, 31 Mar 2013 10:56:14 +0200 Subject: [PATCH 084/104] Log when a sessionID in checkAccess is not found --- src/node/db/SecurityManager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 410204e01..06e58bc4b 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -133,6 +133,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) //skip session if it doesn't exist if(err && err.message == "sessionID does not exist") { + authLogger.debug("Auth failed: unknown session"); callback(); return; } From 1793e93ea1b831329b580c7a5aa4fb982c665417 Mon Sep 17 00:00:00 2001 From: mluto Date: Sun, 31 Mar 2013 11:30:01 +0200 Subject: [PATCH 085/104] Decode the sessionID before sending it to the server since our separator ',' gets encoded --- src/static/js/pad.js | 2 +- src/static/js/timeslider.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index e5e6e95f3..5cc02d87e 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -191,7 +191,7 @@ function handshake() createCookie("token", token, 60); } - var sessionID = readCookie("sessionID"); + var sessionID = decodeURIComponent(readCookie("sessionID")); var password = readCookie("password"); var msg = { diff --git a/src/static/js/timeslider.js b/src/static/js/timeslider.js index eb3703d9f..cd98deadd 100644 --- a/src/static/js/timeslider.js +++ b/src/static/js/timeslider.js @@ -116,7 +116,7 @@ function init() { //sends a message over the socket function sendSocketMsg(type, data) { - var sessionID = readCookie("sessionID"); + var sessionID = decodeURIComponent(readCookie("sessionID")); var password = readCookie("password"); var msg = { "component" : "pad", // FIXME: Remove this stupidity! From 98f00e293c66ff03e980dbb3b1767199f535df63 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 1 Apr 2013 13:24:22 +0200 Subject: [PATCH 086/104] Update ueberDB to add support for node 0.10.x --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index a7147cf2a..7d30cc49b 100644 --- a/src/package.json +++ b/src/package.json @@ -16,7 +16,7 @@ "require-kernel" : "1.0.5", "resolve" : "0.2.x", "socket.io" : "0.9.x", - "ueberDB" : "0.1.94", + "ueberDB" : "0.2.x", "async" : "0.1.x", "express" : "3.x", "connect" : "2.4.x", From 782c512e930cc4db2ed05a8c1de69ca5fe671088 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 1 Apr 2013 14:07:38 +0200 Subject: [PATCH 087/104] Drop support for node v0.6, officially --- README.md | 2 ++ bin/installDeps.sh | 4 ++-- bin/installOnWindows.bat | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 574740730..291ecaeeb 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Also, check out the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**, # Installation +Etherpad works with node v0.8 and v0.10, only. (We don't suppot v0.6) + ## Windows ### Prebuilt windows package diff --git a/bin/installDeps.sh b/bin/installDeps.sh index fc3133c16..161cabcc9 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -44,8 +44,8 @@ fi #check node version NODE_VERSION=$(node --version) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) -if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ] && [ ! $NODE_V_MINOR = "v0.10" ]; then - echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x, v0.8.x or v0.10.x" >&2 +if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ]; then + echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.8.x or v0.10.x" >&2 exit 1 fi diff --git a/bin/installOnWindows.bat b/bin/installOnWindows.bat index f74529829..f56c79268 100644 --- a/bin/installOnWindows.bat +++ b/bin/installOnWindows.bat @@ -8,7 +8,7 @@ cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && ex echo _ echo Checking node version... -set check_version="if(['6','8','10'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.6.x, v0.8.x or v0.10.x'); process.exit(1) }" +set check_version="if(['8','10'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.8.x or v0.10.x'); process.exit(1) }" cmd /C node -e %check_version% || exit /B 1 echo _ From 56275d8de72f3ecee306c11a3b9cbe2983e952f3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Apr 2013 12:20:38 -0700 Subject: [PATCH 088/104] longer timeout on reconnection --- src/static/js/pad.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 5cc02d87e..01f1bbcb2 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -242,7 +242,7 @@ function handshake() pad.collabClient.setChannelState("RECONNECTING"); - disconnectTimeout = setTimeout(disconnectEvent, 10000); + disconnectTimeout = setTimeout(disconnectEvent, 20000); } }); From 9e523191ea40137899c0082ffff66056ec289e52 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Apr 2013 23:15:16 +0100 Subject: [PATCH 089/104] whoops padid should be in payload.. --- src/node/handler/PadMessageHandler.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 954c116d2..b6e58764b 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -151,19 +151,25 @@ exports.handleMessage = function(client, message) var handleMessageHook = function(callback){ var dropMessage = false; - + console.warn("messsssage", message); // Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages // handleMessage will be called, even if the client is not authorized hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) { +console.warn("Wut", message); if(ERR(err, callback)) return; _.each(messages, function(newMessage){ +console.warn("OH NOES!", message); +console.warn("newMessage", newMessage); if ( newMessage === null ) { +console.warn("FUCK NO!"); dropMessage = true; } }); // If no plugins explicitly told us to drop the message, its ok to proceed - if(!dropMessage){ callback() }; + if(!dropMessage){ +console.warn("proceeding"); + callback() }; }); } @@ -265,7 +271,7 @@ exports.handleCustomObjectMessage = function (msg, sessionID, cb) { if(sessionID){ // If a sessionID is targeted then send directly to this sessionID socketio.sockets.socket(sessionID).json.send(msg); // send a targeted message }else{ - socketio.sockets.in(msg.data.padId).json.send(msg); // broadcast to all clients on this pad + socketio.sockets.in(msg.data.payload.padId).json.send(msg); // broadcast to all clients on this pad } } cb(null, {}); From 57a9ccbb881a487caa92d41431403f274efe1ed8 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Apr 2013 23:16:28 +0100 Subject: [PATCH 090/104] whoops, comments hurt --- src/node/handler/PadMessageHandler.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index b6e58764b..0b0ea3697 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -151,24 +151,18 @@ exports.handleMessage = function(client, message) var handleMessageHook = function(callback){ var dropMessage = false; - console.warn("messsssage", message); // Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages // handleMessage will be called, even if the client is not authorized hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) { -console.warn("Wut", message); if(ERR(err, callback)) return; _.each(messages, function(newMessage){ -console.warn("OH NOES!", message); -console.warn("newMessage", newMessage); if ( newMessage === null ) { -console.warn("FUCK NO!"); dropMessage = true; } }); // If no plugins explicitly told us to drop the message, its ok to proceed if(!dropMessage){ -console.warn("proceeding"); callback() }; }); } From 5855e3d5bfffa9aed9747cc54fa5922a32737be9 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Apr 2013 23:17:25 +0100 Subject: [PATCH 091/104] weird styling --- src/node/handler/PadMessageHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 0b0ea3697..9d0fd780f 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -162,8 +162,7 @@ exports.handleMessage = function(client, message) }); // If no plugins explicitly told us to drop the message, its ok to proceed - if(!dropMessage){ - callback() }; + if(!dropMessage){ callback() }; }); } From c5b4e4934dab4a90fd673bb4087575a885e02c94 Mon Sep 17 00:00:00 2001 From: mluto Date: Wed, 3 Apr 2013 11:19:26 +0200 Subject: [PATCH 092/104] Kick the user if has already successfully authenticated but his session became invalid later --- src/node/handler/PadMessageHandler.js | 26 +++++++++++++++++++------- src/static/js/pad.js | 12 ++++++++++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 9d0fd780f..85efb0083 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -203,17 +203,29 @@ exports.handleMessage = function(client, message) //check permissions function(callback) { - - // If the message has a padId we assume the client is already known to the server and needs no re-authorization - if(!message.padId) - return callback(); + // 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 + }; + } // Note: message.sessionID is an entirely different kind of - // session from the sessions we use here! Beware! FIXME: Call - // our "sessions" "connections". + // session from the sessions we use here! Beware! + // FIXME: Call our "sessions" "connections". // FIXME: Use a hook instead // FIXME: Allow to override readwrite access with readonly - securityManager.checkAccess(message.padId, message.sessionID, message.token, message.password, function(err, statusObject) + var auth = sessioninfos[client.id].auth; + securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, function(err, statusObject) { if(ERR(err, callback)) return; diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 01f1bbcb2..504bc21e4 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -252,14 +252,22 @@ function handshake() socket.on('message', function(obj) { //the access was not granted, give the user a message - if(!receivedClientVars && obj.accessStatus) + if(obj.accessStatus) { - $('.passForm').submit(require(module.id).savePassword); + if(!receivedClientVars) + $('.passForm').submit(require(module.id).savePassword); if(obj.accessStatus == "deny") { $('#loading').hide(); $("#permissionDenied").show(); + + if(receivedClientVars) + { + // got kicked + $("#editorcontainer").hide(); + $("#editorloadingbox").show(); + } } else if(obj.accessStatus == "needPassword") { From 35d84144dbdc4986ed5b6aa6e2e27a47f5295828 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 00:59:51 +0100 Subject: [PATCH 093/104] changelog and package file --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca5b078fa..15ce96841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,17 @@ * NEW: Authors can now send custom object messages to other Authors making 3 way conversations possible. This introduces WebRTC plugin support. * NEW: Hook for Chat Messages Allows for Desktop Notification support * NEW: FreeBSD installation docs + * NEW: Ctrl S for save revision makes the Icon glow for a few sconds. + * NEW: Various hooks and expose the document ACE object + * NEW: Plugin page revamp makes finding and installing plugins more sane. + * NEW: Icon to enable sticky chat from the Chat box * Fix: Cookies inside of plugins + * Fix: Don't leak event emitters when accessing admin/plugins + * Fix: Don't allow user to send messages after they have been "kicked" from a pad * Fix: Refactor Caret navigation with Arrow and Pageup/down keys stops cursor being lost * Fix: Long lines in Firefox now wrap properly + * Fix: Session Disconnect limit is increased from 10 to 20 to support slower restarts + * Fix: Support Node 0.10 * Fix: Log HTTP on DEBUG log level * Fix: Server wont crash on import fails on 0 file import. * Fix: Import no longer fails consistantly @@ -12,6 +20,7 @@ * Fix: Mobile support for chat notifications are now usable * Fix: Re-Enable Editbar buttons on reconnect * Fix: Clearing authorship colors no longer disconnects all clients + * Other: New debug information for sessions # 1.2.9 * Fix: MAJOR Security issue, where a hacker could submit content as another user From 4f07f829db0ac9adf5d6b2c6a0707d218d176c99 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 01:20:46 +0100 Subject: [PATCH 094/104] new readme --- README.md | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 291ecaeeb..d437646f2 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,13 @@ -# Making collaborative editing the standard on the web +# Really Real-Time Collaborative Editor +![alt text](http://primarypad.com/files/2012/11/PrimaryPad1.jpg "Etherpad in action on PrimaryPad") # About -Etherpad lite is a really-real time collaborative editor spawned from the Hell fire of Etherpad. -We're reusing the well tested Etherpad easysync library to make it really realtime. Etherpad Lite -is based on node.js ergo is much lighter and more stable than the original Etherpad. Our hope -is that this will encourage more users to use and install a realtime collaborative editor. A smaller, manageable and well -documented codebase makes it easier for developers to improve the code and contribute towards the project. - -**Etherpad vs Etherpad lite** - - - - - - - - - - - - - - - - -
     EtherpadEtherpad Lite
    Size of the folder (without git history)30 MB1.5 MB
    Languages used server sideJavascript (Rhino), Java, ScalaJavascript (node.js)
    Lines of server side Javascript code~101k~9k
    RAM Usage immediately after start257 MB (grows to ~1GB)16 MB (grows to ~30MB)
    - - -Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) +Etherpad is a really-real time collaborative editor maintained by the Etherpad Community. +Etherpad is written in Javascript(99.9%) on both the server and client so it's easy for developers to maintain and add new features. Because of this Etherpad has tons of customizations that you can leverage. +Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. -There's also a full-featured plugin framework, allowing you to easily add your own features. -Finally, Etherpad Lite comes with translations into tons of different languages! +There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparce and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control. +Finally, Etherpad comes with translations into most languages! Users are automatically delivered the correct language for their local settings. **Visit [beta.etherpad.org](http://beta.etherpad.org) to test it live** @@ -86,9 +63,9 @@ You like it? [Next steps](#next-steps). # Next Steps ## Tweak the settings -You can modify the settings in `settings.json`. (If you need to handle multiple settings files, you can pass the path to a settings file to `bin/run.sh` using the `-s|--settings` option. This allows you to run multiple Etherpad Lite instances from the same installation.) +You can initially modify the settings in `settings.json`. (If you need to handle multiple settings files, you can pass the path to a settings file to `bin/run.sh` using the `-s|--settings` option. This allows you to run multiple Etherpad instances from the same installation.) Once you have access to your /admin section settings can be modified through the web browser. -You should use a dedicated database such as "mysql", if you are planning on using etherpad-lite in a production environment, since the "dirtyDB" database driver is only for testing and/or development purposes. +You should use a dedicated database such as "mysql", if you are planning on using etherpad-in a production environment, since the "dirtyDB" database driver is only for testing and/or development purposes. ## Helpful resources The [wiki](https://github.com/ether/etherpad-lite/wiki) is your one-stop resource for Tutorials and How-to's, really check it out! Also, feel free to improve these wiki pages. @@ -98,11 +75,11 @@ Documentation can be found in `docs/`. # Development ## Things you should know -Read this [git guide](http://learn.github.com/p/intro.html) and watch this [video on getting started with Etherpad Lite Development](http://youtu.be/67-Q26YH97E). +Read this [git guide](http://learn.github.com/p/intro.html) and watch this [video on getting started with Etherpad Development](http://youtu.be/67-Q26YH97E). If you're new to node.js, start with Ryan Dahl's [Introduction to Node.js](http://youtu.be/jo_B4LTHi3I). -You can debug Etherpad lite using `bin/debugRun.sh`. +You can debug Etherpad using `bin/debugRun.sh`. If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading). @@ -114,7 +91,7 @@ Look at the [TODO list](https://github.com/ether/etherpad-lite/wiki/TODO) and ou Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), really! # Get in touch -Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)! +Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our busy freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)! # Modules created for this project From 2003dd832721d418d873811f3e6a675166481087 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 01:22:46 +0100 Subject: [PATCH 095/104] line breaks --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d437646f2..ec04b2d88 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ # About Etherpad is a really-real time collaborative editor maintained by the Etherpad Community. + Etherpad is written in Javascript(99.9%) on both the server and client so it's easy for developers to maintain and add new features. Because of this Etherpad has tons of customizations that you can leverage. + Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparce and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control. From a677286a401758aa80004900a3b8710aaf86cd94 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 01:24:09 +0100 Subject: [PATCH 096/104] line breaks 2 --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ec04b2d88..553f7cf9a 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,12 @@ Etherpad is a really-real time collaborative editor maintained by the Etherpad C Etherpad is written in Javascript(99.9%) on both the server and client so it's easy for developers to maintain and add new features. Because of this Etherpad has tons of customizations that you can leverage. Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) -that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. +that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. + +There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. + There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparce and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control. + Finally, Etherpad comes with translations into most languages! Users are automatically delivered the correct language for their local settings. **Visit [beta.etherpad.org](http://beta.etherpad.org) to test it live** From a3136d778dc96c720f613ad09061c7aba04c0a55 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 01:56:36 +0100 Subject: [PATCH 097/104] animated image prolly wont work --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 553f7cf9a..868eab324 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Really Real-Time Collaborative Editor -![alt text](http://primarypad.com/files/2012/11/PrimaryPad1.jpg "Etherpad in action on PrimaryPad") +![alt text](http://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad") # About Etherpad is a really-real time collaborative editor maintained by the Etherpad Community. From 380821781f1fe5e8309ff58dde095d4add91ee1f Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 02:25:19 +0100 Subject: [PATCH 098/104] dont use top, use the scheduler --- src/static/js/ace2_inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index cfe24ccd4..067b546b9 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3651,7 +3651,7 @@ function Ace2Inner(){ evt.preventDefault(); var originalBackground = parent.parent.$('#revisionlink').css("background") parent.parent.$('#revisionlink').css({"background":"lightyellow"}); - top.setTimeout(function(){ + scheduler.setTimeout(function(){ parent.parent.$('#revisionlink').css({"background":originalBackground}); }, 1000); parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ From 43f877824136a8b64d0201def90d30406fd99384 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 15:35:19 +0100 Subject: [PATCH 099/104] hrm, maybe this makes sense to a wider audience --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 868eab324..6354d9b06 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Really Real-Time Collaborative Editor +# A really-real time collaborative word processor for the web ![alt text](http://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad") # About From 5fb5b6c7afb9bec34162073f713b4cbdc1c421c5 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 4 Apr 2013 18:23:56 +0100 Subject: [PATCH 100/104] Linux clarity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6354d9b06..db673468a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind [Next steps](#next-steps). -## Linux/Unix +## GNU/Linux and other UNIX-like systems You'll need gzip, git, curl, libssl develop libraries, python and gcc. *For Debian/Ubuntu*: `apt-get install gzip git-core curl python libssl-dev pkg-config build-essential` *For Fedora/CentOS*: `yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"` From 6dc4ddd29e1c04e509c6140b02ad5023bb8a92a7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 5 Apr 2013 03:38:47 +0100 Subject: [PATCH 101/104] no need for the language string on that div --- src/templates/pad.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/pad.html b/src/templates/pad.html index 71dc55a17..e632e7daa 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -371,7 +371,7 @@ <% e.end_block(); %> -
    +
    0 From 402a4b7b3ec5cd04fd3d423581f12bb7f3787158 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 5 Apr 2013 14:18:46 +0200 Subject: [PATCH 102/104] html10n.js: Finally fix two-part locale specs fixes #1706 --- src/static/js/html10n.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index aa53a2668..406409ada 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -177,8 +177,7 @@ window.html10n = (function(window, document, undefined) { cb(new Error('A file couldn\'t be parsed as json.')) return } - - if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-')) + if (!data[lang]) { cb(new Error('Couldn\'t find translations for '+lang)) return @@ -667,7 +666,15 @@ window.html10n = (function(window, document, undefined) { var that = this // if only one string => create an array if ('string' == typeof langs) langs = [langs] - + + // Expand two-part locale specs + var i=0 + langs.forEach(function(lang) { + if(!lang) return + langs[i++] = lang + if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-')) + }) + this.build(langs, function(er, translations) { html10n.translations = translations html10n.translateElement(translations) From cbf0535f972aaeb9d146507a3827152b56c6ecbd Mon Sep 17 00:00:00 2001 From: goldquest Date: Tue, 2 Apr 2013 16:42:50 +0200 Subject: [PATCH 103/104] browser detection was dropped in jquery 1.9, so we have to add the browser detection js file --- src/node/handler/ImportHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 7bb9c5db9..a155f5c67 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -176,7 +176,7 @@ exports.doImport = function(req, res, padId) ERR(err); //close the connection - res.send("", 200); + res.send("", 200); }); } From 7492fb18a4874dbd170611d3313c537f9d9e9d32 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 6 Apr 2013 14:29:21 +0100 Subject: [PATCH 104/104] version bump --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 2366daf3a..48f7e2c76 100644 --- a/src/package.json +++ b/src/package.json @@ -46,5 +46,5 @@ "engines" : { "node" : ">=0.6.3", "npm" : ">=1.0" }, - "version" : "1.2.9" + "version" : "1.2.91" }