diff --git a/.gitignore b/.gitignore index a01ee28..3101bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .*.swp +node_modules diff --git a/Jakefile.js b/Jakefile.js new file mode 100644 index 0000000..1a58aa7 --- /dev/null +++ b/Jakefile.js @@ -0,0 +1,283 @@ +var // Dependency References + fs = require( "fs" ), + _ = require( "underscore" ), + jshint = require( "jshint" ).JSHINT, + colors = require( "colors" ), + uglifyjs = require( "uglify-js" ), + Buffer = require( "buffer" ).Buffer, + zlib = require( "zlib" ), + dateFormat = require( "dateformat" ), + stats = require( "stats" ), + cp = require("child_process"), + exec = cp.exec, + spawn = cp.spawn, + assert = require("assert"), + child; + +var // Shortcut References + slice = Array.prototype.slice, + now = new Date(); + +var // Program References + $$ = {}, + // Get options, defaults merged with build.json file. + config = _.extend({}, true, { + + // Meta Build Info + "meta": { + "buildDate": dateFormat( now, "m/d/yyyy" ) + }, + + // Overridden with build.json + "files": {}, + + // License Banner Template + "banner": [ + "// <%= label %> - v<%= version %> - <%= buildDate %>", + "// <%= homeurl %>", + "// <%= copyright %>; Licensed <%= license.join(', ') %>" + ].join( "\n" ), + + // JSHint Optional Settings + "jshint": { + unused: true, + unuseds: true, + devel: true, + undef: true, + noempty: true, + evil: true, + forin: false, + maxerr: 100, + loopfunc: true, + eqnull: true, + jquery: true + }, + + // Uglify Optional Settings + "uglify": { + "mangle": { + "except": [ "$" ] + }, + "squeeze": {}, + "codegen": {} + } + }, + readJson( "build.json", true ) + ), + // Setup Distribution File Banner (License Block) + banner = _.template( typeof config.banner == "string" ? config.banner : "" ); + +// Logging Utility Functions +function header( msg ) { + writeln( "\n" + msg.underline ); +} +function write( msg ) { + process.stdout.write( (msg != null && msg) || "" ); +} +function writeln( msg ) { + console.log( (msg != null && msg) || "" ); +} +function ok( msg ) { + writeln( msg ? "\n>> ".green + msg : "OK".green ); +} +function error( msg ) { + writeln( msg ? "\n>> ".red + msg : "ERROR".red ); +} + + +// Read a file. +function readFile( filepath ) { + var src; + write( "Reading " + filepath + "..." ); + try { + src = fs.readFileSync( filepath, "UTF-8" ); + ok(); + return src; + } catch( e ) { + error(); + fail( e.message ); + } +} + +// Write a file. +function writeFile( filepath, contents, silent ) { + // if ( config.nowrite ) { + // writeln('Not'.underline + ' writing ' + filepath + ' (dry run).'); + // return true; + // } + + if ( arguments.length < 3 ) { + silent = true; + } + + silent || write( "Writing " + filepath + "..." ); + + try { + fs.writeFileSync( filepath, contents, "UTF-8" ); + } catch( e ) { + error(); + fail( e ); + } + + ok(); + return true; +} + +// Read and parse a JSON file. +function readJson( filepath, silent ) { + var result; + + silent || write( "Reading " + filepath + "..." ); + + try { + result = JSON.parse( + fs.readFileSync( filepath, "UTF-8" ) + ); + } catch( e ) { + silent || error(); + fail( e.message ); + } + + silent || ok(); + return result; +} + + +// # Lint some source code. +// From http://jshint.com +function hint( src, path ) { + write( "Validating with JSHint..."); + + if ( jshint( src, config.jshint ) ) { + ok(); + } else { + error(); + + jshint.errors.forEach(function( e ) { + if ( !e ) { return; } + var str = e.evidence ? e.evidence.inverse : ""; + + str = str.replace( /\t/g, " " ).trim(); + error( path + " [L" + e.line + ":C" + e.character + "] " + e.reason + "\n " + str ); + }); + fail( "JSHint found errors." ); + } +} + +// # Minify with UglifyJS. +// From https://github.com/mishoo/UglifyJS +function uglify( src ) { + write( "Uglifying..." ); + + var jsp = uglifyjs.parser, + pro = uglifyjs.uglify, + ast; + + try { + ast = jsp.parse( src ); + ast = pro.ast_mangle( ast, config.uglify.mangle || {}); + ast = pro.ast_squeeze( ast, config.uglify.squeeze || {}); + src = pro.gen_code( ast, config.uglify.codegen || {}); + + } catch( e ) { + error(); + error( "[L" + e.line + ":C" + e.col + "] " + e.message + " (position: " + e.pos + ")" ); + fail( e.message ); + return false; + } + + ok(); + return src; +} + +// Return deflated src input. +function gzip( src ) { + return zlib.deflate( new Buffer( src ) ); +} + +// Jake Tasks + +desc( "Hint & Minify" ); +task( "default", [ "hint", "min" ], function() { + // Nothing +}); + +desc( "Validate with JSHint." ); +task( "hint", function() { + + header( "Validating with JSHint" ); + + _.keys( config.files ).forEach(function( minpath ) { + + var files = config.files[ minpath ], + concat = files.src.map(function( path ) { + var src = readFile( path ); + + config.jshint.devel = config.jshint.debug = files.debug; + + if ( files.prehint ) { + hint( src, path ); + } + + return src; + }).join( "\n" ); + + if ( files.src.length ) { + write( "Hnting concatenated source: " + files.src.length + " scripts..." ); + ok(); + if ( files.posthint ) { + hint( concat, "post" ); + } + } + }); +}); + +desc( "Minify with Uglify-js." ); +task( "min", function() { + + header( "Minifying with Uglify-js" ); + + _.keys( config.files ).forEach(function( minpath ) { + + var file = config.files[ minpath ], + concat = file.src.map( function( path ) { + return readFile( path ); + }).join( "\n" ), + + intro, fullpath, min; + + + fullpath = minpath + ".js"; + minpath = minpath + ".min.js" + + // Generate intro block with banner template, + // Inject meta build data + intro = banner( _.extend( file.meta, config.meta ) ); + + // Without a newline, the min source code will run on the same + // Line as the intro lic/banner block + if ( intro ) { + intro += "\n"; + } + + // Provide information about current file being built + if ( file.src.length ) { + write( "Concatenating " + file.src.length + " script(s)" ); + ok(); + } + + // Write full sized, concatenated source + writeFile( fullpath, concat, false ); + + // Minify/Uglify and Write compressed, concatenated source + if ( min = uglify( concat ) ) { + + min = intro + min; + + if ( writeFile( minpath, min, false ) ) { + ok( "Compressed size: " + (gzip( min ).length + "").yellow + " bytes gzipped (" + ( min.length + "" ).yellow + " bytes minified)." ); + } + } + }); +}); + diff --git a/README.md b/README.md index 738bfc6..312b9cf 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,22 @@ You just need Ruby 1.8.7 with the following gems: * ruby-hmac * openssl (not required but suggested to speedup password hashing) +To use JavaScript build tools, install the following: + +* Node.JS +* NPM + +Setup and install dev dependencies: +``` +$ npm install +``` + +Run JavaScript build tools: +``` +$ jake +``` + + How to contribute === diff --git a/build.json b/build.json new file mode 100644 index 0000000..8d70b6a --- /dev/null +++ b/build.json @@ -0,0 +1,21 @@ +{ + "files": { + "public/js/app": { + "src": [ + "public/js/app.js" + ], + "prehint": true, + "posthint": false, + "debug": true, + + "meta": { + "label": "lamernews app.js", + "author": "antirez", + "version": "0.0.0", + "homeurl": "", + "license": [ "Two Clause BSD" ], + "copyright": "Copyright 2011 Salvatore Sanfilippo. " + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..439db26 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "author": "", + "name": "lamernews", + "description": "lamernews app.js", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "git@github.com:antirez/lamernews.git" + }, + "engines": { + "node": ">=v0.4.8" + }, + "devDependencies": { + "underscore": "*", + "jshint": "*", + "uglify-js": "*", + "express": "*", + "vows": "*", + "stats": "*", + "colors": "*", + "zlib": "*", + "dateformat": "*" + } +} diff --git a/public/js/app.js b/public/js/app.js index adc2dc0..1602904 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,7 +1,11 @@ -function login() { +(function( window, document, $ ) { + +var apisecret = ""; + +window.login = function() { var data = { username: $("input[name=username]").val(), - password: $("input[name=password]").val(), + password: $("input[name=password]").val() }; var register = $("input[name=register]").attr("checked"); $.ajax({ @@ -16,14 +20,14 @@ function login() { '; expires=Thu, 1 Aug 2030 20:00:00 UTC; path=/'; window.location.href = "/"; } else { - $("#errormsg").html(r.error) + $("#errormsg").html(r.error); } } }); return false; -} +}; -function submit() { +window.submit = function() { var data = { news_id: $("input[name=news_id]").val(), title: $("input[name=title]").val(), @@ -40,14 +44,14 @@ function submit() { if (r.status == "ok") { window.location.href = "/news/"+r.news_id; } else { - $("#errormsg").html(r.error) + $("#errormsg").html(r.error); } } }); return false; -} +}; -function update_profile() { +window.update_profile = function() { var data = { email: $("input[name=email]").val(), about: $("textarea[name=about]").val(), @@ -62,14 +66,14 @@ function update_profile() { if (r.status == "ok") { window.location.reload(); } else { - $("#errormsg").html(r.error) + $("#errormsg").html(r.error); } } }); return false; -} +}; -function post_comment() { +window.post_comment = function() { var data = { news_id: $("input[name=news_id]").val(), comment_id: $("input[name=comment_id]").val(), @@ -94,12 +98,12 @@ function post_comment() { window.location.href = "/news/"+r.news_id; } } else { - $("#errormsg").html(r.error) + $("#errormsg").html(r.error); } } }); return false; -} +}; // Install the onclick event in all news arrows the user did not voted already. $(document).ready(function() { @@ -118,7 +122,8 @@ $(document).ready(function() { url: "/api/votenews", data: data, success: function(reply) { - var r = jQuery.parseJSON(reply); + var r = jQuery.parseJSON(reply), + n; if (r.status == "ok") { n = $("#"+news_id)[0]; n.children[0].setAttribute("class","voted"); @@ -128,7 +133,7 @@ $(document).ready(function() { } } }); - } + }; } var down_class = news.children[3].getAttribute("class"); if (!down_class) { @@ -143,7 +148,8 @@ $(document).ready(function() { url: "/api/votenews", data: data, success: function(reply) { - var r = jQuery.parseJSON(reply); + var r = jQuery.parseJSON(reply), + n; if (r.status == "ok") { n = $("#"+news_id)[0]; n.children[0].setAttribute("class","disabled"); @@ -153,7 +159,9 @@ $(document).ready(function() { } } }); - } + }; } }); }); + +})( this, this.document, this.jQuery ); \ No newline at end of file diff --git a/public/js/app.min.js b/public/js/app.min.js new file mode 100644 index 0000000..0e7f382 --- /dev/null +++ b/public/js/app.min.js @@ -0,0 +1,4 @@ +// lamernews app.js - v0.0.0 - 10/23/2011 +// +// Copyright 2011 Salvatore Sanfilippo. ; Licensed Two Clause BSD +(function(a,b,$){var c="";a.login=function(){var c={username:$("input[name=username]").val(),password:$("input[name=password]").val()},d=$("input[name=register]").attr("checked");return $.ajax({type:d?"POST":"GET",url:d?"/api/create_account":"/api/login",data:c,success:function(c){var d=jQuery.parseJSON(c);d.status=="ok"?(b.cookie="auth="+d.auth+"; expires=Thu, 1 Aug 2030 20:00:00 UTC; path=/",a.location.href="/"):$("#errormsg").html(d.error)}}),!1},a.submit=function(){var b={news_id:$("input[name=news_id]").val(),title:$("input[name=title]").val(),url:$("input[name=url]").val(),text:$("textarea[name=text]").val(),apisecret:c};return $.ajax({type:"POST",url:"/api/submit",data:b,success:function(b){var c=jQuery.parseJSON(b);c.status=="ok"?a.location.href="/news/"+c.news_id:$("#errormsg").html(c.error)}}),!1},a.update_profile=function(){var b={email:$("input[name=email]").val(),about:$("textarea[name=about]").val(),apisecret:c};return $.ajax({type:"POST",url:"/api/updateprofile",data:b,success:function(b){var c=jQuery.parseJSON(b);c.status=="ok"?a.location.reload():$("#errormsg").html(c.error)}}),!1},a.post_comment=function(){var b={news_id:$("input[name=news_id]").val(),comment_id:$("input[name=comment_id]").val(),parent_id:$("input[name=parent_id]").val(),comment:$("textarea[name=comment]").val(),apisecret:c};return $.ajax({type:"POST",url:"/api/postcomment",data:b,success:function(b){var c=jQuery.parseJSON(b);c.status=="ok"?c.op=="insert"?a.location.href="/news/"+c.news_id+"?r="+Math.random()+"#"+c.news_id+"-"+c.comment_id:c.op=="update"?a.location.href="/editcomment/"+c.news_id+"/"+c.comment_id:c.op=="delete"&&(a.location.href="/news/"+c.news_id):$("#errormsg").html(c.error)}}),!1},$(b).ready(function(){$("news").each(function(a,b){var d=b.id,e=b.children[0].getAttribute("class");e||(b.children[0].onclick=function(){var a={news_id:d,vote_type:"up",apisecret:c};$.ajax({type:"POST",url:"/api/votenews",data:a,success:function(a){var b=jQuery.parseJSON(a),c;b.status=="ok"?(c=$("#"+d)[0],c.children[0].setAttribute("class","voted"),c.children[3].setAttribute("class","disabled")):alert("Vote not registered: "+b.error)}})});var f=b.children[3].getAttribute("class");f||(b.children[3].onclick=function(){var a={news_id:d,vote_type:"down",apisecret:c};$.ajax({type:"POST",url:"/api/votenews",data:a,success:function(a){var b=jQuery.parseJSON(a),c;b.status=="ok"?(c=$("#"+d)[0],c.children[0].setAttribute("class","disabled"),c.children[3].setAttribute("class","voted")):alert("Vote not registered: "+b.error)}})})})})})(this,this.document,this.jQuery) \ No newline at end of file