diff --git a/.gitignore b/.gitignore index 64ed357b6..0bd7f0664 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ bin/convertSettings.json src/static/js/jquery.js src/static/js/prefixfree.js npm-debug.log +*.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a35c25535 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 THE ETHERPAD FOUNDATION + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index d593115c3..84e866585 100644 --- a/README.md +++ b/README.md @@ -122,5 +122,9 @@ contribute to Etherpad Lite. * [channels](https://github.com/Pita/channels) "Event channels in node.js" - ensures that ueberDB operations are atomic and in series for each key * [async-stacktrace](https://github.com/Pita/async-stacktrace) "Improves node.js stacktraces and makes it easier to handle errors" +# Donations +* [Etherpad Foundation Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation) +* [Paypal] (http://etherpad.org) <-- Click the donate button + # License -[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html) +[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html) \ No newline at end of file diff --git a/bin/loadTesting/README b/bin/loadTesting/README new file mode 100644 index 000000000..297756f9d --- /dev/null +++ b/bin/loadTesting/README @@ -0,0 +1,75 @@ +This load tester is extremely useful for testing how many dormant clients can connect to etherpad lite. + +TODO: +Emulate characters being typed into a pad + +HOW TO USE (from @mjd75) proper formatting at: https://github.com/Pita/etherpad-lite/issues/360 + +Server 1: +Installed Node.js (etc), EtherPad Lite and MySQL + +Server 2: +Installed Xvfb and PhantomJS + +I installed Xvfb following (roughly) this guide: http://blog.martin-lyness.com/archives/installing-xvfb-on-ubuntu-9-10-karmic-koala + + #sudo apt-get install xvfb + #sudo apt-get install xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic + +Launched two instances of Xvfb directly from the terminal: + + #Xvfb :0 -ac + #Xvfb :1 -ac + +I installed PhantomJS following this guide: http://code.google.com/p/phantomjs/wiki/Installation + + #sudo add-apt-repository ppa:jerome-etienne/neoip + #sudo apt-get update + #sudo apt-get install phantomjs + +I created a small JavaScript file for PhatomJS to use to control the browser instances: + +### BEGIN JAVASCRIPT ### + +var page = new WebPage(), + t, address; + +if (phantom.args.length === 0) { + console.log('Usage: loader.js '); + phantom.exit(); +} else { + t = Date.now(); + address = phantom.args[0]; + + var page = new WebPage(); + page.onResourceRequested = function (request) { + console.log('Request ' + JSON.stringify(request, undefined, 4)); + }; + page.onResourceReceived = function (response) { + console.log('Receive ' + JSON.stringify(response, undefined, 4)); + }; + page.open(address); + +} + +### END JAVASCRIPT ### + +And finally a launcher script that uses screen to run 400 instances of PhantomJS with the above script: + +### BEGIN SHELL SCRIPT ### + +#!/bin/bash + +# connect 200 instances to display :0 +for i in {1..200} +do + DISPLAY=:0 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2 +done + +# connect 200 instances to display :1 +for i in {1..200} +do + DISPLAY=:1 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2 +done + +### END SHELL SCRIPT ### diff --git a/bin/loadTesting/launcher.sh b/bin/loadTesting/launcher.sh new file mode 100755 index 000000000..375b15444 --- /dev/null +++ b/bin/loadTesting/launcher.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# connect 500 instances to display :0 +for i in {1..500} +do + echo $i + echo "Displaying Some shit" + DISPLAY=:0 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2 +done + +# connect 500 instances to display :1 +for i in {1..500} +do + echo $i + DISPLAY=:1 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2 +done diff --git a/bin/loadTesting/loader.js b/bin/loadTesting/loader.js new file mode 100644 index 000000000..ddcd0572d --- /dev/null +++ b/bin/loadTesting/loader.js @@ -0,0 +1,20 @@ +var page = new WebPage(), + t, address; + +if (phantom.args.length === 0) { + console.log('Usage: loader.js '); + phantom.exit(); +} else { + t = Date.now(); + address = phantom.args[0]; + + var page = new WebPage(); + page.onResourceRequested = function (request) { + console.log('Request ' + JSON.stringify(request, undefined, 4)); + }; + page.onResourceReceived = function (response) { + console.log('Receive ' + JSON.stringify(response, undefined, 4)); + }; + page.open(address); + +} diff --git a/node/utils/caching_middleware.js b/node/utils/caching_middleware.js new file mode 100644 index 000000000..f25059b88 --- /dev/null +++ b/node/utils/caching_middleware.js @@ -0,0 +1,177 @@ +/* + * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var async = require('async'); +var Buffer = require('buffer').Buffer; +var fs = require('fs'); +var path = require('path'); +var server = require('../server'); +var zlib = require('zlib'); +var util = require('util'); + +var ROOT_DIR = path.normalize(__dirname + "/../"); +var CACHE_DIR = ROOT_DIR + '../var/'; + +var responseCache = {}; + +/* + This caches and compresses 200 and 404 responses to GET and HEAD requests. + TODO: Caching and compressing are solved problems, a middleware configuration + should replace this. +*/ + +function CachingMiddleware() { +} +CachingMiddleware.prototype = new function () { + function handle(req, res, next) { + if (!(req.method == "GET" || req.method == "HEAD")) { + return next(undefined, req, res); + } + + var old_req = {}; + var old_res = {}; + + var supportsGzip = + req.header('Accept-Encoding', '').indexOf('gzip') != -1; + + var path = require('url').parse(req.url).path; + var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, ''); + + fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) { + var modifiedSince = (req.headers['if-modified-since'] + && new Date(req.headers['if-modified-since'])); + var lastModifiedCache = !error && stats.mtime; + if (lastModifiedCache) { + req.headers['if-modified-since'] = lastModifiedCache.toUTCString(); + } else { + delete req.headers['if-modified-since']; + } + + // Always issue get to downstream. + old_req.method = req.method; + req.method = 'GET'; + + var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']); + if (expirationDate > new Date()) { + // Our cached version is still valid. + return respond(); + } + + var _headers = {}; + old_res.setHeader = res.setHeader; + res.setHeader = function (key, value) { + _headers[key.toLowerCase()] = value; + old_res.setHeader.call(res, key, value); + }; + + old_res.writeHead = res.writeHead; + res.writeHead = function (status, headers) { + var lastModified = (res.getHeader('last-modified') + && new Date(res.getHeader('last-modified'))); + + res.writeHead = old_res.writeHead; + if (status == 200 || status == 404) { + // Update cache + var buffer = ''; + + Object.keys(headers || {}).forEach(function (key) { + res.setHeader(key, headers[key]); + }); + headers = _headers; + + old_res.write = res.write; + old_res.end = res.end; + res.write = function(data, encoding) { + buffer += data.toString(encoding); + }; + res.end = function(data, encoding) { + async.parallel([ + function (callback) { + var path = CACHE_DIR + 'minified_' + cacheKey; + fs.writeFile(path, buffer, function (error, stats) { + callback(); + }); + } + , function (callback) { + var path = CACHE_DIR + 'minified_' + cacheKey + '.gz'; + zlib.gzip(buffer, function(error, content) { + if (error) { + callback(); + } else { + fs.writeFile(path, content, function (error, stats) { + callback(); + }); + } + }); + } + ], function () { + responseCache[cacheKey] = {statusCode: status, headers: headers}; + respond(); + }); + }; + } else if (status == 304) { + // Nothing new changed from the cached version. + old_res.write = res.write; + old_res.end = res.end; + res.write = function(data, encoding) {}; + res.end = function(data, encoding) { respond() }; + } else { + res.writeHead(status, headers); + } + }; + + next(undefined, req, res); + + // This handles read/write synchronization as well as its predecessor, + // which is to say, not at all. + // TODO: Implement locking on write or ditch caching of gzip and use + // existing middlewares. + function respond() { + req.method = old_req.method || req.method; + res.write = old_res.write || res.write; + res.end = old_res.end || res.end; + + var headers = responseCache[cacheKey].headers; + var statusCode = responseCache[cacheKey].statusCode; + + var pathStr = CACHE_DIR + 'minified_' + cacheKey; + if (supportsGzip && (headers['content-type'] || '').match(/^text\//)) { + pathStr = pathStr + '.gz'; + headers['content-encoding'] = 'gzip'; + } + + var lastModified = (headers['last-modified'] + && new Date(headers['last-modified'])); + + if (statusCode == 200 && lastModified <= modifiedSince) { + res.writeHead(304, headers); + res.end(); + } else if (req.method == 'GET') { + var readStream = fs.createReadStream(pathStr); + res.writeHead(statusCode, headers); + util.pump(readStream, res); + } else { + res.writeHead(statusCode, headers); + res.end(); + } + } + }); + } + + this.handle = handle; +}(); + +module.exports = CachingMiddleware; diff --git a/settings.json.template b/settings.json.template index 7cd0e8195..94a60fd40 100644 --- a/settings.json.template +++ b/settings.json.template @@ -38,6 +38,10 @@ /* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly, but makes it impossible to debug the javascript/css */ "minify" : true, + + /* How long may clients use served javascript code? Without versioning this + is may cause problems during deployment. */ + "maxAge" : 21600000, // 6 hours /* This is the path to the Abiword executable. Setting it to null, disables abiword. Abiword is needed to enable the import/export of pads*/ diff --git a/src/node/README.md b/src/node/README.md index 031810f6f..4b443289e 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -6,7 +6,7 @@ # Module name conventions -Module file names starts with a capital letter and uses camelCase +Module file names start with a capital letter and uses camelCase # Where does it start? diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 231aa901f..4e3a31999 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -43,7 +43,8 @@ var globalPads = { * time, and allow us to "play back" these changes so legacy padIds can be found. */ var padIdTransforms = [ - [/\s+/g, '_'] + [/\s+/g, '_'], + [/:+/g, '_'] ]; /** @@ -114,7 +115,7 @@ exports.doesPadExists = function(padId, callback) db.get("pad:"+padId, function(err, value) { if(ERR(err, callback)) return; - callback(null, value != null); + callback(null, value != null && value.atext); }); } diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 06a2ce5ca..ed5eb05ee 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -82,7 +82,7 @@ exports.doImport = function(req, res, padId) //this allows us to accept source code files like .c or .java function(callback) { - var fileEnding = srcFile.split(".")[1].toLowerCase(); + var fileEnding = (srcFile.split(".")[1] || "").toLowerCase(); var knownFileEndings = ["txt", "doc", "docx", "pdf", "odt", "html", "htm"]; //find out if this is a known file ending @@ -115,7 +115,15 @@ exports.doImport = function(req, res, padId) { var randNum = Math.floor(Math.random()*0xFFFFFFFF); destFile = tempDirectory + "eplite_import_" + randNum + ".txt"; - abiword.convertFile(srcFile, destFile, "txt", callback); + abiword.convertFile(srcFile, destFile, "txt", function(err){ + //catch convert errors + if(err){ + console.warn("Converting Error:", err); + return callback("convertFailed"); + } else { + callback(); + } + }); }, //get the pad object @@ -176,16 +184,18 @@ exports.doImport = function(req, res, padId) } ], function(err) { - //the upload failed, there is nothing we can do, send a 500 - if(err == "uploadFailed") + var status = "ok"; + + //check for known errors and replace the status + if(err == "uploadFailed" || err == "convertFailed") { - res.send(500); - return; + status = err; + err = null; } ERR(err); //close the connection - res.send("ok"); + res.send("", 200); }); } diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js index c53a6ab37..27138e648 100644 --- a/src/node/utils/Abiword.js +++ b/src/node/utils/Abiword.js @@ -53,7 +53,7 @@ if(os.type().indexOf("Windows") > -1) abiword.on('exit', function (code) { if(code != 0) { - throw "Abiword died with exit code " + code; + return callback("Abiword died with exit code " + code); } if(stdoutBuffer != "") @@ -75,52 +75,54 @@ if(os.type().indexOf("Windows") > -1) else { //spawn the abiword process - var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); - - //append error messages to the buffer - abiword.stderr.on('data', function (data) - { - stdoutBuffer += data.toString(); - }); - - //throw exceptions if abiword is dieing - abiword.on('exit', function (code) - { - throw "Abiword died with exit code " + code; - }); - - //delegate the processing of stdout to a other function - abiword.stdout.on('data',onAbiwordStdout); - + var abiword; var stdoutCallback = null; - var stdoutBuffer = ""; - var firstPrompt = true; + var spawnAbiword = function (){ + abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); + var stdoutBuffer = ""; + var firstPrompt = true; - function onAbiwordStdout(data) - { - //add data to buffer - stdoutBuffer+=data.toString(); - - //we're searching for the prompt, cause this means everything we need is in the buffer - if(stdoutBuffer.search("AbiWord:>") != -1) + //append error messages to the buffer + abiword.stderr.on('data', function (data) { - //filter the feedback message - var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer; + stdoutBuffer += data.toString(); + }); + + //abiword died, let's restart abiword and return an error with the callback + abiword.on('exit', function (code) + { + spawnAbiword(); + stdoutCallback("Abiword died with exit code " + code); + }); + + //delegate the processing of stdout to a other function + abiword.stdout.on('data',function (data) + { + //add data to buffer + stdoutBuffer+=data.toString(); - //reset the buffer - stdoutBuffer = ""; - - //call the callback with the error message - //skip the first prompt - if(stdoutCallback != null && !firstPrompt) + //we're searching for the prompt, cause this means everything we need is in the buffer + if(stdoutBuffer.search("AbiWord:>") != -1) { - stdoutCallback(err); - stdoutCallback = null; + //filter the feedback message + var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer; + + //reset the buffer + stdoutBuffer = ""; + + //call the callback with the error message + //skip the first prompt + if(stdoutCallback != null && !firstPrompt) + { + stdoutCallback(err); + stdoutCallback = null; + } + + firstPrompt = false; } - - firstPrompt = false; - } + }); } + spawnAbiword(); doConvertTask = function(task, callback) { @@ -130,6 +132,7 @@ else stdoutCallback = function (err) { callback(); + console.log("queue continue"); task.callback(err); }; } diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 2536daefe..24237de49 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -60,6 +60,11 @@ exports.requireSession = false; */ exports.editOnly = false; +/** + * Max age that responses will have (affects caching layer). + */ +exports.maxAge = 1000*60*60*6; // 6 hours + /** * A flag that shows if minification is enabled or not */ diff --git a/src/package.json b/src/package.json index 8aa98fc66..b81f724f2 100644 --- a/src/package.json +++ b/src/package.json @@ -10,14 +10,16 @@ "name": "Robin Buse" } ], "dependencies" : { + "yajsml" : "1.1.2", + "request" : "2.9.100", "require-kernel" : "1.0.3", "socket.io" : "0.8.7", "ueberDB" : "0.1.7", - "async" : "0.1.15", - "express" : "2.5.0", + "async" : "0.1.18", + "express" : "2.5.8", "clean-css" : "0.3.2", "uglify-js" : "1.2.5", - "formidable" : "1.0.7", + "formidable" : "1.0.9", "log4js" : "0.4.1", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index 6a483c07b..d2d2f9774 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -170,34 +170,3 @@ p { } #overlaysdiv { position: absolute; left: -1000px; top: -1000px; } - -/* ---------- Used by JavaScript Lexer ---------- */ -.syntax .c { color: #bd3f00; font-style: italic } /* Comment */ -.syntax .o { font-weight: bold; } /* Operator */ -.syntax .p { font-weight: bold; } /* Punctuation */ -.syntax .k { color: blue; } /* Keyword */ -.syntax .kc { color: purple } /* Keyword.Constant */ -.syntax .nx { } /* Name.Other */ -.syntax .mf { color: purple } /* Literal.Number.Float */ -.syntax .mh { color: purple } /* Literal.Number.Hex */ -.syntax .mi { color: purple } /* Literal.Number.Integer */ -.syntax .sr { color: purple } /* Literal.String.Regex */ -.syntax .s2 { color: purple } /* Literal.String.Double */ -.syntax .s1 { color: purple } /* Literal.String.Single */ -.syntax .sd { color: purple } /* Literal.String.Doc */ -.syntax .cs { color: #00aa33; font-weight: bold; font-style: italic } /* Comment.Special */ -.syntax .err { color: #cc0000; font-weight: bold; text-decoration: underline; } /* Error */ - -/* css */ -.syntax .nt { font-weight: bold; } /* tag */ -.syntax .nc { color: #336; } /* class */ -.syntax .nf { color: #336; } /* id */ -.syntax .nd { color: #999; } /* :foo */ -.syntax .m { color: purple } /* number */ -.syntax .nb { color: purple } /* built-in */ -.syntax .cp { color: #bd3f00; } /* !important */ - -.syntax .flash { background-color: #adf !important; } -.syntax .flashbad { background-color: #f55 !important; } - - diff --git a/src/static/css/pad.css b/src/static/css/pad.css index e407b3a4f..969d00276 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -65,6 +65,7 @@ a img { text-decoration: none; color: #ccc; + position: absolute; } #editbar ul li a span @@ -75,11 +76,13 @@ a img #editbar ul li:hover { background: #fff; + background: linear-gradient(#f4f4f4, #e4e4e4); } #editbar ul li:active { background: #eee; background: linear-gradient(#ddd, #fff); + box-shadow: 0 0 8px rgba(0,0,0,.1) inset; } #editbar ul li.separator @@ -798,12 +801,11 @@ ul#colorpickerswatches li:hover #chatlabel { font-size:13px; - line-height:16px; font-weight:bold; color:#555; text-decoration: none; - position: relative; - bottom: 3px; + margin-right: 3px; + vertical-align: middle; } #chatinput @@ -816,8 +818,8 @@ ul#colorpickerswatches li:hover #chaticon { z-index: 400; - position:absolute; - bottom:0px; + position: fixed; + bottom: 0px; right: 20px; padding: 5px; border-left: 1px solid #999; @@ -838,8 +840,7 @@ ul#colorpickerswatches li:hover { color:#555; font-size:9px; - position:relative; - bottom: 2px; + vertical-align: middle; } #titlebar @@ -877,40 +878,6 @@ ul#colorpickerswatches li:hover margin-left: 3px; margin-right: 3px; margin-top:2px; -} - - -/* resizable stuff for chat */ -.ui-resizable { -position: relative; -} -.ui-resizable-handle { - position: absolute; - font-size: 0.1px; - z-index: 99999; - display: block; - -} - -.ui-resizable-nw { - background-image: url("../../static/img/etherpad_lite_icons.png"); - background-position: 0 -416px; - background-repeat: no-repeat; - background-size: 100% auto; - cursor: nw-resize; - height: 17px; - left: 3px; - top: 3px; - width: 17px; -} - -.ui-resizable-ne -{ - cursor: ne-resize; - width: 9px; - height: 9px; - right: -5px; - top: -5px; } .exporttype{ @@ -1060,6 +1027,8 @@ margin-top: 1px; .buttonicon-chat { background-position: 0px -102px; display: inline-block; + vertical-align: middle; + margin: 0 !important; } .buttonicon-showusers { background-position: 0px -183px; @@ -1085,7 +1054,10 @@ width:33px !important; } #online_count{ - color: #999; + color: #888; + font-size: 11px; + line-height: 18px; + position: fixed; } #qr_center { @@ -1104,83 +1076,6 @@ width:33px !important; transform: scale(1.5); } -@media screen and (max-width: 960px) { - .modaldialog { - position: relative; - margin: 0 auto; - width: 80%; - top: 40px; - left: 0; - } -} - -@media screen and (max-width: 600px) { - #editbar ul li { - padding: 4px 1px; - } -} - -@media only screen and (min-device-width: 320px) and (max-device-width: 720px) { - #editbar ul li { - padding: 4px 3px; - } - #editbar ul#menu_right > li { - padding: 4px 8px; - margin-top: 2px; - } - #chaticon { - opacity: .8; - } - #users { - right: 4px; - } - #mycolorpicker { - left: -72px; /* #mycolorpicker:width - #users:width */ - } - #editorcontainer { - margin-bottom: 33px; - } - #editbar ul#menu_right { - background: #f7f7f7; - background: linear-gradient(#f7f7f7, #f1f1f1 80%); - width: 100%; - overflow: hidden; - height: 32px; - position: fixed; - bottom: 0; - border-top: 1px solid #ccc; - } - #editbar ul#menu_right li:not(:last-child) { - display: none; - } - #editbar ul#menu_right li:last-child { - height: 24px; - border-radius: 0; - margin-top: 0; - border: 0; - float: right; - } - #chaticon { - bottom: 0; - right: 55px; - border-right: none; - border-radius: 0; - background: #f7f7f7; - background: linear-gradient(#f7f7f7, #f1f1f1 80%); - border: 0; - } - #chatbox { - bottom: 32px; - right: 0; - border-top-right-radius: 0; - } - #editbar ul li a span { - top: -3px; - } - #usericonback { - margin-top: 4px; - } -} .rtl{ direction:RTL; } @@ -1189,10 +1084,9 @@ width:33px !important; word-wrap: break-word; } -/* fix for misaligned labels */ -label { - position: relative; - bottom: 1px; +/* fix for misaligned checkboxes */ +input[type=checkbox] { + vertical-align: -1px; } .right { @@ -1234,17 +1128,11 @@ label { margin: 5px 0; } -.left_popup { +.column { float: left; width: 50%; } -.right_popup { - float: left; - width: 50%; - box-sizing: border-box; -} - #settingsmenu, #importexport, #embed { position: absolute; top: 55px; @@ -1262,3 +1150,121 @@ label { background: #eee !important; background: linear-gradient(#EEE, #F0F0F0) !important; } + +.stickyChat { + background-color: #f1f1f1 !important; + right: 0px !important; + top: 36px; + border-radius: 0px !important; + height: auto !important; + border: none !important; + border-left: 1px solid #ccc !important; + width: 185px !important; +} + +@media screen and (max-width: 960px) { + .modaldialog { + position: relative; + margin: 0 auto; + width: 80%; + top: 40px; + left: 0; + } +} + +@media screen and (max-width: 600px) { + #editbar ul li { + padding: 4px 1px; + } +} + +@media only screen and (min-device-width: 320px) and (max-device-width: 720px) { + #editbar ul li { + padding: 4px 3px; + } + #users { + right: 0; + top: 36px; + bottom: 33px; + border-radius: none; + } + #mycolorpicker { + left: -72px; /* #mycolorpicker:width - #users:width */ + } + #editorcontainer { + margin-bottom: 33px; + } + #editbar ul#menu_right { + background: #f7f7f7; + background: linear-gradient(#f7f7f7, #f1f1f1 80%); + width: 100%; + overflow: hidden; + height: 32px; + position: fixed; + bottom: 0; + border-top: 1px solid #ccc; + } + #editbar ul#menu_right li:last-child { + height: 24px; + border-radius: 0; + margin-top: 0; + border: 0; + float: right; + } + #chaticon { + bottom: 3px; + right: 55px; + border-right: none; + border-radius: 0; + background: #f7f7f7; + background: linear-gradient(#f7f7f7, #f1f1f1 80%); + border: 0; + } + #chatbox { + bottom: 32px; + right: 0; + border-top-right-radius: 0; + border-right: none; + } + #editbar ul li a span { + top: -3px; + } + #usericonback { + margin-top: 4px; + } + #qrcode { + display: none; + } + #editbar ul#menu_right li:not(:last-child) { + display: block; + } + #editbar ul#menu_right > li { + background: none; + border: none; + margin-top: 4px; + padding: 4px 8px; + } + .selected { + background: none !important; + } + #timesliderlink { + display: none !important; + } + .popup { + border-radius: 0; + box-sizing: border-box; + width: 100%; + } + #settingsmenu, #importexport, #embed { + left: 0; + top: 0; + bottom: 33px; + right: 0; + } + .separator { + display: none; + } + #online_count { + line-height: 24px; + } +} \ No newline at end of file diff --git a/src/static/favicon.ico b/src/static/favicon.ico index 2529c923f..df7b62899 100644 Binary files a/src/static/favicon.ico and b/src/static/favicon.ico differ diff --git a/src/static/index.html b/src/static/index.html index 97736d1ef..58f688017 100644 --- a/src/static/index.html +++ b/src/static/index.html @@ -4,27 +4,24 @@ Etherpad Lite - + - - + - -
-
New Pad

or create/open a Pad with the name
-
+
+
+
New Pad
+
or create/open a Pad with the name
+ - - + + +
+ - + - +