From b4850932dceea88171249278fec4ee7e1b45c5b3 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Mon, 20 Apr 2015 19:12:14 -0700 Subject: [PATCH 01/25] trivial change for example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d621fd4b5e..054a8f17c3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Free Code Camp! -======================= +======================== We're a community of busy people learning to code by collaborating on projects for nonprofits. We learn, then use, the JavaScript MEAN stack - MongoDB, Express.js, Angular.js and Node.js. From bf723b522bc45a451780dc6944e18cc3ee6d3575 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Thu, 23 Apr 2015 23:46:58 -0700 Subject: [PATCH 02/25] start making ux changes --- app.js | 4 +- controllers/challengeMap.js | 38 +++++++++++++++---- controllers/challenges.js | 59 ------------------------------ public/js/main.js | 13 +------ views/challengeMap/show.jade | 39 +++++++++++++++++--- views/challenges/show.jade | 50 ------------------------- views/partials/footer.jade | 3 -- views/partials/navbar-narrow.jade | 4 +- views/resources/learn-to-code.jade | 18 --------- 9 files changed, 68 insertions(+), 160 deletions(-) delete mode 100644 controllers/challenges.js delete mode 100644 views/challenges/show.jade diff --git a/app.js b/app.js index bd36ff727c..5f730fe264 100755 --- a/app.js +++ b/app.js @@ -299,9 +299,9 @@ app.get('/nodeschool-challenges', function(req, res) { app.get('/news', function(req, res) { res.redirect(301, '/stories/hot'); }); -app.get('/learn-to-code', resourcesController.about); +app.get('/learn-to-code', challengeMapController.challengeMap); app.get('/about', function(req, res) { - res.redirect(301, '/learn-to-code'); + res.redirect(301, '/map'); }); app.get('/signin', userController.getSignin); diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js index b858bf7385..c6418b41c7 100644 --- a/controllers/challengeMap.js +++ b/controllers/challengeMap.js @@ -52,14 +52,36 @@ module.exports = { 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 + 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 numberWithCommas(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } + + 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)); + + User.count({}, function (err, camperCount) { + if (err) { + debug('User err: ', err); + return next(err); + } + res.render('challengeMap/show', { + daysRunning: daysRunning, + camperCount: numberWithCommas(camperCount), + 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/challenges.js b/controllers/challenges.js deleted file mode 100644 index 463edda920..0000000000 --- a/controllers/challenges.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * GET / - * Challenges. - */ -var _ = require('lodash'), - debug = require('debug')('freecc:cntr:challenges'), - Challenge = require('./../models/Challenge'), - resources = require('./resources'); - -var highestChallengeNumber = 53; - - -exports.returnNextChallenge = function(req, res) { - if (req.user) { - ch = req.user.challengesHash; - if (req.user.challengesHash[0] > 0) { - var max = Object.keys(ch).reduce(function(max, key) { - return (max === undefined || ch[key] > ch[max]) ? +key : max; - }); - nextChallenge = max + 1; - res.redirect('challenges/' + nextChallenge); - } else { - res.redirect('challenges/0'); - } - } else { - return res.redirect('../challenges/0'); - } -}; - -exports.returnChallenge = function(req, res, next) { - var challengeNumber = parseInt(req.params.challengeNumber) || 0; - - if (challengeNumber > highestChallengeNumber) { - req.flash('errors', { - msg: "It looks like you've either completed all the challenges we have available or requested a challenge we don't have." - }); - return res.redirect('../challenges/0'); - } - Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) { - if (err) { - debug('Challenge err: ', err); - return next(err); - } - res.render('challenges/show', { - title: 'Challenge: ' + c[challengeNumber].name, - name: c[challengeNumber].name, - video: c[challengeNumber].video, - time: c[challengeNumber].time, - steps: c[challengeNumber].steps, - number: challengeNumber, - cc: req.user ? req.user.challengesHash : undefined, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - challenges: c - }); - }); -}; diff --git a/public/js/main.js b/public/js/main.js index 59905c18e3..ee6c9e1cb8 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -4,13 +4,6 @@ $(document).ready(function() { 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"; - } - var CSRF_HEADER = 'X-CSRF-Token'; var setCSRFToken = function(securityToken) { @@ -204,10 +197,6 @@ $(document).ready(function() { } }); - $('.all-challenges').on('click', function() { - $('#show-all-dialog').modal('show'); - }); - $('#showAllButton').on('click', function() { $('#show-all-dialog').modal('show'); }); @@ -217,7 +206,7 @@ $(document).ready(function() { window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); }); -// Bonfire instructions functions + // Bonfire instructions functions $('#more-info').on('click', function() { ga('send', 'event', 'Challenge', 'more-info', challengeName); $('#brief-instructions').hide(); diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 5c230072df..bd4a76ea7b 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -5,10 +5,19 @@ block content .panel-heading.text-center h1 Challenge Map .panel-body - .row - .col-xs-12.col-sm-12.col-md-10.col-md-offset-2 - h3 Complete all of these challenges from top to bottom. - h3 Then we'll assign you to your first nonprofit project. + .col-xs-12 + if (Math.random() > 0.99) + 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') + .col-xs-12.col-md-8.col-md-offset-2 + h2.text-center + span.text-primary #{camperCount}   + | campers have joined our community + br + | since we launched   + span.text-primary #{daysRunning}   + | days ago. h2 span.fa.fa-flag |   Waypoints (200 hours of lessons) @@ -92,7 +101,7 @@ block content li a(href="/challenges/#{basejump.name}")= basejump.name h2 - span.ion-ios-heart   Nonprofit Projects (800 hours of real-world experience) + span.ion-ios-heart   Nonprofit Projects (800 hours of real-world experience)* h3.negative-15 ul .row @@ -101,3 +110,23 @@ block content .col-xs-12.col-sm-9.col-md-10 li a(href="/nonprofits/directory") Browse our nonprofit projects + p * Complete all Waypoints, Bonfires, Ziplines and Basejumps to be assigned your first nonprofit project + + //#announcementModal.modal(tabindex='-1') + // .modal-dialog + // .modal-content + // .modal-header.challenge-list-header We've updated our curriculum + // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + // .modal-body + // h3.text-left We now have a 1,600 hour curriculum and tons of new challenges. Read more about it + // a(href='http://blog.freecodecamp.com', target='_blank') here + // | . + // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://blog.freecodecamp.com/', target='_blank') Take me to the blog post. + // 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.newCurriculum) { + // $('#announcementModal').modal('show'); + // localStorage.campWideMeeting = "true"; + // } + // }); diff --git a/views/challenges/show.jade b/views/challenges/show.jade deleted file mode 100644 index c2843bb107..0000000000 --- a/views/challenges/show.jade +++ /dev/null @@ -1,50 +0,0 @@ -extends ../layout -block content - .row - .col-sm-12.col-md-12.col-xs-12 - .panel.panel-primary - .panel-heading.text-center - h1 #{name} (takes #{time} minutes) - script. - var challengeName = null; - .panel.panel-body - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') - .col-xs-12.col-sm-10.col-sm-offset-1.col-md-8.col-md-offset-2 - h3 Steps: - h4 - ol - for step in steps - li!= step - .btn.btn-primary.btn-big.btn-block.completed-challenge I've completed this challenge - .ten-pixel-break - .btn.btn-success.btn-big.btn-block.all-challenges Show me all the challenges - #complete-challenge-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 - .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-challenge-button(name='_csrf', value=_csrf, aria-hidden='true') Take me to my next challenge - - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(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. - $.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' - }) - .success( - function (data) { - console.log(data); - 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); - } - ); diff --git a/views/partials/footer.jade b/views/partials/footer.jade index 3004dcbcf1..de503bde8f 100644 --- a/views/partials/footer.jade +++ b/views/partials/footer.jade @@ -4,7 +4,6 @@ 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') @@ -15,7 +14,5 @@ span.sr-only Free Code Camp on GitHub a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank') span.sr-only Free Code Camp on Twitter - 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 diff --git a/views/partials/navbar-narrow.jade b/views/partials/navbar-narrow.jade index b8ff7267f9..8b13789179 100644 --- a/views/partials/navbar-narrow.jade +++ b/views/partials/navbar-narrow.jade @@ -1,3 +1 @@ -nav.navbar.navbar-default.navbar-fixed-top.nav-height - .container - include ./navbar \ No newline at end of file + diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 9c87cf1a7a..59b9586ecc 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -63,21 +63,3 @@ block content .col-xs-12.github-and-twitter-button-text html. - //#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"; - // } - // }); From e5e47136ea8ef0fe4a3fa87c1f4bc8762830b16c Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 01:31:59 -0700 Subject: [PATCH 03/25] make all navbar layouts wide style, redirect logo to challenge map, improve navbar collapse --- controllers/home.js | 2 +- public/css/main.less | 51 ++++++++++++++--- views/account/show.jade | 11 ++-- views/layout-wide.jade | 4 +- views/layout.jade | 2 +- views/partials/navbar-narrow.jade | 1 - views/partials/navbar-wide.jade | 2 - views/partials/navbar.jade | 94 ++++++++++++++++--------------- 8 files changed, 103 insertions(+), 64 deletions(-) delete mode 100644 views/partials/navbar-narrow.jade delete mode 100644 views/partials/navbar-wide.jade diff --git a/controllers/home.js b/controllers/home.js index 836ad9e62b..686cd2a80c 100644 --- a/controllers/home.js +++ b/controllers/home.js @@ -5,7 +5,7 @@ exports.index = function(req, res) { if (req.user) { - res.redirect('/challenges/') + res.redirect('/map') } else { res.render('home', { title: 'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits' diff --git a/public/css/main.less b/public/css/main.less index d6e905c8ea..cf93cebef7 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -273,11 +273,6 @@ ul { height: 30px; margin-top: -5px; } - @media (min-width: 767px) and (max-width: 890px) { - height: 30px; - margin-top: -5px; - } - } .navbar-right { @@ -790,10 +785,52 @@ iframe.iphone { transition: background .2s ease-in-out, border .2s ease-in-out; } +@media (max-width: 991px) { + .navbar-header { + float: none; + } + + .navbar-toggle { + display: block; + } + + .navbar-collapse.collapse { + display: none !important; + } + + .navbar-nav { + float: none !important; + margin: 7.5px -15px; + } + + .navbar-nav > li { + float: none; + } + + .navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + } + + .navbar-text { + float: none; + margin: 15px 0; + } + + /* since 3.1.0 */ + .navbar-collapse.collapse.in { + display: block !important; + } + + .collapsing { + overflow: hidden !important; + } +} + .hamburger { width: 80px; padding-left: 0px; - padding-right: 0px; + padding-right: 8px; margin-left: 0px; margin-right: 2px; text-align:left; @@ -804,7 +841,7 @@ iframe.iphone { line-height: 0.75em; margin-top: 10px; font-size: 16px; - margin-left: -5px; + margin-left: -8px; } .tight-h3 { diff --git a/views/account/show.jade b/views/account/show.jade index 8dd1314227..deb8168b4f 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -8,10 +8,13 @@ block content h1 #{username}'s portfolio .panel-body if (user && user.profile.username === username) - .col-xs-12 - .text-center - a.btn.btn-big.btn-primary(href="/account") Update my portfolio page or manage my account - br + .row.text-center + .col-xs-12.col-sm-10.col-sm-offset-1 + a.btn.btn-big.btn-primary.btn-block(href="/account") Update my portfolio page or manage my account + .spacer + .col-xs-12.col-sm-10.col-sm-offset-1 + a.btn.btn-big.btn-success.btn-block(href="/signout") Sign out of Free Code Camp + .spacer .row .col-xs-12 .col-xs-12.col-sm-12.col-md-5 diff --git a/views/layout-wide.jade b/views/layout-wide.jade index a320f210a5..fe05524cb0 100644 --- a/views/layout-wide.jade +++ b/views/layout-wide.jade @@ -6,8 +6,8 @@ html(ng-app='profileValidation', lang='en') body.no-top-and-bottom-margins.full-screen-body-background include partials/css-cdns - include partials/navbar-wide + include partials/navbar include partials/flash block content include partials/footer - != js('application') \ No newline at end of file + != js('application') diff --git a/views/layout.jade b/views/layout.jade index e50689c0a7..48e11a3143 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -5,7 +5,7 @@ html(ng-app='profileValidation', lang='en') != css('main') body.top-and-bottom-margins include partials/css-cdns - include partials/navbar-narrow + include partials/navbar .container include partials/flash block content diff --git a/views/partials/navbar-narrow.jade b/views/partials/navbar-narrow.jade deleted file mode 100644 index 8b13789179..0000000000 --- a/views/partials/navbar-narrow.jade +++ /dev/null @@ -1 +0,0 @@ - diff --git a/views/partials/navbar-wide.jade b/views/partials/navbar-wide.jade deleted file mode 100644 index 96165a030d..0000000000 --- a/views/partials/navbar-wide.jade +++ /dev/null @@ -1,2 +0,0 @@ -nav.navbar.navbar-default.navbar-fixed-top.nav-height - include ./navbar \ No newline at end of file diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index f563f92c81..e409e3860a 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -1,54 +1,56 @@ -.navbar-header - button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') - .col-xs-6 - span.hamburger-text Menu - .col-xs-6 - span.sr-only Toggle navigation - span.icon-bar - span.icon-bar - span.icon-bar - a.navbar-brand(href='/') - 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 +nav.navbar.navbar-default.navbar-fixed-top.nav-height + .navbar-header + button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') + .col-xs-6 + span.hamburger-text Menu + .col-xs-6 + span.sr-only Toggle navigation + span.icon-bar + span.icon-bar + span.icon-bar + a.navbar-brand(href='/') + 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='/map') Map - - if (user && user.sentSlackInvite) li - a(href='/chat', target='_blank') Chat - else + a(href='/challenges') Next Challenge li - a(href='/challenges/join-our-chat-room') Chat - li - a(href='/stories/hot') News - li - a(href='/field-guide') Field Guide - if !user - li       + a(href='/map') Map + if (user && user.sentSlackInvite) + li + a(href='/chat', target='_blank') Chat + else + li + a(href='/challenges/join-our-chat-room') Chat li - a.btn.signup-btn.signup-btn-nav(href='/login') Sign in - else + a(href='/stories/hot') News li - if (user.profile.username) - - a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] - - else - a(href='/account') [ #{user.progressTimestamps.length} ] - .hidden-xs - if user.profile.picture + a(href='/field-guide') Field Guide + if !user + li       + li + a.btn.signup-btn.signup-btn-nav(href='/login') Sign in + else + li if (user.profile.username) - a(href='/' + user.profile.username) - img.profile-picture.float-right(src='#{user.profile.picture}') + + a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] + else - a(href='/account') - img.profile-picture.float-right(src='#{user.profile.picture}') - else - if (user.profile.username) - a(href='/' + user.profile.username) - img.profile-picture.float-right(src='#{user.gravatar(60)}') + a(href='/account') [ #{user.progressTimestamps.length} ] + .hidden-xs.hidden-sm + if user.profile.picture + if (user.profile.username) + a(href='/' + user.profile.username) + img.profile-picture.float-right(src='#{user.profile.picture}') + else + a(href='/account') + img.profile-picture.float-right(src='#{user.profile.picture}') else - a(href='/account') - img.profile-picture.float-right(src='#{user.gravatar(60)}') + if (user.profile.username) + a(href='/' + user.profile.username) + img.profile-picture.float-right(src='#{user.gravatar(60)}') + else + a(href='/account') + img.profile-picture.float-right(src='#{user.gravatar(60)}') From bea77c580ec050c5a8e3967c0afe4a00fc7c4778 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 02:02:02 -0700 Subject: [PATCH 04/25] redirect user to bonfires if has completed waypoints but not bonfires gulp --- controllers/challengeMap.js | 5 ----- controllers/courseware.js | 7 +++++++ models/User.js | 4 ++++ seed_data/coursewares.json | 32 +++++++++++++++----------------- views/partials/navbar.jade | 5 +++-- views/resources/twitch.jade | 26 +++++++++++--------------- 6 files changed, 40 insertions(+), 39 deletions(-) diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js index c6418b41c7..e10c7ab409 100644 --- a/controllers/challengeMap.js +++ b/controllers/challengeMap.js @@ -52,11 +52,6 @@ module.exports = { if (challenge.challengeType === 4) { return challenge } }); - 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 numberWithCommas(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } diff --git a/controllers/courseware.js b/controllers/courseware.js index a7d2b4af0c..e3119196fb 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -28,6 +28,10 @@ exports.returnNextCourseware = function(req, res, next) { if (!req.user) { return res.redirect('../challenges/learn-how-free-code-camp-works'); } + if (req.user.finishedWaypoints && req.user.uncompletedBonfires.length > 0) { + return res.redirect('../bonfires') + } + var completed = req.user.completedCoursewares.map(function (elem) { return elem._id; }); @@ -247,6 +251,9 @@ exports.completedCourseware = function (req, res, next) { var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; + if (coursewareHash === "bd7139d8c441eddfaeb5bdef") { + req.user.finishedWaypoints = true; + } req.user.completedCoursewares.push({ _id: coursewareHash, diff --git a/models/User.js b/models/User.js index 0ad81d2556..81c66f9061 100644 --- a/models/User.js +++ b/models/User.js @@ -18,6 +18,10 @@ var userSchema = new mongoose.Schema({ github: String, linkedin: String, tokens: Array, + finishedWaypoints: { + type: Boolean, + default: false + }, progressTimestamps: { type: Array, default: [] diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 738c5d8b7e..7bc7dd3abf 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -634,10 +634,8 @@ "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!" + "Pair program as much as possible with different campers until you've completed all the Bonfire 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." ], "challengeType": 2, "tests": [] @@ -681,7 +679,7 @@ "Hint: The relevant documentation about this API call is here: https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel.", "Hint: Here's an array of the Twitch.tv usernames of people who regularly stream coding: [\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"notmichaelmcdonald\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"joe_at_underflow\",\"noobs2ninjas\",\"mdwasp\",\"beohoff\",\"xenocomagain\"]", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -700,7 +698,7 @@ "User Story: As a user, I can click a button to show me a new random quote.", "Bonus User Story: As a user, I can press a button to tweet out a quote.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -723,7 +721,7 @@ "Hint: Get a zipcode's weather (in Kelvin) at http://api.openweathermap.org/data/2.5/weather?q=99705.", "Hint: Get your current user's zipcode (based on their IP address) with this line of jQuery: $.get(\"http://ipinfo.io\", function(response) {}, \"jsonp\");", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -745,7 +743,7 @@ "Bonus User Story: As a user, I can see how many upvotes each story has.", "Hint: Here's the Camper News Hot Stories API endpoint: http://www.freecodecamp.com/stories/hotStories.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -766,7 +764,7 @@ "Bonus User Story:As a user, when I type in the search box, I can see a dropdown with autocomplete options for matching wikipedia entries.", "Hint: Here's an entry on using Wikipedia's API: http://www.mediawiki.org/wiki/API:Main_page.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -786,7 +784,7 @@ "Bonus User Story: As a user, I can reset the clock for my next pomodoro.", "Bonus User Story: As a user, I can customize the length of each pomodoro.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -806,7 +804,7 @@ "Bonus User Story: I can clear the input field with a clear button.", "Bonus User Story: I can keep chaining mathematical operations together until I hit the clear button and it will tell me the correct output.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -827,7 +825,7 @@ "Bonus User Story: As a user, I can choose whether I want to play as X or O.", "Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 3, "tests": [] @@ -909,7 +907,7 @@ "Bonus User Story: As an unauthenticated or authenticated user, I can see the in chart form. (This could be implemented using Chart.js or Google Charts.)", "Bonus User Story: 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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 4, "tests": [] @@ -930,7 +928,7 @@ "User Story: As an authenticated user, I can remove myself from a bar if I no longer want to go there.", "Bonus User Story: As an unauthenticated user, when I login I should not have to search again.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 4, "tests": [] @@ -951,7 +949,7 @@ "User Story: As a user, I can remove stocks.", "Bonus User Story: As a user, I can see changes in real-time when any other user adds or removes a stock.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 4, "tests": [] @@ -972,7 +970,7 @@ "User Story: As an authenticated user, I can update my settings to store my full name, city, and state.", "Bonus User Story: As an authenticated user, I should be able to propose a trade and wait for the other user to accept the trade.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 4, "tests": [] @@ -998,7 +996,7 @@ "Bonus User Story: As an authenticated user, if I don't like the options on a poll I can create a new option.", "Hint: Masonry.js is a library that allows for Pintrest-style image grids.", "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.", - "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text." + "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], "challengeType": 4, "tests": [] diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index e409e3860a..ff2ce4dae3 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -13,8 +13,9 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown - li - a(href='/challenges') Next Challenge + if user + li + a(href='/challenges') Next Challenge li a(href='/map') Map if (user && user.sentSlackInvite) diff --git a/views/resources/twitch.jade b/views/resources/twitch.jade index eb0ea47a5e..653aa9e4d8 100644 --- a/views/resources/twitch.jade +++ b/views/resources/twitch.jade @@ -26,35 +26,31 @@ block content 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") + br .row .col-xs-12 - h2 Here are some of our previous shows (you can full-screen them) - .row + h2 Here are some of our previous shows (you can full-screen them): + .row.negative-20 .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') + iframe.embed-responsive-item(src='//www.youtube.com/embed/YMz_vrK_KlQ') p.wrappable.negative-45 link:   - a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw + a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/YMz_vrK_KlQ .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') + iframe.embed-responsive-item(src='//www.youtube.com/embed/vLcuOanKVMw') p.wrappable.negative-45 link:   - a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 + a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/vLcuOanKVMw .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') + iframe.embed-responsive-item(src='//www.youtube.com/embed/bbFVxaza8Ik') p.wrappable.negative-45 link:   - a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs + a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/bbFVxaza8Ik .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') + iframe.embed-responsive-item(src='//www.youtube.com/embed/6okiEBZ2y-Y') 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 + a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/6okiEBZ2y-Y br br br From 32d1a605ff01e2fe6e6292655e2e92a39e05dc0b Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 02:55:14 -0700 Subject: [PATCH 05/25] fix www redirect to include full path --- app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 5f730fe264..49506a624d 100755 --- a/app.js +++ b/app.js @@ -85,11 +85,12 @@ console.log(process.env.NODE_ENV); if (process.env.NODE_ENV === 'production') { app.all(/.*/, function (req, res, next) { - var host = req.header("host"); + var host = req.header('host'); + var originalUrl = req['originalUrl']; if (host.match(/^www\..*/i)) { next(); } else { - res.redirect(301, "http://www." + host); + res.redirect(301, "http://www." + host + originalUrl); } }); } From 12fbc417613636baa8133147f550e2b31371a980 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 03:04:53 -0700 Subject: [PATCH 06/25] clean up User.js a little --- models/User.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/models/User.js b/models/User.js index 81c66f9061..5874bb2bdb 100644 --- a/models/User.js +++ b/models/User.js @@ -18,10 +18,6 @@ var userSchema = new mongoose.Schema({ github: String, linkedin: String, tokens: Array, - finishedWaypoints: { - type: Boolean, - default: false - }, progressTimestamps: { type: Array, default: [] @@ -144,6 +140,7 @@ var userSchema = new mongoose.Schema({ default: 0 }, needsMigration: { type: Boolean, default: true }, + finishedWaypoints: { type: Boolean, default: false }, challengesHash: {} }); From a8689fec4a97cf9bc0e8facf92a2dc7944773556 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 13:13:50 -0400 Subject: [PATCH 07/25] Refactor sitemap function to use async for for easier reasoning. Factor out the user fetch as this was causing a huge blocking operation and eating a massive amount of memory. --- controllers/resources.js | 329 +++++++++++++++++++++-------------- views/resources/sitemap.jade | 8 - 2 files changed, 202 insertions(+), 135 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index a3f32179c7..8e57651e34 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -21,6 +21,12 @@ var async = require('async'), request = require('request'), R = require('ramda'); +/** + * Cached values + */ +var allBonfireIds, allBonfireNames, allCoursewareIds, allCoursewareNames, + allFieldGuideIds, allFieldGuideNames, allNonprofitNames; + /** * GET / * Resources. @@ -48,53 +54,77 @@ module.exports = { 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); - } + + async.parallel({ + challenges: function (callback) { + Courseware.find({}, function (err, challenges) { + if (err) { + debug('Courseware err: ', err); + callback(err); + } else { + callback(null, challenges); + } + }); + }, + bonfires: function (callback) { + Bonfire.find({}, function (err, bonfires) { + if (err) { + debug('Bonfire err: ', err); + callback(err); + } else { + callback(null, bonfires); + } + }); + }, + stories: function (callback) { Story.find({}, function (err, stories) { if (err) { - debug('User err: ', err); - return next(err); + debug('Story err: ', err); + callback(err); + } else { + callback(null, stories); } - 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 - }); - }); - }); }); - }); - }); - }); + }, + nonprofits: function (callback) { + Nonprofit.find({}, function (err, nonprofits) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + callback(null, nonprofits); + } + }); + }, + fieldGuides: function (callback) { + FieldGuide.find({}, function (err, fieldGuides) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + callback(null, fieldGuides); + } + }); + } + }, function (err, results) { + if (err) { + return next(err); + } else { + setTimeout(function() { + res.header('Content-Type', 'application/xml'); + res.render('resources/sitemap', { + appUrl: appUrl, + now: now, + challenges: results.challenges, + bonfires: results.bonfires, + stories: results.stories, + nonprofits: results.nonprofits, + fieldGuides: results.fieldGuides + }); + }, 0); + } + } + ); }, chat: function chat(req, res) { @@ -161,6 +191,8 @@ module.exports = { debug('User err: ', err); return next(err); } + + // todo is this necessary anymore? User.count({'points': {'$gt': 53}}, function (err, all) { if (err) { debug('User err: ', err); @@ -178,115 +210,154 @@ module.exports = { }, randomPhrase: function() { - var phrases = resources.phrases; - return phrases[Math.floor(Math.random() * phrases.length)]; + return resources.phrases[Math.floor( + Math.random() * resources.phrases.length)]; }, randomVerb: function() { - var verbs = resources.verbs; - return verbs[Math.floor(Math.random() * verbs.length)]; + return resources.verbs[Math.floor( + Math.random() * resources.verbs.length)]; }, randomCompliment: function() { - var compliments = resources.compliments; - return compliments[Math.floor(Math.random() * compliments.length)]; + return resources.compliments[Math.floor( + Math.random() * resources.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; - }); + if (allBonfireIds) { + return allBonfireIds; + } else { + allBonfireIds = 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; + }); + return allBonfireIds; + } }, allFieldGuideIds: function() { - return fieldGuides.map(function(elem) { - return { - _id: elem._id, - } - }) - .map(function(elem) { - return elem._id; - }); + if (allFieldGuideIds) { + return allFieldGuideIds; + } else { + allFieldGuideIds = fieldGuides. + map(function (elem) { + return { + _id: elem._id + }; + }); + return allFieldGuideIds; + } }, 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 - } - }); + if (allBonfireNames) { + return allBonfireNames; + } else { + allBonfireNames = 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 + }; + }); + return allBonfireNames; + } }, allFieldGuideNames: function() { - return fieldGuides.map(function(elem) { - return { - name: elem.name - } - }) + if (allFieldGuideNames) { + return allFieldGuideNames; + } else { + allFieldGuideNames = fieldGuides. + map(function (elem) { + return { + name: elem.name + }; + }); + return allFieldGuideNames; + } }, allNonprofitNames: function() { - return nonprofits.map(function(elem) { - return { - name: elem.name - } - }) + if (allNonprofitNames) { + return allNonprofitNames; + } else { + allNonprofitNames = nonprofits. + map(function (elem) { + return { + name: elem.name + }; + }); + return allNonprofitNames; + } }, 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; - }); + if (allCoursewareIds) { + return allCoursewareIds; + } else { + allCoursewareIds = 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; + }); + return allCoursewareIds; + } }, 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 - }; - }); + if (allCoursewareNames) { + return allCoursewareNames; + } else { + allCoursewareNames = 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 + }; + }); + return allCoursewareNames; + } }, whichEnvironment: function() { @@ -302,8 +373,9 @@ module.exports = { var metaDescription = $("meta[name='description']"); var metaImage = $("meta[property='og:image']"); var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; + var metaTitle = $('title'); var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; - result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; + result.title = metaTitle.text().length < 141 ? metaTitle.text() : metaTitle.text().slice(0, 137) + " ..."; result.image = urlImage; result.description = description; callback(null, result); @@ -314,6 +386,7 @@ module.exports = { })(); }, + // todo analyze this function in detail updateUserStoryPictures: function(userId, picture, username, cb) { var counter = 0, @@ -360,7 +433,9 @@ module.exports = { }); }, foundStories); async.parallel(tasks, function(err) { - if (err) { return cb(err); } + if (err) { + return cb(err); + } cb(); }); } diff --git a/views/resources/sitemap.jade b/views/resources/sitemap.jade index 5c9a2c7b88..c40a4c9cfb 100644 --- a/views/resources/sitemap.jade +++ b/views/resources/sitemap.jade @@ -32,14 +32,6 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") changefreq daily priority= 0.8 - //- Users - each user in users - url - loc #{appUrl}/#{user.profile.username} - lastmod= now - changefreq daily - priority= 0.9 - //- Products each bonfire in bonfires url From 5f6177378f72658c036e427fd9c747ae9372dd16 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 13:15:15 -0400 Subject: [PATCH 08/25] Clean up more linting errors and split out returns from Mongo. --- controllers/bonfire.js | 14 +++++++------- controllers/courseware.js | 16 ++++++++-------- controllers/user.js | 17 +++++++++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index dbe60a0da0..88678099e1 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -63,12 +63,12 @@ exports.returnNextBonfire = function(req, res, next) { var uncompletedBonfires = req.user.uncompletedBonfires; var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); - displayedBonfires.exec(function(err, bonfire) { + displayedBonfires.exec(function(err, bonfireFromMongo) { if (err) { return next(err); } - bonfire = bonfire.pop(); - if (bonfire === undefined) { + var bonfire = bonfireFromMongo.pop(); + if (typeof bonfire === 'undefined') { req.flash('errors', { msg: "It looks like you've completed all the bonfires we have available. Good job!" }); @@ -84,13 +84,13 @@ exports.returnIndividualBonfire = function(req, res, next) { var bonfireName = dashedName.replace(/\-/g, ' '); - Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfire) { + Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfireFromMongo) { if (err) { next(err); } - if (bonfire.length < 1) { + if (bonfireFromMongo.length < 1) { req.flash('errors', { msg: "404: We couldn't find a bonfire with that name. Please double check the name." }); @@ -98,9 +98,9 @@ exports.returnIndividualBonfire = function(req, res, next) { return res.redirect('/bonfires'); } - bonfire = bonfire.pop(); + var bonfire = bonfireFromMongo.pop(); var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../bonfires/' + dashedNameFull); } res.render('bonfire/show', { diff --git a/controllers/courseware.js b/controllers/courseware.js index a7d2b4af0c..3feb22f3d4 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -50,14 +50,14 @@ exports.returnNextCourseware = function(req, res, next) { } courseware = courseware.pop(); - if (courseware === undefined) { + if (typeof 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, '-'); + var nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); return res.redirect('../challenges/' + nameString); }); }; @@ -68,23 +68,23 @@ exports.returnIndividualCourseware = function(req, res, next) { var coursewareName = dashedName.replace(/\-/g, ' '); Courseware.find({'name': new RegExp(coursewareName, 'i')}, - function(err, courseware) { + function(err, coursewareFromMongo) { if (err) { next(err); } // Handle not found - if (courseware.length < 1) { + if (coursewareFromMongo.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(); + var courseware = coursewareFromMongo.pop(); // Redirect to full name if the user only entered a partial var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../challenges/' + dashedNameFull); } @@ -292,7 +292,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { if (isCompletedWith) { var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { + paired.exec(function (err, pairedWithFromMongo) { if (err) { return next(err); } else { @@ -301,7 +301,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); } - pairedWith = pairedWith.pop(); + var pairedWith = pairedWithFromMongo.pop(); req.user.completedCoursewares.push({ _id: coursewareHash, diff --git a/controllers/user.js b/controllers/user.js index 034716ddcb..c00a0a1f6d 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -253,7 +253,7 @@ exports.checkExistingUsername = function(req, res, next) { exports.checkUniqueEmail = function(req, res, next) { User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { if (err) { return next(err); } - if (data == 1) { + if (data === 1) { return res.send(true); } else { return res.send(false); @@ -271,7 +271,7 @@ exports.returnUser = function(req, res, next) { 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 = user[0]; user.progressTimestamps = user.progressTimestamps.sort(function(a, b) { return a - b; @@ -284,6 +284,7 @@ exports.returnUser = function(req, res, next) { 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()) { @@ -305,13 +306,17 @@ exports.returnUser = function(req, res, next) { var data = {}; var progressTimestamps = user.progressTimestamps; - for (var i = 0; i < progressTimestamps.length; i++) { - data[(progressTimestamps[i] / 1000).toString()] = 1; - } + progressTimestamps.forEach(function(timeStamp) { + data[(timeStamp / 1000)] = 1; + }); + + //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 ) { + var challenges = user.completedCoursewares.filter(function ( obj ) { return !!obj.solution; }); res.render('account/show', { From b3929f693b116ac1bb1ddd39edc4124167df17c7 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 13:49:32 -0400 Subject: [PATCH 09/25] Clean up privacy policy routing. Adopt same pattern from courseware/bonfire mongo lookup to fieldguides. --- app.js | 18 +++++++----------- controllers/fieldGuide.js | 10 +++++----- controllers/resources.js | 6 ------ 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/app.js b/app.js index bd36ff727c..aec3f88d4d 100755 --- a/app.js +++ b/app.js @@ -253,12 +253,8 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 })); app.get('/', homeController.index); -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"); + res.redirect(301, '/field-guide/nonprofit-project-instructions'); }); app.get('/chat', resourcesController.chat); @@ -384,17 +380,17 @@ app.post( passportConf.isAuthenticated, contactController.postDoneWithFirst100Hours ); -//app.get( -// '/nonprofit-project-instructions', -// passportConf.isAuthenticated, -// resourcesController.nonprofitProjectInstructions -//); + app.post( '/update-progress', passportConf.isAuthenticated, userController.updateProgress ); +app.get('/privacy', function(req, res) { + res.redirect(301, '/field-guide/the-free-code-camp-privacy-policy'); +}); + app.get('/api/slack', function(req, res) { if (req.user) { if (req.user.email) { @@ -590,7 +586,7 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); app.get('/field-guide/:fieldGuideName', fieldGuideController.returnIndividualFieldGuide); -app.get('/field-guide', fieldGuideController.returnNextFieldGuide); +app.get('/field-guide/', fieldGuideController.returnNextFieldGuide); app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide); diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index d131e6436b..022dbd3d2b 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -9,12 +9,12 @@ exports.returnIndividualFieldGuide = function(req, res, next) { var fieldGuideName = dashedName.replace(/\-/g, ' '); - FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) { + FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuideFromMongo) { if (err) { next(err); } - if (fieldGuide.length < 1) { + if (fieldGuideFromMongo.length < 1) { req.flash('errors', { msg: "404: We couldn't find a field guide entry with that name. Please double check the name." }); @@ -22,9 +22,9 @@ exports.returnIndividualFieldGuide = function(req, res, next) { return res.redirect('/field-guide'); } - fieldGuide = fieldGuide.pop(); + fieldGuide = fieldGuideFromMongo.pop(); var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); } res.render('field-guide/show', { @@ -69,7 +69,7 @@ exports.returnNextFieldGuide = function(req, res, next) { return next(err); } fieldGuide = fieldGuide.pop(); - if (fieldGuide === undefined) { + if (typeof fieldGuide === 'undefined') { req.flash('success', { msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here." }); diff --git a/controllers/resources.js b/controllers/resources.js index 8e57651e34..be29c7145a 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -44,12 +44,6 @@ Array.zip = function(left, right, combinerFunction) { }; 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'); From 62df37072dcc9e03fffdba3356c5395008823a3b Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 13:50:13 -0400 Subject: [PATCH 10/25] prevent global "fieldGuide" variable --- controllers/fieldGuide.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index 022dbd3d2b..5ec46ed979 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -22,7 +22,7 @@ exports.returnIndividualFieldGuide = function(req, res, next) { return res.redirect('/field-guide'); } - fieldGuide = fieldGuideFromMongo.pop(); + var fieldGuide = fieldGuideFromMongo.pop(); var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); From 79ff3631df4c48fa41d92c87ee78977293d3f6a5 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 13:50:48 -0400 Subject: [PATCH 11/25] minor eslint cleanup --- controllers/fieldGuide.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index 5ec46ed979..d4bf9a216d 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -42,7 +42,7 @@ exports.showAllFieldGuides = function(req, res) { if (req.user && req.user.completedFieldGuides) { data.completedFieldGuides = req.user.completedFieldGuides; } else { - data.completedFieldGuides = [] + data.completedFieldGuides = []; } res.send(data); }; From a6d5aa05d045b2ee812684d0b0554e0adc012910 Mon Sep 17 00:00:00 2001 From: benmcmahon100 Date: Fri, 24 Apr 2015 20:48:29 +0100 Subject: [PATCH 12/25] slight tidy and example Replaced one occurrence of gitter with slack and added an example bit torrent client after someone pointed out in the help section that there was not guide to demonstrate how to do so. --- seed_data/field-guides.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seed_data/field-guides.json b/seed_data/field-guides.json index 0d72f30919..74b9db9668 100644 --- a/seed_data/field-guides.json +++ b/seed_data/field-guides.json @@ -338,7 +338,7 @@ "

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.
  • ", + "
  • • Slack 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.
  • ", @@ -354,7 +354,7 @@ "
  • • 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.

