diff --git a/app.js b/app.js index a4e9847f1f..e5503ad7df 100644 --- a/app.js +++ b/app.js @@ -208,16 +208,26 @@ app.get('/deploy-a-website', resourcesController.deployAWebsite); app.get('/gmail-shortcuts', resourcesController.gmailShortcuts); app.get('/control-shortcuts', resourcesController.controlShortcuts); app.get('/control-shortcuts', resourcesController.deployAWebsite); -app.get('/stats', resourcesController.stats); +app.get('/stats', function(req, res) { + res.redirect(301, '/learn-to-code'); +}); app.get( '/pair-program-with-team-viewer', resourcesController.pairProgramWithTeamViewer ); app.get('/learn-to-code', resourcesController.about); -app.get('/about', resourcesController.about); -app.get('/login', userController.getLogin); -app.post('/login', userController.postLogin); -app.get('/logout', userController.logout); +app.get('/about', function(req, res) { + res.redirect(301, '/learn-to-code'); +}); +app.get('/signin', userController.getSignin); +app.get('/login', function(req, res) { + res.redirect(301, '/signin'); +}); +app.post('/signin', userController.postSignin); +app.get('/signout', userController.signout); +app.get('/logout', function(req, res) { + res.redirect(301, '/signout'); +}); app.get('/forgot', userController.getForgot); app.post('/forgot', userController.postForgot); app.get('/reset/:token', userController.getReset); @@ -225,7 +235,7 @@ app.post('/reset/:token', userController.postReset); app.get('/email-signup', userController.getEmailSignup); app.get('/email-signin', userController.getEmailSignin); app.post('/email-signup', userController.postEmailSignup); -app.post('/email-signin', userController.postLogin); +app.post('/email-signin', userController.postSignin); app.get('/nonprofits', contactController.getNonprofitsForm); app.post('/nonprofits', contactController.postNonprofitsForm); @@ -255,14 +265,93 @@ app.get( ); app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); -app.get('/bonfire', bonfireController.index); + +/** + * Bonfire related routes + */ +app.get('/playground', bonfireController.index); +app.get('/bonfires', bonfireController.returnNextBonfire); +app.get('/bonfire-json-generator', bonfireController.returnGenerator); +app.post('/bonfire-json-generator', bonfireController.generateChallenge); +app.get('/bonfire-challenge-generator', bonfireController.publicGenerator); +app.post('/bonfire-challenge-generator', bonfireController.testBonfire) app.get( - '/bonfire/:bonfireNumber', - bonfireController.returnBonfire + '/bonfires/:bonfireName', + bonfireController.returnIndividualBonfire ); +app.get('/bonfire', function(req, res) { + res.redirect(301, '/playground'); +}); + +app.post('/completed-bonfire/', function (req, res) { + var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; + var isCompletedDate = Math.round(+new Date() / 1000); + var bonfireHash = req.body.bonfireInfo.bonfireHash; + var isSolution = req.body.bonfireInfo.solution; + // TODO + debug(isCompletedWith, 'Is completed with'); + + if (isCompletedWith) { + var paired = User.find({"profile.username": isCompletedWith}).limit(1); + paired.exec(function(err, pairedWith) { + if (err) { + return err; + } else { + var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + + if (index > -1) { + req.user.uncompletedBonfires.splice(index,1) + } + pairedWith = pairedWith.pop(); + + //debug('This is paired with', Object.keys(pairedWith)); + debug('This is paired with\'s uncompleted bonfires array', pairedWith.uncompletedBonfires); + index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + pairedWith.uncompletedBonfires.splice(index,1) + } + + pairedWith.completedBonfires.push({ + _id: bonfireHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: isSolution + }) + + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: isSolution + }) + + req.user.save(); + pairedWith.save(); + res.redirect('/bonfires'); + } + }) + } else { + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: null, + completedDate: isCompletedDate, + solution: isSolution + }) + + var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + + if (index > -1) { + req.user.uncompletedBonfires.splice(index,1) + } + req.user.save(); + res.redirect('/bonfires'); + } + +}); // Unique Check API route app.get('/api/checkUniqueUsername/:username', userController.checkUniqueUsername); +app.get('/api/checkExistingUsername/:username', userController.checkExistingUsername); app.get('/api/checkUniqueEmail/:email', userController.checkUniqueEmail); app.get('/account', userController.getAccount); app.post('/account/profile', userController.postUpdateProfile); @@ -280,14 +369,14 @@ app.get('/account/unlink/:provider', userController.getOauthUnlink); app.post('/completed-challenge', function (req, res) { req.user.challengesHash[parseInt(req.body.challengeNumber)] = Math.round(+new Date() / 1000); - var ch = req.user.challengesHash; - var p = 0; - for (var k in ch) { - if (ch[k] > 0) { - p += 1; + var timestamp = req.user.challengesHash; + var points = 0; + for (var key in timestamp) { + if (timestamp[key] > 0) { + points += 1; } } - req.user.points = p; + req.user.points = points; req.user.save(); }); diff --git a/controllers/bonfire.js b/controllers/bonfire.js index cf7f80eb4e..e36285a199 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,78 +1,232 @@ var _ = require('lodash'), debug = require('debug')('freecc:cntr:bonfires'), - Bonfire = require('./../models/Bonfire'); + Bonfire = require('./../models/Bonfire'), + User = require('./../models/User'), + resources = require('./resources'); /** * Bonfire controller */ -var highestBonfireNumber = 1; +var highestBonfireNumber = resources.numberOfBonfires(); exports.index = function(req, res) { - res.render('bonfire/bonfire.jade', { - title: 'Learn to code with Bonfire' - }); + res.render('bonfire/show.jade', { + completedWith: null, + title: 'Bonfire Playground', + name: 'Bonfire Playground', + difficulty: 0, + brief: 'Feel free to play around!', + details: '', + tests: [], + challengeSeed: '', + challengeEntryPoint: '', + cc: req.user ? req.user.bonfiresHash : undefined, + points: req.user ? req.user.points : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliments: resources.randomCompliment(), + bonfires: [], + bonfireHash: 'test' - Bonfire.find({}, null, { sort: { bonfireNumber: 1 } }, function(err, c) { + }); +}; + +exports.returnNextBonfire = function(req, res, next) { + // TODO + //var tempUser = false; + if (!req.user) { + res.redirect('bonfires/meet-bonfire'); + //tempUser = true; + //req.user = new User(); + } + var currentTime = parseInt(+new Date() / 1000) + if (currentTime - req.user.lastContentSync > 86400) { + req.user.lastContentSync = currentTime; + var completed = req.user.completedBonfires.map(function(elem) { + return elem._id; + }); + + req.user.uncompletedBonfires = resources.allBonfireIds().filter(function(elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.save(); + } + + + var uncompletedBonfires = req.user.uncompletedBonfires; + + + var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); + displayedBonfires.exec(function(err, bonfire) { if (err) { - debug('bonfire err: ', err); next(err); } + + nameString = bonfire[0].name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('/bonfires/' + nameString); + //res.render('bonfire/show', { + // completedWith: null, + // title: bonfire[bonfireNumber].name, + // name: bonfire[bonfireNumber].name, + // difficulty: +bonfire[bonfireNumber].difficulty, + // brief: bonfire[bonfireNumber].description[0], + // details: bonfire[bonfireNumber].description.slice(1), + // tests: bonfire[bonfireNumber].tests, + // challengeSeed: bonfire[bonfireNumber].challengeSeed, + // challengeEntryPoint: bonfire[bonfireNumber].challengeEntryPoint, + // cc: req.user ? req.user.bonfiresHash : undefined, + // points: req.user ? req.user.points : undefined, + // verb: resources.randomVerb(), + // phrase: resources.randomPhrase(), + // compliments: resources.randomCompliment(), + // bonfires: bonfire, + // bonfireHash: bonfire[bonfireNumber]._id + //}); + }); +}; + +exports.returnIndividualBonfire = function(req, res, next) { + var bonfireName = req.params.bonfireName; + + bonfireName = bonfireName.replace(/\-/g, ' '); + var bonfireNumber = 0; + + Bonfire.find({"name" : new RegExp(bonfireName, 'i')}, function(err, bonfire) { + if (err) { + next(err); + } + if (bonfire.length < 1) { + debug('Full Bonfire', bonfire); + req.flash('errors', { + msg: "404: We couldn't find a bonfire with that name. Please double check the name." + }); + return res.redirect('/bonfires/meet-bonfire') + } else { + res.render('bonfire/show', { + completedWith: null, + title: bonfire[bonfireNumber].name, + name: bonfire[bonfireNumber].name, + difficulty: +bonfire[bonfireNumber].difficulty, + brief: bonfire[bonfireNumber].description[0], + details: bonfire[bonfireNumber].description.slice(1), + tests: bonfire[bonfireNumber].tests, + challengeSeed: bonfire[bonfireNumber].challengeSeed, + challengeEntryPoint: bonfire[bonfireNumber].challengeEntryPoint, + cc: !!req.user, + points: req.user ? req.user.points : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + bonfires: bonfire, + bonfireHash: bonfire[bonfireNumber]._id + + }); + } }); }; -exports.returnBonfire = function(req, res, next) { - var bonfireNumber = parseInt(req.params.bonfireNumber) || 0; - var verbs = [ - 'ACED', - 'NAILED', - 'ROCKED', - 'SCORCHED', - 'DEVASTATED', - 'OWNED', - 'CRUSHED', - 'CONQUERED', - 'KILLED', - 'SHREDDED', - 'ANNIHILATED', - 'NUKED' - ]; - var phrases = [ - "Shout it from on top of a mountain", - "Tell everyone and their dogs", - "Show them. Show them all!", - "Inspire your friends", - "Tell the world of your greatness", - "Look accomplished on social media", - "Share news of your grand endeavor", - "Establish your alibi for the past two hours", - "Prove to mom that computers aren't just for games" - ]; - - if (bonfireNumber > highestBonfireNumber) { bonfireNumber = 0; } - Bonfire.find({}, null, { sort: { bonfireNumber: 1 } }, function(err, bonfire) { - debug(bonfire[bonfireNumber].challengeEntryPoint); - if (err) { - debug('bonfire err: ', err); - next(err); - } - res.render('bonfire/show', { - title: bonfire[bonfireNumber].name, - name: bonfire[bonfireNumber].name, - number: bonfire[bonfireNumber].bonfireNumber, - difficulty: bonfire[bonfireNumber].difficulty, - description: bonfire[bonfireNumber].description, - publicTests: bonfire[bonfireNumber].publicTests, - privateTests: bonfire[bonfireNumber].privateTests, - challengeSeed: bonfire[bonfireNumber].challengeSeed, - challengeEntryPoint: bonfire[bonfireNumber].challengeEntryPoint, - challengeEntryPointNegate: bonfire[bonfireNumber].challengeEntryPointNegate, - - //cc: req.user ? req.user.bonfiresHash : undefined, - //points: req.user ? req.user.points : undefined, - //verb: verbs[Math.floor(Math.random() * verbs.length)], - //phrase: phrases[Math.floor(Math.random() * phrases.length)], - bonfires: bonfire - }); +/** + * Bonfire generator + */ +exports.returnGenerator = function(req, res) { + res.render('bonfire/generator', { + title: null, + name: null, + difficulty: null, + brief: null, + details: null, + tests: null, + challengeSeed: null, + challengeEntryPoint: null, + bonfireHash: randomString() }); -}; \ No newline at end of file +}; + +/** + * Post for bonfire generation + */ + +function randomString() { + var chars = "0123456789abcdef"; + var string_length = 23; + var randomstring = 'a'; + for (var i=0; i 0) { + return elem; + } +} + +exports.publicGenerator = function(req, res) { + res.render('bonfire/public-generator'); +} + +exports.generateChallenge = function(req, res) { + var bonfireName = req.body.name, + bonfireTests = req.body.tests, + bonfireDifficulty = req.body.difficulty, + bonfireDescription = req.body.description, + bonfireEntryPoint = req.body.challengeEntryPoint, + bonfireChallengeSeed = req.body.challengeSeed; + bonfireTests = bonfireTests.split('\r\n'); + bonfireDescription = bonfireDescription.split('\r\n'); + bonfireTests.filter(getRidOfEmpties); + bonfireDescription.filter(getRidOfEmpties); + bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); + + + var response = { + _id: randomString(), + name: bonfireName, + difficulty: bonfireDifficulty, + description: bonfireDescription, + challengeEntryPoint: bonfireEntryPoint, + challengeSeed: bonfireChallengeSeed, + tests: bonfireTests + }; + res.send(response); +} diff --git a/controllers/challenges.js b/controllers/challenges.js index 9d3b325df6..6341ce520f 100644 --- a/controllers/challenges.js +++ b/controllers/challenges.js @@ -4,56 +4,13 @@ */ var _ = require('lodash'), debug = require('debug')('freecc:cntr:challenges'), - Challenge = require('./../models/Challenge'); + Challenge = require('./../models/Challenge'), + resources = require('./resources'); var highestChallengeNumber = 53; exports.returnChallenge = function(req, res, next) { var challengeNumber = parseInt(req.params.challengeNumber) || 0; - var verbs = [ - 'ACED', - 'NAILED', - 'ROCKED', - 'SCORCHED', - 'DEVASTATED', - 'OWNED', - 'CRUSHED', - 'CONQUERED', - 'KILLED', - 'SHREDDED', - 'ANNIHILATED', - 'NUKED' - ]; - var compliments = [ - "You've got the power!", - "Nicely done!", - "Rock and roll!", - "High five!", - "Bravo!", - "Encore!", - "Challenge destroyed!", - "Power level 9000!", - "Most efficient!", - "Party on Wayne!", - "You've got the touch!", - "You're on fire!", - "Don't hurt 'em, Hammer!", - "The town is now red", - "To the nines!", - "Nothing but net!", - "Even grumpy cat is impressed" - ]; - var phrases = [ - "Shout it from on top of a mountain", - "Tell everyone and their dogs", - "Show them. Show them all!", - "Inspire your friends", - "Tell the world of your greatness", - "Look accomplished on social media", - "Share news of your grand endeavor", - "Establish your alibi for the past two hours", - "Prove to mom that computers aren't just for games" - ]; if (challengeNumber > highestChallengeNumber) { challengeNumber = 0; } Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) { if (err) { @@ -69,8 +26,9 @@ exports.returnChallenge = function(req, res, next) { number: challengeNumber, cc: req.user ? req.user.challengesHash : undefined, points: req.user ? req.user.points : undefined, - verb: verbs[Math.floor(Math.random() * verbs.length)], - phrase: phrases[Math.floor(Math.random() * phrases.length)], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), challenges: c }); }); diff --git a/controllers/resources.js b/controllers/resources.js index f073188bca..aefafe02c0 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -4,8 +4,10 @@ var User = require('../models/User'), steps = resources.steps, secrets = require('./../config/secrets'), Challenge = require('./../models/Challenge'), + bonfires = require('../seed_data/bonfires.json'); Client = require('node-rest-client').Client, - client = new Client(); + client = new Client(), + debug = require('debug')('freecc:cntr:bonfires'); /** * GET / @@ -13,161 +15,195 @@ var User = require('../models/User'), */ module.exports = { - privacy: function privacy(req, res) { - res.render('resources/privacy', { - title: 'Privacy' - }); - }, - - stats: function stats(req, res) { - var date1 = new Date("10/15/2014"); - var date2 = new Date(); - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); - var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); - client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, response) { - var nonprofitProjects = (trello && trello.length) || 15; - User.count({'points': {'$gt': 2}}, function(err, c3) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 9}}, function(err, c10) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 29}}, function(err, c30) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 53}}, function(err, all) { if (err) { debug('User err: ', err); next(err); } - res.render('resources/stats', { - title: 'Free Code Camp Stats:', - daysRunning: daysRunning, - nonprofitProjects: nonprofitProjects, - c3: c3, - c10: c10, - c30: c30, - all: all - }); - }); - }); + privacy: function privacy(req, res) { + res.render('resources/privacy', { + title: 'Privacy' }); - }); - }); - }, + }, - deployAWebsite: function deployAWebsite(req, res) { - res.render('resources/deploy-a-website', { - title: 'Deploy a Dynamic Website in 7 Minutes' - }); - }, - - nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { - res.render('resources/nonprofit-project-instructions', { - title: 'Nonprofit Project Instructions' - }); - }, - - gmailShortcuts: function gmailShortcuts(req, res) { - res.render('resources/gmail-shortcuts', { - title: 'These Gmail Shortcuts will save you Hours' - }); - }, - - controlShortcuts: function controlShortcuts(req, res) { - res.render('resources/control-shortcuts', { - title: 'These Control Shortcuts will save you Hours' - }); - }, - - chromebook: function chromebook(req, res) { - res.render('resources/chromebook', { - title: 'Win a Chromebook' - }); - }, - - jqueryExercises: function jqueryExercises(req, res) { - res.render('resources/jquery-exercises', { - title: 'jQuery Exercises' - }); - }, - - livePairProgramming: function(req, res) { - res.render('resources/live-pair-programming', { - title: 'Live Pair Programming' - }); - }, - - javaScriptInYourInbox: function(req, res) { - res.render('resources/javascript-in-your-inbox', { - title: 'JavaScript in your Inbox' - }); - }, - - pairProgramWithTeamViewer: function(req, res) { - Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) { - if (err) { - debug('Challenge err: ', err); - next(err); - } - res.render('resources/pair-program-with-team-viewer', { - title: 'Challenge: Pair Program with Team Viewer', - name: 'Pair Program with Team Viewer', - video: '', - time: 30, - steps: steps, - cc: req.user ? req.user.challengesHash : undefined, - points: req.user ? req.user.points : undefined, - challenges: c - }); - }); - }, - - about: function(req, res) { - var date1 = new Date("10/15/2014"); - var date2 = new Date(); - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); - var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); - client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, res2) { - client.get('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function(blogger, res3) { - var nonprofitProjects = trello.length || 15; - var blog = JSON.parse(blogger); - User.count({'points': {'$gt': 2}}, function (err, c3) { - if (err) { - debug('User err: ', err); - next(err); - } - User.count({'points': {'$gt': 9}}, function (err, c10) { - if (err) { - debug('User err: ', err); - next(err); - } - User.count({'points': {'$gt': 29}}, function (err, c30) { - if (err) { - debug('User err: ', err); - next(err); - } - User.count({'points': {'$gt': 53}}, function (err, all) { - if (err) { - debug('User err: ', err); - next(err); - } - res.render('resources/about', { - title: 'About Free Code Camp and Our Team of Volunteers', - daysRunning: daysRunning, - nonprofitProjects: nonprofitProjects, - c3: c3, - c10: c10, - c30: c30, - all: all, - blog1Title: blog["items"][0]["title"], - blog1Link: blog["items"][0]["url"], - blog2Title: blog["items"][1]["title"], - blog2Link: blog["items"][1]["url"], - blog3Title: blog["items"][2]["title"], - blog3Link: blog["items"][2]["url"], - blog4Title: blog["items"][3]["title"], - blog4Link: blog["items"][3]["url"], - blog5Title: blog["items"][4]["title"], - blog5Link: blog["items"][4]["url"] + stats: function stats(req, res) { + var date1 = new Date("10/15/2014"); + var date2 = new Date(); + var timeDiff = Math.abs(date2.getTime() - date1.getTime()); + var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); + client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, response) { + var nonprofitProjects = (trello && trello.length) || 15; + User.count({'points': {'$gt': 2}}, function(err, c3) { if (err) { debug('User err: ', err); next(err); } + User.count({'points': {'$gt': 9}}, function(err, c10) { if (err) { debug('User err: ', err); next(err); } + User.count({'points': {'$gt': 29}}, function(err, c30) { if (err) { debug('User err: ', err); next(err); } + User.count({'points': {'$gt': 53}}, function(err, all) { if (err) { debug('User err: ', err); next(err); } + res.render('resources/stats', { + title: 'Free Code Camp Stats:', + daysRunning: daysRunning, + nonprofitProjects: nonprofitProjects, + c3: c3, + c10: c10, + c30: c30, + all: all + }); + }); + }); }); - }); }); - }); }); - }); - }); - } + }, + + deployAWebsite: function deployAWebsite(req, res) { + res.render('resources/deploy-a-website', { + title: 'Deploy a Dynamic Website in 7 Minutes' + }); + }, + + nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { + res.render('resources/nonprofit-project-instructions', { + title: 'Nonprofit Project Instructions' + }); + }, + + gmailShortcuts: function gmailShortcuts(req, res) { + res.render('resources/gmail-shortcuts', { + title: 'These Gmail Shortcuts will save you Hours' + }); + }, + + controlShortcuts: function controlShortcuts(req, res) { + res.render('resources/control-shortcuts', { + title: 'These Control Shortcuts will save you Hours' + }); + }, + + chromebook: function chromebook(req, res) { + res.render('resources/chromebook', { + title: 'Win a Chromebook' + }); + }, + + jqueryExercises: function jqueryExercises(req, res) { + res.render('resources/jquery-exercises', { + title: 'jQuery Exercises' + }); + }, + + livePairProgramming: function(req, res) { + res.render('resources/live-pair-programming', { + title: 'Live Pair Programming' + }); + }, + + javaScriptInYourInbox: function(req, res) { + res.render('resources/javascript-in-your-inbox', { + title: 'JavaScript in your Inbox' + }); + }, + + pairProgramWithTeamViewer: function(req, res) { + Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) { + if (err) { + debug('Challenge err: ', err); + next(err); + } + res.render('resources/pair-program-with-team-viewer', { + title: 'Challenge: Pair Program with Team Viewer', + name: 'Pair Program with Team Viewer', + video: '', + time: 30, + steps: steps, + cc: req.user ? req.user.challengesHash : undefined, + points: req.user ? req.user.points : undefined, + challenges: c + }); + }); + }, + + about: function(req, res) { + var date1 = new Date("10/15/2014"); + var date2 = new Date(); + var timeDiff = Math.abs(date2.getTime() - date1.getTime()); + var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); + client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, res2) { + client.get('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function(blogger, res3) { + var nonprofitProjects = trello.length || 15; + var blog = JSON.parse(blogger); + User.count({'points': {'$gt': 2}}, function (err, c3) { + if (err) { + debug('User err: ', err); + next(err); + } + User.count({'points': {'$gt': 9}}, function (err, c10) { + if (err) { + debug('User err: ', err); + next(err); + } + User.count({'points': {'$gt': 29}}, function (err, c30) { + if (err) { + debug('User err: ', err); + next(err); + } + User.count({'points': {'$gt': 53}}, function (err, all) { + if (err) { + debug('User err: ', err); + next(err); + } + res.render('resources/learn-to-code', { + title: 'About Free Code Camp and Our Team of Volunteers', + daysRunning: daysRunning, + nonprofitProjects: nonprofitProjects, + c3: c3, + c10: c10, + c30: c30, + all: all, + blog1Title: blog["items"][0]["title"], + blog1Link: blog["items"][0]["url"], + blog2Title: blog["items"][1]["title"], + blog2Link: blog["items"][1]["url"], + blog3Title: blog["items"][2]["title"], + blog3Link: blog["items"][2]["url"], + blog4Title: blog["items"][3]["title"], + blog4Link: blog["items"][3]["url"], + blog5Title: blog["items"][4]["title"], + blog5Link: blog["items"][4]["url"] + }); + }); + }); + }); + }); + }); + }); + }, + + randomPhrase: function() { + var phrases = resources.phrases; + return phrases[Math.floor(Math.random() * phrases.length)]; + }, + + randomVerb: function() { + var verbs = resources.verbs; + return verbs[Math.floor(Math.random() * verbs.length)]; + }, + + randomCompliment: function() { + var compliments = resources.compliments; + return compliments[Math.floor(Math.random() * compliments.length)]; + }, + + numberOfBonfires: function() { + return bonfires.length - 1; + }, + + allBonfireIds: function() { + return bonfires.map(function(elem) { + return { + _id: elem._id, + difficulty: elem.difficulty + } + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map(function(elem) { + return elem._id; + }) + } }; diff --git a/controllers/resources.json b/controllers/resources.json index 098f41fa46..0c9427e5bb 100644 --- a/controllers/resources.json +++ b/controllers/resources.json @@ -144,5 +144,101 @@ "You can complete CoderByte problems while you continue to work through Free Code Camp's challenges.", "Be sure to pair program on these challenges, and remember to apply the RSAP methodology.", "Click the button below to return to the Pair Programming challenge, then mark it complete." + ], + "verbs": [ + "aced", + "nailed", + "rocked", + "scorched", + "devastated", + "destroyed", + "owned", + "crushed", + "conquered", + "killed", + "shredded", + "annihilated", + "nuked", + "smashed", + "decimated", + "demolished", + "devoured", + "pulvarized", + "banished", + "throttled", + "blew away", + "roundhoused", + "tangoed", + "wrangled", + "shot down", + "scarfed", + "grappled", + "incinerated" + ], + "compliments": [ + "You've got the power!", + "Nicely done!", + "Rock and roll!", + "High five!", + "Bravo!", + "Yes!", + "Swoosh!", + "There is no try", + "Done and done!", + "You make this look easy", + "Terminated", + "We have liftoff!", + "To infinity and beyond!", + "Encore!", + "Challenge destroyed!", + "Power level 9000!", + "Most efficient!", + "Party on, Wayne!", + "You've got the touch!", + "You're on fire!", + "Don't hurt 'em, Hammer!", + "The town is now red", + "To the nines!", + "Nothing but net!", + "Grumpy cat approves", + "The world rejoices", + "That's the way it's done!", + "You rock!", + "Woo-hoo!", + "We knew you could do it", + "Hyper Combo Finish!", + "Nothing but net!", + "Boom-shakalaka!", + "You're a shooting star!", + "You're unstoppable!", + "Way cool!", + "You're king of the world!", + "Walk on that sunshine!", + "Keep on trucking!", + "Off the charts!", + "There is no spoon", + "Cranked it up to 11!", + "Escape velocity reached!", + "You make this look easy!", + "Passed with flying colors!", + "One step closer...", + "You've got this!", + "Happy happy joy joy!", + "Tomorrow, the world!", + "Here's looking at you, Code!", + "It's alive. It's alive!" + ], + "phrases": [ + "Shout it from on top of a mountain", + "Tell everyone and their dogs", + "Show them. Show them all!", + "Inspire your friends", + "Tell the world of your greatness", + "Look accomplished on social media", + "Share news of your grand endeavor", + "Establish your alibi for the past two hours", + "Prove to mom that computers aren't just for games", + "With coding power comes sharing responsibility", + "Have you told your friends of your coding powers?" ] -} +} \ No newline at end of file diff --git a/controllers/user.js b/controllers/user.js index 9934f2a5e7..7c4a9dc77a 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -12,23 +12,23 @@ var _ = require('lodash'), //TODO(Berks): Refactor to use module.exports = {} pattern. /** - * GET /login - * Login page. + * GET /signin + * Siginin page. */ -exports.getLogin = function(req, res) { +exports.getSignin = function(req, res) { if (req.user) return res.redirect('/'); - res.render('account/login', { + res.render('account/signin', { title: 'Free Code Camp Login' }); }; /** - * POST /login + * POST /signin * Sign in using email and password. */ -exports.postLogin = function(req, res, next) { +exports.postSignin = function(req, res, next) { req.assert('email', 'Email is not valid').isEmail(); req.assert('password', 'Password cannot be blank').notEmpty(); @@ -36,14 +36,14 @@ exports.postLogin = function(req, res, next) { if (errors) { req.flash('errors', errors); - return res.redirect('/login'); + return res.redirect('/signin'); } passport.authenticate('local', function(err, user, info) { if (err) return next(err); if (!user) { req.flash('errors', { msg: info.message }); - return res.redirect('/login'); + return res.redirect('/signin'); } req.logIn(user, function(err) { if (err) return next(err); @@ -54,11 +54,11 @@ exports.postLogin = function(req, res, next) { }; /** - * GET /logout + * GET /signout * Log out. */ -exports.logout = function(req, res) { +exports.signout = function(req, res) { req.logout(); res.redirect('/'); }; @@ -76,7 +76,7 @@ exports.getEmailSignin = function(req, res) { }; /** - * GET /email-signin + * GET /signin * Signup page. */ @@ -146,7 +146,7 @@ exports.getAccount = function(req, res) { console.error('Challenge err: ', err); next(err); } - res.render('account/profile', { + res.render('account/account', { title: 'Manage your Free Code Camp Account', challenges: c, ch: req.user.challengesHash, @@ -184,6 +184,22 @@ exports.checkUniqueUsername = function(req, res) { } }); }; + +/** + * Existing username check + */ +exports.checkExistingUsername = function(req, res) { + User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { + if (data == 1) { + debug('sending false back') + return res.send(true); + } else { + debug('sending true back') + return res.send(false); + } + }); +}; + /** * Unique email check API Call */ diff --git a/gulpfile.js b/gulpfile.js index fb5377729d..70c59bf1fe 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,7 +5,7 @@ var gulp = require('gulp'), sync = require('browser-sync'), reload = sync.reload, inject = require('gulp-inject'), - reloadDelay = 3000; + reloadDelay = 1000; var paths = { server: './app.js', diff --git a/models/Bonfire.js b/models/Bonfire.js index 461e663d77..5cb531547e 100644 --- a/models/Bonfire.js +++ b/models/Bonfire.js @@ -8,20 +8,16 @@ var secrets = require('../config/secrets'); var bonfireSchema = new mongoose.Schema({ - name: { type: String, unique: true }, - difficulty: Number, + difficulty: String, description: Array, - publicTests: Array, - privateTests: Array, + tests: Array, challengeSeed: String, - bonfireNumber: Number, challengeEntryPoint: String, challengeEntryPointNegate: String - }); module.exports = mongoose.model('Bonfire', bonfireSchema); diff --git a/models/BonfireCompletion.js b/models/BonfireCompletion.js index 671c7ff4d9..eb4249400a 100644 --- a/models/BonfireCompletion.js +++ b/models/BonfireCompletion.js @@ -3,11 +3,8 @@ var secrets = require('../config/secrets'); var bonfireCompletionSchema = new mongoose.Schema({ dateCompleted: Number, - completedWith: String, - bonfireNumber: { - bonfireNumber: Number, - bonfireId: String - }, + completedWith: ObjectId, + bonfireHash: ObjectId, solution: String }); diff --git a/models/User.js b/models/User.js index 582bd551e5..d35020b0ad 100644 --- a/models/User.js +++ b/models/User.js @@ -353,7 +353,12 @@ var userSchema = new mongoose.Schema({ }, resetPasswordToken: String, resetPasswordExpires: Date, - bonfires: Array + uncompletedBonfires: Array, + completedBonfires: Array, + lastContentSync: { + type: Number, + default: 0 + } }); /** diff --git a/public/css/main.less b/public/css/main.less index 0630a4e272..78b4a42b08 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -2,8 +2,9 @@ @import "lib/bootstrap-social/bootstrap-social"; @import "lib/ionicons/ionicons"; @import "lib/animate.min.less"; +@import "lib/bootstrap/variables"; -@import url(http://fonts.googleapis.com/css?family=Lato:300); +//fonts.googleapis.com/css?family=Lato:300); @import url(http://fonts.googleapis.com/css?family=Lato:400); @import url(http://fonts.googleapis.com/css?family=Inconsolata); @@ -28,11 +29,19 @@ html { min-height: 100%; } -body { +body.full-screen-body-background { + background-color: #eeeeee; +} + +body.top-and-bottom-margins { padding-top: 80px; margin-bottom: 60px; } +body.no-top-and-bottom-margins { + margin: 70px 20px 50px 20px; +} + h1, h2 { font-weight: 400; } @@ -205,6 +214,11 @@ ul { animation-duration: 0.5s; } +.slow-animation { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; +} + .disabled { pointer-events: none; cursor: default; @@ -247,6 +261,10 @@ ul { .navbar { white-space: nowrap; border: none; + @media (min-width: 767px) { + padding-left: 30px; + padding-right: 30px; + } } .panel-body { @@ -505,6 +523,7 @@ thead { text-align: center; margin-bottom: -30px; border-radius: 5px 5px 0px 0px; + padding-left: 50px; } .closing-x { @@ -531,22 +550,6 @@ thead { } } -form.code span { - font-size: 14px; - font-family: "Ubuntu Mono"; - padding-bottom: 0px; - margin-bottom: 0px; - height: auto; -} - -#mainEditorPanel .panel-body { - padding-bottom: 0px; -} - -div.CodeMirror-scroll { - padding-bottom: 100px; -} - .embed-responsive-twitch-chat { padding-bottom: 117%; } @@ -569,13 +572,78 @@ div.CodeMirror-scroll { text-size: 250px; } -.bonfire-instructions p { - padding: 0; -} .bonfire-instructions { - margin-bottom: 2px; + margin-bottom: 5px; } + + +/** + * Bonfire styling + */ + +form.code span { + font-size: 18px; + font-family: "Ubuntu Mono"; + padding-bottom: 0px; + margin-bottom: 0px; + height: 100%; +} + +.CodeMirror-linenumber { + font-size: 18px; + font-family: "Ubuntu Mono"; +} + +#mainEditorPanel { + height: 100%; +} + +.big-error-icon { + font-size: 40px; + color: @brand-danger; +} + +.big-success-icon { + font-size: 40px; + color: @brand-primary; +} + +.test-output { + font-size: 15px; + font-family: "Ubuntu Mono"; +} + +#mainEditorPanel .panel-body { + padding-bottom: 0px; +} + +.panel-bonfire { + height: 100% +} + +div.CodeMirror-scroll { + padding-bottom: 30px; +} + +.test-vertical-center { + margin-top: 15px; +} + +.cm-s-monokai.CodeMirror { + border-radius: 5px; +} +.bonfire-flames { + margin-top: -20px; + margin-bottom: -2px; +} + +.bonfire-top { + margin-top: -30px; +} + + + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/public/js/lib/bonfire/bonfireFramework.js b/public/js/lib/bonfire/bonfireFramework.js index 4311a7fa09..e7fc90cf28 100644 --- a/public/js/lib/bonfire/bonfireFramework.js +++ b/public/js/lib/bonfire/bonfireFramework.js @@ -7,7 +7,6 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") lint: true, matchBrackets: true, autoCloseBrackets: true, - cursorHeight: 1, scrollbarStyle: 'null', lineWrapping: true, gutters: ["CodeMirror-lint-markers"], @@ -20,13 +19,13 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") } }); var editor = myCodeMirror; +editor.setSize("100%", "auto"); // Default value for editor if one isn't provided in (i.e. a challenge) var nonChallengeValue = '/*Welcome to Bonfire, Free Code Camp\'s future CoderByte replacement.\n' + 'Please feel free to use Bonfire as an in-browser playground and linting tool.\n' + - 'Note that you can also write tests using Chai.js\n' + - ' by using the keywords assert and expect */\n\n' + + 'Note that you can also write tests using Chai.js by using the keywords assert and expect */\n\n' + 'function test() {\n' + ' assert(2 !== 3, "2 is not equal to 3");\n' + ' return [1,2,3].map(function(elem) {\n' + @@ -35,7 +34,9 @@ var nonChallengeValue = '/*Welcome to Bonfire, Free Code Camp\'s future CoderByt '}\n' + 'expect(test()).to.be.a("array");\n\n' + 'assert.deepEqual(test(), [1,4,9]);\n\n' + - 'test();'; + 'var foo = test();\n' + + 'foo.should.be.a("array");\n\n' + + 'test();\n'; var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { lineNumbers: false, @@ -45,9 +46,9 @@ var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), lineWrapping: true }); codeOutput.setValue('/**\n' + - ' * Your output will go here. Console.log() -type statements\n' + - ' * will appear in your browser\'s javascript console.\n' + - ' */'); +' * Your output will go here.\n' + ' * Console.log() -type statements\n' + +' * will appear in your browser\'s\n' + ' * DevTools JavaScript console.\n' + +' */'); codeOutput.setSize("100%", "100%"); var info = editor.getScrollInfo(); var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; @@ -58,10 +59,8 @@ var editorValue; var challengeSeed = challengeSeed || null; -var publicTests = publicTests || []; -var privateTests = privateTests || []; +var tests = tests || []; var challengeEntryPoint = challengeEntryPoint || null; -var challengeEntryPointNegate = challengeEntryPointNegate || null; if (challengeSeed !== null) { @@ -101,11 +100,15 @@ $('#submitButton').on('click', function () { }); function bonfireExecute() { - tests = null; + userTests= null; $('#codeOutput').empty(); var userJavaScript = myCodeMirror.getValue(); userJavaScript = removeComments(userJavaScript); userJavaScript = scrapeTests(userJavaScript); + // simple fix in case the user forgets to invoke their function + if (challengeEntryPoint) { + userJavaScript = challengeEntryPoint + ' ' + userJavaScript; + } submit(userJavaScript, function(cls, message) { if (cls) { codeOutput.setValue(message.error); @@ -118,24 +121,16 @@ function bonfireExecute() { }); } -var replaceQuotesInTests = function() { - tests.forEach(function(elt, ix, arr) { - arr[ix].text = arr[ix].text.replace(/\"/g,'\''); - }); -}; -var tests; +var userTests; var testSalt = Math.random(); var scrapeTests = function(userJavaScript) { - for (var i = 0; i < publicTests.length; i++) { - userJavaScript += '\n' + publicTests[i]; - } - - for (var i = 0; i < privateTests.length; i++) { - userJavaScript += '\n' + privateTests[i]; + // insert tests from mongo + for (var i = 0; i < tests.length; i++) { + userJavaScript += '\n' + tests[i]; } var counter = 0; @@ -143,17 +138,16 @@ var scrapeTests = function(userJavaScript) { var match = regex.exec(userJavaScript); while (match != null) { var replacement = '//' + counter + testSalt; - userJavaScript = userJavaScript.substring(0, match.index) - + replacement - + userJavaScript.substring(match.index + match[0].length); + userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length); - if (!tests) tests = []; - tests.push({"text": match[0], "line": counter, "err": null}); + if (!userTests) { + userTests= []; + } + userTests.push({"text": match[0], "line": counter, "err": null}); counter++; match = regex.exec(userJavaScript); } - if (tests) replaceQuotesInTests(); return userJavaScript; }; @@ -164,30 +158,23 @@ function removeComments(userJavaScript) { function removeLogs(userJavaScript) { return userJavaScript.replace(/(console\.[\w]+\s*\(.*\;)/g, ''); - return userJavaScript; } var pushed = false; var createTestDisplay = function() { if (pushed) { - tests.pop(); + userTests.pop(); } - for (var i = 0; i < tests.length;i++) { - var test = tests[i]; - var testDoc = document.createElement("li"); - $(testDoc) - .addClass('list-group-item') - .addClass('well img-rounded') - .addClass('well-sm') + for (var i = 0; i < userTests.length;i++) { + var test = userTests[i]; + var testDoc = document.createElement("div"); if (test.err != null) { $(testDoc) - .html(test.text + "\n" + test.err) - .css("background-color", 'rgba(255,0,0,.2)') - .prependTo($('#testSuite')); + .html("
" + test.text + "
" + test.err + "
") + .prependTo($('#testSuite')) } else { $(testDoc) - .html(test.text) - .css('background-color', 'rgba(0,255,0,.2)') + .html("
" + test.text + "
") .appendTo($('#testSuite')); } }; @@ -201,28 +188,39 @@ var reassembleTest = function(test, data) { var regexp = new RegExp("\/\/" + lineNum + testSalt); return data.input.replace(regexp, test.text); }; + var runTests = function(err, data) { + var allTestsPassed = true; pushed = false; $('#testSuite').children().remove(); - if (err && tests.length > 0) { - tests = [{text:"Program Execution Failure", err: "No tests were run."}]; + if (err && userTests.length > 0) { + userTests= [{text:"Program Execution Failure", err: "No user tests were run."}]; createTestDisplay(); - } else if (tests) { - tests.push(false); + } else if (userTests) { + userTests.push(false); pushed = true; - tests.forEach(function(test, ix, arr){ + userTests.forEach(function(test, ix, arr){ try { if (test) { + console.log(); var output = eval(reassembleTest(test, data)); } } catch(error) { - + allTestsPassed = false; arr[ix].err = error.name + ":" + error.message; - } finally { + } finally { if (!test) { createTestDisplay(); } } }); } -}; \ No newline at end of file + if (allTestsPassed) { + allTestsPassed = false; + showCompletion(); + } +}; + +function showCompletion() { + $('#complete-bonfire-dialog').modal('show'); +} \ No newline at end of file diff --git a/public/js/lib/codemirror/theme/monokai.css b/public/js/lib/codemirror/theme/monokai.css index 2aa742c4ca..8ac80e7ac2 100644 --- a/public/js/lib/codemirror/theme/monokai.css +++ b/public/js/lib/codemirror/theme/monokai.css @@ -1,6 +1,6 @@ /* Based on Sublime Text's Monokai theme */ -.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2; border-radius: 5px; height: auto;} +.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2; height: auto;} .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} .cm-s-monokai .CodeMirror-guttermarker { color: white; } diff --git a/public/js/main.js b/public/js/main.js index ced2575987..99e2ff1c03 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,46 +1,96 @@ $(document).ready(function() { - var CSRF_HEADER = 'X-CSRF-Token'; + var CSRF_HEADER = 'X-CSRF-Token'; - var setCSRFToken = function(securityToken) { - jQuery.ajaxPrefilter(function(options, _, xhr) { - if (!xhr.crossDomain) { - xhr.setRequestHeader(CSRF_HEADER, securityToken); - } + var setCSRFToken = function(securityToken) { + jQuery.ajaxPrefilter(function(options, _, xhr) { + if (!xhr.crossDomain) { + xhr.setRequestHeader(CSRF_HEADER, securityToken); + } + }); + }; + + setCSRFToken($('meta[name="csrf-token"]').attr('content')); + + $('.start-challenge').on('click', function() { + $(this).parent().remove(); + $('.challenge-content') + .removeClass('hidden-element') + .addClass('animated fadeInDown'); }); - }; - setCSRFToken($('meta[name="csrf-token"]').attr('content')); + $('.completed-challenge').on('click', function() { + $('#complete-challenge-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { + l = location.pathname.split('/'); + cn = l[l.length - 1]; + $.ajax({ + type: 'POST', + data: {challengeNumber: cn}, + url: '/completed-challenge/' + }); + } + }); - $('.start-challenge').on('click', function() { - $(this).parent().remove(); - $('.challenge-content') - .removeClass('hidden-element') - .addClass('animated fadeInDown'); - }); - $('.completed-challenge').on('click', function() { - $('#complete-dialog').modal('show'); - // Only post to server if there is an authenticated user - if ($('.signup-btn-nav').length < 1) { - l = location.pathname.split('/'); - cn = l[l.length - 1]; - $.ajax({ - type: 'POST', - data: {challengeNumber: cn}, - url: '/completed-challenge/' - }); - } - }); - $('.all-challenges').on('click', function() { - $('#all-challenges-dialog').modal('show'); - }); + function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash) { + $('#complete-bonfire-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { + $.ajax({ + type: 'POST', + data: { + bonfireInfo: { + completedWith : didCompleteWith, + solution: bonfireSolution, + bonfireHash: thisBonfireHash + } + }, + url: '/completed-bonfire/' - $('.next-button').on('click', function() { - l = location.pathname.split('/'); - window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); - }); + }); + + //$.post( '/completed-bonfire', function( data ) { + // window.location = '/bonfires'; + //}); + } + } + + $('.all-challenges').on('click', function() { + $('#all-challenges-dialog').modal('show'); + }); + + $('.all-bonfires').on('click', function() { + $('#all-bonfires-dialog').modal('show'); + }); + + $('.next-challenge-button').on('click', function() { + l = location.pathname.split('/'); + window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); + }); + + $('.next-bonfire-button').on('click', function() { + var bonfireSolution = myCodeMirror.getValue(); + var thisBonfireHash = passedBonfireHash || null; + var didCompleteWith = $('#completed-with').val() || null; + + completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); + window.location = '/bonfires'; + + }); + + // Bonfire instructions functions + $('#more-info').on('click', function() { + $('#brief-instructions').hide(); + $('#long-instructions').show().removeClass('hide'); + + }); + $('#less-info').on('click', function() { + $('#brief-instructions').show(); + $('#long-instructions').hide(); + }); }); var profileValidation = angular.module('profileValidation',['ui.bootstrap']); @@ -58,6 +108,12 @@ profileValidation.controller('profileValidationController', ['$scope', '$http', } ]); +profileValidation.controller('pairedWithController', ['$scope', + function($scope) { + $scope.existingUser = null; + } +]); + profileValidation.controller('emailSignUpController', ['$scope', function($scope) { @@ -102,6 +158,31 @@ profileValidation.directive('uniqueUsername', function($http) { } } }); +// TODO: FIX THIS +profileValidation.directive('existingUsername', function($http) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + if (element.val().length > 0) { + ngModel.$setValidity('exists', false); + } else { + ngModel.$setPristine(); + } + if (element.val()) { + $http.get("/api/checkExistingUsername/" + element.val() + ' ').success(function (data) { + if (element.val() == scope.existingUsername) { + ngModel.$setValidity('exists', false); + } else if (data) { + ngModel.$setValidity('exists', true); + } + }); + } + }); + } + } +}); profileValidation.directive('uniqueEmail', function($http) { return { diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 79f5f34bfb..39b3236edb 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -1,44 +1,66 @@ [ { - "name": "Palindrome Tester", - "difficulty": 1, + "_id" : "ad7123c8c441eddfaeb5bdef", + "name": "Meet Bonfire", + "difficulty": "0", "description": [ - "Your job is to determine if a provided string is a palindrome.", - "The definition of a palindrome can be found at http://en.wikipedia.org/wiki/Palindrome.", - "Strings will be passed in with varying formats, such as \"racecar\", \"RaceCar\", and \"race CAR\" among others.", - "Return true if the string is a palindrome, otherwise false" + "Click the button below for further instructions.", + "Your goal is to fix the failing test.", + "First, run all the tests by clickin \"Run code\" or by pressing Control + Enter", + "The failing test is in red. Fix the code so that all tests pass. Then you can move on to the next Bonfire." ], - "publicTests": [ + "tests": [ + "expect(meetBonfire(\"test\")).to.be.a(\"boolean\");", + "expect(meetBonfire(\"test\")).to.be.true;" + ], + "challengeSeed": "function meetBonfire(argument) {\n // Good luck!\n console.log(\"you can read this function's argument in the developer tools\", argument);\n\nreturn false;\n}\n\n", + "challengeEntryPoint": "meetBonfire(\"You can do this!\");" + }, + { + "_id" : "aaa48de84e1ecc7c742e1124", + "name": "Check for Palindromes", + "difficulty": "1", + "description": [ + "Return 'true' if a given string is a palindrome.", + "A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation and case.", + "You'll need to remove punctuation and turn everything lower case in order to check for palindromes.", + "We'll pass strings with varying formats, such as \"racecar\", \"RaceCar\", and \"race CAR\" among others.", + "Return true if the string is a palindrome. Otherwise, return false." + ], + "tests": [ "expect(palindrome(\"eye\")).to.be.a(\"boolean\");", "assert.deepEqual(palindrome(\"eye\"), true);", "assert.deepEqual(palindrome(\"race car\"), true);", - "assert.deepEqual(palindrome(\"not a palindrome\"), false);" - ], - "privateTests": [ + "assert.deepEqual(palindrome(\"not a palindrome\"), false);", "assert.deepEqual(palindrome(\"A man, a plan, a canal. Panama\"), true);", "assert.deepEqual(palindrome(\"never odd or even\"), true);", "assert.deepEqual(palindrome(\"nope\"), false);" ], "challengeSeed": "function palindrome(str) {\n // Good luck!\n return true;\n}\n\n", - "challengeEntryPoint": "palindrome(\"eye\");", - "bonfireNumber": 1, - "challengeEntryPointNegate" : "palindrome\\([^str].*\\;" + "challengeEntryPoint": "palindrome(\"eye\");" }, { + "_id" : "aff0395860f5d3034dc0bfc9", "name": "Validate US Telephone Numbers", - "difficulty": 3, + "difficulty": "3", "description": [ + "Return true if the passed string is a valid US phone number", "The user may fill out the form field any way they choose as long as it is a valid US number. The following are all valid formats for US numbers:", "555-555-5555, (555)555-5555, (555) 555-5555, 555 555 5555, 5555555555, 1 555 555 5555", "For this challenge you will be presented with a string such as \"800-692-7753\" or \"8oo-six427676;laskdjf\". Your job is to validate or reject the US phone number based on any combination of the formats provided above. The area code is required. If the country code code is provided, you must confirm that the country code is \"1\". Return true if the string is a valid US phone number; otherwise false." ], - "publicTests": [ + "tests": [ "expect(telephoneCheck(\"555-555-5555\")).to.be.a(\"boolean\");", + "assert.deepEqual(telephoneCheck(\"1 555-555-5555\"), true);", + "assert.deepEqual(telephoneCheck(\"1 (555) 555-5555\"), true);", + "assert.deepEqual(telephoneCheck(\"5555555555\"), true);", + "assert.deepEqual(telephoneCheck(\"555-555-5555\"), true);", + "assert.deepEqual(telephoneCheck(\"(555)555-5555\"), true);", + "assert.deepEqual(telephoneCheck(\"1(555)555-5555\"), true);", + "assert.deepEqual(telephoneCheck(\"1 555 555 5555\"), true);", "assert.deepEqual(telephoneCheck(\"555-555-5555\"), true);", "assert.deepEqual(telephoneCheck(\"1 456 789 4444\"), true);", - "assert.deepEqual(telephoneCheck(\"123**&!!asdf#\"), false);" - ], - "privateTests": [ + "assert.deepEqual(telephoneCheck(\"123**&!!asdf#\"), false);", "assert.deepEqual(telephoneCheck(\"55555555\"), false);", "assert.deepEqual(telephoneCheck(\"(6505552368)\"), false);", "assert.deepEqual(telephoneCheck(\"2 (757) 622-7382\"), false);", @@ -52,9 +74,97 @@ "assert.deepEqual(telephoneCheck(\"2(757)622-7382\"), false);" ], "challengeSeed": "function telephoneCheck(str) {\n // Good luck!\n return true;\n}\n\n", - "challengeEntryPoint": "telephoneCheck(\"555-555-5555\");", - "bonfireNumber": 2, - "challengeEntryPointNegate" : "palindrome\\([^str].*\\;" + "challengeEntryPoint": "telephoneCheck(\"555-555-5555\");" + }, + { + "_id": "a202eed8fc186c8434cb6d61", + "name": "Reverse a String", + "difficulty": "1", + "tests": [ + "expect(reverseString('hello')).to.be.a('String');", + "expect(reverseString('hello')).to.equal('olleh');", + "expect(reverseString('Howdy')).to.equal('ydwoH');", + "expect(reverseString('Greetings from Earth')).to.equal('htraE morf sgniteerG');" + ], + "description": [ + "Reverse the provided string.", + "You may need to turn the string into an array before you can reverse it.", + "Your result must be a string." + ], + "challengeEntryPoint": "reverseString('hello');", + "challengeSeed": "function reverseString(str) {\n return str;\r\n}" + }, + { + "_id": "a302f7aae1aa3152a5b413bc", + "name": "Factorialize a Number", + "tests": [ + "expect(factorialize(5)).to.be.a(\"Number\");", + "expect(factorialize(5)).to.equal(120);", + "expect(factorialize(10)).to.equal(3628800);", + "expect(factorialize(20)).to.equal(2432902008176640000);" + ], + "difficulty": "1", + "description": [ + "Return the factorial of the provided integer.", + "If the integer is represented with the letter n, a factorial is the product of all positive integers less than or equal to n.", + "Factorials are often represented with the shorthand notation n!", + "For example: 5! = 1 * 2 * 3 * 4 * 5 = 120f" + ], + "challengeSeed": "function factorialize(num) {\n return num;\r\n}", + "challengeEntryPoint": "factorialize(5);" + }, + { + "_id": "a26cbbe9ad8655a977e1ceb5", + "name": "Find the Longest Word in a String", + "difficulty": "1", + "description": [ + "Return the length of the longest word in the provided sentence.", + "Your response should be a number." + ], + "challengeEntryPoint": "findLongestWord('The quick brown fox jumped over the lazy dog');", + "challengeSeed": "function findLongestWord(str) {\n return str.length;\r\n}", + "tests": [ + "expect(findLongestWord('The quick brown fox jumped over the lazy dog')).to.be.a('Number');", + "expect(findLongestWord('The quick brown fox jumped over the lazy dog')).to.equal(6);", + "expect(findLongestWord('May the force be with you')).to.equal(5);", + "expect(findLongestWord('Google do a barrel roll')).to.equal(6);", + "expect(findLongestWord('What is the average airspeed velocity of an unladen swallow')).to.equal(8);" + ] + }, + { + "_id": "a3566b1109230028080c9345", + "name": "Sum All Numbers in a Range", + "difficulty": "2", + "description": [ + "We'll pass you an array of two numbers. Return the sum those two numbers and all numbers between them.", + "The lowest number will not always come first." + ], + "challengeEntryPoint": "sumAll([1, 4]);", + "challengeSeed": "function sumAll(arr) {\n return(1);\r\n}", + "tests": [ + "expect(sumAll([1, 4])).to.be.a('Number');", + "expect(sumAll([1, 4])).to.equal(10);", + "expect(sumAll([4, 1])).to.equal(10);", + "expect(sumAll([5, 10])).to.equal(45);", + "expect(sumAll([10, 5])).to.equal(45);" + ] + }, + { + "_id": "ab6137d4e35944e21037b769", + "name": "Title Case a Sentence", + "difficulty": "1", + "description": [ + "Return the provided string with the first letter of each word capitalized.", + "For the purpose of this exercise, you should also capitalize connecting words like 'the' and 'of'." + ], + "challengeEntryPoint": "titleCase(\"I'm a little tea pot\")", + "challengeSeed": "function titleCase(str) {\n return str;\r\n}", + "tests": [ + "expect(titleCase(\"I'm a little tea pot\")).to.be.a('String');", + "expect(titleCase(\"I'm a little tea pot\")).to.equal(\"I'm A Little Tea Pot\");", + "expect(titleCase(\"sHoRt AnD sToUt\")).to.equal(\"Short And Stout\");", + "expect(titleCase(\"HERE IS MY HANDLE HERE IS MY SPOUT\")).to.equal(\"Here Is My Handle Here Is My Spout\");" + ] } ] diff --git a/seed_data/challenge-hashes b/seed_data/challenge-hashes index 8b6ddfa36d..64932960ca 100644 --- a/seed_data/challenge-hashes +++ b/seed_data/challenge-hashes @@ -1,8 +1,7 @@ /* -"aaa48de84e1ecc7c742e1124" -"ff0395860f5d3034dc0bfc94" -"7123c8c441eddfaeb5bdef0d" -"c3a4d278b9e760a0ffe8321f" + + + "aceca143b92049a4392a859e" "ce9394f67d413734758e27e4" "1369953ef6f03098cb60e2f7" diff --git a/views/account/profile.jade b/views/account/account.jade similarity index 100% rename from views/account/profile.jade rename to views/account/account.jade diff --git a/views/account/login.jade b/views/account/signin.jade similarity index 100% rename from views/account/login.jade rename to views/account/signin.jade diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 8fcf04a9ed..498f7d3a23 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -1,4 +1,4 @@ -extends ../layout +extends ../layout-wide block content script(src='/js/lib/codemirror/lib/codemirror.js') script(src='/js/lib/codemirror/addon/edit/closebrackets.js') @@ -15,9 +15,15 @@ block content script(src='/js/lib/jailed/jailed.js') script(src='/js/lib/bonfire/bonfireInit.js') .row + script(type="text/javascript"). + var tests = !{JSON.stringify(tests)}; + var challengeSeed = !{JSON.stringify(challengeSeed)}; + var challengeEntryPoint = !{JSON.stringify(challengeEntryPoint)}; + var title = !{JSON.stringify(title)}; + #mainEditorPanel.col-sm-12.col-md-7.col-xs-12 .panel.panel-primary.panel-bonfire - .panel-heading.text-center Bonfire Playground + .panel-heading.text-center Playground .panel.panel-body form.code .form-group.codeMirrorView diff --git a/views/bonfire/generator.jade b/views/bonfire/generator.jade new file mode 100644 index 0000000000..63f53f7be1 --- /dev/null +++ b/views/bonfire/generator.jade @@ -0,0 +1,45 @@ +extends ../layout +block content + .row + .col-md-offset-2.col-md-8.col-lg-offset-2.col-lg-8.text-center + + h1 JSON generator for bonfire challenges - just fill the form out and get the correct format back + .col-xs-12.col-sm-12.col-md-offset-1.col-md-10.col-lg-offset-1-col-lg-10 + .panel + form.form-horizontal(method="POST", action="/bonfire-json-generator", name="bonfireInfo") + .form-group + label.col-sm-2.control-label(for='name') name: + .col-sm-10 + input#name.form-control(type='text', placeholder='name', name="name") + .form-group + label.col-sm-2.control-label(for='difficultyField') difficulty: + .col-sm-10 + label.radio-inline 1 + input#inlineRadio1(type='radio', name='difficulty', value='1') + label.radio-inline 2 + input#inlineRadio2(type='radio', name='difficulty', value='2') + label.radio-inline 3 + input#inlineRadio3(type='radio', name='difficulty', value='3') + label.radio-inline 4 + input#inlineRadio4(type='radio', name='difficulty', value='4') + label.radio-inline 5 + input#inlineRadio5(type='radio', name='difficulty', value='5') + .form-group + label.col-sm-2.control-label.wrappable(for='description') description: + .col-sm-10 + textarea#description.form-control(name="description", placeholder="Separate sentences by exactly one space only. Do not add in line breaks.") + .form-group + label.col-sm-2.control-label.wrappable(for='challengeSeed') challengeSeed: + .col-sm-10 + textarea#challengeSeed.form-control(name="challengeSeed", rows=5) + .form-group + label.col-sm-2.control-label.wrappable(for='challengeEntryPoint') challenge entrypoint: + .col-sm-10 + textarea#name.form-control(name="challengeEntryPoint", rows=1, type='text', placeholder="palindrome(\"eye\");") + .form-group + label.col-sm-2.control-label.wrappable(for='tests') tests: + .col-sm-10 + textarea#tests.form-control(name="tests", rows=5, placeholder="Separate tests by a newline.") + .form-group + .col-sm-offset-2.col-sm-10 + input.btn.btn-default(type='submit', value="submit") diff --git a/views/bonfire/public-generator.jade b/views/bonfire/public-generator.jade new file mode 100644 index 0000000000..c3761fe674 --- /dev/null +++ b/views/bonfire/public-generator.jade @@ -0,0 +1,44 @@ +extends ../layout +block content + .row + .col-md-offset-2.col-md-8.col-lg-offset-2.col-lg-8.text-center + h1 Welcome to the challenge generator. Test your ideas out and see them live in bonfire! + .col-xs-12.col-sm-12.col-md-offset-1.col-md-10.col-lg-offset-1-col-lg-10 + .panel + form.form-horizontal(method="POST", action="/bonfire-challenge-generator", name="bonfireInfo") + .form-group + label.col-sm-2.control-label(for='name') name: + .col-sm-10 + input#name.form-control(type='text', placeholder='name', name="name") + .form-group + label.col-sm-2.control-label(for='difficultyField') difficulty: + .col-sm-10 + label.radio-inline 1 + input#inlineRadio1(type='radio', name='difficulty', value='1') + label.radio-inline 2 + input#inlineRadio2(type='radio', name='difficulty', value='2') + label.radio-inline 3 + input#inlineRadio3(type='radio', name='difficulty', value='3') + label.radio-inline 4 + input#inlineRadio4(type='radio', name='difficulty', value='4') + label.radio-inline 5 + input#inlineRadio5(type='radio', name='difficulty', value='5') + .form-group + label.col-sm-2.control-label.wrappable(for='description') description: + .col-sm-10 + textarea#description.form-control(name="description", placeholder="Separate sentences by exactly one space only. Do not add in line breaks.") + .form-group + label.col-sm-2.control-label.wrappable(for='challengeSeed') challengeSeed: + .col-sm-10 + textarea#challengeSeed.form-control(name="challengeSeed", rows=5) + .form-group + label.col-sm-2.control-label.wrappable(for='challengeEntryPoint') challenge entrypoint: + .col-sm-10 + textarea#name.form-control(name="challengeEntryPoint", rows=1, type='text', placeholder="palindrome(\"eye\");") + .form-group + label.col-sm-2.control-label.wrappable(for='tests') tests: + .col-sm-10 + textarea#tests.form-control(name="tests", rows=5, placeholder="Separate tests by a newline.") + .form-group + .col-sm-offset-2.col-sm-10 + input.btn.btn-default(type='submit', value="submit") diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 6b51fe72f1..259634d536 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -1,4 +1,4 @@ -extends ../layout +extends ../layout-wide block content script(src='/js/lib/codemirror/lib/codemirror.js') @@ -18,33 +18,123 @@ block content .row - #mainEditorPanel.col-sm-12.col-md-7.col-xs-12 - .panel.panel-primary.panel-bonfire - .panel-heading.text-center #{name} (Level #{difficulty} bonfire) - .panel.panel-body - .well - .text-justify.bonfire-instructions - for sentence in description - p.bonfire-instructions!= sentence - form.code - .form-group.codeMirrorView - textarea#codeEditor(autofocus=true) - #testCreatePanel.col-sm-12.col-md-5.col-xs-12 - .panel.panel-primary.panel-bonfire - .panel-heading.text-center Output - .panel.panel-body - #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) - br - form.code - .form-group.codeMirrorView - textarea#codeOutput - br - ul#testSuite.list-group - br - script(type="text/javascript"). - var publicTests = !{JSON.stringify(publicTests)}; - var privateTests = !{JSON.stringify(privateTests)}; - var challengeSeed = !{JSON.stringify(challengeSeed)}; - var challengeEntryPoint = !{JSON.stringify(challengeEntryPoint)}; - var challengeEntryPointNegate = !{JSON.stringify(challengeEntryPointNegate)}; - script(src='/js/lib/bonfire/bonfireFramework.js') + .col-xs-12.col-sm-12.col-md-4.bonfire-top + #testCreatePanel + + h2.text-center= name + h2.text-center.bonfire-flames + if (difficulty == "0") + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + if (difficulty == "1") + i.ion-ios-flame + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + if (difficulty == "2") + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame-outline + i.ion-ios-flame-outline + i.ion-ios-flame-outline + if (difficulty == "3") + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame-outline + i.ion-ios-flame-outline + if (difficulty == "4") + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame-outline + if (difficulty == "5") + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + i.ion-ios-flame + .well + .row.text-center + row.text-center + .col-xs-12 + .bonfire-instructions + = brief + #brief-instructions.col-xs-12 + button#more-info.btn.btn-info + span.ion-help-circled + | More information + #long-instructions.row.text-center.hide + .col-xs-12 + .bonfire-instructions + for sentence in details + p!= sentence + button#less-info.btn.btn-info + span.ion-help-circled + | Less information + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) + br + form.code + .form-group.codeMirrorView + textarea#codeOutput + br + #testSuite + br + script(type="text/javascript"). + var tests = !{JSON.stringify(tests)}; + var challengeSeed = !{JSON.stringify(challengeSeed)}; + var challengeEntryPoint = !{JSON.stringify(challengeEntryPoint)}; + var passedBonfireHash = !{JSON.stringify(bonfireHash)}; + .col-xs-12.col-sm-12.col-md-8 + #mainEditorPanel + form.code + .form-group.codeMirrorView + textarea#codeEditor(autofocus=true) + script(src='/js/lib/bonfire/bonfireFramework.js') + + + + #complete-bonfire-dialog.modal(tabindex='-1') + .modal-dialog.animated.zoomIn.fast-animation + .modal-content + .modal-header.challenge-list-header= compliment + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body(ng-controller="pairedWithController") + + .text-center + .animated.zoomInDown.delay-half + span.landing-icon.ion-checkmark-circled.text-primary + - if (cc) + form.form-horizontal(novalidate='novalidate', name='completedWithForm') + .form-group.text-center + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn + // extra field to distract password tools like lastpass from injecting css into our username field + input.form-control(ng-show="false") + input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser", autofocus) + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") + alert(type='danger') + span.ion-close-circled + | Username not found + + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, aria-hidden='true', ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Take me to my next challenge + + + - if (points && points > 2) + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{number}&hashtags=learntocode, javascript" target="_blank") + i.fa.fa-twitter   + = phrase + - else + a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + + #all-bonfires-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.challenge-list-header Bonfires + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/bonfires diff --git a/views/challenges/show.jade b/views/challenges/show.jade index 36d9dc8e0d..ddb2fa6dce 100644 --- a/views/challenges/show.jade +++ b/views/challenges/show.jade @@ -16,19 +16,19 @@ block content .btn.btn-primary.btn-big.btn-block.completed-challenge I've completed this challenge .ten-pixel-break .btn.btn-success.btn-big.btn-block.all-challenges Show me all the challenges - #complete-dialog.modal(tabindex='-1') + #complete-challenge-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content - .modal-header.challenge-list-header Nicely done! + .modal-header.challenge-list-header= compliment a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body .text-center .animated.zoomInDown.delay-half span.landing-icon.ion-checkmark-circled.text-primary - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-button(name='_csrf', value=_csrf, aria-hidden='true') Take me to my next challenge + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-challenge-button(name='_csrf', value=_csrf, aria-hidden='true') Take me to my next challenge - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20Free%20Code%20Camp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{number}&hashtags=learntocode, javascript" target="_blank") + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{number}&hashtags=learntocode, javascript" target="_blank") i.fa.fa-twitter   = phrase - else diff --git a/views/layout-wide.jade b/views/layout-wide.jade new file mode 100644 index 0000000000..0bed8cac5f --- /dev/null +++ b/views/layout-wide.jade @@ -0,0 +1,33 @@ +doctype html +html(ng-app='profileValidation', lang='en') + head + script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") + script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js") + script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") + link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicon.ico') + link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') + link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') + link(rel='stylesheet', href='//code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css') + include partials/meta + title #{title} | Free Code Camp + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge') + meta(name='viewport', content='width=device-width, initial-scale=1.0') + meta(name='csrf-token', content=_csrf) + != css('main') + + body.no-top-and-bottom-margins.full-screen-body-background + include partials/navbar-wide + include partials/flash + block content + include partials/footer + != js('application') +script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + ga('create', 'UA-55446531-1', 'auto'); + ga('require', 'displayfeatures'); + ga('send', 'pageview'); +script(src="//cdn.optimizely.com/js/999692993.js") \ No newline at end of file diff --git a/views/layout.jade b/views/layout.jade index de6f552728..dfddef33b4 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -16,14 +16,13 @@ html(ng-app='profileValidation', lang='en') meta(name='csrf-token', content=_csrf) != css('main') - body - include partials/navbar - + body.top-and-bottom-margins + include partials/navbar-narrow .container include partials/flash block content include partials/footer - != js('application') + != js('application') script. (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 1adbb74b8d..cabd9ecf93 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -1 +1,7 @@ -//TODO: STUFF \ No newline at end of file +h3 + ol(start='0') + for bonfire in bonfires + li + a(href="/bonfires/#{bonfire.bonfireNumber}", class="#{ (cc && cc[bonfire.bonfireNumber] > 0) ? 'strikethrough' : '' }") #{bonfire.name} + |   (Level #{bonfire.difficulty}) +a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((cc && cc[53] === 0) || (!cc)) ? 'disabled' : '' }") I'm done with all the challenges! \ No newline at end of file diff --git a/views/partials/footer.jade b/views/partials/footer.jade index 0e1ccbe8a3..8651171bf6 100644 --- a/views/partials/footer.jade +++ b/views/partials/footer.jade @@ -15,4 +15,6 @@ |   a.ion-information-circled(title="About Free Code Camp", href="/learn-to-code") |   - a.ion-locked(title="Free Code Camp's Privacy Policy", href="/privacy") \ No newline at end of file + a.ion-locked(title="Free Code Camp's Privacy Policy", href="/privacy") + |   + a.ion-code-working(title="Bonfire Coding Playground", href="/playground") \ No newline at end of file diff --git a/views/partials/navbar-narrow.jade b/views/partials/navbar-narrow.jade new file mode 100644 index 0000000000..b8ff7267f9 --- /dev/null +++ b/views/partials/navbar-narrow.jade @@ -0,0 +1,3 @@ +nav.navbar.navbar-default.navbar-fixed-top.nav-height + .container + include ./navbar \ No newline at end of file diff --git a/views/partials/navbar-wide.jade b/views/partials/navbar-wide.jade new file mode 100644 index 0000000000..96165a030d --- /dev/null +++ b/views/partials/navbar-wide.jade @@ -0,0 +1,2 @@ +nav.navbar.navbar-default.navbar-fixed-top.nav-height + include ./navbar \ No newline at end of file diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 6a026ed505..8388ea2ba6 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -1,46 +1,44 @@ -nav.navbar.navbar-default.navbar-fixed-top.nav-height - .container - .navbar-header - button.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') +.navbar-header + button.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') span.sr-only Toggle navigation span.icon-bar span.icon-bar span.icon-bar - a.navbar-brand(href='/') + a.navbar-brand(href='/') img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt='learn to code javascript at Free Code Camp logo') - .collapse.navbar-collapse - ul.nav.navbar-nav.navbar-right.hamburger-dropdown +.collapse.navbar-collapse + ul.nav.navbar-nav.navbar-right.hamburger-dropdown - if (!cc) - li - a(href='/challenges/0') Challenges + li + a(href='/challenges/0') Challenges - else - li - a(href='/') Challenges + li + a(href='/') Challenges - if (!cc || (cc && cc[1] < 1)) - li - a(href='/challenges/1') Chat + li + a(href='/challenges/1') Chat - else - li - a(href='http://chat.freecodecamp.com' target='_blank') Chat + li + a(href='http://chat.freecodecamp.com' target='_blank') Chat - if (!cc || (cc && cc[2] < 1)) - li - a(href='/challenges/2') Forum + li + a(href='/challenges/2') Forum - else - li - a(href='http://forum.freecodecamp.com' target='_blank') Forum + li + a(href='http://forum.freecodecamp.com' target='_blank') Forum li - a(href='/bonfire') Bonfire - li       + a(href='/bonfires') Bonfires if !user - li - a.btn.signup-btn.signup-btn-nav(href='/login') Sign in + li       + li + a.btn.signup-btn.signup-btn-nav(href='/login') Sign in else - li - a(href='/account') [ #{user.points} ] + li + a(href='/account') [ #{user.points} ] .hidden-xs - if user.profile.picture - a(href='/account') - img.profile-picture.float-right(src='#{user.profile.picture}') - else - a(href='/account') - img.profile-picture.float-right(src='#{user.gravatar(60)}') \ No newline at end of file + if user.profile.picture + a(href='/account') + img.profile-picture.float-right(src='#{user.profile.picture}') + else + a(href='/account') + img.profile-picture.float-right(src='#{user.gravatar(60)}') \ No newline at end of file diff --git a/views/resources/about.jade b/views/resources/learn-to-code.jade similarity index 100% rename from views/resources/about.jade rename to views/resources/learn-to-code.jade diff --git a/views/resources/live-pair-programming.jade b/views/resources/live-pair-programming.jade index b6c3a18727..92a911cc69 100644 --- a/views/resources/live-pair-programming.jade +++ b/views/resources/live-pair-programming.jade @@ -1,91 +1,50 @@ -doctype html -html(ng-app='profileValidation', lang='en') - head - script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") - script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js") - script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") - link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicon.ico') - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') - link(rel='stylesheet', href='//code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css') - include ../partials/meta - title #{title} | Free Code Camp - meta(charset='utf-8') - meta(http-equiv='X-UA-Compatible', content='IE=edge') - meta(name='viewport', content='width=device-width, initial-scale=1.0') - meta(name='csrf-token', content=_csrf) - != css('main') - body - block content - include ../partials/navbar - .panel.panel-primary - .panel-heading.landing-panel-heading.text-center Live Pair Programming - .panel-body - .landing-panel-body.text-center - h2 We live pair program every Tuesday from 9 pm to 10 pm EST (Eastern Standard Time). - h2 Our next session will be January 27th, 2015 at 9 p.m. EST! - h2 Join the discussion in our   - a(href="chat.freecodecamp.com", target="_blank") FreeCodeCamp chat room. - h2 Watch the live stream below or on our   - a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel - | . - .row - .col-md-8.col-xs-12 - .embed-responsive.embed-responsive-16by9 - iframe(src='http://www.twitch.tv/freecodecamp/embed', frameborder='0', scrolling='no') - .col-md-4.col-xs-12 - .visible-sm.visible-xs - .embed-responsive.embed-responsive-16by9 - iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') - .visible-md.visible-lg - .embed-responsive.embed-responsive-twitch-chat - iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') +extends ../layout-wide +block content + include ../partials/navbar-wide + .panel + .panel-body + .landing-panel-body.text-center + h1 Live Pair Programming + h2 We live pair program every Tuesday from 9 pm to 10 pm EST (Eastern Standard Time). + h2 Our next session will be January 27th, 2015 at 9 p.m. EST! + h2 Watch the live stream below or on our   + a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel + | . + .row + .col-md-8.col-xs-12 + .embed-responsive.embed-responsive-16by9 + iframe(src='http://www.twitch.tv/freecodecamp/embed', frameborder='0', scrolling='no') + .col-md-4.col-xs-12 + .visible-sm.visible-xs + .embed-responsive.embed-responsive-16by9 + iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') + .visible-md.visible-lg + .embed-responsive.embed-responsive-twitch-chat + iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') - br - .panel.panel-primary - .panel-heading.landing-panel-heading.text-center Previous Live Pair Programming Sessions - .panel-body - .landing-panel-body.text-center - .col-xs-12 - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA - h3 Got 3 minutes? Learn to code with us! - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br - br - include ../partials/footer - != js('application') - script. - (function (i, s, o, g, r, a, m) { - i['GoogleAnalyticsObject'] = r; - i[r] = i[r] || function () { - (i[r].q = i[r].q || []).push(arguments) - }, i[r].l = 1 * new Date(); - a = s.createElement(o), - m = s.getElementsByTagName(o)[0]; - a.async = 1; - a.src = g; - m.parentNode.insertBefore(a, m) - })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); - ga('create', 'UA-55446531-1', 'auto'); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); - script(src="//cdn.optimizely.com/js/999692993.js") + h1 Previous Live Pair Programming Sessions + .col-xs-12 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw') + h3.wrappable link:   + a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0') + h3.wrappable link:   + a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs') + h3.wrappable link:   + a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE') + h3.wrappable link:   + a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA') + h3.wrappable link:   + a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA + h3 Got 3 minutes? Learn to code with us! + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) + br + br \ No newline at end of file