diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..13ef57c654 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/app.js b/app.js index 73e19ad850..5c6835918c 100644 --- a/app.js +++ b/app.js @@ -33,28 +33,29 @@ var express = require('express'), connectAssets = require('connect-assets'), request = require('request'), + /** + * Controllers (route handlers). + */ + homeController = require('./controllers/home'), + resourcesController = require('./controllers/resources'), + userController = require('./controllers/user'), + contactController = require('./controllers/contact'), + nonprofitController = require('./controllers/nonprofits'), + bonfireController = require('./controllers/bonfire'), + coursewareController = require('./controllers/courseware'), + fieldGuideController = require('./controllers/fieldGuide'), + challengeMapController = require('./controllers/challengeMap'), - /** - * Controllers (route handlers). - */ - homeController = require('./controllers/home'), - challengesController = require('./controllers/challenges'), - resourcesController = require('./controllers/resources'), - userController = require('./controllers/user'), - contactController = require('./controllers/contact'), - bonfireController = require('./controllers/bonfire'), - coursewareController = require('./controllers/courseware'), + /** + * Stories + */ + storyController = require('./controllers/story'); - /** - * Stories - */ - storyController = require('./controllers/story'), - - /** - * API keys and Passport configuration. - */ - secrets = require('./config/secrets'), - passportConf = require('./config/passport'); + /** + * API keys and Passport configuration. + */ + secrets = require('./config/secrets'), + passportConf = require('./config/passport'); /** * Create Express server. @@ -66,9 +67,9 @@ var app = express(); */ mongoose.connect(secrets.db); mongoose.connection.on('error', function () { - console.error( - 'MongoDB Connection Error. Please make sure that MongoDB is running.' - ); + console.error( + 'MongoDB Connection Error. Please make sure that MongoDB is running.' + ); }); /** @@ -83,21 +84,21 @@ app.use(compress()); var oneYear = 31557600000; app.use(express.static(__dirname + '/public', {maxAge: oneYear})); app.use(connectAssets({ - paths: [ - path.join(__dirname, 'public/css'), - path.join(__dirname, 'public/js') - ], - helperContext: app.locals + paths: [ + path.join(__dirname, 'public/css'), + path.join(__dirname, 'public/js') + ], + helperContext: app.locals })); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(expressValidator({ - customValidators: { - matchRegex: function (param, regex) { - return regex.test(param); + customValidators: { + matchRegex: function (param, regex) { + return regex.test(param); + } } - } })); app.use(methodOverride()); app.use(cookieParser()); @@ -119,9 +120,9 @@ app.use(helmet.xssFilter()); app.use(helmet.noSniff()); app.use(helmet.xframe()); app.use(function(req, res, next) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); }); var trusted = [ @@ -164,44 +165,48 @@ var trusted = [ ]; app.use(helmet.contentSecurityPolicy({ - defaultSrc: trusted, - scriptSrc: ['*.optimizely.com', '*.aspnetcdn.com'].concat(trusted), - 'connect-src': [ - 'ws://*.rafflecopter.com', - 'wss://*.rafflecopter.com', - 'https://*.rafflecopter.com', - 'ws://www.freecodecamp.com', - 'http://www.freecodecamp.com' - ].concat(trusted), - styleSrc: trusted, - imgSrc: [ - '*.evernote.com', - '*.amazonaws.com', - 'data:', - '*.licdn.com', - '*.gravatar.com', - '*.akamaihd.net', - 'graph.facebook.com', - '*.githubusercontent.com', - '*.googleusercontent.com', - '*' /* allow all input since we have user submitted images for public profile*/ - ].concat(trusted), - fontSrc: ['*.googleapis.com'].concat(trusted), - mediaSrc: [ - '*.amazonaws.com', - '*.twitter.com' - ].concat(trusted), - frameSrc: [ - '*.gitter.im', - '*.gitter.im https:', - '*.vimeo.com', - '*.twitter.com', - '*.rafflecopter.com', - '*.ghbtns.com' - ].concat(trusted), - reportOnly: false, // set to true if you only want to report errors - setAllHeaders: false, // set to true if you want to set all headers - safari5: false // set to true if you want to force buggy CSP in Safari 5 + defaultSrc: trusted, + scriptSrc: [ + '*.optimizely.com', + '*.aspnetcdn.com', + '*.d3js.org', + ].concat(trusted), + 'connect-src': [ + 'ws://*.rafflecopter.com', + 'wss://*.rafflecopter.com', + 'https://*.rafflecopter.com', + 'ws://www.freecodecamp.com', + 'http://www.freecodecamp.com' + ].concat(trusted), + styleSrc: trusted, + imgSrc: [ + '*.evernote.com', + '*.amazonaws.com', + 'data:', + '*.licdn.com', + '*.gravatar.com', + '*.akamaihd.net', + 'graph.facebook.com', + '*.githubusercontent.com', + '*.googleusercontent.com', + '*' /* allow all input since we have user submitted images for public profile*/ + ].concat(trusted), + fontSrc: ['*.googleapis.com'].concat(trusted), + mediaSrc: [ + '*.amazonaws.com', + '*.twitter.com' + ].concat(trusted), + frameSrc: [ + '*.gitter.im', + '*.gitter.im https:', + '*.vimeo.com', + '*.twitter.com', + '*.rafflecopter.com', + '*.ghbtns.com' + ].concat(trusted), + reportOnly: false, // set to true if you only want to report errors + setAllHeaders: false, // set to true if you want to set all headers + safari5: false // set to true if you want to force buggy CSP in Safari 5 })); app.use(function (req, res, next) { @@ -211,13 +216,15 @@ app.use(function (req, res, next) { }); app.use(function (req, res, next) { - // Remember original destination before login. - var path = req.path.split('/')[1]; - if (/auth|login|logout|signup|fonts|favicon/i.test(path)) { - return next(); - } - req.session.returnTo = req.path; - next(); + // Remember original destination before login. + var path = req.path.split('/')[1]; + if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) { + return next(); + } else if (/\/stories\/comments\/\w+/i.test(req.path)) { + return next(); + } + req.session.returnTo = req.path; + next(); }); app.use( @@ -231,23 +238,50 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 })); */ app.get('/', homeController.index); -app.get('/privacy', resourcesController.privacy); -app.get('/jquery-exercises', resourcesController.jqueryExercises); -app.get('/chat', resourcesController.chat); -app.get('/live-pair-programming', resourcesController.livePairProgramming); -app.get('/install-screenhero', resourcesController.installScreenHero); -app.get('/javascript-in-your-inbox', resourcesController.javaScriptInYourInbox); -app.get('/guide-to-our-nonprofit-projects', resourcesController.guideToOurNonprofitProjects); -app.get('/chromebook', resourcesController.chromebook); -app.get('/styleguide', resourcesController.styleguide); -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('/nodeschool-challenges', resourcesController.nodeSchoolChallenges); -app.get('/stats', function(req, res) { - res.redirect(301, '/learn-to-code'); + +app.get('/privacy', function(req, res) { + res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); }); + +app.get('/nonprofit-project-instructions', function(req, res) { + res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); +}); + +app.get('/chat', resourcesController.chat); + +app.get('/twitch', resourcesController.twitch); + +app.get('/map', challengeMapController.challengeMap); + +app.get('/live-pair-programming', function(req, res) { + res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); +}); + +app.get('/install-screenhero', function(req, res) { + res.redirect(301, '/field-guide/install-screenhero'); +}); + +app.get('/guide-to-our-nonprofit-projects', function(req, res) { + res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); +}); + +app.get('/chromebook', function(req, res) { + res.redirect(301, '/field-guide/chromebook'); +}); + +app.get('/deploy-a-website', function(req, res) { + res.redirect(301, '/field-guide/deploy-a-website'); +}); + +app.get('/gmail-shortcuts', function(req, res) { + res.redirect(301, '/field-guide/gmail-shortcuts'); +}); + +app.get('/nodeschool-challenges', function(req, res) { + res.redirect(301, '/field-guide/nodeschool-challenges'); +}); + + app.get('/news', function(req, res) { res.redirect(301, '/stories/hot'); }); @@ -256,25 +290,76 @@ 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); + 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.postSignin); app.get('/nonprofits', contactController.getNonprofitsForm); app.post('/nonprofits', contactController.postNonprofitsForm); +/** + * Nonprofit Project routes. + */ + +app.get('/nonprofits', nonprofitController.nonprofitsHome); + +app.get('/nonprofits/directory', nonprofitController.nonprofitsDirectory); + +app.get('/nonprofits/are-you-with-a-registered-nonprofit', nonprofitController.areYouWithARegisteredNonprofit); + +app.get('/nonprofits/are-there-people-that-are-already-benefiting-from-your-services', nonprofitController.areTherePeopleThatAreAlreadyBenefitingFromYourServices); + +app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); + +app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); + +app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); + +app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); + +app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); + +app.get('/nonprofits/tell-us-your-name', nonprofitController.tellUsYourName); + +app.get('/nonprofits/tell-us-your-email', nonprofitController.tellUsYourEmail); + +app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', nonprofitController.yourNonprofitProjectApplicationHasBeenSubmitted); + +app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); + +app.get('/nonprofits/getNonprofitList', nonprofitController.showAllNonprofits); + +app.get('/nonprofits/interested-in-nonprofit/:nonprofitName', nonprofitController.interestedInNonprofit); + +app.get( + '/nonprofits/:nonprofitName', + nonprofitController.returnIndividualNonprofit +); + app.get( '/done-with-first-100-hours', passportConf.isAuthenticated, @@ -285,11 +370,11 @@ app.post( passportConf.isAuthenticated, contactController.postDoneWithFirst100Hours ); -app.get( - '/nonprofit-project-instructions', - passportConf.isAuthenticated, - resourcesController.nonprofitProjectInstructions -); +//app.get( +// '/nonprofit-project-instructions', +// passportConf.isAuthenticated, +// resourcesController.nonprofitProjectInstructions +//); app.post( '/update-progress', passportConf.isAuthenticated, @@ -351,7 +436,7 @@ app.get('/api/slack', function(req, res) { }); /** - * Main routes. + * Camper News routes. */ app.get( '/stories/hotStories', @@ -436,19 +521,8 @@ app.post( storyController.upvote ); -/** - * Challenge related routes - */ -app.get( - '/challenges/', - challengesController.returnNextChallenge -); -app.get( - '/challenges/:challengeNumber', - challengesController.returnChallenge -); - app.all('/account', passportConf.isAuthenticated); + app.get('/account/api', userController.getAccountAngular); /** @@ -456,72 +530,86 @@ app.get('/account/api', userController.getAccountAngular); */ app.get('/api/github', resourcesController.githubCalls); + app.get('/api/blogger', resourcesController.bloggerCalls); + app.get('/api/trello', resourcesController.trelloCalls); /** * Bonfire related routes */ + +app.get('/field-guide/getFieldGuideList', fieldGuideController.showAllFieldGuides); + 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.post('/bonfire-challenge-generator', bonfireController.testBonfire); + app.get( '/bonfires/:bonfireName', bonfireController.returnIndividualBonfire ); + app.get('/bonfire', function(req, res) { res.redirect(301, '/playground'); }); app.post('/completed-bonfire/', bonfireController.completedBonfire); +/** + * Field Guide related routes + */ + + +app.get('/field-guide/:fieldGuideName', fieldGuideController.returnIndividualFieldGuide); + +app.get('/field-guide', fieldGuideController.returnNextFieldGuide); + +app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide); + + /** * Courseware related routes */ -app.get('/coursewares/', coursewareController.returnNextCourseware); +app.get('/challenges/', coursewareController.returnNextCourseware); + app.get( - '/coursewares/:coursewareName', - coursewareController.returnIndividualCourseware + '/challenges/:coursewareName', + coursewareController.returnIndividualCourseware ); + app.post('/completed-courseware/', coursewareController.completedCourseware); +app.post('/completed-zipline-or-basejump', + coursewareController.completedZiplineOrBasejump); + // 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); -app.post('/account/password', userController.postUpdatePassword); -app.post('/account/delete', userController.postDeleteAccount); -app.get('/account/unlink/:provider', userController.getOauthUnlink); -app.get('/sitemap.xml', resourcesController.sitemap); -/** - * API examples routes. - * accepts a post request. the challenge id req.body.challengeNumber - * and updates user.challengesHash & user.challengesCompleted - * - */ -app.post('/completed-challenge', function (req, res, done) { - req.user.challengesHash[parseInt(req.body.challengeNumber)] = - Math.round(+new Date() / 1000); - var timestamp = req.user.challengesHash; - var points = 0; - for (var key in timestamp) { - if (timestamp[key] > 0 && req.body.challengeNumber < 54) { - points += 1; - } - } - req.user.points = points; - req.user.save(function(err) { - if (err) { return done(err); } - res.status(200).send({ msg: 'progress saved' }); - }); -}); +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); + +app.post('/account/password', userController.postUpdatePassword); + +app.post('/account/delete', userController.postDeleteAccount); + +app.get('/account/unlink/:provider', userController.getOauthUnlink); + +app.get('/sitemap.xml', resourcesController.sitemap); /** * OAuth sign-in routes. @@ -533,6 +621,7 @@ var passportOptions = { }; app.get('/auth/twitter', passport.authenticate('twitter')); + app.get( '/auth/twitter/callback', passport.authenticate('twitter', { @@ -566,6 +655,7 @@ app.get( ); app.get('/auth/github', passport.authenticate('github')); + app.get( '/auth/github/callback', passport.authenticate('github', passportOptions), function (req, res) { @@ -577,6 +667,7 @@ app.get( '/auth/google', passport.authenticate('google', {scope: 'profile email'}) ); + app.get( '/auth/google/callback', passport.authenticate('google', passportOptions), function (req, res) { @@ -584,10 +675,6 @@ app.get( } ); -app.get('/induce-vomiting', function(req, res, next) { - next(new Error('vomiting induced')); -}); - // put this route last app.get( '/:username', @@ -617,11 +704,7 @@ if (process.env.NODE_ENV === 'development') { var accept = accepts(req); var type = accept.type('html', 'json', 'text'); - var message = 'oops! Something went wrong. Please try again later.' + - ' Twitter authentication is currently unavailable for FreeCodeCamp.' + - ' We are working with Twitter to restore functionality' + - ' as soon as possible.'; - console.log('ERROR!!', err); + var message = 'opps! Something went wrong. Please try again later'; if (type === 'html') { req.flash('errors', { msg: message }); return res.redirect('/'); diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 3bf4b48150..ee8b26a5ce 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,138 +1,148 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:bonfires'), - Bonfire = require('./../models/Bonfire'), - User = require('./../models/User'), - resources = require('./resources'), - MDNlinks = require('./../seed_data/bonfireMDNlinks'); + debug = require('debug')('freecc:cntr:bonfires'), + Bonfire = require('./../models/Bonfire'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'); + MDNlinks = require('./../seed_data/bonfireMDNlinks'); /** * Bonfire controller */ -exports.bonfireNames = function(req, res) { - res.render('bonfires/showList', { - bonfireList: resources.allBonfireNames() - }); +exports.showAllBonfires = function(req, res) { + var completedBonfires = []; + if(req.user) { + completedBonfires = req.user.completedBonfires.map(function (elem) { + return elem._id; + }); + } + var noDuplicateBonfires = R.uniq(completedBonfires); + var data = {}; + data.bonfireList = resources.allBonfireNames(); + data.completedList = noDuplicateBonfires; + res.send(data); }; exports.index = function(req, res) { - res.render('bonfire/show.jade', { - completedWith: null, - title: 'Bonfire Playground', - name: 'Bonfire Playground', - difficulty: 0, - brief: 'Feel free to play around!', - details: '', - tests: [], - challengeSeed: '', - 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' + res.render('bonfire/show.jade', { + completedWith: null, + title: 'Bonfire Playground', + name: 'Bonfire Playground', + difficulty: 0, + brief: 'Feel free to play around!', + details: '', + tests: [], + challengeSeed: '', + cc: req.user ? req.user.bonfiresHash : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliments: resources.randomCompliment(), + bonfires: [], + bonfireHash: 'test' - }); + }); }; exports.returnNextBonfire = function(req, res, next) { - if (!req.user) { - return res.redirect('../bonfires/meet-bonfire'); - } - var completed = req.user.completedBonfires.map(function (elem) { - return elem._id; - }); + if (!req.user) { + return res.redirect('../bonfires/meet-bonfire'); + } + 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(function(err) { - if (err) return next(err); - var uncompletedBonfires = req.user.uncompletedBonfires; - var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); - displayedBonfires.exec(function(err, bonfire) { - if (err) { - return next(err); - } - bonfire = bonfire.pop(); - if (bonfire === undefined) { - req.flash('errors', { - msg: "It looks like you've completed all the bonfires we have available. Good job!" - }); - return res.redirect('../bonfires/meet-bonfire'); - } - nameString = bonfire.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../bonfires/' + nameString); - }); - }); + 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) { + return next(err); + } + bonfire = bonfire.pop(); + if (bonfire === undefined) { + req.flash('errors', { + msg: "It looks like you've completed all the bonfires we have available. Good job!" + }); + return res.redirect('../bonfires/meet-bonfire'); + } + var nameString = bonfire.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../bonfires/' + nameString); + }); }; exports.returnIndividualBonfire = function(req, res, next) { - var dashedName = req.params.bonfireName; + var dashedName = req.params.bonfireName; - bonfireName = dashedName.replace(/\-/g, ' '); + var bonfireName = dashedName.replace(/\-/g, ' '); - Bonfire.find({"name" : new RegExp(bonfireName, 'i')}, function(err, bonfire) { - if (err) { - return next(err); - } + Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfire) { + if (err) { + next(err); + } - if (bonfire.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a bonfire with that name. Please double check the name." - }); + if (bonfire.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a bonfire with that name. Please double check the name." + }); - return res.redirect('/bonfires'); - } + return res.redirect('/bonfires'); + } - bonfire = bonfire.pop(); - var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../bonfires/' + dashedNameFull); - } - - res.render('bonfire/show', { - completedWith: null, - title: bonfire.name, - dashedName: dashedName, - name: bonfire.name, - difficulty: Math.floor(+bonfire.difficulty), - brief: bonfire.description[0], - details: bonfire.description.slice(1), - tests: bonfire.tests, - challengeSeed: bonfire.challengeSeed, - cc: !!req.user, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - bonfires: bonfire, - bonfireHash: bonfire._id, - MDNkeys: bonfire.MDNlinks, - MDNlinks: getMDNlinks(bonfire.MDNlinks) - - }); + bonfire = bonfire.pop(); + var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../bonfires/' + dashedNameFull); + } + res.render('bonfire/show', { + completedWith: null, + title: bonfire.name, + dashedName: dashedName, + name: bonfire.name, + difficulty: Math.floor(+bonfire.difficulty), + brief: bonfire.description[0], + details: bonfire.description.slice(1), + tests: bonfire.tests, + challengeSeed: bonfire.challengeSeed, + points: req.user ? req.user.points : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + bonfires: bonfire, + bonfireHash: bonfire._id, + MDNkeys: bonfire.MDNlinks, + MDNlinks: getMDNlinks(bonfire.MDNlinks) }); + }); }; /** - * Bonfire generator + * Bonfire Generator + * @param req Request Object + * @param res Response Object + * @returns void */ + exports.returnGenerator = function(req, res) { - res.render('bonfire/generator', { - title: null, - name: null, - difficulty: null, - brief: null, - details: null, - tests: null, - challengeSeed: null, - bonfireHash: randomString() - }); + res.render('bonfire/generator', { + title: null, + name: null, + difficulty: null, + brief: null, + details: null, + tests: null, + challengeSeed: null, + bonfireHash: randomString() + }); }; /** @@ -140,83 +150,84 @@ exports.returnGenerator = function(req, res) { */ function randomString() { - var chars = "0123456789abcdef"; - var string_length = 23; - var randomstring = 'a'; - for (var i=0; i 0) { - return elem; - } -}; + if (elem.length > 0) { + return elem; + } +} exports.publicGenerator = function(req, res) { - res.render('bonfire/public-generator'); + 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, - bonfireChallengeSeed = req.body.challengeSeed; + var bonfireName = req.body.name, + bonfireTests = req.body.tests, + bonfireDifficulty = req.body.difficulty, + bonfireDescription = req.body.description, + bonfireChallengeSeed = req.body.challengeSeed; bonfireTests = bonfireTests.split('\r\n'); bonfireDescription = bonfireDescription.split('\r\n'); bonfireTests.filter(getRidOfEmpties); @@ -224,95 +235,100 @@ exports.generateChallenge = function(req, res) { bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); - var response = { - _id: randomString(), - name: bonfireName, - difficulty: bonfireDifficulty, - description: bonfireDescription, - challengeSeed: bonfireChallengeSeed, - tests: bonfireTests - }; - res.send(response); + var response = { + _id: randomString(), + name: bonfireName, + difficulty: bonfireDifficulty, + description: bonfireDescription, + challengeSeed: bonfireChallengeSeed, + tests: bonfireTests + }; + res.send(response); }; exports.completedBonfire = function (req, res, next) { - 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; - - if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { - if (err) { - return next(err); - } else { - var index = req.user.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - req.user.points++; - req.user.uncompletedBonfires.splice(index, 1) - } - pairedWith = pairedWith.pop(); - - index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - pairedWith.points++; - 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(function (err, user) { - if (err) { - return next(err); - } - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { - res.send(true); - } - }) - }); - } - }) - } else { - - req.user.completedBonfires.push({ - _id: bonfireHash, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution - }); + var isCompletedWith = req.body.bonfireInfo.completedWith || ''; + var isCompletedDate = Math.round(+new Date()); + var bonfireHash = req.body.bonfireInfo.bonfireHash; + var isSolution = req.body.bonfireInfo.solution; + var bonfireName = req.body.bonfireInfo.bonfireName; + if (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith + .toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return next(err); + } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - req.user.points++; - req.user.uncompletedBonfires.splice(index, 1) + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1); + } + pairedWith = pairedWith.pop(); + + index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedBonfires.splice(index, 1); + } - req.user.save(function (err, user) { - if (err) { - return next(err); - } - if (user) { - debug('Saving user'); - res.send(true) - } + pairedWith.completedBonfires.push({ + _id: bonfireHash, + name: bonfireName, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: isSolution }); + + req.user.completedBonfires.push({ + _id: bonfireHash, + name: bonfireName, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: isSolution + }); + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + res.send(true); + } + }); + }); + } + }); + } else { + req.user.completedBonfires.push({ + _id: bonfireHash, + name: bonfireName, + completedWith: null, + completedDate: isCompletedDate, + solution: isSolution + }); + + var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1); } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + debug('Saving user'); + res.send(true); + } + }); + } }; diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js new file mode 100644 index 0000000000..b858bf7385 --- /dev/null +++ b/controllers/challengeMap.js @@ -0,0 +1,65 @@ +var async = require('async'), + User = require('../models/User'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Nonprofit = require('./../models/Nonprofit'), + Comment = require('./../models/Comment'), + Courseware = require('./../models/Courseware'), + resources = require('./resources'), + steps = resources.steps, + secrets = require('./../config/secrets'), + bonfires = require('../seed_data/bonfires.json'), + nonprofits = require('../seed_data/nonprofits.json'), + coursewares = require('../seed_data/coursewares.json'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); + +module.exports = { + challengeMap: function challengeMap(req, res) { + var completedBonfires = []; + var completedList = []; + + if (req.user) { + completedBonfires = req.user.completedBonfires.map(function (elem) { + return elem._id; + }); + } + + if (req.user) { + completedList = req.user.completedCoursewares.map(function (elem) { + return elem._id; + }); + } + + var noDuplicateBonfires = R.uniq(completedBonfires); + var noDuplicatedCoursewares = R.uniq(completedList); + + bonfireList = resources.allBonfireNames(); + completedBonfireList = noDuplicateBonfires; + coursewareList = resources.allCoursewareNames(); + completedCoursewareList = noDuplicatedCoursewares; + waypoints = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 2) { return challenge } + }); + ziplines = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 3) { return challenge } + }); + basejumps = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 4) { return challenge } + }); + + res.render('challengeMap/show', { + title: "A map of all Free Code Camp's Challenges", + bonfires: bonfireList, + waypoints: waypoints, + ziplines: ziplines, + basejumps: basejumps, + completedBonfireList: completedBonfireList, + completedCoursewareList: completedCoursewareList + }); + } +}; diff --git a/controllers/courseware.js b/controllers/courseware.js index 0930bf2b90..72b23b3cf6 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -2,236 +2,380 @@ var _ = require('lodash'), debug = require('debug')('freecc:cntr:courseware'), Courseware = require('./../models/Courseware'), User = require('./../models/User'), - resources = require('./resources'); + resources = require('./resources'), + R = require('ramda'), + moment = require('moment'); /** * Courseware controller */ -exports.coursewareNames = function(req, res) { - res.render('coursewares/showList', { - coursewareList: resources.allCoursewareNames() +exports.showAllCoursewares = function(req, res) { + var completedList = []; + if(req.user) { + completedList = req.user.completedCoursewares.map(function (elem) { + return elem._id; }); + } + var noDuplicatedCoursewares = R.uniq(completedList); + var data = {}; + data.coursewareList = resources.allCoursewareNames(); + data.completedList = noDuplicatedCoursewares; + res.send(data); }; exports.returnNextCourseware = function(req, res, next) { - if (!req.user) { - return res.redirect('../coursewares/start-our-challenges'); + if (!req.user) { + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + var completed = req.user.completedCoursewares.map(function (elem) { + return elem._id; + }); + + req.user.uncompletedCoursewares = resources.allCoursewareIds() + .filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; } - var completed = req.user.completedCoursewares.map(function (elem) { - return elem._id; - }); + }); + req.user.save(); - req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - - req.user.save(function(err) { - if (err) { - return next(err); - } - - var uncompletedCoursewares = req.user.uncompletedCoursewares; - - var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares[0]}); - displayedCoursewares.exec(function(err, courseware) { - if (err) { - return next(err); - } - courseware = courseware.pop(); - if (courseware === undefined) { - req.flash('errors', { - msg: "It looks like you've completed all the courses we have available. Good job!" - }) - return res.redirect('../coursewares/start-our-challenges'); - } - nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../coursewares/' + nameString); - }); - }); + var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); + var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); + displayedCoursewares.exec(function(err, courseware) { + if (err) { + return next(err); + } + + courseware = courseware.pop(); + if (courseware === undefined) { + req.flash('errors', { + msg: "It looks like you've completed all the courses we have " + + "available. Good job!" + }); + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../challenges/' + nameString); + }); }; exports.returnIndividualCourseware = function(req, res, next) { - var dashedName = req.params.coursewareName; + var dashedName = req.params.coursewareName; - coursewareName = dashedName.replace(/\-/g, ' '); + var coursewareName = dashedName.replace(/\-/g, ' '); - Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { - if (err) { - return next(err); - } - // Handle not found - if (courseware.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a challenge with that name. Please double check the name." + Courseware.find({'name': new RegExp(coursewareName, 'i')}, + function(err, courseware) { + if (err) { + next(err); + } + // Handle not found + if (courseware.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a challenge with that name. " + + "Please double check the name." + }); + return res.redirect('/challenges'); + } + courseware = courseware.pop(); + + // Redirect to full name if the user only entered a partial + var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../challenges/' + dashedNameFull); + } + + var challengeType = { + 0: function() { + res.render('coursewares/showHTML', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + brief: courseware.description[0], + details: courseware.description.slice(1), + tests: courseware.tests, + challengeSeed: courseware.challengeSeed, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + environment: resources.whichEnvironment(), + challengeType: courseware.challengeType }); - return res.redirect('/coursewares') + }, + + 1: function() { + res.render('coursewares/showJS', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + brief: courseware.description[0], + details: courseware.description.slice(1), + tests: courseware.tests, + challengeSeed: courseware.challengeSeed, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: courseware.challengeType + }); + }, + + 2: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: courseware.challengeType + }); + }, + + 3: function() { + res.render('coursewares/showZiplineOrBasejump', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: courseware.challengeType + }); + }, + + 4: function() { + res.render('coursewares/showZiplineOrBasejump', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: courseware.challengeType + }); } - courseware = courseware.pop(); + }; - // Redirect to full name if the user only entered a partial - var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../coursewares/' + dashedNameFull); - } + return challengeType[courseware.challengeType](); - var challengeType = { - 0 : function() { - res.render('coursewares/showHTML', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - brief: courseware.description[0], - details: courseware.description.slice(1), - tests: courseware.tests, - challengeSeed: courseware.challengeSeed, - cc: !!req.user, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - environment: resources.whichEnvironment() - }); - }, - - 1 : function() { - res.render('coursewares/showJS', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - brief: courseware.description[0], - details: courseware.description.slice(1), - tests: courseware.tests, - challengeSeed: courseware.challengeSeed, - cc: !!req.user, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - environment: resources.whichEnvironment() - - }); - }, - - 2: function() { - res.render('coursewares/showVideo', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - details: courseware.description, - tests: courseware.tests, - video: courseware.challengeSeed[0], - cc: !!req.user, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - environment: resources.whichEnvironment() - }); - } - }; - - return challengeType[courseware.challengeType](); - - }); + }); }; exports.testCourseware = function(req, res) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - res.render('courseware/show', { - completedWith: null, - title: coursewareName, - name: coursewareName, - difficulty: +coursewareDifficulty, - brief: coursewareDescription[0], - details: coursewareDescription.slice(1), - tests: coursewareTests, - challengeSeed: coursewareChallengeSeed, - challengeEntryPoint: coursewareEntryPoint, - cc: req.user ? req.user.coursewaresHash : undefined, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewares: [], - coursewareHash: "test" - }); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + res.render('courseware/show', { + completedWith: null, + title: coursewareName, + name: coursewareName, + difficulty: +coursewareDifficulty, + brief: coursewareDescription[0], + details: coursewareDescription.slice(1), + tests: coursewareTests, + challengeSeed: coursewareChallengeSeed, + challengeEntryPoint: coursewareEntryPoint, + cc: req.user ? req.user.coursewaresHash : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewares: [], + coursewareHash: 'test' + }); }; function getRidOfEmpties(elem) { - if (elem.length > 0) { - return elem; - } -}; + if (elem.length > 0) { + return elem; + } +} exports.publicGenerator = function(req, res) { - res.render('courseware/public-generator'); + res.render('courseware/public-generator'); }; exports.generateChallenge = function(req, res) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - var response = { - _id: randomString(), - name: coursewareName, - difficulty: coursewareDifficulty, - description: coursewareDescription, - challengeEntryPoint: coursewareEntryPoint, - challengeSeed: coursewareChallengeSeed, - tests: coursewareTests - }; - res.send(response); + var response = { + _id: randomString(), + name: coursewareName, + difficulty: coursewareDifficulty, + description: coursewareDescription, + challengeEntryPoint: coursewareEntryPoint, + challengeSeed: coursewareChallengeSeed, + tests: coursewareTests + }; + res.send(response); }; exports.completedCourseware = function (req, res, next) { - var isCompletedDate = Math.round(+new Date() / 1000); - var coursewareHash = req.body.coursewareInfo.coursewareHash; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + + req.user.completedCoursewares.push({ + _id: coursewareHash, + completedDate: isCompletedDate, + name: req.body.coursewareInfo.coursewareName, + solution: null, + githubLink: null, + verified: true + }); + var index = req.user.completedCoursewares.indexOf(coursewareHash); + + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + res.sendStatus(200); + } + }); +}; + +exports.completedZiplineOrBasejump = function (req, res, next) { + debug('Inside controller for completed zipline or basejump with data %s', + req.body.coursewareInfo); + var isCompletedWith = req.body.coursewareInfo.completedWith || false; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + var solutionLink = req.body.coursewareInfo.publicURL; + var githubLink = req.body.coursewareInfo.challengeType === '4' + ? req.body.coursewareInfo.githubURL : true; + if (!solutionLink || !githubLink) { + req.flash('errors', { + msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + + 'your work.' + }); + return res.sendStatus(403); + } + + if (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return next(err); + } else { + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + pairedWith = pairedWith.pop(); + + req.user.completedCoursewares.push({ + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false + }); + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + debug('this is the user object returned %s,' + + ' this is the req.user._id %s, ' + + 'this is the pairedWith._id %s', user, req.user._id, pairedWith._id); + debug(req.user._id.toString() === pairedWith._id.toString()); + if (req.user._id.toString() === pairedWith._id.toString()) { + return res.sendStatus(200); + } + index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedCoursewares.splice(index, 1); + + } + + pairedWith.completedCoursewares.push({ + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false + }); + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + return res.sendStatus(200); + } + }); + }); + } + }); + } else { req.user.completedCoursewares.push({ - _id: coursewareHash, - completedDate: isCompletedDate + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: null, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false }); var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); if (index > -1) { - req.user.points++; - req.user.uncompletedCoursewares.splice(index, 1) + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); } req.user.save(function (err, user) { - if (err) { - return next(err); - } - if (user) { - res.send(true) - } + if (err) { + return next(err); + } + if (user) { + return res.sendStatus(200); + } }); + } }; diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js new file mode 100644 index 0000000000..49b79e6bd6 --- /dev/null +++ b/controllers/fieldGuide.js @@ -0,0 +1,99 @@ +var _ = require('lodash'), + debug = require('debug')('freecc:cntr:fieldGuide'), + FieldGuide = require('./../models/FieldGuide'), + resources = require('./resources'), + R = require('ramda'); + +exports.returnIndividualFieldGuide = function(req, res, next) { + var dashedName = req.params.fieldGuideName; + + var fieldGuideName = dashedName.replace(/\-/g, ' '); + + FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) { + if (err) { + next(err); + } + + if (fieldGuide.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a field guide entry with that name. Please double check the name." + }); + + return res.redirect('/field-guide'); + } + + fieldGuide = fieldGuide.pop(); + var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + if (dashedNameFull != dashedName) { + return res.redirect('../field-guide/' + dashedNameFull); + } + res.render('field-guide/show', { + title: fieldGuide.name, + fieldGuideId: fieldGuide._id, + description: fieldGuide.description.join('') + }); + }); +}; + +exports.showAllFieldGuides = function(req, res) { + var data = {}; + data.fieldGuideList = resources.allFieldGuideNames(); + data.fieldGuideIds = resources.allFieldGuideIds(); + data.completedFieldGuides = req.user.completedFieldGuides; + res.send(data); +}; + +exports.returnNextFieldGuide = function(req, res, next) { + if (!req.user) { + return res.redirect('../field-guide/how-do-i-use-this-guide?'); + } + + var completed = req.user.completedFieldGuides; + + req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.save(); + + var uncompletedFieldGuides = req.user.uncompletedFieldGuides; + + var displayedFieldGuides = FieldGuide.find({'_id': uncompletedFieldGuides[0]}); + displayedFieldGuides.exec(function(err, fieldGuide) { + if (err) { + return next(err); + } + fieldGuide = fieldGuide.pop(); + if (fieldGuide === undefined) { + req.flash('success', { + msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here." + }); + return res.redirect('../field-guide/how-do-i-use-this-guide?'); + } + var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../field-guide/' + nameString); + }); +}; + +exports.completedFieldGuide = function (req, res, next) { + debug('params in completedFieldGuide', req.params); + var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId; + + req.user.completedFieldGuides.push(fieldGuideId); + + var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedFieldGuides.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + res.send(true); + } + }); +}; diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js new file mode 100644 index 0000000000..a7871327a3 --- /dev/null +++ b/controllers/nonprofits.js @@ -0,0 +1,188 @@ +var async = require('async'), + Nonprofit = require('./../models/Nonprofit'), + resources = require('./resources'), + secrets = require('./../config/secrets'), + moment = require('moment'), + debug = require('debug')('freecc:cntr:nonprofits'), + R = require('ramda'); + +exports.nonprofitsHome = function(req, res) { + res.render('nonprofits/home', { + title: 'A guide to our Nonprofit Projects' + }); +}; + +exports.nonprofitsDirectory = function(req, res) { + Nonprofit.find({estimatedHours: { $gt: 0 } }, function(err, nonprofits) { + if (err) { + next(err); + } + res.render('nonprofits/directory', { + title: 'Nonprofits we help', + nonprofits: nonprofits + }); + }); +}; + +exports.areYouWithARegisteredNonprofit = function(req, res) { + res.render('nonprofits/are-you-with-a-registered-nonprofit', { + title: 'Are you with a with a registered nonprofit', + step: 1 + }); +}; + +exports.areTherePeopleThatAreAlreadyBenefitingFromYourServices = function(req, res) { + res.render('nonprofits/are-there-people-that-are-already-benefiting-from-your-services', { + title: 'Are there people already benefiting from your services', + step: 2 + }); +}; + +exports.okWithJavaScript = function(req, res) { + res.render('nonprofits/ok-with-javascript', { + title: 'Are you OK with us using JavaScript', + step: 3 + }); +}; + +exports.inExchangeWeAsk = function(req, res) { + res.render('nonprofits/in-exchange-we-ask', { + title: 'In exchange we ask that you ...', + step: 4 + }); +}; + +exports.howCanFreeCodeCampHelpYou = function(req, res) { + res.render('nonprofits/how-can-free-code-camp-help-you', { + title: 'Are you with a with a registered nonprofit', + step: 5 + }); +}; + +exports.whatDoesYourNonprofitDo = function(req, res) { + res.render('nonprofits/what-does-your-nonprofit-do', { + existingParams: req.params, + title: 'What does your nonprofit do?', + step: 6 + }); +}; + +exports.linkUsToYourWebsite = function(req, res) { + res.render('nonprofits/link-us-to-your-website', { + title: 'Link us to your website', + step: 7 + }); +}; + +exports.tellUsYourEmail = function(req, res) { + res.render('nonprofits/tell-us-your-email', { + title: 'Tell us your email', + step: 8 + }); +}; + +exports.tellUsYourName = function(req, res) { + res.render('nonprofits/tell-us-your-name', { + title: 'Tell us your name', + step: 9 + }); +}; + +exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { + res.render('nonprofits/your-nonprofit-project-application-has-been-submitted', { + title: 'Your Nonprofit Project application has been submitted!', + step: 10, + getBackDay: moment().weekday(5).format('dddd') + }); +}; + +exports.otherSolutions = function(req, res) { + res.render('nonprofits/other-solutions', { + title: 'Here are some other possible solutions for you' + }); +}; + +exports.returnIndividualNonprofit = function(req, res, next) { + var dashedName = req.params.nonprofitName; + + var nonprofitName = dashedName.replace(/\-/g, ' '); + + Nonprofit.find({'name': new RegExp(nonprofitName, 'i')}, function(err, nonprofit) { + if (err) { + next(err); + } + + if (nonprofit.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a nonprofit with that name. Please double check the name." + }); + + return res.redirect('/nonprofits'); + } + + nonprofit = nonprofit.pop(); + var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../nonprofit/' + dashedNameFull); + } + var buttonActive = false; + if (req.user) { + if (req.user.uncompletedBonfires.length === 0) { + if (req.user.completedCoursewares.length > 63) { + var hasShownInterest = nonprofit.interestedCampers.filter(function ( obj ) { + return obj.username === req.user.profile.username; + }); + console.log(hasShownInterest); + if (hasShownInterest.length === 0) { + buttonActive = true; + } + } + } + } + res.render('nonprofits/show', { + dashedName: dashedNameFull, + title: nonprofit.name, + logoUrl: nonprofit.logoUrl, + estimatedHours: nonprofit.estimatedHours, + projectDescription: nonprofit.projectDescription, + approvedOther: nonprofit.approvedDeliverables.indexOf('other') > -1, + approvedWebsite: nonprofit.approvedDeliverables.indexOf('website') > -1, + approvedDonor: nonprofit.approvedDeliverables.indexOf('donor') > -1, + approvedInventory: nonprofit.approvedDeliverables.indexOf('inventory') > -1, + approvedVolunteer: nonprofit.approvedDeliverables.indexOf('volunteer') > -1, + approvedForm: nonprofit.approvedDeliverables.indexOf('form') > -1, + approvedCommunity: nonprofit.approvedDeliverables.indexOf('community') > -1, + approvedELearning: nonprofit.approvedDeliverables.indexOf('eLearning') > -1, + websiteLink: nonprofit.websiteLink, + imageUrl: nonprofit.imageUrl, + whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, + interestedCampers: nonprofit.interestedCampers, + assignedCampers: nonprofit.assignedCampers, + buttonActive: buttonActive, + currentStatus: nonprofit.currentStatus + }); + }); +}; + +exports.showAllNonprofits = function(req, res) { + var data = {}; + data.nonprofitsList = resources.allNonprofitNames(); + res.send(data); +}; + +exports.interestedInNonprofit = function(req, res) { + if (req.user) { + Nonprofit.findOne({name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i')}, function(err, nonprofit) { + if (err) { return next(err); } + nonprofit.interestedCampers.push({"username": req.user.profile.username, + "picture": req.user.profile.picture, + "timeOfInterest": Date.now() + }); + nonprofit.save(function(err) { + if (err) { return done(err); } + req.flash('success', { msg: "Thanks for expressing interest in this nonprofit project! We've added you to this project as an interested camper!" }); + res.redirect('back'); + }); + }); + } +}; diff --git a/controllers/resources.js b/controllers/resources.js index e8fbb0a6ee..5b5026bd17 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -1,364 +1,367 @@ var async = require('async'), - User = require('../models/User'), - Challenge = require('./../models/Challenge'), - Bonfire = require('./../models/Bonfire'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - resources = require('./resources.json'), - steps = resources.steps, - secrets = require('./../config/secrets'), - bonfires = require('../seed_data/bonfires.json'), - coursewares = require('../seed_data/coursewares.json'), - moment = require('moment'), - https = require('https'), - debug = require('debug')('freecc:cntr:resources'), - cheerio = require('cheerio'), - request = require('request'), - R = require('ramda'); + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + FieldGuide = require('./../models/FieldGuide'), + Nonprofit = require('./../models/Nonprofit'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + bonfires = require('../seed_data/bonfires.json'), + nonprofits = require('../seed_data/nonprofits.json'), + coursewares = require('../seed_data/coursewares.json'), + fieldGuides = require('../seed_data/field-guides.json'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); /** * GET / * Resources. */ -module.exports = { - privacy: function privacy(req, res) { - res.render('resources/privacy', { - title: 'Privacy' - }); - }, +Array.zip = function(left, right, combinerFunction) { + var counter, + results = []; - sitemap: function sitemap(req, res, next) { - var appUrl = 'http://www.freecodecamp.com'; - var now = moment(new Date()).format('YYYY-MM-DD'); + for (counter = 0; counter < Math.min(left.length, right.length); counter++) { + results.push(combinerFunction(left[counter], right[counter])); + } - User.find({'profile.username': {'$ne': '' }}, function(err, users) { - if (err) { - debug('User err: ', err); - return next(err); - } - Challenge.find({}, function (err, challenges) { - if (err) { - debug('User err: ', err); - return next(err); - } - Bonfire.find({}, function (err, bonfires) { - if (err) { - debug('User err: ', err); - return next(err); - } - Story.find({}, function (err, stories) { - if (err) { - debug('User err: ', err); - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.render('resources/sitemap', { - appUrl: appUrl, - now: now, - users: users, - challenges: challenges, - bonfires: bonfires, - stories: stories - }); - }); - }); - }); - }); - }, - - deployAWebsite: function deployAWebsite(req, res) { - res.render('resources/deploy-a-website', { - title: 'Deploy a Dynamic Website in 7 Minutes' - }); - }, - - chat: function chat(req, res) { - if (req.user && req.user.sentSlackInvite) { - res.redirect('https://freecode.slack.com/messages/general/'); - } else { - res.render('resources/chat', { - title: "Join our chat room" - }); - } - }, - - 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' - }); - }, - - guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { - res.render('resources/guide-to-our-nonprofit-projects', { - title: 'A guide to our Nonprofit Projects' - }); - }, - - 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' - }); - }, - - styleguide: function styleguide(req, res) { - res.render('resources/styleguide', { - title: 'A Styleguide for Contributing to our Bonfires' - }); - }, - - 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' - }); - }, - - installScreenHero: function(req, res) { - res.render('resources/install-screenhero', { - title: 'Install ScreenHero' - }); - }, - - javaScriptInYourInbox: function(req, res) { - res.render('resources/javascript-in-your-inbox', { - title: 'JavaScript in your Inbox' - }); - }, - - nodeSchoolChallenges: function(req, res) { - res.render('resources/nodeschool-challenges', { - title: 'NodeSchool Challenges' - }); - }, - - githubCalls: function(req, res) { - var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; - request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { - pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github"; - request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) { - issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub"; - res.send({"issues": issues, "pulls" : pulls}); - }); - }); - }, - - trelloCalls: function(req, res, next) { - request('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(err, status, trello) { - if (err) { return next(err); } - trello = (status && status.statusCode == 200) ? (JSON.parse(trello)) : "Can't connect to to Trello"; - res.end(JSON.stringify(trello)); - }); - }, - bloggerCalls: function(req, res, next) { - request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) { - if (err) { return next(err); } - blog = (status && status.statusCode == 200) ? JSON.parse(blog) : "Can't connect to Blogger"; - res.end(JSON.stringify(blog)); - }); - }, - - about: function(req, res, next) { - if (req.user) { - if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { - req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"; - req.user.save(function(err) { - if (err) return next(err); - }); - } - } - 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)); - var announcements = resources.announcements; - function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); - } - User.count({}, function (err, c3) { - if (err) { - debug('User err: ', err); - return next(err); - } - User.count({'points': {'$gt': 53}}, function (err, all) { - if (err) { - debug('User err: ', err); - return next(err); - } - - res.render('resources/learn-to-code', { - title: 'About Free Code Camp and Our Team of Volunteers', - daysRunning: daysRunning, - c3: numberWithCommas(c3), - all: all, - announcements: announcements - }); - }); - }); - }, - - 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)]; - }, - - 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; - }); - }, - allBonfireNames: function() { - return bonfires.map(function(elem) { - return { - name: elem.name, - difficulty: elem.difficulty - } - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map(function(elem) { - return elem.name; - }); - }, - - allCoursewareIds: function() { - return coursewares.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; - }); - }, - allCoursewareNames: function() { - return coursewares.map(function(elem) { - return { - name: elem.name, - difficulty: elem.difficulty - }; - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map(function(elem) { - return elem.name; - }); - }, - whichEnvironment: function() { - return process.env.NODE_ENV; - }, - getURLTitle: function(url, callback) { - debug('got url in meta scraping function', url); - (function () { - var result = {title: '', image: '', url: '', description: ''}; - request(url, function (error, response, body) { - if (!error && response.statusCode === 200) { - var $ = cheerio.load(body); - var metaDescription = $("meta[name='description']"); - var metaImage = $("meta[property='og:image']"); - var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; - var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; - result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; - result.image = urlImage; - result.description = description; - callback(null, result); - } else { - callback('failed'); - } - }); - })(); - }, - updateUserStoryPictures: function(userId, picture, username, cb) { - - var counter = 0, - foundStories, - foundComments; - - Story.find({'author.userId': userId}, function(err, stories) { - if (err) { - return cb(err); - } - foundStories = stories; - counter++; - saveStoriesAndComments(); - }); - Comment.find({'author.userId': userId}, function(err, comments) { - if (err) { - return cb(err); - } - foundComments = comments; - counter++; - saveStoriesAndComments(); - }); - - function saveStoriesAndComments() { - if (counter !== 2) { - return; - } - var tasks = []; - R.forEach(function(comment) { - comment.author.picture = picture; - comment.author.username = username; - comment.markModified('author'); - tasks.push(function(cb) { - comment.save(cb); - }); - }, foundComments); - - R.forEach(function(story) { - story.author.picture = picture; - story.author.username = username; - story.markModified('author'); - tasks.push(function(cb) { - story.save(cb); - }); - }, foundStories); - async.parallel(tasks, function(err) { - if (err) { return cb(err); } - cb(); - }); - } - } + return results; +}; + +module.exports = { + privacy: function privacy(req, res) { + res.render('resources/privacy', { + title: 'Privacy' + }); + }, + + sitemap: function sitemap(req, res, next) { + var appUrl = 'http://www.freecodecamp.com'; + var now = moment(new Date()).format('YYYY-MM-DD'); + + User.find({'profile.username': {'$ne': '' }}, function(err, users) { + if (err) { + debug('User err: ', err); + return next(err); + } + Courseware.find({}, function (err, challenges) { + if (err) { + debug('User err: ', err); + return next(err); + } + Bonfire.find({}, function (err, bonfires) { + if (err) { + debug('User err: ', err); + return next(err); + } + Story.find({}, function (err, stories) { + if (err) { + debug('User err: ', err); + return next(err); + } + Nonprofit.find({}, function (err, nonprofits) { + if (err) { + debug('User err: ', err); + return next(err); + } + FieldGuide.find({}, function (err, fieldGuides) { + if (err) { + debug('User err: ', err); + return next(err); + } + res.header('Content-Type', 'application/xml'); + res.render('resources/sitemap', { + appUrl: appUrl, + now: now, + users: users, + challenges: challenges, + bonfires: bonfires, + stories: stories, + nonprofits: nonprofits, + fieldGuides: fieldGuides + }); + }); + }); + }); + }); + }); + }); + }, + + chat: function chat(req, res) { + if (req.user && req.user.progressTimestamps.length > 5) { + res.redirect('http://freecode.slack.com'); + } else { + res.render('resources/chat', { + title: "Watch us code live on Twitch.tv" + }); + } + }, + + twitch: function twitch(req, res) { + res.render('resources/twitch', { + title: "Enter Free Code Camp's Chat Rooms" + }); + }, + + githubCalls: function(req, res) { + var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; + request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { + pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github"; + request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) { + issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub"; + res.send({"issues": issues, "pulls" : pulls}); + }); + }); + }, + + trelloCalls: function(req, res, next) { + request('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(err, status, trello) { + if (err) { return next(err); } + trello = (status && status.statusCode === 200) ? (JSON.parse(trello)) : "Can't connect to to Trello"; + res.end(JSON.stringify(trello)); + }); + }, + + bloggerCalls: function(req, res, next) { + request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) { + if (err) { return next(err); } + blog = (status && status.statusCode === 200) ? JSON.parse(blog) : "Can't connect to Blogger"; + res.end(JSON.stringify(blog)); + }); + }, + + about: function(req, res, next) { + if (req.user) { + if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { + req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"; + req.user.save(); + } + } + 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)); + var announcements = resources.announcements; + function numberWithCommas(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } + User.count({}, function (err, c3) { + if (err) { + debug('User err: ', err); + return next(err); + } + User.count({'points': {'$gt': 53}}, function (err, all) { + if (err) { + debug('User err: ', err); + return next(err); + } + + res.render('resources/learn-to-code', { + title: 'About Free Code Camp', + daysRunning: daysRunning, + c3: numberWithCommas(c3), + announcements: announcements + }); + }); + }); + }, + + 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)]; + }, + + 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; + }); + }, + + allFieldGuideIds: function() { + return fieldGuides.map(function(elem) { + return { + _id: elem._id, + } + }) + .map(function(elem) { + return elem._id; + }); + }, + + allBonfireNames: function() { + return bonfires.map(function(elem) { + return { + name: elem.name, + difficulty: elem.difficulty, + _id: elem._id + } + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map (function(elem) { + return { + name : elem.name, + _id: elem._id + } + }); + }, + + allFieldGuideNames: function() { + return fieldGuides.map(function(elem) { + return { + name: elem.name + } + }) + }, + + allNonprofitNames: function() { + return nonprofits.map(function(elem) { + return { + name: elem.name + } + }) + }, + + allCoursewareIds: function() { + return coursewares.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; + }); + }, + + allCoursewareNames: function() { + return coursewares.map(function(elem) { + return { + name: elem.name, + difficulty: elem.difficulty, + challengeType: elem.challengeType, + _id: elem._id + }; + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map (function(elem) { + return { + name: elem.name, + challengeType: elem.challengeType, + _id: elem._id + }; + }); + }, + + whichEnvironment: function() { + return process.env.NODE_ENV; + }, + + getURLTitle: function(url, callback) { + (function () { + var result = {title: '', image: '', url: '', description: ''}; + request(url, function (error, response, body) { + if (!error && response.statusCode === 200) { + var $ = cheerio.load(body); + var metaDescription = $("meta[name='description']"); + var metaImage = $("meta[property='og:image']"); + var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; + var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; + result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; + result.image = urlImage; + result.description = description; + callback(null, result); + } else { + callback('failed'); + } + }); + })(); + }, + + updateUserStoryPictures: function(userId, picture, username, cb) { + + var counter = 0, + foundStories, + foundComments; + + Story.find({'author.userId': userId}, function(err, stories) { + if (err) { + return cb(err); + } + foundStories = stories; + counter++; + saveStoriesAndComments(); + }); + Comment.find({'author.userId': userId}, function(err, comments) { + if (err) { + return cb(err); + } + foundComments = comments; + counter++; + saveStoriesAndComments(); + }); + + function saveStoriesAndComments() { + if (counter !== 2) { + return; + } + var tasks = []; + R.forEach(function(comment) { + comment.author.picture = picture; + comment.author.username = username; + comment.markModified('author'); + tasks.push(function(cb) { + comment.save(cb); + }); + }, foundComments); + + R.forEach(function(story) { + story.author.picture = picture; + story.author.username = username; + story.markModified('author'); + tasks.push(function(cb) { + story.save(cb); + }); + }, foundStories); + async.parallel(tasks, function(err) { + if (err) { return cb(err); } + cb(); + }); + } + } }; diff --git a/controllers/story.js b/controllers/story.js index be62c54b4c..34b8693890 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -1,440 +1,486 @@ +/* eslint-disable no-catch-shadow, no-unused-vars */ var R = require('ramda'), - debug = require('debug')('freecc:cntr:story'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - User = require('./../models/User'), - moment = require('../public/js/lib/moment/moment.js'), - resources = require('./resources'), - mongodb = require('mongodb'), - MongoClient = mongodb.MongoClient, - secrets = require('../config/secrets'), - sanitizeHtml = require('sanitize-html'); + debug = require('debug')('freecc:cntr:story'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + User = require('./../models/User'), + moment = require('../public/js/lib/moment/moment.js'), + resources = require('./resources'), + mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + secrets = require('../config/secrets'), + nodemailer = require('nodemailer'), + sanitizeHtml = require('sanitize-html'); function hotRank(timeValue, rank) { - /* - * Hotness ranking algorithm: http://amix.dk/blog/post/19588 - * tMS = postedOnDate - foundationTime; - * Ranking... - * f(ts, 1, rank) = log(10)z + (ts)/45000; - */ - var hotness; - var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / 115200000); - return hotness; + /* + * Hotness ranking algorithm: http://amix.dk/blog/post/19588 + * tMS = postedOnDate - foundationTime; + * Ranking... + * f(ts, 1, rank) = log(10)z + (ts)/45000; + */ + var hotness; + var z = Math.log(rank) / Math.log(10); + hotness = z + (timeValue / 115200000); + return hotness; } -exports.hotJSON = function(req, res) { - var story = Story.find({}).sort({'timePosted': -1}).limit(1000); - story.exec(function(err, stories) { - if (err) { - res.send(500); - return next(err); - } +exports.hotJSON = function(req, res, next) { + var story = Story.find({}).sort({'timePosted': -1}).limit(1000); + story.exec(function(err, stories) { + if (err) { + return next(err); + } - var foundationDate = 1413298800000; + var foundationDate = 1413298800000; - var sliceVal = stories.length >= 100 ? 100 : stories.length; - return res.json(stories.map(function(elem) { - return elem; - }).sort(function(a, b) { - return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline); - }).slice(0, sliceVal)); + var sliceVal = stories.length >= 100 ? 100 : stories.length; + return res.json(stories.map(function(elem) { + return elem; + }).sort(function(a, b) { + return hotRank(b.timePosted - foundationDate, b.rank, b.headline) + - hotRank(a.timePosted - foundationDate, a.rank, a.headline); + }).slice(0, sliceVal)); - }); + }); }; exports.recentJSON = function(req, res, next) { - var story = Story.find({}).sort({'timePosted': -1}).limit(100); - story.exec(function(err, stories) { - if (err) { - res.status(500); - return next(err); - } - res.json(stories); - }); + var story = Story.find({}).sort({'timePosted': -1}).limit(100); + story.exec(function(err, stories) { + if (err) { + return next(err); + } + return res.json(stories); + }); }; exports.hot = function(req, res) { - res.render('stories/index', { - title: 'Hot stories currently trending on Camper News', - page: 'hot' - }); + return res.render('stories/index', { + title: 'Hot stories currently trending on Camper News', + page: 'hot' + }); }; exports.submitNew = function(req, res) { - res.render('stories/index', { - title: 'Submit a new story to Camper News', - page: 'submit' - }); + return res.render('stories/index', { + title: 'Submit a new story to Camper News', + page: 'submit' + }); }; exports.search = function(req, res) { - res.render('stories/index', { - title: 'Search the archives of Camper News', - page: 'search' - }); + return res.render('stories/index', { + title: 'Search the archives of Camper News', + page: 'search' + }); }; exports.recent = function(req, res) { - res.render('stories/index', { - title: 'Recently submitted stories on Camper News', - page: 'recent' - }); + return res.render('stories/index', { + title: 'Recently submitted stories on Camper News', + page: 'recent' + }); }; exports.preSubmit = function(req, res) { - var data = req.query; - var cleanData = sanitizeHtml(data.url, { - allowedTags: [], - allowedAttributes: [] - }).replace(/";/g, '"'); - if (data.url.replace(/&/g, '&') !== cleanData) { + var data = req.query; + var cleanData = sanitizeHtml(data.url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/";/g, '"'); + if (data.url.replace(/&/g, '&') !== cleanData) { - req.flash('errors', { - msg: 'The data for this post is malformed' - }); - return res.render('stories/index', { - page: 'stories/submit' - }); - } - - var title = data.title || ''; - var image = data.image || ''; - var description = data.description || ''; - return res.render('stories/index', { - title: "Confirm your Camper News story submission", - page: 'storySubmission', - storyURL: data.url, - storyTitle: title, - storyImage: image, - storyMetaDescription: description + req.flash('errors', { + msg: 'The data for this post is malformed' }); + return res.render('stories/index', { + page: 'stories/submit' + }); + } + + var title = data.title || ''; + var image = data.image || ''; + var description = data.description || ''; + return res.render('stories/index', { + title: 'Confirm your Camper News story submission', + page: 'storySubmission', + storyURL: data.url, + storyTitle: title, + storyImage: image, + storyMetaDescription: description + }); }; exports.returnIndividualStory = function(req, res, next) { - var dashedName = req.params.storyName; + var dashedName = req.params.storyName; - var storyName = dashedName.replace(/\-/g, ' '); + var storyName = dashedName.replace(/\-/g, ' '); - Story.findOne({'storyLink' : new RegExp(storyName, 'i')}, function(err, story) { - if (err) { - next(err); - } + Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) { + if (err) { + return next(err); + } - if (story == null) { - req.flash('errors', { - msg: "404: We couldn't find a story with that name. Please double check the name." - }); + if (story.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a story with that name. Please double check the name." + }); - return res.redirect('/stories/'); - } + return res.redirect('/stories/'); + } - var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../stories/' + dashedNameFull); - } + story = story.pop(); + var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull !== dashedName) { + return res.redirect('../stories/' + dashedNameFull); + } - var userVoted = false; - try { - var votedObj = story.upVotes.filter(function(a){ - return a['upVotedByUsername'] === req.user['profile']['username']; - }); - if (votedObj.length > 0){ - userVoted = true; - } - } catch(err){ - userVoted = false; - } - res.render('stories/index', { - title: story.headline, - link: story.link, - author: story.author, - description: story.description, - rank: story.upVotes.length, - upVotes: story.upVotes, - comments: story.comments, - id: story._id, - timeAgo: moment(story.timePosted).fromNow(), - image: story.image, - page: 'show', - storyMetaDescription: story.metaDescription, - hasUserVoted: userVoted - }); + var userVoted = false; + try { + var votedObj = story.upVotes.filter(function(a) { + return a['upVotedByUsername'] === req.user['profile']['username']; + }); + if (votedObj.length > 0) { + userVoted = true; + } + } catch(err) { + userVoted = false; + } + res.render('stories/index', { + title: story.headline, + link: story.link, + originalStoryLink: dashedName, + originalStoryAuthorEmail: story.author.email || "", + author: story.author, + description: story.description, + rank: story.upVotes.length, + upVotes: story.upVotes, + comments: story.comments, + id: story._id, + timeAgo: moment(story.timePosted).fromNow(), + image: story.image, + page: 'show', + storyMetaDescription: story.metaDescription, + hasUserVoted: userVoted }); + }); }; -exports.getStories = function(req, res) { - MongoClient.connect(secrets.db, function(err, database) { - database.collection('stories').find({ - "$text": { - "$search": req.body.data.searchValue - } - }, { - headline: 1, - timePosted: 1, - link: 1, - description: 1, - rank: 1, - upVotes: 1, - author: 1, - comments: 1, - image: 1, - storyLink: 1, - metaDescription: 1, - textScore: { - $meta: "textScore" - } - }, { - sort: { - textScore: { - $meta: "textScore" - } - } - }).toArray(function(err, items) { - if (items !== null && items.length !== 0) { - return res.json(items); - } - return res.status(404); - }); +exports.getStories = function(req, res, next) { + MongoClient.connect(secrets.db, function(err, database) { + if (err) { + return next(err); + } + database.collection('stories').find({ + '$text': { + '$search': req.body.data.searchValue + } + }, { + headline: 1, + timePosted: 1, + link: 1, + description: 1, + rank: 1, + upVotes: 1, + author: 1, + comments: 1, + image: 1, + storyLink: 1, + metaDescription: 1, + textScore: { + $meta: 'textScore' + } + }, { + sort: { + textScore: { + $meta: 'textScore' + } + } + }).toArray(function(err, items) { + if (err) { + return next(err); + } + if (items !== null && items.length !== 0) { + return res.json(items); + } + return res.sendStatus(404); }); + }); }; exports.upvote = function(req, res, next) { - var data = req.body.data; - Story.find({'_id': data.id}, function(err, story) { - if (err) { - res.status(500); - return next(err); - } - story = story.pop(); - story.rank++; - story.upVotes.push( - { - upVotedBy: data.upVoter._id, - upVotedByUsername: data.upVoter.profile.username - } - ); - story.markModified('rank'); - story.save(); - return res.send(story); + var data = req.body.data; + Story.find({'_id': data.id}, function(err, story) { + if (err) { + return next(err); + } + story = story.pop(); + story.rank++; + story.upVotes.push( + { + upVotedBy: data.upVoter._id, + upVotedByUsername: data.upVoter.profile.username + } + ); + story.markModified('rank'); + story.save(); + User.find({'_id': story.author.userId}, function(err, user) { + 'use strict'; + if (err) { + return next(err); + } + user = user.pop(); + user.progressTimestamps.push(Date.now()); + user.save(); }); + return res.send(story); + }); }; exports.comments = function(req, res, next) { - var data = req.params.id; - Comment.find({'_id': data}, function(err, comment) { + var data = req.params.id; + Comment.find({'_id': data}, function(err, comment) { + if (err) { + return next(err); + } + comment = comment.pop(); + return res.send(comment); + }); +}; + +exports.newStory = function(req, res, next) { + if (!req.user) { + return next(new Error('Must be logged in')); + } + var url = req.body.data.url; + var cleanURL = sanitizeHtml(url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (cleanURL !== url) { + req.flash('errors', { + msg: "The URL you submitted doesn't appear valid" + }); + return res.json({ + alreadyPosted: true, + storyURL: '/stories/submit' + }); + + } + if (url.search(/^https?:\/\//g) === -1) { + url = 'http://' + url; + } + Story.find({'link': url}, function(err, story) { + if (err) { + return next(err); + } + if (story.length) { + req.flash('errors', { + msg: "Someone's already posted that link. Here's the discussion." + }); + return res.json({ + alreadyPosted: true, + storyURL: '/stories/' + story.pop().storyLink + }); + } + resources.getURLTitle(url, processResponse); + }); + + function processResponse(err, story) { + if (err) { + res.json({ + alreadyPosted: false, + storyURL: url, + storyTitle: '', + storyImage: '', + storyMetaDescription: '' + }); + } else { + res.json({ + alreadyPosted: false, + storyURL: url, + storyTitle: story.title, + storyImage: story.image, + storyMetaDescription: story.description + }); + } + } +}; + +exports.storySubmission = function(req, res, next) { + var data = req.body.data; + if (req.user._id.toString() !== data.author.userId.toString()) { + return next(new Error('Not authorized')); + } + var storyLink = data.headline + .replace(/\'/g, '') + .replace(/\"/g, '') + .replace(/,/g, '') + .replace(/[^a-z0-9]/gi, ' ') + .replace(/\s+/g, ' ') + .toLowerCase(); + var link = data.link; + if (link.search(/^https?:\/\//g) === -1) { + link = 'http://' + link; + } + var story = new Story({ + headline: sanitizeHtml(data.headline, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'), + timePosted: Date.now(), + link: link, + description: sanitizeHtml(data.description, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'), + rank: 1, + upVotes: data.upVotes, + author: data.author, + comments: [], + image: data.image, + storyLink: storyLink, + metaDescription: data.storyMetaDescription, + originalStoryAuthorEmail: req.user.email + }); + + story.save(function(err) { if (err) { - res.status(500); - return next(err); + return res.status(500); } - comment = comment.pop(); - return res.send(comment); + res.send(JSON.stringify({ + storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() + })); }); }; -exports.newStory = function(req, res) { - if (!req.user) { - return res.status(500); - } - var url = req.body.data.url; - var cleanURL = sanitizeHtml(url, { +exports.commentSubmit = function(req, res, next) { + var data = req.body.data; + if (req.user._id.toString() !== data.author.userId.toString()) { + return next(new Error('Not authorized')); + } + var sanitizedBody = sanitizeHtml(data.body, + { allowedTags: [], allowedAttributes: [] }).replace(/"/g, '"'); - if (cleanURL !== url) { - req.flash('errors', { - msg: "The URL you submitted doesn't appear valid" - }); - return res.json({ - alreadyPosted: true, - storyURL: '/stories/submit' - }); - - } - if (url.search(/^https?:\/\//g) === -1) { - url = 'http://' + url; - } - Story.find({'link': url}, function(err, story) { - if (err) { - return res.status(500); - } - if (story.length) { - req.flash('errors', { - msg: "Someone's already posted that link. Here's the discussion." - }); - return res.json({ - alreadyPosted: true, - storyURL: '/stories/' + story.pop().storyLink - }); - } - resources.getURLTitle(url, processResponse); + if (data.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' }); + return res.send(true); + } + var comment = new Comment({ + associatedPost: data.associatedPost, + originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, + body: sanitizedBody, + rank: 0, + upvotes: 0, + author: data.author, + comments: [], + topLevel: true, + commentOn: Date.now() + }); - function processResponse(err, story) { - if (err) { - res.json({ - alreadyPosted: false, - storyURL: url, - storyTitle: '', - storyImage: '', - storyMetaDescription: '' - }); - } else { - res.json({ - alreadyPosted: false, - storyURL: url, - storyTitle: story.title, - storyImage: story.image, - storyMetaDescription: story.description - }); - } - } + commentSave(comment, Story, res, next); }; -exports.storySubmission = function(req, res) { - var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); +exports.commentOnCommentSubmit = function(req, res, next) { + var data = req.body.data; + if (req.user._id.toString() !== data.author.userId.toString()) { + return next(new Error('Not authorized')); + } + + var sanitizedBody = sanitizeHtml(data.body, + { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (data.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' + }); + return res.send(true); + } + var comment = new Comment({ + associatedPost: data.associatedPost, + body: sanitizedBody, + rank: 0, + upvotes: 0, + originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, + author: data.author, + comments: [], + topLevel: false, + commentOn: Date.now() + }); + commentSave(comment, Comment, res, next); +}; + +function commentSave(comment, Context, res, next) { + comment.save(function(err, data) { + if (err) { + return next(err); } - var storyLink = data.headline - .replace(/\'/g, '') - .replace(/\"/g, '') - .replace(/,/g, '') - .replace(/[^a-z0-9]/gi, ' ') - .replace(/\s+/g, ' ') - .toLowerCase(); - - Story.count({'storyLink': storyLink}, function(err, storyCount) { + try { + Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { if (err) { - return res.status(500); + return next(err); } - - // if duplicate storyLink add unique number - storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; - - var link = data.link; - if (link.search(/^https?:\/\//g) === -1) { - link = 'http://' + link; - } - var story = new Story({ - headline: sanitizeHtml(data.headline, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - timePosted: Date.now(), - link: link, - description: sanitizeHtml(data.description, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - rank: 1, - upVotes: data.upVotes, - author: data.author, - comments: [], - image: data.image, - storyLink: storyLink, - metaDescription: data.storyMetaDescription - }); - - story.save(function(err) { + associatedStory = associatedStory.pop(); + if (associatedStory) { + associatedStory.comments.push(data._id); + associatedStory.save(function (err) { if (err) { - return res.status(500); + return next(err); } - res.send(JSON.stringify({ - storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() - })); - }); - }); -}; - -exports.commentSubmit = function(req, res) { - var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); - } - var sanitizedBody = sanitizeHtml(data.body, - { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (data.body !== sanitizedBody) { - req.flash('errors', { - msg: 'HTML is not allowed' - }); - return res.send(true); - } - var comment = new Comment({ - associatedPost: data.associatedPost, - body: sanitizedBody, - rank: 0, - upvotes: 0, - author: data.author, - comments: [], - topLevel: true, - commentOn: Date.now() - }); - commentSave(comment, Story, res); -}; - -exports.commentOnCommentSubmit = function(req, res) { - var data = req.body.data; - - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); - } - - var sanitizedBody = sanitizeHtml(data.body, - { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (data.body !== sanitizedBody) { - req.flash('errors', { - msg: 'HTML is not allowed' - }); - return res.send(true); - } - var comment = new Comment({ - associatedPost: data.associatedPost, - body: sanitizedBody, - rank: 0, - upvotes: 0, - author: data.author, - comments: [], - topLevel: false, - commentOn: Date.now() - }); - commentSave(comment, Comment, res); -}; - -function commentSave(comment, Context, res) { - comment.save(function(err, data) { - if (err) { - return res.status(500); + res.send(true); + }); } - try { - Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { - if (err) { - return res.status(500); - } - associatedStory = associatedStory.pop(); - if (associatedStory) { - associatedStory.comments.push(data._id); - associatedStory.save(function (err) { - if (err) { - res.status(500); - } - res.send(true); - }); - } + User.findOne({'profile.username': associatedStory.author.username}, function(err, recipient) { + if (err) { + return next(err); + } + var recipients = ''; + if (data.originalStoryAuthorEmail && (data.originalStoryAuthorEmail !== recipient.email)) { + recipients = data.originalStoryAuthorEmail + ',' + recipient.email; + } else { + recipients = recipient.email; + } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } }); - } catch (e) { - // delete comment - return res.status(500); - } - }); + var mailOptions = { + to: recipients, + from: 'Team@freecodecamp.com', + subject: associatedStory.author.username + " replied to your post on Camper News", + text: [ + "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", + "You can keep this conversation going.", + "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + transporter.sendMail(mailOptions, function (err) { + if (err) { + return err; + } + }); + }); + }); + } catch (e) { + // delete comment + return next(err); + } + }); } diff --git a/controllers/user.js b/controllers/user.js index 9c38e02022..034716ddcb 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1,14 +1,14 @@ var _ = require('lodash'), - async = require('async'), - crypto = require('crypto'), - nodemailer = require('nodemailer'), - passport = require('passport'), - User = require('../models/User'), - secrets = require('../config/secrets'), - moment = require('moment'), - Challenge = require('./../models/Challenge'), - debug = require('debug')('freecc:cntr:challenges'), - resources = require('./resources'); + async = require('async'), + crypto = require('crypto'), + nodemailer = require('nodemailer'), + passport = require('passport'), + User = require('../models/User'), + secrets = require('../config/secrets'), + moment = require('moment'), + debug = require('debug')('freecc:cntr:challenges'), + resources = require('./resources'), + R = require('ramda'); @@ -18,10 +18,12 @@ var _ = require('lodash'), */ exports.getSignin = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/signin', { - title: 'Free Code Camp Login' - }); + if (req.user) { + return res.redirect('/'); + } + res.render('account/signin', { + title: 'Free Code Camp Login' + }); }; /** @@ -30,28 +32,32 @@ exports.getSignin = function(req, res) { */ exports.postSignin = function(req, res, next) { - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password cannot be blank').notEmpty(); + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password cannot be blank').notEmpty(); - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/signin'); + if (errors) { + req.flash('errors', errors); + return res.redirect('/signin'); + } + + passport.authenticate('local', function(err, user, info) { + if (err) { + return next(err); } - - passport.authenticate('local', function(err, user, info) { - if (err) return next(err); - if (!user) { - req.flash('errors', { msg: info.message }); - return res.redirect('/signin'); - } - req.logIn(user, function(err) { - if (err) return next(err); - req.flash('success', { msg: 'Success! You are logged in.' }); - res.redirect(req.session.returnTo || '/'); - }); - })(req, res, next); + if (!user) { + req.flash('errors', { msg: info.message }); + return res.redirect('/signin'); + } + req.logIn(user, function(err) { + if (err) { + return next(err); + } + req.flash('success', { msg: 'Success! You are logged in.' }); + res.redirect(req.session.returnTo || '/'); + }); + })(req, res, next); }; /** @@ -60,8 +66,8 @@ exports.postSignin = function(req, res, next) { */ exports.signout = function(req, res) { - req.logout(); - res.redirect('/'); + req.logout(); + res.redirect('/'); }; /** @@ -70,10 +76,12 @@ exports.signout = function(req, res) { */ exports.getEmailSignin = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/email-signin', { - title: 'Sign in to your Free Code Camp Account' - }); + if (req.user) { + return res.redirect('/'); + } + res.render('account/email-signin', { + title: 'Sign in to your Free Code Camp Account' + }); }; /** @@ -82,10 +90,12 @@ exports.getEmailSignin = function(req, res) { */ exports.getEmailSignup = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/email-signup', { - title: 'Create Your Free Code Camp Account' - }); + if (req.user) { + return res.redirect('/'); + } + res.render('account/email-signup', { + title: 'Create Your Free Code Camp Account' + }); }; /** @@ -95,94 +105,95 @@ exports.getEmailSignup = function(req, res) { exports.postEmailSignup = function(req, res, next) { + req.assert('email', 'valid email required').isEmail(); var errors = req.validationErrors(); - + if (errors) { req.flash('errors', errors); return res.redirect('/email-signup'); } - var possibleUserData = req.body; + var possibleUserData = req.body; - if (possibleUserData.password.length < 8) { - req.flash('errors', { - msg: 'Your password is too short' - }); - return res.redirect('email-signup'); + if (possibleUserData.password.length < 8) { + req.flash('errors', { + msg: 'Your password is too short' + }); + return res.redirect('email-signup'); + } + + if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { + req.flash('errors', { + msg: 'Your username must be between 5 and 20 characters' + }); + return res.redirect('email-signup'); + } + + + var user = new User({ + email: req.body.email.trim(), + password: req.body.password, + profile: { + username: req.body.username.trim(), + picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' + } + }); + + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); } - if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { - req.flash('errors', { - msg: 'Your username must be between 5 and 20 characters' - }); - return res.redirect('email-signup'); + if (existingEmail) { + req.flash('errors', { + msg: 'Account with that email address already exists.' + }); + return res.redirect('/email-signup'); } - - - var user = new User({ - email: req.body.email.trim(), - password: req.body.password, - profile : { - username: req.body.username.trim(), - picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' - } - }); - - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - - if (existingEmail) { - req.flash('errors', { - msg: 'Account with that email address already exists.' - }); - return res.redirect('/email-signup'); - } - User.findOne({'profile.username': req.body.username }, function(err, existingUsername) { - if (err) { - return next(err); - } - if (existingUsername) { - req.flash('errors', { - msg: 'Account with that username already exists.' - }); - return res.redirect('/email-signup'); - } - - user.save(function(err) { - if (err) { return next(err); } - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- Our All-Volunteer Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return next(err); } - }); - req.logIn(user, function(err) { - if (err) { return next(err); } - res.redirect('/email-signup'); - }); - }); + User.findOne({'profile.username': req.body.username }, function(err, existingUsername) { + if (err) { + return next(err); + } + if (existingUsername) { + req.flash('errors', { + msg: 'Account with that username already exists.' }); + return res.redirect('/email-signup'); + } + + user.save(function(err) { + if (err) { return next(err); } + req.logIn(user, function(err) { + if (err) { return next(err); } + res.redirect('/email-signup'); + }); + }); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + 'And if you have a moment, check out our blog: blog.freecodecamp.com.\n', + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); }); + }); }; /** @@ -191,9 +202,9 @@ exports.postEmailSignup = function(req, res, next) { */ exports.getAccount = function(req, res) { - res.render('account/account', { - title: 'Manage your Free Code Camp Account' - }); + res.render('account/account', { + title: 'Manage your Free Code Camp Account' + }); }; /** @@ -201,9 +212,9 @@ exports.getAccount = function(req, res) { */ exports.getAccountAngular = function(req, res) { - res.json({ - user: req.user - }); + res.json({ + user: req.user + }); }; /** @@ -257,45 +268,89 @@ exports.checkUniqueEmail = function(req, res, next) { */ exports.returnUser = function(req, res, next) { - User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { - if (err) { debug('Username err: ', err); return next(err); } - if (user[0]) { - var user = user[0]; - Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { - if (err) { return next(err); } - res.render('account/show', { - title: 'Camper ' + user.profile.username + '\'s portfolio', - username: user.profile.username, - name: user.profile.name, - location: user.profile.location, - githubProfile: user.profile.githubProfile, - linkedinProfile: user.profile.linkedinProfile, - codepenProfile: user.profile.codepenProfile, - twitterHandle: user.profile.twitterHandle, - bio: user.profile.bio, - picture: user.profile.picture, - points: user.points, - website1Link: user.portfolio.website1Link, - website1Title: user.portfolio.website1Title, - website1Image: user.portfolio.website1Image, - website2Link: user.portfolio.website2Link, - website2Title: user.portfolio.website2Title, - website2Image: user.portfolio.website2Image, - website3Link: user.portfolio.website3Link, - website3Title: user.portfolio.website3Title, - website3Image: user.portfolio.website3Image, - challenges: c, - ch: user.challengesHash, - moment: moment - }); - }); - } else { - req.flash('errors', { - msg: "404: We couldn't find a page with that url. Please double check the link." - }); - return res.redirect('/'); + User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { + if (err) { debug('Username err: ', err); next(err); } + if (user[0]) { + var user = user[0]; + + user.progressTimestamps = user.progressTimestamps.sort(function(a, b) { + return a - b; + }); + + var timeObject = Object.create(null); + R.forEach(function(time) { + timeObject[moment(time).format('YYYY-MM-DD')] = time; + }, user.progressTimestamps); + + var tmpLongest = 1; + var timeKeys = R.keys(timeObject); + for (var i = 1; i <= timeKeys.length; i++) { + if (moment(timeKeys[i - 1]).add(1, 'd').toString() + === moment(timeKeys[i]).toString()) { + tmpLongest++; + if (tmpLongest > user.currentStreak) { + user.currentStreak = tmpLongest; + } + if ( user.currentStreak > user.longestStreak) { + user.longestStreak = user.currentStreak; + } } - }); + } + + user.save(function(err) { + if (err) { + return next(err); + } + }); + + var data = {}; + var progressTimestamps = user.progressTimestamps; + for (var i = 0; i < progressTimestamps.length; i++) { + data[(progressTimestamps[i] / 1000).toString()] = 1; + } + + user.currentStreak = user.currentStreak || 1; + user.longestStreak = user.longestStreak || 1; + challenges = user.completedCoursewares.filter(function ( obj ) { + return !!obj.solution; + }); + res.render('account/show', { + title: 'Camper ' + user.profile.username + '\'s portfolio', + username: user.profile.username, + name: user.profile.name, + location: user.profile.location, + githubProfile: user.profile.githubProfile, + linkedinProfile: user.profile.linkedinProfile, + codepenProfile: user.profile.codepenProfile, + twitterHandle: user.profile.twitterHandle, + bio: user.profile.bio, + picture: user.profile.picture, + progressTimestamps: user.progressTimestamps, + points: user.progressTimestamps.length, + website1Link: user.portfolio.website1Link, + website1Title: user.portfolio.website1Title, + website1Image: user.portfolio.website1Image, + website2Link: user.portfolio.website2Link, + website2Title: user.portfolio.website2Title, + website2Image: user.portfolio.website2Image, + website3Link: user.portfolio.website3Link, + website3Title: user.portfolio.website3Title, + website3Image: user.portfolio.website3Image, + challenges: challenges, + bonfires: user.completedBonfires, + calender: data, + moment: moment, + longestStreak: user.longestStreak + (user.longestStreak === 1 ? " day" : " days"), + currentStreak: user.currentStreak + (user.currentStreak === 1 ? " day" : " days") + }); + + } else { + req.flash('errors', { + msg: "404: We couldn't find a page with that url. Please double check the link." + }); + return res.redirect('/'); + } + }); }; @@ -304,21 +359,21 @@ exports.returnUser = function(req, res, next) { * Update profile information. */ -exports.updateProgress = function(req, res, next) { - User.findById(req.user.id, function(err, user) { - if (err) return next(err); - user.email = req.body.email || ''; - user.profile.name = req.body.name || ''; - user.profile.gender = req.body.gender || ''; - user.profile.location = req.body.location || ''; - user.profile.website = req.body.website || ''; +exports.updateProgress = function(req, res) { + User.findById(req.user.id, function(err, user) { + if (err) return next(err); + user.email = req.body.email || ''; + user.profile.name = req.body.name || ''; + user.profile.gender = req.body.gender || ''; + user.profile.location = req.body.location || ''; + user.profile.website = req.body.website || ''; - user.save(function(err) { - if (err) return next(err); - req.flash('success', { msg: 'Profile information updated.' }); - res.redirect('/account'); - }); + user.save(function(err) { + if (err) return next(err); + req.flash('success', { msg: 'Profile information updated.' }); + res.redirect('/account'); }); + }); }; /** @@ -328,77 +383,77 @@ exports.updateProgress = function(req, res, next) { exports.postUpdateProfile = function(req, res, next) { - User.findById(req.user.id, function(err, user) { - if (err) return next(err); - var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/account'); - } + User.findById(req.user.id, function(err, user) { + if (err) return next(err); + var errors = req.validationErrors(); + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - var user = req.user; - if (existingEmail && existingEmail.email != user.email) { - req.flash('errors', { - msg: "An account with that email address already exists." - }); - return res.redirect('/account'); - } - User.findOne({ 'profile.username': req.body.username }, function(err, existingUsername) { - if (err) { - return next(err); - } - var user = req.user; - if (existingUsername && existingUsername.profile.username !== user.profile.username) { - req.flash('errors', { - msg: 'An account with that username already exists.' - }); - return res.redirect('/account'); - } - user.email = req.body.email.trim() || ''; - user.profile.name = req.body.name.trim() || ''; - user.profile.username = req.body.username.trim() || ''; - user.profile.location = req.body.location.trim() || ''; - user.profile.githubProfile = req.body.githubProfile.trim() || ''; - user.profile.linkedinProfile = req.body.linkedinProfile.trim() || ''; - user.profile.codepenProfile = req.body.codepenProfile.trim() || ''; - user.profile.twitterHandle = req.body.twitterHandle.trim() || ''; - user.profile.bio = req.body.bio.trim() || ''; - user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; - user.portfolio.website1Title = req.body.website1Title.trim() || ''; - user.portfolio.website1Link = req.body.website1Link.trim() || ''; - user.portfolio.website1Image = req.body.website1Image.trim() || ''; - user.portfolio.website2Title = req.body.website2Title.trim() || ''; - user.portfolio.website2Link = req.body.website2Link.trim() || ''; - user.portfolio.website2Image = req.body.website2Image.trim() || ''; - user.portfolio.website3Title = req.body.website3Title.trim() || ''; - user.portfolio.website3Link = req.body.website3Link.trim() || ''; - user.portfolio.website3Image = req.body.website3Image.trim() || ''; - - - user.save(function (err) { - if (err) { - return next(err); - } - resources.updateUserStoryPictures( - user._id.toString(), - user.profile.picture, - user.profile.username, - function(err) { - if (err) { return next(err); } - req.flash('success', { - msg: 'Profile information updated.' - }); - res.redirect('/account'); - } - ); - }); - }); + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); + } + var user = req.user; + if (existingEmail && existingEmail.email != user.email) { + req.flash('errors', { + msg: "An account with that email address already exists." }); + return res.redirect('/account'); + } + User.findOne({ 'profile.username': req.body.username }, function(err, existingUsername) { + if (err) { + return next(err); + } + var user = req.user; + if (existingUsername && existingUsername.profile.username !== user.profile.username) { + req.flash('errors', { + msg: 'An account with that username already exists.' + }); + return res.redirect('/account'); + } + user.email = req.body.email.trim() || ''; + user.profile.name = req.body.name.trim() || ''; + user.profile.username = req.body.username.trim() || ''; + user.profile.location = req.body.location.trim() || ''; + user.profile.githubProfile = req.body.githubProfile.trim() || ''; + user.profile.linkedinProfile = req.body.linkedinProfile.trim() || ''; + user.profile.codepenProfile = req.body.codepenProfile.trim() || ''; + user.profile.twitterHandle = req.body.twitterHandle.trim() || ''; + user.profile.bio = req.body.bio.trim() || ''; + user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; + user.portfolio.website1Title = req.body.website1Title.trim() || ''; + user.portfolio.website1Link = req.body.website1Link.trim() || ''; + user.portfolio.website1Image = req.body.website1Image.trim() || ''; + user.portfolio.website2Title = req.body.website2Title.trim() || ''; + user.portfolio.website2Link = req.body.website2Link.trim() || ''; + user.portfolio.website2Image = req.body.website2Image.trim() || ''; + user.portfolio.website3Title = req.body.website3Title.trim() || ''; + user.portfolio.website3Link = req.body.website3Link.trim() || ''; + user.portfolio.website3Image = req.body.website3Image.trim() || ''; + + + user.save(function (err) { + if (err) { + return next(err); + } + resources.updateUserStoryPictures( + user._id.toString(), + user.profile.picture, + user.profile.username, + function(err) { + if (err) { return next(err); } + req.flash('success', { + msg: 'Profile information updated.' + }); + res.redirect('/account'); + } + ); + }); + }); }); + }); }; /** @@ -407,29 +462,29 @@ exports.postUpdateProfile = function(req, res, next) { */ exports.postUpdatePassword = function(req, res, next) { - req.assert('password', 'Password must be at least 4 characters long').len(4); - req.assert('confirmPassword', 'Passwords do not match') - .equals(req.body.password); + req.assert('password', 'Password must be at least 4 characters long').len(4); + req.assert('confirmPassword', 'Passwords do not match') + .equals(req.body.password); - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/account'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } + User.findById(req.user.id, function(err, user) { + if (err) { return next(err); } - user.password = req.body.password; + user.password = req.body.password; - user.save(function(err) { - if (err) { return next(err); } + user.save(function(err) { + if (err) { return next(err); } - req.flash('success', { msg: 'Password has been changed.' }); - res.redirect('/account'); - }); + req.flash('success', { msg: 'Password has been changed.' }); + res.redirect('/account'); }); + }); }; /** @@ -438,12 +493,12 @@ exports.postUpdatePassword = function(req, res, next) { */ exports.postDeleteAccount = function(req, res, next) { - User.remove({ _id: req.user.id }, function(err) { - if (err) { return next(err); } - req.logout(); - req.flash('info', { msg: 'Your account has been deleted.' }); - res.redirect('/'); - }); + User.remove({ _id: req.user.id }, function(err) { + if (err) { return next(err); } + req.logout(); + req.flash('info', { msg: 'Your account has been deleted.' }); + res.redirect('/'); + }); }; /** @@ -452,22 +507,22 @@ exports.postDeleteAccount = function(req, res, next) { */ exports.getOauthUnlink = function(req, res, next) { - var provider = req.params.provider; - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } + var provider = req.params.provider; + User.findById(req.user.id, function(err, user) { + if (err) { return next(err); } - user[provider] = undefined; - user.tokens = - _.reject(user.tokens, function(token) { - return token.kind === provider; - }); + user[provider] = undefined; + user.tokens = + _.reject(user.tokens, function(token) { + return token.kind === provider; + }); - user.save(function(err) { - if (err) { return next(err); } - req.flash('info', { msg: provider + ' account has been unlinked.' }); - res.redirect('/account'); - }); + user.save(function(err) { + if (err) { return next(err); } + req.flash('info', { msg: provider + ' account has been unlinked.' }); + res.redirect('/account'); }); + }); }; /** @@ -475,26 +530,26 @@ exports.getOauthUnlink = function(req, res, next) { * Reset Password page. */ -exports.getReset = function(req, res, next) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('/forgot'); - } - res.render('account/reset', { - title: 'Password Reset', - token: req.params.token - }); +exports.getReset = function(req, res) { + if (req.isAuthenticated()) { + return res.redirect('/'); + } + User + .findOne({ resetPasswordToken: req.params.token }) + .where('resetPasswordExpires').gt(Date.now()) + .exec(function(err, user) { + if (err) { return next(err); } + if (!user) { + req.flash('errors', { + msg: 'Password reset token is invalid or has expired.' }); + return res.redirect('/forgot'); + } + res.render('account/reset', { + title: 'Password Reset', + token: req.params.token + }); + }); }; /** @@ -503,72 +558,72 @@ exports.getReset = function(req, res, next) { */ exports.postReset = function(req, res, next) { - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('back'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('back'); + } - async.waterfall([ - function(done) { - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('back'); - } - - user.password = req.body.password; - user.resetPasswordToken = undefined; - user.resetPasswordExpires = undefined; - - user.save(function(err) { - if (err) { return done(err); } - req.logIn(user, function(err) { - done(err, user); - }); - }); - }); - }, - function(user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } + async.waterfall([ + function(done) { + User + .findOne({ resetPasswordToken: req.params.token }) + .where('resetPasswordExpires').gt(Date.now()) + .exec(function(err, user) { + if (err) { return next(err); } + if (!user) { + req.flash('errors', { + msg: 'Password reset token is invalid or has expired.' }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Your Free Code Camp password has been changed', - text: [ - 'Hello,\n\n', - 'This email is confirming that you requested to', - 'reset your password for your Free Code Camp account.', - 'This is your email:', - user.email, - '\n' - ].join(' ') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('success', { - msg: 'Success! Your password has been changed.' - }); - done(); + return res.redirect('back'); + } + + user.password = req.body.password; + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + + user.save(function(err) { + if (err) { return done(err); } + req.logIn(user, function(err) { + done(err, user); }); + }); + }); + }, + function(user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password } - ], function(err) { - if (err) { return next(err); } - res.redirect('/'); - }); + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Your Free Code Camp password has been changed', + text: [ + 'Hello,\n\n', + 'This email is confirming that you requested to', + 'reset your password for your Free Code Camp account.', + 'This is your email:', + user.email, + '\n' + ].join(' ') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('success', { + msg: 'Success! Your password has been changed.' + }); + done(); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/'); + }); }; /** @@ -577,12 +632,12 @@ exports.postReset = function(req, res, next) { */ exports.getForgot = function(req, res) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - res.render('account/forgot', { - title: 'Forgot Password' - }); + if (req.isAuthenticated()) { + return res.redirect('/'); + } + res.render('account/forgot', { + title: 'Forgot Password' + }); }; /** @@ -591,80 +646,80 @@ exports.getForgot = function(req, res) { */ exports.postForgot = function(req, res, next) { - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/forgot'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('/forgot'); + } - async.waterfall([ - function(done) { - crypto.randomBytes(16, function(err, buf) { - if (err) { return done(err); } - var token = buf.toString('hex'); - done(null, token); - }); - }, - function(token, done) { - User.findOne({ - email: req.body.email.toLowerCase() - }, function(err, user) { - if (err) { return done(err); } - if (!user) { - req.flash('errors', { - msg: 'No account with that email address exists.' - }); - return res.redirect('/forgot'); - } - - user.resetPasswordToken = token; - user.resetPasswordExpires = Date.now() + 3600000; // 1 hour - - user.save(function(err) { - if (err) { return done(err); } - done(null, token, user); - }); - }); - }, - function(token, user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Reset your Free Code Camp password', - text: [ - 'You are receiving this email because you (or someone else)\n', - 'requested we reset your Free Code Camp account\'s password.\n\n', - 'Please click on the following link, or paste this into your\n', - 'browser to complete the process:\n\n', - 'http://', - req.headers.host, - '/reset/', - token, - '\n\n', - 'If you did not request this, please ignore this email and\n', - 'your password will remain unchanged.\n' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('info', { - msg: 'An e-mail has been sent to ' + - user.email + - ' with further instructions.' - }); - done(null, 'done'); - }); + async.waterfall([ + function(done) { + crypto.randomBytes(16, function(err, buf) { + if (err) { return done(err); } + var token = buf.toString('hex'); + done(null, token); + }); + }, + function(token, done) { + User.findOne({ + email: req.body.email.toLowerCase() + }, function(err, user) { + if (err) { return done(err); } + if (!user) { + req.flash('errors', { + msg: 'No account with that email address exists.' + }); + return res.redirect('/forgot'); } - ], function(err) { - if (err) { return next(err); } - res.redirect('/forgot'); - }); + + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + + user.save(function(err) { + if (err) { return done(err); } + done(null, token, user); + }); + }); + }, + function(token, user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Reset your Free Code Camp password', + text: [ + 'You are receiving this email because you (or someone else)\n', + 'requested we reset your Free Code Camp account\'s password.\n\n', + 'Please click on the following link, or paste this into your\n', + 'browser to complete the process:\n\n', + 'http://', + req.headers.host, + '/reset/', + token, + '\n\n', + 'If you did not request this, please ignore this email and\n', + 'your password will remain unchanged.\n' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('info', { + msg: 'An e-mail has been sent to ' + + user.email + + ' with further instructions.' + }); + done(null, 'done'); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/forgot'); + }); }; diff --git a/gulpfile.js b/gulpfile.js index 70c59bf1fe..4faab8e3ad 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,7 +5,8 @@ var gulp = require('gulp'), sync = require('browser-sync'), reload = sync.reload, inject = require('gulp-inject'), - reloadDelay = 1000; + reloadDelay = 1000, + eslint = require('gulp-eslint'); var paths = { server: './app.js', @@ -54,11 +55,17 @@ gulp.task('sync', ['serve'], function() { sync.init(null, { proxy: 'http://localhost:3000', logLeval: 'debug', - files: ['public/**/*'], + files: ['public/js/lib/*/*.{js, jsx}'], port: 3001, open: true, reloadDelay: reloadDelay }); }); +gulp.task('lint', function() { + return gulp.src(['public/js/lib/**/*']) + .pipe(eslint()) + .pipe(eslint.format()); +}); + gulp.task('default', ['serve', 'sync']); diff --git a/models/BonfireCompletion.js b/models/BonfireCompletion.js deleted file mode 100644 index eb4249400a..0000000000 --- a/models/BonfireCompletion.js +++ /dev/null @@ -1,11 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -var bonfireCompletionSchema = new mongoose.Schema({ - dateCompleted: Number, - completedWith: ObjectId, - bonfireHash: ObjectId, - solution: String -}); - -module.exports = mongoose.model('BonfireCompletion', bonfireCompletionSchema); diff --git a/models/Comment.js b/models/Comment.js index a3a35fe6fd..4a1e15506d 100644 --- a/models/Comment.js +++ b/models/Comment.js @@ -6,6 +6,14 @@ var commentSchema = new mongoose.Schema({ type: String, required: true }, + originalStoryLink: { + type: String, + default: '' + }, + originalStoryAuthorEmail: { + type: String, + default: '' + }, body: { type: String, default: '' @@ -36,4 +44,4 @@ module.exports = mongoose.model('Comment', commentSchema); type: mongoose.Schema.Types.ObjectId, ref: 'User' }, - */ \ No newline at end of file + */ diff --git a/models/Courseware.js b/models/Courseware.js index aa4f8ca5d4..64309a6540 100644 --- a/models/Courseware.js +++ b/models/Courseware.js @@ -16,7 +16,7 @@ var coursewareSchema = new mongoose.Schema({ tests: Array, challengeSeed: Array, completionMessage: String, // Congratulations! You've finished our HTML and CSS track! - challengeType: Number // 0 = html, 1 = javascript only, 2 = video + challengeType: Number // 0 = html, 1 = javascript only, 2 = video, 3 = zipline, 4 = basejump }); -module.exports = mongoose.model('Courseware', coursewareSchema); \ No newline at end of file +module.exports = mongoose.model('Courseware', coursewareSchema); diff --git a/models/FieldGuide.js b/models/FieldGuide.js new file mode 100644 index 0000000000..b705f44774 --- /dev/null +++ b/models/FieldGuide.js @@ -0,0 +1,15 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +var fieldGuideSchema = new mongoose.Schema({ + name: { + type: String, + unique: false + }, + description: { + type: Array, + unique: false + } +}); + +module.exports = mongoose.model('FieldGuide', fieldGuideSchema); diff --git a/models/Nonprofit.js b/models/Nonprofit.js new file mode 100644 index 0000000000..34443461da --- /dev/null +++ b/models/Nonprofit.js @@ -0,0 +1,28 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +/** + * + * @type {exports.Schema} + */ + +var Long = mongoose.Types.Long; +var nonprofitSchema = new mongoose.Schema({ + name: String, + requestedDeliverables: Array, + whatDoesNonprofitDo: String, + websiteLink: String, + stakeholderName: String, + stakeholderEmail: String, + endUser: String, + approvedDeliverables: Array, + projectDescription: String, + logoUrl: String, + imageUrl: String, + estimatedHours: 0, + interestedCampers: [], + confirmedCampers: [], + currentStatus: String // "confirmed", "started", "completed", "aborted" +}); + +module.exports = mongoose.model('Nonprofit', nonprofitSchema); diff --git a/models/Story.js b/models/Story.js index d5d163ccf1..7b60674845 100644 --- a/models/Story.js +++ b/models/Story.js @@ -23,6 +23,10 @@ var storySchema = new mongoose.Schema({ type: String, unique: false }, + originalStoryAuthorEmail: { + type: String, + default: '' + }, rank: { type: Number, default: -Infinity @@ -46,4 +50,4 @@ var storySchema = new mongoose.Schema({ } }); -module.exports = mongoose.model('Story', storySchema); \ No newline at end of file +module.exports = mongoose.model('Story', storySchema); diff --git a/models/User.js b/models/User.js index 083d95f871..efb813cd2b 100644 --- a/models/User.js +++ b/models/User.js @@ -1,12 +1,13 @@ var bcrypt = require('bcrypt-nodejs'); var crypto = require('crypto'); var mongoose = require('mongoose'); +require('mongoose-long')(mongoose); +var Long = mongoose.Types.Long; var userSchema = new mongoose.Schema({ email: { type: String, lowercase: true, - unique: true, trim: true, sparse: true }, @@ -17,257 +18,10 @@ var userSchema = new mongoose.Schema({ github: String, linkedin: String, tokens: Array, - points: { - type: Number, - default: 0 - }, - challengesCompleted: { type: Array, default: [] }, - challengesHash: { - 0: { - type: Number, - default: 0 - }, - 1: { - type: Number, - default: 0 - }, - 2: { - type: Number, - default: 0 - }, - 3: { - type: Number, - default: 0 - }, - 4: { - type: Number, - default: 0 - }, - 5: { - type: Number, - default: 0 - }, - 6: { - type: Number, - default: 0 - }, - 7: { - type: Number, - default: 0 - }, - 8: { - type: Number, - default: 0 - }, - 9: { - type: Number, - default: 0 - }, - 10: { - type: Number, - default: 0 - }, - 11: { - type: Number, - default: 0 - }, - 12: { - type: Number, - default: 0 - }, - 13: { - type: Number, - default: 0 - }, - 14: { - type: Number, - default: 0 - }, - 15: { - type: Number, - default: 0 - }, - 16: { - type: Number, - default: 0 - }, - 17: { - type: Number, - default: 0 - }, - 18: { - type: Number, - default: 0 - }, - 19: { - type: Number, - default: 0 - }, - 20: { - type: Number, - default: 0 - }, - 21: { - type: Number, - default: 0 - }, - 22: { - type: Number, - default: 0 - }, - 23: { - type: Number, - default: 0 - }, - 24: { - type: Number, - default: 0 - }, - 25: { - type: Number, - default: 0 - }, - 26: { - type: Number, - default: 0 - }, - 27: { - type: Number, - default: 0 - }, - 28: { - type: Number, - default: 0 - }, - 29: { - type: Number, - default: 0 - }, - 30: { - type: Number, - default: 0 - }, - 31: { - type: Number, - default: 0 - }, - 32: { - type: Number, - default: 0 - }, - 33: { - type: Number, - default: 0 - }, - 34: { - type: Number, - default: 0 - }, - 35: { - type: Number, - default: 0 - }, - 36: { - type: Number, - default: 0 - }, - 37: { - type: Number, - default: 0 - }, - 38: { - type: Number, - default: 0 - }, - 39: { - type: Number, - default: 0 - }, - 40: { - type: Number, - default: 0 - }, - 41: { - type: Number, - default: 0 - }, - 42: { - type: Number, - default: 0 - }, - 43: { - type: Number, - default: 0 - }, - 44: { - type: Number, - default: 0 - }, - 45: { - type: Number, - default: 0 - }, - 46: { - type: Number, - default: 0 - }, - 47: { - type: Number, - default: 0 - }, - 48: { - type: Number, - default: 0 - }, - 49: { - type: Number, - default: 0 - }, - 50: { - type: Number, - default: 0 - }, - 51: { - type: Number, - default: 0 - }, - 52: { - type: Number, - default: 0 - }, - 53: { - type: Number, - default: 0 - }, - 54: { - type: Number, - default: 0 - }, - 55: { - type: Number, - default: 0 - }, - 56: { - type: Number, - default: 0 - }, - 57: { - type: Number, - default: 0 - }, - 58: { - type: Number, - default: 0 - }, - 59: { - type: Number, - default: 0 - } - }, + progressTimestamps: [], profile: { username: { type: String, - unique: true, sparse: true, lowercase: true, trim: true @@ -309,6 +63,7 @@ var userSchema = new mongoose.Schema({ default: '' } }, + challengesHash: {}, portfolio: { website1Link: { type: String, @@ -351,9 +106,38 @@ var userSchema = new mongoose.Schema({ sentSlackInvite: false, resetPasswordExpires: Date, uncompletedBonfires: Array, - completedBonfires: Array, + completedBonfires: [ + { + _id: String, + name: String, + completedWith: String, + completedDate: Long, + solution: String + } + ], uncompletedCoursewares: Array, - completedCoursewares: Array + completedCoursewares: [ + { + completedDate: Long, + _id: String, + name: String, + completedWith: String, + solution: String, + githubLink: String, + verified: Boolean + } + ], + completedFieldGuides: [], + uncompletedFieldGuides: [], + currentStreak: { + type: Number, + default: 0 + }, + longestStreak: { + type: Number, + default: 0 + }, + needsMigration: { type: Boolean, default: true } }); /** @@ -387,23 +171,4 @@ userSchema.methods.comparePassword = function(candidatePassword, cb) { }); }; -/** - * Helper method for getting user's gravatar. - */ - -userSchema.methods.gravatar = function(size) { - if (!size) { size = 200; } - - if (!this.email) { - return 'https://gravatar.com/avatar/?s=' + size + '&d=retro'; - } - - var md5 = crypto - .createHash('md5') - .update(this.email) - .digest('hex'); - - return 'https://gravatar.com/avatar/' + md5 + '?s=' + size + '&d=retro'; -}; - module.exports = mongoose.model('User', userSchema); diff --git a/package.json b/package.json index ffe8c6281b..787a19c547 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,10 @@ "method-override": "^2.3.0", "moment": "^2.8.4", "mongodb": "^1.4.33", - "mongoose": "^3.8.19", - "mongoose-text-search": "0.0.2", + "mongoose": "^4.0.1", + "mongoose-long": "0.0.2", "morgan": "^1.5.0", "newrelic": "^1.13.3", - "node": "0.0.0", "nodemailer": "^1.3.0", "passport": "^0.2.1", "passport-facebook": "^1.0.3", @@ -75,8 +74,8 @@ "browser-sync": "^1.8.1", "chai": "^1.10.0", "gulp": "^3.8.8", + "gulp-eslint": "^0.9.0", "gulp-inject": "^1.0.2", - "gulp-minify-css": "^0.5.1", "gulp-nodemon": "^1.0.4", "mocha": "^2.0.1", "multiline": "^1.0.1", diff --git a/public/css/lib/bootstrap/variables.less b/public/css/lib/bootstrap/variables.less index 3de76507c7..01e26f3433 100755 --- a/public/css/lib/bootstrap/variables.less +++ b/public/css/lib/bootstrap/variables.less @@ -16,7 +16,7 @@ @brand-primary: #215f1e; @brand-success: #457E86; -@brand-info: #5bc0de; +@brand-info: #4A2B0F; @brand-warning: #f0ad4e; @brand-danger: #d9534f; diff --git a/public/css/main.less b/public/css/main.less index 4f13e21270..cfbc54eeb6 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -335,6 +335,10 @@ ul { margin-bottom: -10px; } +.nonprofit-landing { + font-size: 50px; +} + .big-text { font-size: 63px; } @@ -585,6 +589,17 @@ thead { padding-left: 50px; } +.all-list-header { + background-color: #4A2B0F; + color: #eee; + font-size: 36px; + text-align: center; + margin-bottom: -30px; + border-radius: 5px 5px 0px 0px; + padding-left: 50px; + +} + .closing-x { color: #eee; font-size: 50px; @@ -593,16 +608,23 @@ thead { .fcc-footer { width: 100%; + height: 50px; text-align: center; background-color: #4a2b0f; - height: 40px; + padding: 12px; bottom: 0; left: 0; position: absolute; a { - font-size: 28px; + font-size: 20px; color: #eee; + margin-left: 0px; + margin-right: 0px; + padding-left: 10px; + padding-right: 10px; &:hover { + padding-top: 14px; + padding-bottom: 14px; color: #4a2b0f; background-color: #eee; text-decoration: none; @@ -720,6 +742,11 @@ iframe.iphone { } } +.nonprofit-help-select-text-height { + font-size: 40px; + padding-top: 20px; +} + // To adjust right margin, negative values bring the image closer to the edge of the screen .iphone-position { position: absolute; @@ -816,6 +843,10 @@ iframe.iphone { max-height: 110px; } +.button-spacer { + padding: 3px 0 2px 0; +} + .spacer { padding: 15px 0 15px 0; } @@ -852,6 +883,39 @@ iframe.iphone { box-shadow: 0px 0px 0px rgba(0, 0, 0, 0); } +#cal-heatmap { + width: 361px; +} + +.cal-heatmap-container { + background-color: #EEEEEE; +} + +.checkbox-table label { + margin-left: 10px; +} + +.interested-camper-image { + height: 50px; + width: 50px; + padding: 5px; +} + +.svg-challenge-map { + fill: #333; + height: 40px; +} + +.alert a { + text-decoration: underline; +} + +.step-text { + margin-left: -10px; + line-height: 120%; + padding-bottom: 10px; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/public/js/application.js b/public/js/application.js index eeebfa316b..bf07cd484f 100644 --- a/public/js/application.js +++ b/public/js/application.js @@ -16,4 +16,4 @@ //= require lib/jquery-2.1.1.min //= require lib/bootstrap.min //= require lib/moment/moment -//= require main +//= require main \ No newline at end of file diff --git a/public/js/lib/bonfire/bonfireFramework_v0.1.2.js b/public/js/lib/bonfire/bonfireFramework_v0.1.2.js index 7721ece9f2..2e68888f53 100644 --- a/public/js/lib/bonfire/bonfireFramework_v0.1.2.js +++ b/public/js/lib/bonfire/bonfireFramework_v0.1.2.js @@ -250,7 +250,7 @@ var runTests = function(err, data) { }; function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time +', Attempts: ' + attempts); $('#complete-bonfire-dialog').modal('show'); $('#complete-bonfire-dialog').keydown(function(e) { diff --git a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js index d7928afc20..6ab42ee738 100644 --- a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js +++ b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js @@ -3,12 +3,11 @@ */ var widgets = []; -var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { +var editor = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { lineNumbers: true, mode: "text/html", theme: 'monokai', runnable: true, - //lint: true, matchBrackets: true, autoCloseBrackets: true, scrollbarStyle: 'null', @@ -16,7 +15,6 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") gutters: ["CodeMirror-lint-markers"], onKeyEvent: doLinting }); -var editor = myCodeMirror; // Hijack tab key to insert two spaces instead @@ -36,10 +34,6 @@ editor.setOption("extraKeys", { var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(spaces); } - }, - "Ctrl-Enter": function() { - bonfireExecute(); - return false; } }); @@ -117,7 +111,7 @@ var allSeeds = ''; }); })(); -myCodeMirror.setValue(allSeeds); +editor.setValue(allSeeds); function doLinting () { editor.operation(function () { @@ -144,7 +138,7 @@ function doLinting () { //$('#testSuite').empty(); function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time); $('#next-courseware-button').removeAttr('disabled'); $('#next-courseware-button').addClass('animated tada'); diff --git a/public/js/lib/coursewares/coursewaresJSFramework.js b/public/js/lib/coursewares/coursewaresJSFramework.js index ee89e7eaf4..a8cdc78bc2 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework.js +++ b/public/js/lib/coursewares/coursewaresJSFramework.js @@ -235,12 +235,12 @@ var runTests = function(err, data) { }; function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time +', Attempts: ' + attempts); $('#complete-courseware-dialog').modal('show'); $('#complete-courseware-dialog').keydown(function(e) { if (e.ctrlKey && e.keyCode == 13) { - $('.next-bonfire-button').click(); + $('#next-courseware-button').click(); } }); } \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 734d5ea5bd..62a0a8473e 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,358 +1,437 @@ $(document).ready(function() { - var challengeName = typeof challengeName !== undefined ? challengeName : 'Untitled'; - if (challengeName) { - ga('send', 'event', 'Challenge', 'load', challengeName); - } + var challengeName = typeof challengeName !== undefined ? challengeName : 'Untitled'; + if (challengeName) { + ga('send', 'event', 'Challenge', 'load', challengeName); + } - // When introducing a new announcement, change the localStorage attribute - // and the HTML located in the footer - if (!localStorage || !localStorage.nodeSchoolAnnouncement) { - $('#announcementModal').modal('show'); - localStorage.fccShowAnnouncement = "true"; - } + // When introducing a new announcement, change the localStorage attribute + // and the HTML located in the footer + if (!localStorage || !localStorage.nodeSchoolAnnouncement) { + $('#announcementModal').modal('show'); + localStorage.fccShowAnnouncement = "true"; + } - 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); - } - }); - }; - - setCSRFToken($('meta[name="csrf-token"]').attr('content')); - - $('.start-challenge').on('click', function() { - $(this).parent().remove(); - $('.challenge-content') - .removeClass('hidden-element') - .addClass('animated fadeInDown'); - }); - - $('.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/' - }); - } - }); - - - 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) { - - $.post( - '/completed-bonfire', - { - bonfireInfo: { - completedWith : didCompleteWith, - solution: bonfireSolution, - bonfireHash: thisBonfireHash - } - }, - function(res) { - if (res) { - window.location.href = '/bonfires' - } - }); - } - } - - $('.next-bonfire-button').on('click', function() { - var bonfireSolution = myCodeMirror.getValue(); - var thisBonfireHash = passedBonfireHash || null; - var didCompleteWith = $('#completed-with').val() || null; - completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); - - }); - $('#completed-courseware').on('click', function() { - $('#complete-courseware-dialog').modal('show'); - }); - - $('#complete-courseware-dialog').on('keypress', function(e) { - if (e.which === 13 || e === 13) { - $('#next-courseware-button').click(); + var setCSRFToken = function(securityToken) { + jQuery.ajaxPrefilter(function(options, _, xhr) { + if (!xhr.crossDomain) { + xhr.setRequestHeader(CSRF_HEADER, securityToken); } }); + }; - $('#complete-bonfire-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); + setCSRFToken($('meta[name="csrf-token"]').attr('content')); - $('#complete-courseware-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); - $('#next-courseware-button').on('click', function() { - if ($('.signup-btn-nav').length < 1) { - $.post( - '/completed-courseware', - { - coursewareInfo: { - coursewareHash: passedCoursewareHash - } - }, - function(res) { - if (res) { - window.location.href = '/coursewares' - } - }) - } - }) + $('.start-challenge').on('click', function() { + $(this).parent().remove(); + $('.challenge-content') + .removeClass('hidden-element') + .addClass('animated fadeInDown'); + }); - $('.all-challenges').on('click', function() { - $('#all-challenges-dialog').modal('show'); - }); + $('.challenge-list-checkbox').on('change', function() { + if ($(this).is(":checked")) { + $(this).parent().parent().children('.step-text').addClass('strikethrough text-primary'); + } + if (!$(this).is(":checked")) { + $(this).parent().parent().children('.step-text').removeClass('strikethrough text-primary'); + } + }); - $('.all-bonfires').on('click', function() { - $('#all-bonfires-dialog').modal('show'); - }); + function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash, bonfireName) { + $('#complete-bonfire-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { + $.post( + '/completed-bonfire', + { + bonfireInfo: { + completedWith: didCompleteWith, + solution: bonfireSolution, + bonfireHash: thisBonfireHash, + bonfireName: bonfireName + } + }, + function(res) { + if (res) { + window.location.href = '/bonfires' + } + }); + } + } - $('.next-challenge-button').on('click', function() { - l = location.pathname.split('/'); - window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); - }); + function completedFieldGuide(fieldGuideId) { + if ($('.signup-btn-nav').length < 1) { + $.post( + '/completed-field-guide', + { + fieldGuideInfo: { + fieldGuideId: fieldGuideId + } + }, + function(res) { + if (res) { + window.location.href = '/field-guide' + } + }); + } + } + $('.next-bonfire-button').on('click', function() { + var bonfireSolution = myCodeMirror.getValue(); + var thisBonfireHash = passedBonfireHash || null; + var bonfireName = $('#bonfire-name').text(); + var didCompleteWith = $('#completed-with').val() || null; + completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash, bonfireName); - // Bonfire instructions functions - $('#more-info').on('click', function() { - ga('send', 'event', 'Challenge', 'more-info', challengeName); - $('#brief-instructions').hide(); - $('#long-instructions').show().removeClass('hide'); + }); - }); - $('#less-info').on('click', function() { - $('#brief-instructions').show(); - $('#long-instructions').hide(); - }); + $('.next-field-guide-button').on('click', function() { + console.log('click'); + var fieldGuideId = $('#fieldGuideId').text(); + completedFieldGuide(fieldGuideId); + }); - var upvoteHandler = function () { - var _id = storyId; - $('#upvote').unbind('click'); - var alreadyUpvoted = false; - for (var i = 0; i < upVotes.length; i++) { - if (upVotes[i].upVotedBy === user._id) { - alreadyUpvoted = true; - break; + $("img").error(function () { + $(this).unbind("error").attr("src", "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"); + }); + + $('#completed-courseware').on('click', function() { + $('#complete-courseware-dialog').modal('show'); + }); + + $('#completed-zipline-or-basejump').on('click', function() { + $('#complete-zipline-or-basejump-dialog').modal('show'); + }); + + $('#complete-bonfire-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + + $('#next-courseware-button').on('click', function() { + console.log(passedCoursewareHash); + if ($('.signup-btn-nav').length < 1) { + switch (challengeType) { + case 0: + case 1: + case 2: + $.post( + '/completed-courseware/', + { + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName + } + }).success( + function(res) { + if (res) { + window.location.href = '/challenges'; + } } - } - if (!alreadyUpvoted) { - $.post('/stories/upvote', - { - data: { - id: _id, - upVoter: user - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#upvote').bind('click', upvoteHandler); - }) - .done(function (data, textStatus, xhr) { - $('#upvote').text('Upvoted!').addClass('disabled'); - - $('#storyRank').text(data.rank + " points"); - }); - } - }; - $('#upvote').on('click', upvoteHandler); - - - var storySubmitButtonHandler = function storySubmitButtonHandler() { - - var link = $('#story-url').val(); - var headline = $('#story-title').val(); - var description = $('#description-box').val(); - var userDataForUpvote = { - upVotedBy: user._id, - upVotedByUsername: user.profile.username - }; - $('#story-submit').unbind('click'); - $.post('/stories/', + ); + break; + case 3: + var didCompleteWith = $('#completed-with').val() || null; + var publicURL = $('#public-url').val() || null; + $.post( + '/completed-zipline-or-basejump/', { - data: { - link: link, - headline: headline, - timePosted: Date.now(), - description: description, - storyMetaDescription: storyMetaDescription, - rank: 1, - upVotes: [userDataForUpvote], - author: { - picture: user.profile.picture, - userId: user._id, - username: user.profile.username - }, - comments: [], - image: storyImage - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#story-submit').bind('click', storySubmitButtonHandler); - }) - .done(function (data, textStatus, xhr) { - window.location = '/stories/' + JSON.parse(data).storyLink; + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName, + completedWith: didCompleteWith, + publicURL: publicURL, + challengeType: challengeType + } + }).success( + function() { + window.location.href = '/challenges'; + }).fail( + function() { + window.location.href = '/challenges'; }); - - }; - - $('#story-submit').on('click', storySubmitButtonHandler); - - var commentSubmitButtonHandler = function commentSubmitButtonHandler() { - $('comment-button').unbind('click'); - var data = $('#comment-box').val(); - - $('#comment-button').attr('disabled', 'disabled'); - $.post('/stories/comment/', + break; + case 4: + var didCompleteWith = $('#completed-with').val() || null; + var publicURL = $('#public-url').val() || null; + var githubURL = $('#github-url').val() || null; + $.post( + '/completed-zipline-or-basejump/', { - data: { - associatedPost: storyId, - body: data, - author: { - picture: user.profile.picture, - userId: user._id, - username: user.profile.username - } - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#comment-button').attr('disabled', false); - }) - .done(function (data, textStatus, xhr) { - window.location.reload(); + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName, + completedWith: didCompleteWith, + publicURL: publicURL, + githubURL: githubURL, + challengeType: challengeType, + verified: false + } + }).success(function() { + window.location.href = '/challenges'; + }).fail(function() { + window.location.replace(window.location.href); }); + break; + default: + break; + } + } + }); + + $('.all-challenges').on('click', function() { + $('#show-all-dialog').modal('show'); + }); + + $('#showAllButton').on('click', function() { + $('#show-all-dialog').modal('show'); + }); + + $('.next-challenge-button').on('click', function() { + l = location.pathname.split('/'); + window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); + }); + +// Bonfire instructions functions + $('#more-info').on('click', function() { + ga('send', 'event', 'Challenge', 'more-info', challengeName); + $('#brief-instructions').hide(); + $('#long-instructions').show().removeClass('hide'); + + }); + $('#less-info').on('click', function() { + $('#brief-instructions').show(); + $('#long-instructions').hide(); + }); + + var upvoteHandler = function () { + var _id = storyId; + $('#upvote').unbind('click'); + var alreadyUpvoted = false; + for (var i = 0; i < upVotes.length; i++) { + if (upVotes[i].upVotedBy === user._id) { + alreadyUpvoted = true; + break; + } + } + if (!alreadyUpvoted) { + $.post('/stories/upvote', + { + data: { + id: _id, + upVoter: user + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#upvote').bind('click', upvoteHandler); + }) + .done(function (data, textStatus, xhr) { + $('#upvote').text('Upvoted!').addClass('disabled'); + + $('#storyRank').text(data.rank + " points"); + }); + } + }; + $('#upvote').on('click', upvoteHandler); + + var storySubmitButtonHandler = function storySubmitButtonHandler() { + + var link = $('#story-url').val(); + var headline = $('#story-title').val(); + var description = $('#description-box').val(); + var userDataForUpvote = { + upVotedBy: user._id, + upVotedByUsername: user.profile.username }; + $('#story-submit').unbind('click'); + $.post('/stories/', + { + data: { + link: link, + headline: headline, + timePosted: Date.now(), + description: description, + storyMetaDescription: storyMetaDescription, + originalStoryAuthorEmail: user.email, + rank: 1, + upVotes: [userDataForUpvote], + author: { + picture: user.profile.picture, + email: user.email, + userId: user._id, + username: user.profile.username + }, + comments: [], + image: storyImage + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#story-submit').bind('click', storySubmitButtonHandler); + }) + .done(function (data, textStatus, xhr) { + window.location = '/stories/' + JSON.parse(data).storyLink; + }); - $('#comment-button').on('click', commentSubmitButtonHandler); + }; + + $('#story-submit').on('click', storySubmitButtonHandler); + + var commentSubmitButtonHandler = function commentSubmitButtonHandler() { + $('comment-button').unbind('click'); + var data = $('#comment-box').val(); + + $('#comment-button').attr('disabled', 'disabled'); + $.post('/stories/comment/', + { + data: { + associatedPost: storyId, + originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, + body: data, + author: { + picture: user.profile.picture, + userId: user._id, + username: user.profile.username, + email: user.email + } + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#comment-button').attr('disabled', false); + }) + .done(function (data, textStatus, xhr) { + window.location.reload(); + }); + }; + + $('#comment-button').on('click', commentSubmitButtonHandler); }); var profileValidation = angular.module('profileValidation',['ui.bootstrap']); profileValidation.controller('profileValidationController', ['$scope', '$http', - function($scope, $http) { - $http.get('/account/api').success(function(data) { - $scope.user = data.user; - $scope.user.profile.username = $scope.user.profile.username ? $scope.user.profile.username.toLowerCase() : undefined; - $scope.storedUsername = data.user.profile.username; - $scope.storedEmail = data.user.email; - $scope.user.email = $scope.user.email ? $scope.user.email.toLowerCase() : undefined; - $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle ? $scope.user.profile.twitterHandle.toLowerCase() : undefined; - $scope.asyncComplete = true; - }); - } + function($scope, $http) { + $http.get('/account/api').success(function(data) { + $scope.user = data.user; + $scope.user.profile.username = $scope.user.profile.username ? $scope.user.profile.username.toLowerCase() : undefined; + $scope.storedUsername = data.user.profile.username; + $scope.storedEmail = data.user.email; + $scope.user.email = $scope.user.email ? $scope.user.email.toLowerCase() : undefined; + $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle ? $scope.user.profile.twitterHandle.toLowerCase() : undefined; + $scope.asyncComplete = true; + }); + } ]); profileValidation.controller('pairedWithController', ['$scope', - function($scope) { - $scope.existingUser = null; - } + function($scope) { + $scope.existingUser = null; + } ]); profileValidation.controller('emailSignUpController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('emailSignInController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('URLSubmitController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('nonprofitFormController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('doneWithFirst100HoursFormController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('submitStoryController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.directive('uniqueUsername',['$http',function($http) { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, element, attrs, ngModel) { - element.bind("keyup", function (event) { - ngModel.$setValidity('unique', true); - if (element.val()) { - $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { - if (element.val() == scope.storedUsername) { - ngModel.$setValidity('unique', true); - } else if (data) { - ngModel.$setValidity('unique', false); - } - }); - } - }); + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { + if (element.val() == scope.storedUsername) { + ngModel.$setValidity('unique', true); + } else if (data) { + ngModel.$setValidity('unique', false); + } + }); } + }); } + } }]); profileValidation.directive('existingUsername', ['$http', 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 { - element.removeClass('ng-dirty'); - ngModel.$setPristine(); - } - if (element.val()) { - $http - .get("/api/checkExistingUsername/" + element.val()) - .success(function (data) { - ngModel.$setValidity('exists', data); - }); - } + 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 { + element.removeClass('ng-dirty'); + ngModel.$setPristine(); + } + if (element.val()) { + $http + .get("/api/checkExistingUsername/" + element.val()) + .success(function (data) { + ngModel.$setValidity('exists', data); }); } + }); } + } }]); profileValidation.directive('uniqueEmail', ['$http', function($http) { - return { - restrict: 'A', - require: 'ngModel', - link: function getUnique (scope, element, attrs, ngModel) { - element.bind("keyup", function (event) { - ngModel.$setValidity('unique', true); - if (element.val()) { - $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { - if (element.val() == scope.storedEmail) { - ngModel.$setValidity('unique', true); - } else if (data) { - ngModel.$setValidity('unique', false); - } - }); - }; - }); - } + return { + restrict: 'A', + require: 'ngModel', + link: function getUnique (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { + if (element.val() == scope.storedEmail) { + ngModel.$setValidity('unique', true); + } else if (data) { + ngModel.$setValidity('unique', false); + } + }); + }; + }); } + } }]); diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 29bed7d4ca..7038fb414d 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -17,6 +17,10 @@ var links = "Global Function Object": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function", "Arguments object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments", + // ========= GLOBAL OBJECT METHODS + "parseInt()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt", + + // ========= PROPERTIES/MISC "String.length" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length", @@ -63,7 +67,7 @@ var links = "Array.sort()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort", "Array.splice()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice", "Array.toString()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString", - + // ======== MATH METHODS "Math.max()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max", "Math.min()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min", diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 2ee74a11d9..023df8403e 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -50,7 +50,7 @@ "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}\n\nfactorialize(5);", + "challengeSeed": "function factorialize(num) {\n return num;\r\n}\n\nfactorialize(5);", "MDNlinks" : ["Arithmetic Operators"] }, { @@ -253,7 +253,7 @@ "assert.deepEqual(where([{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], 'should return with multiples');" ], "MDNlinks" : ["Global Object", "Object.hasOwnProperty()", "Object.keys()"] - }, + }, { "_id":"a39963a4c10bc8b4d4f06d7e", "name":"Seek and Destroy", @@ -318,6 +318,80 @@ "assert.deepEqual(diff([], ['snuffleupagus', 'cookie monster', 'elmo']), ['snuffleupagus', 'cookie monster', 'elmo'], 'empty array');" ] }, + { + "_id": "a7f4d8f2483413a6ce226cac", + "name": "Roman Numeral Converter", + "tests": [ + "expect(convert(12)).to.equal(\"XII\");", + "expect(convert(5)).to.equal(\"V\");", + "expect(convert(9)).to.equal(\"IX\");", + "expect(convert(29)).to.equal(\"XXIX\");", + "expect(convert(16)).to.equal(\"XVI\");" + ], + "difficulty": "2.02", + "description": [ + "Convert the number be a roman numeral.", + "All roman numerals answers should be provided in upper-case." + ], + "challengeSeed": "function convert(num) {\n return num;\r\n}\n\nconvert(36);", + "MDNlinks" : ["Array.splice()", "Array.indexOf()", "Array.join()"] + }, + { + "_id": "a0b5010f579e69b815e7c5d6", + "name": "Search and Replace", + "tests": [ + "expect(replace(\"Let us go to the store\", \"store\", \"mall\")).to.equal(\"Let us go to the mall\");", + "expect(replace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\")).to.equal(\"He is Sitting on the couch\");", + "expect(replace(\"This has a spellngi error\", \"spellngi\", \"spelling\")).to.equal(\"This has a spelling error\");", + "expect(replace(\"His name is Tom\", \"Tom\", \"john\")).to.equal(\"His name is John\");", + "expect(replace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\")).to.equal(\"Let us get back to more Bonfires\");" + ], + "difficulty": "2.03", + "description": [ + "Perform a search and replace on the sentence using the arguments provided and return the new sentence.", + "First argument is the sentence the perform the search and replace on.", + "Second argument is the word that you will be replacing (before).", + "Third argument is what you will be replacing the second argument with (after)." + ], + "challengeSeed": "function replace(str, before, after) {\n return str;\r\n}\n\nreplace(\"A quick brown fox jumped over the lazy dog\", \"jumped\", \"leaped\");", + "MDNlinks" : ["Array.splice()", "String.replace()", "Array.join()"] + }, + { + "_id": "aa7697ea2477d1316795783b", + "name": "Pig Latin", + "tests": [ + "expect(translate(\"california\")).to.equal(\"aliforniacay\");", + "expect(translate(\"paragraphs\")).to.equal(\"aragraphspay\");", + "expect(translate(\"glove\")).to.equal(\"oveglay\");", + "expect(translate(\"algorithm\")).to.equal(\"algorithmway\");", + "expect(translate(\"eight\")).to.equal(\"eightway\");" + ], + "difficulty": "2.04", + "description": [ + "Translate the provided string to pig latin.", + "Pig Latin takes the first consonant (or consonant cluster) of an English word, moves it to the end of the word and suffixes an \"ay\".", + "If a word begins with a vowel you just add \"way\" to the end." + ], + "challengeSeed": "function translate(str) {\n return str;\r\n}\n\ntranslate(\"consonant\");", + "MDNlinks" : ["Array.indexOf()", "Array.push()", "Array.join()", "String.substr()", "String.split()"] + }, + { + "_id": "afd15382cdfb22c9efe8b7de", + "name": "DNA Pairing", + "tests": [ + "assert.deepEqual(pair(\"ATCGA\"),[['A','T'],['T','A'],['C','G'],['G','C'],['A','T']], 'should return the dna pair');", + "assert.deepEqual(pair(\"TTGAG\"),[['T','A'],['T','A'],['G','C'],['A','T'],['G','C']], 'should return the dna pair');", + "assert.deepEqual(pair(\"CTCTA\"),[['C','G'],['T','A'],['C','G'],['T','A'],['A','T']], 'should return the dna pair');" + ], + "difficulty": "2.05", + "description": [ + "The DNA strand is missing the pairing element. Match each character with the missing element and return the results as a 2d array.", + "Base pairs are a pair of AT and CG. Match the missing element to the provided character.", + "Return the provided character as the first element in each array." + ], + "challengeSeed": "function pair(str) {\n return str;\r\n}\n\npair(\"GCG\");", + "MDNlinks" : ["Array.push()", "String.split()"] + }, { "_id": "af7588ade1100bde429baf20", "name" : "Missing letters", @@ -495,7 +569,7 @@ "assert.deepEqual(steamroller([[['a']], [['b']]]), ['a', 'b'], 'should flatten nested arrays');", "assert.deepEqual(steamroller([1, [2], [3, [[4]]]]), [1, 2, 3, 4], 'should flatten nested arrays');", "assert.deepEqual(steamroller([1, [], [3, [[4]]]]), [1, 3, 4], 'should work with empty arrays');" - ], + ], "MDNlinks" : ["Array.isArray()"] }, { @@ -519,12 +593,12 @@ "difficulty": "2.16", "description": [ "Return the sum of all indices of elements of 'arr' that can be paired with one other element to form a sum that equals the value in the second argument 'arg'. If multiple sums are possible, return the smallest sum. Once an element has been used, it cannot be reused to pair with another.", - "For example, pairwise([1, 4, 2, 3, 0, 5], 5) should return 15 because every element can be paired with another element to equal 5.", + "For example, pairwise([1, 4, 2, 3, 0, 5], 7) should return 11 because 4, 2, 3 and 5 can be paired with each other to equal 7.", "pairwise([1, 3, 2, 4], 4) would only equal 1, because only the first two elements can be paired to equal 4, and the first element has an index of 0!" ], - "challengeSeed": "function pairwise(arr, arg) {\n return arg;\n}\n\npairwise([1,4,2,3,0,5], 5);", + "challengeSeed": "function pairwise(arr, arg) {\n return arg;\n}\n\npairwise([1,4,2,3,0,5], 7);", "tests": [ - "expect(pairwise([1, 4, 2, 3, 0, 5], 5)).to.equal(15);", + "expect(pairwise([1, 4, 2, 3, 0, 5], 7)).to.equal(11);", "expect(pairwise([1, 3, 2, 4], 4)).to.equal(1);", "expect(pairwise([1,1,1], 2)).to.equal(1);", "expect(pairwise([0, 0, 0, 0, 1, 1], 1)).to.equal(10);", @@ -714,5 +788,27 @@ "expect(permAlone('abfdefa')).to.equal(2640);", "expect(permAlone('zzzzzzzz')).to.equal(0);" ] + }, + { + "_id": "a19f0fbe1872186acd434d5a", + "name": "Friendly Date Ranges", + "difficulty": "4.05", + "description": [ + "Implement a way of converting two dates into a more friendly date range that could be presented to a user.", + "It must not show any redundant information in the date range.", + "For example, if the year and month are the same then only the day range should be displayed.", + "Secondly, if the starting year is the current year, and the ending year can be inferred by the reader, the year should be omitted.", + "Input date is formatted as YYYY-MM-DD" + ], + "challengeSeed": "function friendly(str) {\n return str;\n}\n\nfriendly(['2015-07-01', '2015-07-04']);", + "tests": [ + "assert.deepEqual(friendly(['2015-07-01', '2015-07-04']), ['July 1st','4th'], 'ending month should be omitted since it is already mentioned');", + "assert.deepEqual(friendly(['2015-12-01', '2016-02-03']), ['December 1st','February 3rd'], 'one month apart can be inferred it is the next year');", + "assert.deepEqual(friendly(['2015-12-01', '2017-02-03']), ['December 1st, 2015','February 3rd, 2017']);", + "assert.deepEqual(friendly(['2016-03-01', '2016-05-05']), ['March 1st','May 5th, 2016']);", + "assert.deepEqual(friendly(['2017-01-01', '2017-01-01']), ['January 1st, 2017'], 'since we do not duplicate only return once');", + "assert.deepEqual(friendly(['2022-09-05', '2023-09-04']), ['September 5th, 2022','September 4th, 2023']);" + ], + "MDNlinks" : ["String.split()", "String.substr()", "parseInt()"] } ] diff --git a/seed_data/challenge-hashes b/seed_data/challenge-hashes deleted file mode 100644 index 64932960ca..0000000000 --- a/seed_data/challenge-hashes +++ /dev/null @@ -1,201 +0,0 @@ -/* - - - -"aceca143b92049a4392a859e" -"ce9394f67d413734758e27e4" -"1369953ef6f03098cb60e2f7" -"fa229986db0716662e63163a" -"0a6207dfc9ac12f223b4e686" -"2e538b339cbcb7437e61d71f" -"21de1104b8ea7c6a382442d9" -"0aa3497514cb1d8a0ed04933" -"3a45068415f5a20a5d4a2039" -"6a73ec9b86f0e742f6c6ea76" -"06dc21940748badcdb29561e" -"0f2341ef36740b5c2b9d830c" -"dc4cc7ce1fd0418859b10e0b" -"1fcda9b5e0bf502c8b770374" -"482879c42188cde1f9ad3d8f" -"3d680bd03fd291028af57f4f" -"6b444fe5aa97359238bd3c0d" -"72d6ad274e72ffcd399e05cb" -"e864a710647bb6883c9ec617" -"024dcd8cc0178de7daa4f59f" -"ded24fb937a3fd60dc624677" -"9ad4c3b2aebe7282e973e5df" -"d16c66ecc656c82899a2d427" -"ee3e4bf39b840609dd88f5af" -"9a10d27eb6ab6f5030bdbcd9" -"b4cca5ecdeca1eebc0dc4183" -"ccdf1ef407864df62b03331e" -"1baa0329f02e41a5517b11a6" -"b5b33e5ecf6c02a5780ac419" -"84b690f53cd6cbbbf4a361f5" -"4e3b1b9d41ff11bdf3722ffe" -"1fd45e64c43cd640abcf8a76" -"c8bd32cdd943c88fb8a74934" -"9e5440269d09b71f07002333" -"5080016b8b68f40f7476c341" -"f762d45cc870d9f18c6b7024" -"cf21358ab1245c7033504b42" -"41eff7cb0f2fd8ec9e0cbf68" -"a344c63de6a00feea5c541e0" -"7ab3c18c0815d7b1f02e8768" -"a6f36e4387e2e1e25af4e000" -"9064af1431c1ff3aaa15a0d6" -"50946066aaba43a294155ab5" -"70e82e77857317d6936ecdcc" -"dc644dc20d5b02ffd7c8cc94" -"92af1e9f1813619d820dec4d" -"9d4a077fdaaa24b78a54cee9" -"cfd5fa7aea14fb294bd4f5ff" -"068541b6c5130ca541d6c05c" -"39f4d1f12142d583b0318265" -"4a201b8fa69d11cb669dca8c" -"6fa6bba2e334dd2c9e5a6911" -"30ff082b6a28558f16743fea" -"afed31c4dc2ddf056e263acb" -"b2431ecf36f0b780959aa03d" -"54e42938ab6fa923a5fbfbff" -"48d50e3ff5e96f152d912ebf" -"6c7aad2ee996132f76630c05" -"2705f0482774977fa9b5c7bc" -"c09d6682c7a144d9afd4bfab" -"1a0a30c55dd9bc2fff4e2aac" -"30c22b502e80afd144d79120" -"8f882989bbc98c68b6c7347e" -"888bf771b63a5f64e6b696ed" -"959aa13a2e6439fd5d29c5fe" -"d03d311ca64bf59a2404dcd0" -"7a650f3e4308a097d7883b1f" -"54c207808ac349fa34ed24a9" -"3cb12e3ea31fe15ba9d234e5" -"0d9f8123e52f791ae172f627" -"387518b1bb96bd918e7d0b91" -"27198d8918c9802ac0b06a58" -"7a519a552f723c6b1fdc9389" -"4240096651a50f888765ed60" -"8ba94f78cc44cfb82b3678d2" -"1cf7c3bda6ec7952923bc90f" -"cd54811e976edbc798a87c24" -"e4ee17912f5d7d80d41dc52d" -"572346dd1874a0c749c3a031" -"cf700031502680580ff25980" -"7054518832e5c7efde04a58c" -"7716c6b6a3dcc9c5df79a262" -"f4d1084c692f5a07c25a1b47" -"817b80892a78fc6f83eab4eb" -"ae0e0adda6d063d3fd2c9499" -"33184c427ecaab77b5332521" -"c6c4da707e3eed987eb65ba6" -"40f9801913c0cb9800960bf7" -"af11cf78e8340f172ba76bbe" -"c1daea28b5996b8e8800e300" -"7312b3234b5f2f50887597b9" -"3e4361c5356b30f8ce833268" -"e6fc8827b9aa96fc91035ac2" -"11658fe51b23319533c72106" -"34207fe236870fd855392ef6" -"dc970a2b17aa1432d180d078" -"2f1686ad711b5ac3d98905b5" -"ed20f4bf782e10ec26c7b3b1" -"cf4d6241df36b0bbcea401e2" -"97e414eb00338bd7bd5d1b31" -"bcfebb743729d4fc51b0db49" -"52782c1eaa15e050ac748b18" -"57f65a6cd2eb0a2745fc90ee" -"fcc569e80808c6cd84c839cc" -"2447f8e0e3ccfd895a619695" -"600687fe6332b2e2b20d6a84" -"0f8a972361baddb3ac64b6d9" -"f768594ab352cf8e2282b615" -"3163b25e25b0d6859df17751" -"c73d68f0cf77aa113337fcde" -"cc16163137f13f70decbcb5c" -"4adbf6c7ac2004ee8ae20f55" -"c2779f78a0e57dcee471cefd" -"bf44903cd2fca4ae4daf0818" -"ca8013372d58365e21bd0428" -"e308901ca272ad447dca965a" -"ec8cefe29832cd726185119f" -"36bf51da3f32b1e9550f3b15" -"aefff6dffdf534a6c9601f70" -"ce237971a71127f9ce747232" -"99f4d97fae16ccbeb8c26d46" -"c9f6480328ec4b63b1034826" -"ce10bb6e80b09533cb21c9a5" -"311a54aaf062b29422ac0b02" -"b2073bec873b295c04e9fb7e" -"7b7d94035d63c9c0598b242b" -"e19e9a0487f1bd3eabdb777c" -"eeafe5169a2afd9ce3979698" -"96c7a1f9fb73468fdb770034" -"77e607be2498160d3ceeff60" -"c784fa6b1fd7d9aedb05b607" -"a76319bca87e0b57bc79064f" -"a08db36785c51e5b92a6e92f" -"0933ab4a366d57fd1f1c0a48" -"17663f17a835cfe0bca64f4a" -"aee8323d1de634fc42c11d90" -"682044caf09a068e26c11cab" -"1346fdbe3c6fcb808b99cdf6" -"26fd8f47fe4791e2dd07b2e0" -"b1da27dd129216c2ead2e162" -"618adebe192e896eb1501708" -"2628d9eeb38db68c0b255a75" -"1fb78eda5deb8972cc357a9b" -"2fdebb85a401de75bb580a02" -"c59eae24bee36f63c938723d" -"fb66b55d5617e27ca64836a3" -"55994022872e01fd41d8ec7b" -"53ae83b5490e1cf55625ff3d" -"c44975deeb67b96e6cc37eb5" -"94c64d5ff48ca6487f5e0b07" -"e34e7c1a3d70678955a5df8e" -"6426d30a923dec6377132c47" -"d67811849354ded9d34c9ece" -"01e7ae81207cfd230cb02747" -"e24558477f94bcdd82f16ebe" -"4370f6dcc2f53bd5abed7899" -"679b1145cd3072fbcd513bcb" -"4dafd3fd84e7a337ef3e9933" -"2aca3d41cb148603b4a2d70b" -"5e5aa8c960d024109543c577" -"c5f5f313c8a7056bbcdb7639" -"a7b2aaa4cf85fce98cdeb314" -"e556c0d14bc0ce9cc7bde0ca" -"922a8284880a7c879d846f90" -"b146cf2fbf206ca48687e9b6" -"fbdc52bb7c7620bd4508e5a3" -"23d79a262a81459e75576cc2" -"a26721706d3b9f971cef3ce6" -"fb3801ed1b056a3ab4bbfdc1" -"d5cafae617f1485826de335c" -"e9650dfc8b570f9d33381012" -"453c804c7ce82e83305cd751" -"3eaab01bdd1393be8d461777" -"2b6d9ddd4754b71d92ed31b7" -"2338e9af6acab9fcf5327f6a" -"588e7405c30c346ed2ea5e44" -"eec8837994054fb2043b6166" -"0877406795956ca9eb13fea1" -"1dea30e852067eec48a24035" -"ee716ce45ea812167ed2497a" -"9621d5aeaf8de9b2f58977bd" -"e9bb838723083bac7f83d602" -"ef64da08bbe787891d47c31b" -"e01b6616cc6cf7819172f7f3" -"17c821c34fca587daec981fc" -"d0c4b7153dcb3e6af47bcd98" -"5188eb00680051c35cd6e0f0" -"9fc7b68e736a1f4931226ad8" -"b5eac897471f125224a3e594" -"c1d0f890a02c85a9320f96d5" -"7e3b1ca1b1b1e990367f0cc2" -"bff5ff77174c43f7c681e0bd" -"ddeb794121937d07a887c9e6" -"9db902e930f25fbacc125087" -"4f82d277b47ae9c98095f068" -"24f999491fa46cd194c7fd35" -*/ \ No newline at end of file diff --git a/seed_data/challenge-migration.js b/seed_data/challenge-migration.js new file mode 100644 index 0000000000..6109f7775b --- /dev/null +++ b/seed_data/challenge-migration.js @@ -0,0 +1,58 @@ +require('dotenv').load(); +var bonfires = require('./bonfires.json'), + app = require('../server/server'), + mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + User = app.models.User, + UserIdentity = app.models.userIdentity, + oldUri='mongodb://localhost:27017/app30893198', + coursewares = require('./coursewares.json'); + +var counter = 0; +var offerings = 2; + +var CompletionMonitor = function() { + counter++; + console.log('call ' + counter); + + if (counter < offerings) { + return; + } else { + process.exit(0); + } +}; + +MongoClient.connect(oldUri, function(err, database) { + + database.collection('users').find({}).batchSize(20).toArray(function(err, users) { + if (users !== null && users.length !== 0) { + var mappedUserArray = users.map(function(user) { + Object.keys(user.profile).forEach(function(prop) { + user[prop] = user.profile[prop]; + }); + Object.keys(user.portfolio).forEach(function(prop) { + user[prop] = user.portfolio[prop]; + }); + + user.completedCoursewares = Object.keys(user.challengesHash) + .filter(function(key) { + return user.challengesHash[key] !== 0; + }) + .map(function(key) { + return({ + _id: coursewares[key].id, + completedDate: user.challengesHash[key] + }); + }); + + return user; + }); + User.create(mappedUserArray, function(err) { + if (err) { + console.log(err); + } + console.log("a batch finished"); + }); + } + }); +}); diff --git a/seed_data/challengeMapping.json b/seed_data/challengeMapping.json new file mode 100644 index 0000000000..db6686d266 --- /dev/null +++ b/seed_data/challengeMapping.json @@ -0,0 +1,226 @@ +[ + { + "oldNumber": "0", + "newId": "bd7124d8c441eddfaeb5bdef" + }, + { + "oldNumber": "1", + "newId": "bd7125d8c441eddfaeb5bd0f" + }, + { + "oldNumber": "2", + "newId": "" + }, + { + "oldNumber": "3", + "newId": "bd7127d8c441eddfaeb5bdef" + }, + { + "oldNumber": "4", + "newId": "bd7128d8c441eddfaeb5bdef" + }, + { + "oldNumber": "5", + "newId": "bd8129d8c441eddfaeb5bdef" + }, + { + "oldNumber": "6", + "newId": "" + }, + { + "oldNumber": "7", + "newId": "" + }, + { + "oldNumber": "8", + "newId": "bd7112d8c441eddfaeb5bdef" + }, + { + "oldNumber": "9", + "newId": "bd7113d8c441eddfaeb5bdef" + }, + { + "oldNumber": "10", + "newId": "bd7114d8c441eddfaeb5bdef" + }, + { + "oldNumber": "11", + "newId": "bd7115d8c441eddfaeb5bdef" + }, + { + "oldNumber": "12", + "newId": "bd7116d8c441eddfaeb5bdef" + }, + { + "oldNumber": "13", + "newId": "bd7117d8c441eddfaeb5bdef" + }, + { + "oldNumber": "14", + "newId": "bd7118d8c441eddfaeb5bdef" + }, + { + "oldNumber": "15", + "newId": "" + }, + { + "oldNumber": "16", + "newId": "" + }, + { + "oldNumber": "17", + "newId": "" + }, + { + "oldNumber": "18", + "newId": "" + }, + { + "oldNumber": "19", + "newId": "bd7123d8c441eddfaeb5bdef" + }, + { + "oldNumber": "20", + "newId": "bd8124d8c441eddfaeb5bdef" + }, + { + "oldNumber": "21", + "newId": "bd8126d8c441eddfaeb5bdef" + }, + { + "oldNumber": "22", + "newId": "bd8127d8c441eddfaeb5bdef" + }, + { + "oldNumber": "23", + "newId": "bd8128d8c441eddfaeb5bdef" + }, + { + "oldNumber": "24", + "newId": "bd7129d8c441eddfaeb5bdef" + }, + { + "oldNumber": "25", + "newId": "bd7130d8c441eddfaeb5bdef" + }, + { + "oldNumber": "26", + "newId": "bd7131d8c441eddfaeb5bdef" + }, + { + "oldNumber": "27", + "newId": "bd7132d8c441eddfaeb5bdef" + }, + { + "oldNumber": "28", + "newId": "bd7133d8c441eddfaeb5bdef" + }, + { + "oldNumber": "29", + "newId": "bd7134d8c441eddfaeb5bdef" + }, + { + "oldNumber": "30", + "newId": "bd7135d8c441eddfaeb5bdef" + }, + { + "oldNumber": "31", + "newId": "bd7136d8c441eddfaeb5bdef" + }, + { + "oldNumber": "32", + "newId": "" + }, + { + "oldNumber": "33", + "newId": "bd7138d8c441eddfaeb5bdef" + }, + { + "oldNumber": "34", + "newId": "bd7137d8c441eddfaeb5bdef" + }, + { + "oldNumber": "35", + "newId": "bd7140d8c441eddfaeb5bdef" + }, + { + "oldNumber": "36", + "newId": "" + }, + { + "oldNumber": "37", + "newId": "" + }, + { + "oldNumber": "38", + "newId": "" + }, + { + "oldNumber": "39", + "newId": "" + }, + { + "oldNumber": "40", + "newId": "" + }, + { + "oldNumber": "41", + "newId": "" + }, + { + "oldNumber": "42", + "newId": "" + }, + { + "oldNumber": "43", + "newId": "" + }, + { + "oldNumber": "44", + "newId": "" + }, + { + "oldNumber": "45", + "newId": "" + }, + { + "oldNumber": "46", + "newId": "" + }, + { + "oldNumber": "47", + "newId": "" + }, + { + "oldNumber": "48", + "newId": "bd7153d8c441eddfaeb5bd2f" + }, + { + "oldNumber": "49", + "newId": "bd7154d8c441eddfaeb5bdef" + }, + { + "oldNumber": "50", + "newId": "bd7155d8c441eddfaeb5bdef" + }, + { + "oldNumber": "51", + "newId": "bd7156d8c441eddfaeb5bdef" + }, + { + "oldNumber": "52", + "newId": "bd7157d8c441eddfaeb5bdef" + }, + { + "oldNumber": "53", + "newId": "bd7158d8c441eddfaeb5bdef" + }, + { + "oldNumber": "54", + "newId": "" + }, + { + "oldNumber": "55", + "newId": "" + } +] diff --git a/seed_data/comments.json b/seed_data/comments.json deleted file mode 100644 index c44dc44f37..0000000000 --- a/seed_data/comments.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - -] \ No newline at end of file diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 72bf8f1b5d..07e2ada181 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -1,2590 +1,11 @@ [ - { - "_id" : "bd7123c8c441eddfaeb5bdef", - "name": "Start our Challenges", - "difficulty": "0.00", - "description": [ - "Welcome to Free Code Camp's first challenge! Click on the button below for further instructions.", - "Awesome. Now you can read the rest of this challenge's instructions.", - "You can edit code in the text editor we've embedded into this web page.", - "Do you see the code in the text editor that says <h1>hello</h1>? That's an HTML element.", - "Most HTML elements have an opening tag and a closing tag. Opening tags look like this: <h1>. Closing tags look like this: </h1>. Note that the only difference between opening and closing tags is that closing tags have a slash after their opening angle bracket.", - "Once you've completed the challenge, the \"Go to my next challenge\" button will become enabled. Click it - or press control and enter at the same time - to advance to the next challenge.", - "To enable the \"Go to my next challenge\" button on this exercise, change the h1 tag's text to say \"Hello World\" instead of \"Hello\"." - ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;" - ], - "challengeSeed": [ - "