", + "

Install a bit torrent client, for example utorrent 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!

", From 096edd553b81cbd121342af13f69ac3e311e3492 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 15:53:22 -0400 Subject: [PATCH 13/25] Factor out $('title') to a re-usable variable to make the linter happy and remove old database lookup based on old challenge architecture. --- controllers/resources.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index be29c7145a..a9edf03480 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -8,7 +8,6 @@ var async = require('async'), 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'), @@ -186,19 +185,11 @@ module.exports = { return next(err); } - // todo is this necessary anymore? - 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 - }); + res.render('resources/learn-to-code', { + title: 'About Free Code Camp', + daysRunning: daysRunning, + c3: numberWithCommas(c3), + announcements: announcements }); }); }, From eaf81fd3f7a0b28517ff11b8b643ec2e125c5bb9 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 16:04:25 -0400 Subject: [PATCH 14/25] Closes #333 --- seed_data/bonfires.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index d3207d38f9..24d2813064 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -644,7 +644,8 @@ "difficulty": "3.12", "description": [ "Fill in the object constructor with the methods specified in the tests.", - "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(), setLastName(), and setFullName().", + "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(first), setLastName(last), and setFullName(firstAndLast).", + "All functions that take an argument have an arity of 1, and the argument will be a string.", "These methods must be the only available means for interacting with the object." ], "challengeSeed": "var Person = function(firstAndLast) {\n return firstAndLast;\r\n};\n\nvar bob = new Person('Bob Ross');\nbob.getFullName();", From 8c10f4efcc5f42410c8eca08a9219bf12d801f7a Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 13:57:50 -0700 Subject: [PATCH 15/25] make improvements to coursewares.json --- seed_data/coursewares.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 7bc7dd3abf..1f71a32e70 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -532,7 +532,7 @@ "_id": "bd7153d8c441eddfaeb5bdff", "name": "Start a Node.js Server", "difficulty": 0.39, - "challengeSeed": "", + "challengeSeed": "114685061", "description": [ "We'll build this Waypoint 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.", @@ -556,7 +556,7 @@ "_id": "bd7153d8c441eddfaeb5bd0f", "name": "Manage Packages with NPM", "difficulty": 0.40, - "challengeSeed": "", + "challengeSeed": "114685061", "description": [ "We'll build this Waypoint 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.", @@ -580,7 +580,7 @@ "_id": "bd7153d8c441eddfaeb5bd1f", "name": "Build Web Apps with Express.js", "difficulty": 0.41, - "challengeSeed": "", + "challengeSeed": "114685061", "description": [ "We'll build this Waypoint 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.", From f79834bec9400adf7734c136a892a7977392fdb8 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 18:57:55 -0400 Subject: [PATCH 16/25] Greatly increase the speed of sitemap by a factor of at least 10 (Nathan, Quincy, and Berkeley pairing). --- controllers/resources.js | 58 +++++++++++++++++++++++++----------- views/resources/sitemap.jade | 19 ++++++++---- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index a9edf03480..d9e9450c6f 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -49,53 +49,77 @@ module.exports = { async.parallel({ + users: function(callback) { + User.aggregate() + .group({_id: 1, usernames: { $addToSet: '$profile.username'}}) + .match({'profile.username': { $ne: ''}}) + .exec(function(err, users) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + callback(null, users[0].usernames); + } + }); + }, + challenges: function (callback) { - Courseware.find({}, function (err, challenges) { - if (err) { - debug('Courseware err: ', err); - callback(err); - } else { - callback(null, challenges); - } - }); + Courseware.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, challenges) { + if (err) { + debug('Courseware err: ', err); + callback(err); + } else { + callback(null, challenges[0].names); + } + }); }, bonfires: function (callback) { - Bonfire.find({}, function (err, bonfires) { + Bonfire.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, bonfires) { if (err) { debug('Bonfire err: ', err); callback(err); } else { - callback(null, bonfires); + callback(null, bonfires[0].names); } }); }, stories: function (callback) { - Story.find({}, function (err, stories) { + Story.aggregate() + .group({_id: 1, links: {$addToSet: '$link'}}) + .exec(function (err, stories) { if (err) { debug('Story err: ', err); callback(err); } else { - callback(null, stories); + callback(null, stories[0].links); } }); }, nonprofits: function (callback) { - Nonprofit.find({}, function (err, nonprofits) { + Nonprofit.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, nonprofits) { if (err) { debug('User err: ', err); callback(err); } else { - callback(null, nonprofits); + callback(null, nonprofits[0].names); } }); }, fieldGuides: function (callback) { - FieldGuide.find({}, function (err, fieldGuides) { + FieldGuide.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, fieldGuides) { if (err) { debug('User err: ', err); callback(err); } else { - callback(null, fieldGuides); + callback(null, fieldGuides[0].names); } }); } @@ -108,6 +132,7 @@ module.exports = { res.render('resources/sitemap', { appUrl: appUrl, now: now, + users: results.users, challenges: results.challenges, bonfires: results.bonfires, stories: results.stories, @@ -371,7 +396,6 @@ module.exports = { })(); }, - // todo analyze this function in detail updateUserStoryPictures: function(userId, picture, username, cb) { var counter = 0, diff --git a/views/resources/sitemap.jade b/views/resources/sitemap.jade index c40a4c9cfb..7d7c7b1c1c 100644 --- a/views/resources/sitemap.jade +++ b/views/resources/sitemap.jade @@ -32,10 +32,19 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") changefreq daily priority= 0.8 + //- User + each user in users + url + loc #{appUrl}/#{user} + lastmod= now + changefreq daily + priority= 0.9 + + //- Products each bonfire in bonfires url - loc #{appUrl}/bonfires/#{bonfire.name.replace(/\s/g, '-')} + loc #{appUrl}/bonfires/#{bonfire.replace(/\s/g, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -43,7 +52,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Challenges each challenge in challenges url - loc #{appUrl}/challenges/#{challenge.name.replace(/\s/g, '-')} + loc #{appUrl}/challenges/#{challenge.replace(/\s/g, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -51,7 +60,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Stories each story in stories url - loc #{appUrl}/stories/#{story.storyLink.replace(/\s/g, '-')} + loc #{appUrl}/stories/#{story.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9 @@ -59,7 +68,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Nonprofit each nonprofit in nonprofits url - loc #{appUrl}/nonprofits/#{nonprofit.name.replace(/\s/g, '-')} + loc #{appUrl}/nonprofits/#{nonprofit.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9 @@ -67,7 +76,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Nonprofit each fieldGuide in fieldGuides url - loc #{appUrl}/field-guide/#{fieldGuide.name.replace(/\s/g, '-')} + loc #{appUrl}/field-guide/#{fieldGuide.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9 From 96d119f22d47372482689380d68041ddc85188c0 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 16:10:11 -0700 Subject: [PATCH 17/25] update gitignore to ignore builtAssets/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a801149c52..9df97d6e25 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lib-cov *.swp .floo .flooignore +builtAssets/ *.env pids From e7371b0a7b412c664d632da2710f7c8fb568313a Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 24 Apr 2015 19:11:17 -0400 Subject: [PATCH 18/25] Configure for production on DO. --- .gitignore | 1 + app.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a801149c52..9df97d6e25 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lib-cov *.swp .floo .flooignore +builtAssets/ *.env pids diff --git a/app.js b/app.js index aec3f88d4d..b8c951494e 100755 --- a/app.js +++ b/app.js @@ -102,6 +102,8 @@ app.use(connectAssets({ path.join(__dirname, 'public/css'), path.join(__dirname, 'public/js') ], + build: false, + buildDir: false, helperContext: app.locals })); app.use(logger('dev')); From e49c9ca87e5a0241fb5aa277169e57ab2025d5d0 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 24 Apr 2015 21:30:22 -0700 Subject: [PATCH 19/25] make some improvements to the field guide articles and move the bonfire styleguide into field guides --- seed_data/bonfireMDNlinks.js | 4 +- seed_data/field-guides.json | 57 +++++--- views/resources/chat.jade | 22 --- views/resources/jquery-exercises.jade | 185 -------------------------- views/resources/learn-to-code.jade | 65 --------- views/resources/styleguide.jade | 26 ---- 6 files changed, 39 insertions(+), 320 deletions(-) delete mode 100644 views/resources/chat.jade delete mode 100644 views/resources/jquery-exercises.jade delete mode 100644 views/resources/learn-to-code.jade delete mode 100644 views/resources/styleguide.jade diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 7038fb414d..65ca1e0d02 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -1,6 +1,6 @@ // MDN Links -/* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links. +/* These links are for Bonfires. Each key/value pair is used to render a Bonfire with appropriate links. The text of the key is what the link text will be, e.g. Global Array Object @@ -67,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/field-guides.json b/seed_data/field-guides.json index 74b9db9668..5b40a05a0c 100644 --- a/seed_data/field-guides.json +++ b/seed_data/field-guides.json @@ -330,8 +330,8 @@ " ", "

