diff --git a/.gitignore b/.gitignore index 50cd6e212..32f9ea7d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules settings.json -static/js/jquery.min.js +static/js/jquery.js APIKEY.txt bin/abiword.exe bin/node.exe diff --git a/README.md b/README.md index ab241333f..b2f85fcff 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Look at this wiki pages: You can find more information in the [wiki](https://github.com/Pita/etherpad-lite/wiki). Feel free to improve these wiki pages # Develop -If you're new to git and github, start here . +If you're new to git and github, start by watching [this video](http://youtu.be/67-Q26YH97E) then read this [git guide](http://learn.github.com/p/intro.html). If you're new to node.js, start with this video . @@ -111,6 +111,9 @@ You can join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) You also help the project, if you only host a Etherpad Lite instance and share your experience with us. +Please consider using [jshint](http://www.jshint.com/about/) if you plan to +contribute to Etherpad Lite. + # Modules created for this project * [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access diff --git a/bin/installDeps.sh b/bin/installDeps.sh index 0533caf2d..a3f767a24 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -48,8 +48,8 @@ npm install || { echo "Ensure jQuery is downloaded and up to date..." DOWNLOAD_JQUERY="true" NEEDED_VERSION="1.7" -if [ -f "static/js/jquery.min.js" ]; then - VERSION=$(cat static/js/jquery.min.js | head -n 3 | grep -o "v[0-9].[0-9]"); +if [ -f "static/js/jquery.js" ]; then + VERSION=$(cat static/js/jquery.js | head -n 3 | grep -o "v[0-9].[0-9]"); if [ ${VERSION#v} = $NEEDED_VERSION ]; then DOWNLOAD_JQUERY="false" @@ -57,7 +57,7 @@ if [ -f "static/js/jquery.min.js" ]; then fi if [ $DOWNLOAD_JQUERY = "true" ]; then - curl -lo static/js/jquery.min.js http://code.jquery.com/jquery-$NEEDED_VERSION.min.js || exit 1 + curl -lo static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1 fi #Remove all minified data to force node creating it new diff --git a/bin/jshint.sh b/bin/jshint.sh new file mode 100755 index 000000000..4dea73961 --- /dev/null +++ b/bin/jshint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -d "../bin" ]; then + cd "../" +fi + +JSHINT=./node_modules/jshint/bin/hint + +$JSHINT ./node/ diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js index 0b48320b3..4a078542c 100644 --- a/node/handler/PadMessageHandler.js +++ b/node/handler/PadMessageHandler.js @@ -516,7 +516,12 @@ exports.updatePadClients = function(pad, callback) ], function(err) { if(ERR(err, callback)) return; - + // next if session has not been deleted + if(sessioninfos[session] == null) + { + callback(null); + return; + } if(author == sessioninfos[session].author) { socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); @@ -538,7 +543,10 @@ exports.updatePadClients = function(pad, callback) callback ); - sessioninfos[session].rev = pad.getHeadRevisionNumber(); + if(sessioninfos[session] != null) + { + sessioninfos[session].rev = pad.getHeadRevisionNumber(); + } },callback); } diff --git a/node/server.js b/node/server.js index cb368db32..42f86daff 100644 --- a/node/server.js +++ b/node/server.js @@ -45,8 +45,9 @@ var socketIORouter; var version = ""; try { - var ref = fs.readFileSync("../.git/HEAD", "utf-8"); - var refPath = "../.git/" + ref.substring(5, ref.indexOf("\n")); + var rootPath = path.normalize(__dirname + "/../") + var ref = fs.readFileSync(rootPath + ".git/HEAD", "utf-8"); + var refPath = rootPath + ".git/" + ref.substring(5, ref.indexOf("\n")); version = fs.readFileSync(refPath, "utf-8"); version = version.substring(0, 7); console.log("Your Etherpad Lite git version is " + version); @@ -79,7 +80,12 @@ async.waterfall([ { //create server var app = express.createServer(); - + + app.use(function (req, res, next) { + res.header("Server", serverName); + next(); + }); + //load modules that needs a initalized db readOnlyManager = require("./db/ReadOnlyManager"); exporthtml = require("./utils/ExportHtml"); @@ -110,31 +116,24 @@ async.waterfall([ gracefulShutdown(); }); + //serve minified files + app.get('/minified/:filename', minify.minifyJS); + //serve static files + app.get('/static/js/require-kernel.js', function (req, res, next) { + res.header("Content-Type","application/javascript; charset: utf-8"); + res.write(minify.requireDefinition()); + res.end(); + }); app.get('/static/*', function(req, res) { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/.." + req.url.replace(/\.\./g, '').split("?")[0]); res.sendfile(filePath, { maxAge: exports.maxAge }); }); //serve minified files - app.get('/minified/:id', function(req, res, next) - { - res.header("Server", serverName); - - var id = req.params.id; - - if(id == "pad.js" || id == "timeslider.js") - { - minify.minifyJS(req,res,id); - } - else - { - next(); - } - }); + app.get('/minified/:filename', minify.minifyJS); //checks for padAccess function hasPadAccess(req, res, callback) @@ -179,8 +178,6 @@ async.waterfall([ //serve read only pad app.get('/ro/:id', function(req, res) { - res.header("Server", serverName); - var html; var padId; var pad; @@ -265,7 +262,6 @@ async.waterfall([ app.get('/p/:pad', function(req, res, next) { goToPad(req, res, function() { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/../static/pad.html"); res.sendfile(filePath, { maxAge: exports.maxAge }); }); @@ -275,7 +271,6 @@ async.waterfall([ app.get('/p/:pad/timeslider', function(req, res, next) { goToPad(req, res, function() { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/../static/timeslider.html"); res.sendfile(filePath, { maxAge: exports.maxAge }); }); @@ -302,7 +297,6 @@ async.waterfall([ } res.header("Access-Control-Allow-Origin", "*"); - res.header("Server", serverName); hasPadAccess(req, res, function() { @@ -322,8 +316,6 @@ async.waterfall([ return; } - res.header("Server", serverName); - hasPadAccess(req, res, function() { importHandler.doImport(req, res, req.params.pad); @@ -336,7 +328,6 @@ async.waterfall([ //This is for making an api call, collecting all post information and passing it to the apiHandler var apiCaller = function(req, res, fields) { - res.header("Server", serverName); res.header("Content-Type", "application/json; charset=utf-8"); apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields)); @@ -397,7 +388,6 @@ async.waterfall([ //serve index.html under / app.get('/', function(req, res) { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/../static/index.html"); res.sendfile(filePath, { maxAge: exports.maxAge }); }); @@ -405,7 +395,6 @@ async.waterfall([ //serve robots.txt app.get('/robots.txt', function(req, res) { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/../static/robots.txt"); res.sendfile(filePath, { maxAge: exports.maxAge }); }); @@ -413,7 +402,6 @@ async.waterfall([ //serve favicon.ico app.get('/favicon.ico', function(req, res) { - res.header("Server", serverName); var filePath = path.normalize(__dirname + "/../static/custom/favicon.ico"); res.sendfile(filePath, { maxAge: exports.maxAge }, function(err) { diff --git a/node/utils/Minify.js b/node/utils/Minify.js index 719cdaeeb..348f25373 100644 --- a/node/utils/Minify.js +++ b/node/utils/Minify.js @@ -29,9 +29,14 @@ var pro = require("uglify-js").uglify; var path = require('path'); var Buffer = require('buffer').Buffer; var gzip = require('gzip'); +var RequireKernel = require('require-kernel'); var server = require('../server'); var os = require('os'); +var ROOT_DIR = path.normalize(__dirname + "/../" ); +var JS_DIR = ROOT_DIR + '../static/js/'; +var CSS_DIR = ROOT_DIR + '../static/css/'; +var CACHE_DIR = ROOT_DIR + '../var/'; var TAR_PATH = path.join(__dirname, 'tar.json'); var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); @@ -40,17 +45,30 @@ var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); * @param req the Express request * @param res the Express response */ -exports.minifyJS = function(req, res, jsFilename) +exports.minifyJS = function(req, res, next) { - res.header("Content-Type","text/javascript"); + var jsFilename = req.params['filename']; //choose the js files we need var jsFiles = undefined; if (Object.prototype.hasOwnProperty.call(tar, jsFilename)) { jsFiles = tar[jsFilename]; + _handle(req, res, jsFilename, jsFiles) } else { - throw new Error("there is no profile for creating " + name); + // Not in tar list, but try anyways, if it fails, pass to `next`. + jsFiles = [jsFilename]; + fs.stat(JS_DIR + jsFilename, function (error, stats) { + if (error || !stats.isFile()) { + next(); + } else { + _handle(req, res, jsFilename, jsFiles); + } + }); } +} + +function _handle(req, res, jsFilename, jsFiles) { + res.header("Content-Type","text/javascript"); //minifying is enabled if(settings.minify) @@ -63,7 +81,7 @@ exports.minifyJS = function(req, res, jsFilename) //find out the highest modification date function(callback) { - var folders2check = ["../static/css","../static/js"]; + var folders2check = [CSS_DIR, JS_DIR]; //go trough this two folders async.forEach(folders2check, function(path, callback) @@ -102,7 +120,7 @@ exports.minifyJS = function(req, res, jsFilename) function(callback) { //check the modification time of the minified js - fs.stat("../var/minified_" + jsFilename, function(err, stats) + fs.stat(CACHE_DIR + "/minified_" + jsFilename, function(err, stats) { if(err && err.code != "ENOENT") { @@ -127,7 +145,7 @@ exports.minifyJS = function(req, res, jsFilename) { async.forEach(jsFiles, function (item, callback) { - fs.readFile("../static/js/" + item, "utf-8", function(err, data) + fs.readFile(JS_DIR + item, "utf-8", function(err, data) { if(ERR(err, callback)) return; fileValues[item] = data; @@ -145,7 +163,7 @@ exports.minifyJS = function(req, res, jsFilename) return; } - var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"]+\)/gi); + var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"-]+\)/gi); //go trough all includes async.forEach(founds, function (item, callback) @@ -156,21 +174,31 @@ exports.minifyJS = function(req, res, jsFilename) var type = item.match(/INCLUDE_[A-Z]+/g)[0].substr("INCLUDE_".length); //read the included file - fs.readFile(filename, "utf-8", function(err, data) + var shortFilename = filename.replace(/^..\/static\/js\//, ''); + if (shortFilename == 'require-kernel.js') { + // the kernel isn’t actually on the file system. + handleEmbed(null, requireDefinition()); + } else { + fs.readFile(ROOT_DIR + filename, "utf-8", handleEmbed); + } + function handleEmbed(err, data) { if(ERR(err, callback)) return; if(type == "JS") { - embeds[filename] = compressJS([data]); + if (shortFilename == 'require-kernel.js') { + embeds[filename] = compressJS([data]); + } else { + embeds[filename] = compressJS([isolateJS(data, shortFilename)]); + } } else { embeds[filename] = compressCSS([data]); } - callback(); - }); + } }, function(err) { if(ERR(err, callback)) return; @@ -191,21 +219,16 @@ exports.minifyJS = function(req, res, jsFilename) //put all together and write it into a file function(callback) { - //put all javascript files in an array - var values = []; - for(var i in jsFiles) - { - values.push(fileValues[jsFiles[i]]); - } - //minify all javascript files to one + var values = []; + tarCode(jsFiles, fileValues, function (content) {values.push(content)}); var result = compressJS(values); async.parallel([ //write the results plain in a file function(callback) { - fs.writeFile("../var/minified_" + jsFilename, result, "utf8", callback); + fs.writeFile(CACHE_DIR + "minified_" + jsFilename, result, "utf8", callback); }, //write the results compressed in a file function(callback) @@ -219,7 +242,7 @@ exports.minifyJS = function(req, res, jsFilename) if(ERR(err, callback)) return; - fs.writeFile("../var/minified_" + jsFilename + ".gz", compressedResult, callback); + fs.writeFile(CACHE_DIR + "minified_" + jsFilename + ".gz", compressedResult, callback); }); } //skip this step on windows @@ -243,12 +266,12 @@ exports.minifyJS = function(req, res, jsFilename) var pathStr; if(gzipSupport && os.type().indexOf("Windows") == -1) { - pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename + ".gz"); + pathStr = path.normalize(CACHE_DIR + "minified_" + jsFilename + ".gz"); res.header('Content-Encoding', 'gzip'); } else { - pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename ); + pathStr = path.normalize(CACHE_DIR + "minified_" + jsFilename ); } res.sendfile(pathStr, { maxAge: server.maxAge }); @@ -262,7 +285,7 @@ exports.minifyJS = function(req, res, jsFilename) //read all js files async.forEach(jsFiles, function (item, callback) { - fs.readFile("../static/js/" + item, "utf-8", function(err, data) + fs.readFile(JS_DIR + item, "utf-8", function(err, data) { if(ERR(err, callback)) return; fileValues[item] = data; @@ -274,18 +297,44 @@ exports.minifyJS = function(req, res, jsFilename) { if(ERR(err)) return; - for(var i=0;i - Primary Technology Ltd", - "contributors": [ - { "name": "John McLear", - "name": "Hans Pinckaers", - "name": "Robin Buse"} - ], - "dependencies" : { + "name" : "etherpad-lite", + "description" : "A Etherpad based on node.js", + "homepage" : "https://github.com/Pita/etherpad-lite", + "keywords" : ["etherpad", "realtime", "collaborative", "editor"], + "author" : "Peter 'Pita' Martischka - Primary Technology Ltd", + "contributors" : [ + { "name": "John McLear", + "name": "Hans Pinckaers", + "name": "Robin Buse" } + ], + "dependencies" : { + "require-kernel" : "1.0.0", "socket.io" : "0.8.7", "ueberDB" : "0.1.3", "async" : "0.1.15", @@ -22,6 +23,9 @@ "log4js" : "0.3.9", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2" - }, - "version" : "1.0.0" + }, + "devDependencies": { + "jshint" : "*" + }, + "version" : "1.0.0" } diff --git a/static/js/ace.js b/static/js/ace.js index 2bcf12419..6854b1142 100644 --- a/static/js/ace.js +++ b/static/js/ace.js @@ -28,9 +28,10 @@ Ace2Editor.registry = { nextId: 1 }; +var plugins = require('/plugins').plugins; + function Ace2Editor() { - var thisFunctionsName = "Ace2Editor"; var ace2 = Ace2Editor; var editor = {}; @@ -215,24 +216,41 @@ function Ace2Editor() return {embeded: embededFiles, remote: remoteFiles}; } + function pushRequireScriptTo(buffer) { + /* Folling is for packaging regular expression. */ + /* $$INCLUDE_JS("../static/js/require-kernel.js"); */ + var KERNEL_SOURCE = '../static/js/require-kernel.js'; + if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) { + buffer.push(' + @@ -337,5 +338,13 @@ + + diff --git a/static/timeslider.html b/static/timeslider.html index c1310cc6c..4e85047e3 100644 --- a/static/timeslider.html +++ b/static/timeslider.html @@ -9,6 +9,7 @@ + @@ -19,6 +20,10 @@ //