Hello

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf0887a", - "name": "Use the h2 Element", - "difficulty" : "0.01", - "description": [ - "Add an h2 tag that says \"cat photo app\" to make a second HTML element below the \"hello world\" h1 element.", - "The h2 element you enter will create an h2 element on the website.", - "This element tells the browser how to render the text that it contains.", - "h2 elements are slightly smaller than h1 elements. There are also h3, h4, h5 and h6 elements." - ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;", - "expect((/cat(\\s)+photo(\\s)+app/gi).test($('h2').text())).to.be.true;" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08801", - "name": "Use the P Element", - "difficulty" : "0.02", - "description": [ - "Create a p element below the h2 element, and give it the text \"hello paragraph\".", - "p elements are the preferred element for normal-sized paragraph text on websites.", - "You can create a p element like so: <p>I'm a p tag!</p>" - ], - "tests": [ - "expect((/hello(\\s)+paragraph/gi).test($('p').text())).to.be.true;" - ], - "challengeSeed": [ - "

hello world

", - "

hello html

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aeaf08801", - "name": "Add a Line Break to Visually Separate Elements", - "difficulty" : "0.03", - "description": [ - "Add a line break between the <h2> and <p> elements.", - "You can create an line break element with <br/>.", - "Note that <br/> has no closing tag. It is a self-closing element. See how a forward-slash precedes the closing bracket?" - ], - "tests": [ - "expect($('br')).to.exist;" - ], - "challengeSeed": [ - "

hello world

", - "

hello html

", - "

hello paragraph

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08802", - "name": "Uncomment HTML", - "difficulty" : "0.04", - "description": [ - "Uncomment the h1, h2 and p elements.", - "Commenting is a way that you can leave comments within your code without affecting the code itself.", - "Commenting is also a convenient way to make code inactive without having to delete it entirely.", - "You can start a comment with <!-- and end a comment with -->." - ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;" - ], - "challengeSeed": [ - "", - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08809", - "name": "Using Important to Override Styles", - "difficulty" : "0.14", - "description": [ - "Apply both the \"blue-text\" and \"urgently-red\" classes to all h2 elements, but use !important to ensure the element is rendered as being red.", - "Sometimes HTML elements will receive conflicting information from CSS classes as to how they should be styled.", - "If there's a conflict in the CSS, the browser will use whichever style declaration is closest to the bottom of the CSS document (whichever declaration comes last). Note that in-line style declarations are the final authority in how an HTML element will be rendered.", - "There's one way to ensure that an element is rendered with a certain style, regardless of where that declaration is located. That one way is to use !important.", - "Look at the example in the editor's style tag to see how you can use !important.", - "Now see if you can make sure the h2 element is rendered in the color red without removing the blue-text class, doing an in-line styling, or changing the sequence of CSS class declarations." - ], - "tests": [ - "expect($('h2')).to.have.class('urgently-red');", - "expect($('h2')).to.have.class('blue-text');", - "expect($('h2')).to.have.css('color', 'rgb(255, 0, 0)');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08811", - "name": "Use rgb Codes for Precise Colors", - "difficulty" : "0.17", - "description": [ - "Change the red-text class's color rgb value to be red.", - "Another way to represent color in CSS is with rgb, or red-green-blue notation.", - "For each of the three colors, you specify a value between 0 and 256.", - "For example, black is rgb(0, 0, 0), white is rgb(255, 255, 255), bright green is rgb(0, 255, 0). You can also get less intense colors by using values lower than 255. For example, light green is rgb(0, 123, 0).", - "If you think about it, this is just as precise as using hex code, because 16 times 16 is 256. In practice, most developers use hex code since it's faster to say out loud and to type.", - "We'll use 6-digit hex code in all our challenges going forward, but it's good to be aware of this rgb notation." - ], - "tests": [ - "expect($('h2')).to.have.css('color', 'rgb(255, 0, 0)');", - "expect($('h2')).to.have.class('red-text');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08810", - "name": "Use Hex Codes for Precise Colors", - "difficulty" : "0.15", - "description": [ - "Change the hex code in the \"red-text\" class to hex code for the color red.", - "Hexadecimal (hex) code is a popular way of specifying color in CSS.", - "Hex code is called \"hex\" because each digit has 16 possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, and f", - "The six hex code correspond to red-red-green-green-blue-blue.", - "You can change these six values to make more than 16 million colors!", - "The higher the value in a field, the more intense its color. For example, #000000 is black, #ffffff is white, and #00ff00 is bright green. You can also get less intense colors by using values lower than f. For example, #00f000 with the second green digit set to 0 is a light green, and #00f900 is a slightly brighter green", - "Now figure out how to make the bright green in the \"red-text\" class into a bright red." - ], - "tests": [ - "expect($('h2')).to.have.css('color', 'rgb(255, 0, 0)');", - "expect($('h2')).to.have.class('red-text');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9bedf08810", - "name": "Use Shortened 3 Digit Hex Codes", - "difficulty" : "0.16", - "description": [ - "Change the hex code in the \"red-text\" class to the shortened 3-digit hex code for the color red.", - "You can also shorten the 6-digit color hex code to a 3-digit code. For example, #00ff00 becomes #0f0. This is less precise, but equally effective." - ], - "tests": [ - "expect($('h2')).to.have.css('color', 'rgb(255, 0, 0)');", - "expect($('h2')).to.have.class('red-text');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08811", - "name": "Set the Alpha of a Color with rgba", - "difficulty" : "0.17", - "description": [ - "Change the red-text class's color rgb value to be red.", - "Another way to represent color in CSS is with rgb, or red-green-blue notation.", - "For each of the three colors, you specify a value between 0 and 256.", - "For example, black is rgb(0, 0, 0), white is rgb(255, 255, 255), bright green is rgb(0, 255, 0). You can also get less intense colors by using values lower than 255. For example, light green is rgb(0, 123, 0).", - "If you think about it, this is just as precise as using hex code, because 16 times 16 is 256. In practice, most developers use hex code since it's faster to say out loud and to type.", - "We'll use 6-digit hex code in all our challenges going forward, but it's good to be aware of this rgb notation." - ], - "tests": [ - "expect($('h2')).to.have.css('color', 'rgb(255, 0, 0)');", - "expect($('h2')).to.have.class('red-text');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08812", - "name": "Add an Image to your Website", - "difficulty" : "0.18", - "description": [ - "Use an img element to add the image http://bit.ly/cutegraycat to your website.", - "You can add images to your website by using the img element.", - "An example of this would be <img src=\"www.your-image-source.com/your-image.jpg\"/>. Note that in most cases, img elements are self-closing.", - "Try it with this image: http://bit.ly/cutegraycat." - ], - "tests": [ - "expect($('img').attr('src')).to.equal('http://bit.ly/cutegraycat');" - ], - "challengeSeed": [ - "", - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9acdf08812", - "name": "Specify an Image Size", - "difficulty" : "0.19", - "description": [ - "Create a class called narrow-image and use it to resize the image so that it's only 200 pixels wide", - "Uh-oh, our image is too big to fit on a mobile phone. As a result, our user will need to scroll horizontally to view the image. But we can fix this by specifying an image size.", - "CSS has an attribute called width that controls an element's width. Just like with fonts, we'll use pixels(px) to specify the images width.", - "Create a class called narrow-image and added it to the image element. Change the width to 200 pixels." - ], - "tests": [ - "expect($('img')).to.have.class('narrow-image');", - "expect($('img')).to.have.css('width', '200px')" - ], - "challengeSeed": [ - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9bedf08813", - "name": "Add a Border Around an Element", - "difficulty" : "0.20", - "description": [ - "Create a class called \"thick-green-border\" that puts a 5-pixel-wide green border around your cat photo.", - "CSS Borders have attributes like style, color and width.", - "We've created an example border around your h1 element. See if you can add a 10-pixel-wide green border around your cat photo." - ], - "tests": [ - "expect($('img')).to.have.class('thick-green-border');", - "expect($('img')).to.have.css('border-color', 'rgb(0, 255, 0)');", - "expect($('img')).to.have.css('border-width', '10px');" - ], - "challengeSeed": [ - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08814", - "name": "Add Rounded Corners with a Border Radius", - "difficulty" : "0.21", - "description": [ - "Give your cat photo a border-radius of 10 pixels.", - "Your cat photo currently has sharp corners. We can round out those corners with a CSS attribute called border-radius.", - "You can specify a border-radius with pixels. This will affect how rounded the corners are. Add this attribute to your thick-green-border class and set it to 10 pixels." - ], - "tests": [ - "expect($('img')).to.have.class('thick-green-border');", - "expect($('img')).to.have.css('border-radius', '10px');" - ], - "challengeSeed": [ - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08815", - "name": "Make an Image Circular with a Border Radius", - "difficulty" : "0.22", - "description": [ - "Give your cat photo a border-radius of 50%.", - "In addition to pixels, you can also specify a border-radius of a percentage." - ], - "tests": [ - "expect($('img')).to.have.css('border-radius', '50%');" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08816", - "name": "Use an Anchor Tag to Link to an External Page", - "difficulty" : "0.23", - "description": [ - "Create an anchor tag hyperlink that links to freecodecamp.com", - "hyperlinks link your users to other URLs (web addresses).", - "All hyperlinks include an href attribute that tells the browser which URL to go to when your user clicks on it.", - "hyperlinks are also important for web crawlers (like those used by Google to index the internet), which use links not only in determining which page to crawl next, but also to determine the relative importance of a given website.", - "To create a , use an anchor tag, specify an href and enclose the text you want to appear as the link, like this: <a href=\"http://www.google.com\">This is a link to Google</a>" - ], - "tests": [ - "expect((/free(\\s+)?code(\\s+)?camp(\\s+)?/gi).test($('a').text())).to.be.true;", - "expect($('a').filter(function(index) { return /freecodecamp\\.com/gi.test($('a')[index]); }).length).to.eql(1);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "", - "
", - "This is a link to Google", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08817", - "name": "Make Named Anchors using the Hash Symbol", - "difficulty" : "0.24", - "description": [ - "Use the hash symbol(#) to turn the link at the bottom of your website into a named anchor.", - "Sometimes you'll want to add an anchor element to your website before you know which URL its href will link to.", - "This is also handy when you're changing the behavior of your link using jQuery, which we'll learn about later.", - "Replace your link to freecodecamp.com's href with a hash symbol to turn it into a named anchor." - ], - "tests": [ - "expect((/this link leads nowhere/gi).test($('a').text())).to.be.true;", - "expect($('a').filter(function(index) { return /#/gi.test($('a')[index]); }).length).to.eql(1);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "", - "
", - "This named anchor leads nowhere", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08820", - "name": "Turn an Image into a Link", - "difficulty" : "0.25", - "description": [ - "Wrap the gray cat's image with an anchor tag that leads nowhere.", - "You can make elements into links by wrapping them in an anchor tag.", - "Check out the example snow-colored cat's photo. When you hover over it with your cursor, you'll see the finger pointer you usually see when you hover over a link. The photo is now a link.", - "Wrap your gray cat's photo in an anchor tag", - "Use the hash symbol as the anchor tag's href." - ], - "tests": [ - "expect($('a').filter(function(index) { return /#/gi.test($('a')[index]); }).length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08818", - "name": "Add Alt Text to an image", - "difficulty" : "0.26", - "description": [ - "Add the alt text \"a photo of a cute gray cat\" to our cat photo", - "alt text is what browsers will display if they fail to load the image. alt text also helps your blind or visually impaired users understand what your image portrays. Search engines also look at alt text.", - "In short, every image on your website should have alt text!", - "You can add alt text right in the image tag, like we've done here with the \"cute white cat\" image." - ], - "tests": [ - "expect($('img').filter(function(){ return /cat/gi.test(this.alt) }).length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "\"a", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad88fee1348bd9aedf08825", - "name": "Adjusting the Padding of an Element", - "difficulty" : "0.27", - "description": [ - "Change the padding of the green box to match that of the red box.", - "An element's padding controls the amount of space between an element and its border.", - "Here, we can see that the green box and the red box and the green box are nested within the yellow box. Note that the red box has more padding than the green box.", - "When you increase the green box's padding, it will increase the distance between the word \"padding\" and the border around the text." - ], - "tests": [ - "expect($('.green-box')).to.have.css('padding', '20px')" - ], - "challengeSeed": [ - "", - "
margin
", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08822", - "name": "Adjust the Margin of an Element", - "difficulty" : "0.28", - "description": [ - "Change the margin of the green box to match that of the red box.", - "An element's margin controls the amount of space between an element's border and surrounding elements.", - "Here, we can see that the green box and the red box and the green box are nested within the yellow box. Note that the red box has more margin than the green box, making it appear smaller.", - "When you increase the green box's padding, it will increase the distance between its border and surrounding elements." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin', '20px')" - ], - "challengeSeed": [ - "", - "
margin
", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08823", - "name": "Add a Negative Margin to an Element", - "difficulty" : "0.29", - "description": [ - "Change the margin of the green box to a negative value, so it fills the entire horizontal width of the blue box.", - "An element's margin controls the amount of space between an element's border and surrounding elements.", - "If you set an element's margin to a negative value, the element will grow larger.", - "Try to set the margin to a negative value like the one for the red box." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin', '-15px')" - ], - "challengeSeed": [ - "", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08824", - "name": "Add Different Padding to Each Side of an Element TEST", - "difficulty" : "0.30", - "description": [ - "Give the green box a padding of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", - "Sometimes you will want to customize an element so that it has different padding on each of its sides.", - "CSS allows you to control the padding of an element on all four sides with padding-top, padding-right, padding-bottom, and padding-left attributes." - ], - "tests": [ - "expect($('.green-box')).to.have.css('padding-bottom', '20px')", - "expect($('.green-box')).to.have.css('padding-left', '40px')" - ], - "challengeSeed": [ - "", - "
margin
", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1248bd9aedf08824", - "name": "Add Different a Margin to Each Side of an Element TEST", - "difficulty" : "0.31", - "description": [ - "Give the green box a margin of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", - "Sometimes you will want to customize an element so that it has a different margin on each of its sides.", - "CSS allows you to control the margin of an element on all four sides with margin-top, margin-right, margin-bottom, and margin-left attributes." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin-bottom', '20px')", - "expect($('.green-box')).to.have.css('margin-left', '40px')" - ], - "challengeSeed": [ - "", - "
margin
", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08826", - "name": "Use Clockwise Notation to Specify an Element's Padding", - "difficulty" : "0.32", - "description": [ - "Use Clockwise Notation to give an element padding of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", - "Instead of specifying an element's padding-top, padding-right, padding-bottom, and padding-left attributes, you can specify them all in one line, like this: padding: 10px 20px 10px 20px;.", - "These four values work like a clock: top, right, bottom, left, and will produce the exact same result as using the side-specific padding instructions.", - "You can also use this notation for margins!" - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin-bottom', '20px')", - "expect($('.green-box')).to.have.css('margin-left', '40px')" - ], - "challengeSeed": [ - "", - "
margin
", - "", - "
", - "
padding
", - "
padding
", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9acde08812", - "name": "Use Bootstrap for Responsive Images", - "difficulty" : "0.33", - "description": [ - "Add the img-responsive Bootstrap class to the image.", - "Specifying a width of 200 pixels on our img element made it fit our phone's screen, but it's not a perfect fit. It would be great if the image could be exactly the width of our phone's screen.", - "Fortunately, there's a Responsive CSS Framework called written by Twitter called Bootstrap. You can add Bootstrap to any app just by including it with <link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/> at the top of your HTML, but we've gone ahead and included it for you here.", - "Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name Responsive Design.", - "Now all you need to do is add the img-responsive class to your image." - ], - "tests": [ - "expect($('img')).to.have.class('img-responsive');" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "This is a link to Google", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd8acde08812", - "name": "Center Text with Bootstrap", - "difficulty" : "0.34", - "description": [ - "Add Bootstrap's text-center class to your h2 element.", - "Now that we're using Bootstrap, we can center our heading elements (h2) to make them look better. All we need to do is add the class text-center to the h1 and h2 elements.", - "Note that you can add several classes to the same element by seperating each of them with a space, like this: <h2 class=\"text-red text-center\">your text</h2>." - ], - "tests": [ - "expect($('h2')).to.have.class('text-center');" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348cd8acde08812", - "name": "Create a Button", - "difficulty" : "0.35", - "description": [ - "Create a button with the text \"Delete\" using the HTML button element.", - "HTML has special elements that function like links, but look like buttons. Let's creating a default HTML button." - ], - "tests": [ - "expect((/delete/gi).test($('button').text())).to.be.true;" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348cd8acdf08812", - "name": "Create a Bootstrap Button", - "difficulty" : "0.36", - "description": [ - "Apply the Bootstrap's btn class to both of your buttons.", - "Bootstrap has its own button styles, which look much better than the plain HTML ones." - ], - "tests": [ - "expect($('.btn').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348cd8acef08812", - "name": "Create a Block Element Bootstrap Button", - "difficulty" : "0.37", - "description": [ - "Add Bootstrap's btn-block class to both of your buttons.", - "Normally, your buttons are only as wide as the text they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space.", - "Note that these buttons still need the btn class." - ], - "tests": [ - "expect($('.btn-block').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bae87fee1348cd8acef08812", - "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "0.38", - "description": [ - "Add Bootstrap's btn-block class to both of your buttons.", - "Normally, your buttons are only as wide as the text they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space.", - "Note that these buttons still need the btn class." - ], - "tests": [ - "expect($('.btn-block').length).to.eql(2);", - "expect($('.btn-primary').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348cd8acef08812", - "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "0.39", - "description": [ - "Add Bootstrap's btn-primary class to both of your buttons.", - "Bootstrap comes with several pre-defined colors for buttons. The btn-primary class is the main button color you'll use throughout your app.", - "Note that these buttons still need the btn and btn-block classes." - ], - "tests": [ - "expect($('.btn-primary').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348ce8acef08812", - "name": "Warn your Users of a Dangerous Action with the Bootstrap Button Danger Class", - "difficulty" : "0.40", - "description": [ - "Change the \"Delete\" button from btn-primary to btn-danger.", - "Bootstrap comes with several pre-defined colors for buttons. The btn-danger class is the button color you'll use to notify users that the button performs a destructive action, such as deleting a cat photo.", - "Note that this button still needs the btn and btn-block classes." - ], - "tests": [ - "expect($('.btn-danger').length).to.eql(1);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad88fee1348ce8acef08812", - "name": "Use the Bootstrap Grid to Put Two Elements Side By Side", - "difficulty" : "0.41", - "description": [ - "Put the \"Like\" and \"Delete\" buttons side-by-side by wrapping them in both in a <div class=\"row\"> element and each of them in a <div class=\"row\"> element.", - "Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", - "The row class is applied to a div, and the buttons themselves can be nested within it." - ], - "tests": [ - "expect($('.row').length).to.eql(2);", - "expect($('.col-xs-12').length).to.eql(4);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "
", - "
", - " ", - "
", - "
", - " ", - "
", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad89fee1348ce8acef08812", - "name": "Wrap Side By Side Elements in a Bootstrap Row", - "difficulty" : "0.42", - "description": [ - "Put the \"Like\" and \"Delete\" buttons side-by-side by wrapping them in both in a <div class=\"row\"> element and each of them in a <div class=\"row\"> element.", - "Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", - "The row class is applied to a div, and the buttons themselves can be nested within it." - ], - "tests": [ - "expect($('.row').length).to.eql(2);", - "expect($('.col-xs-6').length).to.eql(4);" - ], - "challengeSeed": [ - "", - "

cat photo app

", - "", - "
", - "
", - "
", - " ", - "
", - "
", - " ", - "
", - "
", - "", - "
", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08813", - "name": "Add Alt Text to an Image TEST", - "difficulty" : "0.43", - "description": [ - "Add the alt text \"A picture of a gray cat\" to the image.", - "Alt text is a useful way to tell people (and web crawlers like Google) what is pictured in a photo. It's extremely important for helping blind or visually impaired people understand the content of your website.", - "You can add alt text right in the img element like this: <img src=\"www.your-image-source.com/your-image.jpg\" alt=\"your alt text\"/>." - ], - "tests": [ - "expect((/cat/gi).test($('img').attr('alt')).to.be.true;" - ], - "challengeSeed": [ - "", - "

hello world

", - "

cat photo app

", - "

lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", - "" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08827", - "name": "Create an Bulleted Unordered List", - "difficulty" : "0.44", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08828", - "name": "Created a Numbered Ordered List", - "difficulty" : "0.45", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08829", - "name": "Create a Text Field", - "difficulty" : "0.46", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08830", - "name": "Use HTML5 to Make a Field Required", - "difficulty" : "0.47", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08831", - "name": "Use HTML5 to Specify an Input Type", - "difficulty" : "0.49", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08832", - "name": "Create a Text Area", - "difficulty" : "0.50", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08834", - "name": "Create a Set of Radio Buttons", - "difficulty" : "0.51", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08835", - "name": "Create a Set of Checkboxes", - "difficulty" : "0.52", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08836", - "name": "Create a HTML Form", - "difficulty" : "0.53", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08841", - "name": "Change the background of element", - "difficulty" : "0.54", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08842", - "name": "Make an element translucent", - "difficulty" : "0.55", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08844", - "name": "Add a Drop Shadow", - "difficulty" : "0.56", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08845", - "name": "Make a Navbar", - "difficulty" : "0.57", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08847", - "name": "Add a Logo to a Navbar", - "difficulty" : "0.58", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08848", - "name": "Make a Footer", - "difficulty" : "0.59", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08849", - "name": "Use Icons as Links", - "difficulty" : "0.60", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08850", - "name": "Add Hover Effects to Icons", - "difficulty" : "0.61", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08851", - "name": "Add Depth to a Page with a Well", - "difficulty" : "0.62", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08852", - "name": "Add an ID to a Button", - "difficulty" : "0.52", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08853", - "name": "Fire a Modal by Clicking a Button", - "difficulty" : "0.63", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08854", - "name": "Style a Modal with a Header", - "difficulty" : "0.64", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08855", - "name": "Style a Modal with a Body", - "difficulty" : "0.65", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08856", - "name": "Make a Modal Dismissable", - "difficulty" : "0.66", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08857", - "name": "Create an Accordian Menu", - "difficulty" : "0.67", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08863", - "name": "Add a Gradient to a Button", - "difficulty" : "0.68", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08864", - "name": "Adjust the Line Height of Text", - "difficulty" : "0.69", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08866", - "name": "", - "difficulty" : "0.70", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08867", - "name": "", - "difficulty" : "0.71", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08868", - "name": "", - "difficulty" : "0.711", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08869", - "name": "", - "difficulty" : "0.712", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08870", - "name": "", - "difficulty" : "0.713", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08871", - "name": "", - "difficulty" : "0.714", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08872", - "name": "", - "difficulty" : "0.72", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08873", - "name": "", - "difficulty" : "0.73", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08874", - "name": "", - "difficulty" : "0.74", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08875", - "name": "", - "difficulty" : "0.75", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08876", - "name": "", - "difficulty" : "0.76", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08877", - "name": "", - "difficulty" : "0.77", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08878", - "name": "", - "difficulty" : "0.78", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08879", - "name": "", - "difficulty" : "0.79", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08880", - "name": "", - "difficulty" : "0.80", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08881", - "name": "", - "difficulty" : "0.81", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08882", - "name": "", - "difficulty" : "0.82", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08883", - "name": "", - "difficulty" : "0.83", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08884", - "name": "", - "difficulty" : "0.84", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08885", - "name": "", - "difficulty" : "0.85", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08886", - "name": "", - "difficulty" : "0.86", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08887", - "name": "", - "difficulty" : "0.87", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08888", - "name": "", - "difficulty" : "0.88", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08889", - "name": "", - "difficulty" : "0.89", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08890", - "name": "", - "difficulty" : "0.90", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08891", - "name": "", - "difficulty" : "0.91", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08892", - "name": "", - "difficulty" : "0.92", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08893", - "name": "", - "difficulty" : "0.93", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08894", - "name": "", - "difficulty" : "0.94", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08895", - "name": "", - "difficulty" : "0.95", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08896", - "name": "", - "difficulty" : "0.96", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08897", - "name": "", - "difficulty" : "0.97", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08898", - "name": "", - "difficulty" : "0.98", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08899", - "name": "", - "difficulty" : "0.99", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08100", - "name": "", - "difficulty" : "1.00", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - { - "_id" : "bd7123d8c441eddfaeb5bdef", + "_id": "bd7124d8c441eddfaeb5bdef", "name": "Learn how Free Code Camp Works", - "difficulty": 9.99, + "difficulty": 0.01, + "challengeSeed": "114486344", "description": [ - "Watch this 90 second video, or simply read this summary:", + "Watch this 1-minute video, or simply read this summary:", "Welcome to Free Code Camp. We're a community of busy people learning to code.", "We built this community because learning to code is hard. But anyone who can stay motivated can learn to code. And the best way to stay motivated is to code with friends.", "To maximize accessibility, all our challenges are self-paced, browser-based, and free.", @@ -2594,53 +15,725 @@ "If you make it through Free Code Camp, you will be able to get a coding job. There are far more job openings out there than there are qualified coders to fill them.", "Also, for every pure coding job, there are at least 5 additional jobs that require some coding skills. So even if you decide not to pursue coding as a career, you'll still walk away with a valuable job skill.", "There are 3 keys to succeeding in our community: do the challenges, make friends, and find a routine.", - "Now it's time to join our chatroom. Click the \"Go to my next challenge\" button to move on to your next challenge." + "Now it's time to join our chatroom. Click the \"I've completed this challenge\" button to move on to your next challenge." ], - "tests": [ - "" - ], - "challengeSeed": [ - "114486344" - ], - "challengeType" : 2 + "challengeType": 2, + "tests": [] }, { - "_id": "bd7123c9c441eddfaeb5bdef", - "name": "Meet Booleans", - "difficulty": "9.98", + "_id": "bd7125d8c441eddfaeb5bdff", + "name": "Preview our Challenge Map", + "difficulty": 0.02, + "challengeSeed": "114627322", "description": [ - "Return true", - "Some additional directions" + "Before you start learning how to code, we'd like to introduce you to a few things.", + "In the next 15 minutes, we'll introduce you to our challenges, chat rooms, field guide, portfolios and news site. Then we'll teach you how to get help if you get stuck somewhere.", + "First let's look at our Challenge Map. Click on the \"Map\" button in the upper right hand corner. This map shows all the challenges that will teach you how to code.", + "You should complete all these challenges in order.", + "Once you finish these Waypoint challenges, you'll move on to Bonfires (algorithm practice), then Ziplines (front end development practice) and finally Basejumps (full stack development practice). After that, you'll start building projects for nonprofits.", + "All of these challenges can be completed in a single one- or two-hour sitting.", + "This challenge map is just for your reference. When you return to FreeCodeCamp.com, we'll automatically redirect you to the next challenge that you should be doing." ], - "tests": [ - "expect(welcomeToBooleans()).to.be.a(\"boolean\");", - "expect(welcomeToBooleans()).to.be.true;" - ], - "challengeSeed": [ - "function welcomeToBooleans() {\n // Good luck!\n return false;\n}\n\nwelcomeToBooleans();" - ], - "challengeType": 1 + "challengeType": 2, + "tests": [] }, { - "_id": "bd7123c9c441eddfaeb5bdef", - "name": "Meet Booleans", - "difficulty": "9.98", + "_id": "bd7125d8c441eddfaeb5bd0f", + "name": "Join Our Chat Room", + "difficulty": 0.03, + "challengeSeed": "124555254", "description": [ - "Return true", - "Some additional directions" + "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.", + "Make sure your Free Code Camp account includes your email address. You can do this here: http://freecodecamp.com/account.", + "Click this link, which will email you an invite to Free Code Camp's Slack chat room: http://freecodecamp.com/api/slack.", + "Now check your email and click the link in the email from Slack", + "Complete the sign up process, then update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.", + "Now enter the general chat room and introduce yourself to our chat room by typing: \"hello world!\".", + "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", + "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", + "You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner." ], - "tests": [ - "expect(welcomeToBooleans()).to.be.a(\"boolean\");", - "expect(welcomeToBooleans()).to.be.true;" + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7125d8c441eddfaeb5bd1f", + "name": "Browse our Field Guide", + "difficulty": 0.04, + "challengeSeed": "114627322", + "description": [ + "Free Code Camp has an up-to-date field guide that will answer your many questions.", + "Click the \"Field Guide\" button in the upper right hand corner.", + "You can browse the field guide at your convenience. Most of its articles take less than 1 minute to read.", + "When you click the Field Guide button, it will always take you back to whichever article you were last reading" ], - "challengeSeed": [ - "function welcomeToBooleans() {", - "// Good luck!", - "return false;", - "}", - "", - "welcomeToBooleans();" + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7125d8c441eddfaeb5bd2f", + "name": "Customize your Porfolio Page", + "difficulty": 0.05, + "challengeSeed": "114627322", + "description": [ + "You and all your fellow campers have portfolio pages.", + "To see your portfolio page, click your picture in the upper right hand corner.", + "Your portfolio page will automatically show off your progress through Free Code Camp.", + "Click the \"Update my portfolio page or manage my account\" button", + "You can link to your Github, Twitter and LinkedIn accounts. If you've already built some websites, you can link to them here as well.", + "Be sure to click the \"Update my Bio\" or \"Update my Social Links\" button to save this new information to your portfolio page." ], - "challengeType": 1 + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7126d8c441eddfaeb5bd3f", + "name": "Try Camper News", + "difficulty": 0.06, + "challengeSeed": "115275066", + "description": [ + "Camper News is the best place for our campers to share and discuss helpful links.", + "Click \"News\" in the upper right hand corner.", + "You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.", + "You can upvote links. This will push the link up the rankings of hot links.", + "You an also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.", + "You can also submit links. You can modify the link's headline and also leave an initial comment about the link.", + "You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.", + "When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7137d8c441eddfaeb5bdef", + "name": "Get Help the Hacker Way with RSAP", + "difficulty": 0.07, + "challengeSeed": "111500801", + "description": [ + "Let's cover one last thing before you start working through our lessons: how to get help.", + "Any time you get stuck or don't know what to do next, follow this simple algorithm (procedure): RSAP (Read, Search, Ask, Post).", + "First, R - Read the documentation or error message. A key skill that good coders have is the ability to interpret and then follow instructions.", + "Next, S - Search Google. Good Google queries take a lot of practice. When you search Google, you usually want to include the language or framework you're using. You also want to limit the results to a recent period.", + "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", + "Finally, P - Post on Stack Overflow. Before you attempt to do this, re-read Stack Overflow's guide to asking a good question: http://stackoverflow.com/help/how-to-ask.", + "Now you have a clear algorithm to follow when you need help!" + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7127d8c441eddfaeb5bdef", + "name": "Build a Personal Website", + "difficulty": 0.08, + "challengeSeed": "114627406", + "description": [ + "There are tons of interactive HTML and CSS tutorials out there, but Nathan Bashaw and Christine Bower's Dash tutorials - which they built for General Assembly - are our favorite.", + "Go to https://dash.generalassemb.ly/projects/annas-website-1 and get started with your first project." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7128d8c441eddfaeb5bdef", + "name": "Build a Responsive Blog Theme", + "difficulty": 0.09, + "challengeSeed": "114578441", + "description": [ + "Next, let's learn about responsive web design and continue learning about HTML and CSS.", + "A responsive website will automatically adapt to changes in your browser's width. This means that you can make one version of a website that will look good on desktop, tablet and phone.", + "Later, we'll use Twitter's Bootstrap CSS framework to build responsive websites.", + "You can check it out here: http://getbootstrap.com/.", + "Go to https://dash.generalassemb.ly/projects/jeffs-blog-1 and complete the second project." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8129d8c441eddfaeb5bdef", + "name": "Build a Small Business Website", + "difficulty": 0.10, + "challengeSeed": "114578438", + "description": [ + "Ready for some more HTML and CSS fundamentals?", + "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7112d8c441eddfaeb5bdef", + "name": "Get Started with jQuery", + "difficulty": 0.11, + "challengeSeed": "114578435", + "description": [ + "jQuery is a powerful tool for manipulating HTML elements.", + "It's a lot easier to use than JavaScript itself, so we'll learn it first.", + "It's also extremely popular with employers, so we're going to learn it well.", + "Code School has an excellent free course that will walk us through the basics of jQuery.", + "Go to http://try.jquery.com/levels/1/challenges/1 and complete the first section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7113d8c441eddfaeb5bdef", + "name": "Traverse the DOM", + "difficulty": 0.12, + "challengeSeed": "114591805", + "description": [ + "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", + "Go to http://try.jquery.com/levels/2/challenges/1 and complete the second section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7114d8c441eddfaeb5bdef", + "name": "Work with the DOM", + "difficulty": 0.13, + "challengeSeed": "114591804", + "description": [ + "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", + "Go to http://try.jquery.com/levels/3/challenges/1 and complete the third section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7115d8c441eddfaeb5bdef", + "name": "Listen for DOM Events", + "difficulty": 0.14, + "challengeSeed": "114591802", + "description": [ + "Now let's learn how to use jQuery Listeners. These will \"listen\" for something to happen, and then trigger a subsequent event", + "Go to http://try.jquery.com/levels/4/challenges/1 and complete the fourth section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7116d8c441eddfaeb5bdef", + "name": "Use jQuery for Styling", + "difficulty": 0.15, + "challengeSeed": "114591801", + "description": [ + "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", + "Go to http://try.jquery.com/levels/5/challenges/1 and complete the fifth section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7117d8c441eddfaeb5bdef", + "name": "Build a MadLibs Game", + "difficulty": 0.16, + "challengeSeed": "114591799", + "description": [ + "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", + "If you aren't familiar with Mad Libs, they basically involve inserting random nouns, adjectives and verbs into stories. The stories that result are often hilarious.", + "Go to https://dash.generalassemb.ly/projects/mad-libs-1 and complete the fifth project." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7123d8c441eddfaeb5bdef", + "name": "Learn Basic Computer Science", + "difficulty": 0.17, + "challengeSeed": "114628241", + "description": [ + "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", + "Note that Harvard also has an excellent introduction to computer science course called CS50, but it takes more than 100 hours to complete, and doesn't use JavaScript.", + "Despite being completely self-paced, Stanford's CS101 course is broken up into weeks. Each of the following challenges will address one of those weeks.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ and complete the first week's course work." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8124d8c441eddfaeb5bdef", + "name": "Learn Loops", + "difficulty": 0.18, + "challengeSeed": "114597348", + "description": [ + "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", + "This will introduce us to loops, a fundamental feature of every programming language.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ and complete Week 2." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8125d8c441eddfaeb5bdef", + "name": "Learn Computer Hardware", + "difficulty": 0.19, + "challengeSeed": "114597347", + "description": [ + "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", + "This challenge will also give you an understanding of how bits and bytes work.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ and complete Week 3." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8126d8c441eddfaeb5bdef", + "name": "Learn Computer Networking", + "difficulty": 0.20, + "challengeSeed": "114604811", + "description": [ + "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", + "Particularly important, you will learn about networks and TCP/IP - the protocol that powers the internet.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ and complete Week 4." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8127d8c441eddfaeb5bdef", + "name": "Learn Boolean Logic", + "difficulty": 0.21, + "challengeSeed": "114604812", + "description": [ + "Now we'll do some more table exercises and learn boolean logic.", + "We'll also learn the difference between digital data and analog data.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ and complete Week 5." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd8128d8c441eddfaeb5bdef", + "name": "Learn Computer Security", + "difficulty": 0.22, + "challengeSeed": "114604813", + "description": [ + "We're almost done with Stanford's Introduction to Computer Science course!", + "We'll learn about one of the most important inventions of the 20th century - spreadsheets.", + "We'll also learn about Computer Security and some of the more common vulnerabilities software systems have.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z229/z213/ and complete Week 6, the final week of the course." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7129d8c441eddfaeb5bdef", + "name": "Build an Adventure Game", + "difficulty": 0.23, + "challengeSeed": "114604814", + "description": [ + "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", + "We're going to work through Codecademy's famous interactive JavaScript course.", + "This course will teach us some JavaScript fundamentals while guiding us through the process of building interesting web apps, all within Codecademy's learner-friendly environment!", + "Go to http://www.codecademy.com/courses/getting-started-v2/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-x9DnD/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7130d8c441eddfaeb5bdef", + "name": "Build Rock Paper Scissors", + "difficulty": 0.24, + "challengeSeed": "114604815", + "description": [ + "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-6LzGd/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-Bthev-mskY8/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7131d8c441eddfaeb5bdef", + "name": "Learn JavaScript For Loops", + "difficulty": 0.25, + "challengeSeed": "114614220", + "description": [ + "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-NhsaT/0/1web and complete both the both For and While loop section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-XEDZA/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7132d8c441eddfaeb5bdef", + "name": "Learn JavaScript While Loops", + "difficulty": 0.26, + "challengeSeed": "114612889", + "description": [ + "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-mrTNH-6VIZ9/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7133d8c441eddfaeb5bdef", + "name": "Learn Control Flow", + "difficulty": 0.27, + "challengeSeed": "114612888", + "description": [ + "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", + "We'll also learn some advanced \"Control Flow\" principals, such as ways we can exit loops early.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-qDwp0/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-ZA2rb/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7134d8c441eddfaeb5bdef", + "name": "Build a Contact List", + "difficulty": 0.28, + "challengeSeed": "114612887", + "description": [ + "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-9Sgpi/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-3bmfN/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7135d8c441eddfaeb5bdef", + "name": "Build an Address Book", + "difficulty": 0.29, + "challengeSeed": "114612885", + "description": [ + "Let's learn more about objects.", + "Go to http://www.codecademy.com/courses/spencer-sandbox/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/building-an-address-book/0/1?curriculum_id=506324b3a7dffd00020bf661." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7136d8c441eddfaeb5bdef", + "name": "Build a Cash Register", + "difficulty": 0.30, + "challengeSeed": "114612882", + "description": [ + "In this final Codecademy section, we'll learn even more about JavaScript objects.", + "Go to http://www.codecademy.com/courses/objects-ii/0/1 and complete this section.", + "Be sure to also complete the final section: http://www.codecademy.com/courses/close-the-super-makert/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7118d8c441eddfaeb5bdef", + "name": "Discover Chrome's DevTools", + "difficulty": 0.31, + "challengeSeed": "110752743", + "description": [ + "It's time to learn the most powerful tool your browser has - the Development Tools!", + "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", + "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", + "Go to http://discover-devtools.codeschool.com and complete this short course." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7138d8c441eddfaeb5bdef", + "name": "Learn Regular Expressions", + "difficulty": 0.32, + "challengeSeed": "112547802", + "description": [ + "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", + "Check out http://www.regexr.com. It's a Regular Expression Sandbox.", + "Now go to http://www.regexone.com and complete the tutorial and exercises 1 - 6.", + "Note that you can click \"continue\" to move on to the next step as soon as all the tasks have green check marks beside them. You can often do this just by using the wildcard \"dot\" operator, but try to use the techniques that each lesson recommends." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7154d8c441eddfaeb5bdef", + "name": "Get Started with Angular.js", + "difficulty": 0.33, + "challengeSeed": "114684726", + "description": [ + "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", + "In this course, we'll build a virtual shop entirely in Angular.js.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/1/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7155d8c441eddfaeb5bdef", + "name": "Apply Angular.js Directives", + "difficulty": 0.34, + "challengeSeed": "114684727", + "description": [ + "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", + "Let's learn how these powerful directives work, and how to use them to make your web apps more dynamic", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7156d8c441eddfaeb5bdef", + "name": "Power Forms with Angular.js", + "difficulty": 0.35, + "challengeSeed": "114684729", + "description": [ + "One area where Angular.js really shines is its powerful web forms.", + "Learn how to create reactive Angular.js forms, including real-time form validation.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/3/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7157d8c441eddfaeb5bdef", + "name": "Customize Angular.js Directives", + "difficulty": 0.36, + "challengeSeed": "114685062", + "description": [ + "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/4/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7158d8c441eddfaeb5bdef", + "name": "Create Angular.js Services", + "difficulty": 0.37, + "challengeSeed": "114685060", + "description": [ + "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", + "We'll learn how to use services in this final Code School Angular.js challenge.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/5/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bdff", + "name": "Start a Node.js Server", + "difficulty": 0.38, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd0f", + "name": "Manage Packages with NPM", + "difficulty": 0.39, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd1f", + "name": "Build Web Apps with Express.js", + "difficulty": 0.40, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd2f", + "name": "Manage Data with MongoDB", + "difficulty": 0.41, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + + { + "_id": "bd7140d8c441eddfaeb5bdef", + "name": "Manage Source Code with Git", + "difficulty": 0.42, + "challengeSeed": "114635309", + "description": [ + "Revision Control Systems like Git ensure that, no matter how you experiment with your code, you can always roll back your app to a stable previous state.", + "Git is also a great way to share and contribute to open source software.", + "Go to https://www.codeschool.com/courses/try-git and complete this short interactive course." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7139d8c441eddfaeb5bdef", + "name": "Pair Program on Bonfires", + "difficulty": 0.43, + "challengeSeed": "119657641", + "description": [ + "OK, we're finally ready to start pair programming!", + "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", + "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", + "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", + "We have a special chat room for people ready to pair program. Go to our Slack chatroom and navigate to the # letspair channel and type \"Hello Pair Programmers!\"", + "If someone is available, they will be your \"pair\" - the person you pair programming with.", + "If no one gets back to you in the first few minutes, don't worry. There will be lots of opportunities to pair program in the future.", + "If someone does get back to you, private message them and ask for the email address they used to register Screen Hero.", + "Add them as a new contact in Screen Hero, then click the monitor-looking button to attempt to share your screen with them.", + "Once the Screen Hero session starts, your screen's margins will glow orange. You are now sharing your screen.", + "Your pair will have their own cursor, and will be able to type text on his or her and keyboard.", + "Now it's time to tackle our Bonfires.", + "Go to http://freecodecamp.com/bonfires and start working through our Bonfire challenges.", + "Once you you finish pair programming, end the session in Screen Hero session.", + "Congratulations! You have completed your first pair programming session.", + "Pair program as much as possible with different campers until you've completed all the Bonfire, Zipline and Basejump challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!", + "Mark this challenge as complete and move on to the Bonfires.", + "Keep in mind, the Bonfires are a significant challenge in and of themselves. You are not expected to complete them in one sitting, or to complete them before moving on to our next challenges. Mix them in as you keep learning and have fun!", + "In order to participate in our non-profit projects you will need to have successfully completed all \"3 flame\" bonfires and below. Lastly, completing bonfires is not meant to be a sisyphean task. You won't be required to complete new bonfires that are of lower difficuly than you've already completed!" + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7158d8c442eddfaeb5bdff", + "name": "Zipline: Integrate with Twitch's API", + "difficulty": 1.01, + "challengeSeed": "123488494", + "description": [ + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id": "bd7158d8c442eddfaeb5bd0f", + "name": "Zipline: Integrate with Trello's API", + "difficulty": 1.02, + "challengeSeed": "123488494", + "description": [ + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "In the JavaScript box, scroll down to the comment that reads \"//Changeable code be made here\". This CodePen is already pulling in the relevant JSON from our API. This JSON is from one of our Trello boards. You can view the actual Trello board here: https://trello.com/b/BA3xVpz9/nonprofit-projects.", + "You can view the JSON output here: http://www.freecodecamp.com/api/trello.", + "In addition to each Trello card's description,(which is currently shown), you will also want to show it's name.", + "You'll also want to render each card's labels. Render each label with the card's name as the label's text and each label's color as the label's color.", + "Hint: you will need to write a jQuery loop that which dynamically inserts <li> elements. You will do this using jQuery's each function, described here: https://api.jquery.com/each/.", + "Style the output to make it look visually appealing. Give it a custom font, color scheme, and a clear visual separation between data elements.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id": "bd7158d8c442eddfaeb5bd1f", + "name": "Zipline: Integrate with a Weather API", + "difficulty": 1.03, + "challengeSeed": "123488494", + "description": [ + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id": "bd7158d8c443eddfaeb5bdef", + "name": "Basejump: Build a Voting App", + "difficulty": 2.01, + "challengeSeed": "123488494", + "description": [ + "For this Basejump challenge, you'll build a fully functional voting app. By the time you've finished, your app will function similarly to this: http://voteplex.herokuapp.com/. This Basejump is a great opportunity to pair program with a friend.", + "We'll build this Basejump on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.", + "If you don't already have Cloud 9 account, create one now at http://c9.io.", + "Now let's get your development environment ready for a new Angular-Fullstack application provided by Yeoman.", + "Open up http://c9.io and sign in to your account.", + "Click on Create New Workspace at the top right of the c9.io page, then click on the \"Create a new workspace\" popup that appears below it the button after you click on it.", + "Name your workspace to match your project name that you are working on.", + "Choose Node.js in the selection area below the name field", + "Click the Create button", + "Wait for the workspace to finish processing and select it on the left sidebar, below the Create New Workspace button", + "Click the \"Start Editing\" button.", + "In the lower right hand corner you should see a terminal window. In this window use the following commands. You don't need to know what these mean at this point.", + "rm -rf * && npm install -g yo grunt grunt-cli generator-angular-fullstack && yo angular-fullstack", + "Yeoman will prompt you to answer some questions. Answer them like this:", + "What would you like to write scripts with? JavaScript", + "What would you like to write markup with? HTML", + "What would you like to write stylesheets with? CSS", + "What Angular router would you like to use? ngRoute", + "Would you like to include Bootstrap? Yes", + "Would you like to include UI Bootstrap? Yes", + "Would you like to use MongoDB with Mongoose for data modeling? Yes", + "Would you scaffold out an authentication boilerplate? Yes", + "Would you like to include additional oAuth strategies? Twitter", + "Would you like to use socket.io? No", + "May bower anonymously report usage statistics to improve the tool over time? (Y/n) Y", + "You may get an error similar to ERR! EEXIST, open ‘/home/ubuntu/.npm. This is caused when Cloud9 runs out of memory and kills an install. If you get this, simply re-run this process with the command yo angular-fullstack. You will then be asked a few questions regarding the re-install. Answer them as follows:", + "Existing .yo-rc configuration found, would you like to use it? (Y/n) Y", + "Overwrite client/favicon.ico? (Ynaxdh) Y", + "To finish the installation run the commands: bower install && npm install", + "To start MongoDB, run the following commands in your terminal: mkdir data && echo 'mongod --bind_ip=$IP --dbpath=data --nojournal --rest \"$@\"' > mongod && chmod a+x mongod && ./mongod", + "You will want to open up a new terminal to work from by clicking on the + icon and select New Terminal", + "Start the application by running the following command in your new terminal window: grunt serve", + "Wait for the following message to appear: xdg-open: no method available for opening 'http://localhost:8080' . Now you can open the internal Cloud9 browser. To launch the browser select Preview in the toolbar then select the dropdown option Preview Running Application.", + "Turn the folder in which your application is running into a Git repository by running the following commands: git init && git add . && git commit -am 'initial commit'.", + "Create a new Github repository by signing in to http://github.com and clicking on the + button next to your username in the upper-right hand side of your screen, then selecting \"New Repository\"", + "Enter a project name, then click the \"Create Repository\" button.", + "Find the \"...or push an existing repository from the command line\" section and click the Copy to Clipboard button beside it.", + "Paste the commands from your clipboard into the Cloud9 terminal prompt. This will push your changes to your repository on Cloud 9 up to Github.", + "Check back on your Github profile to verify the changes were successfully pushed up to Github.", + "Now let's push your code to Heroku. If you don't already have a Heroku account, create one at http://heroku.com. You shouldn't be charged for anything, but you will need to add your credit card information to your Heroku before you will be able to use Heroku's free MongoLab add on.", + "Before you publish to Heroku, you should free up as much memory as possible on Cloud9. In each of the Cloud9 terminal prompt tabs where MongoDB and Grunt are running, press the control + c hotkey to shut down these processes.", + "Run the following command in a Cloud9 terminal prompt tab: npm install grunt-contrib-imagemin --save-dev && npm install --save-dev && heroku login. At this point, the terminal will prompt you to log in to Heroku from the command line.", + "Now run yo angular-fullstack:heroku. You can choose a name for your Heroku project, or Heroku will create a random one for you. You can choose whether you want to deploy to servers the US or the EU.", + "Set the config flag for your Heroku environment and add MongoLab for your MongoDB instance by running the following command: cd ~/workspace/dist && heroku config:set NODE_ENV=production && heroku addons:add mongolab.", + "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", + "You can push these new commits to Github by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", + "Now that you've configured your environment, here are the specific User Stories you should implement for this Basejump:,", + "As an authenticated user, I should be able to keep my polls and come back later to access them.", + "As an authenticated user, I should be able to share my polls with my friends.", + "As an authenticated user, I should be able to see the aggregate results of my polls.", + "As an authenticated user, I should be able to delete polls that I decide I don't want anymore.", + "As an authenticated user, I should be able to create a poll with any number of possible items.", + "Here are some bonus User Stories you can try to implement once you've finished the above User Stories:", + "As an unauthenticated user, I should be able to see everyone's polls, but not be able to vote.", + "As an unauthenticated or authenticated user, I should be able to see the in chart form. (This could be implemented using Chart.js or Google Charts.)", + "As an authenticated user, if I don't like the options on a poll I can create a new option.", + "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it." + ], + "challengeType": 4, + "tests": [] + }, + { + "_id": "bd7158d8c443eddfaeb5bdff", + "name": "Basejump: Build a Yelp App", + "difficulty": 2.02, + "challengeSeed": "123488494", + "description": [ + + ], + "challengeType": 4, + "tests": [] + }, + { + "_id": "bd7158d8c443eddfaeb5bd0f", + "name": "Basejump: Build a Pintrest Clone", + "difficulty": 2.03, + "challengeSeed": "123488494", + "description": [ + + ], + "challengeType": 4, + "tests": [] } ] diff --git a/seed_data/field-guides.json b/seed_data/field-guides.json new file mode 100644 index 0000000000..4ffdffa058 --- /dev/null +++ b/seed_data/field-guides.json @@ -0,0 +1,502 @@ +[ + { + "_id": "bd7158d9c441eddfaeb5bdef", + "name": "How do I use this guide?", + "description": [ + "
", + "

This guide will answer many of your questions about learning to code and getting a coding job.

", + "

You can click the \"Next article\" button to go to your next article.

", + "

You can also click the \"Show me all articles\" button to browse all the questions we answer.

", + "
" + ] + }, + { + "_id": "bd7158d9c441eddfaeb5bdff", + "name": "What is Free Code Camp?", + "description": [ + "
", + "

We're a community of busy adults who learn to code by building projects for nonprofits.

", + "

We help our campers (students):

", + "

", + "
    ", + "
  1. Learn full stack JavaScript
  2. ", + "
  3. Build a portfolio of real apps that real people are using
  4. ", + "
  5. Get a coding job
  6. ", + "
", + "

", + "
" + ] + }, + { + "_id": "bd7158d9c441eddfaeb5bd1f", + "name": "Why do I need Free Code Camp?", + "description": [ + "
", + "

Learning to code is hard.

", + "

Most people who successfully learn to code:

", + "

", + "
    ", + "
  1. Code every day
  2. ", + "
  3. Have lots of friends who code
  4. ", + "
", + "

", + "

We give you the structure and the community you need so you can successfully learn to code.

", + "
" + ] + }, + { + "_id": "bd7158d9c441eddfaeb5bd2f", + "name": "How does Free Code Camp work?", + "description": [ + "
", + "First you", + "
", + "Next you", + "
", + "Finally you build full stack JavaScript apps for nonprofits for 800 hours.", + "
", + "This gives you a total of 1,000 hours of coding experience.", + "
" + ] + }, + { + "_id": "bd7158d9c441eddfaeb5bd3f", + "name": "What are the advantages of Free Code Camp?", + "description": [ + "
", + "

", + "
    ", + "
  • • We're free
  • ", + "
  • • We're self-paced
  • ", + "
  • • We're browser-based
  • ", + "
  • • You'll code the entire time
  • ", + " ", + "

", + "
" + ] + }, + { + "_id": "bd7158d9c441eddfaeb5bd4f", + "name": "Will I really be able to get a job after Free Code Camp?", + "description": [ + "
", + "

If you complete this program, you will be able to get a coding job.

", + "", + "

Here are the facts:

", + "

", + "
    ", + "
  • • There are hundreds of thousands of unfilled coding jobs.
  • ", + "
  • • Employers and the US government have joined together to promote nontraditional coding programs like Free Code Camp.
  • ", + "
  • • Full stack JavaScript is one of best paying skill sets, and has a ton of job openings.
  • ", + "
  • • The best proof that you're an employable developer is a portfolio filled with real apps that real people are using.
  • ", + "
", + "

", + "
" + ] + }, + { + "_id": "bd7158d9c440eddfaeb5bdef", + "name": "What will I learn, and in what sequence?", + "description": [ + "
", + "

First, you'll learn basic web design tools like:", + "

    ", + "
  • • HTML - the structure of web pages
  • ", + "
  • • CSS - the visual style of web pages
  • ", + "
  • • Bootstrap - a \"responsive design\" tool that helps your websites look great on tablets and phones
  • ", + "
  • • jQuery - an easy tool for controlling content in the browser
  • ", + "
  • • Chrome DevTools - a tool for understanding and debugging websites, right in your browser
  • ", + "
", + "

", + "

Then you'll learn computer science and the art of programming:", + "

    ", + "
  • • JavaScript - the one programming language that all web browsers use
  • ", + "
  • • Algorithms - step-by-step recipes for getting things done
  • ", + "
  • • Automated Testing - write tests to test the limits of your code
  • ", + "
", + "

", + "

Finally you'll learn Agile Methodologies and Full Stack JavaScript to build projects for nonprofits:", + "

    ", + "
  • • Agile - a set of software development principles that focus the design and production of a project on the needs of its users
  • ", + "
  • • Git - a version control system for saving and sharing your projects
  • ", + "
  • • MongoDB - a popular non-relational database
  • ", + "
  • • Angular.js - a tool for making exciting web interfaces
  • ", + "
  • • Express.js - a powerful web development framework
  • ", + "
  • • Node.js - a JavaScript-based web server
  • ", + "
", + "

", + "
" + ] + }, + { + "_id": "bd7158d9c434eddfaeb5bdef", + "name": "How long does Free Code Camp take?", + "description": [ + "
", + "

It takes about 1,000 hours of coding to develop the skills you'll need to get an entry level software engineering job. Most coding bootcamps try to jam all this into 12 weeks of intensive study. Free Code Camp is fully online, and there will always be other people at your skill level that you can pair program with, so you can learn at your own pace. Here are some example coding schedules:

", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
Time budgetedHours per weekWeeks to complete
Weekends10 hours/week100 weeks (2 years)
Nights and Weekends20 hours/week50 weeks (1 year)
Full time40 hours/week25 weeks (6 months)
Traditional Bootcamp Pacing80 hours/week12 weeks (3 months)
", + "
", + "
" + ] + }, + { + "_id": "bd7158d9c438eddfaeb5bdef", + "name": "Why does Free Code Camp use JavaScript instead of Ruby or Python?", + "description": [ + "
", + "

Like JavaScript, Ruby and Python are high-level scripting languages that can be used for full stack web development. But even if you learned these languages, you'd still need to learn JavaScript. That's because JavaScript is the only language that runs in web browsers. JavaScript has been around for 20 years, and it is still growing in popularity. Because of this, JavaScript has more tools and online learning resources than any other language.

", + " \"A", + "
", + "
" + ] + }, + { + "_id": "bd7158d9c437eddfaeb5bdef", + "name": "What's pair programming, and what's so special about it?", + "description": [ + "
", + "

Pair programming is where two people code together on one computer. You discuss different approaches to solving problems, and keep each other motivated. The result is better code than either of you could have written by yourselves. Because of its benefits, many engineers pair program full time. And it's the best way to learn coding. Thanks to tools that allow two people to share mouse and keyboard inputs, you can pair program with a friend without needing to be in the same room.

", + "

By pair programming with other Free Code Camp students on our coding challenges. Eventually, you'll work with people at nonprofits to build real-life software solutions.

", + "
" + ] + }, + { + "_id": "bd7158d9c436eddfaeb5bdef", + "name": "If Free Code Camp is free, how does it make money?", + "description": [ + "
", + "

We are completely free for both students and for nonprofits.

", + "

Our name is Free Code Camp. We are a free code camp. If you had to pay us (or sign over future earnings), we'd have to change our name. And we are not going to do that.

", + "

We don't make any money at all. Everyone working on our community and our open source projects is a volunteer.

", + "

We plan to eventually cover our operational costs by earning job placement bonuses from companies who hire our graduates. Note that we will not actually garnish any wages from our graduates - employers are already paying recruiters thousands of dollars to find successful candidates. Employers will simply pay the recruitment bonus to Free Code Camp instead of paying a recruiter.

", + "
" + ] + }, + { + "_id": "bd7158d9c435eddfaeb5bdef", + "name": "Does Free Code Camp have an application process?", + "description": [ + "
", + "

Unlike coding bootcamps, anyone can study at Free Code Camp. We're not going to tell you that you can't become a software engineer. We believe the only person who should be able to tell you that is you.

", + "

If you persevere, and keep working through our challenges and nonprofit projects, you will become an employable software engineer.

", + "
" + ] + }, + { + "_id": "bd7158d9c442eddfaeb5bdef", + "name": "Global Control Shortcuts for Mac", + "description": [ + "
", + "

These Global Control Shortcuts for Mac will save you hours by speeding up your typing

", + "
", + "
", + "

These global shortcuts work everywhere on a Mac:", + "
    ", + "
  • Control + F = Forward
  • ", + "
  • Control + B = Backward
  • ", + "
  • Control + N = Next Line
  • ", + "
  • Control + P = Previous Line
  • ", + "
  • Control + H = Backspace
  • ", + "
  • Control + D = Delete
  • ", + "
  • Control + A = Beginning of Line
  • ", + "
  • Control + E = End of Line
  • ", + "
  • Control + K = Kill line
  • ", + "
", + "

" + ] + }, + { + "_id": "bd7158d9c445eddfaeb5bdef", + "name": "Gmail Zero Inbox Shortcuts", + "description": [ + "

These Gmail Shortcuts will save you hours and help you get to Zero Inbox


", + "
", + " ", + "
", + "
", + "

The shortcuts:", + "
    ", + "
  • j - move down
  • ", + "
  • k - move up
  • ", + "
  • o - open
  • ", + "
  • r - reply
  • ", + "
  • a - reply all
  • ", + "
  • f - forward
  • ", + "
  • c - compose
  • ", + "
  • x - select
  • ", + "
  • e - archive
  • ", + "
  • ! - mark spam
  • ", + "
  • z - undo
  • ", + "
", + "

", + "
" + ] + }, + { + "_id": "bd7158d9c446eddfaeb5bdef", + "name": "A Guide to our Nonprofit Projects", + "description": [ + "
", + "

Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp challenges and Bonfire challenges, you'll begin this process.

", + "

Once you've finished all the challenges, click the \"I'm done with all the challenges\" button, which will become enabled. We will prompt you for your email address, then give you further instructions on our  Nonprofit Projects Instructions page.

", + "

We will add you to our  Nonprofit Project Trello board.

", + "

Starting with the end in mind

", + "

Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction).

", + "

You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects.

", + "

Choosing your first Nonprofit Project

", + "

We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates.

", + "

Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week (on the  form you filled out). If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks.

", + "

Our team of nonprofit project camp counselors will match you and your pair based on:", + "

    ", + "
  1. Your estimated time commitment (10, 20 or 40 hours per week)
  2. ", + "
  3. Your time zone
  4. ", + "
  5. The nonprofit projects you've chosen
  6. ", + "
  7. Prior coding experience (we'd like both campers to be able to contribute equally)
  8. ", + "
", + "

", + "

We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace.

", + "

You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project Trello cards. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one.

", + "

Finalizing the Project

", + "

Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process:", + "

    ", + "
  1. We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above.
  2. ", + "
  3. We'll call the stakeholder to confirm once again that he or she agrees with our  terms  and has signed our  Nonprofit Project Stakeholder Pledge.
  4. ", + "
  5. We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder.
  6. ", + "
  7. If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project.
  8. ", + "
", + "

", + "

This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time.

", + "

Nonprofit Stakeholders

", + "

Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a \"stakeholder\" - an authorative person who understands the organization and its needs for this particular project.

", + "

Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs.

", + "

When you and your pair first speak with your nonprofit stakeholder, you'll:", + "

    ", + "
  • talk at length to better understand their needs.
  • ", + "
  • create a new Trello board and use it to prioritize what needs to be built.
  • ", + "
  • and establish deadlines based on your weekly time commitment, and how long you think each task will take.
  • ", + "
", + "

", + "

It's notoriously difficult to estimate how long building software projects will take, so feel free to ask camp counselors for help.

", + "

You'll continue to meet with your stakeholder at least twice a month in your project's Gitter channel.

", + "

You should also ask questions in your project's Gitter channel as they come up throughout the week, and your stakeholder can answer them asynchronously.

", + "

Getting \"blocked\" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter.

", + "

Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!

", + "

Working with your Pair

", + "

You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time.

", + "

Here are our recommended ways of collaborating:", + "

    ", + "
  • • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
  • ", + "
  • • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them.
  • ", + "
  • • Screen Hero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming.
  • ", + "
  • • Write clear and readable code, commit messages, branch names, and pull request messages.
  • ", + "
", + "

", + "

Setting up your Development Environment

", + "

We've created a custom virtual machine image with Ubuntu Linux, Git, Team Viewer, the MEAN Stack and all its dependencies. You can run this virtual image on any computer with at least 2 gigabytes of RAM and 16 gigabytes of hard drive space.

", + "

The benefits of using this virtual machine are as follows:

", + "
    ", + "
  • • Everyone else on Free Code Camp is using this image, so we can all help you troubleshoot various problems that may arise.
  • ", + "
  • • When you pair program, you and your pair will have the exact same environment, which means you will both feel comfortable on each other's machines.
  • ", + "
  • • You can install the image on any computer without worrying about messing up the computer's original data or configuration.
  • ", + "
  • • Even if you end up using Windows or Mac OSX for development later, your server will almost certainly run Linux, so it's worth getting used to Linux.
  • ", + "
  • • Even experienced developers encounter hangups when setting up a development environment. This virtual machine image will remove this tedious process.
  • ", + "
", + "

Install a bit torrent client, then  download our virtual machine image.

", + "

Please note that even though Bit Torrent is often used to download content illegally, all the content on our image is open source and perfectly legal to redistribute.

", + "

Once you've downloaded the file,  download VirtualBox  and follow  this tutorial  to open the image in VirtualBox. You'll want to assign the virtual machine at least two gigabytes of ram.

", + "

Now you have your own Linux development environment. You can shut it down when you're not using it and it will save its state. Please continue to seed the file in bit torrent so that other campers can download it as well. Enjoy!

", + "

Hosting Apps

", + "

Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away.

", + "

If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them.

", + "

Maintaining Apps

", + "

Once you complete a nonprofit project, your obligation to its stakeholder is finished. You goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical \"super user\").

", + "

While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers.

", + "

Office Hours

", + "

Quincy Larson and/or Michael Johnson will be in the  Gitter Nonprofit Project Channel  every Monday and Thursday from 9 - 10 p.m. EST.

", + "

Our goal is to make the discussion as public as possible so all our campers can benefit from each camper’s questions.

", + "

If necessary, we can also hop on Screen Hero with you to help you with issues more specific to your project.

", + "

Pledging to finish the project

", + "

Your nonprofit stakeholder, your pair, and the volunteer camp counselor team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one.

", + "

To confirm that you understand the seriousness of this commitment, we require that all campers  sign this pledge  before starting on their nonprofit projects.

", + "

There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, \"Real artists ship.\" And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career.

", + "
" + ] + }, + { + "_id": "bd7158d9c447eddfaeb5bdef", + "name": "Install Screenhero", + "description": [ + "
", + "

Download for Mac

", + "

Download for Windows

", + "

You can learn more about using Screen Hero by taking  Challenge 34.

", + "

Screen Hero was recently acquired by a collaboration tool called Slack.

", + "It's still available and free, but will go away in the indefinite future. We'll replace it.

", + "
" + ] + }, + { + "_id": "bd7158d9c448eddfaeb5bdef", + "name": "Live Stream Pair Programming on Twitch.tv", + "description": [ + "
", + "

Live Pair Programming

", + "

", + "

Watch the live stream below or on our  Twitch.tv channel.

", + "
", + "
", + "
", + " ", + "
", + "
", + "
", + "
", + "
", + " ", + "
", + "
", + "
", + "
", + " ", + "
", + "
", + "
", + "
", + "

Previous Live Pair Programming Sessions

", + "
", + "
", + " ", + "
", + "

link:  http://www.youtube.com/watch/_BErpDdmBOw

", + "
", + " ", + "
", + "

link:  http://www.youtube.com/watch/Fn9HMn79KH0

", + "
", + " ", + "
", + "

link:  http://www.youtube.com/watch/S7iRBZJwOAs

", + "
", + " ", + "
", + "

link:  http://www.youtube.com/watch/BHNRg39ZblE

", + "
", + " ", + "
", + "

link:  http://www.youtube.com/watch/YDfkHlDmehA

", + "
", + " ", + " ", + " ", + "
" + ] + }, + { + "_id": "bd7158d9c449eddfaeb5bdef", + "name": "Nodeschool Challenges", + "description": [ + "

Learn Node.js, NPM, Express.js, and advanced JavaScript like Functional Programming and Promises


", + "
", + " ", + "
", + "
" + ] + }, + { + "_id": "bd7158d9c450eddfaeb5bdef", + "name": "Nonprofit Project Instructions", + "description": [ + "
", + "

It's time to apply what you've learned here at Free Code Camp.

", + "

By the end of this process, you'll have a portfolio of live apps being used by real people.

", + "

Please do the following immediately:

", + "

", + "
    ", + "
  1. Complete this form:  http://goo.gl/forms/f61dLt67t8.
  2. ", + "
  3. Read this document, which will answer many questions you may have about our nonprofit projects:  http://freecodecamp.com/field-guide/guide-to-our-nonprofit-projects.
  4. ", + "
  5. We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you.
  6. ", + "
  7. Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp \"exit test\". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look: team@freecodecamp.com.
  8. ", + "
", + "

Please email us if you have further questions:  team@freecodecamp.com.

", + "", + "
" + ] + }, + { + "_id": "bd7158d9c451eddfaeb5bdef", + "name": "Free Code Camp's Privacy Policy", + "description": [ + "
", + "

Free Code Camp is committed to respecting the privacy of visitors to our web sites and web applications. The guidelines below explain how we protect the privacy of visitors to FreeCodeCamp.com and its features.

", + "

Personally Identifiable Information

", + "

Free Code Camp protects the identity of visitors to FreeCodeCamp.com by limiting the collection of personally identifiable information.

", + "

Free Code Camp does not knowingly collect or solicit personally identifiable information from or about children under 13, except as permitted by law. If we discover we have received any information from a child under 13 in violation of this policy, we will delete that information immediately. If you believe Free Code Camp has any information from or about anyone under 13, please e-mail us at team@freecodecamp.com.

", + "

All personally identifiable information you provide to us is used by Free Code Camp and its team to process and manage your account, analyze the demographic of our users, or to deliver services through the site.

", + "

If you choose to provide personally identifiable information to us, you may receive occasional e-mails from us that are relevant to Free Code Camp, getting a job, or learning to code in general.

", + "

Free Code Camp may also use other third-party providers to facilitate the delivery of the services described above, and these third-party providers may be supplied with or have access to personally identifiable information for the sole purpose of providing these services, to you on behalf of Free Code Camp.

", + "

Free Code Camp may also disclose personally identifiable information in special legal circumstances. For instance, such information may be used where it is necessary to protect our copyright or intellectual property rights, or if the law requires us to do so.

", + "

Anonymous Information

", + "

Anonymous aggregated data may be provided to other organizations we associate with for statistical purposes. For example, we may report to an organization that a certain percentage of our site's visitors are adults between the ages of 25 and 35.

", + "

Cookies and Beacons—Use by Free Code Camp; Opting Out

", + "

We use cookies and software logs to monitor the use of FreeCodeCamp.com and to gather non-personal information about visitors to the site. Cookies are small files that Free Code Camp transfers to the hard drives of visitors for record-keeping purposes. These monitoring systems allow us to track general information about our visitors, such as the type of browsers (for example, Firefox or Internet Explorer), the operating systems (for instance, Windows or Macintosh), or the Internet providers (for instance, Comcast) they use. This information is used for statistical and market research purposes to tailor content to usage patterns and to provide services requested by our customers. To delete these cookies, please see your browser's privacy settings.

", + "

A beacon is an electronic file object (typically a transparent image) placed in the code of a Web page. We use third party beacons to monitor the traffic patterns of visitors from one Free Code Camp.com page to another and to improve site performance.

", + "

None of the information we gather in this way can be used to identify any individual who visits our site.

", + "

Security

", + "

Any personally identifiable information collected through this site is stored on limited-access servers. We will maintain safeguards to protect these servers and the information they store.

", + "

Surveys

", + "

We may occasionally conduct on-line surveys. All surveys are voluntary and you may decline to participate.

", + "

Copyright

", + "

All of the content on FreeCodeCamp.com is copyrighted by Free Code Camp. If you'd like to redistribute it beyond simply sharing it through social media, please contact us at team@freecodecamp.com.

", + "

Contacting Us

", + "

If you have questions about Free Code Camp, or to correct, update, or remove personally identifiable information, please email us at team@freecodecamp.com.

", + "

Links to Other Web sites

", + "

Free Code Camp's sites each contain links to other Web sites. Free Code Camp is not responsible for the privacy practices or content of these third-party Web sites. We urge all FreeCodeCamp.com visitors to follow safe Internet practices: Do not supply Personally Identifiable Information to these Web sites unless you have verified their security and privacy policies.

", + "

Data Retention

", + "

We retain your information for as long as necessary to permit us to use it for the purposes that we have communicated to you and comply with applicable law or regulations.

", + "

Business Transfers

", + "

As we continue to develop our business, we might sell or buy subsidiaries, or business units. In such transactions, customer information generally is one of the transferred business assets but remains subject to the promises made in any pre-existing Privacy Policy (unless, of course, the customer consents otherwise). Also, in the unlikely event that Free Code Camp, or substantially all of its assets are acquired, customer information will be one of the transferred assets, and will remain subject to our Privacy Policy.

", + "

Your California Privacy Rights

", + "

If you are a California resident, you are entitled to prevent sharing of your personal information with third parties for their own marketing purposes through a cost-free means. If you send a request to the address above, Free Code Camp will provide you with a California Customer Choice Notice that you may use to opt-out of such information sharing. To receive this notice, submit a written request to team@freecodecamp.com, specifying that you seek your "California Customer Choice Notice." Please allow at least thirty (30) days for a response.

", + "

Acceptance of Privacy Policy Terms and Conditions

", + "

By using this site, you signify your agreement to the terms and conditions of this FreeCodeCamp.com Privacy Policy. If you do not agree to these terms, please do not use this site. We reserve the right, at our sole discretion, to change, modify, add, or remove portions of this policy at any time. All amended terms automatically take effect 30 days after they are initially posted on the site. Please check this page periodically for any modifications. Your continued use of FreeCodeCamp.com following the posting of any changes to these terms shall mean that you have accepted those changes.

", + "

If you have any questions or concerns, please send an e-mail to team@freecodecamp.com.

", + "
" + ] + } +] diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json new file mode 100644 index 0000000000..ff432373ef --- /dev/null +++ b/seed_data/nonprofits.json @@ -0,0 +1,170 @@ +[ + { + "id": "bd7157d8c441cbafaeb5bdef", + "requestedDeliverables": [ + "website", + "donor", + "inventory", + "volunteer", + "form" + ], + "whatDoesNonprofitDo": "We help the many less-fortunate Jewish families in our community, by providing them with nutritious food and energy to grow, learn, work, and give them hope for a better and brighter future.", + "websiteLink": "http://chasdeikaduri.org/", + "stakeholderName": "Jonathan Tebeka", + "stakeholderEmail": "jonathan@chasdeikaduri.org", + "name": "Chasdei Kaduri", + "endUser": "Clients, donors, and admin.", + "approvedDeliverables": [ + "website", + "donor", + "inventory", + "volunteer", + "form" + ], + "projectDescription": "Campers will create a system will integrate the food inventory, donor and delivery driver management systems as well as replace the current application system with a custom form solution. System will include a more streamlined operations management, with user printable lists of inventory, drivers, and deliveries.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c7e02f2c173c37015b2f36/604x309/00580a0567a4b3afda29d52b09e7e829/rQQ6zwq31Uya8ie9QHC-MlvfXxqftm9UPPe524JUhmwSEaZjQ7oL7U1tVoHLUj-gVUwM-7uzBGFsAXD_A_cx_JyAZP4Td-GMBJ-AebJNRAQP0m0v253eKMkURp63aG4%3Ds0-d-e1-ft.png", + "imageUrl": "http://chasdeikaduri.org/images/523455_516325865106850_1885515210_n.jpg", + "estimatedHours": 200, + "currentStatus": "started" + }, + { + "id": "bd7158d8c464cbafaeb4bdef", + "requestedDeliverables": [ + "other" + ], + "whatDoesNonprofitDo": "We connect simple technology with last mile communities to reduce poverty.", + "websiteLink": "http://kopernik.info/", + "stakeholderName": "Amber Gregory", + "stakeholderEmail": "amber.gregory@kopernik.info", + "name": "Kopernik", + "endUser": "Women in rural Indonesia.", + "approvedDeliverables": [ + "other" + ], + "projectDescription": "Campers will create a Chrome browser extension to preserve sales data from a form, and upload in batches as the internet connection allows.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d29f1e4c726fd765fa87ef/54d29f6388812dd367a243ab/x/018d9d3be5439870c56cccba5b3aa8bf/kopernik-logo-global.png", + "imageUrl": "http://kopernik.info/sites/default/files/updates/Presenting_the_low_carbon_t.jpg", + "estimatedHours": 100, + "currentStatus": "completed" + }, + { + "id": "bd1326d9c245cbafaeb4bdef", + "requestedDeliverables": [ + "website" + ], + "whatDoesNonprofitDo": "We distribute biodegradable toothbrushes globally to children in need.", + "websiteLink": "http://www.operationbrush.org/", + "stakeholderName": "Dane Jonas", + "stakeholderEmail": "DaneJonas@operationbrush.org", + "name": "Operation Brush", + "endUser": "Donors", + "approvedDeliverables": [ + "website" + ], + "projectDescription": "Campers will create a mobile responsive website for the organization, with donation capabilities.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d9810307b159a4d9027aa2/54d981bfe5eb145560fbb769/x/cf7f318bfe4aee631b0d0eeef272225c/logo.png", + "imageUrl": "http://www.operationbrush.org/images/temp/hands1.png", + "estimatedHours": 100, + "currentStatus": "started" + }, + { + "id": "bd1325d8c464cbafaeb5bdef", + "requestedDeliverables": [ + "community" + ], + "whatDoesNonprofitDo": "We are the largest roller derby league in the world with around 250 adults and 150 junior skater members plus 500+ volunteers.", + "websiteLink": "http://www.rosecityrollers.com/about/our-charities/", + "stakeholderName": "Charity Kuahiwinui", + "stakeholderEmail": "insurance@rosecityrollers.com", + "name": "Rose City Rollers", + "endUser": "Administrators, Coaches, and Volunteers", + "approvedDeliverables": [ + "community" + ], + "projectDescription": "Campers will create a volunteer management system with multi-user access and reporting capabilities.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1daf2d72d8eb868910b60/54c1dd4ecffcb09fc52b68a1/x/a8148f08769b449217e433bab8f39ddd/RCR-color.jpg", + "imageUrl": "http://www.rosecityrollers.com/wp-content/uploads/2015/01/BZ7_5923-X3-675x375.jpg", + "estimatedHours": 200, + "currentStatus": "started" + }, + { + "id": "bd1325d8c464cbafaeb6bdef", + "requestedDeliverables": [ + "website" + ], + "whatDoesNonprofitDo": "Save a Child's Heart provides urgently needed pediatric heart surgery and follow-up care for indigent children from developing countries", + "websiteLink": "http://www.saveachildsheart.com/global/young-leadership-program/", + "stakeholderName": "Shier Ziser", + "stakeholderEmail": "Shier_z@hotmail.com", + "name": "Save a Child's Heart", + "endUser": "Donors", + "approvedDeliverables": [ + "website" + ], + "projectDescription": "Campers will create a single page fundraising website. In exchange for a donation, a user can customize a graphical 'heart' in someone's name or anonymously. The page will display all of the hearts on a 'wall of hearts.'", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/548b36629137780091a973cc/666x666/6c7a366ffb659649f6377d4a431687cd/country-logos-1-300dpi.jpg", + "imageUrl": "http://www.saveachildsheart.com/wp-content/uploads/2013/10/7.2.5_Internation_Photograohy_Exhibition.jpg", + "estimatedHours": 100, + "currentStatus": "completed" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "requestedDeliverables": [ + "website" + ], + "whatDoesNonprofitDo": "Savvy Cyber Kids enables youth to be empowered with technology by providing age appropriate resources and education.", + "websiteLink": "http://savvycyberkids.org/", + "stakeholderName": "Ben Halpert", + "stakeholderEmail": "info@savvycyberkids.org ", + "name": "Savvy Cyber Kids", + "endUser": "Donors", + "approvedDeliverables": [ + "website" + ], + "projectDescription": "Campers will create a website where potential donors can view which schools already have the Savvy Cyber Kids books, and donate books to those schools that do not.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54ee3c7bf205562680177b59/218x190/1dc460de4edc9fdd4b481b24e93cfb23/logo.png", + "imageUrl": "http://www.privatewifi.com/wp-content/uploads/2014/10/Halpert.jpg", + "estimatedHours": 200, + "currentStatus": "started" + }, + { + "id": "bd1325d8c464cbafaeb7bdef", + "requestedDeliverables": [ + "other" + ], + "whatDoesNonprofitDo": "Transcendent Pathways", + "websiteLink": "http://transcendentpathways.org/", + "stakeholderName": "Mark Ackerley", + "stakeholderEmail": "mackerley.music@gmail.com", + "name": "Transcendent Pathways", + "endUser": "Medical Facilities, Musicians", + "approvedDeliverables": [ + "other" + ], + "projectDescription": "Campers will build a website where medical facilities can list music therapy time slots, and musicians can sign up to fill these slots.", + "logoUrl": "http://static1.squarespace.com/static/521b8957e4b024f66a58b214/t/521b8e9de4b093a8696eb9b8/1398718364447/?format=750w", + "imageUrl": "https://trello-attachments.s3.amazonaws.com/54fdb0328917ca64e9e8a79f/54fdc3b710f67caf6da14719/x/49fbe0012179bf254928f3f2a44810b4/Screen_2BShot_2B2013-08-26_2Bat_2B1.32.35_2BPM.png", + "estimatedHours": 200, + "currentStatus": "started" + }, + { + "id": "bd1325d8c464cbafaeb8bdef", + "requestedDeliverables": [ + "other" + ], + "whatDoesNonprofitDo": "Timeraiser is a volunteer matching fair, a silent art auction, and a night out on the town. The big Timeraiser twist is rather than bid money on artwork, participants bid volunteer hours. ", + "websiteLink": "http://www.timeraiser.ca/", + "stakeholderName": "Stephanie McAllister", + "stakeholderEmail": "stephanie@timeraiser.ca", + "name": "Timeraiser", + "endUser": "Eventgoers", + "approvedDeliverables": [ + "other" + ], + "projectDescription": "Campers will build a mobile responsive web form to allow Timeraiser eventgoers to select which nonprofit organizations they're interested in volunteering with. System will have Salesforce integration and reporting capabilities.", + "logoUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/1277176.png?480", + "imageUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/______________4571248_orig.png", + "estimatedHours": 100, + "currentStatus": "started" + } +] diff --git a/seed_data/seed.js b/seed_data/seed.js index 9387b55337..95c35b328f 100644 --- a/seed_data/seed.js +++ b/seed_data/seed.js @@ -1,17 +1,19 @@ require('dotenv').load(); -var Challenge = require('../models/Challenge.js'), - Bonfire = require('../models/Bonfire.js'), +var Bonfire = require('../models/Bonfire.js'), Courseware = require('../models/Courseware.js'), + FieldGuide = require('../models/FieldGuide.js'), + Nonprofit = require('../models/Nonprofit.js'), mongoose = require('mongoose'), secrets = require('../config/secrets'), - challenges = require('./challenges.json'), coursewares = require('./coursewares.json'), + fieldGuides = require('./field-guides.json'), + nonprofits = require('./nonprofits.json'), bonfires = require('./bonfires.json'); mongoose.connect(secrets.db); var counter = 0; -var offerings = 3; +var offerings = 4; var CompletionMonitor = function() { counter++; @@ -22,24 +24,8 @@ var CompletionMonitor = function() { } else { process.exit(0); } -} +}; -Challenge.remove({}, function(err, data) { - if (err) { - console.error(err); - } else { - console.log('Deleted ', data); - } - Challenge.create(challenges, function(err, data) { - if (err) { - console.log(err); - } else { - console.log('Saved ', data); - } - CompletionMonitor(); - }); - console.log('challenges'); -}); Bonfire.remove({}, function(err, data) { if (err) { @@ -74,3 +60,37 @@ Courseware.remove({}, function(err, data) { }); console.log('coursewares'); }); + +FieldGuide.remove({}, function(err, data) { + if (err) { + console.error(err); + } else { + console.log('Deleted ', data); + } + FieldGuide.create(fieldGuides, function(err, data) { + if (err) { + console.log(err); + } else { + console.log('Saved ', data); + } + CompletionMonitor(); + }); + console.log('field guides'); +}); + +Nonprofit.remove({}, function(err, data) { + if (err) { + console.error(err); + } else { + console.log('Deleted ', data); + } + Nonprofit.create(nonprofits, function(err, data) { + if (err) { + console.log(err); + } else { + console.log('Saved ', data); + } + CompletionMonitor(); + }); + console.log('nonprofits'); +}); diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js new file mode 100644 index 0000000000..ffad7be0c4 --- /dev/null +++ b/seed_data/userMigration.js @@ -0,0 +1,108 @@ +require('dotenv').load(); +var mongodb = require('mongodb'), + User = require('../models/User.js'), + newChallenges = require('./challengeMapping.json'), + secrets = require('../config/secrets'); + mongoose = require('mongoose'); + +mongoose.connect(secrets.db); + +function userModelAssurity(cb) { + console.log('userModelAssurity'); + var i = 1; + var stream = User.find({}).skip(0).limit(0).batchSize(20000).stream(); + + stream.on('data', function (user) { + console.log(i++); + this.pause(); + user.needsMigration = true; + user.save(function (err) { + if (err) { + console.log('woops'); + } + this.resume(); + }.bind(this)); + }) + .on('error', function (err) { + console.log(err); + }).on('close', function () { + console.log('done with set'); + stream.destroy(); + cb(); + }); +} + +function migrateIt() { + console.log('migrateIt'); + var dones = 0; + var done = function() { + dones++; + if (dones === 2) { + process.exit(0); + } + if (dones === 1) { + userModelMigration(done); + } + }; + console.log('calling userModelAssurity'); + userModelAssurity(done); +} + +function userModelMigration(cb) { + + var i = 1; + + var stream = User.find({needsMigration: true}).skip(0).limit(0) + .batchSize(20000).stream(); + + stream.on('data', function (user) { + console.log(i++); + if (user.challengesHash) { + this.pause(); + user.needsMigration = false; + var oldChallenges = Object.keys(user.challengesHash).filter(function (key) { + if (user.challengesHash[key]) { + user.progressTimestamps.push(user.challengesHash[key] * 1000); + } + return user.challengesHash[key]; + }); + + newChallenges.forEach(function (challenge) { + if (oldChallenges.indexOf(challenge.oldNumber) !== -1 && challenge.newId) { + user.completedCoursewares.push({ + _id: challenge.newId, + completedDate: user.challengesHash[challenge.oldNumber] * 1000 + }); + } + }); + + user.completedCoursewares.forEach(function (course) { + var indexOfCourse = user.uncompletedCoursewares.indexOf(course._id) !== -1; + if (indexOfCourse !== -1) { + user.uncompletedCoursewares.splice(indexOfCourse, 1); + } + }); + + user.completedBonfires.forEach(function (bonfire) { + bonfire.completedDate = bonfire.completedDate * 1000; + user.progressTimestamps.push(bonfire.completedDate); + }); + } + + var self = this; + user.save(function (err) { + if (err) { + console.log('woops'); + } + self.resume(); + }); + }).on('error', function (err) { + console.log(err); + }).on('close', function () { + console.log('done with set'); + stream.destroy(); + cb(); + }); +} + +migrateIt(); diff --git a/views/account/account.jade b/views/account/account.jade index 80424c1b56..3e8a5b834a 100644 --- a/views/account/account.jade +++ b/views/account/account.jade @@ -176,15 +176,11 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.portfolio.website1Image', placeholder='http://www.example.com/image.jpg', ng-pattern="/[\.](jpg|png|jpeg|gif)\s?$/") + input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.portfolio.website1Image', placeholder='http://www.example.com/image.jpg') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.url && !profileForm.website1Image.$pristine") alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .col-sm-4.col-sm-offset-5.flat-top h3 Second Portfolio Project @@ -209,15 +205,11 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.portfolio.website2Image', placeholder='http://www.example.com/image.jpg', ng-pattern="/[\.](jpg|png|jpeg|gif)\s?$/") + input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.portfolio.website2Image', placeholder='http://www.example.com/image.jpg') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.url && !profileForm.website2Image.$pristine") alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .col-sm-4.col-sm-offset-5.flat-top h3 Third Portfolio Project @@ -248,10 +240,6 @@ block content alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .form-group .col-sm-offset-5.col-sm-4 diff --git a/views/account/show.jade b/views/account/show.jade index 2438ed680d..8dd1314227 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -1,5 +1,7 @@ extends ../layout block content + script. + var challengeName = 'Profile View'; .col-xs-12.col-sm-12.col-md-12 .panel.panel-info .panel-heading.text-center @@ -8,7 +10,7 @@ block content if (user && user.profile.username === username) .col-xs-12 .text-center - a.btn.btn-big.btn-primary(href="/account") Update my public portfolio or manage my account + a.btn.btn-big.btn-primary(href="/account") Update my portfolio page or manage my account br .row .col-xs-12 @@ -39,7 +41,7 @@ block content .col-xs-12.col-sm-12.col-md-3.text-center .background-svg.img-center .points-on-top - = "[ " + points + " ]" + = "[ " + (user ? user.progressTimestamps.length : 0) + " ]" .row @@ -47,7 +49,7 @@ block content if (website1Title && website1Link && website1Image) .row .col-xs-12.col-md-5 - img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title") + img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website1Title a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank') @@ -64,7 +66,7 @@ block content if (website2Title && website2Link && website2Image) .row .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title") + img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website2Title a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank') @@ -81,7 +83,7 @@ block content if (website3Title && website3Link && website3Image) .row .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title") + img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website3Title a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') @@ -94,16 +96,65 @@ block content i.fa.icon-beaker | Try it out - - if (ch[0] > 0) - .col-sm-12 - table.table.table-striped - thead - tr - th Challenge - th Date Finished - for challenge in challenges - if ch[challenge.challengeNumber] > 0 - tr - td= challenges[challenge.challengeNumber].name - td= moment(ch[challenge.challengeNumber], 'X').format("MMM DD, YYYY") - br + .hidden-xs.col-sm-12 + script(src="//d3js.org/d3.v3.min.js") + script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") + #cal-heatmap.img-center + script. + $(document).ready(function() { + setTimeout(function() { + var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + cal.init({ + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "day", + data: calendar, + cellSize: 15, + align: 'center', + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 4, + start: new Date().setDate(new Date().getDate() - 90), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3] + }); + }, 300); + }); + .row + .hidden-xs.col-sm-12.text-center + .row.text-primary + h4.col-sm-6.text-right Longest Streak: #{longestStreak} + h4.col-sm-6.text-left Current Streak: #{currentStreak} + + - if (challenges.length > 0) + .col-sm-12 + table.table.table-striped + thead + tr + th.col-xs-4 Challenge + th.col-xs-2 Completed + th.col-xs-6 Link + for challenge in challenges + tr + td.col-xs-4= challenge.name + td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") + td.col-xs-6 + a(href=challenge.solution) View my solution + + br + - if (bonfires.length > 0) + .col-sm-12 + table.table.table-striped + thead + tr + th.col-xs-4 Bonfire + th.col-xs-2 Completed + th.col-xs-6 Solution + for bonfire in bonfires + tr + td.col-xs-4= bonfire.name + td.col-xs-2= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY") + td.col-xs-6= bonfire.solution + br diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 071acd1d0b..5e7f620224 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -21,7 +21,7 @@ block content .row .col-xs-12.col-sm-12.col-md-4.bonfire-top #testCreatePanel - h1.text-center= name + h1#bonfire-name.text-center= name h2.text-center .bonfire-flames Difficulty:  if (difficulty == "0") @@ -80,7 +80,7 @@ block content #less-info.btn.btn-primary.btn-block.btn-primary-ghost span.ion-arrow-up-b | Less information - + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) br form.code @@ -94,7 +94,7 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedBonfireHash = !{JSON.stringify(bonfireHash)}; var challengeName = !{JSON.stringify(name)}; - var started = Math.floor(Date.now() / 1000); + var started = Math.floor(Date.now()); var _ = R; var dashed = !{JSON.stringify(dashedName)}; @@ -116,7 +116,7 @@ block content .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) + - if (user) 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 @@ -131,7 +131,7 @@ block content a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next bonfire (ctrl + enter) - - if (points && points > 2) + - if (user.progressTimestamps.length > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") i.fa.fa-twitter   = phrase @@ -139,19 +139,7 @@ block content a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. - $.ajax({ - url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fbonfires%2F' + dashed + '&format=txt' - }) - .success( - function (data) { - console.log(data); - url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; - $('.btn-twitter').attr('href', url); - } - ); var MDNlinks = !{JSON.stringify(MDNlinks)}; if (!MDNlinks.length) { $('#MDN-links').addClass('collapse'); } - - diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade new file mode 100644 index 0000000000..8243874525 --- /dev/null +++ b/views/challengeMap/show.jade @@ -0,0 +1,71 @@ +extends ../layout +block content + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center + h1 Challenge Map + .panel-body + .row + .col-xs-12.col-sm-12.col-md-8.col-md-offset-2 + h3 You must complete all of these challenges before you can start working on nonprofit projects. + h3 You should complete these in order from top to bottom. + h2 + span.fa.fa-flag + |   Waypoints (web development lessons) + + .col-xs-12 + h3.negative-15 + ol + for waypoint in waypoints + if completedCoursewareList.indexOf(waypoint._id) > -1 + li.strikethrough + a(href="/challenges/#{waypoint.name}")= waypoint.name + else + li + a(href="/challenges/#{waypoint.name}")= waypoint.name + h2 + span.ion-bonfire + |   Bonfires (algorithm practice) + .col-xs-12 + h3.negative-15 + ol + for bonfire in bonfires + if completedBonfireList.indexOf(bonfire._id) > -1 + li.strikethrough + a(href="/bonfires/#{bonfire.name}")= bonfire.name + else + li + a(href="/bonfires/#{bonfire.name}")= bonfire.name + h2 + span.fa.fa-angle-double-right + |   Ziplines (front end development practice) + .col-xs-12 + h3.negative-15 + ol + for zipline in ziplines + if completedCoursewareList.indexOf(zipline._id) > -1 + li.strikethrough + a(href="/challenges/#{zipline.name}")= zipline.name + else + li + a(href="/challenges/#{zipline.name}")= zipline.name + h2 + span.fa.fa-level-down + |   Basejumps (full stack development practice) + .col-xs-12 + h3.negative-15 + ol + for basejump in basejumps + if completedCoursewareList.indexOf(basejump._id) > -1 + li.strikethrough + a(href="/challenges/#{basejump.name}")= basejump.name + else + li + a(href="/challenges/#{basejump.name}")= basejump.name + h2 + span.ion-ios-heart + |   Nonprofit Projects + h3.negative-15 + ul + li + a(href="/nonprofits/directory") Browse our nonprofit projects diff --git a/views/challenges/show.jade b/views/challenges/show.jade index a03b095640..c2843bb107 100644 --- a/views/challenges/show.jade +++ b/views/challenges/show.jade @@ -36,13 +36,7 @@ block content = phrase - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - #all-challenges-dialog.modal(tabindex='-1') - .modal-dialog.animated.fadeInUp.fast-animation - .modal-content - .modal-header.challenge-list-header Challenges - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - include ../partials/challenges + script. $.ajax({ url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fchallenges%2F' + !{JSON.stringify(number)} + '&format=txt' @@ -53,4 +47,4 @@ block content url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; $('.btn-twitter').attr('href', url); } - ); \ No newline at end of file + ); diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 4db63b544b..840056724d 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -38,22 +38,19 @@ block content span.ion-arrow-up-b | Less information br - - - if (cc) - a.btn.btn-primary.btn-lg.btn-block#next-courseware-button + - if (user) + a.btn.btn-primary.btn-big.btn-block#next-courseware-button | Go to my next challenge br | (ctrl + enter) script. var userLoggedIn = true; - - else a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - script. - var userLoggedIn = false; - br + script. + var userLoggedIn = false; + .button-spacer ul#testSuite.list-group - br script(type="text/javascript"). $('#next-courseware-button').attr('disabled', 'disabled'); @@ -61,8 +58,10 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; var prodOrDev = !{JSON.stringify(environment)}; - var started = Math.floor(Date.now() / 1000); + var challengeType = !{JSON.stringify(challengeType)}; + var started = Math.floor(Date.now()); .col-xs-12.col-sm-12.col-md-5.col-lg-6 #mainEditorPanel form.code @@ -72,8 +71,6 @@ block content .hidden-xs.hidden-sm img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") iframe.iphone#preview - - #complete-courseware-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index 68a82cbfb1..3e33ee4a4b 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -47,7 +47,10 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; - var started = Math.floor(Date.now() / 1000); + var challengeType = !{JSON.stringify(challengeType)}; + var passedCoursewareName = challengeName; + var started = Math.floor(Date.now()); + .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code @@ -63,12 +66,11 @@ block content .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) - - a#next-courseware-button.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf) Go to my next challenge (ctrl + enter) - - 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%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") - i.fa.fa-twitter   - = phrase + - if (user) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + - 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%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&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 diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index c042cc749e..98c35261c7 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -7,43 +7,53 @@ block content h4 ol for step in details - li!= step + .row + li + .col-xs-2 + input(type='checkbox' class='challenge-list-checkbox') + .col-xs-10.step-text!= step .col-xs-12.col-sm-12.col-md-8 .embed-responsive.embed-responsive-16by9 iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') br - - if (cc) - a.btn.btn-primary.btn-lg.btn-block#next-courseware-button Go to my next challenge (ctrl + enter) + - if (user) + a.btn.btn-primary.btn-big.btn-block#completed-courseware I've completed this challenge (ctrl + enter) script. var userLoggedIn = true; - else - a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + a.btn.btn-big.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. var userLoggedIn = false; br - script(type="text/javascript"). + .button-spacer + script(type="text/javascript"). var tests = !{JSON.stringify(tests)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; - var started = Math.floor(Date.now() / 1000); + var passedCoursewareName = challengeName; + var started = Math.floor(Date.now()); + var challengeType = !{JSON.stringify(challengeType)}; #complete-courseware-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') × + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body(ng-controller="pairedWithController") .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - - if (points && points > 2) + - if (user) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) Go to my next challenge (ctrl + enter) + - if (user.progressTimestamps.length > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&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 h1 #{name} - script. - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file + script. + $('body').on('keypress', function(e) { + if (e.ctrlKey && e.keyCode == 13) { + $('#complete-courseware-dialog').modal('show'); + } + }); diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade new file mode 100644 index 0000000000..ffe853d4ba --- /dev/null +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -0,0 +1,76 @@ +extends ../layout-wide +block content + .row + .col-xs-12.col-sm-12.col-md-4.bonfire-top + h1.text-center= name + .well + h4 + ol + for step in details + .row + li + .col-xs-2 + input(type='checkbox' class='challenge-list-checkbox') + .col-xs-10.step-text!= step + .col-xs-12.col-sm-12.col-md-8 + .embed-responsive.embed-responsive-16by9 + iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') + br + - if (user) + a.btn.btn-primary.btn-lg.btn-block#completed-zipline-or-basejump I've completed this challenge (ctrl + enter) + script. + var userLoggedIn = true; + - else + a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + script. + var userLoggedIn = false; + br + script(type="text/javascript"). + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; + var started = Math.floor(Date.now()); + var challengeType = !{JSON.stringify(challengeType)}; + #complete-zipline-or-basejump-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.completion-icon.ion-checkmark-circled.text-primary + - if (user) + 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") + if (challengeType === 3) + input.form-control#public-url(name="codepenUrl", placeholder="http://codepen.io/your-pen-here", autofocus) + else + input.form-control#public-url(name="depoloymentUrl", placeholder="http://yourapp.com", autofocus) + input.form-control#github-url(name="githubUrl", placeholder="http://github.com/camper/project") + + input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser") + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-cloak, ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") + alert(type='danger') + span.ion-close-circled + | Username not found + + if (user) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + + - if (user.progressTimestamps.length > 2) + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(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 + script. + $('body').on('keypress', function(e) { + if (e.ctrlKey && e.keyCode == 13) { + $('#complete-zipline-or-basejump-dialog').modal('show'); + } + }); diff --git a/views/field-guide/show.jade b/views/field-guide/show.jade new file mode 100644 index 0000000000..574abeffe8 --- /dev/null +++ b/views/field-guide/show.jade @@ -0,0 +1,42 @@ +extends ../layout +block content + script. + var challengeName = 'Field Guide View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center + h1= title + .panel-body + .row + .col-xs-12 + div!= description + .spacer + .spacer + .spacer + .col-xs-12.col-sm-6.col-sm-offset-3 + .text-center + .next-field-guide-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) + .ten-pixel-break + #showAllButton.btn.btn-info.btn-big.btn-block Show me all articles + .spacer + .row + .col-xs-12.text-center + if !user + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) + .spacer + #show-all-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Field Guide Articles + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/field-guide + #fieldGuideId.hidden= fieldGuideId + script. + $(document).ready(function() { + $('body').keydown(function(e) { + if (e.ctrlKey && e.keyCode == 13) { + $('.next-field-guide-button').click(); + } + }); + }); diff --git a/views/home.jade b/views/home.jade index c7d876b136..335c1daffa 100644 --- a/views/home.jade +++ b/views/home.jade @@ -89,4 +89,4 @@ block content .col-xs-12.col-sm-8.col-sm-offset-2 a.btn.btn-cta.signup-btn.btn-block(href="/login") Learn to code today (it's free) script. - challengeName = 'Home' \ No newline at end of file + challengeName = 'Home' diff --git a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade new file mode 100644 index 0000000000..5f82c3c82b --- /dev/null +++ b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 We build solutions for nonprofits who are already serving a need. Are there people who already benefit from your services? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/ok-with-javascript') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/are-you-with-a-registered-nonprofit.jade b/views/nonprofits/are-you-with-a-registered-nonprofit.jade new file mode 100644 index 0000000000..4dc9a6e639 --- /dev/null +++ b/views/nonprofits/are-you-with-a-registered-nonprofit.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Do you represent a nonprofit organization that is registered with your government? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/are-there-people-that-are-already-benefiting-from-your-services') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/directory.jade b/views/nonprofits/directory.jade new file mode 100644 index 0000000000..309cad9287 --- /dev/null +++ b/views/nonprofits/directory.jade @@ -0,0 +1,19 @@ +extends ../layout +block content + script. + var challengeName = 'Nonprofits View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center Nonprofits We Help + .panel-body + for nonprofit in nonprofits + .spacer + .row + .col-xs-12.col-sm-3 + img.img-responsive(src=nonprofit.logoUrl) + .col-xs-12.col-sm-9 + h2.negative-15= nonprofit.name + h3.negative-15= nonprofit.whatDoesNonprofitDo + a.text-center.btn.btn-primary.btn-lg(href='/nonprofits/' + nonprofit.name.toLowerCase().replace(/\s/g, '-')) Read more + .spacer + .spacer diff --git a/views/nonprofits/home.jade b/views/nonprofits/home.jade new file mode 100644 index 0000000000..b76a306f25 --- /dev/null +++ b/views/nonprofits/home.jade @@ -0,0 +1,83 @@ +extends ../layout +block content + .jumbotron + .text-center + h2.nonprofit-landing.hug-top We'll code for your nonprofit, pro bono + .big-break + h2 Some of our success stories + .row + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-jen.jpg", alt="@jenthebest's testimonial image") + .testimonial-copy Getting back on track with Free Code Camp and committing to a new career in 2015! + h3 - @jenbestyoga + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-tate.jpg", alt="@TateThurston's testimonial image") + .testimonial-copy Just built my company's website with skills I've learned from Free Code Camp! + h3 - @TateThurston + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-cynthia.jpg", alt="@cynthialanel's testimonial image") + .testimonial-copy I'm currently working through Free Code Camp to improve my JavaScript. The community is very welcoming! + h3 - @cynthialanel + .big-break + a.btn.btn-cta.signup-btn(href="/nonprofits/are-you-with-a-registered-nonprofit") Get pro bono help for my nonprofit + .big-break + h2 Our process + .row + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Your idea + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_portfolio.svg.gz', title='Get great references and connections to help you get a job') + p.landing-p You tell us how we can help you. + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Our team + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_nonprofits.svg.gz', title='Build a portfolio of apps for nonprofits') + p.landing-p We'll hand pick developers and a project manager. + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Your solution + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_connect.svg.gz', title='Help nonprofits') + p.landing-p Together we'll set milestones and complete your project. + .big-break + h2 Solutions we can help you build: + .text-center.negative-35 + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-globe + h2.black-text Websites + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-card + h2.black-text Donation Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-calendar + h2.black-text Volunteer Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-box + h2.black-text Inventory Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-university + h2.black-text E-learning Platforms + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-list + h2.black-text Web Forms + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-people + h2.black-text Community Tools + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-settings + h2.black-text ...and other tools + br + br + .big-break + h2 Why you should join our community right now: + h3.col-xs-offset-0.col-sm-offset-1 + ul.text-left + li.ion-code   We're thousands of professionals, all learning to code together + li.ion-code   We're building projects for dozens of nonprofits + li.ion-code   Our community is 100% free and open source + li.ion-code   You'll learn Full Stack JavaScript and become a Software Engineer + li.ion-code   You'll work through our focused, interactive courses and tutorials + li.ion-code   You'll learn to code at your own pace, in your browser or on your phone + li.ion-code   You'll become qualified for thousands of jobs currently going unfilled + li.ion-code   You can get help in real time from our community chat rooms and forum + li.ion-code   We all share one common goal: to boost our careers with code + .big-break + a.btn.btn-cta.signup-btn(href="/nonprofits/are-you-with-a-registered-nonprofit") Get pro bono help for my nonprofit + script. + challengeName = 'Home' \ No newline at end of file diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade new file mode 100644 index 0000000000..8c9b9678a5 --- /dev/null +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -0,0 +1,33 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 How can Free Code Camp help you? + .text-left.form-inline + h3.col-xs-12.col-sm-offset-5.checkbox-table + .col-xs-12 + input.checkbox(type='checkbox', id='website') + label.ion-android-globe   Websites + .col-xs-12 + input.checkbox(type='checkbox', id='donation') + label.ion-card   Donation Systems + .col-xs-12 + input.checkbox(type='checkbox', id='volunteer') + label.ion-android-calendar   Volunteer Systems + .col-xs-12 + input.checkbox(type='checkbox', id='inventory') + label.ion-ios-box   Inventory Systems + .col-xs-12 + input.checkbox(type='checkbox', id='eLearning') + label.ion-university   E-learning Platforms + .col-xs-12 + input.checkbox(type='checkbox', id='form') + label.ion-ios-list   Web Forms + .col-xs-12 + input.checkbox(type='checkbox', id='community') + label.ion-ios-people   Community Tools + .col-xs-12 + input.checkbox(type='checkbox', id='other') + label.ion-settings   Other tools + button#next-step.btn.btn-primary.btn-big.btn-block(type='submit') I've selected all that apply and am ready to move on diff --git a/views/nonprofits/in-exchange-we-ask.jade b/views/nonprofits/in-exchange-we-ask.jade new file mode 100644 index 0000000000..e6b02c0877 --- /dev/null +++ b/views/nonprofits/in-exchange-we-ask.jade @@ -0,0 +1,17 @@ +extends ../layout +block content + .jumbotron + h1.hug-top.text-center Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Great! In exchange for our help, we ask only that you: + h3 + ol + li Appoint one principal stakeholder to serve on behalf of your organization. + li Communicate with our campers on a regular basis, to answer questions and provide them with direction. + li Commit to using the solution that our campers build for your nonprofit. + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/link-us-to-your-website.jade b/views/nonprofits/link-us-to-your-website.jade new file mode 100644 index 0000000000..bdb59455ad --- /dev/null +++ b/views/nonprofits/link-us-to-your-website.jade @@ -0,0 +1,14 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Link us to the website, blog, or social media page that best represents your organization. + .spacer + form(role='form', method='GET', action="/nonprofits/tell-us-your-email/?" + existingParams) + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', name='link', autocomplete='off', maxlength='500', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/ok-with-javascript.jade b/views/nonprofits/ok-with-javascript.jade new file mode 100644 index 0000000000..1835218868 --- /dev/null +++ b/views/nonprofits/ok-with-javascript.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Our campers are learning to code using modern full stack JavaScript technologies like Node.js. We do not build or maintain Wordpress, Drupal, or other non-JavaScript based frameworks. + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/in-exchange-we-ask') Sounds good! + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/other-solutions.jade b/views/nonprofits/other-solutions.jade new file mode 100644 index 0000000000..752728e772 --- /dev/null +++ b/views/nonprofits/other-solutions.jade @@ -0,0 +1,65 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Here are some other solutions we recommend + .spacer + h2 Please note that Free Code Camp is not partnered with, nor do we receive a referral fee from, any of the following providers. We simply want to help guide you towards a solution for your organization. + h3 Skills-based Volunteer Organizations + ul + li + a(href='http://www.volunteermatch.com') Volunteer Match + li + a(href='http://www.catchafire.com') Catchafire + h3 DIY Websites + ul + li + a(href='http://www.wix.com') Wix + li + a(href='http://www.squarespace.com/') Square Space + + h3 Donor and Volunteer Management Systems + ul + li + a(href='https://www.thedatabank.com/') The Data Bank + li + a(href='http://www.donorsnap.com/') DonorSnap + li + a(href='http://www.donorperfect.com/') Donor Perfect + li + a(href='https://www.blackbaud.com/fundraising-crm/etapestry-donor-management') eTapestry + li + a(href='http://www.z2systems.com/') NeonCRM + li + a(href='http://www.regpacks.com/volunteer-management/') Regpack + li + a(href='http://sumac.com/') Sumac + li + a(href='http://www.volgistics.com/') Volgistics + h3 Inventory Management Systems + ul + li + a(href='https://www.ezofficeinventory.com/industries/non-profits') EZ Office Inventory + li + a(href='https://www.ordoro.com') Ordoro + li + a(href='http://www.unleashedsoftware.com') Unleashed + h3 E-Learning platforms + ul + li + a(href='http://www.dokeos.com/') Dokeos + li + a(href='http://www.efrontlearning.net/') eFront + li + a(href='https://moodle.org/') Moodle + li + a(href='https://sakaiproject.org/') Sakai + h3 Community Management + ul + li + a(href='https://civicrm.org/') CiviCRM + li + a(href='http://tcmgr.com/') Total Community Manager + h3 Electronic Forms + ul + li + a(href='http://www.google.com/forms') Google Forms diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade new file mode 100644 index 0000000000..de4d735815 --- /dev/null +++ b/views/nonprofits/show.jade @@ -0,0 +1,82 @@ +extends ../layout +block content + script. + var challengeName = 'Nonprofits View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center= title + .panel-body + .row + .col-xs-12.col-sm-10.col-sm-offset-1 + .row + .col-xs-12 + img.img-center.img-responsive(src=imageUrl) + .spacer + .row + .col-xs-12.col-sm-4 + img.img-responsive(src=logoUrl) + .col-xs-12.col-sm-8 + .col-xs-12 + h4= whatDoesNonprofitDo + h4 + a(href=websiteLink)= websiteLink + .spacer + h3 Project Description: + .col-xs-12 + h4.negative-15 #{projectDescription} (About #{estimatedHours} hours per camper) + .spacer + h3 This project involves building: + h4.negative-15.col-xs-12 + if (approvedWebsite) + .ion-android-globe   Website + if (approvedDonor) + .ion-card   Donor Management System + if (approvedInventory) + .ion-ios-box   Inventory Management System + if (approvedVolunteer) + .ion-android-calendar   Volunteer Management System + if (approvedForm) + .ion-ios-list   Webform + if (approvedCommunity) + .ion-ios-people   Community Management System + if (approvedELearning) + .ion-university   E-learning Platform + if (approvedOther) + .ion-settings   Other tools + h3 Project Status: #{currentStatus} + if (interestedCampers && interestedCampers.length > 0) + h3 Interested campers: + .col-xs-12.text-left + for interestedCamper in interestedCampers + a(href='/' + interestedCamper.username class="interested-camper-image") + img.profile-picture.float-right(src=interestedCamper.picture) + if (assignedCampers && assignedCampers.length > 0) + h3 Assigned campers: + .col-xs-12.text-left + for assignedCamper in assignedCampers + a(href='/' + assignedCamper.username class="interested-camper-image") + img.profile-picture.float-right(src=assignedCamper.picture) + .spacer + if (!buttonActive) + .col-xs-12.col-sm-8.col-sm-offset-2 + .text-center + a.btn.btn-primary.btn-big.btn-block.disabled(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project * + p * Complete all our Bonfires, Ziplines, and Basejumps to unlock this. + #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects + if (buttonActive) + .col-xs-12.col-sm-8.col-sm-offset-2 + .text-center + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project + #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects + .row + .col-xs-12.text-center + if !user + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) + .spacer + #show-all-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Nonprofit Projects + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/nonprofits diff --git a/views/nonprofits/tell-us-your-email.jade b/views/nonprofits/tell-us-your-email.jade new file mode 100644 index 0000000000..3bd8918f6b --- /dev/null +++ b/views/nonprofits/tell-us-your-email.jade @@ -0,0 +1,14 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Please tell us your email + .spacer + form(role='form', method='GET', novalidate='novalidate', name='nonprofitForm', action="/nonprofits/tell-us-your-name/") + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', name='email', autocomplete='off', maxlength='500', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-name.jade b/views/nonprofits/tell-us-your-name.jade new file mode 100644 index 0000000000..915c0b3258 --- /dev/null +++ b/views/nonprofits/tell-us-your-name.jade @@ -0,0 +1,13 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Please tell us your name + .spacer + form(role='form', method='POST', novalidate='novalidate', name='nonprofitForm', action="/nonprofits/finish-application/") + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', name='name', autocomplete='off', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/what-does-your-nonprofit-do.jade b/views/nonprofits/what-does-your-nonprofit-do.jade new file mode 100644 index 0000000000..166cb71418 --- /dev/null +++ b/views/nonprofits/what-does-your-nonprofit-do.jade @@ -0,0 +1,28 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 In 140 characters or less, what does your nonprofit do? For whom? + .spacer + form(role='form', method='GET', action="/nonprofits/link-us-to-your-website/") + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive#what-does-the-nonprofit-do(type='text', maxlength='140', autofocus='', autocomplete='off', name='mission') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/link-us-to-your-website') Submit + .text-left + span#what-does-the-nonprofit-do-feedback + + script. + var text_max = 140; + $('#what-does-the-nonprofit-do-feedback').html(text_max + ' characters remaining'); + $('#what-does-the-nonprofit-do').keyup(function (e) { + if (e.which === 13 || e === 13) { + $('#submit-comment-to-comment').click(); + } + var text_length = $('#what-does-the-nonprofit-do').val().length; + var text_remaining = text_max - text_length; + $('#what-does-the-nonprofit-do-feedback').html(text_remaining + ' characters remaining'); + }); \ No newline at end of file diff --git a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade new file mode 100644 index 0000000000..1882ada3bc --- /dev/null +++ b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade @@ -0,0 +1,6 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Thank you for reaching out to us. We’ll send you an email no later than #{getBackDay}. diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade deleted file mode 100644 index e371443a16..0000000000 --- a/views/partials/bonfires.jade +++ /dev/null @@ -1,7 +0,0 @@ -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/challenges.jade b/views/partials/challenges.jade deleted file mode 100644 index 0c959ff122..0000000000 --- a/views/partials/challenges.jade +++ /dev/null @@ -1,8 +0,0 @@ -h3 - ol(start='0') - for challenge in challenges - li - a(href="/challenges/#{challenge.challengeNumber}", class="#{ (cc && cc[challenge.challengeNumber] > 0) ? 'strikethrough' : '' }") #{challenge.name} - |   (#{challenge.time} mins) - -a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((points && points < 54) || (!points)) ? 'disabled' : '' }") I'm done with all the challenges! \ No newline at end of file diff --git a/views/partials/css-cdns.jade b/views/partials/css-cdns.jade index 6f76033d43..3aade4da1e 100644 --- a/views/partials/css-cdns.jade +++ b/views/partials/css-cdns.jade @@ -32,4 +32,16 @@ script. var raf = requestAnimationFrame || mozRequestAnimationFrame || webkitRequestAnimationFrame || msRequestAnimationFrame; if (raf) raf(cb); - else window.addEventListener('load', cb); \ No newline at end of file + else window.addEventListener('load', cb); + + var cb = function () { + var l = document.createElement('link'); + l.rel = 'stylesheet'; + l.href = '//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css'; + var h = document.getElementsByTagName('head')[0]; + h.parentNode.insertBefore(l, h); + }; + var raf = requestAnimationFrame || mozRequestAnimationFrame || + webkitRequestAnimationFrame || msRequestAnimationFrame; + if (raf) raf(cb); + else window.addEventListener('load', cb); diff --git a/views/partials/faq.jade b/views/partials/faq.jade deleted file mode 100644 index 19144be470..0000000000 --- a/views/partials/faq.jade +++ /dev/null @@ -1,75 +0,0 @@ -.panel.panel-info - .panel-heading.landing-panel-heading.text-center Frequently Asked Questions - .panel-body - .landing-panel-body - .row - .text-left.col-xs-12.col-md-10.col-md-offset-1 - h2 What will I learn, and in what sequence? - ul - p.landing-p First, you'll learn basic web design tools like: - ul - li.landing-p • HTML - the structure of web pages - li.landing-p • CSS - the visual style of web pages - li.landing-p • Bootstrap - a "responsive design" tool that helps your websites look great on tablets and phones - li.landing-p • jQuery - an easy tool for controlling content in the browser - li.landing-p • Chrome DevTools - a tool for understanding and debugging websites, right in your browser - p.landing-p Then you'll learn computer science and the art of programming: - ul - li.landing-p • JavaScript - the one programming language that all web browsers use - li.landing-p • Algorithms - step-by-step recipes for getting things done - li.landing-p • Automated Testing - write tests to test the limits of your code - p.landing-p You'll spend the last half of Free Code Camp using Agile Methodologies and Full Stack JavaScript to build projects for nonprofits: - ul - li.landing-p • Agile - a set of software development principles that focus the design and production of a project on the needs of its users - li.landing-p • Git - a version control system for saving and sharing your projects - li.landing-p • MongoDB - a popular non-relational database - li.landing-p • Angular.js - a tool for making exciting web interfaces - li.landing-p • Express.js - a powerful web development framework - li.landing-p • Node.js - a JavaScript-based web server - h2 Will I be ready to get a software engineer job after this? - ul - p.landing-p At the end of Free Code Camp, you will have pair programmed around 1,000 hours with dozens of other students, built a portfolio of projects that people are actively using, and a roster of glowing references from nonprofits you've helped. This is more coding than most coding bootcamps provide, and on average, 75% of bootcamp graduates get software engineering jobs within 6 months, and earn an average annual salary of $76,000. - img.img-center.img-responsive(src="https://s3.amazonaws.com/freecodecamp/table-of-earnings.png" alt="A chart showing the average earnings of coding bootcamp graduates") - h2 How long does Free Code Camp take? - ul - p.landing-p It takes about 1,000 hours of coding to develop the skills you'll need to get an entry level software engineering job. Most coding bootcamps try to jam all this into 12 weeks of intensive study. Free Code Camp is fully online, and there will always be other people at your skill level that you can pair program with, so you can learn at your own pace. Here are some example coding schedules: - table.table - thead - th Time budgeted - th.hidden-xs Hours per week - th Weeks to complete - tr.info - td Weekends - td.hidden-xs 10 hours/week - td 100 weeks (2 years) - tr.success - td Nights and Weekends - td.hidden-xs 20 hours/week - td 50 weeks (1 year) - tr.warning - td Full time - td.hidden-xs 40 hours/week - td 25 weeks (6 months) - tr.danger - td Traditional Bootcamp Pacing - td.hidden-xs 80 hours/week - td 12 weeks (3 months) - h2 Why does Free Code Camp use JavaScript instead of Ruby or Python? - ul - p.landing-p Like JavaScript, Ruby and Python are high-level scripting languages that can be used for full stack web development. But even if you learned these languages, you'd still need to learn JavaScript. That's because JavaScript is the only language that runs in web browsers. JavaScript has been around for 20 years, and it is still growing in popularity. Because of this, JavaScript has more tools and online learning resources than any other language. - img.img-center.img-responsive(src="https://s3.amazonaws.com/freecodecamp/github-repo-growth.png", style="max-height: 355px;" alt="A chart showing the volume of new GitHub repositories by year, with JavaScript growing and most languages declining.") - br - h2 How will I learn all this? - ul - p.landing-p By pair programming with other Free Code Camp students on our coding challenges. Eventually, you'll work with people at nonprofits to build real-life software solutions. - h2 What is 'pair programming', and what's so special about it? - ul - p.landing-p Pair programming is where two people code together on one computer. You discuss different approaches to solving problems, and keep each other motivated. The result is better code than either of you could have written by yourselves. Because of its benefits, many engineers pair program full time. And it's the best way to learn coding. Thanks to tools that allow two people to share mouse and keyboard inputs, you can pair program with a friend without needing to be in the same room. - h2 Is this really free? Do you claim part of my first year's salary like some bootcamps do? - ul - p.landing-p Our name is Free Code Camp. We are a free code camp. If you had to pay us (or sign over future earnings), we'd have to change our name. And we are not going to do that. - h2 Does Free Code Camp have an application process? - ul - p.landing-p Unlike coding bootcamps, anyone can study at Free Code Camp. We're not going to tell you that you can't become a software engineer. We believe the only person who should be able to tell you that is you. If you persevere, and keep working through our challenges and nonprofit projects, you will become an employable software engineer. - br - br diff --git a/views/partials/field-guide.jade b/views/partials/field-guide.jade new file mode 100644 index 0000000000..7af5c02fc9 --- /dev/null +++ b/views/partials/field-guide.jade @@ -0,0 +1,29 @@ +h3 + ol#fieldGuideList + script(src='/js/lib/ramda/ramda.min.js') + script. + var getLinkedName = function getLinkedName(name) { + // ensure name is a string + name = name + ''; + return name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + } + $.ajax({ + url: '/field-guide/getFieldGuideList', + type: 'GET' + }) + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.fieldGuideList.length; i++) { + var li = document.createElement("li"); + // strike through field guides previously read + var linkedName = getLinkedName(data.fieldGuideList[i].name); + if (data.completedFieldGuides.indexOf(data.fieldGuideIds[i]) > -1) { + $(li).html("" + data.fieldGuideList[i].name + ""); + } else { + $(li).html("" + data.fieldGuideList[i].name + ""); + } + docfrag.appendChild(li); + }; + $('#fieldGuideList').append(docfrag); + }); diff --git a/views/partials/flash.jade b/views/partials/flash.jade index cab3d7de09..e69a6d69bc 100644 --- a/views/partials/flash.jade +++ b/views/partials/flash.jade @@ -1,18 +1,20 @@ -if messages.errors - .alert.alert-danger.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for error in messages.errors - div= error.msg -if messages.info - .alert.alert-info.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for info in messages.info - div= info.msg -if messages.success - .alert.alert-success.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for success in messages.success - div= success.msg \ No newline at end of file +.row + .col-xs-12 + if messages.errors + .alert.alert-danger.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for error in messages.errors + div!= error.msg + if messages.info + .alert.alert-info.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for info in messages.info + div!= info.msg + if messages.success + .alert.alert-success.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for success in messages.success + div!= success.msg diff --git a/views/partials/footer.jade b/views/partials/footer.jade index 8be0eefa28..3004dcbcf1 100644 --- a/views/partials/footer.jade +++ b/views/partials/footer.jade @@ -1,15 +1,15 @@ .fcc-footer .col-xs-12.hidden-xs.hidden-sm - a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')  Blog   - a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank')  Twitch   - a.ion-social-github(href="http://github.com/freecodecamp", target='_blank')  Github   - a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter   - a.ion-information-circled(href="/learn-to-code")  About   - a.ion-locked(href="/privacy")  Privacy   + a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')  Blog   + a.ion-social-twitch-outline(href="/twitch")  Twitch  + a.ion-social-github(href="http://github.com/freecodecamp", target='_blank')  Github   + a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter   + a.ion-information-circled(href="/learn-to-code")  About   + a.ion-locked(href="/privacy")  Privacy   .col-xs-12.visible-xs.visible-sm a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank') span.sr-only Free Code Camp's Blog - a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank') + a.ion-social-twitch-outline(href="/twitch") span.sr-only Free Code Camp Live Pair Programming on Twitch.tv a.ion-social-github(href="http://github.com/freecodecamp", target='_blank') span.sr-only Free Code Camp on GitHub @@ -18,4 +18,4 @@ a.ion-information-circled(href="/learn-to-code") span.sr-only About Free Code Camp a.ion-locked(href="/privacy") - span.sr-only Free Code Camp's Privacy Policy \ No newline at end of file + span.sr-only Free Code Camp's Privacy Policy diff --git a/views/partials/meta.jade b/views/partials/meta.jade index ebaba6a9b2..6a061404fb 100644 --- a/views/partials/meta.jade +++ b/views/partials/meta.jade @@ -21,7 +21,7 @@ meta(name="twitter:creator", content="@freecodecamp") meta(name="twitter:url", content="http://www.freecodecamp.com") meta(name="twitter:site", content="@freecodecamp") meta(name="twitter:card", content="summary_large_image") -meta(name="twitter:image:src", content="https://s3.amazonaws.com/freecodecamp/challenges.png") +meta(name="twitter:image:src", content="https://s3.amazonaws.com/freecodecamp/1000-hours-of-coding.jpg") meta(name="twitter:title", content="Free Code Camp: a community of busy people learning to code") meta(name="twitter:description", content="We're a community of busy people learning to code by collaborating on projects for nonprofits. Learn Full-stack JavaScript, build a portfolio, and get great references with our online coding bootcamp.") meta(content="a40ee5d5dba3bb091ad783ebd2b1383f", name="p:domain_verify") @@ -54,4 +54,4 @@ link(rel="mstile", sizes="310x310", href="https://s3.amazonaws.com/freecodecamp/ link(rel="mstile", sizes="310x150", href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-310x150.png") link(rel="mstile", sizes="70x70", href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-70x70.png") link(rel="favicon", href="https://s3.amazonaws.com/freecodecamp/favicons/favicon.ico") -link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicons/favicon.ico') \ No newline at end of file +link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicons/favicon.ico') diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 852a687477..89ebadcb94 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -11,18 +11,20 @@ img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg.gz', alt='learn to code javascript at Free Code Camp logo') .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown + li - a(href='/challenges') Challenges + a(href='/map') Map + if (user && user.sentSlackInvite) li a(href='/chat', target='_blank') Chat else li - a(href='/challenges/01') Chat + a(href='/challenges/join-our-chat-room') Chat li a(href='/stories/hot') News li - a(href='/bonfires') Bonfires + a(href='/field-guide') Field Guide if !user li       li @@ -30,7 +32,9 @@ else li if (user.profile.username) - a(href='/' + user.profile.username) [ #{user.points} ] + + a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] + else a(href='/account') [ #{user.points} ] .hidden-xs @@ -47,4 +51,4 @@ img.profile-picture.float-right(src='#{user.gravatar(60)}') else a(href='/account') - img.profile-picture.float-right(src='#{user.gravatar(60)}') \ No newline at end of file + img.profile-picture.float-right(src='#{user.gravatar(60)}') diff --git a/views/partials/nonprofit-application-progress-bar.jade b/views/partials/nonprofit-application-progress-bar.jade new file mode 100644 index 0000000000..e9ed80c441 --- /dev/null +++ b/views/partials/nonprofit-application-progress-bar.jade @@ -0,0 +1,39 @@ +.spacer +.progress + .progress-bar(role='progressbar', aria-valuenow= (step * 10), aria-valuemin='0', aria-valuemax='100', style="width: #{step * 10}%;") + span.sr-only= step * 10 + | % Complete +h3.gray-text.text-center Step #{step} of 10 + + +script. + $('#story-url').on('keypress', function (e) { + if (e.which === 13 || e === 13) { + $('#preliminary-story-submit').click(); + } + }); + var preliminaryStorySubmit = function preliminaryStorySubmit() { + var storyURL = $('#story-url').val(); + $('#preliminary-story-submit').attr('disabled', 'disabled'); + $.post('/stories/preliminary', + { + data: { + url: storyURL + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#preliminary-story-submit').attr('disabled', false); + }) + .done(function (data, textStatus, xhr) { + if (data.alreadyPosted) { + window.location = data.storyURL; + } else { + window.location = '/stories/submit/new-story?url=' + + encodeURIComponent(data.storyURL) + + '&title=' + encodeURIComponent(data.storyTitle) + + '&image=' + encodeURIComponent(data.storyImage) + + '&description=' + encodeURIComponent(data.storyMetaDescription); + } + }); + } + $('#preliminary-story-submit').on('click', preliminaryStorySubmit); diff --git a/views/partials/nonprofits.jade b/views/partials/nonprofits.jade new file mode 100644 index 0000000000..c7a9db255a --- /dev/null +++ b/views/partials/nonprofits.jade @@ -0,0 +1,24 @@ +h3 + ol#nonprofitsList + script(src='/js/lib/ramda/ramda.min.js') + script. + var getLinkedName = function getLinkedName(name) { + // ensure name is a string + name = name + ''; + return name.toLowerCase().replace(/\s/g, '-'); + } + $.ajax({ + url: '/nonprofits/getNonprofitList', + type: 'GET' + }) + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.nonprofitsList.length; i++) { + var li = document.createElement("li"); + var linkedName = getLinkedName(data.nonprofitsList[i].name); + $(li).html("" + data.nonprofitsList[i].name + ""); + docfrag.appendChild(li); + }; + $('#nonprofitsList').append(docfrag); + }); diff --git a/views/partials/stats.jade b/views/partials/stats.jade deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index d0c6ec57f3..ad37165618 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -30,4 +30,4 @@ script#inspectletjs(type='text/javascript'). } if (window.attachEvent) window.attachEvent('onload', __ldinsp); else window.addEventListener('load', __ldinsp, false); - })(); \ No newline at end of file + })(); diff --git a/views/resources/chromebook.jade b/views/resources/chromebook.jade deleted file mode 100644 index 620d4698ce..0000000000 --- a/views/resources/chromebook.jade +++ /dev/null @@ -1,10 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Win a Chromebook - h2 Thanks to everyone who participated in our Chromebook giveaway. - script(src='//widget-prime.rafflecopter.com/launch.js') - a#rcwidget_a7khonhd.rcptr(href='http://www.rafflecopter.com/rafl/display/d70901b10/', rel='nofollow', data-raflid='d70901b10', data-theme='classic', data-template='') a Rafflecopter giveaway - h2 Ready to learn to code? - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/control-shortcuts.jade b/views/resources/control-shortcuts.jade deleted file mode 100644 index 25b43a1011..0000000000 --- a/views/resources/control-shortcuts.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Global Control Shortcuts for Mac - h2 These Global Control Shortcuts for Mac will save you hours by speeding up your typing - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/107073108') - .text-left - h3 These global shortcuts work everywhere on a Mac: - ul - li Control + F = Forward - li Control + B = Backward - li Control + N = Next Line - li Control + P = Previous Line - li Control + H = Backspace - li Control + D = Delete - li Control + A = Beginning of Line - li Control + E = End of Line - li Control + K = Kill line - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/deploy-a-website.jade b/views/resources/deploy-a-website.jade deleted file mode 100644 index bd5a156fb4..0000000000 --- a/views/resources/deploy-a-website.jade +++ /dev/null @@ -1,25 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Deploy a Dynamic Website in 7 Minutes - h2 Here's a fast and easy way to deploy a dynamic website to the internet - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/115194017') - .text-left - h3 Use these links: - ul - li - a(href='http://www.atom.io' target='_blank') http://www.atom.io - |   - free text editor - li - a(href='http://www.startbootstrap.com' target='_blank') http://www.startbootstrap.com - |   - free responsive (Bootstrap) templates - li - a(href='http://www.powr.io' target='_blank') http://www.powr.io - |   - great plugins - li - a(href='http://www.bitballoon.com' target='_blank') http://www.bitballoon.com - |   - drag-and-drop deployment - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/gmail-shortcuts.jade b/views/resources/gmail-shortcuts.jade deleted file mode 100644 index 637d187108..0000000000 --- a/views/resources/gmail-shortcuts.jade +++ /dev/null @@ -1,24 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Gmail Shortcuts - h2 These Gmail Shortcuts will save you hours and help you get to Zero Inbox - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/115194016') - .text-left - h3 The shortcuts: - ul - li j - move down - li k - move up - li o - open - li r - reply - li a - reply all - li f - forward - li c - compose - li x - select - li e - archive - li ! - mark spam - li z - undo - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/guide-to-our-nonprofit-projects.jade b/views/resources/guide-to-our-nonprofit-projects.jade deleted file mode 100644 index 82b038cd55..0000000000 --- a/views/resources/guide-to-our-nonprofit-projects.jade +++ /dev/null @@ -1,98 +0,0 @@ -extends ../layout -block content - .jumbotron - h1.hug-top.text-center A Guide to our Nonprofit Projects - br - p Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp challenges and Bonfire challenges, you'll begin this process. - p Once you've finished all the challenges, click the "I'm done with all the challenges" button, which will become enabled. We will prompt you for your email address, then give you further instructions on our   - a(href="http://www.freecodecamp.com/nonprofit-project-instructions") Nonprofit Projects Instructions page - | . - p We will add you to our   - a(href="https://trello.com/b/BA3xVpz9/nonprofit-projects") Nonprofit Project Trello board - | . - h2 Starting with the end in mind - p Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction). - p You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects. - h2 Choosing your first Nonprofit Project - p We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates. - p Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week (on the   - a(href="http://goo.gl/forms/f61dLt67t8") form you filled out - | ). If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks. - p Our team of nonprofit project camp counselors will match you and your pair based on: - ol - li Your estimated time commitment (10, 20 or 40 hours per week) - li Your time zone - li The nonprofit projects you've chosen - li Prior coding experience (we'd like both campers to be able to contribute equally) - p We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace. - p You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project Trello cards. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one. - h2 Finalizing the Project - p Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process: - ol - li We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above. - li We'll call the stakeholder to confirm once again that he or she agrees with our   - a(href="freecodecamp.com/nonprofits") terms   - | and has signed our   - a(href="http://goo.gl/forms/0YKkd9bpcR") Nonprofit Project Stakeholder Pledge - | . - li We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder. - li If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project. - p This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time. - h2 Nonprofit Stakeholders - p Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a "stakeholder" - an authorative person who understands the organization and its needs for this particular project. - p Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs. - p When you and your pair first speak with your nonprofit stakeholder, you'll: - ul - li talk at length to better understand their needs. - li create a new Trello board and use it to prioritize what needs to be built. - li and establish deadlines based on your weekly time commitment, and how long you think each task will take. - p It's notoriously difficult to estimate how long building software projects will take, so feel free to ask camp counselors for help. - p You'll continue to meet with your stakeholder at least twice a month in your project's Gitter channel. - p You should also ask questions in your project's Gitter channel as they come up throughout the week, and your stakeholder can answer them asynchronously. - p Getting "blocked" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter. - p Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio! - h2 Working with your Pair - p You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time. - p Here are our recommended ways of collaborating: - ul - li • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email. - li • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them. - li • Screen Hero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming. - li • Write clear and readable code, commit messages, branch names, and pull request messages. - h2 Setting up your Development Environment - p We've created a custom virtual machine image with Ubuntu Linux, Git, Team Viewer, the MEAN Stack and all its dependencies. You can run this virtual image on any computer with at least 2 gigabytes of RAM and 16 gigabytes of hard drive space. - p The benefits of using this virtual machine are as follows: - ul - li • Everyone else on Free Code Camp is using this image, so we can all help you troubleshoot various problems that may arise. - li • When you pair program, you and your pair will have the exact same environment, which means you will both feel comfortable on each other's machines. - li • You can install the image on any computer without worrying about messing up the computer's original data or configuration. - li • Even if you end up using Windows or Mac OSX for development later, your server will almost certainly run Linux, so it's worth getting used to Linux. - li • Even experienced developers encounter hangups when setting up a development environment. This virtual machine image will remove this tedious process. - p Install a bit torrent client, then   - a(href="http://mgnet.me/ZOQk0rd") download our virtual machine image - | . - p Please note that even though Bit Torrent is often used to download content illegally, all the content on our image is open source and perfectly legal to redistribute. - p Once you've downloaded the file,   - a(href="https://www.virtualbox.org/wiki/Downloads") download VirtualBox   - | and follow   - a(href="http://techathlon.com/how-to-run-a-vmdk-file-in-oracle-virtualbox/") this tutorial   - | to open the image in VirtualBox. You'll want to assign the virtual machine at least two gigabytes of ram. - p Now you have your own Linux development environment. You can shut it down when you're not using it and it will save its state. Please continue to seed the file in bit torrent so that other campers can download it as well. Enjoy! - h2 Hosting Apps - p Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away. - p If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them. - h2 Maintaining Apps - p Once you complete a nonprofit project, your obligation to its stakeholder is finished. You goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical "super user"). - p While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers. - h2 Office Hours - p Quincy Larson and/or Michael Johnson will be in the   - a(href="https://gitter.im/FreeCodeCamp/NonprofitProjects") Gitter Nonprofit Project Channel   - | every Monday and Thursday from 9 - 10 p.m. EST. - p Our goal is to make the discussion as public as possible so all our campers can benefit from each camper’s questions. - p If necessary, we can also hop on Screen Hero with you to help you with issues more specific to your project. - h2 Pledging to finish the project - p Your nonprofit stakeholder, your pair, and the volunteer camp counselor team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one. - p To confirm that you understand the seriousness of this commitment, we require that all campers   - a(href="http://goo.gl/forms/ZMn96z2QqY") sign this pledge   - | before starting on their nonprofit projects. - p There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, "Real artists ship." And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career. \ No newline at end of file diff --git a/views/resources/install-screenhero.jade b/views/resources/install-screenhero.jade deleted file mode 100644 index cf0881ada2..0000000000 --- a/views/resources/install-screenhero.jade +++ /dev/null @@ -1,13 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Install ScreenHero - h2 - a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjowLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLnppcD9zb3VyY2U9d2ViIn0=") Download for Mac - h2 - a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjoxLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLXNldHVwLmV4ZSJ9") Download for Windows - p You can learn more about using Screen Hero by taking   - a(href="http://www.freecodecamp.com/challenges/34") Challenge 34. - p Screen Hero was recently acquired by a collaboration tool called Slack. It's still available and free, but will go away in the indefinite future. Discuss alternatives on our   - a(href="http://forum.freecodecamp.com/t/replacing-screen-hero/992") Screen Hero replacement thread - | . \ No newline at end of file diff --git a/views/resources/javascript-in-your-inbox.jade b/views/resources/javascript-in-your-inbox.jade deleted file mode 100644 index 4bc92a5025..0000000000 --- a/views/resources/javascript-in-your-inbox.jade +++ /dev/null @@ -1,12 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Win a Chromebook - h2 Sign up for Inbox.js - JavaScript challenges in your inbox - and enter to win a Chromebook! - img.image-responsive(src="https://s3.amazonaws.com/freecodecamp/chromebook.jpg" alt="HP Chromebook 11") - script(src='//widget-prime.rafflecopter.com/launch.js') - a#rcwidget_a7khonhd.rcptr(href='http://www.rafflecopter.com/rafl/display/d70901b10/', rel='nofollow', data-raflid='d70901b10', data-theme='classic', data-template='') a Rafflecopter giveaway - .animated.zoomInDown.delay-10 - p Finished signing up for the giveaway? - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index cf1cb685f5..7981389cbf 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -1,6 +1,9 @@ extends ../layout-wide block content - img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') + if (Math.random() > 0.98) + img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner-dino.png') + else + img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') br .row.text-center .col-xs-12.col-md-6.col-md-offset-3 @@ -40,7 +43,7 @@ block content script. var challengeName = 'Learn to code' .row - .col-xs-12.col-sm-12.col-md-6 + .col-xs-12 .panel.panel-info .panel-heading.landing-panel-heading.text-center Announcements .panel-body @@ -77,23 +80,21 @@ block content .col-xs-12.github-and-twitter-button-text html. - .col-xs-12.col-sm-12.col-md-6 - include ../partials/faq - //#announcementModal.modal(tabindex='-1') - // .modal-dialog - // .modal-content - // .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST - // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - // .modal-body - // h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   - // a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel - // | . - // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp - // a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! - //script. - // $(document).ready(function() { - // if (!localStorage || !localStorage.campWideMeeting) { - // $('#announcementModal').modal('show'); - // localStorage.campWideMeeting = "true"; - // } - // }); \ No newline at end of file + #announcementModal.modal(tabindex='-1') + .modal-dialog + .modal-content + .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   + a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel + | . + a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp + a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! + script. + $(document).ready(function() { + if (!localStorage || !localStorage.campWideMeeting) { + $('#announcementModal').modal('show'); + localStorage.campWideMeeting = "true"; + } + }); diff --git a/views/resources/live-pair-programming.jade b/views/resources/live-pair-programming.jade deleted file mode 100644 index ba2a4c0094..0000000000 --- a/views/resources/live-pair-programming.jade +++ /dev/null @@ -1,56 +0,0 @@ -extends ../layout-wide -block content - - .text-center - h1 Live Pair Programming - - - h2#next-session - - 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') - 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 - br - br - br - script(src="/js/lib/moment/moment.js") - script(src="/js/lib/moment/nextTuesday.js") - script. - $('#next-session').text(nextSession()); diff --git a/views/resources/nodeschool-challenges.jade b/views/resources/nodeschool-challenges.jade deleted file mode 100644 index cc8f48f736..0000000000 --- a/views/resources/nodeschool-challenges.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top NodeSchool Challenges - h2 Learn Node.js, NPM, Express.js, and advanced JavaScript like Functional Programming and Promises - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/122719685') - .text-left - h3 Here are the NodeSchool courses you should complete: - ol - li - a(href='http://runnable.com/VQuO_Y4BbkhaOOsV/nodeschool-io-s-learnyounode-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's LearnYouNode course - li - a(href='http://runnable.com/VQt7deuMe6RZ3Gcl/nodeschool-io-s-learn-to-npm-course-running-in-your-browser-for-node-js-and-hello-world' target='_blank') NodeSchool.io's Learn-to-NPM course - li - a(href='http://runnable.com/VQufnaRAlaNc9JuM/nodeschool-io-s-express-js-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's Express.js course - li - a(href='http://runnable.com/VQuZjvia8Gxcqkpy/nodeschool-io-s-functional-programming-in-javascript-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's Functional Programming in JavaScript course - li - a(href='http://runnable.com/VQunH26qdytcbLBg/nodeschool-io-s-promise-it-won-t-hurt-promises-course-available-in-your-browser-for-node-js-javascript-and-freecodecamp' target='_blank') NodeSchool.io's Promise It Won't Hurt Promises course - br \ No newline at end of file diff --git a/views/resources/nonprofit-project-instructions.jade b/views/resources/nonprofit-project-instructions.jade deleted file mode 100644 index 3043e41668..0000000000 --- a/views/resources/nonprofit-project-instructions.jade +++ /dev/null @@ -1,23 +0,0 @@ -extends ../layout -block content - .jumbotron - .col-sm-offset-1 - h1 Nonprofit projects - h3 It's time to apply what you've learned here at Free Code Camp. - h3 By the end of this process, you'll have a portfolio of live apps being used by real people. - h3 Please do the following immediately: - h4 - ol - li Complete this form:   - a(href="http://goo.gl/forms/f61dLt67t8" target="_blank") http://goo.gl/forms/f61dLt67t8 - | . - li Read this document, which will answer many questions you may have about our nonprofit projects:   - a(href="/guide-to-our-nonprofit-projects" target="_blank") http://freecodecamp.com/guide-to-our-nonprofit-projects - | . - li We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you. - li Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp "exit test". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look:  - a(href="mailto:team@freecodecamp.com") team@freecodecamp.com - | . - h4 Please email us if you have further questions:   - a(href="mailto:team@freecodecamp.com") team@freecodecamp.com - | . \ No newline at end of file diff --git a/views/resources/privacy.jade b/views/resources/privacy.jade deleted file mode 100644 index 54278c5b57..0000000000 --- a/views/resources/privacy.jade +++ /dev/null @@ -1,40 +0,0 @@ -extends ../layout -block content - .jumbotron - h2.big-text Privacy - html. -

Free Code Camp is committed to respecting the privacy of visitors to our web sites and web applications. The guidelines below explain how we protect the privacy of visitors to FreeCodeCamp.com and its features.

-

Personally Identifiable Information

-

Free Code Camp protects the identity of visitors to FreeCodeCamp.com by limiting the collection of personally identifiable information.

-

Free Code Camp does not knowingly collect or solicit personally identifiable information from or about children under 13, except as permitted by law. If we discover we have received any information from a child under 13 in violation of this policy, we will delete that information immediately. If you believe Free Code Camp has any information from or about anyone under 13, please e-mail us at team@freecodecamp.com.

-

All personally identifiable information you provide to us is used by Free Code Camp and its team to process and manage your account, analyze the demographic of our users, or to deliver services through the site.

-

If you choose to provide personally identifiable information to us, you may receive occasional e-mails from us that are relevant to Free Code Camp, getting a job, or learning to code in general.

-

Free Code Camp may also use other third-party providers to facilitate the delivery of the services described above, and these third-party providers may be supplied with or have access to personally identifiable information for the sole purpose of providing these services, to you on behalf of Free Code Camp.

-

Free Code Camp may also disclose personally identifiable information in special legal circumstances. For instance, such information may be used where it is necessary to protect our copyright or intellectual property rights, or if the law requires us to do so.

-

Anonymous Information

-

Anonymous aggregated data may be provided to other organizations we associate with for statistical purposes. For example, we may report to an organization that a certain percentage of our site's visitors are adults between the ages of 25 and 35.

-

Cookies and Beacons—Use by Free Code Camp; Opting Out

-

We use cookies and software logs to monitor the use of FreeCodeCamp.com and to gather non-personal information about visitors to the site. Cookies are small files that Free Code Camp transfers to the hard drives of visitors for record-keeping purposes. These monitoring systems allow us to track general information about our visitors, such as the type of browsers (for example, Firefox or Internet Explorer), the operating systems (for instance, Windows or Macintosh), or the Internet providers (for instance, Comcast) they use. This information is used for statistical and market research purposes to tailor content to usage patterns and to provide services requested by our customers. To delete these cookies, please see your browser's privacy settings.

-

A beacon is an electronic file object (typically a transparent image) placed in the code of a Web page. We use third party beacons to monitor the traffic patterns of visitors from one Free Code Camp.com page to another and to improve site performance.

-

None of the information we gather in this way can be used to identify any individual who visits our site.

-

Security

-

Any personally identifiable information collected through this site is stored on limited-access servers. We will maintain safeguards to protect these servers and the information they store.

-

Surveys

-

We may occasionally conduct on-line surveys. All surveys are voluntary and you may decline to participate.

-

Copyright

-

All of the content on FreeCodeCamp.com is copyrighted by Free Code Camp. If you'd like to redistribute it beyond simply sharing it through social media, please contact us at team@freecodecamp.com.

-

Contacting Us

-

If you have questions about Free Code Camp, or to correct, update, or remove personally identifiable information, please email us at team@freecodecamp.com.

-

Links to Other Web sites

-

Free Code Camp's sites each contain links to other Web sites. Free Code Camp is not responsible for the privacy practices or content of these third-party Web sites. We urge all FreeCodeCamp.com visitors to follow safe Internet practices: Do not supply Personally Identifiable Information to these Web sites unless you have verified their security and privacy policies.

-

Data Retention

-

We retain your information for as long as necessary to permit us to use it for the purposes that we have communicated to you and comply with applicable law or regulations.

-

Business Transfers

-

As we continue to develop our business, we might sell or buy subsidiaries, or business units. In such transactions, customer information generally is one of the transferred business assets but remains subject to the promises made in any pre-existing Privacy Policy (unless, of course, the customer consents otherwise). Also, in the unlikely event that Free Code Camp, or substantially all of its assets are acquired, customer information will be one of the transferred assets, and will remain subject to our Privacy Policy.

-

Your California Privacy Rights

-

If you are a California resident, you are entitled to prevent sharing of your personal information with third parties for their own marketing purposes through a cost-free means. If you send a request to the address above, Free Code Camp will provide you with a California Customer Choice Notice that you may use to opt-out of such information sharing. To receive this notice, submit a written request to team@freecodecamp.com, specifying that you seek your "California Customer Choice Notice." Please allow at least thirty (30) days for a response.

-

Acceptance of Privacy Policy Terms and Conditions

-

By using this site, you signify your agreement to the terms and conditions of this FreeCodeCamp.com Privacy Policy. If you do not agree to these terms, please do not use this site. We reserve the right, at our sole discretion, to change, modify, add, or remove portions of this policy at any time. All amended terms automatically take effect 30 days after they are initially posted on the site. Please check this page periodically for any modifications. Your continued use of FreeCodeCamp.com following the posting of any changes to these terms shall mean that you have accepted those changes.

-

If you have any questions or concerns, please send an e-mail to team@freecodecamp.com.

- script. - var challengeName = 'Privacy'; diff --git a/views/resources/programmer-interview-questions-app.jade b/views/resources/programmer-interview-questions-app.jade deleted file mode 100644 index 14dbada372..0000000000 --- a/views/resources/programmer-interview-questions-app.jade +++ /dev/null @@ -1,202 +0,0 @@ -extends ../layout -block content - script. - $(document).ready(function () { - var directions = { - 0: "To get started, open your Chrome DevTools. The #next-exercise button is disabled below. Try using jQuery's .attr() method to turn the disabled attribute to false.", - 1: "Move the .target element from #location1 to #location2.", - 2: "Change the background color of .target to red.", - 3: "Change the background color of the even-numbered targets to red.", - 4: "Change the background color of the target4 to red.", - 5: "Clone the target2 in #location1 so that it also exists in #location2.", - 6: "Remove the target3 from element from #location1.", - 7: "Check the following checkboxes using jQuery.", - 8: "Make the text input field read-only.", - 9: "Select the target2 option in the select box.", - 10: "Add the following css classes to .target: 'animated' and 'hinge'.", - 11: "Use jQuery to read the data of .target.", - 12: "Use 'length' to count the number of child elements in #location1, then display that value in #location2.", - 13: "There's an element hidden in #location1. Show it using jQuery, and then click on it." - }; - var hint = { - 0: "$('#next-exercise').attr('disabled', false);", - 1: "$('.target').appendTo('#location2');", - 2: "$('.target').css('background', 'red');", - 3: "$('.target:even').css('background', 'red');", - 4: "$('.target:nth-child(4)').css('background', 'red');", - 5: "$('.target:nth-child(2)').clone().appendTo($('#location2'));", - 6: "$('.target:nth-child(3)').remove();", - 7: "$('#location1 input').attr('checked', 'true')", - 8: "$('#location1 input').attr('readonly', 'true')", - 9: "$('#location1 select').val('target2');", - 10: "$('.target').addClass('animated hinge');", - 11: "$('.target').data();", - 12: "$('#location2').text($('#location1').children().length)", - 13: "$('#finished-button').show().click();" - }; - var elements = { - 0: "", - 1: "
.target
", - 2: "
.target
", - 3: "
target0
target1
target2
target3
target4
", - 4: "
target1
target2
target3
target4
target5
", - 5: "
target1
target2
target3
target4
target5
", - 6: "
target1
target2
target3
target4
target5
", - 7: "checkbox1
checkbox2", - 8: "", - 9: "", - 10: "
.target
", - 11: "
.target
", - 12: "
target1
target2
target3
target4
target5
", - 13: "
Finish!
" - }; - - function refreshEverything() { - $('#directions').text("Exercise " + currentExercise + ": " + directions[currentExercise]); - $('#location1').html(elements[currentExercise]); - $('#hint').text(hint[currentExercise]); - handleExerciseTransition(); - } - - $('#exercise-directory').on('click', 'li', event, function () { - currentExercise = $(this).index(); - event.preventDefault(); - refreshEverything(event); - }); - $('#next-exercise').on('click', event, function () { - ++currentExercise; - event.preventDefault(); - refreshEverything(event); - }); - $('#solution-button').on('click', function () { - $('#hint-modal').modal({backdrop: "static"}); - }); - $('#location1').on('click', '#finished-button', function () { - $('#finished-modal').modal({backdrop: "static"}); - }); - function handleExerciseTransition() { - if (currentExercise === 0) { - $('#next-exercise').attr('disabled', true); - } else { - $('#next-exercise').attr('disabled', false); - } - if (currentExercise === 2 || currentExercise === 6) { - $('#location2 .target').remove(); - } - if (currentExercise === 13) { - $('#location2').text(''); - $('#finished-button').hide(); - $('#next-exercise').attr('disabled', true); - } - } - - var currentExercise = 0; - refreshEverything(currentExercise); - }); - style. - #directions { - text-align: left; - font-size: 15px; - } - - .well { - text-align: left; - height: 200px; - } - - #exercise-directory { - font-size: 20px; - } - - #current-exercise { - text-size: 250px; - } - html. - - - diff --git a/views/resources/sitemap.jade b/views/resources/sitemap.jade index c51b2a3c99..d2b4298424 100644 --- a/views/resources/sitemap.jade +++ b/views/resources/sitemap.jade @@ -105,7 +105,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Products each bonfire in bonfires url - loc #{appUrl}/#{bonfire.name.replace(/\s/, '-')} + loc #{appUrl}/bonfires/#{bonfire.name.replace(/\s/, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -113,7 +113,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Challenges each challenge in challenges url - loc #{appUrl}/#{challenge.challengeNumber} + loc #{appUrl}/challenges/#{challenge.name.replace(/\s/, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -121,15 +121,23 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Stories each story in stories url - loc #{appUrl}/#{story.storyLink} + loc #{appUrl}/stories/#{story.storyLink.name.replace(/\s/, '-')} lastmod= now changefreq daily priority= 0.9 -//- Nonprofit + //- Nonprofit each nonprofit in nonprofits url - loc #{appUrl}/#{nonprofit.nonprofitLink} - lastmod= now - changefreq daily - priority= 0.9 \ No newline at end of file + loc #{appUrl}/nonprofits/#{nonprofit.name.replace(/\s/, '-')} + lastmod= now + changefreq daily + priority= 0.9 + + //- Nonprofit + each fieldGuide in fieldGuides + url + loc #{appUrl}/field-guide/#{fieldGuide.name.replace(/\s/, '-')} + lastmod= now + changefreq daily + priority= 0.9 diff --git a/views/resources/stats.jade b/views/resources/stats.jade deleted file mode 100644 index 71659d344a..0000000000 --- a/views/resources/stats.jade +++ /dev/null @@ -1,3 +0,0 @@ -extends ../layout -block content - include ../partials/stats \ No newline at end of file diff --git a/views/resources/twitch.jade b/views/resources/twitch.jade new file mode 100644 index 0000000000..e6c4ff4913 --- /dev/null +++ b/views/resources/twitch.jade @@ -0,0 +1,59 @@ +extends ../layout +block content + .col-xs-12 + .panel.panel-info + .panel-heading.text-center Watch us code on our Twitch.tv channel + .panel-body.text-center + h2 Watch the live stream below or on our   + a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel + | . + .spacer + .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') + .row + .col-xs-12 + h2 Check out our scheduled shows. You can add them to your calendar. + .embed-responsive.embed-responsive-16by9 + iframe.embed-responsive-item(src="https://www.google.com/calendar/embed?src=freecodecamp.com_r06116ile3o6ucpif7s0g281tc%40group.calendar.google.com&ctz=America/New_York&mode=AGENDA" style="border: 0" width="800" height="600" frameborder="0" scrolling="no") + .row + .col-xs-12 + h2 Here are some of our previous shows (you can full-screen them) + .row + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA + .row + .col-xs-12.text-center + if !user + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) diff --git a/views/stories/comments.jade b/views/stories/comments.jade index 751f65f915..f21f43ac3a 100644 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -56,7 +56,8 @@ sentinel--; if (!sentinel) { $('.comment-a-comment').on('click', 'a', function () { - if (!user) { + if (typeof user == "undefined" || !user) { + window.location.href = '/signin'; return; } $(this).unbind('click'); @@ -91,15 +92,19 @@ }); var submitCommentToCommentHandler = function submitCommentToCommentHandler() { $('#submit-comment-to-comment').unbind('click'); + console.log('in comments.jade', originalStoryAuthorEmail); $.post('/stories/comment/' + commentId + '/comment', { data: { associatedPost: commentId, + originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: $('#comment-to-comment-textinput').val(), author: { picture: user.profile.picture, userId: user._id, - username: user.profile.username + username: user.profile.username, + email: user.email } } }) diff --git a/views/stories/index.jade b/views/stories/index.jade index c5b0c2de34..19aa25dc5e 100644 --- a/views/stories/index.jade +++ b/views/stories/index.jade @@ -1,9 +1,14 @@ extends ../layout block content script(src='/js/lib/moment/moment.js') + if (user) + script. + var user = !{JSON.stringify(user)}; + else + script. + var user = undefined; script. var challengeName = 'Camper News'; - var user = !{JSON.stringify(user)}; var page = !{JSON.stringify(page)}; .panel.panel-info .panel-heading.text-center Camper News diff --git a/views/stories/preliminary-submit.jade b/views/stories/preliminary-submit.jade index 31ef779fcd..4bc8ba873c 100644 --- a/views/stories/preliminary-submit.jade +++ b/views/stories/preliminary-submit.jade @@ -44,4 +44,12 @@ } }); } - $('#preliminary-story-submit').on('click', preliminaryStorySubmit); \ No newline at end of file + $('#preliminary-story-submit').on('click', preliminaryStorySubmit); + + + arr = $( "h3 input:checked" ) + .map(function() { + return this.id; + }) + .get() + .join('&'); \ No newline at end of file diff --git a/views/stories/search-stories.jade b/views/stories/search-stories.jade index 9574bf4b9f..84813fc470 100644 --- a/views/stories/search-stories.jade +++ b/views/stories/search-stories.jade @@ -2,7 +2,7 @@ .spacer h1.text-center Search is coming soon .input-group - input#searchArea.big-text-field.field-responsive.form-control(type='text', placeholder='Search our stories', autofocus) + input#searchArea.big-text-field.field-responsive.form-control(type='text', placeholder='Search our links', autofocus) span.input-group-btn button.disabled#searchbutton.btn.btn-big.btn-primary.btn-responsive(type='button') Search .spacer @@ -70,4 +70,4 @@ script. } }); - } \ No newline at end of file + } diff --git a/views/stories/show.jade b/views/stories/show.jade index ea5e08032e..0d093775c1 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -1,6 +1,8 @@ .spacer script. var storyId = !{JSON.stringify(id)}; + var originalStoryLink = !{JSON.stringify(originalStoryLink)}; + var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)}; var comments = !{JSON.stringify(comments)}; var upVotes = !{JSON.stringify(upVotes)}; var image = !{JSON.stringify(image)}; @@ -56,7 +58,10 @@ $('#image-display').removeClass('hidden-element') } $('#reply-to-main-post').on('click', function() { - if (!user) return; + if (typeof user == "undefined" || !user) { + window.location.href = '/signin'; + return; + } $('#initial-comment-submit').removeClass('hidden-element'); $(this).unbind('click'); $('.comment-to-comment-formgroup').empty();