", "

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.

", + "

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

", + "

You should also ask questions in your project's Slack 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

", @@ -344,30 +344,12 @@ "
  • • 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, for example utorrent 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.

    ", @@ -485,6 +467,41 @@ "" ] }, + { + "_id": "bd7158d9c451eddfaeb5bded", + "name": "Bonfire Style Guide", + "description": [ + "
    ", + "

    Writing Bonfire challenges is a great way to exercise your own problem solving and testing abilities. Follow this process closely to maximize the chances of us accepting your bonfire.

    ", + "

    ", + "
      ", + "
    1. Fork the Free Code Camp repository and open seed_data/bonfires.json to become familiar with the format of our bonfires.
    2. ", + "
    3. Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.
    4. ", + "
    5. In the terminal, run node seed_data/seed.js. Run gulp. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.
    6. ", + "
    7. Solved your own Bonfire. Confirmed that your tests work as expected and that your instructions are sufficiently clear.
    8. ", + "
    9. Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.
    10. ", + "
    ", + "

    ", + "

    Here is a description of each of the Bonfires' fields.

    ", + "

    Name

    ", + "

    The name of your challenge. It's OK for this to be humorous but it must be brief and relevant to the task.

    ", + "

    Difficulty

    ", + "

    Attempt to rate difficulty compared against existing bonfire challenges. A good proxy for the difficulty of a bonfire is how long it takes you to solve it. For every 15 minutes it takes, increase the difficulty. For example, a one-hour bonfire should probably be a 4.

    ", + "

    Description

    ", + "

    Separate paragraphs with a line break. Only the first paragraph is visible prior to a user before they click the the 'More information' button.

    ", + "

    All necessary information must be included in the first paragraph. Write this first paragraph as succinctly as possible. Subsequent paragraphs should offer hints or details if needed.

    ", + "

    If your subject matter warrants deeper understanding, you may link to Wikipedia.

    ", + "

    Challenge Seed

    ", + "

    This is where you set up what will be in the editor when the camper starts the bonfire.

    ", + "

    Tests

    ", + "

    These tests are what bring your challenge to life. Without them, we cannot confirm the accuracy of a user's submitted answer. Choose your tests wisely.

    ", + "

    Bonfire tests are written using the Chai.js assertion library. Please use the should and expect syntax for end user readability. As an example of what not do to, many of the original Bonfire challenges are written with assert syntax and many of the test cases are difficult to read.

    ", + "

    If your bonfire question has a lot of edge cases, you will need to write many tests for full coverage. If you find yourself writing more tests than you desire, you may consider simplifying the requirements of your bonfire challenge. For difficulty level 1 through 3, you will generally only need 2 to 4 tests.

    ", + "

    MDNlinks

    ", + "

    Take a look at seed_data/bonfireMDNlinks.js. If any of these concepts are relevant to your bonfire, be sure to include them. If you know of an MDN article that isn't linked here, you can add it to the bonfireMDNlinks.js file before adding it to your bonfire.

    ", + "
    " + ] + }, { "_id": "bd7158d9c451eddfaeb5bdef", "name": "The Free Code Camp Privacy Policy", diff --git a/views/resources/chat.jade b/views/resources/chat.jade deleted file mode 100644 index 6850041dc3..0000000000 --- a/views/resources/chat.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layout-wide -block content - h3 - ol.col-md-offset-2 - li Create a GitHub Account  - a(href="http://github.com/join", target='_blank') here - | . - li Click "Login with GitHub" in the chat window below or download the chat room app for   - a(href="https://update.gitter.im/win/latest") Windows - | ,  - a(href="https://update.gitter.im/osx/latest") Mac - | ,  - a(href="http://appstore.com/gitter") iPhone - | ,  or   - a(href="https://play.google.com/store/apps/details?id=im.gitter.gitter&hl=en_GB") Android - |  , or go   - a(href="http://chat.freecodecamp.com") here - | . - li Keep the chat room open while you code so that you can meet friends and ask for help. - .col-xs-12 - .embed-responsive.embed-responsive-16by9.gitter-imbed - iframe(src='https://www.gitter.im/freecodecamp/freecodecamp', frameborder='0', scrolling='no') \ No newline at end of file diff --git a/views/resources/jquery-exercises.jade b/views/resources/jquery-exercises.jade deleted file mode 100644 index 60e470d3bc..0000000000 --- a/views/resources/jquery-exercises.jade +++ /dev/null @@ -1,185 +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 target4 to red.", - 5: "Clone target2 in #location1 so that it also exists in #location2.", - 6: "Remove the target3 element from #location1.", - 7: "Check the following checkboxes using jQuery.", - 8: "Make the text input field read-only.", - 9: "Select 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', function (event) { - currentExercise = $(this).index(); - event.preventDefault(); - refreshEverything(event); - }); - $('#next-exercise').on('click', function (event) { - ++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); - }); - .container - .row - .col-xs-12.col-sm-8 - .jumbotron - .row - .col-xs-12.text-center - img.img-responsive(src='https://s3.amazonaws.com/freecodecamp/logo4.0LG.png', alt='learn to code free at freecodecamp.com') - h1 jQuery Exercises - br - #directions.text-primary - br - .row - .col-xs-6 #location1 - .col-xs-6 #location2 - .row - #location1.col-xs-6.well.jquery-exercises-well - #location2.col-xs-6.well.jquery-exercises-well - #next-exercise.btn.btn-primary.btn-lg.btn-block - | #next-exercise - br - button#solution-button.btn.btn-block.btn-lg.btn-info - | #solution-button - br - .text-center - | Created for - a(href='http://www.FreeCodeCamp.com') Free Code Camp - br - | by - a(href='https://twitter.com/ossia') Quincy Larson - | , - a(href='https://www.twitter.com/terakilobyte') Nathan Leniz - | , - a(href='https://twitter.com/iheartkode') Mark Howard - | and - a(href='https://twitter.com/ryanmalm') Ryan Malm - | . Please - a(href='http://codepen.io/ossia/pen/raVEgN') fork this. - #exercise-directory.col-xs-12.col-sm-4 - .panel.panel-primary - .panel.panel-heading List of Exercises - .panel-body - ol(start='0') - li - a(href='#') Re-enable an element - li - a(href='#') Change an element's parent - li - a(href='#') Change an element's CSS - li - a(href='#') Use jQuery filters to modify even elements - li - a(href='#') Change nth child - li - a(href='#') Clone an element - li - a(href='#') Remove an element - li - a(href='#') Check checkboxes - li - a(href='#') Make text read-only - li - a(href='#') Select an option in a select box - li - a(href='#') Add a CSS class to an element - li - a(href='#') Lookup an element's data attribute - li - a(href='#') Count child elements - li - a(href='#') Show an element and click on it - #hint-modal.modal.fade(tabindex='-1', role='dialog', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header - button.close(type='button', data-dismiss='modal') - span(aria-hidden='true') × - span.sr-only Close - h4#myModalLabel.modal-title Your hint - .modal-body - #hint - #finished-modal.modal.fade(tabindex='-1', role='dialog', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header - button.close(type='button', data-dismiss='modal') - span(aria-hidden='true') × - span.sr-only Close - h4#myModalLabel.modal-title - | Congratulations! You've finished our jQuery exercises! - a(href='http://www.freecodecamp.com/') Go back to Free Code Camp - | and mark this challenge complete. - .modal-body - #hint \ No newline at end of file diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade deleted file mode 100644 index 59b9586ecc..0000000000 --- a/views/resources/learn-to-code.jade +++ /dev/null @@ -1,65 +0,0 @@ -extends ../layout -block content - .col-xs-12.col-sm-12.col-md-12 - .panel.panel-info - .panel-heading.text-center - h1 About Free Code Camp - .panel-body - .row.text-center - .col-xs-12 - if (Math.random() > 0.99) - 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') - .col-xs-12.col-md-8.col-md-offset-2 - h2.text-center - span.text-primary #{c3}   - | campers have joined our community since we launched   - span.text-primary #{daysRunning}   - | days ago. - .row.text-center - .col-xs-12.col-md-8.col-md-offset-2 - if (user) - if (!user.sentSlackInvite) - a.btn.btn-cta.signup-btn.next-challenge-button.btn-block(href="/challenges") Take me to my next challenge - .spacer - a.btn.btn-primary.btn-cta.next-challenge-button.btn-block(href="/api/slack") Join our Slack Chat Room - else - a.btn.btn-cta.signup-btn.next-challenge-button.btn-block(href="/signin") Start learning to code (it's free) - br - script. - var challengeName = 'Learn to code' - .row - .col-xs-12.col-md-8.col-md-offset-2 - for announcement in announcements - h2 - if (announcement.length > 1) - a(href=announcement[1])= announcement[0] - else - = announcement[0] - a.twitter-timeline(data-dnt='true', href='https://twitter.com/FreeCodeCamp', data-widget-id='560847186548621312') Tweets by @FreeCodeCamp - script. - !function (d, s, id) { - var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; - if (!d.getElementById(id)) { - js = d.createElement(s); - js.id = id; - js.src = p + "://platform.twitter.com/widgets.js"; - fjs.parentNode.insertBefore(js, fjs); - } - }(document, "script", "twitter-wjs"); - h3 - .col-xs-12 Follow us on Twitter here: - .col-xs-12.github-and-twitter-button-text - html. - - - .col-xs-12.github-and-twitter-button-text Star us on GitHub here: - .col-xs-12.github-and-twitter-button-text - html. - diff --git a/views/resources/styleguide.jade b/views/resources/styleguide.jade deleted file mode 100644 index 71303aa4b1..0000000000 --- a/views/resources/styleguide.jade +++ /dev/null @@ -1,26 +0,0 @@ -extends ../layout -block content - .jumbotron.text-left - h1.hug-top.text-center Bonfire Style Guide - h3 Writing Bonfire challenges is a great way to exercise your own problem solving and testing abilities. It is a simple three step process. - h4 - ol - li Fill out the generator form and test your challenge:  - a(href="http://www.freecodecamp.com/bonfire-challenge-generator") http://www.freecodecamp.com/bonfire-challenge-generator - li Once you have confirmed a working bonfire challenge in the generator, copy and paste the the fields into the JSON generator: http://www.freecodecamp.com/bonfire-json-generator - li Copy the JSON, fork the freecodecamp repository, and submit a pull request with your addition to the bonfires.json:  - a(href="https://github.com/FreeCodeCamp/freecodecamp/blob/master/seed_data/bonfires.json") https://github.com/FreeCodeCamp/freecodecamp/blob/master/seed_data/bonfires.json - h3 Name - p Name your challenge - h3 Difficulty - p Attempt to rate difficulty compared against existing bonfire challenges. - h3 Description - p Separate paragraphs with a line break. Only the first paragraph is visible prior to a user clicking the "More information" button. - p All necessary information must be included in the first paragraph. Write this first paragraph as succinct as possible. Subsequent paragraphs should offer hints or details if needed. - p If your subject matter warrants deeper understanding, you may link to Wikipedia. - h3 Challenge Seed - p This is where you set up what will be in the editor when the camper starts the bonfire. - h3 Tests - p These tests are what bring your challenge to life. Without them, we cannot confirm the accuracy of a user's submitted answer. Choose your tests wisely. - p Bonfire tests are written using the Chai.js assertion library. Please use the should and expect syntax for end user readability. As an example of what not do to, many of the original Bonfire challenges are written with assert syntax and many of the test cases are difficult to read. - p If your bonfire question has a lot of edge cases, you will need to write many tests for full coverage. If you find yourself writing more tests than you desire, you may consider simplifying the requirements of your bonfire challenge. For difficulty level 1 through 3, you will generally only need 2 to 4 tests. \ No newline at end of file From 153970861346f3f18c9d0b35b60cefbd02cfab1a Mon Sep 17 00:00:00 2001 From: Maxim Orlov Date: Sat, 25 Apr 2015 16:05:36 +0200 Subject: [PATCH 20/25] Fix Camper News email notification logic --- controllers/story.js | 73 +++++++++++++++++++------------------ public/js/main.js | 2 + views/stories/comments.jade | 1 + views/stories/show.jade | 1 + 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 9047279e3f..9803a7afa0 100755 --- a/controllers/story.js +++ b/controllers/story.js @@ -155,7 +155,7 @@ exports.returnIndividualStory = function(req, res, next) { title: story.headline, link: story.link, originalStoryLink: dashedName, - originalStoryAuthorEmail: story.author.email || "", + originalStoryAuthorEmail: story.author.email || '', author: story.author, description: story.description, rank: story.upVotes.length, @@ -398,7 +398,7 @@ exports.storySubmission = function(req, res, next) { var comment = new Comment({ associatedPost: data.associatedPost, originalStoryLink: data.originalStoryLink, - originalStoryAuthorEmail: req.user.email, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, body: sanitizedBody, rank: 0, upvotes: 0, @@ -496,53 +496,54 @@ exports.storySubmission = function(req, res, next) { return next(err); } try { - Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { + // Based on the context retrieve the parent object of the comment (Story/Comment) + Context.find({'_id': data.associatedPost}, function (err, associatedContext) { if (err) { return next(err); } - associatedStory = associatedStory.pop(); - if (associatedStory) { - associatedStory.comments.push(data._id); - associatedStory.save(function (err) { + associatedContext = associatedContext.pop(); + if (associatedContext) { + associatedContext.comments.push(data._id); + associatedContext.save(function (err) { if (err) { return next(err); } res.send(true); }); } - User.findOne({'profile.username': associatedStory.author.username}, function(err, recipient) { + // Find the author of the parent object + User.findOne({'profile.username': associatedContext.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; + // If the emails of both authors differ, only then proceed with email notification + if (data.author.email && (data.author.email !== recipient.email)) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: data.author.username + ' replied to your post on Camper News', + text: [ + 'Just a quick heads-up: ' + data.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/' + data.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + + transporter.sendMail(mailOptions, function (err) { + if (err) { + return err; + } + }); } - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - 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) { diff --git a/public/js/main.js b/public/js/main.js index ee6c9e1cb8..4525ca7a72 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -287,6 +287,8 @@ $(document).ready(function() { { data: { associatedPost: storyId, + originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: data } }) diff --git a/views/stories/comments.jade b/views/stories/comments.jade index 07412b59be..425d52890d 100755 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -108,6 +108,7 @@ data: { associatedPost: commentId, originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: $('#comment-to-comment-textinput').val(), } }) diff --git a/views/stories/show.jade b/views/stories/show.jade index edb1e3c6b8..1e04c3a34f 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -2,6 +2,7 @@ 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)}; From a9feb269f6946302da34be9dcc7d60eab72e1dee Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:28:14 -0400 Subject: [PATCH 21/25] Remove some debug/console.log statements. Fix allFieldGuideIds method in resources.js to return an array of _ids, not an array of objects. Field guide controller now correctly completes field guides. Closes #367. --- app.js | 1 - controllers/bonfire.js | 1 - controllers/fieldGuide.js | 11 +++++------ controllers/nonprofits.js | 1 - controllers/resources.js | 4 +--- controllers/story.js | 9 +++++---- public/js/main.js | 2 -- 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app.js b/app.js index 1c92c7d9f1..30df332f22 100755 --- a/app.js +++ b/app.js @@ -81,7 +81,6 @@ app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); -console.log(process.env.NODE_ENV); if (process.env.NODE_ENV === 'production') { app.all(/.*/, function (req, res, next) { diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 88678099e1..68f22d9532 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -326,7 +326,6 @@ exports.completedBonfire = function (req, res, next) { return next(err); } if (user) { - debug('Saving user'); res.send(true); } }); diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index d4bf9a216d..65b87d3931 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -22,7 +22,7 @@ exports.returnIndividualFieldGuide = function(req, res, next) { return res.redirect('/field-guide'); } - var fieldGuide = fieldGuideFromMongo.pop(); + var fieldGuide = R.head(fieldGuideFromMongo); var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); @@ -54,20 +54,20 @@ exports.returnNextFieldGuide = function(req, res, next) { var completed = req.user.completedFieldGuides; - req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { + var uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { if (completed.indexOf(elem) === -1) { return elem; } }); + req.user.uncompletedFieldGuides = uncompletedFieldGuides; 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[0]); fieldGuide = fieldGuide.pop(); if (typeof fieldGuide === 'undefined') { req.flash('success', { @@ -81,14 +81,13 @@ exports.returnNextFieldGuide = function(req, res, next) { }; 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.progressTimestamps.push(Date.now()); req.user.uncompletedFieldGuides.splice(index, 1); } diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index a7871327a3..d3b824cf3e 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -132,7 +132,6 @@ exports.returnIndividualNonprofit = function(req, res, next) { var hasShownInterest = nonprofit.interestedCampers.filter(function ( obj ) { return obj.username === req.user.profile.username; }); - console.log(hasShownInterest); if (hasShownInterest.length === 0) { buttonActive = true; } diff --git a/controllers/resources.js b/controllers/resources.js index d9e9450c6f..29e38c67c8 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -261,9 +261,7 @@ module.exports = { } else { allFieldGuideIds = fieldGuides. map(function (elem) { - return { - _id: elem._id - }; + return elem._id; }); return allFieldGuideIds; } diff --git a/controllers/story.js b/controllers/story.js index 9047279e3f..379222d5a5 100755 --- a/controllers/story.js +++ b/controllers/story.js @@ -118,7 +118,7 @@ exports.preSubmit = function(req, res) { exports.returnIndividualStory = function(req, res, next) { var dashedName = req.params.storyName; - var storyName = dashedName.replace(/\-/g, ' '); + var storyName = dashedName.replace(/\-/g, ' ').trim(); Story.find({'storyLink': storyName}, function(err, story) { if (err) { @@ -321,9 +321,10 @@ exports.storySubmission = function(req, res, next) { .replace(/\'/g, '') .replace(/\"/g, '') .replace(/,/g, '') - .replace(/[^a-z0-9]/gi, ' ') .replace(/\s+/g, ' ') - .toLowerCase(); + .replace(/[^a-z0-9\s]/gi, '') + .toLowerCase() + .trim(); var link = data.link; if (link.search(/^https?:\/\//g) === -1) { link = 'http://' + link; @@ -334,7 +335,7 @@ exports.storySubmission = function(req, res, next) { } // if duplicate storyLink add unique number - storyLink = (storyCount == 0) ? storyLink : storyLink + ' ' + storyCount; + storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; var link = data.link; if (link.search(/^https?:\/\//g) === -1) { diff --git a/public/js/main.js b/public/js/main.js index ee6c9e1cb8..d57425e6eb 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -100,7 +100,6 @@ $(document).ready(function() { }); $('.next-field-guide-button').on('click', function() { - console.log('click'); var fieldGuideId = $('#fieldGuideId').text(); completedFieldGuide(fieldGuideId); }); @@ -126,7 +125,6 @@ $(document).ready(function() { }); $('#next-courseware-button').on('click', function() { - console.log(passedCoursewareHash); if ($('.signup-btn-nav').length < 1) { switch (challengeType) { case 0: From 2fce595e8979eef2bbc6498ace4069b95a15ca70 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:40:07 -0400 Subject: [PATCH 22/25] Add story cleanup script to ensure all story links are proper. --- seed_data/storyCleanup.js | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 seed_data/storyCleanup.js diff --git a/seed_data/storyCleanup.js b/seed_data/storyCleanup.js new file mode 100644 index 0000000000..02d24009dd --- /dev/null +++ b/seed_data/storyCleanup.js @@ -0,0 +1,50 @@ +/** + * Created by nathanleniz on 4/25/15. + */ +require('dotenv').load(); +var mongodb = require('mongodb'), + Story = require('../models/Story.js'), + secrets = require('../config/secrets'); + mongoose = require('mongoose'); + +mongoose.connect(secrets.db); + +function storyLinkCleanup(cb) { + console.log('headLineCleanup'); + var i = 1; + var stream = Story.find({}).skip(0).limit(0).batchSize(20000).stream(); + + stream.on('data', function (story) { + console.log(i++); + this.pause(); + story.storyLink = story.storyLink. + replace(/\'/g, ''). + replace(/\"/g, ''). + replace(/,/g, ''). + replace(/\s+/g, ' '). + replace(/[^a-z0-9\s]/gi, ''). + toLowerCase(). + trim(); + story.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 done() { + console.log('Migration script has completed'); + process.exit(0); +} + + +storyLinkCleanup(done); From 7c8e5e60f4b7f300c3db5fbe9f96b5350fb1e99b Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:44:34 -0400 Subject: [PATCH 23/25] Modification to script. --- seed_data/storyCleanup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/storyCleanup.js b/seed_data/storyCleanup.js index 02d24009dd..645ff84206 100644 --- a/seed_data/storyCleanup.js +++ b/seed_data/storyCleanup.js @@ -33,7 +33,7 @@ function storyLinkCleanup(cb) { }.bind(this)); }) .on('error', function (err) { - console.log(err); + console.error(err); }).on('close', function () { console.log('done with set'); stream.destroy(); From ce84f3eeb10f9bd1c9524d367af40678b6544bbf Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 14:36:34 -0400 Subject: [PATCH 24/25] Fully remove remnant of errant debug statement --- controllers/fieldGuide.js | 1 - 1 file changed, 1 deletion(-) diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index 65b87d3931..51f01e8529 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -67,7 +67,6 @@ exports.returnNextFieldGuide = function(req, res, next) { if (err) { return next(err); } - fieldGuide, fieldGuide[0]); fieldGuide = fieldGuide.pop(); if (typeof fieldGuide === 'undefined') { req.flash('success', { From b8bffae47b11324f786cf99ff82880847fcf6884 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 25 Apr 2015 18:59:15 -0700 Subject: [PATCH 25/25] @terakilobyte cleaned up event handlers --- public/js/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/js/main.js b/public/js/main.js index 6c6fc41521..5ce2c1d9bb 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -277,7 +277,7 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); var commentSubmitButtonHandler = function commentSubmitButtonHandler() { - $('comment-button').unbind('click'); + $('#comment-button').unbind('click'); var data = $('#comment-box').val(); $('#comment-button').attr('disabled', 'disabled'); @@ -292,6 +292,7 @@ $(document).ready(function() { }) .fail(function (xhr, textStatus, errorThrown) { $('#comment-button').attr('disabled', false); + $('#comment-button').bind('click', commentSubmitButtonHandler); }) .done(function (data, textStatus, xhr) { window.location.reload();