From 0eb15269bef5b45ca9b77fbc7cba98fefb832f43 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Mar 2015 16:48:48 -0700 Subject: [PATCH 01/57] create zipline model --- models/Zipline.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 models/Zipline.js diff --git a/models/Zipline.js b/models/Zipline.js new file mode 100644 index 0000000000..f87cf11033 --- /dev/null +++ b/models/Zipline.js @@ -0,0 +1,21 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +/** + * + * @type {exports.Schema} + */ + + +var zipLineSchema = new mongoose.Schema({ + name: { + type: String, + unique: true + }, + picture: String, + gitHubLink: String, + demoLink: String, + description: Array, +}); + +module.exports = mongoose.model('Zipline', zipLineSchema); From 91edf73965a58bc34d1b7656e672e300c6633423 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Mar 2015 17:23:13 -0700 Subject: [PATCH 02/57] start building zipline views and controller --- controllers/zipLine.js | 161 ++++++++++++++++++++++++++++++++++++++ models/Zipline.js | 7 +- seed_data/comments.json | 3 - views/ziplines/index.jade | 0 views/ziplines/show.jade | 50 ++++++++++++ 5 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 controllers/zipLine.js delete mode 100644 seed_data/comments.json create mode 100644 views/ziplines/index.jade create mode 100644 views/ziplines/show.jade diff --git a/controllers/zipLine.js b/controllers/zipLine.js new file mode 100644 index 0000000000..53c5510971 --- /dev/null +++ b/controllers/zipLine.js @@ -0,0 +1,161 @@ +var _ = require('lodash'), + debug = require('debug')('freecc:cntr:zipline'), + Zipline = require('./../models/Zipline'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'); + +/** +* Bonfire controller +*/ + +exports.showAllZiplines = function(req, res) { + var completedZiplines = req.user.completedZiplines.map(function(elem) { + return elem._id; + }); + + var noDuplicateZiplines = R.uniq(completedZiplines); + var data = {}; + data.ziplineList = resources.allZiplineNames(); + data.completedList = noDuplicateZiplines; + res.send(data); +}; + +exports.index = function(req, res) { + res.render('ziplines/show.jade', { + completedWith: null, + title: 'Choose Your Zipline', + name: 'Choose Your Zipline', + difficulty: 0, + //cc: req.user ? req.user.bonfiresHash : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliments: resources.randomCompliment(), + ziplines: [] + //ziplineHash: 'test' + }); +}; + +exports.returnIndividualZipline = function(req, res, next) { + var dashedName = req.params.ziplineName; + + ziplineName = dashedName.replace(/\-/g, ' '); + + Zipline.find({"name" : new RegExp(ziplineName, 'i')}, function(err, zipline) { + if (err) { + next(err); + } + + + if (zipline.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a bonfire with that name. Please double check the name." + }); + + return res.redirect('/ziplines'); + } + + zipline = zipline.pop(); + var dashedNameFull = zipline.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../ziplines/' + dashedNameFull); + } + + res.render('ziplines/show', { + completedWith: null, + title: zipline.name, + dashedName: dashedName, + name: zipline.name, + difficulty: Math.floor(+zipline.difficulty), + details: zipline.details, + tests: zipline.tests, + challengeSeed: zipline.challengeSeed, + //cc: !!req.user, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + ziplines: zipline + //ziplineHash: zipline._id + + }); + }); +}; + +exports.completedZipline = function (req, res) { + var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; + var isCompletedDate = Math.round(+new Date() / 1000); + //var ziplineHash = req.body.bonfireInfo.bonfireHash; + + if (isCompletedWith) { + var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return err; + } else { + //var index = req.user.uncompletedZiplines.indexOf(ziplineHash); + //if (index > -1) { + // req.user.progressTimestamps.push(Date.now() / 1000 | 0); + // req.user.uncompletedZiplines.splice(index, 1) + //} + //pairedWith = pairedWith.pop(); + // + //index = pairedWith.uncompletedZiplines.indexOf(bonfiHash); + //if (index > -1) { + // pairedWith.progressTimestamps.push(Date.now() / 1000 | 0); + // pairedWith.uncompletedZiplines.splice(index, 1); + // + //} + // + //pairedWith.completedBonfires.push({ + // _id: bonfireHash, + // completedWith: req.user._id, + // completedDate: isCompletedDate, + // solution: isSolution + //}); + // + //req.user.completedBonfires.push({ + // _id: bonfireHash, + // completedWith: pairedWith._id, + // completedDate: isCompletedDate, + // solution: isSolution + //}) + // + //req.user.save(function (err, user) { + // pairedWith.save(function (err, paired) { + // if (err) { + // throw err; + // } + // if (user && paired) { + // res.send(true); + // } + // }) + //}); + } + }) + } else { + + //req.user.completedBonfires.push({ + // _id: bonfireHash, + // completedWith: null, + // completedDate: isCompletedDate, + // solution: isSolution + //}); + // + //var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + //if (index > -1) { + // req.user.progressTimestamps.push(Date.now() / 1000 | 0); + // req.user.uncompletedBonfires.splice(index, 1) + //} + // + //req.user.save(function (err, user) { + // if (err) { + // throw err; + // } + // if (user) { + // debug('Saving user'); + // res.send(true) + // } + //}); + } +}; \ No newline at end of file diff --git a/models/Zipline.js b/models/Zipline.js index f87cf11033..cfb23420e9 100644 --- a/models/Zipline.js +++ b/models/Zipline.js @@ -7,15 +7,16 @@ var secrets = require('../config/secrets'); */ -var zipLineSchema = new mongoose.Schema({ +var ziplineSchema = new mongoose.Schema({ name: { type: String, unique: true }, picture: String, + video: String, gitHubLink: String, demoLink: String, - description: Array, + details: Array }); -module.exports = mongoose.model('Zipline', zipLineSchema); +module.exports = mongoose.model('Zipline', ziplineSchema); diff --git a/seed_data/comments.json b/seed_data/comments.json deleted file mode 100644 index c44dc44f37..0000000000 --- a/seed_data/comments.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - -] \ No newline at end of file diff --git a/views/ziplines/index.jade b/views/ziplines/index.jade new file mode 100644 index 0000000000..e69de29bb2 diff --git a/views/ziplines/show.jade b/views/ziplines/show.jade new file mode 100644 index 0000000000..fbc786d94f --- /dev/null +++ b/views/ziplines/show.jade @@ -0,0 +1,50 @@ +extends ../layout-wide +block content + .row + .col-xs-12.col-sm-12.col-md-4.bonfire-top + h1.text-center= name + .well + h4 + ol + for step in details + li!= step + .col-xs-12.col-sm-12.col-md-8 + .embed-responsive.embed-responsive-16by9 + iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') + br + - if (cc) + a.btn.btn-primary.btn-lg.btn-block#completed-zipline I've completed this Zipline (ctrl + enter) + script. + var userLoggedIn = true; + - else + a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + script. + var userLoggedIn = false; + br + script(type="text/javascript"). + var tests = !{JSON.stringify(tests)}; + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; + var started = Math.floor(Date.now() / 1000); + #complete-courseware-dialog.modal(tabindex='-1') + .modal-dialog.animated.zoomIn.fast-animation + .modal-content + .modal-header.challenge-list-header= compliment + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body(ng-controller="pairedWithController") + .text-center + .animated.zoomInDown.delay-half + span.completion-icon.ion-checkmark-circled.text-primary + - if (cc) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) Go to my next challenge (ctrl + enter) + - if (points && points > 2) + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") + i.fa.fa-twitter   + = phrase + - else + a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + h1 #{name} + script. + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file From f2f8d93f9f1ad4379c6d40dec9831b5412cafdc2 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Mar 2015 22:49:31 -0700 Subject: [PATCH 03/57] further work on ziplines --- controllers/resources.js | 1 - seed_data/challenges.json | 663 -------------------------------------- seed_data/ziplines.json | 10 + 3 files changed, 10 insertions(+), 664 deletions(-) delete mode 100644 seed_data/challenges.json create mode 100644 seed_data/ziplines.json diff --git a/controllers/resources.js b/controllers/resources.js index 51a2a6201b..a4a7ca6feb 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -349,7 +349,6 @@ module.exports = { return process.env.NODE_ENV; }, getURLTitle: function(url, callback) { - debug('got url in meta scraping function', url); (function () { var result = {title: '', image: '', url: '', description: ''}; request(url, function (error, response, body) { diff --git a/seed_data/challenges.json b/seed_data/challenges.json deleted file mode 100644 index 826dc92cd1..0000000000 --- a/seed_data/challenges.json +++ /dev/null @@ -1,663 +0,0 @@ -[ - { - "name": "Learn how Free Code Camp Works", - "time": 2, - "video": "114486344", - "challengeNumber": 0, - "steps": [ - "Watch this 1-minute video, or simply read this summary:", - "Welcome to Free Code Camp. We're a community of busy people learning to code.", - "We built this community because learning to code is hard. But anyone who can stay motivated can learn to code. And the best way to stay motivated is to code with friends.", - "To maximize accessibility, all our challenges are self-paced, browser-based, and free.", - "All of us start with the same 100 hours of interactive coding challenges. These cover Computer Science and databases. They also cover in-demand JavaScript tools like jQuery, Node.js and MongoDB.", - "Once we have a basic understanding of web development, we'll spend another 900 hours putting that theory into practice. We'll build full stack solutions for nonprofits.", - "By the end of this process, we'll be good at coding. We'll have a portfolio of apps with happy users to prove it. We'll also have an alumni network of fellow coders and nonprofits ready to serve as references.", - "If you make it through Free Code Camp, you will be able to get a coding job. There are far more job openings out there than there are qualified coders to fill them.", - "Also, for every pure coding job, there are at least 5 additional jobs that require some coding skills. So even if you decide not to pursue coding as a career, you'll still walk away with a valuable job skill.", - "There are 3 keys to succeeding in our community: do the challenges, make friends, and find a routine.", - "Now it's time to join our chatroom. Click the \"I've completed this challenge\" button to move on to your next challenge." - ] - }, - { - "name": "Join Our Chat Room", - "time": 5, - "video": "114627322", - "challengeNumber": 1, - "steps": [ - "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper who's on the same challenge as you and wants to pair program.", - "If you don't already have a GitHub account, create one real quick at https://www.github.com.", - "Be sure to update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.", - "Now enter the chat room by going to https://gitter.im/FreeCodeCamp/FreeCodeCamp and clicking the \"sign in with GitHub\" button.", - "Introduce yourself to our chat room by typing: \"hello world!\".", - "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", - "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", - "Now that you've completed this challenge, you can go directly your most-recently visited chat room by clicking the \"Chat\" button in the navigation bar above." - ] - }, - { - "name": "Join Our Forum (currently disabled)", - "time": 5, - "video": "115275066", - "challengeNumber": 2, - "steps": [ - "Go to Free Code Camp's forum: http://forum.freecodecamp.com.", - "You can come here to share and discuss coding resources, ask questions to our entire community, and coordinate local Free Code Camp events.", - "Our chat room is great for realtime discussions, but our forum is ideal for longer-term discussions and open-ended questions.", - "Sign in with the Github account you created during Challenge 1.", - "Click on the \"Introduce yourself here\" discussion.", - "Here you can read through other Free Code Camp community members' self introductions.", - "Go ahead and type a brief self introduction of your own.", - "Click on the \"Categories\" drop-down menu. You should see a category called \"Local Chapters\". Click that. If your city isn't already on the list, create a topic for it. Otherwise, introduce yourself to the other campers from your city.", - "Come back here daily to ask questions, engage in discussions, and share links to helpful coding tools.", - "Now that you've completed this challenge, you can go directly to the forum by clicking the \"Forum\" button in the navigation bar above." - ] - }, - { - "name": "Build a Personal Website", - "time": 60, - "video": "114627406", - "challengeNumber": 3, - "steps": [ - "There are tons of interactive HTML and CSS tutorials out there, but Nathan Bashaw and Christine Bower's Dash tutorials - which they built for General Assembly - are our favorite.", - "Go to https://dash.generalassemb.ly/projects/annas-website-1 and get started with your first project."] - }, - { - "name": "Build a Responsive Blog Theme", - "time": 60, - "video": "114578441", - "challengeNumber": 4, - "steps": [ - "Next, let's learn about responsive web design and continue learning about HTML and CSS.", - "A responsive website will automatically adapt to changes in your browser's width. This means that you can make one version of a website that will look good on desktop, tablet and phone.", - "Later, we'll use Twitter's Bootstrap CSS framework to build responsive websites.", - "You can check it out here: http://getbootstrap.com/.", - "Go to https://dash.generalassemb.ly/projects/jeffs-blog-1 and complete the second project." - ] - }, - { - "name": "Build a Small Business Website", - "time": 60, - "video": "114578438", - "challengeNumber": 5, - "steps": [ - "Ready for some more HTML and CSS fundamentals?", - "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project."] - }, - { - "name": "Tweak HTML and CSS in CodePen", - "time": 10, - "video": "110752744", - "challengeNumber": 6, - "steps": [ - "Now we're going to learn how to use a tool called CodePen, which lets you experiment with HTML and CSS, and even create single-page web applications, right in your browser!", - "Go to http://www.newsweek.com/", - "Change the window size. Note that Newsweek.com is using Responsive Design.", - "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", - "Select all the text, then copy it.", - "Go to http://codepen.io/pen/", - "Paste the HTML you copied from Newsweek.com into the HTML field of CodePen.", - "You now have your own customizable version of the Newsweek.com website. See if you can change some of the text and images." - ] - }, - { - "name": "Build a CSS Robot", - "time": 60, - "video": "114578436", - "challengeNumber": 7, - "steps": [ - "Now let's learn some more CSS, and a small amount of a JavaScript-based tool called jQuery.", - "Go to https://dash.generalassemb.ly/projects/cotbots-1 and complete the fourth project."] - }, - { - "name": "Get Started with jQuery", - "time": 30, - "video": "114578435", - "challengeNumber": 8, - "steps": [ - "jQuery is a powerful tool for manipulating HTML elements.", - "It's a lot easier to use than JavaScript itself, so we'll learn it first.", - "It's also extremely popular with employers, so we're going to learn it well.", - "Code School has an excellent free course that will walk us through the basics of jQuery.", - "Go to http://try.jquery.com/levels/1/challenges/1 and complete the first section." - ] - }, - { - "name": "Traverse the DOM", - "time": 30, - "video": "114591805", - "challengeNumber": 9, - "steps": [ - "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", - "Go to http://try.jquery.com/levels/2/challenges/1 and complete the second section." - ] - }, - { - "name": "Work with the DOM", - "time": 30, - "video": "114591804", - "challengeNumber": 10, - "steps": [ - "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", - "Go to http://try.jquery.com/levels/3/challenges/1 and complete the third section." - ] - }, - { - "name": "Listen for DOM Events", - "time": 30, - "video": "114591802", - "challengeNumber": 11, - "steps": [ - "Now let's learn how to use jQuery Listeners. These will \"listen\" for something to happen, and then trigger a subsequent event", - "Go to http://try.jquery.com/levels/4/challenges/1 and complete the fourth section." - ] - }, - { - "name": "Use jQuery for Styling", - "time": 30, - "video": "114591801", - "challengeNumber": 12, - "steps": [ - "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", - "Go to http://try.jquery.com/levels/5/challenges/1 and complete the fifth section." - ] - }, - { - "name": "Build a MadLibs Game", - "time": 60, - "video": "114591799", - "challengeNumber": 13, - "steps": [ - "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", - "If you aren't familiar with Mad Libs, they basically involve inserting random nouns, adjectives and verbs into stories. The stories that result are often hilarious.", - "Go to https://dash.generalassemb.ly/projects/mad-libs-1 and complete the fifth project." - ] - }, - { - "name": "Discover Chrome's DevTools", - "time": 90, - "video": "110752743", - "challengeNumber": 14, - "steps": [ - "It's time to learn the most powerful tool your browser has - the Development Tools!", - "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", - "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", - "Go to http://discover-devtools.codeschool.com and complete this short course." - ] - }, - { - "name": "Tackle jQuery Exercises", - "time": 60, - "video": "113173612", - "challengeNumber": 15, - "steps": [ - "We've built some special jQuery challenges to help you reinforce your knowledge of this fundamental skill.", - "There are many correct ways to solve each of these exercises. After you complete the challenge, you can compare your solution with our solution by pressing the \"#solution-button\" button.", - "Go to http://freecodecamp.com/jquery-exercises and complete the exercises." - ] - }, - { - "name": "Customize Bootstrap", - "time": 15, - "video": "110752741", - "challengeNumber": 16, - "steps": [ - "Let's learn a little more about Twitter's responsive CSS framework, Bootstrap, and how we can add some custom themes to it.", - "Go to http://getbootstrap.com/components/", - "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", - "Select all the text, then copy it.", - "Go to http://codepen.io/pen/", - "Paste the HTML you copied from GetBootStrap.com into the HTML field of CodePen.", - "Go to http://bootswatch.com/", - "Decide which theme you want to use.", - "Click the down arrow next to the download button and choose 'bootstrap.css'.", - "Select all the text, then copy it.", - "Go back to CodePen and paste the CSS you copied from Bootswatch.com into the CSS field of CodePen.", - "Your Bootswatch CSS should now be applied to the HTML from the GetBootStrap page.", - "This page is currently using a two-column layout, with the main content on the left and additional navigation on the right. See if you can make it a one-column layout." - ] - }, - { - "name": "Inject Animation into CSS", - "time": 15, - "video": "110752740", - "challengeNumber": 17, - "steps": [ - "You may have noticed some sites have cool animations. Actually, animating DOM elements is pretty straightforward if you use a CSS library called Animate.css.", - "Go to http://daneden.github.io/animate.css/ and try out some of the CSS animations.", - "Go to http://codepen.io/ossia/pen/bGegt.", - "Press the \"Fork\" button. This will fork, meaning create a copy of, the CodePen.", - "Click the gear in the CSS column.", - "Click \"Add another resource\" and start typing \"animate.css\". Click on the dropdown results to autocomplete it.", - "Now that you have Animate.css enabled, use jQuery and the \"toggleClass\" method to add an animated class to all h1 elements when you click the \"Press Me\" button." - ] - }, - { - "name": "Learn Basic Computer Science", - "time": 120, - "video": "114628241", - "challengeNumber": 18, - "steps": [ - "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", - "Note that Harvard also has an excellent introduction to computer science course called CS50, but it takes more than 100 hours to complete, and doesn't use JavaScript.", - "Despite being completely self-paced, Stanford's CS101 course is broken up into weeks. Each of the following challenges will address one of those weeks.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ and complete the first week's course work." - ] - }, - { - "name": "Learn Loops", - "time": 120, - "video": "114597348", - "challengeNumber": 19, - "steps": [ - "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", - "This will introduce us to loops, a fundamental feature of every programming language.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ and complete Week 2." - ] - }, - { - "name": "Learn Computer Hardware", - "time": 120, - "video": "114597347", - "challengeNumber": 20, - "steps": [ - "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", - "This challenge will also give you an understanding of how bits and bytes work.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ and complete Week 3." - ] - }, - { - "name": "Learn Computer Networking", - "time": 120, - "video": "114604811", - "challengeNumber": 21, - "steps": [ - "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", - "Particularly important, you will learn about networks and TCP/IP - the protocol that powers the internet.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ and complete Week 4." - ] - }, - { - "name": "Learn Boolean Logic", - "time": 120, - "video": "114604812", - "challengeNumber": 22, - "steps": [ - "Now we'll do some more table exercises and learn boolean logic.", - "We'll also learn the difference between digital data and analog data.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ and complete Week 5." - ] - }, - { - "name": "Learn Computer Security", - "time": 120, - "video": "114604813", - "challengeNumber": 23, - "steps": [ - "We're almost done with Stanford's Introduction to Computer Science course!", - "We'll learn about one of the most important inventions of the 20th century - spreadsheets.", - "We'll also learn about Computer Security and some of the more common vulnerabilities software systems have.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z229/z213/ and complete Week 6, the final week of the course." - ] - }, - { - "name": "Build an Adventure Game", - "time": 60, - "video": "114604814", - "challengeNumber": 24, - "steps": [ - "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", - "We're going to work through Codecademy's famous interactive JavaScript course.", - "This course will teach us some JavaScript fundamentals while guiding us through the process of building interesting web apps, all within Codecademy's learner-friendly environment!", - "Go to http://www.codecademy.com/courses/getting-started-v2/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-x9DnD/0/1." - ] - }, - { - "name": "Build Rock Paper Scissors", - "time": 60, - "video": "114604815", - "challengeNumber": 25, - "steps": [ - "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", - "Go to http://www.codecademy.com/courses/javascript-beginner-en-6LzGd/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-Bthev-mskY8/0/1." - ] - }, - { - "name": "Learn JavaScript For Loops", - "time": 60, - "video": "114614220", - "challengeNumber": 26, - "steps": [ - "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", - "Go to http://www.codecademy.com/courses/javascript-beginner-en-NhsaT/0/1web and complete both the both For and While loop section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-XEDZA/0/1." - ] - }, - { - "name": "Learn JavaScript While Loops", - "time": 60, - "video": "114612889", - "challengeNumber": 27, - "steps": [ - - "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-mrTNH-6VIZ9/0/1." - ] - }, - { - "name": "Learn Control Flow", - "time": 60, - "video": "114612888", - "challengeNumber": 28, - "steps": [ - "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", - "We'll also learn some advanced \"Control Flow\" principals, such as ways we can exit loops early.", - "Go to http://www.codecademy.com/courses/javascript-beginner-en-qDwp0/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-ZA2rb/0/1." - ] - }, - { - "name": "Build a Contact List", - "time": 60, - "video": "114612887", - "challengeNumber": 29, - "steps": [ - "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", - "Go to http://www.codecademy.com/courses/javascript-beginner-en-9Sgpi/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-3bmfN/0/1." - ] - }, - { - "name": "Build an Address Book", - "time": 60, - "video": "114612885", - "challengeNumber": 30, - "steps": [ - "Let's learn more about objects.", - "Go to http://www.codecademy.com/courses/spencer-sandbox/0/1 and complete the section.", - "Be sure to also complete this section: http://www.codecademy.com/courses/building-an-address-book/0/1?curriculum_id=506324b3a7dffd00020bf661." - ] - }, - { - "name": "Build a Cash Register", - "time": 60, - "video": "114612882", - "challengeNumber": 31, - "steps": [ - "In this final Codecademy section, we'll learn even more about JavaScript objects.", - "Go to http://www.codecademy.com/courses/objects-ii/0/1 and complete this section.", - "Be sure to also complete the final section: http://www.codecademy.com/courses/close-the-super-makert/0/1." - ] - }, - { - "name": "Get Help the Hacker Way", - "time": 30, - "video": "111500801", - "challengeNumber": 32, - "steps": [ - "Watch the video to learn the RSAP (Read, Search, Ask, Post) methodology for getting help.", - "Try an intelligent Google query that involves JavaScript and filters for this year (since JavaScript changes).", - "Go to http://stackoverflow.com/ and view the recent questions.", - "Go to http://webchat.freenode.net/ and create an IRC account.", - "Join the #LearnJavaScript chat room and introduce yourself as a Free Code Camp student.", - "Finally, we have a special chat room specifically for getting help with tools you learn through Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", - "Now you have several ways of getting help when you're stuck." - ] - }, - { - "name": "Learn Regular Expressions", - "time": 60, - "video": "112547802", - "challengeNumber": 33, - "steps": [ - "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", - "Check out http://www.regexr.com. It's a Regular Expression Sandbox.", - "Now go to http://www.regexone.com and complete the tutorial and exercises 1 - 6.", - "Note that you can click \"continue\" to move on to the next step as soon as all the tasks have green check marks beside them. You can often do this just by using the wildcard \"dot\" operator, but try to use the techniques that each lesson recommends." - ] - }, - { - "name": "Pair Program on Bonfires", - "time": 60, - "video": "119657641", - "challengeNumber": 34, - "steps": [ - "OK, we're finally ready to start pair programming!", - "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", - "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", - "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", - "We have a special chat room for people ready to pair program. Go to https://gitter.im/FreeCodeCamp/LetsPair and type \"Hello Pair Programmers!\"", - "If someone is available, they will be your \"pair\" - the person you pair programming with.", - "If no one gets back to you in the first few minutes, don't worry. There will be lots of opportunities to pair program in the future.", - "If someone does get back to you, private message them and ask for the email address they used to register Screen Hero.", - "Add them as a new contact in Screen Hero, then click the monitor-looking button to attempt to share your screen with them.", - "Once the Screen Hero session starts, your screen's margins will glow orange. You are now sharing your screen.", - "Your pair will have their own cursor, and will be able to type text on his or her and keyboard.", - "Now it's time to tackle our Bonfires.", - "Go to http://freecodecamp.com/bonfires and start working through our Bonfire challenges.", - "Once you you finish pair programming, end the session in Screen Hero session.", - "Congratulations! You have completed your first pair programming session.", - "Try to pair program with different campers until you've completed all the Bonfire challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!", - "You can complete Bonfire challenges while you continue to work through Free Code Camp's challenges. Take your time.", - "Mark this challenge as complete and move on." - ] - }, - { - "name": "Manage Source Code with Git", - "time": 30, - "video": "114635309", - "challengeNumber": 35, - "steps": [ - "Revision Control Systems like Git ensure that, no matter how you experiment with your code, you can always roll back your app to a stable previous state.", - "Git is also a great way to share and contribute to open source software.", - "Go to https://www.codeschool.com/courses/try-git and complete this short interactive course." - ] - }, - { - "name": "Get Started with Node.js", - "time": 45, - "video": "114686471", - "challengeNumber": 36, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Now that we understand some Computer Science and JavaScript programming, you're ready to move on to Full-stack JavaScript!", - "The first step is to familiarize ourselves Node.js, the JavaScript-based web server that most full-stack JavaScript apps use.", - "When you're ready, go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/1/video/1 and complete the first chapter." - ] - }, - { - "name": "Try Node.js Events", - "time": 45, - "video": "114684206", - "challengeNumber": 37, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "One of the reasons Node.js is so fast is that it is \"evented.\" It processes events in an asynchronous manner.", - "As a result, Node.js relies on asynchronous callbacks.", - "We'll learn more about how events and callbacks work in this exciting Code School lesson.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/2/video/1 and complete the section." - ] - }, - { - "name": "Try Node.js Streams", - "time": 45, - "video": "114684209", - "challengeNumber": 38, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "In this Code School lesson, we'll learn about streaming data back and forth between the client to the server.", - "We'll also learn about FS, or File System, an important Node.js module for streaming data.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/3/video/1 and complete the section." - ] - }, - { - "name": "Learn how Node.js Modules Work", - "time": 45, - "video": "114684213", - "challengeNumber": 39, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "One of the most exciting features of Node.js is NPM - Node Package Manager", - "With NPM, you quickly install any of thousands of Node.js modules into your app, and it will automatically handle the other modules that each module dependends upon downstream.", - "In this lesson, we'll learn how to include these modules in our Node.js app by requiring them as variables.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/4/video/1 and complete the section." - ] - }, - { - "name": "Start an Express.js Server", - "time": 45, - "video": "114684247", - "challengeNumber": 40, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "We'll complete Code School's Express.js course shortly after completing this course, but this challenge will give you a quick tour of the Express.js framework.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/5/video/1 and complete the section." - ] - }, - { - "name": "Use Socket.io", - "time": 45, - "video": "114684530", - "challengeNumber": 41, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/6/video/1 and complete the section." - ] - }, - { - "name": "Use Redis to Persist Data", - "time": 45, - "video": "114684532", - "challengeNumber": 42, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Redis is a key-value store, which is a type of non-relational database. It's one of the fastest and easiest ways to persist data.", - "Even though we'll ultimately use MongoDB and other technologies to persist data, Redis is quite easy to learn and is still worth learning.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/7/video/1 and complete the section." - ] - }, - { - "name": "Dive Deeper into Express.js", - "time": 45, - "video": "114684533", - "challengeNumber": 43, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/1/video/1 and complete the section." - ] - }, - { - - "name": "Setup Express.js Middleware", - "time": 45, - "video": "114684535", - "challengeNumber": 44, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Express.js makes extensive use of middleware - a stack of functions that run sequentially in response to a specific event.", - "Let's learn how to incorporate modules and middleware into our Express.js app.", - "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/2/video/1 and complete the section." - ] - }, - { - "name": "Take Advantage of Parameters", - "time": 45, - "video": "114684537", - "challengeNumber": 45, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Have you ever noticed a question mark in your browser's address bar, followed by a series of strings? Those are parameters. Parameters are an efficient way to pass information to the server between page loads.", - "We'll learn about parameters, along with a powerful Express.js feature called Dynamic Routing, in this exciting Code School lesson.", - "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/3/video/1 and complete the section." - ] - }, - { - "name": "Add the Body Parser", - "time": 45, - "video": "114684720", - "challengeNumber": 46, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "Now we'll add the Body Parser module to Express.js. Body Parser is a powerful middleware that helps with routing.", - "We'll also learn more about HTTP Requests, such as Post and Delete.", - "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/4/video/1 and complete the section." - ] - }, - { - "name": "Configure Routes in Express.js", - "time": 45, - "video": "114684724", - "challengeNumber": 47, - "steps": [ - "Note that this Code School course is no longer free. We have free alternatives to this course here.", - "For this last Code School Express.js challenge, we'll refactor our routes.", - "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/5/video/1 and complete the section." - - ] - }, - { - "name": "Try MongoDB", - "time": 30, - "video": "114685061", - "challengeNumber": 48, - "steps": [ - "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", - "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." - ] - }, - { - "name": "Get Started with Angular.js", - "time": 45, - "video": "114684726", - "challengeNumber": 49, - "steps": [ - "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", - "In this course, we'll build a virtual shop entirely in Angular.js.", - "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/1/section/1/video/1 and complete the section." - ] - }, - { - "name": "Apply Angular.js Directives", - "time": 45, - "video": "114684727", - "challengeNumber": 50, - "steps": [ - "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", - "Let's learn how these powerful directives work, and how to use them to make your web apps more dynamic", - "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/1/video/1 and complete the section." - ] - }, - { - "name": "Power Forms with Angular.js", - "time": 45, - "video": "114684729", - "challengeNumber": 51, - "steps": [ - "One area where Angular.js really shines is its powerful web forms.", - "Learn how to create reactive Angular.js forms, including real-time form validation.", - "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/3/section/1/video/1 and complete the section." - ] - }, - { - "name": "Customize Angular.js Directives", - "time": 45, - "video": "114685062", - "challengeNumber": 52, - "steps": [ - "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", - "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/4/section/1/video/1 and complete the section." - ] - }, - { - "name": "Create Angular.js Services", - "time": 45, - "video": "114685060", - "challengeNumber": 53, - "steps": [ - "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", - "We'll learn how to use services in this final Code School Angular.js challenge.", - "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/5/section/1/video/1 and complete the section." - ] - } -] diff --git a/seed_data/ziplines.json b/seed_data/ziplines.json new file mode 100644 index 0000000000..8a58133697 --- /dev/null +++ b/seed_data/ziplines.json @@ -0,0 +1,10 @@ +[ + { + "name": "Status Checker", + "picture": "", + "video": "", + "gitHubLink": "https://github.com/FreeCodeCamp/ZiplineStatusChecker", + "demoLink": "", + "details": [] + } +] \ No newline at end of file From 31ce7b686afd2e6db3b571f8f2de945dee7bca8e Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Mar 2015 20:07:08 +0900 Subject: [PATCH 04/57] User will now get a point for posting a story as well as receiving an upvote on a posted story --- controllers/story.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 82032ba752..d3bfd55405 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -19,7 +19,7 @@ function hotRank(timeValue, rank) { */ var hotness; var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / 115200000); + hotness = z + (timeValue / 172800000); return hotness; } @@ -38,7 +38,8 @@ exports.hotJSON = function(req, res) { return res.json(stories.map(function(elem) { return elem; }).sort(function(a, b) { - return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline); + return hotRank(b.timePosted - foundationDate, b.rank, b.headline) + - hotRank(a.timePosted - foundationDate, a.rank, a.headline); }).slice(0, sliceVal)); }); @@ -221,6 +222,17 @@ exports.upvote = function(req, res, next) { ); story.markModified('rank'); story.save(); + User.find({'_id': story.author.userId}, function(err, user) { + 'use strict'; + if (err) { + return next(err); + } + // todo debug + debug('This is the user in upvote', user); + user = user.pop(); + user.progressTimestamps.push(Date.now()); + user.save(); + }); return res.send(story); }); }; @@ -332,6 +344,9 @@ exports.storySubmission = function(req, res) { metaDescription: data.storyMetaDescription }); + req.user.progressTimestamps.push(Date.now()); + req.user.save(); + story.save(function(err) { if (err) { return res.status(500); From fb5e8dc387824573965bcc11e9b444d39f2343f1 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Mar 2015 20:11:37 +0900 Subject: [PATCH 05/57] Remove debug statement, closes #156, story decay to 32 hours --- controllers/story.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index d3bfd55405..cc7f3dfbea 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -19,7 +19,7 @@ function hotRank(timeValue, rank) { */ var hotness; var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / 172800000); + hotness = z + (timeValue / 115200000); return hotness; } @@ -227,8 +227,6 @@ exports.upvote = function(req, res, next) { if (err) { return next(err); } - // todo debug - debug('This is the user in upvote', user); user = user.pop(); user.progressTimestamps.push(Date.now()); user.save(); From 999ba14db7e8d909710dfa0ef7fbc95a00a62c28 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Mar 2015 20:20:08 +0900 Subject: [PATCH 06/57] Fix indenting to 2 spaces, remove most lint errors --- controllers/story.js | 729 ++++++++++++++++++++++--------------------- 1 file changed, 367 insertions(+), 362 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index cc7f3dfbea..29f663654a 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -1,444 +1,449 @@ var R = require('ramda'), - debug = require('debug')('freecc:cntr:story'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - User = require('./../models/User'), - moment = require('../public/js/lib/moment/moment.js'), - resources = require('./resources'), - mongodb = require('mongodb'), - MongoClient = mongodb.MongoClient, - secrets = require('../config/secrets'), - sanitizeHtml = require('sanitize-html'); + debug = require('debug')('freecc:cntr:story'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + User = require('./../models/User'), + moment = require('../public/js/lib/moment/moment.js'), + resources = require('./resources'), + mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + secrets = require('../config/secrets'), + sanitizeHtml = require('sanitize-html'); function hotRank(timeValue, rank) { - /* - * Hotness ranking algorithm: http://amix.dk/blog/post/19588 - * tMS = postedOnDate - foundationTime; - * Ranking... - * f(ts, 1, rank) = log(10)z + (ts)/45000; - */ - var hotness; - var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / 115200000); - return hotness; + /* + * Hotness ranking algorithm: http://amix.dk/blog/post/19588 + * tMS = postedOnDate - foundationTime; + * Ranking... + * f(ts, 1, rank) = log(10)z + (ts)/45000; + */ + var hotness; + var z = Math.log(rank) / Math.log(10); + hotness = z + (timeValue / 115200000); + return hotness; } exports.hotJSON = function(req, res) { - var story = Story.find({}).sort({'timePosted': -1}).limit(1000); - story.exec(function(err, stories) { - if (err) { - res.send(500); - return next(err); - } + var story = Story.find({}).sort({'timePosted': -1}).limit(1000); + story.exec(function(err, stories) { + if (err) { + return res.status(500); + } - var foundationDate = 1413298800000; + var foundationDate = 1413298800000; - var sliceVal = stories.length >= 100 ? 100 : stories.length; - return res.json(stories.map(function(elem) { - return elem; - }).sort(function(a, b) { - return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - - hotRank(a.timePosted - foundationDate, a.rank, a.headline); - }).slice(0, sliceVal)); + var sliceVal = stories.length >= 100 ? 100 : stories.length; + return res.json(stories.map(function(elem) { + return elem; + }).sort(function(a, b) { + return hotRank(b.timePosted - foundationDate, b.rank, b.headline) + - hotRank(a.timePosted - foundationDate, a.rank, a.headline); + }).slice(0, sliceVal)); - }); + }); }; exports.recentJSON = function(req, res, next) { - var story = Story.find({}).sort({'timePosted': -1}).limit(100); - story.exec(function(err, stories) { - if (err) { - res.status(500); - return next(err); - } - res.json(stories); - }); + var story = Story.find({}).sort({'timePosted': -1}).limit(100); + story.exec(function(err, stories) { + if (err) { + res.status(500); + return next(err); + } + res.json(stories); + }); }; exports.hot = function(req, res) { - res.render('stories/index', { - title: 'Hot stories currently trending on Camper News', - page: 'hot' - }); + res.render('stories/index', { + title: 'Hot stories currently trending on Camper News', + page: 'hot' + }); }; exports.submitNew = function(req, res) { - res.render('stories/index', { - title: 'Submit a new story to Camper News', - page: 'submit' - }); + res.render('stories/index', { + title: 'Submit a new story to Camper News', + page: 'submit' + }); }; exports.search = function(req, res) { - res.render('stories/index', { - title: 'Search the archives of Camper News', - page: 'search' - }); + res.render('stories/index', { + title: 'Search the archives of Camper News', + page: 'search' + }); }; exports.recent = function(req, res) { - res.render('stories/index', { - title: 'Recently submitted stories on Camper News', - page: 'recent' - }); + res.render('stories/index', { + title: 'Recently submitted stories on Camper News', + page: 'recent' + }); }; exports.preSubmit = function(req, res) { - var data = req.query; - var cleanData = sanitizeHtml(data.url, { - allowedTags: [], - allowedAttributes: [] - }).replace(/";/g, '"'); - if (data.url.replace(/&/g, '&') !== cleanData) { + var data = req.query; + var cleanData = sanitizeHtml(data.url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/";/g, '"'); + if (data.url.replace(/&/g, '&') !== cleanData) { - req.flash('errors', { - msg: 'The data for this post is malformed' - }); - return res.render('stories/index', { - page: 'stories/submit' - }); - } - - var title = data.title || ''; - var image = data.image || ''; - var description = data.description || ''; - return res.render('stories/index', { - title: "Confirm your Camper News story submission", - page: 'storySubmission', - storyURL: data.url, - storyTitle: title, - storyImage: image, - storyMetaDescription: description + req.flash('errors', { + msg: 'The data for this post is malformed' }); + return res.render('stories/index', { + page: 'stories/submit' + }); + } + + var title = data.title || ''; + var image = data.image || ''; + var description = data.description || ''; + return res.render('stories/index', { + title: 'Confirm your Camper News story submission', + page: 'storySubmission', + storyURL: data.url, + storyTitle: title, + storyImage: image, + storyMetaDescription: description + }); }; exports.returnIndividualStory = function(req, res, next) { - var dashedName = req.params.storyName; + var dashedName = req.params.storyName; - var storyName = dashedName.replace(/\-/g, ' '); + var storyName = dashedName.replace(/\-/g, ' '); - Story.find({'storyLink' : new RegExp(storyName, 'i')}, function(err, story) { - if (err) { - next(err); - } + Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) { + if (err) { + next(err); + } - if (story.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a story with that name. Please double check the name." - }); + if (story.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a story with that name. Please double check the name." + }); - return res.redirect('/stories/'); - } + return res.redirect('/stories/'); + } - story = story.pop(); - var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../stories/' + dashedNameFull); - } + story = story.pop(); + var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull !== dashedName) { + return res.redirect('../stories/' + dashedNameFull); + } - var userVoted = false; - try { - var votedObj = story.upVotes.filter(function(a){ - return a['upVotedByUsername'] === req.user['profile']['username']; - }) - if (votedObj.length > 0){ - userVoted = true; - } - } catch(err){ - userVoted = false; - } - res.render('stories/index', { - title: story.headline, - link: story.link, - author: story.author, - description: story.description, - rank: story.upVotes.length, - upVotes: story.upVotes, - comments: story.comments, - id: story._id, - timeAgo: moment(story.timePosted).fromNow(), - image: story.image, - page: 'show', - storyMetaDescription: story.metaDescription, - hasUserVoted: userVoted - }); + var userVoted = false; + try { + var votedObj = story.upVotes.filter(function(a) { + return a['upVotedByUsername'] === req.user['profile']['username']; + }); + if (votedObj.length > 0) { + userVoted = true; + } + } catch(err) { + userVoted = false; + } + res.render('stories/index', { + title: story.headline, + link: story.link, + author: story.author, + description: story.description, + rank: story.upVotes.length, + upVotes: story.upVotes, + comments: story.comments, + id: story._id, + timeAgo: moment(story.timePosted).fromNow(), + image: story.image, + page: 'show', + storyMetaDescription: story.metaDescription, + hasUserVoted: userVoted }); + }); }; exports.getStories = function(req, res) { - MongoClient.connect(secrets.db, function(err, database) { - database.collection('stories').find({ - "$text": { - "$search": req.body.data.searchValue - } - }, { - headline: 1, - timePosted: 1, - link: 1, - description: 1, - rank: 1, - upVotes: 1, - author: 1, - comments: 1, - image: 1, - storyLink: 1, - metaDescription: 1, - textScore: { - $meta: "textScore" - } - }, { - sort: { - textScore: { - $meta: "textScore" - } - } - }).toArray(function(err, items) { - if (items !== null && items.length !== 0) { - return res.json(items); - } - return res.status(404); - }); + MongoClient.connect(secrets.db, function(err, database) { + if (err) { + return res.status(500); + } + database.collection('stories').find({ + '$text': { + '$search': req.body.data.searchValue + } + }, { + headline: 1, + timePosted: 1, + link: 1, + description: 1, + rank: 1, + upVotes: 1, + author: 1, + comments: 1, + image: 1, + storyLink: 1, + metaDescription: 1, + textScore: { + $meta: 'textScore' + } + }, { + sort: { + textScore: { + $meta: 'textScore' + } + } + }).toArray(function(err, items) { + if (err) { + return res.status(500); + } + if (items !== null && items.length !== 0) { + return res.json(items); + } + return res.status(404); }); + }); }; exports.upvote = function(req, res, next) { - var data = req.body.data; - Story.find({'_id': data.id}, function(err, story) { - if (err) { - res.status(500); - return next(err); - } - story = story.pop(); - story.rank++; - story.upVotes.push( - { - upVotedBy: data.upVoter._id, - upVotedByUsername: data.upVoter.profile.username - } - ); - story.markModified('rank'); - story.save(); - User.find({'_id': story.author.userId}, function(err, user) { - 'use strict'; - if (err) { - return next(err); - } - user = user.pop(); - user.progressTimestamps.push(Date.now()); - user.save(); - }); - return res.send(story); + var data = req.body.data; + Story.find({'_id': data.id}, function(err, story) { + if (err) { + res.status(500); + return next(err); + } + story = story.pop(); + story.rank++; + story.upVotes.push( + { + upVotedBy: data.upVoter._id, + upVotedByUsername: data.upVoter.profile.username + } + ); + story.markModified('rank'); + story.save(); + User.find({'_id': story.author.userId}, function(err, user) { + 'use strict'; + if (err) { + return next(err); + } + user = user.pop(); + user.progressTimestamps.push(Date.now()); + user.save(); }); + return res.send(story); + }); }; exports.comments = function(req, res, next) { - var data = req.params.id; - Comment.find({'_id': data}, function(err, comment) { - if (err) { - res.status(500); - return next(err); - } - comment = comment.pop(); - return res.send(comment); - }); + var data = req.params.id; + Comment.find({'_id': data}, function(err, comment) { + if (err) { + res.status(500); + return next(err); + } + comment = comment.pop(); + return res.send(comment); + }); }; exports.newStory = function(req, res) { - if (!req.user) { - return res.status(500); - } - var url = req.body.data.url; - var cleanURL = sanitizeHtml(url, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (cleanURL !== url) { - req.flash('errors', { - msg: "The URL you submitted doesn't appear valid" - }); - return res.json({ - alreadyPosted: true, - storyURL: '/stories/submit' - }); - - } - if (url.search(/^https?:\/\//g) === -1) { - url = 'http://' + url; - } - Story.find({'link': url}, function(err, story) { - if (err) { - return res.status(500); - } - if (story.length) { - req.flash('errors', { - msg: "Someone's already posted that link. Here's the discussion." - }); - return res.json({ - alreadyPosted: true, - storyURL: '/stories/' + story.pop().storyLink - }); - } - resources.getURLTitle(url, processResponse); + if (!req.user) { + return res.status(500); + } + var url = req.body.data.url; + var cleanURL = sanitizeHtml(url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (cleanURL !== url) { + req.flash('errors', { + msg: "The URL you submitted doesn't appear valid" + }); + return res.json({ + alreadyPosted: true, + storyURL: '/stories/submit' }); - function processResponse(err, story) { - if (err) { - res.json({ - alreadyPosted: false, - storyURL: url, - storyTitle: '', - storyImage: '', - storyMetaDescription: '' - }); - } else { - res.json({ - alreadyPosted: false, - storyURL: url, - storyTitle: story.title, - storyImage: story.image, - storyMetaDescription: story.description - }); - } + } + if (url.search(/^https?:\/\//g) === -1) { + url = 'http://' + url; + } + Story.find({'link': url}, function(err, story) { + if (err) { + return res.status(500); } + if (story.length) { + req.flash('errors', { + msg: "Someone's already posted that link. Here's the discussion." + }); + return res.json({ + alreadyPosted: true, + storyURL: '/stories/' + story.pop().storyLink + }); + } + resources.getURLTitle(url, processResponse); + }); + + function processResponse(err, story) { + if (err) { + res.json({ + alreadyPosted: false, + storyURL: url, + storyTitle: '', + storyImage: '', + storyMetaDescription: '' + }); + } else { + res.json({ + alreadyPosted: false, + storyURL: url, + storyTitle: story.title, + storyImage: story.image, + storyMetaDescription: story.description + }); + } + } }; exports.storySubmission = function(req, res) { - var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); - } - var storyLink = data.headline - .replace(/\'/g, '') - .replace(/\"/g, '') - .replace(/,/g, '') - .replace(/[^a-z0-9]/gi, ' ') - .replace(/\s+/g, ' ') - .toLowerCase(); - var link = data.link; - if (link.search(/^https?:\/\//g) === -1) { - link = 'http://' + link; - } - var story = new Story({ - headline: sanitizeHtml(data.headline, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - timePosted: Date.now(), - link: link, - description: sanitizeHtml(data.description, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - rank: 1, - upVotes: data.upVotes, - author: data.author, - comments: [], - image: data.image, - storyLink: storyLink, - metaDescription: data.storyMetaDescription - }); + var data = req.body.data; + if (req.user._id.toString() !== data.author.userId.toString()) { + return res.status(500); + } + var storyLink = data.headline + .replace(/\'/g, '') + .replace(/\"/g, '') + .replace(/,/g, '') + .replace(/[^a-z0-9]/gi, ' ') + .replace(/\s+/g, ' ') + .toLowerCase(); + var link = data.link; + if (link.search(/^https?:\/\//g) === -1) { + link = 'http://' + link; + } + var story = new Story({ + headline: sanitizeHtml(data.headline, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'), + timePosted: Date.now(), + link: link, + description: sanitizeHtml(data.description, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'), + rank: 1, + upVotes: data.upVotes, + author: data.author, + comments: [], + image: data.image, + storyLink: storyLink, + metaDescription: data.storyMetaDescription + }); - req.user.progressTimestamps.push(Date.now()); - req.user.save(); + req.user.progressTimestamps.push(Date.now()); + req.user.save(); - story.save(function(err) { - if (err) { - return res.status(500); - } - res.send(JSON.stringify({ - storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() - })); - }); + story.save(function(err) { + if (err) { + return res.status(500); + } + res.send(JSON.stringify({ + storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() + })); + }); }; exports.commentSubmit = function(req, res) { - var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); - } - var sanitizedBody = sanitizeHtml(data.body, - { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (data.body !== sanitizedBody) { - req.flash('errors', { - msg: 'HTML is not allowed' - }); - return res.send(true); - } - var comment = new Comment({ - associatedPost: data.associatedPost, - body: sanitizedBody, - rank: 0, - upvotes: 0, - author: data.author, - comments: [], - topLevel: true, - commentOn: Date.now() + var data = req.body.data; + if (req.user._id.toString() !== data.author.userId.toString()) { + return res.status(500); + } + var sanitizedBody = sanitizeHtml(data.body, + { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (data.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' }); - commentSave(comment, Story, res); + return res.send(true); + } + var comment = new Comment({ + associatedPost: data.associatedPost, + body: sanitizedBody, + rank: 0, + upvotes: 0, + author: data.author, + comments: [], + topLevel: true, + commentOn: Date.now() + }); + commentSave(comment, Story, res); }; exports.commentOnCommentSubmit = function(req, res) { - var data = req.body.data; + var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); - } + if (req.user._id.toString() !== data.author.userId.toString()) { + return res.status(500); + } - var sanitizedBody = sanitizeHtml(data.body, - { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (data.body !== sanitizedBody) { - req.flash('errors', { - msg: 'HTML is not allowed' - }); - return res.send(true); - } - var comment = new Comment({ - associatedPost: data.associatedPost, - body: sanitizedBody, - rank: 0, - upvotes: 0, - author: data.author, - comments: [], - topLevel: false, - commentOn: Date.now() + var sanitizedBody = sanitizeHtml(data.body, + { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (data.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' }); - commentSave(comment, Comment, res); + return res.send(true); + } + var comment = new Comment({ + associatedPost: data.associatedPost, + body: sanitizedBody, + rank: 0, + upvotes: 0, + author: data.author, + comments: [], + topLevel: false, + commentOn: Date.now() + }); + commentSave(comment, Comment, res); }; function commentSave(comment, Context, res) { - comment.save(function(err, data) { + comment.save(function(err, data) { + if (err) { + return res.status(500); + } + try { + Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { if (err) { - return res.status(500); + return res.status(500); } - try { - Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { - if (err) { - return res.status(500); - } - associatedStory = associatedStory.pop(); - if (associatedStory) { - associatedStory.comments.push(data._id); - associatedStory.save(function (err) { - if (err) { - res.status(500); - } - res.send(true); - }); - } - }); - } catch (e) { - // delete comment - return res.status(500); + associatedStory = associatedStory.pop(); + if (associatedStory) { + associatedStory.comments.push(data._id); + associatedStory.save(function (err) { + if (err) { + res.status(500); + } + res.send(true); + }); } - }); + }); + } catch (e) { + // delete comment + return res.status(500); + } + }); } From f246ef618cd2ed17f97cb475a0a9de0aa7fce002 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 22 Mar 2015 16:57:10 -0700 Subject: [PATCH 07/57] Close web worker after completion closes #257 --- public/js/lib/bonfire/plugin_v0.1.4.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/js/lib/bonfire/plugin_v0.1.4.js b/public/js/lib/bonfire/plugin_v0.1.4.js index 9aa5978c06..f71c0d34f1 100644 --- a/public/js/lib/bonfire/plugin_v0.1.4.js +++ b/public/js/lib/bonfire/plugin_v0.1.4.js @@ -17,6 +17,7 @@ var run = function(code) { } application.remote.output(result); + self.close(); }; From 940dbea6227b30627f9b224594e17845e7291434 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 22 Mar 2015 22:07:58 -0700 Subject: [PATCH 08/57] Handle err in config/passport --- config/passport.js | 254 +++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 113 deletions(-) diff --git a/config/passport.js b/config/passport.js index af46f12713..9c3beb04b0 100644 --- a/config/passport.js +++ b/config/passport.js @@ -27,8 +27,10 @@ passport.deserializeUser(function(id, done) { passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) { User.findOne({ email: email }, function(err, user) { + if (err) { return done(err); } if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); user.comparePassword(password, function(err, isMatch) { + if (err) { return done(err); } if (isMatch) { return done(null, user); } else { @@ -58,30 +60,35 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ facebook: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); - done(err); + done(); } else { User.findById(req.user.id, function(err, user) { + if (err) { return done(err); } user.facebook = profile.id; user.tokens.push({ kind: 'facebook', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.gender = user.profile.gender || profile._json.gender; user.profile.picture = user.profile.picture || 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; user.save(function(err) { + if (err) { return done(err); } req.flash('info', { msg: 'Facebook account has been linked.' }); - done(err, user); + done(null, user); }); }); } }); } else { User.findOne({ facebook: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' }); - done(err); + done(); } else { var user = new User(); user.email = profile._json.email; @@ -92,30 +99,31 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.profile.location = (profile._json.location) ? profile._json.location.name : ''; user.save(function(err) { - done(err, user); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + if (err) { return done(err); } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + done(null, user); + }); }); } }); @@ -128,9 +136,10 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ github: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); - done(err); + done(); } else { User.findById(req.user.id, function(err, user) { user.github = profile.id; @@ -140,19 +149,22 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre user.profile.location = user.profile.location || profile._json.location; user.profile.website = user.profile.website || profile._json.blog; user.save(function(err) { + if (err) { return done(err); } req.flash('info', { msg: 'GitHub account has been linked.' }); - done(err, user); + done(null, user); }); }); } }); } else { User.findOne({ github: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' }); - done(err); + done(null); } else { var user = new User(); user.email = profile._json.email; @@ -163,30 +175,31 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre user.profile.location = profile._json.location; user.profile.website = profile._json.blog; user.save(function(err) { - done(err, user); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + if (err) { return done(err); } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + done(null, user); + }); }); } }); @@ -199,9 +212,10 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) { if (req.user) { User.findOne({ twitter: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a Twitter account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); - done(err); + done(); } else { User.findById(req.user.id, function(err, user) { user.twitter = profile.id; @@ -212,8 +226,9 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok user.profile.picture = user.profile.picture || profile._json.profile_image_url_https.replace('_normal', ''); user.profile.twitterHandle = user.profile.twitterHandle || profile.username.toLowerCase(); user.save(function(err) { + if (err) { return done(err); } req.flash('info', { msg: 'Twitter account has been linked.' }); - done(err, user); + done(null, user); }); }); } @@ -221,6 +236,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok } else { User.findOne({ twitter: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) return done(null, existingUser); var user = new User(); user.profile.username = profile.username.toLowerCase(); @@ -231,7 +247,8 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok user.profile.picture = profile._json.profile_image_url_https.replace('_normal', ''); user.profile.twitterHandle = user.profile.twitterHandle || profile.username.toLowerCase(); user.save(function(err) { - done(err, user); + if (err) { return done(err); } + done(null, user); }); }); } @@ -242,30 +259,35 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ google: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); - done(err); + done(); } else { User.findById(req.user.id, function(err, user) { + if (err) { return done(err); } user.google = profile.id; user.tokens.push({ kind: 'google', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.gender = user.profile.gender || profile._json.gender; user.profile.picture = user.profile.picture || profile._json.picture; user.save(function(err) { + if (err) { return done(err); } req.flash('info', { msg: 'Google account has been linked.' }); - done(err, user); + done(null, user); }); }); } }); } else { User.findOne({ google: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' }); - done(err); + done(); } else { var user = new User(); user.email = profile._json.email; @@ -275,30 +297,31 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre user.profile.gender = profile._json.gender; user.profile.picture = profile._json.picture; user.save(function(err) { - done(err, user); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + if (err) { return done(err); } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + done(null, user); + }); }); } }); @@ -311,11 +334,13 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ linkedin: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); - done(err); + done(); } else { User.findById(req.user.id, function(err, user) { + if (err) { return done(err); } user.linkedin = profile.id; user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; @@ -323,8 +348,9 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r user.profile.picture = user.profile.picture || profile._json.pictureUrl; user.profile.website = user.profile.website || profile._json.publicProfileUrl; user.save(function(err) { + if (err) { return done(err); } req.flash('info', { msg: 'LinkedIn account has been linked.' }); - done(err, user); + done(null, user); }); }); } @@ -333,9 +359,10 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r User.findOne({ linkedin: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) { + if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' }); - done(err); + done(); } else { var user = new User(); user.linkedin = profile.id; @@ -346,30 +373,31 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r user.profile.picture = profile._json.pictureUrl; user.profile.website = profile._json.publicProfileUrl; user.save(function(err) { - done(err, user); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + if (err) { return done(err); } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + done(null, user); + }); }); } }); @@ -380,7 +408,7 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r // Login Required middleware. exports.isAuthenticated = function(req, res, next) { - if (req.isAuthenticated()) return next(); + if (req.isAuthenticated()) { return next(); } res.redirect('/login'); }; @@ -394,4 +422,4 @@ exports.isAuthorized = function(req, res, next) { } else { res.redirect('/auth/' + provider); } -}; \ No newline at end of file +}; From d8bf99708d4288ca994f53c68a64affcbffafb2e Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 22 Mar 2015 22:18:01 -0700 Subject: [PATCH 09/57] Add uncaught exception handler closes #258 --- app.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index fc2a8e81ed..5c5b748823 100644 --- a/app.js +++ b/app.js @@ -2,9 +2,17 @@ if (process.env.NODE_ENV !== 'development') { require('newrelic'); } require('dotenv').load(); -/** - * Module dependencies. - */ +// handle uncaught exceptions. Forever will restart process on shutdown +process.on('uncaughtException', function (err) { + console.error( + (new Date()).toUTCString() + ' uncaughtException:', + err.message + ); + console.error(err.stack); + /* eslint-disable no-process-exit */ + process.exit(1); + /* eslint-enable no-process-exit */ +}); var express = require('express'), cookieParser = require('cookie-parser'), From 89ee552a12e42a072bc125cec2ec77e7099dc353 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 22:22:52 -0700 Subject: [PATCH 10/57] start adding error handling to passport --- config/passport.js | 53 +++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/config/passport.js b/config/passport.js index af46f12713..1961f88cca 100644 --- a/config/passport.js +++ b/config/passport.js @@ -29,6 +29,7 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw User.findOne({ email: email }, function(err, user) { if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); user.comparePassword(password, function(err, isMatch) { + if (err) { return done(err); } if (isMatch) { return done(null, user); } else { @@ -58,6 +59,7 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ facebook: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); @@ -77,8 +79,10 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r }); } else { User.findOne({ facebook: profile.id }, function(err, existingUser) { + if (err) { return done(err); } if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' }); done(err); @@ -92,30 +96,31 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.profile.location = (profile._json.location) ? profile._json.location.name : ''; user.save(function(err) { - done(err, user); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + if (err) { return done(err); } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + done(null, user); + }); }); } }); From 6c929b83757ebc2f13e0360ff20bb51693b5ffb6 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 22:23:46 -0700 Subject: [PATCH 11/57] Revert "start adding error handling to passport" This reverts commit 89ee552a12e42a072bc125cec2ec77e7099dc353. --- config/passport.js | 53 +++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/config/passport.js b/config/passport.js index 9c3beb04b0..970753ce21 100644 --- a/config/passport.js +++ b/config/passport.js @@ -30,7 +30,6 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw if (err) { return done(err); } if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); user.comparePassword(password, function(err, isMatch) { - if (err) { return done(err); } if (isMatch) { return done(null, user); } else { @@ -60,7 +59,6 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ facebook: profile.id }, function(err, existingUser) { - if (err) { return done(err); } if (existingUser) { req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(); @@ -82,10 +80,8 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r }); } else { User.findOne({ facebook: profile.id }, function(err, existingUser) { - if (err) { return done(err); } if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { - if (err) { return done(err); } if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' }); done(); @@ -99,31 +95,30 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.profile.location = (profile._json.location) ? profile._json.location.name : ''; user.save(function(err) { - if (err) { return done(err); } - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - done(null, user); - }); + done(err, user); + }); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } }); } }); From ac8487a4fc5fde2d4b6775bea430777c7986fa6d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 23 Mar 2015 08:33:59 -0700 Subject: [PATCH 12/57] update basejump, zipline and nonprofit models --- models/Basejump.js | 22 ++++++++++++++++++++++ models/BasejumpCompletion.js | 12 ++++++++++++ models/Nonprofit.js | 31 +++++++++++++++++++++++++++++++ models/Zipline.js | 3 +-- models/ZiplineCompletion.js | 12 ++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 models/Basejump.js create mode 100644 models/BasejumpCompletion.js create mode 100644 models/Nonprofit.js create mode 100644 models/ZiplineCompletion.js diff --git a/models/Basejump.js b/models/Basejump.js new file mode 100644 index 0000000000..15eddc5dac --- /dev/null +++ b/models/Basejump.js @@ -0,0 +1,22 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +/** + * + * @type {exports.Schema} + */ + + +var basejumpSchema = new mongoose.Schema({ + name: { + type: String, + unique: true + }, + picture: String, + video: String, + gitHubLink: String, + demoLink: String, + details: Array +}); + +module.exports = mongoose.model('Basejump', basejumpSchema); \ No newline at end of file diff --git a/models/BasejumpCompletion.js b/models/BasejumpCompletion.js new file mode 100644 index 0000000000..872c401265 --- /dev/null +++ b/models/BasejumpCompletion.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +var basejumpCompletionSchema = new mongoose.Schema({ + dateCompleted: Number, + completedWith: ObjectId, + basejumpHash: ObjectId, + githubUrl: String, + demoUrl: String +}); + +module.exports = mongoose.model('BasejumpCompletion', basejumpCompletionSchema); diff --git a/models/Nonprofit.js b/models/Nonprofit.js new file mode 100644 index 0000000000..8fcd9ee973 --- /dev/null +++ b/models/Nonprofit.js @@ -0,0 +1,31 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +/** + * + * @type {exports.Schema} + */ + + +var nonprofitSchema = new mongoose.Schema({ + name: String, + registeredNonprofit: true, + requestedDeliverables: Array, + existingUserbase: true, + acceptJavascript: true, + agreeToTerms: true, + whatDoesNonprofitDo: String, + websiteLink: String, + stakeholderName: String, + stakeholderEmail: String, + endUser: String, + approvedDeliverables: Array, + projectDescription: String, + logoUrl: String, + imageUrl: String, + interestedCampers: Array, + confirmedCampers: Array, + estimatedHours: String +}); + +module.exports = mongoose.model('Nonprofit', nonprofitSchema); diff --git a/models/Zipline.js b/models/Zipline.js index cfb23420e9..a8d49fd173 100644 --- a/models/Zipline.js +++ b/models/Zipline.js @@ -14,8 +14,7 @@ var ziplineSchema = new mongoose.Schema({ }, picture: String, video: String, - gitHubLink: String, - demoLink: String, + codepenLink: String, details: Array }); diff --git a/models/ZiplineCompletion.js b/models/ZiplineCompletion.js new file mode 100644 index 0000000000..676d954fe6 --- /dev/null +++ b/models/ZiplineCompletion.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +var ziplineCompletionSchema = new mongoose.Schema({ + dateCompleted: Number, + completedWith: ObjectId, + basejumpHash: ObjectId, + githubUrl: String, + demoUrl: String +}); + +module.exports = mongoose.model('ziplineCompletion', ziplineCompletionSchema); \ No newline at end of file From 1facf532f3a1993c3589d3b02ec6f9ec82e5eed4 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 23 Mar 2015 10:42:59 -0700 Subject: [PATCH 13/57] start building the nonprofit project flow --- views/nonprofits/home.jade | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 views/nonprofits/home.jade diff --git a/views/nonprofits/home.jade b/views/nonprofits/home.jade new file mode 100644 index 0000000000..41f080b21a --- /dev/null +++ b/views/nonprofits/home.jade @@ -0,0 +1,86 @@ +extends layout +block content + .jumbotron + .text-center + h1.hug-top We code for a cause + h2 We'll code software solutions for your nonprofit, for free! + .row + .col-xs-12.col-sm-12.col-md-3 + h3.nowrap Get Connected + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_connect.svg.gz', title='Get great references and connections to help you get a job') + p.landing-p Join a community of busy, motivated professionals. + .col-xs-12.col-sm-12.col-md-3 + h3.nowrap Learn JavaScript + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_learn.svg.gz', title='Learn to code') + p.landing-p Work together on Full Stack JavaScript coding challenges. + .col-xs-12.col-sm-12.col-md-3 + h3.nowrap Build your Portfolio + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_portfolio.svg.gz', title='Build a portfolio of apps for nonprofits') + p.landing-p Build apps that solve real problems for real people. + .col-xs-12.col-sm-12.col-md-3 + h3.nowrap Help Nonprofits + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_nonprofits.svg.gz', title='Help nonprofits') + p.landing-p Give nonprofits a boost by empowering them with code. + .big-break + a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") I'm with a nonprofit and want help coding something + .big-break + h2 Nonprofits we've helped + .row + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-jen.jpg", alt="@jenthebest's testimonial image") + .testimonial-copy Getting back on track with Free Code Camp and committing to a new career in 2015! + h3 - @jenbestyoga + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-tate.jpg", alt="@TateThurston's testimonial image") + .testimonial-copy Just built my company's website with skills I've learned from Free Code Camp! + h3 - @TateThurston + .col-xs-12.col-sm-12.col-md-4 + img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-cynthia.jpg", alt="@cynthialanel's testimonial image") + .testimonial-copy I'm currently working through Free Code Camp to improve my JavaScript. The community is very welcoming! + h3 - @cynthialanel + .big-break + h2 Solutions we can help you build: + .text-center.negative-35 + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-globe + h2.black-text Websites + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-card + h2.black-text Donation Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-calendar + h2.black-text Volunteer Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-box + h2.black-text Inventory Systems + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-university + h2.black-text E-learning Platforms + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-list + h2.black-text Web Forms + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-people + h2.black-text Community Tools + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-settings + h2.black-text ...and other tools + br + br + .big-break + h2 Why you should join our community right now: + h3.col-xs-offset-0.col-sm-offset-1 + ul.text-left + li.ion-code   We're thousands of professionals, all learning to code together + li.ion-code   We're building projects for dozens of nonprofits + li.ion-code   Our community is 100% free and open source + li.ion-code   You'll learn Full Stack JavaScript and become a Software Engineer + li.ion-code   You'll work through our focused, interactive courses and tutorials + li.ion-code   You'll learn to code at your own pace, in your browser or on your phone + li.ion-code   You'll become qualified for thousands of jobs currently going unfilled + li.ion-code   You can get help in real time from our community chat rooms and forum + li.ion-code   We all share one common goal: to boost our careers with code + .big-break + a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") I'm with a nonprofit and want help coding something + script. + challengeName = 'Home' \ No newline at end of file From 5bfd8a8d5485c7bf5cdbf2e7b74584e624e6054d Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 23 Mar 2015 17:17:39 -0700 Subject: [PATCH 14/57] Handle user save err and respond to end route handler closes #260 --- app.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 5c5b748823..55850d9ab7 100644 --- a/app.js +++ b/app.js @@ -446,7 +446,7 @@ app.get('/sitemap.xml', resourcesController.sitemap); * and updates user.challengesHash & user.challengesCompleted * */ -app.post('/completed-challenge', function (req, res) { +app.post('/completed-challenge', function (req, res, done) { req.user.challengesHash[parseInt(req.body.challengeNumber)] = Math.round(+new Date() / 1000); var timestamp = req.user.challengesHash; @@ -457,7 +457,10 @@ app.post('/completed-challenge', function (req, res) { } } req.user.points = points; - req.user.save(); + req.user.save(function(err) { + if (err) { return done(err); } + res.status(200).send({ msg: 'progress saved' }); + }); }); /** From 0bf68fea5ffe377d3f196556b4e93f4fccd574a8 Mon Sep 17 00:00:00 2001 From: "A. Drake" Date: Mon, 23 Mar 2015 23:20:19 -0400 Subject: [PATCH 15/57] Links from database are being passed to the view. TODO create links --- controllers/bonfire.js | 23 +++++++++++++++++++++-- models/Bonfire.js | 3 ++- seed_data/bonfireMDNlinks.js | 22 ++++++++++++++++++++++ seed_data/bonfires.json | 3 ++- views/bonfire/show.jade | 1 + 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 seed_data/bonfireMDNlinks.js diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 83431a390d..e600375752 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -2,7 +2,8 @@ var _ = require('lodash'), debug = require('debug')('freecc:cntr:bonfires'), Bonfire = require('./../models/Bonfire'), User = require('./../models/User'), - resources = require('./resources'); + resources = require('./resources') + MDNlinks = require('./../seed_data/bonfireMDNlinks'); /** * Bonfire controller @@ -110,7 +111,8 @@ exports.returnIndividualBonfire = function(req, res, next) { phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), bonfires: bonfire, - bonfireHash: bonfire._id + bonfireHash: bonfire._id, + MDNlinks: getMDNlinks(bonfire.MDNlinks) }); }); @@ -147,6 +149,23 @@ function randomString() { return randomstring; }; +/** + * Helper function to populate the MDN links array. +*/ + +function getMDNlinks(links) { + // takes in an array of links, which are strings + var populatedLinks = []; + + // for each key value, push the corresponding link from the MDNlinks object into a new array + links.forEach(function(value, index) { + populatedLinks.push(MDNlinks[value]); + }); + + return populatedLinks; + +}; + /** * */ diff --git a/models/Bonfire.js b/models/Bonfire.js index cee4c8413a..8c0c4599ed 100644 --- a/models/Bonfire.js +++ b/models/Bonfire.js @@ -15,7 +15,8 @@ var bonfireSchema = new mongoose.Schema({ difficulty: String, description: Array, tests: Array, - challengeSeed: String + challengeSeed: String, + MDNlinks: [String] }); module.exports = mongoose.model('Bonfire', bonfireSchema); diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js new file mode 100644 index 0000000000..9d80585185 --- /dev/null +++ b/seed_data/bonfireMDNlinks.js @@ -0,0 +1,22 @@ +// MDN Links + +/* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links. + Making an effort to keep methods in alphabetical order will help others behind you. + +*/ +var links = + { + // ========= GLOBAL OBJECTS + "Array-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "Object-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "String-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", + + // ======== STRING METHODS + "string-split" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split", + + // ======== ARRAY METHODS + "array-join" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join", + "array-reverse": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse" + }; + +module.exports = links; diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index c298c97db7..00b9fa10bd 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -31,7 +31,8 @@ "You may need to turn the string into an array before you can reverse it.", "Your result must be a string." ], - "challengeSeed": "function reverseString(str) {\n return str;\r\n}\n\nreverseString('hello');" + "challengeSeed": "function reverseString(str) {\n return str;\r\n}\n\nreverseString('hello');", + "links" : ["String-global", "string-methods.split"] }, { "_id": "a302f7aae1aa3152a5b413bc", diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 63e6be052e..007caa6fc5 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -92,6 +92,7 @@ block content var started = Math.floor(Date.now() / 1000); var _ = R; var dashed = !{JSON.stringify(dashedName)}; + var MDNlinks = !{JSON.stringify(MDNlinks)}; .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code From dbc57dd0cb0f32f47b7a66a0d65dcf935b520796 Mon Sep 17 00:00:00 2001 From: "A. Drake" Date: Mon, 23 Mar 2015 23:55:24 -0400 Subject: [PATCH 16/57] links iterated through on the page. TODO formatting and adding more data. --- controllers/bonfire.js | 1 + seed_data/bonfireMDNlinks.js | 14 ++++++++------ views/bonfire/show.jade | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index e600375752..2615b5845b 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -112,6 +112,7 @@ exports.returnIndividualBonfire = function(req, res, next) { compliment: resources.randomCompliment(), bonfires: bonfire, bonfireHash: bonfire._id, + MDNkeys: bonfire.MDNlinks, MDNlinks: getMDNlinks(bonfire.MDNlinks) }); diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 9d80585185..1bec840ac9 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -2,21 +2,23 @@ /* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links. Making an effort to keep methods in alphabetical order will help others behind you. + + The text of the key is what the link text will be, e.g. Global Array Object */ var links = { // ========= GLOBAL OBJECTS - "Array-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", - "Object-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", - "String-global" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", + "Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", + "Global Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "Global String Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", // ======== STRING METHODS - "string-split" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split", + "String.split()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split", // ======== ARRAY METHODS - "array-join" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join", - "array-reverse": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse" + "Array.join()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join", + "Array.reverse()": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse" }; module.exports = links; diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 007caa6fc5..96c573a84a 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -66,8 +66,8 @@ block content .bonfire-instructions p= brief #brief-instructions - #more-info.btn.btn-primary.btn-block.btn-primary-ghost - span.ion-arrow-down-b + #more-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-down-b | More information #long-instructions.row.hide .col-xs-12 @@ -76,6 +76,7 @@ block content #less-info.btn.btn-primary.btn-block.btn-primary-ghost span.ion-arrow-up-b | Less information + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) br form.code @@ -92,7 +93,7 @@ block content var started = Math.floor(Date.now() / 1000); var _ = R; var dashed = !{JSON.stringify(dashedName)}; - var MDNlinks = !{JSON.stringify(MDNlinks)}; + .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code From 4dae91ff41dd717b5a9d52624c2fd064bbc2204a Mon Sep 17 00:00:00 2001 From: "A. Drake" Date: Mon, 23 Mar 2015 23:57:44 -0400 Subject: [PATCH 17/57] formatting done. --- views/bonfire/show.jade | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 96c573a84a..0fea3cd4b4 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -66,13 +66,17 @@ block content .bonfire-instructions p= brief #brief-instructions - #more-info.btn.btn-primary.btn-block.btn-primary-ghost - span.ion-arrow-down-b + #more-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-down-b | More information #long-instructions.row.hide .col-xs-12 for sentence in details p!= sentence + #MDN-links + h4 Here are some helpful links. + for link, index in MDNlinks + ul: li: a(href=""+link, target="_blank") !{MDNkeys[index]} #less-info.btn.btn-primary.btn-block.btn-primary-ghost span.ion-arrow-up-b | Less information From c4624b7f20b34870e516a2e0cd74a2e33c17aa93 Mon Sep 17 00:00:00 2001 From: "A. Drake" Date: Tue, 24 Mar 2015 01:17:21 -0400 Subject: [PATCH 18/57] links added, basic functionality working across pages. --- seed_data/bonfireMDNlinks.js | 50 +++++++++++++++++++++++++++++++++--- seed_data/bonfires.json | 44 ++++++++++++++++++++----------- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 1bec840ac9..e604b63919 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -1,10 +1,10 @@ // MDN Links /* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links. - Making an effort to keep methods in alphabetical order will help others behind you. + The text of the key is what the link text will be, e.g. Global Array Object - + General convention is to use the page title of the MDN reference page. */ var links = { @@ -12,13 +12,55 @@ var links = "Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", "Global Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", "Global String Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", + "Boolean Objects" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean", + + // ========= PROPERTIES/MISC + "String.length" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length", + "Arguments object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments", + + // ========== OBJECT METHODS + "Object.getOwnPropertyNames()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames", + "Object.keys()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys", + "Object.hasOwnProperty()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty", + // ======== STRING METHODS + "String.charAt()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt", + "String.charCodeAt()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt", + "String.concat()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat", + "String.indexOf()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf", + "String.lastIndexOf()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf", + "String.match()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match", + "String.replace()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace", + "String.slice()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice", "String.split()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split", - + "String.substring()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring", + "String.substr()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr", + "String.toLowerCase()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase", + "String.toString()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toString", + "String.toUpperCase()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase", // ======== ARRAY METHODS + "Array.concat()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat", + "Array.every()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every", + "Array.filter()": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter", + "Array.forEach()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach", + "Array.indexOf()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf", "Array.join()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join", - "Array.reverse()": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse" + "Array.lastIndexOf()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf", + "Array.map()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map", + "Array.pop()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop", + "Array.push()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push", + "Array.reduce()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce", + "Array.reverse()": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse", + "Array.slice()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice", + "Array.some()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some", + "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", + + // ======== GENERAL JAVASCRIPT REFERENCES + "Arithmetic Operators" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators", + "Comparison Operators" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators" }; module.exports = links; diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 00b9fa10bd..6678027611 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -32,7 +32,7 @@ "Your result must be a string." ], "challengeSeed": "function reverseString(str) {\n return str;\r\n}\n\nreverseString('hello');", - "links" : ["String-global", "string-methods.split"] + "MDNlinks" : ["Global String Object", "String.split()", "Array.reverse()", "Array.join()"] }, { "_id": "a302f7aae1aa3152a5b413bc", @@ -50,7 +50,8 @@ "Factorials are often represented with the shorthand notation n!", "For example: 5! = 1 * 2 * 3 * 4 * 5 = 120f" ], - "challengeSeed": "function factorialize(num) {\n return num;\r\n}\n\nfactorialize(5);" + "challengeSeed": "function factorialize(num) {\n return num;\r\n}\n\nfactorialize(5);", + "MDNlinks" : ["Arithmetic Operators"] }, { "_id": "aaa48de84e1ecc7c742e1124", @@ -71,7 +72,8 @@ "assert.deepEqual(palindrome(\"never odd or even\"), true);", "assert.deepEqual(palindrome(\"nope\"), false);" ], - "challengeSeed": "function palindrome(str) {\n // Good luck!\n return true;\n}\n\n\n\npalindrome(\"eye\");" + "challengeSeed": "function palindrome(str) {\n // Good luck!\n return true;\n}\n\n\n\npalindrome(\"eye\");", + "MDNlinks" : ["String.replace()", "String.toLowerCase()"] }, { "_id": "a26cbbe9ad8655a977e1ceb5", @@ -88,7 +90,8 @@ "expect(findLongestWord('May the force be with you')).to.equal(5);", "expect(findLongestWord('Google do a barrel roll')).to.equal(6);", "expect(findLongestWord('What is the average airspeed velocity of an unladen swallow')).to.equal(8);" - ] + ], + "MDNlinks" : ["String.split()", "String.length"] }, { "_id": "ab6137d4e35944e21037b769", @@ -104,7 +107,8 @@ "expect(titleCase(\"I'm a little tea pot\")).to.equal(\"I'm A Little Tea Pot\");", "expect(titleCase(\"sHoRt AnD sToUt\")).to.equal(\"Short And Stout\");", "expect(titleCase(\"HERE IS MY HANDLE HERE IS MY SPOUT\")).to.equal(\"Here Is My Handle Here Is My Spout\");" - ] + ], + "MDNlinks" : ["String.charAt()"] }, { "_id": "a789b3483989747d63b0e427", @@ -120,7 +124,8 @@ "expect(largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]])).to.be.a('array');", "(largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]])).should.eql([5,27,39,1001]);", "assert(largestOfFour([[4, 9, 1, 3], [13, 35, 18, 26], [32, 35, 97, 39], [1000000, 1001, 857, 1]]).should.eql([9,35,97,1000000]));" - ] + ], + "MDNlinks" : ["Comparison Operators"] }, { "_id": "acda2fb1324d9b0fa741e6b5", @@ -135,7 +140,8 @@ "assert.strictEqual(end('Bastian', 'n'), true, 'should equal true if target equals end of string');", "assert.strictEqual(end('He has to give me a new name', 'name'), true, 'should equal true if target equals end of string');", "assert.strictEqual(end('If you want to save our world, you must hurry. We dont know how much longer we can withstand the nothing', 'mountain'), false, 'should equal false if target does not equal end of string');" - ] + ], + "MDNlinks" : ["String.substr()"] }, { "_id": "afcc8d540bea9ea2669306b6", @@ -149,7 +155,8 @@ "assert.strictEqual(repeat('*', 3), '***', 'should repeat a string n times');", "assert.strictEqual(repeat('abc', 3), 'abcabcabc', 'should repeat a string n times');", "assert.strictEqual(repeat('abc', -2), '', 'should return an empty string for negative numbers');" - ] + ], + "MDNlinks" : ["Global String Object"] }, { "_id": "ac6993d51946422351508a41", @@ -164,7 +171,8 @@ "expect(truncate('A-tisket a-tasket A green and yellow basket', 11)).to.eqls('A-tisket...');", "assert(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length) === 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is = length');", "assert.strictEqual(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length + 2), 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is < length');" - ] + ], + "MDNlinks" : ["String.slice()"] }, { "_id": "a9bd25c716030ec90084d8a1", @@ -178,7 +186,8 @@ "assert.deepEqual(chunk(['a', 'b', 'c', 'd'], 2), [['a', 'b'], ['c', 'd']], 'should return chunked arrays');", "assert.deepEqual(chunk([0, 1, 2, 3, 4, 5], 3), [[0, 1, 2], [3, 4, 5]], 'should return chunked arrays');", "assert.deepEqual(chunk([0, 1, 2, 3, 4, 5], 4), [[0, 1, 2, 3], [4, 5]], 'should return cthe last chunk as remaining elements');" - ] + ], + "MDNlinks" : ["Array.push()"] }, { "_id": "ab31c21b530c0dafa9e241ee", @@ -192,7 +201,8 @@ "assert.deepEqual(slasher([1, 2, 3], 2), [3], 'should drop the first two elements');", "assert.deepEqual(slasher([1, 2, 3], 0), [1, 2, 3], 'should return all elements when n < 1');", "assert.deepEqual(slasher([1, 2, 3], 9), [], 'should return an empty array when n >= array.length');" - ] + ], + "MDNlinks" : ["Array.slice()", "Array.splice()"] }, { "_id": "af2170cad53daa0770fabdea", @@ -211,7 +221,8 @@ "expect(mutation(['zyxwvutsrqponmlkjihgfedcba', 'qrstu'])).to.be.true;", "expect(mutation(['Mary', 'Army'])).to.be.true;", "expect(mutation(['Alien', 'line'])).to.be.true;" - ] + ], + "MDNlinks" : ["Array.sort()"] }, { "_id": "adf08ec01beb4f99fc7a68f2", @@ -226,7 +237,8 @@ "assert.deepEqual(bouncer([7, 'ate', '', false, 9]), [7, 'ate', 9], 'should remove falsey values');", "assert.deepEqual(bouncer(['a', 'b', 'c']), ['a', 'b', 'c'], 'should return full array if no falsey elements');", "assert.deepEqual(bouncer([false, null, 0]), [], 'should return empty array if all elements are falsey');" - ] + ], + "MDNlinks" : ["Boolean Objects", "Array.filter()"] }, { "_id":"a8e512fbe388ac2f9198f0fa", @@ -239,7 +251,8 @@ "tests":[ "assert.deepEqual(where([{ first: 'Romeo', last: 'Montague' }, { first: 'Mercutio', last: null }, { first: 'Tybalt', last: 'Capulet' }], { last: 'Capulet' }), [{ first: 'Tybalt', last: 'Capulet' }], 'should return an array of objects');", "assert.deepEqual(where([{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], 'should return with multiples');" - ] + ], + "MDNlinks" : ["Global Object", "Object.hasOwnProperty()", "Object.keys()"] }, { "_id":"a39963a4c10bc8b4d4f06d7e", @@ -252,7 +265,8 @@ "tests": [ "assert.deepEqual(destroyer([1, 2, 3, 1, 2, 3], 2, 3), [1, 1], 'should remove correct values from an array');", "assert.deepEqual(destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3), [1, 5, 1], 'should remove correct values from an array');" - ] + ], + "MDNlinks" : ["Array.filter()"] }, { "_id": "a24c1a4622e3c05097f71d67", From b3174eeadf5e831c96adc6549ac52c5448678f7c Mon Sep 17 00:00:00 2001 From: "A. Drake" Date: Tue, 24 Mar 2015 01:24:06 -0400 Subject: [PATCH 19/57] If no links are present, the template does not show the helpful links header. --- views/bonfire/show.jade | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 0fea3cd4b4..aed449bdb5 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -149,5 +149,9 @@ block content $('.btn-twitter').attr('href', url); } ); + var MDNlinks = !{JSON.stringify(MDNlinks)}; + if (!MDNlinks.length) { + $('#MDN-links').addClass('collapse'); + } From d0a00428d57ad9f36e8ab300a2b080ce66c0fef1 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 24 Mar 2015 18:26:02 +0900 Subject: [PATCH 20/57] Refactor story.js to use sendStatus instead of status, ensure returning on next(err) --- controllers/story.js | 47 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 29f663654a..a90694d6d6 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -1,3 +1,4 @@ +/* eslint-disable no-catch-shadow, no-unused-vars */ var R = require('ramda'), debug = require('debug')('freecc:cntr:story'), Story = require('./../models/Story'), @@ -28,7 +29,7 @@ exports.hotJSON = function(req, res) { var story = Story.find({}).sort({'timePosted': -1}).limit(1000); story.exec(function(err, stories) { if (err) { - return res.status(500); + return res.sendStatus(500); } var foundationDate = 1413298800000; @@ -48,36 +49,36 @@ exports.recentJSON = function(req, res, next) { var story = Story.find({}).sort({'timePosted': -1}).limit(100); story.exec(function(err, stories) { if (err) { - res.status(500); + res.sendStatus(500); return next(err); } - res.json(stories); + return res.json(stories); }); }; exports.hot = function(req, res) { - res.render('stories/index', { + return res.render('stories/index', { title: 'Hot stories currently trending on Camper News', page: 'hot' }); }; exports.submitNew = function(req, res) { - res.render('stories/index', { + return res.render('stories/index', { title: 'Submit a new story to Camper News', page: 'submit' }); }; exports.search = function(req, res) { - res.render('stories/index', { + return res.render('stories/index', { title: 'Search the archives of Camper News', page: 'search' }); }; exports.recent = function(req, res) { - res.render('stories/index', { + return res.render('stories/index', { title: 'Recently submitted stories on Camper News', page: 'recent' }); @@ -121,7 +122,7 @@ exports.returnIndividualStory = function(req, res, next) { Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) { if (err) { - next(err); + return next(err); } @@ -171,7 +172,7 @@ exports.returnIndividualStory = function(req, res, next) { exports.getStories = function(req, res) { MongoClient.connect(secrets.db, function(err, database) { if (err) { - return res.status(500); + return res.sendStatus(500); } database.collection('stories').find({ '$text': { @@ -200,12 +201,12 @@ exports.getStories = function(req, res) { } }).toArray(function(err, items) { if (err) { - return res.status(500); + return res.sendStatus(500); } if (items !== null && items.length !== 0) { return res.json(items); } - return res.status(404); + return res.sendStatus(404); }); }); }; @@ -214,7 +215,7 @@ exports.upvote = function(req, res, next) { var data = req.body.data; Story.find({'_id': data.id}, function(err, story) { if (err) { - res.status(500); + res.sendStatus(500); return next(err); } story = story.pop(); @@ -244,7 +245,7 @@ exports.comments = function(req, res, next) { var data = req.params.id; Comment.find({'_id': data}, function(err, comment) { if (err) { - res.status(500); + res.sendStatus(500); return next(err); } comment = comment.pop(); @@ -254,7 +255,7 @@ exports.comments = function(req, res, next) { exports.newStory = function(req, res) { if (!req.user) { - return res.status(500); + return res.sendStatus(500); } var url = req.body.data.url; var cleanURL = sanitizeHtml(url, { @@ -276,7 +277,7 @@ exports.newStory = function(req, res) { } Story.find({'link': url}, function(err, story) { if (err) { - return res.status(500); + return res.sendStatus(500); } if (story.length) { req.flash('errors', { @@ -314,7 +315,7 @@ exports.newStory = function(req, res) { exports.storySubmission = function(req, res) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); + return res.sendStatus(500); } var storyLink = data.headline .replace(/\'/g, '') @@ -352,7 +353,7 @@ exports.storySubmission = function(req, res) { story.save(function(err) { if (err) { - return res.status(500); + return res.sendStatus(500); } res.send(JSON.stringify({ storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() @@ -363,7 +364,7 @@ exports.storySubmission = function(req, res) { exports.commentSubmit = function(req, res) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); + return res.sendStatus(500); } var sanitizedBody = sanitizeHtml(data.body, { @@ -393,7 +394,7 @@ exports.commentOnCommentSubmit = function(req, res) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.status(500); + return res.sendStatus(500); } var sanitizedBody = sanitizeHtml(data.body, @@ -423,19 +424,19 @@ exports.commentOnCommentSubmit = function(req, res) { function commentSave(comment, Context, res) { comment.save(function(err, data) { if (err) { - return res.status(500); + return res.sendStatus(500); } try { Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { if (err) { - return res.status(500); + return res.sendStatus(500); } associatedStory = associatedStory.pop(); if (associatedStory) { associatedStory.comments.push(data._id); associatedStory.save(function (err) { if (err) { - res.status(500); + return res.sendStatus(500); } res.send(true); }); @@ -443,7 +444,7 @@ function commentSave(comment, Context, res) { }); } catch (e) { // delete comment - return res.status(500); + return res.sendStatus(500); } }); } From 83d23ed2a4b7b210639f4e1a9ba7644bd6e53505 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 24 Mar 2015 18:28:02 +0900 Subject: [PATCH 21/57] Remove sending 500 response to user manually and rely on middleware instead --- controllers/story.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index a90694d6d6..34a85a6669 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -49,7 +49,6 @@ exports.recentJSON = function(req, res, next) { var story = Story.find({}).sort({'timePosted': -1}).limit(100); story.exec(function(err, stories) { if (err) { - res.sendStatus(500); return next(err); } return res.json(stories); @@ -215,7 +214,6 @@ exports.upvote = function(req, res, next) { var data = req.body.data; Story.find({'_id': data.id}, function(err, story) { if (err) { - res.sendStatus(500); return next(err); } story = story.pop(); @@ -245,7 +243,6 @@ exports.comments = function(req, res, next) { var data = req.params.id; Comment.find({'_id': data}, function(err, comment) { if (err) { - res.sendStatus(500); return next(err); } comment = comment.pop(); From a6ce7008317d06e032339db3b0dc5254115a10d4 Mon Sep 17 00:00:00 2001 From: Julie Myers Date: Tue, 24 Mar 2015 04:26:39 -0600 Subject: [PATCH 22/57] Add chai-jquery to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 732eb86787..dadcd03961 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "async": "^0.9.0", "bcrypt-nodejs": "^0.0.3", "body-parser": "^1.9.3", + "chai-jquery": "^2.0.0", "cheerio": "^0.18.0", "clockwork": "^0.1.1", "compression": "^1.2.1", From 2b0cc9175496ff90b94ef2dfd5e082720efce344 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 24 Mar 2015 08:03:59 -0700 Subject: [PATCH 23/57] Add production error handler --- app.js | 44 +++++++++++++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 55850d9ab7..ecbdc18fd5 100644 --- a/app.js +++ b/app.js @@ -15,6 +15,7 @@ process.on('uncaughtException', function (err) { }); var express = require('express'), + accepts = require('accepts'), cookieParser = require('cookie-parser'), compress = require('compression'), session = require('express-session'), @@ -524,17 +525,54 @@ app.get( } ); -//put this route last +app.get('/induce-vomiting', function(req, res, next) { + next(new Error('vomiting induced')); +}); + +// put this route last app.get( '/:username', userController.returnUser ); - /** * 500 Error Handler. */ -app.use(errorHandler()); +if (process.env.NODE_ENV === 'development') { + app.use(errorHandler({ log: true })); +} else { + // error handling in production + app.use(function(err, req, res, next) { + + // respect err.status + if (err.status) { + res.statusCode = err.status; + } + + // default status code to 500 + if (res.statusCode < 400) { + res.statusCode = 500; + } + + // parse res type + var accept = accepts(req); + var type = accept.type('html', 'json', 'text'); + + var message = 'opps! Something went wrong. Please try again later'; + if (type === 'html') { + req.flash('errors', { msg: message }); + return res.redirect('/'); + // json + } else if (type === 'json') { + res.setHeader('Content-Type', 'application/json'); + return res.send({ message: message }); + // plain text + } else { + res.setHeader('Content-Type', 'text/plain'); + return res.send(message); + } + }); +} /** * Start Express server. diff --git a/package.json b/package.json index dadcd03961..ffe8c6281b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "postinstall": "node seed_data/seed.js" }, "dependencies": { + "accepts": "^1.2.5", "async": "^0.9.0", "bcrypt-nodejs": "^0.0.3", "body-parser": "^1.9.3", From 482cc3fa7b477800353448f4f54c9edcd3ee0027 Mon Sep 17 00:00:00 2001 From: Geoff Storbeck Date: Tue, 24 Mar 2015 15:58:17 -0400 Subject: [PATCH 24/57] fixed typo for issue #266 --- seed_data/bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index c298c97db7..a686f7e7f7 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -176,7 +176,7 @@ "tests": [ "assert.deepEqual(chunk(['a', 'b', 'c', 'd'], 2), [['a', 'b'], ['c', 'd']], 'should return chunked arrays');", "assert.deepEqual(chunk([0, 1, 2, 3, 4, 5], 3), [[0, 1, 2], [3, 4, 5]], 'should return chunked arrays');", - "assert.deepEqual(chunk([0, 1, 2, 3, 4, 5], 4), [[0, 1, 2, 3], [4, 5]], 'should return cthe last chunk as remaining elements');" + "assert.deepEqual(chunk([0, 1, 2, 3, 4, 5], 4), [[0, 1, 2, 3], [4, 5]], 'should return the last chunk as remaining elements');" ] }, { From bcec96e427462c5069d72bc2d900c375b15717df Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 24 Mar 2015 19:08:26 -0700 Subject: [PATCH 25/57] half way done with the initial views and controllers --- app.js | 5 ++++- controllers/basejumps.js | 21 +++++++++++++++++++ controllers/nonprofits.js | 21 +++++++++++++++++++ controllers/ziplines.js | 21 +++++++++++++++++++ public/css/main.less | 4 ++++ views/home.jade | 2 +- views/nonprofits/home.jade | 43 ++++++++++++++++++-------------------- 7 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 controllers/basejumps.js create mode 100644 controllers/nonprofits.js create mode 100644 controllers/ziplines.js diff --git a/app.js b/app.js index 7f15966228..b843772a06 100644 --- a/app.js +++ b/app.js @@ -27,10 +27,12 @@ var express = require('express'), * Controllers (route handlers). */ homeController = require('./controllers/home'), - challengesController = require('./controllers/challenges'), resourcesController = require('./controllers/resources'), userController = require('./controllers/user'), contactController = require('./controllers/contact'), + ziplineController = require('./controllers/ziplines'), + basejumpController = require('./controllers/basejumps'), + nonprofitController = require('./controllers/nonprofits'), bonfireController = require('./controllers/bonfire'), coursewareController = require('./controllers/courseware'), @@ -264,6 +266,7 @@ app.post('/email-signup', userController.postEmailSignup); app.post('/email-signin', userController.postSignin); app.get('/nonprofits', contactController.getNonprofitsForm); app.post('/nonprofits', contactController.postNonprofitsForm); +app.get('/nonprofits/home', nonprofitController.nonprofitsHome); app.get( '/done-with-first-100-hours', diff --git a/controllers/basejumps.js b/controllers/basejumps.js new file mode 100644 index 0000000000..e9687201a6 --- /dev/null +++ b/controllers/basejumps.js @@ -0,0 +1,21 @@ +var async = require('async'), + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); + +nonprofitHome: function nonprofitHome(req, res) { + res.render('nonprofits/home', { + title: 'A guide to our Nonprofit Projects' + }); +} \ No newline at end of file diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js new file mode 100644 index 0000000000..24f5c7bdb4 --- /dev/null +++ b/controllers/nonprofits.js @@ -0,0 +1,21 @@ +var async = require('async'), + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); + +exports.nonprofitsHome = function(req, res) { + res.render('nonprofits/home', { + title: 'A guide to our Nonprofit Projects' + }); +}; \ No newline at end of file diff --git a/controllers/ziplines.js b/controllers/ziplines.js new file mode 100644 index 0000000000..e9687201a6 --- /dev/null +++ b/controllers/ziplines.js @@ -0,0 +1,21 @@ +var async = require('async'), + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); + +nonprofitHome: function nonprofitHome(req, res) { + res.render('nonprofits/home', { + title: 'A guide to our Nonprofit Projects' + }); +} \ No newline at end of file diff --git a/public/css/main.less b/public/css/main.less index 5b136853e5..138c6e2c8b 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -335,6 +335,10 @@ ul { margin-bottom: -10px; } +.nonprofit-landing { + font-size: 50px; +} + .big-text { font-size: 63px; } diff --git a/views/home.jade b/views/home.jade index 70da30cc7e..cb76c768f1 100644 --- a/views/home.jade +++ b/views/home.jade @@ -25,7 +25,7 @@ block content a.btn.btn-cta.signup-btn(href="/login") Start learning to code (it's free) br br - a.btn.nonprofit-cta.btn-success(href="/nonprofits") I'm with a nonprofit and want help coding something + a.btn.nonprofit-cta.btn-success(href="/nonprofits") Get pro bono help for my nonprofit .big-break h2 Campers you'll hang out with: .row diff --git a/views/nonprofits/home.jade b/views/nonprofits/home.jade index 41f080b21a..832895e45e 100644 --- a/views/nonprofits/home.jade +++ b/views/nonprofits/home.jade @@ -1,30 +1,10 @@ -extends layout +extends ../layout block content .jumbotron .text-center - h1.hug-top We code for a cause - h2 We'll code software solutions for your nonprofit, for free! - .row - .col-xs-12.col-sm-12.col-md-3 - h3.nowrap Get Connected - img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_connect.svg.gz', title='Get great references and connections to help you get a job') - p.landing-p Join a community of busy, motivated professionals. - .col-xs-12.col-sm-12.col-md-3 - h3.nowrap Learn JavaScript - img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_learn.svg.gz', title='Learn to code') - p.landing-p Work together on Full Stack JavaScript coding challenges. - .col-xs-12.col-sm-12.col-md-3 - h3.nowrap Build your Portfolio - img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_portfolio.svg.gz', title='Build a portfolio of apps for nonprofits') - p.landing-p Build apps that solve real problems for real people. - .col-xs-12.col-sm-12.col-md-3 - h3.nowrap Help Nonprofits - img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_nonprofits.svg.gz', title='Help nonprofits') - p.landing-p Give nonprofits a boost by empowering them with code. + h2.nonprofit-landing.hug-top We'll code for your nonprofit, pro bono .big-break - a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") I'm with a nonprofit and want help coding something - .big-break - h2 Nonprofits we've helped + h2 Some of our success stories .row .col-xs-12.col-sm-12.col-md-4 img.img-responsive.testimonial-image.img-center(src="https://s3.amazonaws.com/freecodecamp/testimonial-jen.jpg", alt="@jenthebest's testimonial image") @@ -39,6 +19,23 @@ block content .testimonial-copy I'm currently working through Free Code Camp to improve my JavaScript. The community is very welcoming! h3 - @cynthialanel .big-break + a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") Get pro bono help for my nonprofit + .big-break + h2 Our process + .row + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Your idea + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_portfolio.svg.gz', title='Get great references and connections to help you get a job') + p.landing-p You tell us how we can help you. + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Our team + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_nonprofits.svg.gz', title='Build a portfolio of apps for nonprofits') + p.landing-p We'll hand pick developers and a project manager. + .col-xs-12.col-sm-12.col-md-4 + h3.nowrap Your solution + img.img-responsive.landing-icon.img-center(src= 'https://s3.amazonaws.com/freecodecamp/landingIcons_connect.svg.gz', title='Help nonprofits') + p.landing-p Together we'll set milestones and complete your project. + .big-break h2 Solutions we can help you build: .text-center.negative-35 .col-xs-12.col-sm-12.col-md-3 From d4935d44ded2a753cb3a26dcc46c50e1b1142b1e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 24 Mar 2015 21:46:42 -0700 Subject: [PATCH 26/57] add the routes, controller actions and views for the nonprofit wizard --- app.js | 12 +++- controllers/nonprofits.js | 62 ++++++++++++++++++- public/css/main.less | 5 ++ public/js/application.js | 2 +- ...already-benefiting-from-your-services.jade | 12 ++++ .../are-you-with-a-registered-nonprofit.jade | 12 ++++ views/nonprofits/home.jade | 4 +- .../how-can-free-code-camp-help-you.jade | 42 +++++++++++++ views/nonprofits/in-exchange-we-ask.jade | 16 +++++ views/nonprofits/ok-with-javascript.jade | 12 ++++ views/nonprofits/other-solutions.jade | 12 ++++ .../tell-us-your-name-and-email.jade | 0 .../what-does-your-nonprofit-do.jade | 23 +++++++ ...roject-application-has-been-submitted.jade | 0 14 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade create mode 100644 views/nonprofits/are-you-with-a-registered-nonprofit.jade create mode 100644 views/nonprofits/how-can-free-code-camp-help-you.jade create mode 100644 views/nonprofits/in-exchange-we-ask.jade create mode 100644 views/nonprofits/ok-with-javascript.jade create mode 100644 views/nonprofits/other-solutions.jade create mode 100644 views/nonprofits/tell-us-your-name-and-email.jade create mode 100644 views/nonprofits/what-does-your-nonprofit-do.jade create mode 100644 views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade diff --git a/app.js b/app.js index b843772a06..3de9ae9f85 100644 --- a/app.js +++ b/app.js @@ -98,7 +98,7 @@ app.use(session({ secret: secrets.sessionSecret, store: new MongoStore({ url: secrets.db, - 'auto_reconnect': true + 'autoReconnect': true }) })); app.use(passport.initialize()); @@ -267,6 +267,16 @@ app.post('/email-signin', userController.postSignin); app.get('/nonprofits', contactController.getNonprofitsForm); app.post('/nonprofits', contactController.postNonprofitsForm); app.get('/nonprofits/home', nonprofitController.nonprofitsHome); +app.get('/nonprofits/are-you-with-a-registered-nonprofit', nonprofitController.areYouWithARegisteredNonprofit); +app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); +app.get('/nonprofits/are-there-people-already-benefiting-from-your-services', nonprofitController.areTherePeopleAlreadyBenefitingFromYourServices); +app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); +app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); +app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); +app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); +app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); +app.get('/nonprofits/tell-us-your-name-and-email', nonprofitController.tellUsYourNameAndEmail); +app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', nonprofitController.yourNonprofitProjectApplicationHasBeenSubmitted); app.get( '/done-with-first-100-hours', diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 24f5c7bdb4..3ac9eb4508 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -18,4 +18,64 @@ exports.nonprofitsHome = function(req, res) { res.render('nonprofits/home', { title: 'A guide to our Nonprofit Projects' }); -}; \ No newline at end of file +}; + +exports.areYouWithARegisteredNonprofit = function(req, res) { + res.render('nonprofits/are-you-with-a-registered-nonprofit', { + title: 'Are you with a with a registered nonprofit' + }); +}; + +exports.howCanFreeCodeCampHelpYou = function(req, res) { + res.render('nonprofits/how-can-free-code-camp-help-you', { + title: 'Are you with a with a registered nonprofit' + }); +}; + +exports.otherSolutions = function(req, res) { + res.render('nonprofits/other-solutions', { + title: 'Here are some other possible solutions for you' + }); +}; + +exports.areTherePeopleAlreadyBenefitingFromYourServices = function(req, res) { + res.render('nonprofits/are-there-people-already-benefiting-from-your-services', { + title: 'Are there people already benefiting from your services' + }); +}; + +exports.inExchangeWeAsk = function(req, res) { + res.render('nonprofits/in-exchange-we-ask', { + title: 'In exchange we ask that you ...' + }); +}; + +exports.okWithJavaScript = function(req, res) { + res.render('nonprofits/ok-with-javascript', { + title: 'Are you OK with us using JavaScript' + }); +}; + +exports.whatDoesYourNonprofitDo = function(req, res) { + res.render('nonprofits/what-does-your-nonprofit-do', { + title: 'What does your nonprofit do?' + }); +}; + +exports.linkUsToYourWebsite = function(req, res) { + res.render('nonprofits/link-us-to-your-website', { + title: 'Link us to your website' + }); +}; + +exports.tellUsYourNameAndEmail = function(req, res) { + res.render('nonprofits/tell-us-your-name-and-email', { + title: 'Tell us your name and email address' + }); +}; + +exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { + res.render('nonprofits/your-nonprofit-project-application-has-been-submitted', { + title: 'Your Nonprofit Project application has been submitted!' + }); +}; diff --git a/public/css/main.less b/public/css/main.less index 138c6e2c8b..09ebb557ea 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -735,6 +735,11 @@ iframe.iphone { } } +.nonprofit-help-select-text-height { + font-size: 40px; + padding-top: 20px; +} + // To adjust right margin, negative values bring the image closer to the edge of the screen .iphone-position { position: absolute; diff --git a/public/js/application.js b/public/js/application.js index eeebfa316b..bf07cd484f 100644 --- a/public/js/application.js +++ b/public/js/application.js @@ -16,4 +16,4 @@ //= require lib/jquery-2.1.1.min //= require lib/bootstrap.min //= require lib/moment/moment -//= require main +//= require main \ No newline at end of file diff --git a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade new file mode 100644 index 0000000000..63d99b8dd6 --- /dev/null +++ b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 We build solutions for nonprofits who are already serving a need. Are there people who already benefit from your services? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/') No \ No newline at end of file diff --git a/views/nonprofits/are-you-with-a-registered-nonprofit.jade b/views/nonprofits/are-you-with-a-registered-nonprofit.jade new file mode 100644 index 0000000000..ce778cec2a --- /dev/null +++ b/views/nonprofits/are-you-with-a-registered-nonprofit.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 Do you represent a nonprofit organization that is registered with your government? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/home.jade b/views/nonprofits/home.jade index 832895e45e..b76a306f25 100644 --- a/views/nonprofits/home.jade +++ b/views/nonprofits/home.jade @@ -19,7 +19,7 @@ block content .testimonial-copy I'm currently working through Free Code Camp to improve my JavaScript. The community is very welcoming! h3 - @cynthialanel .big-break - a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") Get pro bono help for my nonprofit + a.btn.btn-cta.signup-btn(href="/nonprofits/are-you-with-a-registered-nonprofit") Get pro bono help for my nonprofit .big-break h2 Our process .row @@ -78,6 +78,6 @@ block content li.ion-code   You can get help in real time from our community chat rooms and forum li.ion-code   We all share one common goal: to boost our careers with code .big-break - a.btn.btn-cta.signup-btn(href="/with-a-nonprofit") I'm with a nonprofit and want help coding something + a.btn.btn-cta.signup-btn(href="/nonprofits/are-you-with-a-registered-nonprofit") Get pro bono help for my nonprofit script. challengeName = 'Home' \ No newline at end of file diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade new file mode 100644 index 0000000000..6fac5ad44e --- /dev/null +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -0,0 +1,42 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 How can Free Code Camp help you? + .spacer + .row.text-center.negative-35 + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-globe + label.black-text.nonprofit-help-select-text-height Websites + input#websites-help.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-card + h2.black-text.nonprofit-help-select-text-height Donation Systems + input#websites-help.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-android-calendar + h2.black-text.nonprofit-help-select-text-height Volunteer Systems + input#websites-help.inline.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-box + h2.black-text.nonprofit-help-select-text-height Inventory Systems + input#websites-help.inline.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-university + h2.black-text.nonprofit-help-select-text-height E-learning Platforms + input#websites-help.inline.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-list + h2.black-text.nonprofit-help-select-text-height Web Forms + input#websites-help.inline.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-ios-people + h2.black-text.nonprofit-help-select-text-height Community Tools + input#websites-help.inline.checkbox(type='checkbox') + .col-xs-12.col-sm-12.col-md-3 + .landing-skill-icon.ion-settings + h2.black-text.nonprofit-help-select-text-height ...and other tools + input#websites-help.inline.checkbox(type='checkbox') + .spacer + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/') I've selected all that apply and am ready to move on \ No newline at end of file diff --git a/views/nonprofits/in-exchange-we-ask.jade b/views/nonprofits/in-exchange-we-ask.jade new file mode 100644 index 0000000000..0dcb363d0c --- /dev/null +++ b/views/nonprofits/in-exchange-we-ask.jade @@ -0,0 +1,16 @@ +extends ../layout +block content + .jumbotron.text-left + h1.hug-top Nonprofit Sign Up + .spacer + h2 Great! In exchange for our help, we ask only that you: + ol + li Appoint one principal stakeholder to serve on behalf of your organization. + li Communicate with our campers on a regular basis, to answer questions and provide them with direction. + li Commit to using the solution that our campers build for your nonprofit. + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/ok-with-javascript.jade b/views/nonprofits/ok-with-javascript.jade new file mode 100644 index 0000000000..10c1dc9841 --- /dev/null +++ b/views/nonprofits/ok-with-javascript.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 Our campers are learning to code using modern full stack JavaScript technologies (MongoDB, Express, Angular, Node.js). We do not build or maintain Wordpress, Drupal, or other non-JavaScript based frameworks. + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/other-solutions.jade b/views/nonprofits/other-solutions.jade new file mode 100644 index 0000000000..4f58b76668 --- /dev/null +++ b/views/nonprofits/other-solutions.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 Do you represent a nonprofit organization that is registered with your government? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-name-and-email.jade b/views/nonprofits/tell-us-your-name-and-email.jade new file mode 100644 index 0000000000..e69de29bb2 diff --git a/views/nonprofits/what-does-your-nonprofit-do.jade b/views/nonprofits/what-does-your-nonprofit-do.jade new file mode 100644 index 0000000000..3718e46170 --- /dev/null +++ b/views/nonprofits/what-does-your-nonprofit-do.jade @@ -0,0 +1,23 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 In 140 characters or less, what does your nonprofit do? For whom? + .spacer + .input-group + input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit + #textarea-comment-feedback +script. + var text_max = 140; + $('#textarea-comment-feedback').html(text_max + ' characters remaining'); + $('#comment-to-comment-textinput').keyup(function (e) { + if (e.which === 13 || e === 13) { + $('#submit-comment-to-comment').click(); + } + var text_length = $('#comment-to-comment-textinput').val().length; + var text_remaining = text_max - text_length; + $('#textarea-comment-feedback').html(text_remaining + ' characters remaining'); + }); \ No newline at end of file diff --git a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade new file mode 100644 index 0000000000..e69de29bb2 From 6e4067ee0034dd0bf5026f5b5177b1a146810dae Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 24 Mar 2015 22:12:16 -0700 Subject: [PATCH 27/57] most views are now in place --- app.js | 2 +- controllers/nonprofits.js | 4 +-- ...already-benefiting-from-your-services.jade | 4 +-- .../how-can-free-code-camp-help-you.jade | 2 +- views/nonprofits/in-exchange-we-ask.jade | 15 ++++---- views/nonprofits/link-us-to-your-website.jade | 10 ++++++ views/nonprofits/ok-with-javascript.jade | 2 +- views/nonprofits/other-solutions.jade | 11 ++---- .../what-does-your-nonprofit-do.jade | 35 ++++++++++--------- 9 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 views/nonprofits/link-us-to-your-website.jade diff --git a/app.js b/app.js index 3de9ae9f85..b4b047c80d 100644 --- a/app.js +++ b/app.js @@ -269,7 +269,7 @@ app.post('/nonprofits', contactController.postNonprofitsForm); app.get('/nonprofits/home', nonprofitController.nonprofitsHome); app.get('/nonprofits/are-you-with-a-registered-nonprofit', nonprofitController.areYouWithARegisteredNonprofit); app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); -app.get('/nonprofits/are-there-people-already-benefiting-from-your-services', nonprofitController.areTherePeopleAlreadyBenefitingFromYourServices); +app.get('/nonprofits/are-there-people-that-are-already-benefiting-from-your-services', nonprofitController.areTherePeopleThatAreAlreadyBenefitingFromYourServices); app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 3ac9eb4508..8ea6a0f295 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -38,8 +38,8 @@ exports.otherSolutions = function(req, res) { }); }; -exports.areTherePeopleAlreadyBenefitingFromYourServices = function(req, res) { - res.render('nonprofits/are-there-people-already-benefiting-from-your-services', { +exports.areTherePeopleThatAreAlreadyBenefitingFromYourServices = function(req, res) { + res.render('nonprofits/are-there-people-that-are-already-benefiting-from-your-services', { title: 'Are there people already benefiting from your services' }); }; diff --git a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade index 63d99b8dd6..e9e18c1b3b 100644 --- a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade +++ b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade @@ -7,6 +7,6 @@ block content .spacer .row .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/') Yes + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/ok-with-javascript') Yes .col-xs-6 - a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/') No \ No newline at end of file + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade index 6fac5ad44e..deb2ad8f77 100644 --- a/views/nonprofits/how-can-free-code-camp-help-you.jade +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -39,4 +39,4 @@ block content h2.black-text.nonprofit-help-select-text-height ...and other tools input#websites-help.inline.checkbox(type='checkbox') .spacer - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/') I've selected all that apply and am ready to move on \ No newline at end of file + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/are-there-people-that-are-already-benefiting-from-your-services') I've selected all that apply and am ready to move on \ No newline at end of file diff --git a/views/nonprofits/in-exchange-we-ask.jade b/views/nonprofits/in-exchange-we-ask.jade index 0dcb363d0c..87e6a90181 100644 --- a/views/nonprofits/in-exchange-we-ask.jade +++ b/views/nonprofits/in-exchange-we-ask.jade @@ -1,16 +1,17 @@ extends ../layout block content - .jumbotron.text-left - h1.hug-top Nonprofit Sign Up + .jumbotron + h1.hug-top.text-center Nonprofit Sign Up .spacer h2 Great! In exchange for our help, we ask only that you: - ol - li Appoint one principal stakeholder to serve on behalf of your organization. - li Communicate with our campers on a regular basis, to answer questions and provide them with direction. - li Commit to using the solution that our campers build for your nonprofit. + h3 + ol + li Appoint one principal stakeholder to serve on behalf of your organization. + li Communicate with our campers on a regular basis, to answer questions and provide them with direction. + li Commit to using the solution that our campers build for your nonprofit. .spacer .row .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/what-does-your-nonprofit-do') Sounds good! .col-xs-6 a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/link-us-to-your-website.jade b/views/nonprofits/link-us-to-your-website.jade new file mode 100644 index 0000000000..481f527141 --- /dev/null +++ b/views/nonprofits/link-us-to-your-website.jade @@ -0,0 +1,10 @@ +.jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 In 140 characters or less, what does your nonprofit do? For whom? + .spacer + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/tell-us-your-name') Submit \ No newline at end of file diff --git a/views/nonprofits/ok-with-javascript.jade b/views/nonprofits/ok-with-javascript.jade index 10c1dc9841..adeee64a43 100644 --- a/views/nonprofits/ok-with-javascript.jade +++ b/views/nonprofits/ok-with-javascript.jade @@ -7,6 +7,6 @@ block content .spacer .row .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/in-exchange-we-ask') Sounds good! .col-xs-6 a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/other-solutions.jade b/views/nonprofits/other-solutions.jade index 4f58b76668..b9a19a3b36 100644 --- a/views/nonprofits/other-solutions.jade +++ b/views/nonprofits/other-solutions.jade @@ -1,12 +1,5 @@ extends ../layout block content .jumbotron.text-center - h1.hug-top Nonprofit Sign Up - .spacer - h2 Do you represent a nonprofit organization that is registered with your government? - .spacer - .row - .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes - .col-xs-6 - a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file + h1.hug-top Here are some other solutions we recommend + .spacer \ No newline at end of file diff --git a/views/nonprofits/what-does-your-nonprofit-do.jade b/views/nonprofits/what-does-your-nonprofit-do.jade index 3718e46170..92b65b17f4 100644 --- a/views/nonprofits/what-does-your-nonprofit-do.jade +++ b/views/nonprofits/what-does-your-nonprofit-do.jade @@ -5,19 +5,22 @@ block content .spacer h2 In 140 characters or less, what does your nonprofit do? For whom? .spacer - .input-group - input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') - span.input-group-btn - button.btn.btn-big.btn-primary.btn-responsive Submit - #textarea-comment-feedback -script. - var text_max = 140; - $('#textarea-comment-feedback').html(text_max + ' characters remaining'); - $('#comment-to-comment-textinput').keyup(function (e) { - if (e.which === 13 || e === 13) { - $('#submit-comment-to-comment').click(); - } - var text_length = $('#comment-to-comment-textinput').val().length; - var text_remaining = text_max - text_length; - $('#textarea-comment-feedback').html(text_remaining + ' characters remaining'); - }); \ No newline at end of file + .formgroup + .input-group + input.form-control.big-text-field.field-responsive#what-does-the-nonprofit-do(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/link-us-to-your-website') Submit + .text-left + span#what-does-the-nonprofit-do-feedback + + script. + var text_max = 140; + $('#what-does-the-nonprofit-do-feedback').html(text_max + ' characters remaining'); + $('#what-does-the-nonprofit-do').keyup(function (e) { + if (e.which === 13 || e === 13) { + $('#submit-comment-to-comment').click(); + } + var text_length = $('#what-does-the-nonprofit-do').val().length; + var text_remaining = text_max - text_length; + $('#what-does-the-nonprofit-do-feedback').html(text_remaining + ' characters remaining'); + }); \ No newline at end of file From e9c6498cdf0a1239fd7cdc69085924627d14f4f2 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Thu, 26 Mar 2015 02:28:04 +0900 Subject: [PATCH 28/57] Error handling --- controllers/story.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 34a85a6669..27bbf47916 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -25,11 +25,11 @@ function hotRank(timeValue, rank) { } -exports.hotJSON = function(req, res) { +exports.hotJSON = function(req, res, next) { var story = Story.find({}).sort({'timePosted': -1}).limit(1000); story.exec(function(err, stories) { if (err) { - return res.sendStatus(500); + return next(err); } var foundationDate = 1413298800000; @@ -168,10 +168,10 @@ exports.returnIndividualStory = function(req, res, next) { }); }; -exports.getStories = function(req, res) { +exports.getStories = function(req, res, next) { MongoClient.connect(secrets.db, function(err, database) { if (err) { - return res.sendStatus(500); + return next(err); } database.collection('stories').find({ '$text': { @@ -200,7 +200,7 @@ exports.getStories = function(req, res) { } }).toArray(function(err, items) { if (err) { - return res.sendStatus(500); + return next(err); } if (items !== null && items.length !== 0) { return res.json(items); @@ -250,9 +250,9 @@ exports.comments = function(req, res, next) { }); }; -exports.newStory = function(req, res) { +exports.newStory = function(req, res, next) { if (!req.user) { - return res.sendStatus(500); + return next(new Error('Must be logged in')); } var url = req.body.data.url; var cleanURL = sanitizeHtml(url, { @@ -274,7 +274,7 @@ exports.newStory = function(req, res) { } Story.find({'link': url}, function(err, story) { if (err) { - return res.sendStatus(500); + return next(err); } if (story.length) { req.flash('errors', { @@ -309,10 +309,10 @@ exports.newStory = function(req, res) { } }; -exports.storySubmission = function(req, res) { +exports.storySubmission = function(req, res, next) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.sendStatus(500); + return next(new Error('Not authorized')); } var storyLink = data.headline .replace(/\'/g, '') @@ -350,7 +350,7 @@ exports.storySubmission = function(req, res) { story.save(function(err) { if (err) { - return res.sendStatus(500); + return next(err); } res.send(JSON.stringify({ storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() @@ -358,10 +358,10 @@ exports.storySubmission = function(req, res) { }); }; -exports.commentSubmit = function(req, res) { +exports.commentSubmit = function(req, res, next) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.sendStatus(500); + return next(new Error('Not authorized')); } var sanitizedBody = sanitizeHtml(data.body, { @@ -384,14 +384,14 @@ exports.commentSubmit = function(req, res) { topLevel: true, commentOn: Date.now() }); - commentSave(comment, Story, res); + commentSave(comment, Story, res, next); }; -exports.commentOnCommentSubmit = function(req, res) { +exports.commentOnCommentSubmit = function(req, res, next) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { - return res.sendStatus(500); + return next(new Error('Not authorized')); } var sanitizedBody = sanitizeHtml(data.body, @@ -415,25 +415,25 @@ exports.commentOnCommentSubmit = function(req, res) { topLevel: false, commentOn: Date.now() }); - commentSave(comment, Comment, res); + commentSave(comment, Comment, res, next); }; -function commentSave(comment, Context, res) { +function commentSave(comment, Context, res, next) { comment.save(function(err, data) { if (err) { - return res.sendStatus(500); + return next(err); } try { Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { if (err) { - return res.sendStatus(500); + return next(err); } associatedStory = associatedStory.pop(); if (associatedStory) { associatedStory.comments.push(data._id); associatedStory.save(function (err) { if (err) { - return res.sendStatus(500); + return next(err); } res.send(true); }); @@ -441,7 +441,7 @@ function commentSave(comment, Context, res) { }); } catch (e) { // delete comment - return res.sendStatus(500); + return next(err); } }); } From 7d4c30c502c28d56aa1e96166071184e2bab0784 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 25 Mar 2015 14:18:27 -0700 Subject: [PATCH 29/57] start adding progress bar to nonprofit onboarding --- public/css/main.less | 3 +++ views/nonprofits/ok-with-javascript.jade | 6 +++++- ...ur-nonprofit-project-application-has-been-submitted.jade | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/public/css/main.less b/public/css/main.less index 09ebb557ea..3e8267ed57 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -880,6 +880,9 @@ iframe.iphone { background-color: #EEEEEE; } +.gray-text { + color: gray; +} //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/nonprofits/ok-with-javascript.jade b/views/nonprofits/ok-with-javascript.jade index adeee64a43..9a3b65d8c7 100644 --- a/views/nonprofits/ok-with-javascript.jade +++ b/views/nonprofits/ok-with-javascript.jade @@ -3,7 +3,11 @@ block content .jumbotron.text-center h1.hug-top Nonprofit Sign Up .spacer - h2 Our campers are learning to code using modern full stack JavaScript technologies (MongoDB, Express, Angular, Node.js). We do not build or maintain Wordpress, Drupal, or other non-JavaScript based frameworks. + .progress + .progress-bar(role='progressbar', aria-valuenow='60', aria-valuemin='0', aria-valuemax='100', style='width: 60%;') + span.sr-only 60% Complete + h3.gray-text Step 4 of 9 + h2 Our campers are learning to code using modern full stack JavaScript technologies like Node.js. We do not build or maintain Wordpress, Drupal, or other non-JavaScript based frameworks. .spacer .row .col-xs-6 diff --git a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade index e69de29bb2..fcc8c17a87 100644 --- a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade +++ b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade @@ -0,0 +1,4 @@ +.jumbotron.text-center + h1.hug-top Nonprofit Sign Up + .spacer + h2 Thank you for reaching out to us. We’ll get back with you before N of next week. \ No newline at end of file From d64b5c571863918551953a214417d8e17e483fcf Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 25 Mar 2015 15:25:19 -0700 Subject: [PATCH 30/57] add progress bar to nonprofit project proposal wizard --- app.js | 5 +- controllers/nonprofits.js | 56 ++++++++++++------- ...already-benefiting-from-your-services.jade | 2 +- .../are-you-with-a-registered-nonprofit.jade | 18 +++--- .../how-can-free-code-camp-help-you.jade | 2 +- views/nonprofits/in-exchange-we-ask.jade | 2 +- views/nonprofits/link-us-to-your-website.jade | 2 +- views/nonprofits/ok-with-javascript.jade | 6 +- views/nonprofits/tell-us-your-email.jade | 12 ++++ .../tell-us-your-name-and-email.jade | 0 views/nonprofits/tell-us-your-name.jade | 12 ++++ .../what-does-your-nonprofit-do.jade | 2 +- ...roject-application-has-been-submitted.jade | 10 ++-- .../nonprofit-application-progress-bar.jade | 6 ++ 14 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 views/nonprofits/tell-us-your-email.jade delete mode 100644 views/nonprofits/tell-us-your-name-and-email.jade create mode 100644 views/nonprofits/tell-us-your-name.jade create mode 100644 views/partials/nonprofit-application-progress-bar.jade diff --git a/app.js b/app.js index def618a07c..65c8218c65 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,7 @@ process.on('uncaughtException', function (err) { }); var express = require('express'), - accepts = require('accepts'), + //accepts = require('accepts'), cookieParser = require('cookie-parser'), compress = require('compression'), session = require('express-session'), @@ -284,7 +284,8 @@ app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); -app.get('/nonprofits/tell-us-your-name-and-email', nonprofitController.tellUsYourNameAndEmail); +app.get('/nonprofits/tell-us-your-name', nonprofitController.tellUsYourName); +app.get('/nonprofits/tell-us-your-email', nonprofitController.tellUsYourEmail); app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', nonprofitController.yourNonprofitProjectApplicationHasBeenSubmitted); app.get( diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 8ea6a0f295..144c5258ef 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -22,55 +22,65 @@ exports.nonprofitsHome = function(req, res) { exports.areYouWithARegisteredNonprofit = function(req, res) { res.render('nonprofits/are-you-with-a-registered-nonprofit', { - title: 'Are you with a with a registered nonprofit' + title: 'Are you with a with a registered nonprofit', + step: 1 }); }; exports.howCanFreeCodeCampHelpYou = function(req, res) { res.render('nonprofits/how-can-free-code-camp-help-you', { - title: 'Are you with a with a registered nonprofit' + title: 'Are you with a with a registered nonprofit', + step: 2 }); }; -exports.otherSolutions = function(req, res) { - res.render('nonprofits/other-solutions', { - title: 'Here are some other possible solutions for you' - }); -}; exports.areTherePeopleThatAreAlreadyBenefitingFromYourServices = function(req, res) { res.render('nonprofits/are-there-people-that-are-already-benefiting-from-your-services', { - title: 'Are there people already benefiting from your services' - }); -}; - -exports.inExchangeWeAsk = function(req, res) { - res.render('nonprofits/in-exchange-we-ask', { - title: 'In exchange we ask that you ...' + title: 'Are there people already benefiting from your services', + step: 3 }); }; exports.okWithJavaScript = function(req, res) { res.render('nonprofits/ok-with-javascript', { - title: 'Are you OK with us using JavaScript' + title: 'Are you OK with us using JavaScript', + step: 4 + }); +}; + +exports.inExchangeWeAsk = function(req, res) { + res.render('nonprofits/in-exchange-we-ask', { + title: 'In exchange we ask that you ...', + step: 5 }); }; exports.whatDoesYourNonprofitDo = function(req, res) { res.render('nonprofits/what-does-your-nonprofit-do', { - title: 'What does your nonprofit do?' + title: 'What does your nonprofit do?', + step: 6 }); }; exports.linkUsToYourWebsite = function(req, res) { res.render('nonprofits/link-us-to-your-website', { - title: 'Link us to your website' + title: 'Link us to your website', + step: 7 }); }; -exports.tellUsYourNameAndEmail = function(req, res) { - res.render('nonprofits/tell-us-your-name-and-email', { - title: 'Tell us your name and email address' +exports.tellUsYourEmail = function(req, res) { + res.render('nonprofits/tell-us-your-email', { + title: 'Tell us your name', + step: 8 + }); +}; + +exports.tellUsYourName = function(req, res) { + res.render('nonprofits/tell-us-your-name', { + title: 'Tell us your name', + step: 9 }); }; @@ -79,3 +89,9 @@ exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { title: 'Your Nonprofit Project application has been submitted!' }); }; + +exports.otherSolutions = function(req, res) { + res.render('nonprofits/other-solutions', { + title: 'Here are some other possible solutions for you' + }); +}; diff --git a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade index e9e18c1b3b..5f82c3c82b 100644 --- a/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade +++ b/views/nonprofits/are-there-people-that-are-already-benefiting-from-your-services.jade @@ -2,7 +2,7 @@ extends ../layout block content .jumbotron.text-center h1.hug-top Nonprofit Sign Up - .spacer + include ../partials/nonprofit-application-progress-bar h2 We build solutions for nonprofits who are already serving a need. Are there people who already benefit from your services? .spacer .row diff --git a/views/nonprofits/are-you-with-a-registered-nonprofit.jade b/views/nonprofits/are-you-with-a-registered-nonprofit.jade index ce778cec2a..c901d7dbbb 100644 --- a/views/nonprofits/are-you-with-a-registered-nonprofit.jade +++ b/views/nonprofits/are-you-with-a-registered-nonprofit.jade @@ -1,12 +1,12 @@ extends ../layout block content .jumbotron.text-center - h1.hug-top Nonprofit Sign Up - .spacer - h2 Do you represent a nonprofit organization that is registered with your government? - .spacer - .row - .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes - .col-xs-6 - a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Do you represent a nonprofit organization that is registered with your government? + .spacer + .row + .col-xs-6 + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes + .col-xs-6 + a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade index deb2ad8f77..4c0dab17b4 100644 --- a/views/nonprofits/how-can-free-code-camp-help-you.jade +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -2,7 +2,7 @@ extends ../layout block content .jumbotron.text-center h1.hug-top Nonprofit Sign Up - .spacer + include ../partials/nonprofit-application-progress-bar h2 How can Free Code Camp help you? .spacer .row.text-center.negative-35 diff --git a/views/nonprofits/in-exchange-we-ask.jade b/views/nonprofits/in-exchange-we-ask.jade index 87e6a90181..beb4b20da5 100644 --- a/views/nonprofits/in-exchange-we-ask.jade +++ b/views/nonprofits/in-exchange-we-ask.jade @@ -2,7 +2,7 @@ extends ../layout block content .jumbotron h1.hug-top.text-center Nonprofit Sign Up - .spacer + include ../partials/nonprofit-application-progress-bar h2 Great! In exchange for our help, we ask only that you: h3 ol diff --git a/views/nonprofits/link-us-to-your-website.jade b/views/nonprofits/link-us-to-your-website.jade index 481f527141..720aa6f338 100644 --- a/views/nonprofits/link-us-to-your-website.jade +++ b/views/nonprofits/link-us-to-your-website.jade @@ -1,6 +1,6 @@ .jumbotron.text-center h1.hug-top Nonprofit Sign Up - .spacer + include ../partials/nonprofit-application-progress-bar h2 In 140 characters or less, what does your nonprofit do? For whom? .spacer .formgroup diff --git a/views/nonprofits/ok-with-javascript.jade b/views/nonprofits/ok-with-javascript.jade index 9a3b65d8c7..1835218868 100644 --- a/views/nonprofits/ok-with-javascript.jade +++ b/views/nonprofits/ok-with-javascript.jade @@ -2,11 +2,7 @@ extends ../layout block content .jumbotron.text-center h1.hug-top Nonprofit Sign Up - .spacer - .progress - .progress-bar(role='progressbar', aria-valuenow='60', aria-valuemin='0', aria-valuemax='100', style='width: 60%;') - span.sr-only 60% Complete - h3.gray-text Step 4 of 9 + include ../partials/nonprofit-application-progress-bar h2 Our campers are learning to code using modern full stack JavaScript technologies like Node.js. We do not build or maintain Wordpress, Drupal, or other non-JavaScript based frameworks. .spacer .row diff --git a/views/nonprofits/tell-us-your-email.jade b/views/nonprofits/tell-us-your-email.jade new file mode 100644 index 0000000000..95318faf3e --- /dev/null +++ b/views/nonprofits/tell-us-your-email.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Please tell us your email + .spacer + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/your-nonprofit-project-application-has-been-submitted') Submit \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-name-and-email.jade b/views/nonprofits/tell-us-your-name-and-email.jade deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/views/nonprofits/tell-us-your-name.jade b/views/nonprofits/tell-us-your-name.jade new file mode 100644 index 0000000000..a440d89247 --- /dev/null +++ b/views/nonprofits/tell-us-your-name.jade @@ -0,0 +1,12 @@ +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Please tell us your name + .spacer + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/tell-us-your-email') Submit \ No newline at end of file diff --git a/views/nonprofits/what-does-your-nonprofit-do.jade b/views/nonprofits/what-does-your-nonprofit-do.jade index 92b65b17f4..8220e78f08 100644 --- a/views/nonprofits/what-does-your-nonprofit-do.jade +++ b/views/nonprofits/what-does-your-nonprofit-do.jade @@ -2,7 +2,7 @@ extends ../layout block content .jumbotron.text-center h1.hug-top Nonprofit Sign Up - .spacer + include ../partials/nonprofit-application-progress-bar h2 In 140 characters or less, what does your nonprofit do? For whom? .spacer .formgroup diff --git a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade index fcc8c17a87..7190fd14ec 100644 --- a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade +++ b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade @@ -1,4 +1,6 @@ -.jumbotron.text-center - h1.hug-top Nonprofit Sign Up - .spacer - h2 Thank you for reaching out to us. We’ll get back with you before N of next week. \ No newline at end of file +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Thank you for reaching out to us. We’ll get back with you before N of next week. \ No newline at end of file diff --git a/views/partials/nonprofit-application-progress-bar.jade b/views/partials/nonprofit-application-progress-bar.jade new file mode 100644 index 0000000000..283da6db44 --- /dev/null +++ b/views/partials/nonprofit-application-progress-bar.jade @@ -0,0 +1,6 @@ +.spacer +.progress + .progress-bar(role='progressbar', aria-valuenow= (step * 10), aria-valuemin='0', aria-valuemax='100', style="width: #{step * 10}%;") + span.sr-only= step * 10 + | % Complete +h3.gray-text.text-center Step #{step} of 9 \ No newline at end of file From e78609171dfa443744044c310d5b3b9de1ecfa1e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 26 Mar 2015 23:33:52 -0700 Subject: [PATCH 31/57] continue work on the param-based funnel --- app.js | 5 +- controllers/nonprofits.js | 48 +++++++++++--- public/css/main.less | 5 +- .../are-you-with-a-registered-nonprofit.jade | 2 +- .../how-can-free-code-camp-help-you.jade | 64 ++++++++----------- views/nonprofits/in-exchange-we-ask.jade | 2 +- views/nonprofits/tell-us-your-name.jade | 11 ++-- 7 files changed, 80 insertions(+), 57 deletions(-) diff --git a/app.js b/app.js index 65c8218c65..83d758bb10 100644 --- a/app.js +++ b/app.js @@ -277,16 +277,17 @@ app.get('/nonprofits', contactController.getNonprofitsForm); app.post('/nonprofits', contactController.postNonprofitsForm); app.get('/nonprofits/home', nonprofitController.nonprofitsHome); app.get('/nonprofits/are-you-with-a-registered-nonprofit', nonprofitController.areYouWithARegisteredNonprofit); -app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); app.get('/nonprofits/are-there-people-that-are-already-benefiting-from-your-services', nonprofitController.areTherePeopleThatAreAlreadyBenefitingFromYourServices); app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); -app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); +app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); +app.post('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYouPost); app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); app.get('/nonprofits/tell-us-your-name', nonprofitController.tellUsYourName); app.get('/nonprofits/tell-us-your-email', nonprofitController.tellUsYourEmail); app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', nonprofitController.yourNonprofitProjectApplicationHasBeenSubmitted); +app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); app.get( '/done-with-first-100-hours', diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 144c5258ef..1b0b745b1d 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -27,35 +27,47 @@ exports.areYouWithARegisteredNonprofit = function(req, res) { }); }; -exports.howCanFreeCodeCampHelpYou = function(req, res) { - res.render('nonprofits/how-can-free-code-camp-help-you', { - title: 'Are you with a with a registered nonprofit', - step: 2 - }); -}; - - exports.areTherePeopleThatAreAlreadyBenefitingFromYourServices = function(req, res) { res.render('nonprofits/are-there-people-that-are-already-benefiting-from-your-services', { title: 'Are there people already benefiting from your services', - step: 3 + step: 2 }); }; exports.okWithJavaScript = function(req, res) { res.render('nonprofits/ok-with-javascript', { title: 'Are you OK with us using JavaScript', - step: 4 + step: 3 }); }; exports.inExchangeWeAsk = function(req, res) { res.render('nonprofits/in-exchange-we-ask', { title: 'In exchange we ask that you ...', + step: 4 + }); +}; + +exports.howCanFreeCodeCampHelpYou = function(req, res) { + res.render('nonprofits/how-can-free-code-camp-help-you', { + title: 'Are you with a with a registered nonprofit', step: 5 }); }; +exports.howCanFreeCodeCampHelpYouPost = function(req, res) { + var queryString = ''; + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + if (req.body.websites) { queryString += 'websites&'} + res.redirect('/nonprofits/tell-us-your-name?' + encodeURIComponent(queryString)); +}; + exports.whatDoesYourNonprofitDo = function(req, res) { res.render('nonprofits/what-does-your-nonprofit-do', { title: 'What does your nonprofit do?', @@ -63,6 +75,10 @@ exports.whatDoesYourNonprofitDo = function(req, res) { }); }; +exports.whatDoesYourNonprofitDoPost = function(req, res) { + res.redirect('nonprofits/link-us-to-your-website?' + req.params); +}; + exports.linkUsToYourWebsite = function(req, res) { res.render('nonprofits/link-us-to-your-website', { title: 'Link us to your website', @@ -70,6 +86,10 @@ exports.linkUsToYourWebsite = function(req, res) { }); }; +exports.linkUsToYourWebsitePost = function(req, res) { + res.redirect('nonprofits/tell-us-your-email?' + req.params); +}; + exports.tellUsYourEmail = function(req, res) { res.render('nonprofits/tell-us-your-email', { title: 'Tell us your name', @@ -77,6 +97,10 @@ exports.tellUsYourEmail = function(req, res) { }); }; +exports.tellUsYourEmailPost = function(req, res) { + res.redirect('nonprofits/tell-us-your-name?' + req.params); +}; + exports.tellUsYourName = function(req, res) { res.render('nonprofits/tell-us-your-name', { title: 'Tell us your name', @@ -84,6 +108,10 @@ exports.tellUsYourName = function(req, res) { }); }; +exports.tellUsYourNamePost = function(req, res) { +}; + + exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { res.render('nonprofits/your-nonprofit-project-application-has-been-submitted', { title: 'Your Nonprofit Project application has been submitted!' diff --git a/public/css/main.less b/public/css/main.less index 3e8267ed57..ad76627718 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -880,9 +880,10 @@ iframe.iphone { background-color: #EEEEEE; } -.gray-text { - color: gray; +.checkbox-table label { + margin-left: 10px; } + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/nonprofits/are-you-with-a-registered-nonprofit.jade b/views/nonprofits/are-you-with-a-registered-nonprofit.jade index c901d7dbbb..4dc9a6e639 100644 --- a/views/nonprofits/are-you-with-a-registered-nonprofit.jade +++ b/views/nonprofits/are-you-with-a-registered-nonprofit.jade @@ -7,6 +7,6 @@ block content .spacer .row .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Yes + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/are-there-people-that-are-already-benefiting-from-your-services') Yes .col-xs-6 a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') No \ No newline at end of file diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade index 4c0dab17b4..5f2c95f4c1 100644 --- a/views/nonprofits/how-can-free-code-camp-help-you.jade +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -4,39 +4,31 @@ block content h1.hug-top Nonprofit Sign Up include ../partials/nonprofit-application-progress-bar h2 How can Free Code Camp help you? - .spacer - .row.text-center.negative-35 - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-android-globe - label.black-text.nonprofit-help-select-text-height Websites - input#websites-help.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-card - h2.black-text.nonprofit-help-select-text-height Donation Systems - input#websites-help.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-android-calendar - h2.black-text.nonprofit-help-select-text-height Volunteer Systems - input#websites-help.inline.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-ios-box - h2.black-text.nonprofit-help-select-text-height Inventory Systems - input#websites-help.inline.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-university - h2.black-text.nonprofit-help-select-text-height E-learning Platforms - input#websites-help.inline.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-ios-list - h2.black-text.nonprofit-help-select-text-height Web Forms - input#websites-help.inline.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-ios-people - h2.black-text.nonprofit-help-select-text-height Community Tools - input#websites-help.inline.checkbox(type='checkbox') - .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-settings - h2.black-text.nonprofit-help-select-text-height ...and other tools - input#websites-help.inline.checkbox(type='checkbox') - .spacer - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/are-there-people-that-are-already-benefiting-from-your-services') I've selected all that apply and am ready to move on \ No newline at end of file + form.text-left.form-inline(role='form', method='POST', action="/nonprofits/how-can-free-code-camp-help-you/") + input(type='hidden', name='_csrf', value=_csrf) + h3.col-xs-12.col-sm-offset-5.checkbox-table + .col-xs-12 + input.checkbox(type='checkbox', name='websites') + label.ion-android-globe   Websites + .col-xs-12 + input.checkbox(type='checkbox', name='donation-systems') + label.ion-card   Donation Systems + .col-xs-12 + input.checkbox(type='checkbox', name='volunteer-systems') + label.ion-android-calendar   Volunteer Systems + .col-xs-12 + input.checkbox(type='checkbox', name='inventory-systems') + label.ion-ios-box   Inventory Systems + .col-xs-12 + input.checkbox(type='checkbox', name='e-learning-platforms') + label.ion-university   E-learning Platforms + .col-xs-12 + input.checkbox(type='checkbox', name='web-forms') + label.ion-ios-list   Web Forms + .col-xs-12 + input.checkbox(type='checkbox', name='community-tools') + label.ion-ios-people   Community Tools + .col-xs-12 + input.checkbox(type='checkbox', name='other-tools') + label.ion-settings   Other tools + button.btn.btn-primary.btn-big.btn-block(type='submit') I've selected all that apply and am ready to move on \ No newline at end of file diff --git a/views/nonprofits/in-exchange-we-ask.jade b/views/nonprofits/in-exchange-we-ask.jade index beb4b20da5..e6b02c0877 100644 --- a/views/nonprofits/in-exchange-we-ask.jade +++ b/views/nonprofits/in-exchange-we-ask.jade @@ -12,6 +12,6 @@ block content .spacer .row .col-xs-6 - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/what-does-your-nonprofit-do') Sounds good! + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/how-can-free-code-camp-help-you') Sounds good! .col-xs-6 a.btn.btn-warning.btn-big.btn-block(href='/nonprofits/other-solutions') This might not be for us. \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-name.jade b/views/nonprofits/tell-us-your-name.jade index a440d89247..fe0d063e61 100644 --- a/views/nonprofits/tell-us-your-name.jade +++ b/views/nonprofits/tell-us-your-name.jade @@ -5,8 +5,9 @@ block content include ../partials/nonprofit-application-progress-bar h2 Please tell us your name .spacer - .formgroup - .input-group - input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') - span.input-group-btn - button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/tell-us-your-email') Submit \ No newline at end of file + form(role='form', action="/nonprofits/tell-us-your-name-post", method='POST', novalidate='novalidate', name='nonprofitForm') + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file From 683498e62a0b5e0e250d8ce79c8fb84d2437c927 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 00:20:50 -0700 Subject: [PATCH 32/57] remove calls to trello and blogger, camp counselor photos, and update the notice about Saturday's meeting --- app.js | 2 - controllers/resources.js | 28 ------ controllers/resources.json | 4 +- views/partials/about.jade | 140 ----------------------------- views/partials/blogger.jade | 16 ---- views/partials/trello.jade | 13 --- views/resources/learn-to-code.jade | 17 ++-- 7 files changed, 11 insertions(+), 209 deletions(-) delete mode 100644 views/partials/about.jade delete mode 100644 views/partials/blogger.jade delete mode 100644 views/partials/trello.jade diff --git a/app.js b/app.js index 5c5b748823..67b67bbfba 100644 --- a/app.js +++ b/app.js @@ -396,8 +396,6 @@ app.get('/account/api', userController.getAccountAngular); */ app.get('/api/github', resourcesController.githubCalls); -app.get('/api/blogger', resourcesController.bloggerCalls); -app.get('/api/trello', resourcesController.trelloCalls); /** * Bonfire related routes diff --git a/controllers/resources.js b/controllers/resources.js index fe3d09c0aa..6dc3b7e3ce 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -150,34 +150,6 @@ module.exports = { }); }, - - - trelloCalls: function(req, res, next) { - request('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(err, status, trello) { - if (err) { return next(err); } - trello = (status && status.statusCode == 200) ? (JSON.parse(trello)).length : "Can't connect to to Trello"; - res.send({"trello": trello}); - }); - }, - bloggerCalls: function(req, res, next) { - request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) { - if (err) { return next(err); } - blog = (status && status.statusCode == 200) ? JSON.parse(blog) : ''; - res.send({ - blog1Title: blog ? blog["items"][0]["title"] : "Can't connect to Blogger", - blog1Link: blog ? blog["items"][0]["url"] : "http://blog.freecodecamp.com", - blog2Title: blog ? blog["items"][1]["title"] : "Can't connect to Blogger", - blog2Link: blog ? blog["items"][1]["url"] : "http://blog.freecodecamp.com", - blog3Title: blog ? blog["items"][2]["title"] : "Can't connect to Blogger", - blog3Link: blog ? blog["items"][2]["url"] : "http://blog.freecodecamp.com", - blog4Title: blog ? blog["items"][3]["title"] : "Can't connect to Blogger", - blog4Link: blog ? blog["items"][3]["url"] : "http://blog.freecodecamp.com", - blog5Title: blog ? blog["items"][4]["title"] : "Can't connect to Blogger", - blog5Link: blog ? blog["items"][4]["url"] : "http://blog.freecodecamp.com" - }); - }); - }, - about: function(req, res, next) { if (req.user) { if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { diff --git a/controllers/resources.json b/controllers/resources.json index 3237d77c37..47eb9fff75 100644 --- a/controllers/resources.json +++ b/controllers/resources.json @@ -1,8 +1,8 @@ { "announcements": [ + ["We'll live-stream our Camp-wide Meeting Saturday, March 27 at Noon EST. We'll show some of Free Code Camp's new features, and campers will show what they're building.", "http://twitch.tv/freecodecamp"], ["Some of Code School's courses are no longer free. We're switching to NodeSchool.io for our Node.js and Express.js challenges.", "http://freecodecamp.com/nodeschool-challenges"], - ["Screen Hero is now free on Windows and Mac! Follow these special instructions to install it.", "http://freecodecamp.com/install-screenhero"], - ["Once you finish all the challenges, we welcome you to attend our Nonprofit Project Office Hours every Monday and Thursday Night at 9 pm EST.", "https://gitter.im/FreeCodeCamp/NonprofitProjects"] + ["Screen Hero is now free on Windows and Mac! Follow these special instructions to install it.", "http://freecodecamp.com/install-screenhero"] ], "questions": [{ "question": "Time Complexity of Accessing Array Index (int a = ARR[5];)", diff --git a/views/partials/about.jade b/views/partials/about.jade deleted file mode 100644 index 0e983545cf..0000000000 --- a/views/partials/about.jade +++ /dev/null @@ -1,140 +0,0 @@ -.panel.panel-info - .panel-heading.landing-panel-heading.text-center Our Team of Volunteer Camp Counselors - .panel-body - .landing-panel-body.text-center - .col-xs-12 - script. - $(function () { - var parent = $("#shuffle"); - var divs = parent.children(); - while (divs.length) { - parent.append(divs.splice(Math.floor(Math.random() * divs.length), 1)[0]); - } - }); - #shuffle - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Angelica Ferdinand - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/angelica-ferdinand.jpg' alt="Angelica Ferdinand's picture") - h4.text-nowrap Oakland, California - p.negative-10 "Computers have always lit my fire. I took a detour, but it's never too late to realize your dreams. If you love it, live it!" - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Ammar Shah - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/ammar-shah.jpg' alt="Ammar Shah's picture") - h4.text-nowrap Karachi, Pakistan - p.negative-10 "I code whenever I'm not sleeping or in school. Making computers obey me is a dream come true." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Branden Byers - h4.negative-10.text-nowrap Instructional Designer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/branden-byers.jpg' alt="Branden Byers picture") - h4.text-nowrap Madison, Wisconsin - p.negative-10 "Cookbook author and stay-at-home-dad. Started coding as a kid, got distracted, but now I'm back in full JavaScript force!" - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Michael Johnson - h4.negative-10.text-nowrap Nonprofit Coordinator - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/michael-johnson.jpeg' alt="Michael Johnson's picture") - h4.text-nowrap Washington, D.C. - p.negative-10 "I’m a recent Harvard University graduate who took a pass on Wall Street to code for a cause, and help others do the same." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Berkeley Martinez - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/berkeley-martinez.jpg' alt="Berkeley Martinez's picture") - h4.text-nowrap San Francisco, California - p.negative-10 "Former mechanical engineer. Coding is pure creation. I can fly, but only once." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Mychael Zuniga - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/mychael-zuniga.jpg' alt="Mychael Zuniga's picture") - h4.text-nowrap San Diego, California - p.negative-10 "I'm a college student who turned to code as an avenue for creative expression. I love political science and economics." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Jeanette Casteñeta - h4.negative-10.text-nowrap Product Manager - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/jeanette-casteneta.jpg' alt="Jeanette Casteñeta's picture") - h4.text-nowrap San Francisco, California - p.negative-10 "Home-ec diva. I can envision a dress, then stitch it together. Now I'm learning how to do the same thing with code." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Darryl Dixon - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/darryl-dixon.jpg' alt="Darryl Dixon's picture") - h4.text-nowrap Newport News, Virginia - p.negative-10 "I'm a self-taught graphic designer. I'm learning web development here and want you to learn with me." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Kathy O'Driscoll - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/kathy-odriscoll.jpg' alt="Kathy O'Driscoll's picture") - h4.text-nowrap Los Angeles, California - p.negative-10 "Mother and grandmother. All my life I've dabbled in getting machines to do my bidding. Now it's becoming my career." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Ryan Malm - h4.negative-10.text-nowrap Visual Designer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/ryan-malm.jpg' alt="Ryan Malm's picture") - h4.text-nowrap Omaha, Nebraska - p.negative-10 "I love origami, piano, and playing minecraft with my kids. My JavaScript grows stronger every day." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Charles Watson - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/charles-watson.jpg' alt="Charles Watson's picture") - h4.text-nowrap Minneapolis, Minnesota - p.negative-10 "I skipped college. I build iOS apps. I love the obstacles and puzzles that coding presents me." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Quincy Larson - h4.negative-10.text-nowrap Instructional Designer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/quincy-larson.jpg' alt="Quincy Larson's picture") - h4.text-nowrap San Francisco, California - p.negative-10 "I worked as a school director in China before learning to code. It's clear that everyone can - and should - learn to code." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Mark Howard - h4.negative-10.text-nowrap Digital Marketer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/mark-howard.jpg' alt="Mark Howard's picture") - h4.text-nowrap San Diego, California - p.negative-10 "I enjoy helping people, at scale. Code is the best way to do that." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Nathan Leniz - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/nathan-leniz.jpg' alt="Nathan Leniz's picture") - h4.text-nowrap Seoul, South Korea - p.negative-10 "I learned to code for the games, and stayed for the algorithms." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Jason Rueckert - h4.negative-10.text-nowrap Live Content Manager - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/jason-rueckert.jpg' alt="Jason Rueckert's picture") - h4.text-nowrap Seattle, Washington - p.negative-10 "My high school job was testing basketball shoes for Nike. I learned code to work smarter, not harder. I have no thyroid." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Christopher Nguyen - h4.negative-10.text-nowrap QA Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/christopher-nguyen.jpg' alt="Christopher Nguyen's picture") - h4.text-nowrap Seattle, Washington - p.negative-10 "Morning Owl. Code is an equalizer. Barriers exist everywhere, but if you can say 'hello world', the world will say hello back." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Dominic Jones - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/dominic-jones.jpg' alt="Dominic Jones's picture") - h4.text-nowrap York, Pennsylvania - p.negative-10 "Born with Sickle Cell Anemia. Professional writer, working on becoming a professional code writer." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap James McShane - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/james-mcshane.jpg' alt="James McShane's picture") - h4.text-nowrap Minneapolis, Minnesota - p.negative-10 "I just bought our first house, ending a 10 year streak of moving each year. I've used code to solve problems since I was a child." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Ellie Adam - h4.negative-10.text-nowrap Visual Designer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/ellie-adam.jpg' alt="Eliie Adam's picture") - h4.text-nowrap Seattle, Washington - p.negative-10 "I photograph birds and flowers. I'm a designer who recently decided to learn coding and front end web developement." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Kamal Sharif - h4.negative-10.text-nowrap JavaScript Engineer - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/kamal-sharif.jpg' alt="Kamal Sharif's picture") - h4.text-nowrap Dhaka, Bangladesh - p.negative-10 "I build applications that help other people improve their own lives." - .col-xs-12.col-sm-12.col-md-12.col-lg-6.team-member - h3.negative-10.text-nowrap Patrick Ly - h4.negative-10.text-nowrap Community Builder - img.profile-image(src='https://s3.amazonaws.com/freecodecamp/patrick-ly.jpg' alt="Patrick Ly's picture") - h4.text-nowrap Los Angeles, California - p.negative-10 "I'm a student and self-taught Hip Hop dancer. Just using the web isn't enough for me. I want to create and maintain web apps, too" \ No newline at end of file diff --git a/views/partials/blogger.jade b/views/partials/blogger.jade deleted file mode 100644 index f2570c95bd..0000000000 --- a/views/partials/blogger.jade +++ /dev/null @@ -1,16 +0,0 @@ -#blog -script. - (function() { - $.ajax({ - url: '/api/blogger', - type: 'GET' - }).done( - function(data) { - var props = Object.keys(data); - for (var i = 0; i < props.length; i+=2) { - var blogger = document.createElement('div'); - $(blogger).html('

' + data[props[i]] + '

').appendTo($('#blog')) - } - } - ); - })(); \ No newline at end of file diff --git a/views/partials/trello.jade b/views/partials/trello.jade deleted file mode 100644 index 1af9348f47..0000000000 --- a/views/partials/trello.jade +++ /dev/null @@ -1,13 +0,0 @@ -#trello -script. - (function() { - $.ajax({ - url: '/api/trello', - type: 'GET' - }).done( - function(data) { - var trello = document.createElement('div'); - $(trello).html('

We help ' + data.trello + ' nonprofits. (view them)

').prependTo($('#trello')) - } - ); - })(); \ No newline at end of file diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index a7f6182ed1..4eb8198888 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -28,7 +28,8 @@ block content .panel-body.text-center img.img-responsive.img-center(src="https://s3.amazonaws.com/freecodecamp/about-trello.jpg" alt="a screen shot of Free Code Camp's nonprofit project Trello board showing several active projects") h3 We launched #{daysRunning} days ago. - include ../partials/trello + h3 We help 20 nonprofits.   + a(href='https://trello.com/b/BA3xVpz9/nonprofit-projects') (view them) br @@ -46,7 +47,6 @@ block content a(href=announcement[1])= announcement[0] else = announcement[0] - include ../partials/blogger a.twitter-timeline(data-dnt='true', href='https://twitter.com/FreeCodeCamp', data-widget-id='560847186548621312') Tweets by @FreeCodeCamp script. !function (d, s, id) { @@ -73,22 +73,23 @@ block content .col-xs-12.github-and-twitter-button-text html. - include ../partials/about .col-xs-12.col-sm-12.col-md-6 include ../partials/faq #announcementModal.modal(tabindex='-1') .modal-dialog .modal-content - .modal-header.challenge-list-header New NodeSchool Challenges + .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 Some of Code School's courses are no longer free. We're switching to NodeSchool.io for our Node.js and Express.js challenges. - a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='/nodeschool-challenges') Take me to these new challenges + 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.nodeSchoolAnnouncement) { + if (!localStorage || !localStorage.campWideMeeting) { $('#announcementModal').modal('show'); - localStorage.nodeSchoolAnnouncement = "true"; + localStorage.campWideMeeting = "true"; } }); \ No newline at end of file From 94b6cae3ece95cd7bfebc6954da55e004299f6c2 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 12:36:21 -0700 Subject: [PATCH 33/57] apply Nathan's insights into multistep form' --- app.js | 1 - controllers/nonprofits.js | 30 ++-------------- .../how-can-free-code-camp-help-you.jade | 21 ++++++----- views/nonprofits/link-us-to-your-website.jade | 24 +++++++------ views/nonprofits/tell-us-your-email.jade | 12 ++++--- views/nonprofits/tell-us-your-name.jade | 4 +-- .../what-does-your-nonprofit-do.jade | 16 +++++---- .../nonprofit-application-progress-bar.jade | 35 ++++++++++++++++++- views/stories/preliminary-submit.jade | 10 +++++- 9 files changed, 88 insertions(+), 65 deletions(-) diff --git a/app.js b/app.js index 52af8dea7b..ff745098db 100644 --- a/app.js +++ b/app.js @@ -281,7 +281,6 @@ app.get('/nonprofits/are-there-people-that-are-already-benefiting-from-your-serv app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); -app.post('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYouPost); app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); app.get('/nonprofits/tell-us-your-name', nonprofitController.tellUsYourName); diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 1b0b745b1d..4ceda4de1e 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -55,30 +55,14 @@ exports.howCanFreeCodeCampHelpYou = function(req, res) { }); }; -exports.howCanFreeCodeCampHelpYouPost = function(req, res) { - var queryString = ''; - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - if (req.body.websites) { queryString += 'websites&'} - res.redirect('/nonprofits/tell-us-your-name?' + encodeURIComponent(queryString)); -}; - exports.whatDoesYourNonprofitDo = function(req, res) { res.render('nonprofits/what-does-your-nonprofit-do', { + existingParams: req.params, title: 'What does your nonprofit do?', step: 6 }); }; -exports.whatDoesYourNonprofitDoPost = function(req, res) { - res.redirect('nonprofits/link-us-to-your-website?' + req.params); -}; - exports.linkUsToYourWebsite = function(req, res) { res.render('nonprofits/link-us-to-your-website', { title: 'Link us to your website', @@ -86,10 +70,6 @@ exports.linkUsToYourWebsite = function(req, res) { }); }; -exports.linkUsToYourWebsitePost = function(req, res) { - res.redirect('nonprofits/tell-us-your-email?' + req.params); -}; - exports.tellUsYourEmail = function(req, res) { res.render('nonprofits/tell-us-your-email', { title: 'Tell us your name', @@ -97,10 +77,6 @@ exports.tellUsYourEmail = function(req, res) { }); }; -exports.tellUsYourEmailPost = function(req, res) { - res.redirect('nonprofits/tell-us-your-name?' + req.params); -}; - exports.tellUsYourName = function(req, res) { res.render('nonprofits/tell-us-your-name', { title: 'Tell us your name', @@ -108,9 +84,9 @@ exports.tellUsYourName = function(req, res) { }); }; -exports.tellUsYourNamePost = function(req, res) { -}; +exports.finishApplication = function(req, res) { +}; exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { res.render('nonprofits/your-nonprofit-project-application-has-been-submitted', { diff --git a/views/nonprofits/how-can-free-code-camp-help-you.jade b/views/nonprofits/how-can-free-code-camp-help-you.jade index 5f2c95f4c1..3aeec6490a 100644 --- a/views/nonprofits/how-can-free-code-camp-help-you.jade +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -4,31 +4,30 @@ block content h1.hug-top Nonprofit Sign Up include ../partials/nonprofit-application-progress-bar h2 How can Free Code Camp help you? - form.text-left.form-inline(role='form', method='POST', action="/nonprofits/how-can-free-code-camp-help-you/") - input(type='hidden', name='_csrf', value=_csrf) + .text-left.form-inline h3.col-xs-12.col-sm-offset-5.checkbox-table .col-xs-12 - input.checkbox(type='checkbox', name='websites') + input.checkbox(type='checkbox', id='websites') label.ion-android-globe   Websites .col-xs-12 - input.checkbox(type='checkbox', name='donation-systems') + input.checkbox(type='checkbox', id='donationSystems') label.ion-card   Donation Systems .col-xs-12 - input.checkbox(type='checkbox', name='volunteer-systems') + input.checkbox(type='checkbox', id='volunteerSystems') label.ion-android-calendar   Volunteer Systems .col-xs-12 - input.checkbox(type='checkbox', name='inventory-systems') + input.checkbox(type='checkbox', id='inventorySystems') label.ion-ios-box   Inventory Systems .col-xs-12 - input.checkbox(type='checkbox', name='e-learning-platforms') + input.checkbox(type='checkbox', id='eLearningPlatforms') label.ion-university   E-learning Platforms .col-xs-12 - input.checkbox(type='checkbox', name='web-forms') + input.checkbox(type='checkbox', id='webForms') label.ion-ios-list   Web Forms .col-xs-12 - input.checkbox(type='checkbox', name='community-tools') + input.checkbox(type='checkbox', id='communityTools') label.ion-ios-people   Community Tools .col-xs-12 - input.checkbox(type='checkbox', name='other-tools') + input.checkbox(type='checkbox', id='otherTools') label.ion-settings   Other tools - button.btn.btn-primary.btn-big.btn-block(type='submit') I've selected all that apply and am ready to move on \ No newline at end of file + button#next-step.btn.btn-primary.btn-big.btn-block(type='submit') I've selected all that apply and am ready to move on \ No newline at end of file diff --git a/views/nonprofits/link-us-to-your-website.jade b/views/nonprofits/link-us-to-your-website.jade index 720aa6f338..bdb59455ad 100644 --- a/views/nonprofits/link-us-to-your-website.jade +++ b/views/nonprofits/link-us-to-your-website.jade @@ -1,10 +1,14 @@ -.jumbotron.text-center - h1.hug-top Nonprofit Sign Up - include ../partials/nonprofit-application-progress-bar - h2 In 140 characters or less, what does your nonprofit do? For whom? - .spacer - .formgroup - .input-group - input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') - span.input-group-btn - button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/tell-us-your-name') Submit \ No newline at end of file +extends ../layout +block content + .jumbotron.text-center + h1.hug-top Nonprofit Sign Up + include ../partials/nonprofit-application-progress-bar + h2 Link us to the website, blog, or social media page that best represents your organization. + .spacer + form(role='form', method='GET', action="/nonprofits/tell-us-your-email/?" + existingParams) + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', name='link', autocomplete='off', maxlength='500', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-email.jade b/views/nonprofits/tell-us-your-email.jade index 95318faf3e..3bd8918f6b 100644 --- a/views/nonprofits/tell-us-your-email.jade +++ b/views/nonprofits/tell-us-your-email.jade @@ -5,8 +5,10 @@ block content include ../partials/nonprofit-application-progress-bar h2 Please tell us your email .spacer - .formgroup - .input-group - input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') - span.input-group-btn - button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/your-nonprofit-project-application-has-been-submitted') Submit \ No newline at end of file + form(role='form', method='GET', novalidate='novalidate', name='nonprofitForm', action="/nonprofits/tell-us-your-name/") + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive(type='text', name='email', autocomplete='off', maxlength='500', autofocus='') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/tell-us-your-name.jade b/views/nonprofits/tell-us-your-name.jade index fe0d063e61..915c0b3258 100644 --- a/views/nonprofits/tell-us-your-name.jade +++ b/views/nonprofits/tell-us-your-name.jade @@ -5,9 +5,9 @@ block content include ../partials/nonprofit-application-progress-bar h2 Please tell us your name .spacer - form(role='form', action="/nonprofits/tell-us-your-name-post", method='POST', novalidate='novalidate', name='nonprofitForm') + form(role='form', method='POST', novalidate='novalidate', name='nonprofitForm', action="/nonprofits/finish-application/") .formgroup .input-group - input.form-control.big-text-field.field-responsive(type='text', maxlength='140', autofocus='') + input.form-control.big-text-field.field-responsive(type='text', name='name', autocomplete='off', maxlength='140', autofocus='') span.input-group-btn button.btn.btn-big.btn-primary.btn-responsive Submit \ No newline at end of file diff --git a/views/nonprofits/what-does-your-nonprofit-do.jade b/views/nonprofits/what-does-your-nonprofit-do.jade index 8220e78f08..166cb71418 100644 --- a/views/nonprofits/what-does-your-nonprofit-do.jade +++ b/views/nonprofits/what-does-your-nonprofit-do.jade @@ -5,13 +5,15 @@ block content include ../partials/nonprofit-application-progress-bar h2 In 140 characters or less, what does your nonprofit do? For whom? .spacer - .formgroup - .input-group - input.form-control.big-text-field.field-responsive#what-does-the-nonprofit-do(type='text', maxlength='140', autofocus='') - span.input-group-btn - button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/link-us-to-your-website') Submit - .text-left - span#what-does-the-nonprofit-do-feedback + form(role='form', method='GET', action="/nonprofits/link-us-to-your-website/") + input(type='hidden', name='_csrf', value=_csrf) + .formgroup + .input-group + input.form-control.big-text-field.field-responsive#what-does-the-nonprofit-do(type='text', maxlength='140', autofocus='', autocomplete='off', name='mission') + span.input-group-btn + button.btn.btn-big.btn-primary.btn-responsive(href='/nonprofits/link-us-to-your-website') Submit + .text-left + span#what-does-the-nonprofit-do-feedback script. var text_max = 140; diff --git a/views/partials/nonprofit-application-progress-bar.jade b/views/partials/nonprofit-application-progress-bar.jade index 283da6db44..440bb67262 100644 --- a/views/partials/nonprofit-application-progress-bar.jade +++ b/views/partials/nonprofit-application-progress-bar.jade @@ -3,4 +3,37 @@ .progress-bar(role='progressbar', aria-valuenow= (step * 10), aria-valuemin='0', aria-valuemax='100', style="width: #{step * 10}%;") span.sr-only= step * 10 | % Complete -h3.gray-text.text-center Step #{step} of 9 \ No newline at end of file +h3.gray-text.text-center Step #{step} of 9 + + +script. + $('#story-url').on('keypress', function (e) { + if (e.which === 13 || e === 13) { + $('#preliminary-story-submit').click(); + } + }); + var preliminaryStorySubmit = function preliminaryStorySubmit() { + var storyURL = $('#story-url').val(); + $('#preliminary-story-submit').attr('disabled', 'disabled'); + $.post('/stories/preliminary', + { + data: { + url: storyURL + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#preliminary-story-submit').attr('disabled', false); + }) + .done(function (data, textStatus, xhr) { + if (data.alreadyPosted) { + window.location = data.storyURL; + } else { + window.location = '/stories/submit/new-story?url=' + + encodeURIComponent(data.storyURL) + + '&title=' + encodeURIComponent(data.storyTitle) + + '&image=' + encodeURIComponent(data.storyImage) + + '&description=' + encodeURIComponent(data.storyMetaDescription); + } + }); + } + $('#preliminary-story-submit').on('click', preliminaryStorySubmit); \ No newline at end of file diff --git a/views/stories/preliminary-submit.jade b/views/stories/preliminary-submit.jade index 31ef779fcd..4bc8ba873c 100644 --- a/views/stories/preliminary-submit.jade +++ b/views/stories/preliminary-submit.jade @@ -44,4 +44,12 @@ } }); } - $('#preliminary-story-submit').on('click', preliminaryStorySubmit); \ No newline at end of file + $('#preliminary-story-submit').on('click', preliminaryStorySubmit); + + + arr = $( "h3 input:checked" ) + .map(function() { + return this.id; + }) + .get() + .join('&'); \ No newline at end of file From f7f7fb39b9b631ee4ae955009f05120572ffa366 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 14:36:17 -0700 Subject: [PATCH 34/57] start refactoring basejumps and ziplines to just be normal challenges, make all timestamps miliseconds --- controllers/bonfire.js | 12 +- controllers/courseware.js | 133 +++++++++++++-- controllers/resources.js | 5 +- controllers/zipLine.js | 161 ------------------ models/BasejumpCompletion.js | 12 -- models/BonfireCompletion.js | 11 -- models/User.js | 2 +- models/ZiplineCompletion.js | 12 -- .../js/lib/bonfire/bonfireFramework_v0.1.2.js | 2 +- .../coursewaresHCJQFramework_v0.1.1.js | 2 +- .../lib/coursewares/coursewaresJSFramework.js | 2 +- views/bonfire/show.jade | 20 +-- views/coursewares/showHTML.jade | 4 +- views/coursewares/showJS.jade | 4 +- views/coursewares/showVideo.jade | 8 +- views/ziplines/index.jade | 0 views/ziplines/show.jade | 50 ------ 17 files changed, 145 insertions(+), 295 deletions(-) delete mode 100644 controllers/zipLine.js delete mode 100644 models/BasejumpCompletion.js delete mode 100644 models/BonfireCompletion.js delete mode 100644 models/ZiplineCompletion.js delete mode 100644 views/ziplines/index.jade delete mode 100644 views/ziplines/show.jade diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 79f3eb4a75..6a4712f3b1 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -224,26 +224,26 @@ exports.generateChallenge = function(req, res) { exports.completedBonfire = function (req, res) { var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; - var isCompletedDate = Math.round(+new Date() / 1000); + var isCompletedDate = Math.round(+new Date()); var bonfireHash = req.body.bonfireInfo.bonfireHash; var isSolution = req.body.bonfireInfo.solution; if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); + var paired = User.find({"profile.username": isCompletedbWith.toLowerCase()}).limit(1); paired.exec(function (err, pairedWith) { if (err) { return err; } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - req.user.progressTimestamps.push(Date.now() / 1000 | 0); + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } pairedWith = pairedWith.pop(); index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() / 1000 | 0); + pairedWith.progressTimestamps.push(Date.now() || 0); pairedWith.uncompletedBonfires.splice(index, 1); } @@ -260,7 +260,7 @@ exports.completedBonfire = function (req, res) { completedWith: pairedWith._id, completedDate: isCompletedDate, solution: isSolution - }) + }); req.user.save(function (err, user) { pairedWith.save(function (err, paired) { @@ -285,7 +285,7 @@ exports.completedBonfire = function (req, res) { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - req.user.progressTimestamps.push(Date.now() / 1000 | 0); + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } diff --git a/controllers/courseware.js b/controllers/courseware.js index 4d992d310e..28f4f6bec6 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -91,8 +91,6 @@ exports.returnIndividualCourseware = function(req, res, next) { details: courseware.description.slice(1), tests: courseware.tests, challengeSeed: courseware.challengeSeed, - cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -110,13 +108,10 @@ exports.returnIndividualCourseware = function(req, res, next) { details: courseware.description.slice(1), tests: courseware.tests, challengeSeed: courseware.challengeSeed, - cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - environment: resources.whichEnvironment() }); }, @@ -129,13 +124,43 @@ exports.returnIndividualCourseware = function(req, res, next) { details: courseware.description, tests: courseware.tests, video: courseware.challengeSeed[0], - cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - environment: resources.whichEnvironment() + challengeType: 'video' + }); + }, + + 3: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: 'zipline' + }); + }, + + 4: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: 'basejump' }); } }; @@ -212,9 +237,9 @@ exports.generateChallenge = function(req, res) { res.send(response); }; -exports.completedCourseware = function (req, res) { +exports.completedCourseware = function (req, res, next) { - var isCompletedDate = Math.round(+new Date() / 1000); + var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; debug('this is the coursewarehash we got', coursewareHash); @@ -226,18 +251,100 @@ exports.completedCourseware = function (req, res) { }); var index = req.user.completedCoursewares.indexOf(coursewareHash); - debug('this is the index of the found courseware', index); + if (index === -1) { - req.user.progressTimestamps.push(Date.now() / 1000 | 0); + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); } req.user.save(function (err, user) { if (err) { - throw err; + return next(err); } if (user) { res.send(true); } }); }; + +exports.completedZiplineOrBasejump = function (req, res, next) { + var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + var solutionLink = req.body.coursewareInfo.solutionLink; + if(!solutionLink) { + // flash error and redirect + } + + if (isCompletedWith) { + var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return err; + } else { + var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1) + } + pairedWith = pairedWith.pop(); + + index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedBonfires.splice(index, 1); + + } + + pairedWith.completedBonfires.push({ + _id: bonfireHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: isSolution + }); + + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: isSolution + }); + + req.user.save(function (err, user) { + pairedWith.save(function (err, paired) { + if (err) { + throw err; + } + if (user && paired) { + res.send(true); + } + }) + }); + } + }) + } else { + + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: null, + completedDate: isCompletedDate, + solution: isSolution + }); + + var index = req.user.uncompletedCourse.indexOf(bonfireHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1) + } + + req.user.save(function (err, user) { + if (err) { + throw err; + } + if (user) { + debug('Saving user'); + res.send(true) + } + }); + } +}; \ No newline at end of file diff --git a/controllers/resources.js b/controllers/resources.js index a4a7ca6feb..a1d53d34d8 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -199,7 +199,7 @@ module.exports = { var date1 = new Date('10/15/2014'); var date2 = new Date(); var progressTimestamps = req.user.progressTimestamps; - var now = Date.now() / 1000 | 0; + var now = Date.now() || 0; if (req.user.pointsNeedMigration) { var challengesHash = req.user.challengesHash; for (var key in challengesHash) { @@ -211,7 +211,8 @@ module.exports = { var timeStamps = []; R.keys(req.user.challengesHash).forEach(function(key) { "use strict"; - timeStamps.push({timeStamp: challengesHash[key]}); + var timeStamp = parseInt(challengesHash[key], 10); + timeStamps.push({timeStamp: timeStamp.length !== 13 ? (+timeStamp) : (+timeStamp * 1000)}); }); req.user.completedCoursewares = Array.zip(timeStamps, coursewares, diff --git a/controllers/zipLine.js b/controllers/zipLine.js deleted file mode 100644 index 53c5510971..0000000000 --- a/controllers/zipLine.js +++ /dev/null @@ -1,161 +0,0 @@ -var _ = require('lodash'), - debug = require('debug')('freecc:cntr:zipline'), - Zipline = require('./../models/Zipline'), - User = require('./../models/User'), - resources = require('./resources'), - R = require('ramda'); - -/** -* Bonfire controller -*/ - -exports.showAllZiplines = function(req, res) { - var completedZiplines = req.user.completedZiplines.map(function(elem) { - return elem._id; - }); - - var noDuplicateZiplines = R.uniq(completedZiplines); - var data = {}; - data.ziplineList = resources.allZiplineNames(); - data.completedList = noDuplicateZiplines; - res.send(data); -}; - -exports.index = function(req, res) { - res.render('ziplines/show.jade', { - completedWith: null, - title: 'Choose Your Zipline', - name: 'Choose Your Zipline', - difficulty: 0, - //cc: req.user ? req.user.bonfiresHash : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliments: resources.randomCompliment(), - ziplines: [] - //ziplineHash: 'test' - }); -}; - -exports.returnIndividualZipline = function(req, res, next) { - var dashedName = req.params.ziplineName; - - ziplineName = dashedName.replace(/\-/g, ' '); - - Zipline.find({"name" : new RegExp(ziplineName, 'i')}, function(err, zipline) { - if (err) { - next(err); - } - - - if (zipline.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a bonfire with that name. Please double check the name." - }); - - return res.redirect('/ziplines'); - } - - zipline = zipline.pop(); - var dashedNameFull = zipline.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../ziplines/' + dashedNameFull); - } - - res.render('ziplines/show', { - completedWith: null, - title: zipline.name, - dashedName: dashedName, - name: zipline.name, - difficulty: Math.floor(+zipline.difficulty), - details: zipline.details, - tests: zipline.tests, - challengeSeed: zipline.challengeSeed, - //cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - ziplines: zipline - //ziplineHash: zipline._id - - }); - }); -}; - -exports.completedZipline = function (req, res) { - var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; - var isCompletedDate = Math.round(+new Date() / 1000); - //var ziplineHash = req.body.bonfireInfo.bonfireHash; - - if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { - if (err) { - return err; - } else { - //var index = req.user.uncompletedZiplines.indexOf(ziplineHash); - //if (index > -1) { - // req.user.progressTimestamps.push(Date.now() / 1000 | 0); - // req.user.uncompletedZiplines.splice(index, 1) - //} - //pairedWith = pairedWith.pop(); - // - //index = pairedWith.uncompletedZiplines.indexOf(bonfiHash); - //if (index > -1) { - // pairedWith.progressTimestamps.push(Date.now() / 1000 | 0); - // pairedWith.uncompletedZiplines.splice(index, 1); - // - //} - // - //pairedWith.completedBonfires.push({ - // _id: bonfireHash, - // completedWith: req.user._id, - // completedDate: isCompletedDate, - // solution: isSolution - //}); - // - //req.user.completedBonfires.push({ - // _id: bonfireHash, - // completedWith: pairedWith._id, - // completedDate: isCompletedDate, - // solution: isSolution - //}) - // - //req.user.save(function (err, user) { - // pairedWith.save(function (err, paired) { - // if (err) { - // throw err; - // } - // if (user && paired) { - // res.send(true); - // } - // }) - //}); - } - }) - } else { - - //req.user.completedBonfires.push({ - // _id: bonfireHash, - // completedWith: null, - // completedDate: isCompletedDate, - // solution: isSolution - //}); - // - //var index = req.user.uncompletedBonfires.indexOf(bonfireHash); - //if (index > -1) { - // req.user.progressTimestamps.push(Date.now() / 1000 | 0); - // req.user.uncompletedBonfires.splice(index, 1) - //} - // - //req.user.save(function (err, user) { - // if (err) { - // throw err; - // } - // if (user) { - // debug('Saving user'); - // res.send(true) - // } - //}); - } -}; \ No newline at end of file diff --git a/models/BasejumpCompletion.js b/models/BasejumpCompletion.js deleted file mode 100644 index 872c401265..0000000000 --- a/models/BasejumpCompletion.js +++ /dev/null @@ -1,12 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -var basejumpCompletionSchema = new mongoose.Schema({ - dateCompleted: Number, - completedWith: ObjectId, - basejumpHash: ObjectId, - githubUrl: String, - demoUrl: String -}); - -module.exports = mongoose.model('BasejumpCompletion', basejumpCompletionSchema); diff --git a/models/BonfireCompletion.js b/models/BonfireCompletion.js deleted file mode 100644 index eb4249400a..0000000000 --- a/models/BonfireCompletion.js +++ /dev/null @@ -1,11 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -var bonfireCompletionSchema = new mongoose.Schema({ - dateCompleted: Number, - completedWith: ObjectId, - bonfireHash: ObjectId, - solution: String -}); - -module.exports = mongoose.model('BonfireCompletion', bonfireCompletionSchema); diff --git a/models/User.js b/models/User.js index 33d19618cc..6019e7d83b 100644 --- a/models/User.js +++ b/models/User.js @@ -21,7 +21,7 @@ var userSchema = new mongoose.Schema({ type: Number, default: 0 }, - progressTimestamps: { type: Array, default: [] }, + progressTimestamps: { type: Array, default: [Date] }, challengesCompleted: { type: Array, default: [] }, pointsNeedMigration: { type: Boolean, default: true }, challengesHash: { diff --git a/models/ZiplineCompletion.js b/models/ZiplineCompletion.js deleted file mode 100644 index 676d954fe6..0000000000 --- a/models/ZiplineCompletion.js +++ /dev/null @@ -1,12 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -var ziplineCompletionSchema = new mongoose.Schema({ - dateCompleted: Number, - completedWith: ObjectId, - basejumpHash: ObjectId, - githubUrl: String, - demoUrl: String -}); - -module.exports = mongoose.model('ziplineCompletion', ziplineCompletionSchema); \ No newline at end of file diff --git a/public/js/lib/bonfire/bonfireFramework_v0.1.2.js b/public/js/lib/bonfire/bonfireFramework_v0.1.2.js index 7721ece9f2..2e68888f53 100644 --- a/public/js/lib/bonfire/bonfireFramework_v0.1.2.js +++ b/public/js/lib/bonfire/bonfireFramework_v0.1.2.js @@ -250,7 +250,7 @@ var runTests = function(err, data) { }; function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time +', Attempts: ' + attempts); $('#complete-bonfire-dialog').modal('show'); $('#complete-bonfire-dialog').keydown(function(e) { diff --git a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js index d572181512..6ab42ee738 100644 --- a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js +++ b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js @@ -138,7 +138,7 @@ function doLinting () { //$('#testSuite').empty(); function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time); $('#next-courseware-button').removeAttr('disabled'); $('#next-courseware-button').addClass('animated tada'); diff --git a/public/js/lib/coursewares/coursewaresJSFramework.js b/public/js/lib/coursewares/coursewaresJSFramework.js index ee89e7eaf4..dedbf6660b 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework.js +++ b/public/js/lib/coursewares/coursewaresJSFramework.js @@ -235,7 +235,7 @@ var runTests = function(err, data) { }; function showCompletion() { - var time = Math.floor(Date.now() / 1000) - started; + var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challengeName + ', Time: ' + time +', Attempts: ' + attempts); $('#complete-courseware-dialog').modal('show'); $('#complete-courseware-dialog').keydown(function(e) { diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index da09c0a208..e8e03f9b50 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -90,7 +90,7 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedBonfireHash = !{JSON.stringify(bonfireHash)}; var challengeName = !{JSON.stringify(name)}; - var started = Math.floor(Date.now() / 1000); + var started = Math.floor(Date.now()); var _ = R; var dashed = !{JSON.stringify(dashedName)}; .col-xs-12.col-sm-12.col-md-8 @@ -111,7 +111,7 @@ block content .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) + - if (user) form.form-horizontal(novalidate='novalidate', name='completedWithForm') .form-group.text-center .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn @@ -126,7 +126,7 @@ block content a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next bonfire (ctrl + enter) - - if (points && points > 2) + - if (user.progressTimestamps.length > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") i.fa.fa-twitter   = phrase @@ -139,16 +139,4 @@ block content .modal-header.all-list-header Bonfires a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body - include ../partials/bonfires - script. - $.ajax({ - url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fbonfires%2F' + dashed + '&format=txt' - }) - .success( - function (data) { - console.log(data); - url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; - $('.btn-twitter').attr('href', url); - } - ); - + include ../partials/bonfires \ No newline at end of file diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 8f2a457ca6..b854741fc2 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -38,7 +38,7 @@ block content span.ion-arrow-up-b | Less information br - - if (cc) + - if (user) a.btn.btn-primary.btn-lg.btn-block#next-courseware-button | Go to my next challenge br @@ -60,7 +60,7 @@ block content var challengeName = !{JSON.stringify(name)}; var passedCoursewareName = challengeName; var prodOrDev = !{JSON.stringify(environment)}; - var started = Math.floor(Date.now() / 1000); + var started = Math.floor(Date.now()); .col-xs-12.col-sm-12.col-md-5.col-lg-6 #mainEditorPanel form.code diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index f06876ab4e..2070926f5d 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -47,7 +47,7 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; - var started = Math.floor(Date.now() / 1000); + var started = Math.floor(Date.now()); .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code @@ -63,7 +63,7 @@ block content .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) + - if (user) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - if (points && points > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 278a19609f..57663ccb3b 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -12,7 +12,7 @@ block content .embed-responsive.embed-responsive-16by9 iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') br - - if (cc) + - if (user) a.btn.btn-primary.btn-lg.btn-block#completed-courseware I've completed this challenge (ctrl + enter) script. var userLoggedIn = true; @@ -26,7 +26,7 @@ block content var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; var passedCoursewareName = challengeName; - var started = Math.floor(Date.now() / 1000); + var started = Math.floor(Date.now()); #complete-courseware-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content @@ -36,9 +36,9 @@ block content .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) + - if (user) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) Go to my next challenge (ctrl + enter) - - if (points && points > 2) + - if (user.progressTimestamps.length > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") i.fa.fa-twitter   = phrase diff --git a/views/ziplines/index.jade b/views/ziplines/index.jade deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/views/ziplines/show.jade b/views/ziplines/show.jade deleted file mode 100644 index fbc786d94f..0000000000 --- a/views/ziplines/show.jade +++ /dev/null @@ -1,50 +0,0 @@ -extends ../layout-wide -block content - .row - .col-xs-12.col-sm-12.col-md-4.bonfire-top - h1.text-center= name - .well - h4 - ol - for step in details - li!= step - .col-xs-12.col-sm-12.col-md-8 - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') - br - - if (cc) - a.btn.btn-primary.btn-lg.btn-block#completed-zipline I've completed this Zipline (ctrl + enter) - script. - var userLoggedIn = true; - - else - a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - script. - var userLoggedIn = false; - br - script(type="text/javascript"). - var tests = !{JSON.stringify(tests)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareName = challengeName; - var started = Math.floor(Date.now() / 1000); - #complete-courseware-dialog.modal(tabindex='-1') - .modal-dialog.animated.zoomIn.fast-animation - .modal-content - .modal-header.challenge-list-header= compliment - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body(ng-controller="pairedWithController") - .text-center - .animated.zoomInDown.delay-half - span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) Go to my next challenge (ctrl + enter) - - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") - i.fa.fa-twitter   - = phrase - - else - a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - h1 #{name} - script. - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file From efd7707ac83546c88345d4571f83e08d8dd76862 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 28 Mar 2015 11:34:12 +0900 Subject: [PATCH 35/57] Move to Mongoose 4.0, begin implementation of streak --- app.js | 2 ++ controllers/user.js | 76 ++++++++++++++++++++++++++++++++++++++------- package.json | 5 +-- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/app.js b/app.js index cc0d3ea894..878135beb2 100644 --- a/app.js +++ b/app.js @@ -384,6 +384,8 @@ app.post( app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); +app.get('/user/streak', userController.getStreak); + /** * API routes */ diff --git a/controllers/user.js b/controllers/user.js index 50ccf2ba47..5e37f84c47 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -6,7 +6,6 @@ var _ = require('lodash'), User = require('../models/User'), secrets = require('../config/secrets'), moment = require('moment'), - Challenge = require('./../models/Challenge'), debug = require('debug')('freecc:cntr:challenges'), resources = require('./resources'); @@ -18,7 +17,9 @@ var _ = require('lodash'), */ exports.getSignin = function(req, res) { - if (req.user) return res.redirect('/'); + if (req.user) { + return res.redirect('/'); + } res.render('account/signin', { title: 'Free Code Camp Login' }); @@ -41,13 +42,17 @@ exports.postSignin = function(req, res, next) { } passport.authenticate('local', function(err, user, info) { - if (err) return next(err); + if (err) { + return next(err); + } if (!user) { req.flash('errors', { msg: info.message }); return res.redirect('/signin'); } req.logIn(user, function(err) { - if (err) return next(err); + if (err) { + return next(err); + } req.flash('success', { msg: 'Success! You are logged in.' }); res.redirect(req.session.returnTo || '/'); }); @@ -70,7 +75,9 @@ exports.signout = function(req, res) { */ exports.getEmailSignin = function(req, res) { - if (req.user) return res.redirect('/'); + if (req.user) { + return res.redirect('/'); + } res.render('account/email-signin', { title: 'Sign in to your Free Code Camp Account' }); @@ -82,7 +89,9 @@ exports.getEmailSignin = function(req, res) { */ exports.getEmailSignup = function(req, res) { - if (req.user) return res.redirect('/'); + if (req.user) { + return res.redirect('/'); + } res.render('account/email-signup', { title: 'Create Your Free Code Camp Account' }); @@ -98,7 +107,7 @@ exports.postEmailSignup = function(req, res, next) { req.assert('email', 'valid email required').isEmail(); var errors = req.validationErrors(); - + if (errors) { req.flash('errors', errors); return res.redirect('/email-signup'); @@ -124,7 +133,7 @@ exports.postEmailSignup = function(req, res, next) { var user = new User({ email: req.body.email.trim(), password: req.body.password, - profile : { + profile: { username: req.body.username.trim(), picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' } @@ -174,7 +183,7 @@ exports.postEmailSignup = function(req, res, next) { 'Greetings from San Francisco!\n\n', 'Thank you for joining our community.\n', 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'And if you have a moment, check out our blog: blog.freecodecamp.com.\n', 'Good luck with the challenges!\n\n', '- the Volunteer Camp Counselor Team' ].join('') @@ -191,9 +200,54 @@ exports.postEmailSignup = function(req, res, next) { */ exports.getStreak = function(req, res) { - var completedStreak = req.user.challengesHash; -} + Array.prototype.timeReduce = function(combiner, initialValue) { + var counter, + accumulatedValue; + + // If the array is empty, do nothing + if (this.length === 0) { + return this; + } else { + // If the user didn't pass an initial value, use the first item. + if (arguments.length === 1) { + counter = 1; + accumulatedValue = this[0]; + } + else if (arguments.length >= 2) { + counter = 0; + accumulatedValue = initialValue; + } + else { + throw "Invalid arguments."; + } + + // Loop through the array, feeding the current value and the result of + // the previous computation back into the combiner function until + // we've exhausted the entire array and are left with only one function. + while (counter < this.length) { + accumulatedValue = combiner(accumulatedValue, this[counter]); + counter++; + } + + return [accumulatedValue]; + } + }; + + var timeObject = req.user.progressTimestamps.timeReduce(function(accumulatedTime, timeStamp) { + + var copyOfAccumulatedTime = Object.create(accumulatedTime); + + copyOfAccumulatedTime[moment(timeStamp) + .format('MMMM Do YYYY')] = timeStamp; + + return copyOfAccumulatedTime; + }, + {}); + + debug('TimeObject is', timeObject); + return res.send(timeObject); +}; /** * GET /account diff --git a/package.json b/package.json index ffe8c6281b..634fada27b 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,9 @@ "method-override": "^2.3.0", "moment": "^2.8.4", "mongodb": "^1.4.33", - "mongoose": "^3.8.19", - "mongoose-text-search": "0.0.2", + "mongoose": "^4.0.0", "morgan": "^1.5.0", "newrelic": "^1.13.3", - "node": "0.0.0", "nodemailer": "^1.3.0", "passport": "^0.2.1", "passport-facebook": "^1.0.3", @@ -76,7 +74,6 @@ "chai": "^1.10.0", "gulp": "^3.8.8", "gulp-inject": "^1.0.2", - "gulp-minify-css": "^0.5.1", "gulp-nodemon": "^1.0.4", "mocha": "^2.0.1", "multiline": "^1.0.1", From 338d87424c3f0edf2d8d41a6e62ac4e4752770b3 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 28 Mar 2015 12:22:08 +0900 Subject: [PATCH 36/57] More work towards unifying timestamps --- controllers/bonfire.js | 3 +++ controllers/courseware.js | 3 ++- controllers/resources.js | 1 + views/coursewares/showJS.jade | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 6a4712f3b1..3fecaa89f7 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -236,6 +236,7 @@ exports.completedBonfire = function (req, res) { } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } @@ -243,6 +244,7 @@ exports.completedBonfire = function (req, res) { index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); pairedWith.uncompletedBonfires.splice(index, 1); @@ -285,6 +287,7 @@ exports.completedBonfire = function (req, res) { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } diff --git a/controllers/courseware.js b/controllers/courseware.js index 28f4f6bec6..0d40f37c8e 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -253,11 +253,12 @@ exports.completedCourseware = function (req, res, next) { var index = req.user.completedCoursewares.indexOf(coursewareHash); if (index === -1) { + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); } - req.user.save(function (err, user) { + req.user.save(function (err, user, next) { if (err) { return next(err); } diff --git a/controllers/resources.js b/controllers/resources.js index 8cdd70e6d5..b430db79cd 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -172,6 +172,7 @@ module.exports = { var date2 = new Date(); var progressTimestamps = req.user.progressTimestamps; var now = Date.now() || 0; + if (req.user.pointsNeedMigration) { var challengesHash = req.user.challengesHash; for (var key in challengesHash) { diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index adbba9293f..32ed942ae6 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -47,6 +47,7 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; var started = Math.floor(Date.now()); .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel From d3dee00df55128d2f5afa6947c6cafb32ce189ad Mon Sep 17 00:00:00 2001 From: jameskopacz Date: Sat, 28 Mar 2015 00:16:48 -0500 Subject: [PATCH 37/57] Stories reply and upvote redirect to signin when user isn't signed in. Redirects user back to story after signing in. --- app.js | 4 +++- public/js/main.js | 4 ++++ views/stories/comments.jade | 3 ++- views/stories/show.jade | 5 ++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 6a50426361..95de20dfcc 100644 --- a/app.js +++ b/app.js @@ -209,7 +209,9 @@ app.use(function (req, res, next) { app.use(function (req, res, next) { // Remember original destination before login. var path = req.path.split('/')[1]; - if (/auth|login|logout|signup|fonts|favicon/i.test(path)) { + if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) { + return next(); + } else if (/\/stories\/comments\/\w+/i.test(req.path)) { return next(); } req.session.returnTo = req.path; diff --git a/public/js/main.js b/public/js/main.js index 734d5ea5bd..ef23b231bc 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -135,6 +135,10 @@ $(document).ready(function() { }); var upvoteHandler = function () { + if (typeof user == "undefined" || !user) { + window.location.href = '/signin'; + return; + } var _id = storyId; $('#upvote').unbind('click'); var alreadyUpvoted = false; diff --git a/views/stories/comments.jade b/views/stories/comments.jade index 751f65f915..546d843a28 100644 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -56,7 +56,8 @@ sentinel--; if (!sentinel) { $('.comment-a-comment').on('click', 'a', function () { - if (!user) { + if (typeof user == "undefined" || !user) { + window.location.href = '/signin'; return; } $(this).unbind('click'); diff --git a/views/stories/show.jade b/views/stories/show.jade index ea5e08032e..dac2842b0b 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -56,7 +56,10 @@ $('#image-display').removeClass('hidden-element') } $('#reply-to-main-post').on('click', function() { - if (!user) return; + if (typeof user == "undefined" || !user) { + window.location.href = '/signin'; + return; + } $('#initial-comment-submit').removeClass('hidden-element'); $(this).unbind('click'); $('.comment-to-comment-formgroup').empty(); From e4eb5052c7826f1cecffb0e6571b858afadccf6b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 23:15:39 -0700 Subject: [PATCH 38/57] intermediary work on ziplines --- controllers/courseware.js | 2 +- seed_data/ziplines.json | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 seed_data/ziplines.json diff --git a/controllers/courseware.js b/controllers/courseware.js index 28f4f6bec6..ecbb6b2341 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -267,7 +267,7 @@ exports.completedCourseware = function (req, res, next) { }); }; -exports.completedZiplineOrBasejump = function (req, res, next) { +exports.completedBasejump = function (req, res, next) { var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; diff --git a/seed_data/ziplines.json b/seed_data/ziplines.json deleted file mode 100644 index 8a58133697..0000000000 --- a/seed_data/ziplines.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "name": "Status Checker", - "picture": "", - "video": "", - "gitHubLink": "https://github.com/FreeCodeCamp/ZiplineStatusChecker", - "demoLink": "", - "details": [] - } -] \ No newline at end of file From a1ffc88b58b677b416725bc59ebfd7a572af71ff Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 23:30:06 -0700 Subject: [PATCH 39/57] add trello and blogger apis back in --- app.js | 2 ++ controllers/resources.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/app.js b/app.js index 6a50426361..ecbdc18fd5 100644 --- a/app.js +++ b/app.js @@ -397,6 +397,8 @@ app.get('/account/api', userController.getAccountAngular); */ app.get('/api/github', resourcesController.githubCalls); +app.get('/api/blogger', resourcesController.bloggerCalls); +app.get('/api/trello', resourcesController.trelloCalls); /** * Bonfire related routes diff --git a/controllers/resources.js b/controllers/resources.js index 6dc3b7e3ce..b6897857b0 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -150,6 +150,21 @@ module.exports = { }); }, + trelloCalls: function(req, res, next) { + request('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(err, status, trello) { + if (err) { return next(err); } + trello = (status && status.statusCode == 200) ? (JSON.parse(trello)) : "Can't connect to to Trello"; + res.end(JSON.stringify(trello)); + }); + }, + bloggerCalls: function(req, res, next) { + request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) { + if (err) { return next(err); } + blog = (status && status.statusCode == 200) ? JSON.parse(blog) : "Can't connect to Blogger"; + res.end(JSON.stringify(blog)); + }); + }, + about: function(req, res, next) { if (req.user) { if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { From 0a07c1e3972798ad54696481372707bfeaccdcb9 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 28 Mar 2015 17:38:11 +0900 Subject: [PATCH 40/57] Streaks! --- controllers/courseware.js | 585 +++++++++++++++--------------- controllers/resources.js | 722 +++++++++++++++++++------------------- controllers/user.js | 77 ++-- models/User.js | 29 +- 4 files changed, 721 insertions(+), 692 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 0d40f37c8e..b4a08a7e1d 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -1,351 +1,356 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:courseware'), - Courseware = require('./../models/Courseware'), - User = require('./../models/User'), - resources = require('./resources'), - R = require('ramda'); + debug = require('debug')('freecc:cntr:courseware'), + Courseware = require('./../models/Courseware'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'), + moment = require('moment'); /** * Courseware controller */ exports.showAllCoursewares = function(req, res) { - var completedCoursewares = req.user.completedCoursewares.map(function(elem) { - return elem._id; - }); + var completedCoursewares = req.user.completedCoursewares.map(function(elem) { + return elem._id; + }); - var noDuplicatedCoursewares = R.uniq(completedCoursewares); - var data = {}; - data.coursewareList = resources.allCoursewareNames(); - data.completedList = noDuplicatedCoursewares; - res.send(data); + var noDuplicatedCoursewares = R.uniq(completedCoursewares); + var data = {}; + data.coursewareList = resources.allCoursewareNames(); + data.completedList = noDuplicatedCoursewares; + res.send(data); }; exports.returnNextCourseware = function(req, res) { - if (!req.user) { - return res.redirect('../challenges/learn-how-free-code-camp-works'); + if (!req.user) { + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + var completed = req.user.completedCoursewares.map(function (elem) { + return elem._id; + }); + + req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; } - var completed = req.user.completedCoursewares.map(function (elem) { - return elem._id; - }); + }); + req.user.save(); - req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - req.user.save(); - - var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); + var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); - var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); - displayedCoursewares.exec(function(err, courseware) { - if (err) { - next(err); - } + var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); + displayedCoursewares.exec(function(err, courseware) { + if (err) { + next(err); + } - courseware = courseware.pop(); - if (courseware === undefined) { - req.flash('errors', { - msg: "It looks like you've completed all the courses we have available. Good job!" - }); - return res.redirect('../challenges/learn-how-free-code-camp-works'); - } - nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../challenges/' + nameString); - }); + courseware = courseware.pop(); + if (courseware === undefined) { + req.flash('errors', { + msg: "It looks like you've completed all the courses we have available. Good job!" + }); + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../challenges/' + nameString); + }); }; exports.returnIndividualCourseware = function(req, res, next) { - var dashedName = req.params.coursewareName; + var dashedName = req.params.coursewareName; - coursewareName = dashedName.replace(/\-/g, ' '); + coursewareName = dashedName.replace(/\-/g, ' '); - Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { - if (err) { - next(err); - } - // Handle not found - if (courseware.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a challenge with that name. Please double check the name." + Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { + if (err) { + next(err); + } + // Handle not found + if (courseware.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a challenge with that name. Please double check the name." + }); + return res.redirect('/challenges'); + } + courseware = courseware.pop(); + + // Redirect to full name if the user only entered a partial + var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../challenges/' + dashedNameFull); + } + + var challengeType = { + 0 : function() { + res.render('coursewares/showHTML', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + brief: courseware.description[0], + details: courseware.description.slice(1), + tests: courseware.tests, + challengeSeed: courseware.challengeSeed, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + environment: resources.whichEnvironment() }); - return res.redirect('/challenges'); - } - courseware = courseware.pop(); + }, - // Redirect to full name if the user only entered a partial - var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../challenges/' + dashedNameFull); - } + 1 : function() { + res.render('coursewares/showJS', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + brief: courseware.description[0], + details: courseware.description.slice(1), + tests: courseware.tests, + challengeSeed: courseware.challengeSeed, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, - var challengeType = { - 0 : function() { - res.render('coursewares/showHTML', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - brief: courseware.description[0], - details: courseware.description.slice(1), - tests: courseware.tests, - challengeSeed: courseware.challengeSeed, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - environment: resources.whichEnvironment() - }); - }, + }); + }, - 1 : function() { - res.render('coursewares/showJS', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - brief: courseware.description[0], - details: courseware.description.slice(1), - tests: courseware.tests, - challengeSeed: courseware.challengeSeed, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, + 2: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: 'video' + }); + }, - }); - }, + 3: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: 'zipline' + }); + }, - 2: function() { - res.render('coursewares/showVideo', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - details: courseware.description, - tests: courseware.tests, - video: courseware.challengeSeed[0], - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - challengeType: 'video' - }); - }, + 4: function() { + res.render('coursewares/showVideo', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + tests: courseware.tests, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: 'basejump' + }); + } + }; - 3: function() { - res.render('coursewares/showVideo', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - details: courseware.description, - tests: courseware.tests, - video: courseware.challengeSeed[0], - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - challengeType: 'zipline' - }); - }, + return challengeType[courseware.challengeType](); - 4: function() { - res.render('coursewares/showVideo', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - details: courseware.description, - tests: courseware.tests, - video: courseware.challengeSeed[0], - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - challengeType: 'basejump' - }); - } - }; - - return challengeType[courseware.challengeType](); - - }); + }); }; exports.testCourseware = function(req, res) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - res.render('courseware/show', { - completedWith: null, - title: coursewareName, - name: coursewareName, - difficulty: +coursewareDifficulty, - brief: coursewareDescription[0], - details: coursewareDescription.slice(1), - tests: coursewareTests, - challengeSeed: coursewareChallengeSeed, - challengeEntryPoint: coursewareEntryPoint, - cc: req.user ? req.user.coursewaresHash : undefined, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewares: [], - coursewareHash: "test" - }); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + res.render('courseware/show', { + completedWith: null, + title: coursewareName, + name: coursewareName, + difficulty: +coursewareDifficulty, + brief: coursewareDescription[0], + details: coursewareDescription.slice(1), + tests: coursewareTests, + challengeSeed: coursewareChallengeSeed, + challengeEntryPoint: coursewareEntryPoint, + cc: req.user ? req.user.coursewaresHash : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewares: [], + coursewareHash: "test" + }); }; function getRidOfEmpties(elem) { - if (elem.length > 0) { - return elem; - } + if (elem.length > 0) { + return elem; + } }; exports.publicGenerator = function(req, res) { - res.render('courseware/public-generator'); + res.render('courseware/public-generator'); }; exports.generateChallenge = function(req, res) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - var response = { - _id: randomString(), - name: coursewareName, - difficulty: coursewareDifficulty, - description: coursewareDescription, - challengeEntryPoint: coursewareEntryPoint, - challengeSeed: coursewareChallengeSeed, - tests: coursewareTests - }; - res.send(response); + var response = { + _id: randomString(), + name: coursewareName, + difficulty: coursewareDifficulty, + description: coursewareDescription, + challengeEntryPoint: coursewareEntryPoint, + challengeSeed: coursewareChallengeSeed, + tests: coursewareTests + }; + res.send(response); }; exports.completedCourseware = function (req, res, next) { - var isCompletedDate = Math.round(+new Date()); - var coursewareHash = req.body.coursewareInfo.coursewareHash; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; - debug('this is the coursewarehash we got', coursewareHash); + debug('this is the coursewarehash we got', coursewareHash); - req.user.completedCoursewares.push({ - _id: coursewareHash, - completedDate: isCompletedDate, - name: req.body.coursewareInfo.coursewareName - }); + req.user.completedCoursewares.push({ + _id: coursewareHash, + completedDate: isCompletedDate, + name: req.body.coursewareInfo.coursewareName + }); - var index = req.user.completedCoursewares.indexOf(coursewareHash); + var index = req.user.completedCoursewares.indexOf(coursewareHash); - if (index === -1) { + if (index === -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedCoursewares.splice(index, 1); + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + + req.user.save(function (err, user, next) { + if (err) { + return next(err); } - - req.user.save(function (err, user, next) { - if (err) { - return next(err); - } - if (user) { - res.send(true); - } - }); + if (user) { + res.send(true); + } + }); }; exports.completedZiplineOrBasejump = function (req, res, next) { - var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; - var isCompletedDate = Math.round(+new Date()); - var coursewareHash = req.body.coursewareInfo.coursewareHash; - var solutionLink = req.body.coursewareInfo.solutionLink; - if(!solutionLink) { - // flash error and redirect - } + var isCompletedWith = req.body.bonfireInfo.completedWith || false; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + var solutionLink = req.body.coursewareInfo.solutionLink; + if (!solutionLink) { + // flash error and redirect + return next(new Error('No solution provided')); + } - if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { - if (err) { - return err; - } else { - var index = req.user.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) - } - pairedWith = pairedWith.pop(); - - index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedBonfires.splice(index, 1); - - } - - pairedWith.completedBonfires.push({ - _id: bonfireHash, - completedWith: req.user._id, - completedDate: isCompletedDate, - solution: isSolution - }); - - req.user.completedBonfires.push({ - _id: bonfireHash, - completedWith: pairedWith._id, - completedDate: isCompletedDate, - solution: isSolution - }); - - req.user.save(function (err, user) { - pairedWith.save(function (err, paired) { - if (err) { - throw err; - } - if (user && paired) { - res.send(true); - } - }) - }); - } - }) - } else { - - req.user.completedBonfires.push({ - _id: bonfireHash, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution - }); - - var index = req.user.uncompletedCourse.indexOf(bonfireHash); + if (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return next(err); + } else { + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + pairedWith = pairedWith.pop(); + + index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedCoursewares.splice(index, 1); + } - req.user.save(function (err, user) { - if (err) { - throw err; - } - if (user) { - debug('Saving user'); - res.send(true) - } + pairedWith.completedCoursewares.push({ + _id: coursewareHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: solutionLink }); + + req.user.completedCoursewares.push({ + _id: coursewareHash, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: solutionLink + }); + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + return res.send(true); + } + }); + }); + } + }); + } else { + + req.user.completedCoursewares.push({ + _id: coursewareHash, + completedWith: null, + completedDate: isCompletedDate, + solution: solutionLink + }); + + var index = req.user.uncompletedCourse.indexOf(coursewareHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); } -}; \ No newline at end of file + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + debug('Saving user'); + return res.send(true); + } + }); + } +}; diff --git a/controllers/resources.js b/controllers/resources.js index b430db79cd..ff66bfeb7e 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -1,20 +1,20 @@ var async = require('async'), - User = require('../models/User'), - Challenge = require('./../models/Challenge'), - Bonfire = require('./../models/Bonfire'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - resources = require('./resources.json'), - steps = resources.steps, - secrets = require('./../config/secrets'), - bonfires = require('../seed_data/bonfires.json'), - coursewares = require('../seed_data/coursewares.json'), - moment = require('moment'), - https = require('https'), - debug = require('debug')('freecc:cntr:resources'), - cheerio = require('cheerio'), - request = require('request'), - R = require('ramda'); + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + bonfires = require('../seed_data/bonfires.json'), + coursewares = require('../seed_data/coursewares.json'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); /** * GET / @@ -26,371 +26,381 @@ Array.zip = function(left, right, combinerFunction) { results = []; for (counter = 0; counter < Math.min(left.length, right.length); counter++) { - results.push(combinerFunction(left[counter],right[counter])); + results.push(combinerFunction(left[counter], right[counter])); } return results; }; module.exports = { - privacy: function privacy(req, res) { - res.render('resources/privacy', { - title: 'Privacy' - }); - }, + 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'); + sitemap: function sitemap(req, res, next) { + var appUrl = 'http://www.freecodecamp.com'; + var now = moment(new Date()).format('YYYY-MM-DD'); - User.find({'profile.username': {'$ne': '' }}, function(err, users) { + User.find({'profile.username': {'$ne': '' }}, function(err, users) { + if (err) { + debug('User err: ', err); + return next(err); + } + Challenge.find({}, function (err, challenges) { + if (err) { + debug('User err: ', err); + return next(err); + } + Bonfire.find({}, function (err, bonfires) { + if (err) { + debug('User err: ', err); + return next(err); + } + Story.find({}, function (err, stories) { if (err) { - debug('User err: ', err); - return next(err); + debug('User err: ', err); + return next(err); } - Challenge.find({}, function (err, challenges) { - if (err) { - debug('User err: ', err); - return next(err); - } - Bonfire.find({}, function (err, bonfires) { - if (err) { - debug('User err: ', err); - return next(err); - } - Story.find({}, function (err, stories) { - if (err) { - debug('User err: ', err); - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.render('resources/sitemap', { - appUrl: appUrl, - now: now, - users: users, - challenges: challenges, - bonfires: bonfires, - stories: stories - }); - }); - }); + res.header('Content-Type', 'application/xml'); + res.render('resources/sitemap', { + appUrl: appUrl, + now: now, + users: users, + challenges: challenges, + bonfires: bonfires, + stories: stories }); + }); }); - }, + }); + }); + }, - deployAWebsite: function deployAWebsite(req, res) { - res.render('resources/deploy-a-website', { - title: 'Deploy a Dynamic Website in 7 Minutes' - }); - }, + deployAWebsite: function deployAWebsite(req, res) { + res.render('resources/deploy-a-website', { + title: 'Deploy a Dynamic Website in 7 Minutes' + }); + }, - chat: function chat(req, res) { - res.render('resources/chat', { - title: "Enter Free Code Camp's Chat Rooms" - }); - }, + chat: function chat(req, res) { + res.render('resources/chat', { + title: "Enter Free Code Camp's Chat Rooms" + }); + }, - nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { - res.render('resources/nonprofit-project-instructions', { - title: 'Nonprofit Project Instructions' - }); - }, + nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { + res.render('resources/nonprofit-project-instructions', { + title: 'Nonprofit Project Instructions' + }); + }, - gmailShortcuts: function gmailShortcuts(req, res) { - res.render('resources/gmail-shortcuts', { - title: 'These Gmail Shortcuts will save you Hours' - }); - }, + gmailShortcuts: function gmailShortcuts(req, res) { + res.render('resources/gmail-shortcuts', { + title: 'These Gmail Shortcuts will save you Hours' + }); + }, - guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { - res.render('resources/guide-to-our-nonprofit-projects', { - title: 'A guide to our Nonprofit Projects' - }); - }, + guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { + res.render('resources/guide-to-our-nonprofit-projects', { + title: 'A guide to our Nonprofit Projects' + }); + }, - controlShortcuts: function controlShortcuts(req, res) { - res.render('resources/control-shortcuts', { - title: 'These Control Shortcuts will save you Hours' - }); - }, + controlShortcuts: function controlShortcuts(req, res) { + res.render('resources/control-shortcuts', { + title: 'These Control Shortcuts will save you Hours' + }); + }, - chromebook: function chromebook(req, res) { - res.render('resources/chromebook', { - title: 'Win a Chromebook' - }); - }, + chromebook: function chromebook(req, res) { + res.render('resources/chromebook', { + title: 'Win a Chromebook' + }); + }, - jqueryExercises: function jqueryExercises(req, res) { - res.render('resources/jquery-exercises', { - title: 'jQuery Exercises' - }); - }, + jqueryExercises: function jqueryExercises(req, res) { + res.render('resources/jquery-exercises', { + title: 'jQuery Exercises' + }); + }, - livePairProgramming: function(req, res) { - res.render('resources/live-pair-programming', { - title: 'Live Pair Programming' - }); - }, + livePairProgramming: function(req, res) { + res.render('resources/live-pair-programming', { + title: 'Live Pair Programming' + }); + }, - installScreenHero: function(req, res) { - res.render('resources/install-screenhero', { - title: 'Install ScreenHero' - }); - }, + installScreenHero: function(req, res) { + res.render('resources/install-screenhero', { + title: 'Install ScreenHero' + }); + }, - javaScriptInYourInbox: function(req, res) { - res.render('resources/javascript-in-your-inbox', { - title: 'JavaScript in your Inbox' - }); - }, + javaScriptInYourInbox: function(req, res) { + res.render('resources/javascript-in-your-inbox', { + title: 'JavaScript in your Inbox' + }); + }, - nodeSchoolChallenges: function(req, res) { - res.render('resources/nodeschool-challenges', { - title: 'NodeSchool Challenges' - }); - }, + nodeSchoolChallenges: function(req, res) { + res.render('resources/nodeschool-challenges', { + title: 'NodeSchool Challenges' + }); + }, - githubCalls: function(req, res) { - var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; - request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { - pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github"; - request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) { - issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub"; - res.send({"issues": issues, "pulls" : pulls}); - }); - }); - }, + githubCalls: function(req, res) { + var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; + request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { + pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github"; + request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) { + issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub"; + res.send({"issues": issues, "pulls" : pulls}); + }); + }); + }, - about: function(req, res, next) { - if (req.user) { - if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { - req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"; - req.user.save(); - } - } - var date1 = new Date('10/15/2014'); - var date2 = new Date(); - var progressTimestamps = req.user.progressTimestamps; - var now = Date.now() || 0; - - if (req.user.pointsNeedMigration) { - var challengesHash = req.user.challengesHash; - for (var key in challengesHash) { - if (challengesHash[key] > 0) { - req.user.progressTimestamps.push(challengesHash[key]); - } - } - - var timeStamps = []; - R.keys(req.user.challengesHash).forEach(function(key) { - "use strict"; - var timeStamp = parseInt(challengesHash[key], 10); - timeStamps.push({timeStamp: timeStamp.length !== 13 ? (+timeStamp) : (+timeStamp * 1000)}); - }); - - req.user.completedCoursewares = Array.zip(timeStamps, coursewares, - function(left, right) { - "use strict"; - return ({ - completedDate: left.timeStamp, - _id: right._id, - name: right.name - }); - }).filter(function(elem) { - "use strict"; - return elem.completedDate !== 0; - }); - req.user.pointsNeedMigration = false; - req.user.save(); - } - if (progressTimestamps[progressTimestamps.length - 1] <= (now - 43200)) { - req.user.progressTimestamps.push(now); - } - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); - var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); - var announcements = resources.announcements; - function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); - } - User.count({}, function (err, c3) { - if (err) { - debug('User err: ', err); - return next(err); - } - User.count({'points': {'$gt': 53}}, function (err, all) { - if (err) { - debug('User err: ', err); - return next(err); - } - - res.render('resources/learn-to-code', { - title: 'About Free Code Camp and Our Team of Volunteers', - daysRunning: daysRunning, - c3: numberWithCommas(c3), - all: all, - announcements: announcements - }); - }); - }); - }, - - randomPhrase: function() { - var phrases = resources.phrases; - return phrases[Math.floor(Math.random() * phrases.length)]; - }, - - randomVerb: function() { - var verbs = resources.verbs; - return verbs[Math.floor(Math.random() * verbs.length)]; - }, - - randomCompliment: function() { - var compliments = resources.compliments; - return compliments[Math.floor(Math.random() * compliments.length)]; - }, - - allBonfireIds: function() { - return bonfires.map(function(elem) { - return { - _id: elem._id, - difficulty: elem.difficulty - } - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map(function(elem) { - return elem._id; - }); - }, - allBonfireNames: function() { - return bonfires.map(function(elem) { - return { - name: elem.name, - difficulty: elem.difficulty, - _id: elem._id - } - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map (function(elem) { - return { - name : elem.name, - _id: elem._id - } - }); - }, - - getAllCourses: function() { - "use strict"; - return coursewares; - }, - - allCoursewareIds: function() { - return coursewares.map(function(elem) { - return { - _id: elem._id, - difficulty: elem.difficulty - }; - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map(function(elem) { - return elem._id; - }); - }, - allCoursewareNames: function() { - return coursewares.map(function(elem) { - return { - name: elem.name, - difficulty: elem.difficulty, - _id: elem._id - }; - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map (function(elem) { - return { - name: elem.name, - _id: elem._id - }; - }); - }, - whichEnvironment: function() { - return process.env.NODE_ENV; - }, - getURLTitle: function(url, callback) { - (function () { - var result = {title: '', image: '', url: '', description: ''}; - request(url, function (error, response, body) { - if (!error && response.statusCode === 200) { - var $ = cheerio.load(body); - var metaDescription = $("meta[name='description']"); - var metaImage = $("meta[property='og:image']"); - var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; - var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; - result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; - result.image = urlImage; - result.description = description; - callback(null, result); - } else { - callback('failed'); - } - }); - })(); - }, - updateUserStoryPictures: function(userId, picture, username, cb) { - - var counter = 0, - foundStories, - foundComments; - - Story.find({'author.userId': userId}, function(err, stories) { - if (err) { - return cb(err); - } - foundStories = stories; - counter++; - saveStoriesAndComments(); - }); - Comment.find({'author.userId': userId}, function(err, comments) { - if (err) { - return cb(err); - } - foundComments = comments; - counter++; - saveStoriesAndComments(); - }); - - function saveStoriesAndComments() { - if (counter !== 2) { - return; - } - var tasks = []; - R.forEach(function(comment) { - comment.author.picture = picture; - comment.author.username = username; - comment.markModified('author'); - tasks.push(function(cb) { - comment.save(cb); - }); - }, foundComments); - - R.forEach(function(story) { - story.author.picture = picture; - story.author.username = username; - story.markModified('author'); - tasks.push(function(cb) { - story.save(cb); - }); - }, foundStories); - async.parallel(tasks, function(err) { - if (err) { return cb(err); } - cb(); - }); - } + about: function(req, res, next) { + if (req.user) { + if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { + req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"; + req.user.save(); + } } + var date1 = new Date('10/15/2014'); + var date2 = new Date(); + var progressTimestamps = req.user.progressTimestamps; + var now = Date.now() || 0; + + if (req.user.pointsNeedMigration) { + var challengesHash = req.user.challengesHash; + for (var key in challengesHash) { + if (challengesHash[key] > 0) { + req.user.progressTimestamps.push(challengesHash[key]); + } + } + + var oldChallengeKeys = R.keys(req.user.challengesHash); + + var updatedTimesFromOldChallenges = oldChallengeKeys.map(function(timeStamp) { + if (timeStamp.toString().length !== 13) { + timeStamp *= 1000; + } + return timeStamp; + }); + + var newTimeStamps = R.map(function(timeStamp) { + if (timeStamp.toString().length !== 13) { + timeStamp *= 1000; + } + return timeStamp; + }, req.user.progressTimestamps); + + req.user.progressTimestamps = newTimeStamps; + + + req.user.completedCoursewares = Array.zip(updatedTimesFromOldChallenges, coursewares, + function(left, right) { + return ({ + completedDate: left.timeStamp, + _id: right._id, + name: right.name + }); + }).filter(function(elem) { + return elem.completedDate !== 0; + }); + req.user.pointsNeedMigration = false; + req.user.save(); + } + if (progressTimestamps[progressTimestamps.length - 1] <= (now - 43200)) { + req.user.progressTimestamps.push(now); + } + var timeDiff = Math.abs(date2.getTime() - date1.getTime()); + var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); + var announcements = resources.announcements; + function numberWithCommas(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } + User.count({}, function (err, c3) { + if (err) { + debug('User err: ', err); + return next(err); + } + User.count({'points': {'$gt': 53}}, function (err, all) { + if (err) { + debug('User err: ', err); + return next(err); + } + + res.render('resources/learn-to-code', { + title: 'About Free Code Camp and Our Team of Volunteers', + daysRunning: daysRunning, + c3: numberWithCommas(c3), + all: all, + announcements: announcements + }); + }); + }); + }, + + randomPhrase: function() { + var phrases = resources.phrases; + return phrases[Math.floor(Math.random() * phrases.length)]; + }, + + randomVerb: function() { + var verbs = resources.verbs; + return verbs[Math.floor(Math.random() * verbs.length)]; + }, + + randomCompliment: function() { + var compliments = resources.compliments; + return compliments[Math.floor(Math.random() * compliments.length)]; + }, + + allBonfireIds: function() { + return bonfires.map(function(elem) { + return { + _id: elem._id, + difficulty: elem.difficulty + } + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map(function(elem) { + return elem._id; + }); + }, + allBonfireNames: function() { + return bonfires.map(function(elem) { + return { + name: elem.name, + difficulty: elem.difficulty, + _id: elem._id + } + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map (function(elem) { + return { + name : elem.name, + _id: elem._id + } + }); + }, + + getAllCourses: function() { + "use strict"; + return coursewares; + }, + + allCoursewareIds: function() { + return coursewares.map(function(elem) { + return { + _id: elem._id, + difficulty: elem.difficulty + }; + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map(function(elem) { + return elem._id; + }); + }, + allCoursewareNames: function() { + return coursewares.map(function(elem) { + return { + name: elem.name, + difficulty: elem.difficulty, + _id: elem._id + }; + }) + .sort(function(a, b) { + return a.difficulty - b.difficulty; + }) + .map (function(elem) { + return { + name: elem.name, + _id: elem._id + }; + }); + }, + whichEnvironment: function() { + return process.env.NODE_ENV; + }, + getURLTitle: function(url, callback) { + (function () { + var result = {title: '', image: '', url: '', description: ''}; + request(url, function (error, response, body) { + if (!error && response.statusCode === 200) { + var $ = cheerio.load(body); + var metaDescription = $("meta[name='description']"); + var metaImage = $("meta[property='og:image']"); + var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; + var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; + result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; + result.image = urlImage; + result.description = description; + callback(null, result); + } else { + callback('failed'); + } + }); + })(); + }, + updateUserStoryPictures: function(userId, picture, username, cb) { + + var counter = 0, + foundStories, + foundComments; + + Story.find({'author.userId': userId}, function(err, stories) { + if (err) { + return cb(err); + } + foundStories = stories; + counter++; + saveStoriesAndComments(); + }); + Comment.find({'author.userId': userId}, function(err, comments) { + if (err) { + return cb(err); + } + foundComments = comments; + counter++; + saveStoriesAndComments(); + }); + + function saveStoriesAndComments() { + if (counter !== 2) { + return; + } + var tasks = []; + R.forEach(function(comment) { + comment.author.picture = picture; + comment.author.username = username; + comment.markModified('author'); + tasks.push(function(cb) { + comment.save(cb); + }); + }, foundComments); + + R.forEach(function(story) { + story.author.picture = picture; + story.author.username = username; + story.markModified('author'); + tasks.push(function(cb) { + story.save(cb); + }); + }, foundStories); + async.parallel(tasks, function(err) { + if (err) { return cb(err); } + cb(); + }); + } + } }; diff --git a/controllers/user.js b/controllers/user.js index 5e37f84c47..28d8d38a11 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -7,7 +7,8 @@ var _ = require('lodash'), secrets = require('../config/secrets'), moment = require('moment'), debug = require('debug')('freecc:cntr:challenges'), - resources = require('./resources'); + resources = require('./resources'), + R = require('ramda'); @@ -199,54 +200,44 @@ exports.postEmailSignup = function(req, res, next) { * For Calendar display */ -exports.getStreak = function(req, res) { +exports.getStreak = function(req, res, next) { - Array.prototype.timeReduce = function(combiner, initialValue) { - var counter, - accumulatedValue; + req.user.progressTimestamps = req.user.progressTimestamps.sort(function(a, b) { + return a - b; + }); - // If the array is empty, do nothing - if (this.length === 0) { - return this; - } else { - // If the user didn't pass an initial value, use the first item. - if (arguments.length === 1) { - counter = 1; - accumulatedValue = this[0]; - } - else if (arguments.length >= 2) { - counter = 0; - accumulatedValue = initialValue; - } - else { - throw "Invalid arguments."; - } + var timeObject = Object.create(null); + R.forEach(function(time) { + timeObject[moment(time).format('YYYY-MM-DD')] = time; + }, req.user.progressTimestamps); - // Loop through the array, feeding the current value and the result of - // the previous computation back into the combiner function until - // we've exhausted the entire array and are left with only one function. - while (counter < this.length) { - accumulatedValue = combiner(accumulatedValue, this[counter]); - counter++; + var tmpLongest = 1; + var timeKeys = R.keys(timeObject); + for (var i = 1; i <= timeKeys.length; i++) { + if (moment(timeKeys[i - 1]).add(1, 'd').toString() + === moment(timeKeys[i]).toString()) { + tmpLongest++; + if (tmpLongest > req.user.currentStreak) { + req.user.currentStreak = tmpLongest; + } + if ( req.user.currentStreak > req.user.longestStreak) { + req.user.longestStreak = req.user.currentStreak; } - - return [accumulatedValue]; } + } + + req.user.save(function(err) { + if (err) { + return next(err); + } + }); +s + var payload = { + longest: req.user.longestStreak, + timeObject: timeObject }; - var timeObject = req.user.progressTimestamps.timeReduce(function(accumulatedTime, timeStamp) { - - var copyOfAccumulatedTime = Object.create(accumulatedTime); - - copyOfAccumulatedTime[moment(timeStamp) - .format('MMMM Do YYYY')] = timeStamp; - - return copyOfAccumulatedTime; - }, - {}); - - debug('TimeObject is', timeObject); - return res.send(timeObject); + return res.send(payload); }; /** @@ -326,7 +317,7 @@ exports.returnUser = function(req, res, next) { var data = {}; var progressTimestamps = user.progressTimestamps; for (var i = 0; i < progressTimestamps.length; i++) { - data[progressTimestamps[i].toString()] = 1; + data[(progressTimestamps[i] / 1000).toString()] = 1; } res.render('account/show', { diff --git a/models/User.js b/models/User.js index 6019e7d83b..1e1d828853 100644 --- a/models/User.js +++ b/models/User.js @@ -1,7 +1,9 @@ var bcrypt = require('bcrypt-nodejs'); var crypto = require('crypto'); var mongoose = require('mongoose'); +require('mongoose-long')(mongoose); +var Long = mongoose.Types.Long; var userSchema = new mongoose.Schema({ email: { type: String, @@ -21,7 +23,7 @@ var userSchema = new mongoose.Schema({ type: Number, default: 0 }, - progressTimestamps: { type: Array, default: [Date] }, + progressTimestamps: [], challengesCompleted: { type: Array, default: [] }, pointsNeedMigration: { type: Boolean, default: true }, challengesHash: { @@ -332,9 +334,30 @@ var userSchema = new mongoose.Schema({ resetPasswordToken: String, resetPasswordExpires: Date, uncompletedBonfires: Array, - completedBonfires: Array, + completedBonfires: [ + { + _id: String, + completedWith: String, + completedDate: Long, + solution: String + } + ], uncompletedCoursewares: Array, - completedCoursewares: Array + completedCoursewares: [ + { + completedDate: Long, + _id: String, + name: String + } + ], + currentStreak: { + type: Number, + default: 0 + }, + longestStreak: { + type: Number, + default: 0 + } }); /** From 9e1a4a134836ed9c16cfbb4e60d92a2d85201f89 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 28 Mar 2015 23:42:07 +0900 Subject: [PATCH 41/57] Streak display in account/show.jade, refactor courseware.json for error handling and linting --- app.js | 6 +- controllers/bonfire.js | 483 +++++++++++++++++++------------------- controllers/courseware.js | 84 ++++--- controllers/user.js | 78 +++--- package.json | 1 + views/account/show.jade | 23 +- 6 files changed, 337 insertions(+), 338 deletions(-) diff --git a/app.js b/app.js index 07314c65dc..165f737d94 100644 --- a/app.js +++ b/app.js @@ -39,8 +39,6 @@ var express = require('express'), resourcesController = require('./controllers/resources'), userController = require('./controllers/user'), contactController = require('./controllers/contact'), - ziplineController = require('./controllers/ziplines'), - basejumpController = require('./controllers/basejumps'), nonprofitController = require('./controllers/nonprofits'), bonfireController = require('./controllers/bonfire'), coursewareController = require('./controllers/courseware'), @@ -48,7 +46,7 @@ var express = require('express'), /** * Stories */ - storyController = require('./controllers/story'); + storyController = require('./controllers/story'), /** * API keys and Passport configuration. @@ -398,8 +396,6 @@ app.post( app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); -app.get('/user/streak', userController.getStreak); - /** * API routes */ diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 3fecaa89f7..2c7adaee8b 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,142 +1,145 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:bonfires'), - Bonfire = require('./../models/Bonfire'), - User = require('./../models/User'), - resources = require('./resources'), - R = require('ramda'); + debug = require('debug')('freecc:cntr:bonfires'), + Bonfire = require('./../models/Bonfire'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'); /** * Bonfire controller */ exports.showAllBonfires = function(req, res) { - var completedBonfires = req.user.completedBonfires.map(function(elem) { - return elem._id; - }); + var completedBonfires = req.user.completedBonfires.map(function(elem) { + return elem._id; + }); - var noDuplicateBonfires = R.uniq(completedBonfires); - var data = {}; - data.bonfireList = resources.allBonfireNames(); - data.completedList = noDuplicateBonfires; - res.send(data); + var noDuplicateBonfires = R.uniq(completedBonfires); + var data = {}; + data.bonfireList = resources.allBonfireNames(); + data.completedList = noDuplicateBonfires; + res.send(data); }; exports.index = function(req, res) { - res.render('bonfire/show.jade', { - completedWith: null, - title: 'Bonfire Playground', - name: 'Bonfire Playground', - difficulty: 0, - brief: 'Feel free to play around!', - details: '', - tests: [], - challengeSeed: '', - cc: req.user ? req.user.bonfiresHash : undefined, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliments: resources.randomCompliment(), - bonfires: [], - bonfireHash: 'test' + res.render('bonfire/show.jade', { + completedWith: null, + title: 'Bonfire Playground', + name: 'Bonfire Playground', + difficulty: 0, + brief: 'Feel free to play around!', + details: '', + tests: [], + challengeSeed: '', + cc: req.user ? req.user.bonfiresHash : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliments: resources.randomCompliment(), + bonfires: [], + bonfireHash: 'test' - }); + }); }; -exports.returnNextBonfire = function(req, res) { - if (!req.user) { - return res.redirect('../bonfires/meet-bonfire'); +exports.returnNextBonfire = function(req, res, next) { + if (!req.user) { + return res.redirect('../bonfires/meet-bonfire'); + } + var completed = req.user.completedBonfires.map(function (elem) { + return elem._id; + }); + + req.user.uncompletedBonfires = resources.allBonfireIds().filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; } - var completed = req.user.completedBonfires.map(function (elem) { - return elem._id; - }); + }); + req.user.save(); - req.user.uncompletedBonfires = resources.allBonfireIds().filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - req.user.save(); + var uncompletedBonfires = req.user.uncompletedBonfires; - var uncompletedBonfires = req.user.uncompletedBonfires; - - var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); - displayedBonfires.exec(function(err, bonfire) { - if (err) { - next(err); - } - bonfire = bonfire.pop(); - if (bonfire === undefined) { - req.flash('errors', { - msg: "It looks like you've completed all the bonfires we have available. Good job!" - }); - return res.redirect('../bonfires/meet-bonfire'); - } - nameString = bonfire.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../bonfires/' + nameString); - }); + var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); + displayedBonfires.exec(function(err, bonfire) { + if (err) { + return next(err); + } + bonfire = bonfire.pop(); + if (bonfire === undefined) { + req.flash('errors', { + msg: "It looks like you've completed all the bonfires we have available. Good job!" + }); + return res.redirect('../bonfires/meet-bonfire'); + } + var nameString = bonfire.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../bonfires/' + nameString); + }); }; exports.returnIndividualBonfire = function(req, res, next) { - var dashedName = req.params.bonfireName; + var dashedName = req.params.bonfireName; - bonfireName = dashedName.replace(/\-/g, ' '); + var bonfireName = dashedName.replace(/\-/g, ' '); - Bonfire.find({"name" : new RegExp(bonfireName, 'i')}, function(err, bonfire) { - if (err) { - next(err); - } + Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfire) { + if (err) { + next(err); + } - if (bonfire.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a bonfire with that name. Please double check the name." - }); + if (bonfire.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a bonfire with that name. Please double check the name." + }); - return res.redirect('/bonfires'); - } + return res.redirect('/bonfires'); + } - bonfire = bonfire.pop(); - var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../bonfires/' + dashedNameFull); - } + bonfire = bonfire.pop(); + var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../bonfires/' + dashedNameFull); + } - res.render('bonfire/show', { - completedWith: null, - title: bonfire.name, - dashedName: dashedName, - name: bonfire.name, - difficulty: Math.floor(+bonfire.difficulty), - brief: bonfire.description[0], - details: bonfire.description.slice(1), - tests: bonfire.tests, - challengeSeed: bonfire.challengeSeed, - cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - bonfires: bonfire, - bonfireHash: bonfire._id - - }); + res.render('bonfire/show', { + completedWith: null, + title: bonfire.name, + dashedName: dashedName, + name: bonfire.name, + difficulty: Math.floor(+bonfire.difficulty), + brief: bonfire.description[0], + details: bonfire.description.slice(1), + tests: bonfire.tests, + challengeSeed: bonfire.challengeSeed, + cc: !!req.user, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + bonfires: bonfire, + bonfireHash: bonfire._id }); + }); }; /** - * Bonfire generator + * Bonfire Generator + * @param req Request Object + * @param res Response Object + * @returns void */ + exports.returnGenerator = function(req, res) { - res.render('bonfire/generator', { - title: null, - name: null, - difficulty: null, - brief: null, - details: null, - tests: null, - challengeSeed: null, - bonfireHash: randomString() - }); + res.render('bonfire/generator', { + title: null, + name: null, + difficulty: null, + brief: null, + details: null, + tests: null, + challengeSeed: null, + bonfireHash: randomString() + }); }; /** @@ -144,162 +147,164 @@ exports.returnGenerator = function(req, res) { */ function randomString() { - var chars = "0123456789abcdef"; - var string_length = 23; - var randomstring = 'a'; - for (var i=0; i 0) { - return elem; - } -}; + if (elem.length > 0) { + return elem; + } +} exports.publicGenerator = function(req, res) { - res.render('bonfire/public-generator'); + res.render('bonfire/public-generator'); }; exports.generateChallenge = function(req, res) { - var bonfireName = req.body.name, - bonfireTests = req.body.tests, - bonfireDifficulty = req.body.difficulty, - bonfireDescription = req.body.description, - bonfireChallengeSeed = req.body.challengeSeed; - bonfireTests = bonfireTests.split('\r\n'); - bonfireDescription = bonfireDescription.split('\r\n'); - bonfireTests.filter(getRidOfEmpties); - bonfireDescription.filter(getRidOfEmpties); - bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); + var bonfireName = req.body.name, + bonfireTests = req.body.tests, + bonfireDifficulty = req.body.difficulty, + bonfireDescription = req.body.description, + bonfireChallengeSeed = req.body.challengeSeed; + bonfireTests = bonfireTests.split('\r\n'); + bonfireDescription = bonfireDescription.split('\r\n'); + bonfireTests.filter(getRidOfEmpties); + bonfireDescription.filter(getRidOfEmpties); + bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); - var response = { - _id: randomString(), - name: bonfireName, - difficulty: bonfireDifficulty, - description: bonfireDescription, - challengeSeed: bonfireChallengeSeed, - tests: bonfireTests - }; - res.send(response); + var response = { + _id: randomString(), + name: bonfireName, + difficulty: bonfireDifficulty, + description: bonfireDescription, + challengeSeed: bonfireChallengeSeed, + tests: bonfireTests + }; + res.send(response); }; -exports.completedBonfire = function (req, res) { - var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; - var isCompletedDate = Math.round(+new Date()); - var bonfireHash = req.body.bonfireInfo.bonfireHash; - var isSolution = req.body.bonfireInfo.solution; - - if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedbWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { - if (err) { - return err; - } else { - var index = req.user.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) - } - pairedWith = pairedWith.pop(); - - index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedBonfires.splice(index, 1); - - } - - pairedWith.completedBonfires.push({ - _id: bonfireHash, - completedWith: req.user._id, - completedDate: isCompletedDate, - solution: isSolution - }); - - req.user.completedBonfires.push({ - _id: bonfireHash, - completedWith: pairedWith._id, - completedDate: isCompletedDate, - solution: isSolution - }); - - req.user.save(function (err, user) { - pairedWith.save(function (err, paired) { - if (err) { - throw err; - } - if (user && paired) { - res.send(true); - } - }) - }); - } - }) - } else { - - req.user.completedBonfires.push({ - _id: bonfireHash, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution - }); +exports.completedBonfire = function (req, res, next) { + var isCompletedWith = req.body.bonfireInfo.completedWith || ''; + var isCompletedDate = Math.round(+new Date()); + var bonfireHash = req.body.bonfireInfo.bonfireHash; + var isSolution = req.body.bonfireInfo.solution; + if (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith + .toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return next(err); + } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1); + } + pairedWith = pairedWith.pop(); + + index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedBonfires.splice(index, 1); - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) } - req.user.save(function (err, user) { - if (err) { - throw err; - } - if (user) { - debug('Saving user'); - res.send(true) - } + pairedWith.completedBonfires.push({ + _id: bonfireHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: isSolution }); + + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: isSolution + }); + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + res.send(true); + } + }); + }); + } + }); + } else { + req.user.completedBonfires.push({ + _id: bonfireHash, + completedWith: null, + completedDate: isCompletedDate, + solution: isSolution + }); + + var index = req.user.uncompletedBonfires.indexOf(bonfireHash); + if (index > -1) { + + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedBonfires.splice(index, 1); } -}; \ No newline at end of file + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + debug('Saving user'); + res.send(true); + } + }); + } +}; diff --git a/controllers/courseware.js b/controllers/courseware.js index 3b932894f8..74355c34d7 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -1,10 +1,10 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:courseware'), - Courseware = require('./../models/Courseware'), - User = require('./../models/User'), - resources = require('./resources'), - R = require('ramda'), - moment = require('moment'); + debug = require('debug')('freecc:cntr:courseware'), + Courseware = require('./../models/Courseware'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'), + moment = require('moment'); /** * Courseware controller @@ -22,7 +22,7 @@ exports.showAllCoursewares = function(req, res) { res.send(data); }; -exports.returnNextCourseware = function(req, res) { +exports.returnNextCourseware = function(req, res, next) { if (!req.user) { return res.redirect('../challenges/learn-how-free-code-camp-works'); } @@ -30,7 +30,8 @@ exports.returnNextCourseware = function(req, res) { return elem._id; }); - req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) { + req.user.uncompletedCoursewares = resources.allCoursewareIds() + .filter(function (elem) { if (completed.indexOf(elem) === -1) { return elem; } @@ -40,16 +41,17 @@ exports.returnNextCourseware = function(req, res) { var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); - var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); + var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); displayedCoursewares.exec(function(err, courseware) { if (err) { - next(err); + return next(err); } courseware = courseware.pop(); if (courseware === undefined) { req.flash('errors', { - msg: "It looks like you've completed all the courses we have available. Good job!" + 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'); } @@ -61,16 +63,18 @@ exports.returnNextCourseware = function(req, res) { exports.returnIndividualCourseware = function(req, res, next) { var dashedName = req.params.coursewareName; - coursewareName = dashedName.replace(/\-/g, ' '); + var coursewareName = dashedName.replace(/\-/g, ' '); - Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { + Courseware.find({'name': new RegExp(coursewareName, 'i')}, + function(err, courseware) { if (err) { next(err); } // Handle not found if (courseware.length < 1) { req.flash('errors', { - msg: "404: We couldn't find a challenge with that name. Please double check the name." + msg: "404: We couldn't find a challenge with that name. " + + "Please double check the name." }); return res.redirect('/challenges'); } @@ -83,7 +87,7 @@ exports.returnIndividualCourseware = function(req, res, next) { } var challengeType = { - 0 : function() { + 0: function() { res.render('coursewares/showHTML', { title: courseware.name, dashedName: dashedName, @@ -100,7 +104,7 @@ exports.returnIndividualCourseware = function(req, res, next) { }); }, - 1 : function() { + 1: function() { res.render('coursewares/showJS', { title: courseware.name, dashedName: dashedName, @@ -134,7 +138,7 @@ exports.returnIndividualCourseware = function(req, res, next) { }, 3: function() { - res.render('coursewares/showVideo', { + res.render('coursewares/showZipline', { title: courseware.name, dashedName: dashedName, name: courseware.name, @@ -150,7 +154,7 @@ exports.returnIndividualCourseware = function(req, res, next) { }, 4: function() { - res.render('coursewares/showVideo', { + res.render('coursewares/showBasejump', { title: courseware.name, dashedName: dashedName, name: courseware.name, @@ -173,11 +177,11 @@ exports.returnIndividualCourseware = function(req, res, next) { exports.testCourseware = function(req, res) { var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; coursewareTests = coursewareTests.split('\r\n'); coursewareDescription = coursewareDescription.split('\r\n'); coursewareTests.filter(getRidOfEmpties); @@ -190,8 +194,8 @@ exports.testCourseware = function(req, res) { difficulty: +coursewareDifficulty, brief: coursewareDescription[0], details: coursewareDescription.slice(1), - tests: coursewareTests, - challengeSeed: coursewareChallengeSeed, + tests: coursewareTests, + challengeSeed: coursewareChallengeSeed, challengeEntryPoint: coursewareEntryPoint, cc: req.user ? req.user.coursewaresHash : undefined, progressTimestamps: req.user ? req.user.progressTimestamps : undefined, @@ -199,7 +203,7 @@ exports.testCourseware = function(req, res) { phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewares: [], - coursewareHash: "test" + coursewareHash: 'test' }); }; @@ -207,7 +211,7 @@ function getRidOfEmpties(elem) { if (elem.length > 0) { return elem; } -}; +} exports.publicGenerator = function(req, res) { res.render('courseware/public-generator'); @@ -215,11 +219,11 @@ exports.publicGenerator = function(req, res) { exports.generateChallenge = function(req, res) { var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; coursewareTests = coursewareTests.split('\r\n'); coursewareDescription = coursewareDescription.split('\r\n'); coursewareTests.filter(getRidOfEmpties); @@ -243,34 +247,26 @@ exports.completedCourseware = function (req, res, next) { var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; - debug('this is the coursewarehash we got', coursewareHash); - req.user.completedCoursewares.push({ _id: coursewareHash, completedDate: isCompletedDate, name: req.body.coursewareInfo.coursewareName }); - var index = req.user.completedCoursewares.indexOf(coursewareHash); if (index === -1) { - req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); } -exports.completedBasejump = function (req, res, next) { - var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; - var isCompletedDate = Math.round(+new Date()); - var coursewareHash = req.body.coursewareInfo.coursewareHash; - var solutionLink = req.body.coursewareInfo.solutionLink; - if(!solutionLink) { - // flash error and redirect + req.user.save(function (err, user) { + if (err) { + return next(err); } if (user) { res.send(true); } - }; + }); }; exports.completedZiplineOrBasejump = function (req, res, next) { diff --git a/controllers/user.js b/controllers/user.js index 28d8d38a11..984ee2385c 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -196,50 +196,6 @@ exports.postEmailSignup = function(req, res, next) { }); }; -/** - * For Calendar display - */ - -exports.getStreak = function(req, res, next) { - - req.user.progressTimestamps = req.user.progressTimestamps.sort(function(a, b) { - return a - b; - }); - - var timeObject = Object.create(null); - R.forEach(function(time) { - timeObject[moment(time).format('YYYY-MM-DD')] = time; - }, req.user.progressTimestamps); - - var tmpLongest = 1; - var timeKeys = R.keys(timeObject); - for (var i = 1; i <= timeKeys.length; i++) { - if (moment(timeKeys[i - 1]).add(1, 'd').toString() - === moment(timeKeys[i]).toString()) { - tmpLongest++; - if (tmpLongest > req.user.currentStreak) { - req.user.currentStreak = tmpLongest; - } - if ( req.user.currentStreak > req.user.longestStreak) { - req.user.longestStreak = req.user.currentStreak; - } - } - } - - req.user.save(function(err) { - if (err) { - return next(err); - } - }); -s - var payload = { - longest: req.user.longestStreak, - timeObject: timeObject - }; - - return res.send(payload); -}; - /** * GET /account * Profile page. @@ -314,6 +270,36 @@ exports.returnUser = function(req, res, next) { if (user[0]) { var user = user[0]; + user.progressTimestamps = user.progressTimestamps.sort(function(a, b) { + return a - b; + }); + + var timeObject = Object.create(null); + R.forEach(function(time) { + timeObject[moment(time).format('YYYY-MM-DD')] = time; + }, user.progressTimestamps); + + var tmpLongest = 1; + var timeKeys = R.keys(timeObject); + for (var i = 1; i <= timeKeys.length; i++) { + if (moment(timeKeys[i - 1]).add(1, 'd').toString() + === moment(timeKeys[i]).toString()) { + tmpLongest++; + if (tmpLongest > user.currentStreak) { + user.currentStreak = tmpLongest; + } + if ( user.currentStreak > user.longestStreak) { + user.longestStreak = user.currentStreak; + } + } + } + + user.save(function(err) { + if (err) { + return next(err); + } + }); + var data = {}; var progressTimestamps = user.progressTimestamps; for (var i = 0; i < progressTimestamps.length; i++) { @@ -344,7 +330,9 @@ exports.returnUser = function(req, res, next) { website3Image: user.portfolio.website3Image, ch: user.challengesHash, calender: data, - moment: moment + moment: moment, + longestStreak: user.longestStreak, + currentStreak: user.currentStreak }); } else { diff --git a/package.json b/package.json index 634fada27b..b535c5739b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "moment": "^2.8.4", "mongodb": "^1.4.33", "mongoose": "^4.0.0", + "mongoose-long": "0.0.2", "morgan": "^1.5.0", "newrelic": "^1.13.3", "nodemailer": "^1.3.0", diff --git a/views/account/show.jade b/views/account/show.jade index c11fe9e4c8..6dee1e6e41 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -21,13 +21,13 @@ block content img.img-center.img-responsive.public-profile-img(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') h1.text-center.negative-5 - if (twitterHandle) - a.ion-social-twitter.text-primary(title="@#{username}'s Twitter Profile", href="http://twitter.com/#{twitterHandle}", target='_blank') + a.ion-social-twitter.text-primary(title="@#{username}'s Twitter Profile", href="http://twitter.com/#{twitterHandle}", target='_blank') - if (githubProfile) - a.ion-social-github.text-primary(title="@#{username}'s GitHub Profile", href=githubProfile, target='_blank') + a.ion-social-github.text-primary(title="@#{username}'s GitHub Profile", href=githubProfile, target='_blank') - if (linkedinProfile) - a.ion-social-linkedin.text-primary(title="@#{username}'s LinkedIn Profile", href=linkedinProfile, target='_blank') + a.ion-social-linkedin.text-primary(title="@#{username}'s LinkedIn Profile", href=linkedinProfile, target='_blank') - if (codepenProfile) - a.ion-social-codepen.text-primary(title="@#{username}'s CodePen Profile", href=codepenProfile, target='_blank') + a.ion-social-codepen.text-primary(title="@#{username}'s CodePen Profile", href=codepenProfile, target='_blank') .visible-md.visible-lg .col-xs-12.col-sm-12.col-md-4.text-justify h1.flat-top.wrappable= name @@ -118,4 +118,17 @@ block content start: new Date().setDate(new Date().getDate() - 90), legendColors: ["#cccccc", "#215f1e"], legend: [1, 2, 3] - }); \ No newline at end of file + }); + .row + .hidden-xs.col-sm-12.text-center + .row + h3.col-sm-6.text-center + .center-block. + #{longestStreak} + .center-block. + Longest Streak + h3.col-sm-6.text-center + .center-block. + #{currentStreak} + .center-block. + Current Streak \ No newline at end of file From 672df775a2031a67105d9773fcbb3e2214d74d52 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 28 Mar 2015 07:47:47 -0700 Subject: [PATCH 42/57] add a zipline and a base jump to the seed file --- controllers/user.js | 2 +- package.json | 1 + seed_data/coursewares.json | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/controllers/user.js b/controllers/user.js index 28d8d38a11..73545faa99 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -231,7 +231,7 @@ exports.getStreak = function(req, res, next) { return next(err); } }); -s + var payload = { longest: req.user.longestStreak, timeObject: timeObject diff --git a/package.json b/package.json index 634fada27b..97f0cdf46e 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "moment": "^2.8.4", "mongodb": "^1.4.33", "mongoose": "^4.0.0", + "mongoose-long": "^0.0.2", "morgan": "^1.5.0", "newrelic": "^1.13.3", "nodemailer": "^1.3.0", diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 3b1810bec0..ef0881283d 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -760,6 +760,40 @@ "challengeType": 2, "tests": [] }, + { + "_id" : "bd7158d8c442eddfaeb5bdef", + "name": "Zipline Trello API Integration", + "difficulty": 0.55, + "challengeSeed": "123488494", + "description" : [ + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "In the JavaScript box, scroll down to the comment that reads \"//Changeable code be made here\". This CodePen is already pulling in the relevant JSON from our API. This JSON is from one of our Trello boards. You can view the actual Trello board here: https://trello.com/b/BA3xVpz9/nonprofit-projects.", + "You can view the JSON output here: http://www.freecodecamp.com/api/trello.", + "In addition to each Trello card's description,(which is currently shown), you will also want to show it's name and labels.", + "Hint: you will need to write a jQuery loop that which dynamically inserts <li> elements. You will do this using jQuery's each function, described here: https://api.jquery.com/each/.", + "Style the output to make it look visually appealing. Give it a custom font, color scheme, and a clear visual separation between data elements.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id" : "bd7158d8c443eddfaeb5bdef", + "name": "Basejump Hello World in Cloud 9", + "difficulty": 0.56, + "challengeSeed": "123488494", + "description" : [ + "Go to MEAN.js http://meanjs.org and click the 'view on GitHub' button. This will take you to MEAN.js's GitHub repository.", + "Copy the link from the SSH clone URL on the lower right.", + "Go to Cloud 9 IDE at http://c9.io and log in with GitHub.", + "Click the \"Create New Workspace\" button, then the \"Clone from URL\" option. Paste in the link you copied from GitHub'", + "Look under \"My projects\" on the right and wait until it's no longer grayed out. Click on it, then click the \"Start Editing\" button.", + "Once it has loaded, install all of MEAN.js's packages by clicking into Cloud 9's terminal at the bottom and running npm install.", + "Once that command has finished, set up MongoDB and start it by copying and pasting this code into Cloud 9's terminal: mkdir data && echo 'mongod --bind_ip=$IP --dbpath=data --nojournal --rest \"$@\"' > mongod && chmod a+x mongod && ./mongod. You don't worry if you don't understand what that means. From now on, you'll just need to run this command to start MongoDB: ./mongod." + ], + "challengeType": 4, + "tests": [] + }, { "_id" : "bd7123c8c441eddfaeb5bdef", "name": "Start our Challenges", From 6a19dc9bda72a98db58d1f8b0f66e8b5d7a1315c Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 00:09:50 +0900 Subject: [PATCH 43/57] Zipline/Basejump view with controller logic to serve. Will have to change based upon json --- controllers/courseware.js | 21 +----- views/coursewares/showZiplineOrBasejump.jade | 73 ++++++++++++++++++++ 2 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 views/coursewares/showZiplineOrBasejump.jade diff --git a/controllers/courseware.js b/controllers/courseware.js index 74355c34d7..f32e14b0c5 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -138,34 +138,17 @@ exports.returnIndividualCourseware = function(req, res, next) { }, 3: function() { - res.render('coursewares/showZipline', { + res.render('coursewares/showZiplineOrBasejump', { title: courseware.name, dashedName: dashedName, name: courseware.name, details: courseware.description, - tests: courseware.tests, video: courseware.challengeSeed[0], verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - challengeType: 'zipline' - }); - }, - - 4: function() { - res.render('coursewares/showBasejump', { - title: courseware.name, - dashedName: dashedName, - name: courseware.name, - details: courseware.description, - tests: courseware.tests, - video: courseware.challengeSeed[0], - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewareHash: courseware._id, - challengeType: 'basejump' + challengeType: courseware.challengeType }); } }; diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade new file mode 100644 index 0000000000..c5df3c03ac --- /dev/null +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -0,0 +1,73 @@ +extends ../layout-wide +block content + .row + .col-xs-12.col-sm-12.col-md-4.bonfire-top + h1.text-center= name + .well + h4 + ol + for step in details + li!= step + .col-xs-12.col-sm-12.col-md-8 + .embed-responsive.embed-responsive-16by9 + iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') + br + - if (user) + a.btn.btn-primary.btn-lg.btn-block#completed-courseware I've completed this challenge (ctrl + enter) + script. + var userLoggedIn = true; + - else + a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + script. + var userLoggedIn = false; + br + script(type="text/javascript"). + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; + var started = Math.floor(Date.now()); + #complete-zipline-or-basejump-dialog.modal(tabindex='-1') + .modal-dialog.animated.zoomIn.fast-animation + .modal-content + .modal-header.challenge-list-header= compliment + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body(ng-controller="pairedWithController") + .text-center + .animated.zoomInDown.delay-half + span.completion-icon.ion-checkmark-circled.text-primary + - if (user) + form.form-horizontal(novalidate='novalidate', name='completedWithForm') + .form-group.text-center + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn + + // extra field to distract password tools like lastpass from injecting css into our username field + input.form-control(ng-show="false") + if (challengeType === 'zipline') + input.form-control#public-url(name="codepenUrl", placeholder="http://codepen.io/your-pen-here", autofocus) + else + input.form-control#public-url(name="depoloymentUrl", placeholder="http://yourapp.com", autofocus) + input.form-control#github-url(name="githubUrl", placeholder="http://github.com/camper/project") + + input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser") + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-cloak, ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") + alert(type='danger') + span.ion-close-circled + | Username not found + + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next bonfire (ctrl + enter) + + + - if (user.progressTimestamps.length > 2) + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") + i.fa.fa-twitter   + = phrase + - else + a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + script. + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; + $('body').on('keypress', function(e) { + if (e.ctrlKey && e.keyCode == 13) { + $('#complete-courseware-dialog').modal('show'); + } + }); \ No newline at end of file From ef27fda537fdaa0bd331d542e78fff6b9a354f71 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 28 Mar 2015 10:14:49 -0700 Subject: [PATCH 44/57] remove references to meeting from announcements and notification modal --- controllers/resources.json | 1 - views/resources/learn-to-code.jade | 36 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/controllers/resources.json b/controllers/resources.json index 47eb9fff75..847f4538a3 100644 --- a/controllers/resources.json +++ b/controllers/resources.json @@ -1,6 +1,5 @@ { "announcements": [ - ["We'll live-stream our Camp-wide Meeting Saturday, March 27 at Noon EST. We'll show some of Free Code Camp's new features, and campers will show what they're building.", "http://twitch.tv/freecodecamp"], ["Some of Code School's courses are no longer free. We're switching to NodeSchool.io for our Node.js and Express.js challenges.", "http://freecodecamp.com/nodeschool-challenges"], ["Screen Hero is now free on Windows and Mac! Follow these special instructions to install it.", "http://freecodecamp.com/install-screenhero"] ], diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 4eb8198888..1aaea115b2 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -75,21 +75,21 @@ block content .col-xs-12.col-sm-12.col-md-6 include ../partials/faq - #announcementModal.modal(tabindex='-1') - .modal-dialog - .modal-content - .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   - a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel - | . - a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp - a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! - script. - $(document).ready(function() { - if (!localStorage || !localStorage.campWideMeeting) { - $('#announcementModal').modal('show'); - localStorage.campWideMeeting = "true"; - } - }); \ No newline at end of file + //#announcementModal.modal(tabindex='-1') + // .modal-dialog + // .modal-content + // .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST + // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + // .modal-body + // h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   + // a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel + // | . + // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp + // a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! + //script. + // $(document).ready(function() { + // if (!localStorage || !localStorage.campWideMeeting) { + // $('#announcementModal').modal('show'); + // localStorage.campWideMeeting = "true"; + // } + // }); \ No newline at end of file From 1fc3cffdbffebc11cfcc603d266a2d32e95b58d1 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 20:39:41 +0900 Subject: [PATCH 45/57] Add .editorconfig to enforce certain automatic behavior on all editors, Add logic for ziplines and basejumps completion. --- .editorconfig | 15 + app.js | 2 + controllers/bonfire.js | 91 +-- controllers/courseware.js | 66 +- models/User.js | 5 +- public/js/main.js | 663 ++++++++++--------- views/bonfire/show.jade | 10 - views/coursewares/showHTML.jade | 3 +- views/coursewares/showJS.jade | 3 +- views/coursewares/showVideo.jade | 5 +- views/coursewares/showZiplineOrBasejump.jade | 35 +- 11 files changed, 473 insertions(+), 425 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..13ef57c654 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/app.js b/app.js index 6e9e08796f..a37089dae9 100644 --- a/app.js +++ b/app.js @@ -438,6 +438,8 @@ app.get( coursewareController.returnIndividualCourseware ); app.post('/completed-courseware/', coursewareController.completedCourseware); +app.post('/completed-zipline-or-basejump', + coursewareController.completedZiplineOrBasejump); // Unique Check API route app.get('/api/checkUniqueUsername/:username', userController.checkUniqueUsername); diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 90a8bde0e9..8e7a919940 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,17 +1,9 @@ var _ = require('lodash'), -<<<<<<< HEAD debug = require('debug')('freecc:cntr:bonfires'), Bonfire = require('./../models/Bonfire'), User = require('./../models/User'), resources = require('./resources'), - R = require('ramda'); -======= - debug = require('debug')('freecc:cntr:bonfires'), - Bonfire = require('./../models/Bonfire'), - User = require('./../models/User'), - resources = require('./resources'), - MDNlinks = require('./../seed_data/bonfireMDNlinks'); ->>>>>>> upstream/master + MDNlinks = require('./../seed_data/bonfireMDNlinks'); /** * Bonfire controller @@ -103,34 +95,11 @@ exports.returnIndividualBonfire = function(req, res, next) { return res.redirect('/bonfires'); } -<<<<<<< HEAD bonfire = bonfire.pop(); var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); if (dashedNameFull != dashedName) { return res.redirect('../bonfires/' + dashedNameFull); } -======= - res.render('bonfire/show', { - completedWith: null, - title: bonfire.name, - dashedName: dashedName, - name: bonfire.name, - difficulty: Math.floor(+bonfire.difficulty), - brief: bonfire.description[0], - details: bonfire.description.slice(1), - tests: bonfire.tests, - challengeSeed: bonfire.challengeSeed, - cc: !!req.user, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - bonfires: bonfire, - bonfireHash: bonfire._id, - MDNkeys: bonfire.MDNlinks, - MDNlinks: getMDNlinks(bonfire.MDNlinks) ->>>>>>> upstream/master - res.render('bonfire/show', { completedWith: null, title: bonfire.name, @@ -142,12 +111,14 @@ exports.returnIndividualBonfire = function(req, res, next) { tests: bonfire.tests, challengeSeed: bonfire.challengeSeed, cc: !!req.user, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + points: req.user ? req.user.points : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), bonfires: bonfire, - bonfireHash: bonfire._id + bonfireHash: bonfire._id, + MDNkeys: bonfire.MDNlinks, + MDNlinks: getMDNlinks(bonfire.MDNlinks) }); }); }; @@ -189,18 +160,18 @@ function randomString() { /** * Helper function to populate the MDN links array. -*/ + */ function getMDNlinks(links) { - // takes in an array of links, which are strings - var populatedLinks = []; + // takes in an array of links, which are strings + var populatedLinks = []; - // for each key value, push the corresponding link from the MDNlinks object into a new array - links.forEach(function(value, index) { - populatedLinks.push(MDNlinks[value]); - }); + // for each key value, push the corresponding link from the MDNlinks object into a new array + links.forEach(function(value, index) { + populatedLinks.push(MDNlinks[value]); + }); - return populatedLinks; + return populatedLinks; }; @@ -210,15 +181,15 @@ function getMDNlinks(links) { exports.testBonfire = function(req, res) { var bonfireName = req.body.name, - bonfireTests = req.body.tests, - bonfireDifficulty = req.body.difficulty, - bonfireDescription = req.body.description, - bonfireChallengeSeed = req.body.challengeSeed; - bonfireTests = bonfireTests.split('\r\n'); - bonfireDescription = bonfireDescription.split('\r\n'); - bonfireTests.filter(getRidOfEmpties); - bonfireDescription.filter(getRidOfEmpties); - bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); + bonfireTests = req.body.tests, + bonfireDifficulty = req.body.difficulty, + bonfireDescription = req.body.description, + bonfireChallengeSeed = req.body.challengeSeed; + bonfireTests = bonfireTests.split('\r\n'); + bonfireDescription = bonfireDescription.split('\r\n'); + bonfireTests.filter(getRidOfEmpties); + bonfireDescription.filter(getRidOfEmpties); + bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); res.render('bonfire/show', { completedWith: null, @@ -251,15 +222,15 @@ exports.publicGenerator = function(req, res) { exports.generateChallenge = function(req, res) { var bonfireName = req.body.name, - bonfireTests = req.body.tests, - bonfireDifficulty = req.body.difficulty, - bonfireDescription = req.body.description, - bonfireChallengeSeed = req.body.challengeSeed; - bonfireTests = bonfireTests.split('\r\n'); - bonfireDescription = bonfireDescription.split('\r\n'); - bonfireTests.filter(getRidOfEmpties); - bonfireDescription.filter(getRidOfEmpties); - bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); + bonfireTests = req.body.tests, + bonfireDifficulty = req.body.difficulty, + bonfireDescription = req.body.description, + bonfireChallengeSeed = req.body.challengeSeed; + bonfireTests = bonfireTests.split('\r\n'); + bonfireDescription = bonfireDescription.split('\r\n'); + bonfireTests.filter(getRidOfEmpties); + bonfireDescription.filter(getRidOfEmpties); + bonfireChallengeSeed = bonfireChallengeSeed.replace('\r', ''); var response = { diff --git a/controllers/courseware.js b/controllers/courseware.js index f32e14b0c5..541586899c 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -100,7 +100,8 @@ exports.returnIndividualCourseware = function(req, res, next) { phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - environment: resources.whichEnvironment() + environment: resources.whichEnvironment(), + challengeType: courseware.challengeType }); }, @@ -117,7 +118,7 @@ exports.returnIndividualCourseware = function(req, res, next) { phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - + challengeType: courseware.challengeType }); }, @@ -133,7 +134,7 @@ exports.returnIndividualCourseware = function(req, res, next) { phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), coursewareHash: courseware._id, - challengeType: 'video' + challengeType: courseware.challengeType }); }, @@ -160,16 +161,16 @@ exports.returnIndividualCourseware = function(req, res, next) { exports.testCourseware = function(req, res) { var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); res.render('courseware/show', { completedWith: null, title: coursewareName, @@ -233,7 +234,9 @@ exports.completedCourseware = function (req, res, next) { req.user.completedCoursewares.push({ _id: coursewareHash, completedDate: isCompletedDate, - name: req.body.coursewareInfo.coursewareName + name: req.body.coursewareInfo.coursewareName, + solution: null, + githubLink: null }); var index = req.user.completedCoursewares.indexOf(coursewareHash); @@ -247,19 +250,26 @@ exports.completedCourseware = function (req, res, next) { return next(err); } if (user) { - res.send(true); + res.sendStatus(200); } }); }; exports.completedZiplineOrBasejump = function (req, res, next) { - var isCompletedWith = req.body.bonfireInfo.completedWith || false; + debug('Inside controller for completed zipline or basejump with data %s', + req.body.coursewareInfo); + var isCompletedWith = req.body.coursewareInfo.completedWith || false; var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; - var solutionLink = req.body.coursewareInfo.solutionLink; - if (!solutionLink) { - // flash error and redirect - return next(new Error('No solution provided')); + var solutionLink = req.body.coursewareInfo.publicURL; + var githubLink = req.body.coursewareInfo.challengeType === 4 + ? req.body.coursewareInfo.githubURL : true; + if (!solutionLink || !githubLink) { + req.flash('errors', { + msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + + 'your work.' + }); + return res.sendStatus(403); } if (isCompletedWith) { @@ -286,14 +296,16 @@ exports.completedZiplineOrBasejump = function (req, res, next) { _id: coursewareHash, completedWith: req.user._id, completedDate: isCompletedDate, - solution: solutionLink + solution: solutionLink, + githubLink: githubLink }); req.user.completedCoursewares.push({ _id: coursewareHash, completedWith: pairedWith._id, completedDate: isCompletedDate, - solution: solutionLink + solution: solutionLink, + githubLink: githubLink }); req.user.save(function (err, user) { @@ -305,7 +317,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { return next(err); } if (user && paired) { - return res.send(true); + return res.sendStatus(200); } }); }); @@ -317,10 +329,11 @@ exports.completedZiplineOrBasejump = function (req, res, next) { _id: coursewareHash, completedWith: null, completedDate: isCompletedDate, - solution: solutionLink + solution: solutionLink, + githubLink: githubLink }); - var index = req.user.uncompletedCourse.indexOf(coursewareHash); + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); if (index > -1) { req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); @@ -331,8 +344,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { return next(err); } if (user) { - debug('Saving user'); - return res.send(true); + return res.sendStatus(200); } }); } diff --git a/models/User.js b/models/User.js index 1e1d828853..a05e6640ec 100644 --- a/models/User.js +++ b/models/User.js @@ -347,7 +347,10 @@ var userSchema = new mongoose.Schema({ { completedDate: Long, _id: String, - name: String + name: String, + completedWith: String, + solution: String, + githubLink: String } ], currentStreak: { diff --git a/public/js/main.js b/public/js/main.js index 9a903a1d08..c422d2b732 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,377 +1,432 @@ $(document).ready(function() { - var challengeName = typeof challengeName !== undefined ? challengeName : 'Untitled'; - if (challengeName) { - ga('send', 'event', 'Challenge', 'load', challengeName); - } + var challengeName = typeof challengeName !== undefined ? challengeName : 'Untitled'; + if (challengeName) { + ga('send', 'event', 'Challenge', 'load', challengeName); + } - // When introducing a new announcement, change the localStorage attribute - // and the HTML located in the footer - if (!localStorage || !localStorage.nodeSchoolAnnouncement) { - $('#announcementModal').modal('show'); - localStorage.fccShowAnnouncement = "true"; - } + // When introducing a new announcement, change the localStorage attribute + // and the HTML located in the footer + if (!localStorage || !localStorage.nodeSchoolAnnouncement) { + $('#announcementModal').modal('show'); + localStorage.fccShowAnnouncement = "true"; + } - var CSRF_HEADER = 'X-CSRF-Token'; + var CSRF_HEADER = 'X-CSRF-Token'; - var setCSRFToken = function(securityToken) { - jQuery.ajaxPrefilter(function(options, _, xhr) { - if (!xhr.crossDomain) { - xhr.setRequestHeader(CSRF_HEADER, securityToken); - } - }); - }; - - setCSRFToken($('meta[name="csrf-token"]').attr('content')); - - $('.start-challenge').on('click', function() { - $(this).parent().remove(); - $('.challenge-content') - .removeClass('hidden-element') - .addClass('animated fadeInDown'); - }); - - //$('.completed-challenge').on('click', function() { - // $('#complete-challenge-dialog').modal('show'); - // // Only post to server if there is an authenticated user - // if ($('.signup-btn-nav').length < 1) { - // l = location.pathname.split('/'); - // cn = l[l.length - 1]; - // $.ajax({ - // type: 'POST', - // data: {challengeNumber: cn}, - // url: '/completed-challenge/' - // }); - // } - //}); - - - function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash) { - $('#complete-bonfire-dialog').modal('show'); - // Only post to server if there is an authenticated user - if ($('.signup-btn-nav').length < 1) { - - $.post( - '/completed-bonfire', - { - bonfireInfo: { - completedWith : didCompleteWith, - solution: bonfireSolution, - bonfireHash: thisBonfireHash - } - }, - function(res) { - if (res) { - window.location.href = '/bonfires' - } - }); - } - } - - $('.next-bonfire-button').on('click', function() { - var bonfireSolution = myCodeMirror.getValue(); - var thisBonfireHash = passedBonfireHash || null; - var didCompleteWith = $('#completed-with').val() || null; - completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); - - }); - - $('#completed-courseware').on('click', function() { - $('#complete-courseware-dialog').modal('show'); - }); - - $('#complete-courseware-dialog').on('keypress', function(e) { - if (e.ctrlKey && e.keyCode == 13) { - $('#next-courseware-button').click(); + var setCSRFToken = function(securityToken) { + jQuery.ajaxPrefilter(function(options, _, xhr) { + if (!xhr.crossDomain) { + xhr.setRequestHeader(CSRF_HEADER, securityToken); } }); + }; - $('#complete-bonfire-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); + setCSRFToken($('meta[name="csrf-token"]').attr('content')); - $('#all-bonfires-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); + $('.start-challenge').on('click', function() { + $(this).parent().remove(); + $('.challenge-content') + .removeClass('hidden-element') + .addClass('animated fadeInDown'); + }); - $('#showAllCoursewares').on('click', function() { - $('#all-coursewares-dialog').modal('show'); - }); - - $('#all-coursewares-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); + //$('.completed-challenge').on('click', function() { + // $('#complete-challenge-dialog').modal('show'); + // // Only post to server if there is an authenticated user + // if ($('.signup-btn-nav').length < 1) { + // l = location.pathname.split('/'); + // cn = l[l.length - 1]; + // $.ajax({ + // type: 'POST', + // data: {challengeNumber: cn}, + // url: '/completed-challenge/' + // }); + // } + //}); - $('#complete-courseware-dialog').on('hidden.bs.modal', function() { - editor.focus(); - }); - $('#next-courseware-button').on('click', function() { - console.log(passedCoursewareHash); - if ($('.signup-btn-nav').length < 1) { - $.post( - '/completed-courseware/', - { - coursewareInfo: { - coursewareHash: passedCoursewareHash, - coursewareName: passedCoursewareName - } - }).success( - function(res) { - if (res) { - window.location.href = '/challenges'; - } - } - ); + function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash) { + $('#complete-bonfire-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { - } - }); + $.post( + '/completed-bonfire', + { + bonfireInfo: { + completedWith : didCompleteWith, + solution: bonfireSolution, + bonfireHash: thisBonfireHash + } + }, + function(res) { + if (res) { + window.location.href = '/bonfires' + } + }); + } + } + + $('.next-bonfire-button').on('click', function() { + var bonfireSolution = myCodeMirror.getValue(); + var thisBonfireHash = passedBonfireHash || null; + var didCompleteWith = $('#completed-with').val() || null; + completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); + + }); + + $('#completed-courseware').on('click', function() { + $('#complete-courseware-dialog').modal('show'); + }); + + $('#completed-zipline-or-basejump').on('click', function() { + $('#complete-zipline-or-basejump-dialog').modal('show'); + }); - $('.all-challenges').on('click', function() { - $('#all-challenges-dialog').modal('show'); - }); + $('#complete-courseware-dialog').on('keypress', function(e) { + if (e.ctrlKey && e.keyCode === 13) { + $('#next-courseware-button').click(); + } + }); - $('#showAllButton').on('click', function() { - $('#all-bonfires-dialog').modal('show'); - }); + $('#complete-bonfire-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); - $('.next-challenge-button').on('click', function() { - l = location.pathname.split('/'); - window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); - }); + $('#all-bonfires-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + + $('#showAllCoursewares').on('click', function() { + $('#all-coursewares-dialog').modal('show'); + }); + + $('#all-coursewares-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); - // Bonfire instructions functions - $('#more-info').on('click', function() { - ga('send', 'event', 'Challenge', 'more-info', challengeName); - $('#brief-instructions').hide(); - $('#long-instructions').show().removeClass('hide'); - - }); - $('#less-info').on('click', function() { - $('#brief-instructions').show(); - $('#long-instructions').hide(); - }); - - var upvoteHandler = function () { - var _id = storyId; - $('#upvote').unbind('click'); - var alreadyUpvoted = false; - for (var i = 0; i < upVotes.length; i++) { - if (upVotes[i].upVotedBy === user._id) { - alreadyUpvoted = true; - break; + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + $('#next-courseware-button').on('click', function() { + console.log(passedCoursewareHash); + if ($('.signup-btn-nav').length < 1) { + switch (challengeType) { + case 0: + case 1: + case 2: + $.post( + '/completed-courseware/', + { + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName + } + }).success( + function(res) { + if (res) { + window.location.href = '/challenges'; + } } - } - if (!alreadyUpvoted) { - $.post('/stories/upvote', - { - data: { - id: _id, - upVoter: user - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#upvote').bind('click', upvoteHandler); - }) - .done(function (data, textStatus, xhr) { - $('#upvote').text('Upvoted!').addClass('disabled'); - - $('#storyRank').text(data.rank + " points"); - }); - } - }; - $('#upvote').on('click', upvoteHandler); - - - var storySubmitButtonHandler = function storySubmitButtonHandler() { - - var link = $('#story-url').val(); - var headline = $('#story-title').val(); - var description = $('#description-box').val(); - var userDataForUpvote = { - upVotedBy: user._id, - upVotedByUsername: user.profile.username - }; - $('#story-submit').unbind('click'); - $.post('/stories/', + ); + break; + case 3: + var didCompleteWith = $('#completed-with').val() || null; + var publicURL = $('#public-url').val() || null; + $.post( + '/completed-zipline-or-basejump/', { - data: { - link: link, - headline: headline, - timePosted: Date.now(), - description: description, - storyMetaDescription: storyMetaDescription, - rank: 1, - upVotes: [userDataForUpvote], - author: { - picture: user.profile.picture, - userId: user._id, - username: user.profile.username - }, - comments: [], - image: storyImage - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#story-submit').bind('click', storySubmitButtonHandler); - }) - .done(function (data, textStatus, xhr) { - window.location = '/stories/' + JSON.parse(data).storyLink; + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName, + completedWith: didCompleteWith, + publicURL: publicURL, + challengeType: challengeType + } + }).success( + function() { + window.location.href = '/challenges'; + }).fail( + function() { + window.location.href = '/challenges'; }); - - }; - - $('#story-submit').on('click', storySubmitButtonHandler); - - var commentSubmitButtonHandler = function commentSubmitButtonHandler() { - $('comment-button').unbind('click'); - var data = $('#comment-box').val(); - - $('#comment-button').attr('disabled', 'disabled'); - $.post('/stories/comment/', + break; + case 4: + var didCompleteWith = $('#completed-with').val() || null; + var publicURL = $('#public-url').val() || null; + var githubURL = $('#github-url').val() || null; + $.post( + '/completed-zipline-or-basejump/', { - data: { - associatedPost: storyId, - body: data, - author: { - picture: user.profile.picture, - userId: user._id, - username: user.profile.username - } - } - }) - .fail(function (xhr, textStatus, errorThrown) { - $('#comment-button').attr('disabled', false); - }) - .done(function (data, textStatus, xhr) { - window.location.reload(); + coursewareInfo: { + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName, + completedWith: didCompleteWith, + publicURL: publicURl, + githubURL: githubURL, + challengeType: challengeType + } + }).success(function() { + window.location.href = '/challenges'; + }).fail(function() { + window.location.replace(window.location.href); }); + break; + default: + break; + } + } + }); + + + $('.all-challenges').on('click', function() { + $('#all-challenges-dialog').modal('show'); + }); + + $('#showAllButton').on('click', function() { + $('#all-bonfires-dialog').modal('show'); + }); + + $('.next-challenge-button').on('click', function() { + l = location.pathname.split('/'); + window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); + }); + + +// Bonfire instructions functions + $('#more-info').on('click', function() { + ga('send', 'event', 'Challenge', 'more-info', challengeName); + $('#brief-instructions').hide(); + $('#long-instructions').show().removeClass('hide'); + + }); + $('#less-info').on('click', function() { + $('#brief-instructions').show(); + $('#long-instructions').hide(); + }); + + var upvoteHandler = function () { + var _id = storyId; + $('#upvote').unbind('click'); + var alreadyUpvoted = false; + for (var i = 0; i < upVotes.length; i++) { + if (upVotes[i].upVotedBy === user._id) { + alreadyUpvoted = true; + break; + } + } + if (!alreadyUpvoted) { + $.post('/stories/upvote', + { + data: { + id: _id, + upVoter: user + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#upvote').bind('click', upvoteHandler); + }) + .done(function (data, textStatus, xhr) { + $('#upvote').text('Upvoted!').addClass('disabled'); + + $('#storyRank').text(data.rank + " points"); + }); + } + }; + $('#upvote').on('click', upvoteHandler); + + + var storySubmitButtonHandler = function storySubmitButtonHandler() { + + var link = $('#story-url').val(); + var headline = $('#story-title').val(); + var description = $('#description-box').val(); + var userDataForUpvote = { + upVotedBy: user._id, + upVotedByUsername: user.profile.username }; + $('#story-submit').unbind('click'); + $.post('/stories/', + { + data: { + link: link, + headline: headline, + timePosted: Date.now(), + description: description, + storyMetaDescription: storyMetaDescription, + rank: 1, + upVotes: [userDataForUpvote], + author: { + picture: user.profile.picture, + userId: user._id, + username: user.profile.username + }, + comments: [], + image: storyImage + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#story-submit').bind('click', storySubmitButtonHandler); + }) + .done(function (data, textStatus, xhr) { + window.location = '/stories/' + JSON.parse(data).storyLink; + }); - $('#comment-button').on('click', commentSubmitButtonHandler); + }; + + $('#story-submit').on('click', storySubmitButtonHandler); + + var commentSubmitButtonHandler = function commentSubmitButtonHandler() { + $('comment-button').unbind('click'); + var data = $('#comment-box').val(); + + $('#comment-button').attr('disabled', 'disabled'); + $.post('/stories/comment/', + { + data: { + associatedPost: storyId, + body: data, + author: { + picture: user.profile.picture, + userId: user._id, + username: user.profile.username + } + } + }) + .fail(function (xhr, textStatus, errorThrown) { + $('#comment-button').attr('disabled', false); + }) + .done(function (data, textStatus, xhr) { + window.location.reload(); + }); + + }; + + $('#comment-button').on('click', commentSubmitButtonHandler); }); var profileValidation = angular.module('profileValidation',['ui.bootstrap']); profileValidation.controller('profileValidationController', ['$scope', '$http', - function($scope, $http) { - $http.get('/account/api').success(function(data) { - $scope.user = data.user; - $scope.user.profile.username = $scope.user.profile.username ? $scope.user.profile.username.toLowerCase() : undefined; - $scope.storedUsername = data.user.profile.username; - $scope.storedEmail = data.user.email; - $scope.user.email = $scope.user.email ? $scope.user.email.toLowerCase() : undefined; - $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle ? $scope.user.profile.twitterHandle.toLowerCase() : undefined; - $scope.asyncComplete = true; - }); - } + function($scope, $http) { + $http.get('/account/api').success(function(data) { + $scope.user = data.user; + $scope.user.profile.username = $scope.user.profile.username ? $scope.user.profile.username.toLowerCase() : undefined; + $scope.storedUsername = data.user.profile.username; + $scope.storedEmail = data.user.email; + $scope.user.email = $scope.user.email ? $scope.user.email.toLowerCase() : undefined; + $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle ? $scope.user.profile.twitterHandle.toLowerCase() : undefined; + $scope.asyncComplete = true; + }); + } ]); profileValidation.controller('pairedWithController', ['$scope', - function($scope) { - $scope.existingUser = null; - } + function($scope) { + $scope.existingUser = null; + } ]); profileValidation.controller('emailSignUpController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('emailSignInController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('URLSubmitController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('nonprofitFormController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('doneWithFirst100HoursFormController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.controller('submitStoryController', ['$scope', - function($scope) { + function($scope) { - } + } ]); profileValidation.directive('uniqueUsername',['$http',function($http) { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, element, attrs, ngModel) { - element.bind("keyup", function (event) { - ngModel.$setValidity('unique', true); - if (element.val()) { - $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { - if (element.val() == scope.storedUsername) { - ngModel.$setValidity('unique', true); - } else if (data) { - ngModel.$setValidity('unique', false); - } - }); - } - }); + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { + if (element.val() == scope.storedUsername) { + ngModel.$setValidity('unique', true); + } else if (data) { + ngModel.$setValidity('unique', false); + } + }); } + }); } + } }]); profileValidation.directive('existingUsername', ['$http', function($http) { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, element, attrs, ngModel) { - element.bind("keyup", function (event) { - if (element.val().length > 0) { - ngModel.$setValidity('exists', false); - } else { - element.removeClass('ng-dirty'); - ngModel.$setPristine(); - } - if (element.val()) { - $http - .get("/api/checkExistingUsername/" + element.val()) - .success(function (data) { - ngModel.$setValidity('exists', data); - }); - } + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + if (element.val().length > 0) { + ngModel.$setValidity('exists', false); + } else { + element.removeClass('ng-dirty'); + ngModel.$setPristine(); + } + if (element.val()) { + $http + .get("/api/checkExistingUsername/" + element.val()) + .success(function (data) { + ngModel.$setValidity('exists', data); }); } + }); } + } }]); profileValidation.directive('uniqueEmail', ['$http', function($http) { - return { - restrict: 'A', - require: 'ngModel', - link: function getUnique (scope, element, attrs, ngModel) { - element.bind("keyup", function (event) { - ngModel.$setValidity('unique', true); - if (element.val()) { - $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { - if (element.val() == scope.storedEmail) { - ngModel.$setValidity('unique', true); - } else if (data) { - ngModel.$setValidity('unique', false); - } - }); - }; - }); - } + return { + restrict: 'A', + require: 'ngModel', + link: function getUnique (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { + if (element.val() == scope.storedEmail) { + ngModel.$setValidity('unique', true); + } else if (data) { + ngModel.$setValidity('unique', false); + } + }); + }; + }); } + } }]); diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 4810949d9d..2f68cab841 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -148,16 +148,6 @@ block content include ../partials/bonfires script. - $.ajax({ - url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fbonfires%2F' + dashed + '&format=txt' - }) - .success( - function (data) { - console.log(data); - url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; - $('.btn-twitter').attr('href', url); - } - ); var MDNlinks = !{JSON.stringify(MDNlinks)}; if (!MDNlinks.length) { $('#MDN-links').addClass('collapse'); diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 9966844e3a..7ad3530f9b 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -60,6 +60,7 @@ block content var challengeName = !{JSON.stringify(name)}; var passedCoursewareName = challengeName; var prodOrDev = !{JSON.stringify(environment)}; + var challengeType = !{JSON.stringify(challengeType)}; var started = Math.floor(Date.now()); .col-xs-12.col-sm-12.col-md-5.col-lg-6 #mainEditorPanel @@ -81,4 +82,4 @@ block content .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - script(src="/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js") \ No newline at end of file + script(src="/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js") diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index 182d25a69a..3e33ee4a4b 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -47,6 +47,7 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; + var challengeType = !{JSON.stringify(challengeType)}; var passedCoursewareName = challengeName; var started = Math.floor(Date.now()); @@ -72,4 +73,4 @@ block content 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 \ No newline at end of file + a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index fa85520616..b6264c395a 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -27,6 +27,7 @@ block content var challengeName = !{JSON.stringify(name)}; var passedCoursewareName = challengeName; var started = Math.floor(Date.now()); + var challengeType = !{JSON.stringify(challengeType)}; #complete-courseware-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content @@ -46,10 +47,8 @@ block content a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress h1 #{name} script. - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; $('body').on('keypress', function(e) { if (e.ctrlKey && e.keyCode == 13) { $('#complete-courseware-dialog').modal('show'); } - }); \ No newline at end of file + }); diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade index c5df3c03ac..c630d7a6e3 100644 --- a/views/coursewares/showZiplineOrBasejump.jade +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -13,7 +13,7 @@ block content iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') br - if (user) - a.btn.btn-primary.btn-lg.btn-block#completed-courseware I've completed this challenge (ctrl + enter) + a.btn.btn-primary.btn-lg.btn-block#completed-zipline-or-basejump I've completed this challenge (ctrl + enter) script. var userLoggedIn = true; - else @@ -26,6 +26,7 @@ block content var challengeName = !{JSON.stringify(name)}; var passedCoursewareName = challengeName; var started = Math.floor(Date.now()); + var challengeType = !{JSON.stringify(challengeType)}; #complete-zipline-or-basejump-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content @@ -40,21 +41,21 @@ block content .form-group.text-center .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn - // extra field to distract password tools like lastpass from injecting css into our username field - input.form-control(ng-show="false") - if (challengeType === 'zipline') - input.form-control#public-url(name="codepenUrl", placeholder="http://codepen.io/your-pen-here", autofocus) - else - input.form-control#public-url(name="depoloymentUrl", placeholder="http://yourapp.com", autofocus) - input.form-control#github-url(name="githubUrl", placeholder="http://github.com/camper/project") + // extra field to distract password tools like lastpass from injecting css into our username field + input.form-control(ng-show="false") + if (challengeType === 3) + input.form-control#public-url(name="codepenUrl", placeholder="http://codepen.io/your-pen-here", autofocus) + else + input.form-control#public-url(name="depoloymentUrl", placeholder="http://yourapp.com", autofocus) + input.form-control#github-url(name="githubUrl", placeholder="http://github.com/camper/project") - input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser") - .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-cloak, ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") - alert(type='danger') - span.ion-close-circled - | Username not found + input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser") + .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-cloak, ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") + alert(type='danger') + span.ion-close-circled + | Username not found - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next bonfire (ctrl + enter) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - if (user.progressTimestamps.length > 2) @@ -64,10 +65,8 @@ block content - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; $('body').on('keypress', function(e) { if (e.ctrlKey && e.keyCode == 13) { - $('#complete-courseware-dialog').modal('show'); + $('#complete-zipline-or-basejump-dialog').modal('show'); } - }); \ No newline at end of file + }); From aa2f0e8af84b4089fba4770437398b6024166a60 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 21:15:18 +0900 Subject: [PATCH 46/57] Change completion event to remove duplicate entries if the user states they paired with themselves --- controllers/courseware.js | 34 +++++++++++++++++++++------------- public/js/main.js | 2 +- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 541586899c..82987ea7a7 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -285,20 +285,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { } pairedWith = pairedWith.pop(); - index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedCoursewares.splice(index, 1); - } - - pairedWith.completedCoursewares.push({ - _id: coursewareHash, - completedWith: req.user._id, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink - }); req.user.completedCoursewares.push({ _id: coursewareHash, @@ -312,6 +299,27 @@ exports.completedZiplineOrBasejump = function (req, res, next) { if (err) { return next(err); } + debug('this is the user object returned %s,' + + ' this is the req.user._id %s, ' + + 'this is the pairedWith._id %s', user, req.user._id, pairedWith._id); + debug(req.user._id.toString() === pairedWith._id.toString()); + if (req.user._id.toString() === pairedWith._id.toString()) { + return res.sendStatus(200); + } + index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedCoursewares.splice(index, 1); + + } + + pairedWith.completedCoursewares.push({ + _id: coursewareHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink + }); pairedWith.save(function (err, paired) { if (err) { return next(err); diff --git a/public/js/main.js b/public/js/main.js index c422d2b732..d0f73e7732 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -164,7 +164,7 @@ $(document).ready(function() { coursewareHash: passedCoursewareHash, coursewareName: passedCoursewareName, completedWith: didCompleteWith, - publicURL: publicURl, + publicURL: publicURL, githubURL: githubURL, challengeType: challengeType } From 75073e362a52c3f0e2624c7042babc851eb3adb3 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 21:41:29 +0900 Subject: [PATCH 47/57] Add errantly removed ramda include --- controllers/bonfire.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 8e7a919940..f2c13dd56e 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -3,7 +3,8 @@ var _ = require('lodash'), Bonfire = require('./../models/Bonfire'), User = require('./../models/User'), resources = require('./resources'), - MDNlinks = require('./../seed_data/bonfireMDNlinks'); + MDNlinks = require('./../seed_data/bonfireMDNlinks'), + R = require('ramda'); /** * Bonfire controller From b00c5be45c61a25565b65bac6fec3ec0b935cf6f Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 21:41:59 +0900 Subject: [PATCH 48/57] View improvement for users that are not logged in or happen to hit control enter --- views/bonfire/show.jade | 4 ++-- views/coursewares/showZiplineOrBasejump.jade | 14 +++++++------- views/partials/bonfires.jade | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 2f68cab841..9e5f55ca18 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -15,7 +15,7 @@ block content script(src='/js/lib/codemirror/mode/javascript/javascript.js') script(src='/js/lib/jailed/jailed.js') script(src='/js/lib/bonfire/bonfireInit.js') - script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js") + script(src='/js/lib/ramda/ramda.min.js') .row @@ -80,7 +80,7 @@ block content #less-info.btn.btn-primary.btn-block.btn-primary-ghost span.ion-arrow-up-b | Less information - + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) #showAllButton.btn.btn-info.btn-big.btn-block Show all bonfires br diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade index c630d7a6e3..d547ab3019 100644 --- a/views/coursewares/showZiplineOrBasejump.jade +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -55,13 +55,13 @@ block content span.ion-close-circled | Username not found - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - - - - if (user.progressTimestamps.length > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") - i.fa.fa-twitter   - = phrase + if (user) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + + - if (user.progressTimestamps.length > 2) + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") + i.fa.fa-twitter   + = phrase - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 3b59e6c8e5..2c219e089b 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -1,5 +1,6 @@ h3 ol#bonfireList + script(src='/js/lib/ramda/ramda.min.js') script. var getLinkedName = function getLinkedName(name) { return name.toLowerCase().replace(/\s/g, '-'); @@ -20,4 +21,4 @@ h3 $(li).appendTo($('#bonfireList')); } - }); \ No newline at end of file + }); From 4a805729cfde5e71ff89e46e50b9323469011711 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 12:51:23 -0700 Subject: [PATCH 49/57] add javascript waypoints --- seed_data/coursewares.json | 1397 ++++++++++++++++++++---------------- 1 file changed, 798 insertions(+), 599 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index ef0881283d..ac2226e5c2 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -1,347 +1,347 @@ [ { - "_id" : "bd7124d8c441eddfaeb5bdef", + "_id": "bd7124d8c441eddfaeb5bdef", "name": "Learn how Free Code Camp Works", "difficulty": 0.01, "challengeSeed": "114486344", - "description" : [ - "Watch this 1-minute video, or simply read this summary:", - "Welcome to Free Code Camp. We're a community of busy people learning to code.", - "We built this community because learning to code is hard. But anyone who can stay motivated can learn to code. And the best way to stay motivated is to code with friends.", - "To maximize accessibility, all our challenges are self-paced, browser-based, and free.", - "All of us start with the same 100 hours of interactive coding challenges. These cover Computer Science and databases. They also cover in-demand JavaScript tools like jQuery, Node.js and MongoDB.", - "Once we have a basic understanding of web development, we'll spend another 900 hours putting that theory into practice. We'll build full stack solutions for nonprofits.", - "By the end of this process, we'll be good at coding. We'll have a portfolio of apps with happy users to prove it. We'll also have an alumni network of fellow coders and nonprofits ready to serve as references.", - "If you make it through Free Code Camp, you will be able to get a coding job. There are far more job openings out there than there are qualified coders to fill them.", - "Also, for every pure coding job, there are at least 5 additional jobs that require some coding skills. So even if you decide not to pursue coding as a career, you'll still walk away with a valuable job skill.", - "There are 3 keys to succeeding in our community: do the challenges, make friends, and find a routine.", - "Now it's time to join our chatroom. Click the \"I've completed this challenge\" button to move on to your next challenge." + "description": [ + "Watch this 1-minute video, or simply read this summary:", + "Welcome to Free Code Camp. We're a community of busy people learning to code.", + "We built this community because learning to code is hard. But anyone who can stay motivated can learn to code. And the best way to stay motivated is to code with friends.", + "To maximize accessibility, all our challenges are self-paced, browser-based, and free.", + "All of us start with the same 100 hours of interactive coding challenges. These cover Computer Science and databases. They also cover in-demand JavaScript tools like jQuery, Node.js and MongoDB.", + "Once we have a basic understanding of web development, we'll spend another 900 hours putting that theory into practice. We'll build full stack solutions for nonprofits.", + "By the end of this process, we'll be good at coding. We'll have a portfolio of apps with happy users to prove it. We'll also have an alumni network of fellow coders and nonprofits ready to serve as references.", + "If you make it through Free Code Camp, you will be able to get a coding job. There are far more job openings out there than there are qualified coders to fill them.", + "Also, for every pure coding job, there are at least 5 additional jobs that require some coding skills. So even if you decide not to pursue coding as a career, you'll still walk away with a valuable job skill.", + "There are 3 keys to succeeding in our community: do the challenges, make friends, and find a routine.", + "Now it's time to join our chatroom. Click the \"I've completed this challenge\" button to move on to your next challenge." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7125d8c441eddfaeb5bdef", + "_id": "bd7125d8c441eddfaeb5bdef", "name": "Join Our Chat Room", "difficulty": 0.02, "challengeSeed": "114627322", - "description" : [ - "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper who's on the same challenge as you and wants to pair program.", - "If you don't already have a GitHub account, create one real quick at https://www.github.com.", - "Be sure to update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.", - "Now enter the chat room by going to https://gitter.im/FreeCodeCamp/FreeCodeCamp and clicking the \"sign in with GitHub\" button.", - "Introduce yourself to our chat room by typing: \"hello world!\".", - "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", - "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", - "Now that you've completed this challenge, you can go directly your most-recently visited chat room by clicking the \"Chat\" button in the navigation bar above." + "description": [ + "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper who's on the same challenge as you and wants to pair program.", + "If you don't already have a GitHub account, create one real quick at https://www.github.com.", + "Be sure to update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.", + "Now enter the chat room by going to https://gitter.im/FreeCodeCamp/FreeCodeCamp and clicking the \"sign in with GitHub\" button.", + "Introduce yourself to our chat room by typing: \"hello world!\".", + "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", + "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", + "Now that you've completed this challenge, you can go directly your most-recently visited chat room by clicking the \"Chat\" button in the navigation bar above." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7126d8c441eddfaeb5bdef", + "_id": "bd7126d8c441eddfaeb5bdef", "name": "Join Our Forum", "difficulty": 0.03, "challengeSeed": "115275066", - "description" : [ - "Go to Free Code Camp's forum: http://forum.freecodecamp.com.", - "You can come here to share and discuss coding resources, ask questions to our entire community, and coordinate local Free Code Camp events.", - "Our chat room is great for realtime discussions, but our forum is ideal for longer-term discussions and open-ended questions.", - "Sign in with the Github account you created during Challenge 1.", - "Click on the \"Introduce yourself here\" discussion.", - "Here you can read through other Free Code Camp community members' self introductions.", - "Go ahead and type a brief self introduction of your own.", - "Click on the \"Categories\" drop-down menu. You should see a category called \"Local Chapters\". Click that. If your city isn't already on the list, create a topic for it. Otherwise, introduce yourself to the other campers from your city.", - "Come back here daily to ask questions, engage in discussions, and share links to helpful coding tools.", - "Now that you've completed this challenge, you can go directly to the forum by clicking the \"Forum\" button in the navigation bar above." + "description": [ + "Go to Free Code Camp's forum: http://forum.freecodecamp.com.", + "You can come here to share and discuss coding resources, ask questions to our entire community, and coordinate local Free Code Camp events.", + "Our chat room is great for realtime discussions, but our forum is ideal for longer-term discussions and open-ended questions.", + "Sign in with the Github account you created during Challenge 1.", + "Click on the \"Introduce yourself here\" discussion.", + "Here you can read through other Free Code Camp community members' self introductions.", + "Go ahead and type a brief self introduction of your own.", + "Click on the \"Categories\" drop-down menu. You should see a category called \"Local Chapters\". Click that. If your city isn't already on the list, create a topic for it. Otherwise, introduce yourself to the other campers from your city.", + "Come back here daily to ask questions, engage in discussions, and share links to helpful coding tools.", + "Now that you've completed this challenge, you can go directly to the forum by clicking the \"Forum\" button in the navigation bar above." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7127d8c441eddfaeb5bdef", + "_id": "bd7127d8c441eddfaeb5bdef", "name": "Build a Personal Website", "difficulty": 0.04, "challengeSeed": "114627406", - "description" : [ - "There are tons of interactive HTML and CSS tutorials out there, but Nathan Bashaw and Christine Bower's Dash tutorials - which they built for General Assembly - are our favorite.", - "Go to https://dash.generalassemb.ly/projects/annas-website-1 and get started with your first project." + "description": [ + "There are tons of interactive HTML and CSS tutorials out there, but Nathan Bashaw and Christine Bower's Dash tutorials - which they built for General Assembly - are our favorite.", + "Go to https://dash.generalassemb.ly/projects/annas-website-1 and get started with your first project." ], "challengeType": 2, "tests": [] }, { - "_id" : "bd7128d8c441eddfaeb5bdef", + "_id": "bd7128d8c441eddfaeb5bdef", "name": "Build a Responsive Blog Theme", "difficulty": 0.05, "challengeSeed": "114578441", - "description" : [ - "Next, let's learn about responsive web design and continue learning about HTML and CSS.", - "A responsive website will automatically adapt to changes in your browser's width. This means that you can make one version of a website that will look good on desktop, tablet and phone.", - "Later, we'll use Twitter's Bootstrap CSS framework to build responsive websites.", - "You can check it out here: http://getbootstrap.com/.", - "Go to https://dash.generalassemb.ly/projects/jeffs-blog-1 and complete the second project." + "description": [ + "Next, let's learn about responsive web design and continue learning about HTML and CSS.", + "A responsive website will automatically adapt to changes in your browser's width. This means that you can make one version of a website that will look good on desktop, tablet and phone.", + "Later, we'll use Twitter's Bootstrap CSS framework to build responsive websites.", + "You can check it out here: http://getbootstrap.com/.", + "Go to https://dash.generalassemb.ly/projects/jeffs-blog-1 and complete the second project." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8129d8c441eddfaeb5bdef", + "_id": "bd8129d8c441eddfaeb5bdef", "name": "Build a Small Business Website", "difficulty": 0.06, "challengeSeed": "114578438", - "description" : [ - "Ready for some more HTML and CSS fundamentals?", - "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project." + "description": [ + "Ready for some more HTML and CSS fundamentals?", + "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project." ], "challengeType": 2, "tests": [] }, { - "_id" : "bd7110d8c441eddfaeb5bdef", + "_id": "bd7110d8c441eddfaeb5bdef", "name": "Tweak HTML and CSS in CodePen", "difficulty": 0.07, "challengeSeed": "110752744", - "description" : [ - "Now we're going to learn how to use a tool called CodePen, which lets you experiment with HTML and CSS, and even create single-page web applications, right in your browser!", - "Go to http://www.newsweek.com/", - "Change the window size. Note that Newsweek.com is using Responsive Design.", - "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", - "Select all the text, then copy it.", - "Go to http://codepen.io/pen/", - "Paste the HTML you copied from Newsweek.com into the HTML field of CodePen.", - "You now have your own customizable version of the Newsweek.com website. See if you can change some of the text and images." - ], - "challengeType": 2, - "tests": [] - }, - { - "_id" : "bd7111d8c441eddfaeb5bdef", - "name": "Build a CSS Robot", - "difficulty": 0.08, - "challengeSeed": "114578436", - "description" : [ - "Now let's learn some more CSS, and a small amount of a JavaScript-based tool called jQuery.", - "Go to https://dash.generalassemb.ly/projects/cotbots-1 and complete the fourth project." + "description": [ + "Now we're going to learn how to use a tool called CodePen, which lets you experiment with HTML and CSS, and even create single-page web applications, right in your browser!", + "Go to http://www.newsweek.com/", + "Change the window size. Note that Newsweek.com is using Responsive Design.", + "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", + "Select all the text, then copy it.", + "Go to http://codepen.io/pen/", + "Paste the HTML you copied from Newsweek.com into the HTML field of CodePen.", + "You now have your own customizable version of the Newsweek.com website. See if you can change some of the text and images." ], "challengeType": 2, "tests": [] }, { - "_id" : "bd7112d8c441eddfaeb5bdef", + "_id": "bd7111d8c441eddfaeb5bdef", + "name": "Build a CSS Robot", + "difficulty": 0.08, + "challengeSeed": "114578436", + "description": [ + "Now let's learn some more CSS, and a small amount of a JavaScript-based tool called jQuery.", + "Go to https://dash.generalassemb.ly/projects/cotbots-1 and complete the fourth project." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7112d8c441eddfaeb5bdef", "name": "Get Started with jQuery", "difficulty": 0.09, "challengeSeed": "114578435", - "description" : [ - "jQuery is a powerful tool for manipulating HTML elements.", - "It's a lot easier to use than JavaScript itself, so we'll learn it first.", - "It's also extremely popular with employers, so we're going to learn it well.", - "Code School has an excellent free course that will walk us through the basics of jQuery.", - "Go to http://try.jquery.com/levels/1/challenges/1 and complete the first section." + "description": [ + "jQuery is a powerful tool for manipulating HTML elements.", + "It's a lot easier to use than JavaScript itself, so we'll learn it first.", + "It's also extremely popular with employers, so we're going to learn it well.", + "Code School has an excellent free course that will walk us through the basics of jQuery.", + "Go to http://try.jquery.com/levels/1/challenges/1 and complete the first section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7113d8c441eddfaeb5bdef", + "_id": "bd7113d8c441eddfaeb5bdef", "name": "Traverse the DOM", "difficulty": 0.10, "challengeSeed": "114591805", - "description" : [ - "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", - "Go to http://try.jquery.com/levels/2/challenges/1 and complete the second section." + "description": [ + "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", + "Go to http://try.jquery.com/levels/2/challenges/1 and complete the second section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7114d8c441eddfaeb5bdef", + "_id": "bd7114d8c441eddfaeb5bdef", "name": "Work with the DOM", "difficulty": 0.11, "challengeSeed": "114591804", - "description" : [ - "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", - "Go to http://try.jquery.com/levels/3/challenges/1 and complete the third section." + "description": [ + "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", + "Go to http://try.jquery.com/levels/3/challenges/1 and complete the third section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7115d8c441eddfaeb5bdef", + "_id": "bd7115d8c441eddfaeb5bdef", "name": "Listen for DOM Events", "difficulty": 0.12, "challengeSeed": "114591802", - "description" : [ - "Now let's learn how to use jQuery Listeners. These will \"listen\" for something to happen, and then trigger a subsequent event", - "Go to http://try.jquery.com/levels/4/challenges/1 and complete the fourth section." + "description": [ + "Now let's learn how to use jQuery Listeners. These will \"listen\" for something to happen, and then trigger a subsequent event", + "Go to http://try.jquery.com/levels/4/challenges/1 and complete the fourth section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7116d8c441eddfaeb5bdef", + "_id": "bd7116d8c441eddfaeb5bdef", "name": "Use jQuery for Styling", "difficulty": 0.13, "challengeSeed": "114591801", - "description" : [ - "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", - "Go to http://try.jquery.com/levels/5/challenges/1 and complete the fifth section." + "description": [ + "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", + "Go to http://try.jquery.com/levels/5/challenges/1 and complete the fifth section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7117d8c441eddfaeb5bdef", + "_id": "bd7117d8c441eddfaeb5bdef", "name": "Build a MadLibs Game", "difficulty": 0.14, "challengeSeed": "114591799", - "description" : [ - "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", - "If you aren't familiar with Mad Libs, they basically involve inserting random nouns, adjectives and verbs into stories. The stories that result are often hilarious.", - "Go to https://dash.generalassemb.ly/projects/mad-libs-1 and complete the fifth project." + "description": [ + "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", + "If you aren't familiar with Mad Libs, they basically involve inserting random nouns, adjectives and verbs into stories. The stories that result are often hilarious.", + "Go to https://dash.generalassemb.ly/projects/mad-libs-1 and complete the fifth project." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7118d8c441eddfaeb5bdef", + "_id": "bd7118d8c441eddfaeb5bdef", "name": "Discover Chrome's DevTools", "difficulty": 0.15, "challengeSeed": "110752743", - "description" : [ - "It's time to learn the most powerful tool your browser has - the Development Tools!", - "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", - "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", - "Go to http://discover-devtools.codeschool.com and complete this short course." + "description": [ + "It's time to learn the most powerful tool your browser has - the Development Tools!", + "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", + "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", + "Go to http://discover-devtools.codeschool.com and complete this short course." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7119d8c441eddfaeb5bdef", + "_id": "bd7119d8c441eddfaeb5bdef", "name": "Tackle jQuery Exercises", "difficulty": 0.16, "challengeSeed": "113173612", - "description" : [ - "We've built some special jQuery challenges to help you reinforce your knowledge of this fundamental skill.", - "There are many correct ways to solve each of these exercises. After you complete the challenge, you can compare your solution with our solution by pressing the \"#solution-button\" button.", - "Go to http://freecodecamp.com/jquery-exercises and complete the exercises." - ], + "description": [ + "We've built some special jQuery challenges to help you reinforce your knowledge of this fundamental skill.", + "There are many correct ways to solve each of these exercises. After you complete the challenge, you can compare your solution with our solution by pressing the \"#solution-button\" button.", + "Go to http://freecodecamp.com/jquery-exercises and complete the exercises." + ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7120d8c441eddfaeb5bdef", + "_id": "bd7120d8c441eddfaeb5bdef", "name": "Customize Bootstrap", "difficulty": 0.17, "challengeSeed": "110752741", - "description" : [ - "Let's learn a little more about Twitter's responsive CSS framework, Bootstrap, and how we can add some custom themes to it.", - "Go to http://getbootstrap.com/components/", - "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", - "Select all the text, then copy it.", - "Go to http://codepen.io/pen/", - "Paste the HTML you copied from GetBootStrap.com into the HTML field of CodePen.", - "Go to http://bootswatch.com/", - "Decide which theme you want to use.", - "Click the down arrow next to the download button and choose 'bootstrap.css'.", - "Select all the text, then copy it.", - "Go back to CodePen and paste the CSS you copied from Bootswatch.com into the CSS field of CodePen.", - "Your Bootswatch CSS should now be applied to the HTML from the GetBootStrap page.", - "This page is currently using a two-column layout, with the main content on the left and additional navigation on the right. See if you can make it a one-column layout." + "description": [ + "Let's learn a little more about Twitter's responsive CSS framework, Bootstrap, and how we can add some custom themes to it.", + "Go to http://getbootstrap.com/components/", + "Right-click an area of the page that doesn't have any HTML elements on it, then choose 'view page source'.", + "Select all the text, then copy it.", + "Go to http://codepen.io/pen/", + "Paste the HTML you copied from GetBootStrap.com into the HTML field of CodePen.", + "Go to http://bootswatch.com/", + "Decide which theme you want to use.", + "Click the down arrow next to the download button and choose 'bootstrap.css'.", + "Select all the text, then copy it.", + "Go back to CodePen and paste the CSS you copied from Bootswatch.com into the CSS field of CodePen.", + "Your Bootswatch CSS should now be applied to the HTML from the GetBootStrap page.", + "This page is currently using a two-column layout, with the main content on the left and additional navigation on the right. See if you can make it a one-column layout." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7122d8c441eddfaeb5bdef", + "_id": "bd7122d8c441eddfaeb5bdef", "name": "Inject Animation into CSS", "difficulty": 0.18, "challengeSeed": "110752740", - "description" : [ - "You may have noticed some sites have cool animations. Actually, animating DOM elements is pretty straightforward if you use a CSS library called Animate.css.", - "Go to http://daneden.github.io/animate.css/ and try out some of the CSS animations.", - "Go to http://codepen.io/ossia/pen/bGegt.", - "Press the \"Fork\" button. This will fork, meaning create a copy of, the CodePen.", - "Click the gear in the CSS column.", - "Click \"Add another resource\" and start typing \"animate.css\". Click on the dropdown results to autocomplete it.", - "Now that you have Animate.css enabled, use jQuery and the \"toggleClass\" method to add an animated class to all h1 elements when you click the \"Press Me\" button." + "description": [ + "You may have noticed some sites have cool animations. Actually, animating DOM elements is pretty straightforward if you use a CSS library called Animate.css.", + "Go to http://daneden.github.io/animate.css/ and try out some of the CSS animations.", + "Go to http://codepen.io/ossia/pen/bGegt.", + "Press the \"Fork\" button. This will fork, meaning create a copy of, the CodePen.", + "Click the gear in the CSS column.", + "Click \"Add another resource\" and start typing \"animate.css\". Click on the dropdown results to autocomplete it.", + "Now that you have Animate.css enabled, use jQuery and the \"toggleClass\" method to add an animated class to all h1 elements when you click the \"Press Me\" button." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7123d8c441eddfaeb5bdef", + "_id": "bd7123d8c441eddfaeb5bdef", "name": "Learn Basic Computer Science", "difficulty": 0.19, "challengeSeed": "114628241", - "description" : [ - "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", - "Note that Harvard also has an excellent introduction to computer science course called CS50, but it takes more than 100 hours to complete, and doesn't use JavaScript.", - "Despite being completely self-paced, Stanford's CS101 course is broken up into weeks. Each of the following challenges will address one of those weeks.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ and complete the first week's course work." + "description": [ + "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", + "Note that Harvard also has an excellent introduction to computer science course called CS50, but it takes more than 100 hours to complete, and doesn't use JavaScript.", + "Despite being completely self-paced, Stanford's CS101 course is broken up into weeks. Each of the following challenges will address one of those weeks.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ and complete the first week's course work." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8124d8c441eddfaeb5bdef", + "_id": "bd8124d8c441eddfaeb5bdef", "name": "Learn Loops", "difficulty": 0.20, "challengeSeed": "114597348", - "description" : [ + "description": [ "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", "This will introduce us to loops, a fundamental feature of every programming language.", "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ and complete Week 2." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8125d8c441eddfaeb5bdef", + "_id": "bd8125d8c441eddfaeb5bdef", "name": "Learn Computer Hardware", "difficulty": 0.21, "challengeSeed": "114597347", - "description" : [ + "description": [ "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", "This challenge will also give you an understanding of how bits and bytes work.", "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ and complete Week 3." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8126d8c441eddfaeb5bdef", + "_id": "bd8126d8c441eddfaeb5bdef", "name": "Learn Computer Networking", "difficulty": 0.22, "challengeSeed": "114604811", - "description" : [ + "description": [ "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", "Particularly important, you will learn about networks and TCP/IP - the protocol that powers the internet.", "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ and complete Week 4." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8127d8c441eddfaeb5bdef", + "_id": "bd8127d8c441eddfaeb5bdef", "name": "Learn Boolean Logic", "difficulty": 0.23, "challengeSeed": "114604812", - "description" : [ + "description": [ "Now we'll do some more table exercises and learn boolean logic.", "We'll also learn the difference between digital data and analog data.", "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ and complete Week 5." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd8128d8c441eddfaeb5bdef", + "_id": "bd8128d8c441eddfaeb5bdef", "name": "Learn Computer Security", "difficulty": 0.24, "challengeSeed": "114604813", - "description" : [ + "description": [ "We're almost done with Stanford's Introduction to Computer Science course!", "We'll learn about one of the most important inventions of the 20th century - spreadsheets.", "We'll also learn about Computer Security and some of the more common vulnerabilities software systems have.", @@ -349,13 +349,13 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7129d8c441eddfaeb5bdef", + "_id": "bd7129d8c441eddfaeb5bdef", "name": "Build an Adventure Game", "difficulty": 0.25, "challengeSeed": "114604814", - "description" : [ + "description": [ "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", "We're going to work through Codecademy's famous interactive JavaScript course.", "This course will teach us some JavaScript fundamentals while guiding us through the process of building interesting web apps, all within Codecademy's learner-friendly environment!", @@ -364,52 +364,51 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7130d8c441eddfaeb5bdef", + "_id": "bd7130d8c441eddfaeb5bdef", "name": "Build Rock Paper Scissors", "difficulty": 0.26, "challengeSeed": "114604815", - "description" : [ + "description": [ "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", "Go to http://www.codecademy.com/courses/javascript-beginner-en-6LzGd/0/1 and complete the section.", "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-Bthev-mskY8/0/1." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7131d8c441eddfaeb5bdef", + "_id": "bd7131d8c441eddfaeb5bdef", "name": "Learn JavaScript For Loops", "difficulty": 0.27, "challengeSeed": "114614220", - "description" : [ + "description": [ "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", "Go to http://www.codecademy.com/courses/javascript-beginner-en-NhsaT/0/1web and complete both the both For and While loop section.", "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-XEDZA/0/1." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7132d8c441eddfaeb5bdef", + "_id": "bd7132d8c441eddfaeb5bdef", "name": "Learn JavaScript While Loops", "difficulty": 0.28, "challengeSeed": "114612889", - "description" : [ - + "description": [ "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-mrTNH-6VIZ9/0/1." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7133d8c441eddfaeb5bdef", + "_id": "bd7133d8c441eddfaeb5bdef", "name": "Learn Control Flow", "difficulty": 0.29, "challengeSeed": "114612888", - "description" : [ + "description": [ "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", "We'll also learn some advanced \"Control Flow\" principals, such as ways we can exit loops early.", "Go to http://www.codecademy.com/courses/javascript-beginner-en-qDwp0/0/1 and complete the section.", @@ -417,83 +416,83 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7134d8c441eddfaeb5bdef", + "_id": "bd7134d8c441eddfaeb5bdef", "name": "Build a Contact List", "difficulty": 0.30, "challengeSeed": "114612887", - "description" : [ + "description": [ "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", "Go to http://www.codecademy.com/courses/javascript-beginner-en-9Sgpi/0/1 and complete the section.", "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-3bmfN/0/1." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7135d8c441eddfaeb5bdef", + "_id": "bd7135d8c441eddfaeb5bdef", "name": "Build an Address Book", "difficulty": 0.31, "challengeSeed": "114612885", - "description" : [ + "description": [ "Let's learn more about objects.", "Go to http://www.codecademy.com/courses/spencer-sandbox/0/1 and complete the section.", "Be sure to also complete this section: http://www.codecademy.com/courses/building-an-address-book/0/1?curriculum_id=506324b3a7dffd00020bf661." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7136d8c441eddfaeb5bdef", + "_id": "bd7136d8c441eddfaeb5bdef", "name": "Build a Cash Register", "difficulty": 0.32, "challengeSeed": "114612882", - "description" : [ + "description": [ "In this final Codecademy section, we'll learn even more about JavaScript objects.", "Go to http://www.codecademy.com/courses/objects-ii/0/1 and complete this section.", "Be sure to also complete the final section: http://www.codecademy.com/courses/close-the-super-makert/0/1." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7137d8c441eddfaeb5bdef", + "_id": "bd7137d8c441eddfaeb5bdef", "name": "Get Help the Hacker Way", "difficulty": 0.33, "challengeSeed": "111500801", - "description" : [ - "Watch the video to learn the RSAP (Read, Search, Ask, Post) methodology for getting help.", - "Try an intelligent Google query that involves JavaScript and filters for this year (since JavaScript changes).", - "Go to http://stackoverflow.com/ and view the recent questions.", - "Go to http://webchat.freenode.net/ and create an IRC account.", - "Join the #LearnJavaScript chat room and introduce yourself as a Free Code Camp student.", - "Finally, we have a special chat room specifically for getting help with tools you learn through Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", - "Now you have several ways of getting help when you're stuck." + "description": [ + "Watch the video to learn the RSAP (Read, Search, Ask, Post) methodology for getting help.", + "Try an intelligent Google query that involves JavaScript and filters for this year (since JavaScript changes).", + "Go to http://stackoverflow.com/ and view the recent questions.", + "Go to http://webchat.freenode.net/ and create an IRC account.", + "Join the #LearnJavaScript chat room and introduce yourself as a Free Code Camp student.", + "Finally, we have a special chat room specifically for getting help with tools you learn through Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", + "Now you have several ways of getting help when you're stuck." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7138d8c441eddfaeb5bdef", + "_id": "bd7138d8c441eddfaeb5bdef", "name": "Learn Regular Expressions", "difficulty": 0.34, "challengeSeed": "112547802", - "description" : [ - "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", - "Check out http://www.regexr.com. It's a Regular Expression Sandbox.", - "Now go to http://www.regexone.com and complete the tutorial and exercises 1 - 6.", - "Note that you can click \"continue\" to move on to the next step as soon as all the tasks have green check marks beside them. You can often do this just by using the wildcard \"dot\" operator, but try to use the techniques that each lesson recommends." + "description": [ + "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", + "Check out http://www.regexr.com. It's a Regular Expression Sandbox.", + "Now go to http://www.regexone.com and complete the tutorial and exercises 1 - 6.", + "Note that you can click \"continue\" to move on to the next step as soon as all the tasks have green check marks beside them. You can often do this just by using the wildcard \"dot\" operator, but try to use the techniques that each lesson recommends." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7139d8c441eddfaeb5bdef", + "_id": "bd7139d8c441eddfaeb5bdef", "name": "Pair Program on Bonfires", "difficulty": 0.35, "challengeSeed": "119657641", - "description" : [ + "description": [ "OK, we're finally ready to start pair programming!", "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", @@ -515,40 +514,40 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7140d8c441eddfaeb5bdef", + "_id": "bd7140d8c441eddfaeb5bdef", "name": "Manage Source Code with Git", "difficulty": 0.36, "challengeSeed": "114635309", - "description" : [ - "Revision Control Systems like Git ensure that, no matter how you experiment with your code, you can always roll back your app to a stable previous state.", - "Git is also a great way to share and contribute to open source software.", - "Go to https://www.codeschool.com/courses/try-git and complete this short interactive course." - ], + "description": [ + "Revision Control Systems like Git ensure that, no matter how you experiment with your code, you can always roll back your app to a stable previous state.", + "Git is also a great way to share and contribute to open source software.", + "Go to https://www.codeschool.com/courses/try-git and complete this short interactive course." + ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7141d8c441eddfaeb5bdef", + "_id": "bd7141d8c441eddfaeb5bdef", "name": "Get Started with Node.js", "difficulty": 0.37, "challengeSeed": "114686471", - "description" : [ + "description": [ "Now that we understand some Computer Science and JavaScript programming, you're ready to move on to Full-stack JavaScript!", "The first step is to familiarize ourselves Node.js, the JavaScript-based web server that most full-stack JavaScript apps use.", "Code School has an excellent course on Node.js. Note that this course requires a Code School subscription, but that you can get a free two-day membership to Code School by going to https://www.codeschool.com/hall_passes/213f3fedb6b9/claim_shared. The challenges immediately following these Node.js challenges also require a Code School course, so you may want to try to complete all these challenges in one two-day period. Alternatively, you could subscribe to Code School for one month, then take your time in completing these challenges.", "When you're ready, go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/1/video/1 and complete the first chapter." - ], + ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7142d8c441eddfaeb5bdef", + "_id": "bd7142d8c441eddfaeb5bdef", "name": "Try Node.js Events", "difficulty": 0.38, "challengeSeed": "114684206", - "description" : [ + "description": [ "One of the reasons Node.js is so fast is that it is \"evented.\" It processes events in an asynchronous manner.", "As a result, Node.js relies on asynchronous callbacks.", "We'll learn more about how events and callbacks work in this exciting Code School lesson.", @@ -556,26 +555,26 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7143d8c441eddfaeb5bdef", + "_id": "bd7143d8c441eddfaeb5bdef", "name": "Try Node.js Streams", "difficulty": 0.39, "challengeSeed": "114684209", - "description" : [ + "description": [ "In this Code School lesson, we'll learn about streaming data back and forth between the client to the server.", "We'll also learn about FS, or File System, an important Node.js module for streaming data.", "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/3/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7144d8c441eddfaeb5bdef", + "_id": "bd7144d8c441eddfaeb5bdef", "name": "Learn how Node.js Modules Work", "difficulty": 0.40, "challengeSeed": "114684213", - "description" : [ + "description": [ "One of the most exciting features of Node.js is NPM - Node Package Manager", "With NPM, you quickly install any of thousands of Node.js modules into your app, and it will automatically handle the other modules that each module dependends upon downstream.", "In this lesson, we'll learn how to include these modules in our Node.js app by requiring them as variables.", @@ -583,176 +582,175 @@ ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7145d8c441eddfaeb5bdef", + "_id": "bd7145d8c441eddfaeb5bdef", "name": "Start an Express.js Server", "difficulty": 0.41, "challengeSeed": "114684247", - "description" : [ + "description": [ "We'll complete Code School's Express.js course shortly after completing this course, but this challenge will give you a quick tour of the Express.js framework.", "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/5/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7146d8c441eddfaeb5bdef", + "_id": "bd7146d8c441eddfaeb5bdef", "name": "Use Socket.io", "difficulty": 0.42, "challengeSeed": "114684530", - "description" : [ + "description": [ "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/6/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7147d8c441eddfaeb5bdef", + "_id": "bd7147d8c441eddfaeb5bdef", "name": "Use Redis to Persist Data", "difficulty": 0.43, "challengeSeed": "114684532", - "description" : [ + "description": [ "Redis is a key-value store, which is a type of non-relational database. It's one of the fastest and easiest ways to persist data.", "Even though we'll ultimately use MongoDB and other technologies to persist data, Redis is quite easy to learn and is still worth learning.", "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/7/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7148d8c441eddfaeb5bdef", + "_id": "bd7148d8c441eddfaeb5bdef", "name": "Dive Deeper into Express.js", "difficulty": 0.44, "challengeSeed": "114684533", - "description" : [ + "description": [ "Code School has one of the first comprehensive courses on Express.js. Note that this course requires a Code School subscription, but that you can get a free two-day membership to Code School by going to https://www.codeschool.com/hall_passes/213f3fedb6b9/claim_shared. If you've already used your Code School two-day membership, go to the Free Code Camp main chat room and ask how you can get some extra time to work through this course. Alternatively, you could subscribe to Code School for one month, then take your time in completing these challenges.", "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/1/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7149d8c441eddfaeb5bdef", + "_id": "bd7149d8c441eddfaeb5bdef", "name": "Setup Express.js Middleware", "difficulty": 0.45, "challengeSeed": "114684535", - "description" : [ + "description": [ "Express.js makes extensive use of middleware - a stack of functions that run sequentially in response to a specific event.", "Let's learn how to incorporate modules and middleware into our Express.js app.", "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/2/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7150d8c441eddfaeb5bdef", + "_id": "bd7150d8c441eddfaeb5bdef", "name": "Take Advantage of Parameters", "difficulty": 0.46, "challengeSeed": "114684537", - "description" : [ + "description": [ "Have you ever noticed a question mark in your browser's address bar, followed by a series of strings? Those are parameters. Parameters are an efficient way to pass information to the server between page loads.", "We'll learn about parameters, along with a powerful Express.js feature called Dynamic Routing, in this exciting Code School lesson.", "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/3/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7151d8c441eddfaeb5bdef", + "_id": "bd7151d8c441eddfaeb5bdef", "name": "Add the Body Parser", "difficulty": 0.47, "challengeSeed": "114684720", - "description" : [ + "description": [ "Now we'll add the Body Parser module to Express.js. Body Parser is a powerful middleware that helps with routing.", "We'll also learn more about HTTP Requests, such as Post and Delete.", "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/4/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7152d8c441eddfaeb5bdef", + "_id": "bd7152d8c441eddfaeb5bdef", "name": "Configure Routes in Express.js", "difficulty": 0.48, "challengeSeed": "114684724", - "description" : [ + "description": [ "For this last Code School Express.js challenge, we'll refactor our routes.", "Go to http://campus.codeschool.com/courses/building-blocks-of-express-js/level/5/video/1 and complete the section." - ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7153d8c441eddfaeb5bdef", + "_id": "bd7153d8c441eddfaeb5bdef", "name": "Try MongoDB", "difficulty": 0.49, "challengeSeed": "114685061", - "description" : [ + "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7154d8c441eddfaeb5bdef", + "_id": "bd7154d8c441eddfaeb5bdef", "name": "Get Started with Angular.js", "difficulty": 0.50, "challengeSeed": "114684726", - "description" : [ + "description": [ "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", "In this course, we'll build a virtual shop entirely in Angular.js.", "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/1/section/1/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7155d8c441eddfaeb5bdef", + "_id": "bd7155d8c441eddfaeb5bdef", "name": "Apply Angular.js Directives", "difficulty": 0.51, "challengeSeed": "114684727", - "description" : [ + "description": [ "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", "Let's learn how these powerful directives work, and how to use them to make your web apps more dynamic", "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/1/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7156d8c441eddfaeb5bdef", + "_id": "bd7156d8c441eddfaeb5bdef", "name": "Power Forms with Angular.js", "difficulty": 0.52, "challengeSeed": "114684729", - "description" : [ + "description": [ "One area where Angular.js really shines is its powerful web forms.", "Learn how to create reactive Angular.js forms, including real-time form validation.", "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/3/section/1/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7157d8c441eddfaeb5bdef", + "_id": "bd7157d8c441eddfaeb5bdef", "name": "Customize Angular.js Directives", "difficulty": 0.53, "challengeSeed": "114685062", - "description" : [ + "description": [ "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/4/section/1/video/1 and complete the section." ], "challengeType": 2, "tests": [] - }, + }, { - "_id" : "bd7158d8c441eddfaeb5bdef", + "_id": "bd7158d8c441eddfaeb5bdef", "name": "Create Angular.js Services", "difficulty": 0.54, "challengeSeed": "114685060", - "description" : [ + "description": [ "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", "We'll learn how to use services in this final Code School Angular.js challenge.", "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/5/section/1/video/1 and complete the section." @@ -761,11 +759,11 @@ "tests": [] }, { - "_id" : "bd7158d8c442eddfaeb5bdef", + "_id": "bd7158d8c442eddfaeb5bdef", "name": "Zipline Trello API Integration", "difficulty": 0.55, "challengeSeed": "123488494", - "description" : [ + "description": [ "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", "In the JavaScript box, scroll down to the comment that reads \"//Changeable code be made here\". This CodePen is already pulling in the relevant JSON from our API. This JSON is from one of our Trello boards. You can view the actual Trello board here: https://trello.com/b/BA3xVpz9/nonprofit-projects.", "You can view the JSON output here: http://www.freecodecamp.com/api/trello.", @@ -778,11 +776,11 @@ "tests": [] }, { - "_id" : "bd7158d8c443eddfaeb5bdef", + "_id": "bd7158d8c443eddfaeb5bdef", "name": "Basejump Hello World in Cloud 9", "difficulty": 0.56, "challengeSeed": "123488494", - "description" : [ + "description": [ "Go to MEAN.js http://meanjs.org and click the 'view on GitHub' button. This will take you to MEAN.js's GitHub repository.", "Copy the link from the SSH clone URL on the lower right.", "Go to Cloud 9 IDE at http://c9.io and log in with GitHub.", @@ -791,11 +789,11 @@ "Once it has loaded, install all of MEAN.js's packages by clicking into Cloud 9's terminal at the bottom and running npm install.", "Once that command has finished, set up MongoDB and start it by copying and pasting this code into Cloud 9's terminal: mkdir data && echo 'mongod --bind_ip=$IP --dbpath=data --nojournal --rest \"$@\"' > mongod && chmod a+x mongod && ./mongod. You don't worry if you don't understand what that means. From now on, you'll just need to run this command to start MongoDB: ./mongod." ], - "challengeType": 4, + "challengeType": 3, "tests": [] }, { - "_id" : "bd7123c8c441eddfaeb5bdef", + "_id": "bd7123c8c441eddfaeb5bdef", "name": "Start our Challenges", "difficulty": "1.00", "description": [ @@ -815,11 +813,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf0887a", + "_id": "bad87fee1348bd9aedf0887a", "name": "Use the h2 Element", - "difficulty" : "1.01", + "difficulty": "1.01", "description": [ "Add an h2 tag that says \"cat photo app\" to make a second HTML element below the \"hello world\" h1 element.", "The h2 element you enter will create an h2 element on the website.", @@ -835,11 +832,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08801", + "_id": "bad87fee1348bd9aedf08801", "name": "Use the P Element", - "difficulty" : "1.02", + "difficulty": "1.02", "description": [ "Create a p element below the h2 element, and give it the text \"hello paragraph\".", "p elements are the preferred element for normal-sized paragraph text on websites.", @@ -854,11 +850,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aeaf08801", + "_id": "bad87fee1348bd9aeaf08801", "name": "Add a Line Break to Visually Separate Elements", - "difficulty" : "1.03", + "difficulty": "1.03", "description": [ "Add a line break between the <h2> and <p> elements.", "You can create an line break element with <br/>.", @@ -874,11 +869,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08802", + "_id": "bad87fee1348bd9aedf08802", "name": "Uncomment HTML", - "difficulty" : "1.04", + "difficulty": "1.04", "description": [ "Uncomment the h1, h2 and p elements.", "Commenting is a way that you can leave comments within your code without affecting the code itself.", @@ -898,11 +892,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08804", + "_id": "bad87fee1348bd9aedf08804", "name": "Comment out HTML", - "difficulty" : "1.05", + "difficulty": "1.05", "description": [ "Comment out the h1 element and the p element, but leave the h2 element uncommented.", "Remember that in order to start a comment, you need to use <!-- and to end a comment, you need to use -->.", @@ -923,11 +916,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08833", + "_id": "bad87fee1348bd9aedf08833", "name": "Use Lorem Ipsum Text as a Placeholder", - "difficulty" : "1.06", + "difficulty": "1.06", "description": [ "Change the text in the p element to use the first few words of lorem ipsum text.", "Designers use lorem ipsum as placeholder text. It's called lorem ipsum text because it's those are the first two words of a passage by Cicero of Ancient Rome.", @@ -944,11 +936,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08803", + "_id": "bad87fee1348bd9aedf08803", "name": "Change the Color of Text", - "difficulty" : "1.07", + "difficulty": "1.07", "description": [ "Change the h2 element's style so that its text color is red.", "We can do this by changing the style of the h2 element.", @@ -965,11 +956,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08805", + "_id": "bad87fee1348bd9aedf08805", "name": "Create a Style Tag for CSS", - "difficulty" : "1.08", + "difficulty": "1.08", "description": [ "Create a style tag and write the CSS to make all h2 elements blue.", "With CSS, there are hundreds of CSS attributes that you can use to change the way an element looks on a web page.", @@ -983,17 +973,16 @@ "expect($('h2')).to.have.css('color', '#0000ff');" ], "challengeSeed": [ - "

hello world

", + "

hello world

", "

cat photo app

", "

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

" ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aecf08806", + "_id": "bad87fee1348bd9aecf08806", "name": "Use a CSS Class to Style an Element", - "difficulty" : "1.09", + "difficulty": "1.09", "description": [ "Create a CSS class called red-text and apply it to the h2 element.", "classes are reusable styles that can be added to HTML elements.", @@ -1016,11 +1005,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aeff08806", + "_id": "bad87fee1348bd9aeff08806", "name": "Use a CSS Class to Style Multiple Elements", - "difficulty" : "1.10", + "difficulty": "1.10", "description": [ "Apply the \"red-text\" class to the h1, h2 and p elements.", "Remember that you can attach classes to HTML elements by using the class=\"class\" within the relevant element's opening tag." @@ -1045,11 +1033,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08806", + "_id": "bad87fee1348bd9aedf08806", "name": "Change the Font Size of an Element", - "difficulty" : "1.11", + "difficulty": "1.11", "description": [ "Set the font size of all p elements to 16 pixels", "Font size is controlled by the font-size CSS attribute.", @@ -1072,11 +1059,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08807", + "_id": "bad87fee1348bd9aedf08807", "name": "Import a Google Font", - "difficulty" : "1.12", + "difficulty": "1.12", "description": [ "Apply the font-family of Lobster to all h1 elements.", "The first line of code in your text editor is a call to Google that grabs a font called Lobster and loads it into your HTML.", @@ -1104,11 +1090,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08808", + "_id": "bad87fee1348bd9aedf08808", "name": "Specify How Fonts Should Degrade", - "difficulty" : "1.13", + "difficulty": "1.13", "description": [ "Make all h2 elements use Lobster as their font family, but degrade to the Serif font when the Lobster font isn't available.", "We commented out our call to Google Fonts, and now our lobter isn't available.", @@ -1133,11 +1118,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08809", + "_id": "bad87fee1348bd9aedf08809", "name": "Using Important to Override Styles", - "difficulty" : "1.14", + "difficulty": "1.14", "description": [ "Apply both the \"blue-text\" and \"urgently-red\" classes to all h2 elements, but use !important to ensure the element is rendered as being red.", "Sometimes HTML elements will receive conflicting information from CSS classes as to how they should be styled.", @@ -1172,11 +1156,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08811", + "_id": "bad87fee1348bd9aedf08811", "name": "Use rgb Codes for Precise Colors", - "difficulty" : "1.17", + "difficulty": "1.17", "description": [ "Change the red-text class's color rgb value to be red.", "Another way to represent color in CSS is with rgb, or red-green-blue notation.", @@ -1202,11 +1185,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08810", + "_id": "bad87fee1348bd9aedf08810", "name": "Use Hex Codes for Precise Colors", - "difficulty" : "1.15", + "difficulty": "1.15", "description": [ "Change the hex code in the \"red-text\" class to hex code for the color red.", "Hexadecimal (hex) code is a popular way of specifying color in CSS.", @@ -1233,11 +1215,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9bedf08810", + "_id": "bad87fee1348bd9bedf08810", "name": "Use Shortened 3 Digit Hex Codes", - "difficulty" : "1.16", + "difficulty": "1.16", "description": [ "Change the hex code in the \"red-text\" class to the shortened 3-digit hex code for the color red.", "You can also shorten the 6-digit color hex code to a 3-digit code. For example, #00ff00 becomes #0f0. This is less precise, but equally effective." @@ -1259,11 +1240,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad88fee1348bd9aedf08811", + "_id": "bad88fee1348bd9aedf08811", "name": "Set the Alpha of a Color with rgba", - "difficulty" : "1.17", + "difficulty": "1.17", "description": [ "Change the red-text class's color rgb value to be red.", "Another way to represent color in CSS is with rgb, or red-green-blue notation.", @@ -1289,11 +1269,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08812", + "_id": "bad87fee1348bd9aedf08812", "name": "Add an Image to your Website", - "difficulty" : "1.18", + "difficulty": "1.18", "description": [ "Use an img element to add the image http://bit.ly/cutegraycat to your website.", "You can add images to your website by using the img element.", @@ -1316,11 +1295,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9acdf08812", + "_id": "bad87fee1348bd9acdf08812", "name": "Specify an Image Size", - "difficulty" : "1.19", + "difficulty": "1.19", "description": [ "Create a class called narrow-image and use it to resize the image so that it's only 200 pixels wide", "Uh-oh, our image is too big to fit on a mobile phone. As a result, our user will need to scroll horizontally to view the image. But we can fix this by specifying an image size.", @@ -1348,11 +1326,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9bedf08813", + "_id": "bad87fee1348bd9bedf08813", "name": "Add a Border Around an Element", - "difficulty" : "1.20", + "difficulty": "1.20", "description": [ "Create a class called \"thick-green-border\" that puts a 5-pixel-wide green border around your cat photo.", "CSS Borders have attributes like style, color and width.", @@ -1386,11 +1363,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08814", + "_id": "bad87fee1348bd9aedf08814", "name": "Add Rounded Corners with a Border Radius", - "difficulty" : "1.21", + "difficulty": "1.21", "description": [ "Give your cat photo a border-radius of 10 pixels.", "Your cat photo currently has sharp corners. We can round out those corners with a CSS attribute called border-radius.", @@ -1424,11 +1400,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08815", + "_id": "bad87fee1348bd9aedf08815", "name": "Make an Image Circular with a Border Radius", - "difficulty" : "1.22", + "difficulty": "1.22", "description": [ "Give your cat photo a border-radius of 50%.", "In addition to pixels, you can also specify a border-radius of a percentage." @@ -1461,11 +1436,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08816", + "_id": "bad87fee1348bd9aedf08816", "name": "Use an Anchor Tag to Link to an External Page", - "difficulty" : "1.23", + "difficulty": "1.23", "description": [ "Create an anchor tag hyperlink that links to freecodecamp.com", "hyperlinks link your users to other URLs (web addresses).", @@ -1499,11 +1473,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08817", + "_id": "bad87fee1348bd9aedf08817", "name": "Make Named Anchors using the Hash Symbol", - "difficulty" : "1.24", + "difficulty": "1.24", "description": [ "Use the hash symbol(#) to turn the link at the bottom of your website into a named anchor.", "Sometimes you'll want to add an anchor element to your website before you know which URL its href will link to.", @@ -1536,11 +1509,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08820", + "_id": "bad87fee1348bd9aedf08820", "name": "Turn an Image into a Link", - "difficulty" : "1.25", + "difficulty": "1.25", "description": [ "Wrap the gray cat's image with an anchor tag that leads nowhere.", "You can make elements into links by wrapping them in an anchor tag.", @@ -1570,11 +1542,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08818", + "_id": "bad87fee1348bd9aedf08818", "name": "Add Alt Text to an image", - "difficulty" : "1.26", + "difficulty": "1.26", "description": [ "Add the alt text \"a photo of a cute gray cat\" to our cat photo", "alt text is what browsers will display if they fail to load the image. alt text also helps your blind or visually impaired users understand what your image portrays. Search engines also look at alt text.", @@ -1603,11 +1574,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad88fee1348bd9aedf08825", + "_id": "bad88fee1348bd9aedf08825", "name": "Adjusting the Padding of an Element", - "difficulty" : "1.27", + "difficulty": "1.27", "description": [ "Change the padding of the green box to match that of the red box.", "An element's padding controls the amount of space between an element and its border.", @@ -1655,11 +1625,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08822", + "_id": "bad87fee1348bd9aedf08822", "name": "Adjust the Margin of an Element", - "difficulty" : "1.28", + "difficulty": "1.28", "description": [ "Change the margin of the green box to match that of the red box.", "An element's margin controls the amount of space between an element's border and surrounding elements.", @@ -1709,11 +1678,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08823", + "_id": "bad87fee1348bd9aedf08823", "name": "Add a Negative Margin to an Element", - "difficulty" : "1.29", + "difficulty": "1.29", "description": [ "Change the margin of the green box to a negative value, so it fills the entire horizontal width of the blue box.", "An element's margin controls the amount of space between an element's border and surrounding elements.", @@ -1762,11 +1730,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08824", + "_id": "bad87fee1348bd9aedf08824", "name": "Add Different Padding to Each Side of an Element TEST", - "difficulty" : "1.30", + "difficulty": "1.30", "description": [ "Give the green box a padding of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", "Sometimes you will want to customize an element so that it has different padding on each of its sides.", @@ -1816,11 +1783,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1248bd9aedf08824", + "_id": "bad87fee1248bd9aedf08824", "name": "Add Different a Margin to Each Side of an Element TEST", - "difficulty" : "1.31", + "difficulty": "1.31", "description": [ "Give the green box a margin of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", "Sometimes you will want to customize an element so that it has a different margin on each of its sides.", @@ -1870,11 +1836,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08826", + "_id": "bad87fee1348bd9aedf08826", "name": "Use Clockwise Notation to Specify an Element's Padding", - "difficulty" : "1.32", + "difficulty": "1.32", "description": [ "Use Clockwise Notation to give an element padding of 40 pixels on its top and left side, but only 20 pixels on its bottom and right side.", "Instead of specifying an element's padding-top, padding-right, padding-bottom, and padding-left attributes, you can specify them all in one line, like this: padding: 10px 20px 10px 20px;.", @@ -1921,11 +1886,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9acde08812", + "_id": "bad87fee1348bd9acde08812", "name": "Use Bootstrap for Responsive Images", - "difficulty" : "1.33", + "difficulty": "1.33", "description": [ "Add the img-responsive Bootstrap class to the image.", "Specifying a width of 200 pixels on our img element made it fit our phone's screen, but it's not a perfect fit. It would be great if the image could be exactly the width of our phone's screen.", @@ -1957,11 +1921,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd8acde08812", + "_id": "bad87fee1348bd8acde08812", "name": "Center Text with Bootstrap", - "difficulty" : "1.34", + "difficulty": "1.34", "description": [ "Add Bootstrap's text-center class to your h2 element.", "Now that we're using Bootstrap, we can center our heading elements (h2) to make them look better. All we need to do is add the class text-center to the h1 and h2 elements.", @@ -1985,11 +1948,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348cd8acde08812", + "_id": "bad87fee1348cd8acde08812", "name": "Create a Button", - "difficulty" : "1.35", + "difficulty": "1.35", "description": [ "Create a button with the text \"Delete\" using the HTML button element.", "HTML has special elements that function like links, but look like buttons. Let's creating a default HTML button." @@ -2014,11 +1976,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348cd8acdf08812", + "_id": "bad87fee1348cd8acdf08812", "name": "Create a Bootstrap Button", - "difficulty" : "1.36", + "difficulty": "1.36", "description": [ "Apply the Bootstrap's btn class to both of your buttons.", "Bootstrap has its own button styles, which look much better than the plain HTML ones." @@ -2044,11 +2005,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348cd8acef08812", + "_id": "bad87fee1348cd8acef08812", "name": "Create a Block Element Bootstrap Button", - "difficulty" : "1.37", + "difficulty": "1.37", "description": [ "Add Bootstrap's btn-block class to both of your buttons.", "Normally, your buttons are only as wide as the text they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space.", @@ -2075,11 +2035,10 @@ ], "challengeType": 0 }, - { - "_id" : "bae87fee1348cd8acef08812", + "_id": "bae87fee1348cd8acef08812", "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "1.38", + "difficulty": "1.38", "description": [ "Add Bootstrap's btn-block class to both of your buttons.", "Normally, your buttons are only as wide as the text they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space.", @@ -2107,11 +2066,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad88fee1348cd8acef08812", + "_id": "bad88fee1348cd8acef08812", "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "1.39", + "difficulty": "1.39", "description": [ "Add Bootstrap's btn-primary class to both of your buttons.", "Bootstrap comes with several pre-defined colors for buttons. The btn-primary class is the main button color you'll use throughout your app.", @@ -2138,11 +2096,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348ce8acef08812", + "_id": "bad87fee1348ce8acef08812", "name": "Warn your Users of a Dangerous Action with the Bootstrap Button Danger Class", - "difficulty" : "1.40", + "difficulty": "1.40", "description": [ "Change the \"Delete\" button from btn-primary to btn-danger.", "Bootstrap comes with several pre-defined colors for buttons. The btn-danger class is the button color you'll use to notify users that the button performs a destructive action, such as deleting a cat photo.", @@ -2169,11 +2126,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad88fee1348ce8acef08812", + "_id": "bad88fee1348ce8acef08812", "name": "Use the Bootstrap Grid to Put Two Elements Side By Side", - "difficulty" : "1.41", + "difficulty": "1.41", "description": [ "Put the \"Like\" and \"Delete\" buttons side-by-side by wrapping them in both in a <div class=\"row\"> element and each of them in a <div class=\"row\"> element.", "Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", @@ -2209,11 +2165,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad89fee1348ce8acef08812", + "_id": "bad89fee1348ce8acef08812", "name": "Wrap Side By Side Elements in a Bootstrap Row", - "difficulty" : "1.42", + "difficulty": "1.42", "description": [ "Put the \"Like\" and \"Delete\" buttons side-by-side by wrapping them in both in a <div class=\"row\"> element and each of them in a <div class=\"row\"> element.", "Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", @@ -2249,11 +2204,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08813", + "_id": "bad87fee1348bd9aedf08813", "name": "Add Alt Text to an Image TEST", - "difficulty" : "1.43", + "difficulty": "1.43", "description": [ "Add the alt text \"A picture of a gray cat\" to the image.", "Alt text is a useful way to tell people (and web crawlers like Google) what is pictured in a photo. It's extremely important for helping blind or visually impaired people understand the content of your website.", @@ -2275,11 +2229,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08827", + "_id": "bad87fee1348bd9aedf08827", "name": "Create an Bulleted Unordered List", - "difficulty" : "1.44", + "difficulty": "1.44", "description": [ "", "" @@ -2293,11 +2246,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08828", + "_id": "bad87fee1348bd9aedf08828", "name": "Created a Numbered Ordered List", - "difficulty" : "1.45", + "difficulty": "1.45", "description": [ "", "" @@ -2311,11 +2263,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08829", + "_id": "bad87fee1348bd9aedf08829", "name": "Create a Text Field", - "difficulty" : "1.46", + "difficulty": "1.46", "description": [ "", "" @@ -2329,11 +2280,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08830", + "_id": "bad87fee1348bd9aedf08830", "name": "Use HTML5 to Make a Field Required", - "difficulty" : "1.47", + "difficulty": "1.47", "description": [ "", "" @@ -2347,11 +2297,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08831", + "_id": "bad87fee1348bd9aedf08831", "name": "Use HTML5 to Specify an Input Type", - "difficulty" : "1.49", + "difficulty": "1.49", "description": [ "", "" @@ -2365,11 +2314,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08832", + "_id": "bad87fee1348bd9aedf08832", "name": "Create a Text Area", - "difficulty" : "1.50", + "difficulty": "1.50", "description": [ "", "" @@ -2383,11 +2331,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08834", + "_id": "bad87fee1348bd9aedf08834", "name": "Create a Set of Radio Buttons", - "difficulty" : "1.51", + "difficulty": "1.51", "description": [ "", "" @@ -2401,11 +2348,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08835", + "_id": "bad87fee1348bd9aedf08835", "name": "Create a Set of Checkboxes", - "difficulty" : "1.52", + "difficulty": "1.52", "description": [ "", "" @@ -2419,11 +2365,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08836", + "_id": "bad87fee1348bd9aedf08836", "name": "Create a HTML Form", - "difficulty" : "1.53", + "difficulty": "1.53", "description": [ "", "" @@ -2437,11 +2382,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08841", + "_id": "bad87fee1348bd9aedf08841", "name": "Change the background of element", - "difficulty" : "1.54", + "difficulty": "1.54", "description": [ "", "" @@ -2455,11 +2399,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08842", + "_id": "bad87fee1348bd9aedf08842", "name": "Make an element translucent", - "difficulty" : "1.55", + "difficulty": "1.55", "description": [ "", "" @@ -2473,11 +2416,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08844", + "_id": "bad87fee1348bd9aedf08844", "name": "Add a Drop Shadow", - "difficulty" : "1.56", + "difficulty": "1.56", "description": [ "", "" @@ -2491,11 +2433,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08845", + "_id": "bad87fee1348bd9aedf08845", "name": "Make a Navbar", - "difficulty" : "1.57", + "difficulty": "1.57", "description": [ "", "" @@ -2509,11 +2450,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08847", + "_id": "bad87fee1348bd9aedf08847", "name": "Add a Logo to a Navbar", - "difficulty" : "1.58", + "difficulty": "1.58", "description": [ "", "" @@ -2527,11 +2467,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08848", + "_id": "bad87fee1348bd9aedf08848", "name": "Make a Footer", - "difficulty" : "1.59", + "difficulty": "1.59", "description": [ "", "" @@ -2545,11 +2484,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08849", + "_id": "bad87fee1348bd9aedf08849", "name": "Use Icons as Links", - "difficulty" : "1.60", + "difficulty": "1.60", "description": [ "", "" @@ -2563,11 +2501,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08850", + "_id": "bad87fee1348bd9aedf08850", "name": "Add Hover Effects to Icons", - "difficulty" : "1.61", + "difficulty": "1.61", "description": [ "", "" @@ -2581,11 +2518,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08851", + "_id": "bad87fee1348bd9aedf08851", "name": "Add Depth to a Page with a Well", - "difficulty" : "1.62", + "difficulty": "1.62", "description": [ "", "" @@ -2599,11 +2535,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08852", + "_id": "bad87fee1348bd9aedf08852", "name": "Add an ID to a Button", - "difficulty" : "1.52", + "difficulty": "1.52", "description": [ "", "" @@ -2617,11 +2552,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08853", + "_id": "bad87fee1348bd9aedf08853", "name": "Fire a Modal by Clicking a Button", - "difficulty" : "1.63", + "difficulty": "1.63", "description": [ "", "" @@ -2635,11 +2569,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08854", + "_id": "bad87fee1348bd9aedf08854", "name": "Style a Modal with a Header", - "difficulty" : "1.64", + "difficulty": "1.64", "description": [ "", "" @@ -2653,11 +2586,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08855", + "_id": "bad87fee1348bd9aedf08855", "name": "Style a Modal with a Body", - "difficulty" : "1.65", + "difficulty": "1.65", "description": [ "", "" @@ -2671,11 +2603,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08856", + "_id": "bad87fee1348bd9aedf08856", "name": "Make a Modal Dismissable", - "difficulty" : "1.66", + "difficulty": "1.66", "description": [ "", "" @@ -2689,11 +2620,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08857", + "_id": "bad87fee1348bd9aedf08857", "name": "Create an Accordian Menu", - "difficulty" : "1.67", + "difficulty": "1.67", "description": [ "", "" @@ -2707,11 +2637,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08863", + "_id": "bad87fee1348bd9aedf08863", "name": "Add a Gradient to a Button", - "difficulty" : "1.68", + "difficulty": "1.68", "description": [ "", "" @@ -2725,11 +2654,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08864", + "_id": "bad87fee1348bd9aedf08864", "name": "Adjust the Line Height of Text", - "difficulty" : "1.69", + "difficulty": "1.69", "description": [ "", "" @@ -2743,11 +2671,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08866", + "_id": "bad87fee1348bd9aedf08866", "name": "", - "difficulty" : "1.70", + "difficulty": "1.70", "description": [ "", "" @@ -2761,11 +2688,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08867", + "_id": "bad87fee1348bd9aedf08867", "name": "", - "difficulty" : "1.71", + "difficulty": "1.71", "description": [ "", "" @@ -2779,11 +2705,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08868", + "_id": "bad87fee1348bd9aedf08868", "name": "", - "difficulty" : "1.711", + "difficulty": "1.711", "description": [ "", "" @@ -2797,11 +2722,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08869", + "_id": "bad87fee1348bd9aedf08869", "name": "", - "difficulty" : "1.712", + "difficulty": "1.712", "description": [ "", "" @@ -2815,11 +2739,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08870", + "_id": "bad87fee1348bd9aedf08870", "name": "", - "difficulty" : "1.713", + "difficulty": "1.713", "description": [ "", "" @@ -2833,11 +2756,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08871", + "_id": "bad87fee1348bd9aedf08871", "name": "", - "difficulty" : "1.714", + "difficulty": "1.714", "description": [ "", "" @@ -2851,11 +2773,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08872", + "_id": "bad87fee1348bd9aedf08872", "name": "", - "difficulty" : "1.72", + "difficulty": "1.72", "description": [ "", "" @@ -2869,11 +2790,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08873", + "_id": "bad87fee1348bd9aedf08873", "name": "", - "difficulty" : "1.73", + "difficulty": "1.73", "description": [ "", "" @@ -2887,11 +2807,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08874", + "_id": "bad87fee1348bd9aedf08874", "name": "", - "difficulty" : "1.74", + "difficulty": "1.74", "description": [ "", "" @@ -2905,11 +2824,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08875", + "_id": "bad87fee1348bd9aedf08875", "name": "", - "difficulty" : "1.75", + "difficulty": "1.75", "description": [ "", "" @@ -2923,11 +2841,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08876", + "_id": "bad87fee1348bd9aedf08876", "name": "", - "difficulty" : "1.76", + "difficulty": "1.76", "description": [ "", "" @@ -2941,11 +2858,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08877", + "_id": "bad87fee1348bd9aedf08877", "name": "", - "difficulty" : "1.77", + "difficulty": "1.77", "description": [ "", "" @@ -2959,11 +2875,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08878", + "_id": "bad87fee1348bd9aedf08878", "name": "", - "difficulty" : "1.78", + "difficulty": "1.78", "description": [ "", "" @@ -2977,11 +2892,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08879", + "_id": "bad87fee1348bd9aedf08879", "name": "", - "difficulty" : "1.79", + "difficulty": "1.79", "description": [ "", "" @@ -2995,11 +2909,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08880", + "_id": "bad87fee1348bd9aedf08880", "name": "", - "difficulty" : "1.80", + "difficulty": "1.80", "description": [ "", "" @@ -3013,11 +2926,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08881", + "_id": "bad87fee1348bd9aedf08881", "name": "", - "difficulty" : "1.81", + "difficulty": "1.81", "description": [ "", "" @@ -3031,11 +2943,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08882", + "_id": "bad87fee1348bd9aedf08882", "name": "", - "difficulty" : "1.82", + "difficulty": "1.82", "description": [ "", "" @@ -3049,11 +2960,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08883", + "_id": "bad87fee1348bd9aedf08883", "name": "", - "difficulty" : "1.83", + "difficulty": "1.83", "description": [ "", "" @@ -3067,11 +2977,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08884", + "_id": "bad87fee1348bd9aedf08884", "name": "", - "difficulty" : "1.84", + "difficulty": "1.84", "description": [ "", "" @@ -3085,11 +2994,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08885", + "_id": "bad87fee1348bd9aedf08885", "name": "", - "difficulty" : "1.85", + "difficulty": "1.85", "description": [ "", "" @@ -3103,11 +3011,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08886", + "_id": "bad87fee1348bd9aedf08886", "name": "", - "difficulty" : "1.86", + "difficulty": "1.86", "description": [ "", "" @@ -3121,11 +3028,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08887", + "_id": "bad87fee1348bd9aedf08887", "name": "", - "difficulty" : "1.87", + "difficulty": "1.87", "description": [ "", "" @@ -3139,11 +3045,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08888", + "_id": "bad87fee1348bd9aedf08888", "name": "", - "difficulty" : "1.88", + "difficulty": "1.88", "description": [ "", "" @@ -3157,11 +3062,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08889", + "_id": "bad87fee1348bd9aedf08889", "name": "", - "difficulty" : "1.89", + "difficulty": "1.89", "description": [ "", "" @@ -3175,11 +3079,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08890", + "_id": "bad87fee1348bd9aedf08890", "name": "", - "difficulty" : "1.90", + "difficulty": "1.90", "description": [ "", "" @@ -3193,11 +3096,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08891", + "_id": "bad87fee1348bd9aedf08891", "name": "", - "difficulty" : "1.91", + "difficulty": "1.91", "description": [ "", "" @@ -3211,11 +3113,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08892", + "_id": "bad87fee1348bd9aedf08892", "name": "", - "difficulty" : "1.92", + "difficulty": "1.92", "description": [ "", "" @@ -3229,11 +3130,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08893", + "_id": "bad87fee1348bd9aedf08893", "name": "", - "difficulty" : "1.93", + "difficulty": "1.93", "description": [ "", "" @@ -3247,11 +3147,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08894", + "_id": "bad87fee1348bd9aedf08894", "name": "", - "difficulty" : "1.94", + "difficulty": "1.94", "description": [ "", "" @@ -3265,11 +3164,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08895", + "_id": "bad87fee1348bd9aedf08895", "name": "", - "difficulty" : "1.95", + "difficulty": "1.95", "description": [ "", "" @@ -3283,11 +3181,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08896", + "_id": "bad87fee1348bd9aedf08896", "name": "", - "difficulty" : "1.96", + "difficulty": "1.96", "description": [ "", "" @@ -3301,11 +3198,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08897", + "_id": "bad87fee1348bd9aedf08897", "name": "", - "difficulty" : "1.97", + "difficulty": "1.97", "description": [ "", "" @@ -3319,11 +3215,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08898", + "_id": "bad87fee1348bd9aedf08898", "name": "", - "difficulty" : "1.98", + "difficulty": "1.98", "description": [ "", "" @@ -3337,11 +3232,10 @@ ], "challengeType": 0 }, - { - "_id" : "bad87fee1348bd9aedf08899", + "_id": "bad87fee1348bd9aedf08899", "name": "", - "difficulty" : "1.99", + "difficulty": "1.99", "description": [ "", "" @@ -3393,5 +3287,310 @@ "welcomeToBooleans();" ], "challengeType": 1 + }, + { + "_id": "bd7123c9c442eddfaeb5bdef", + "name": "Define Your Name", + "difficulty": "9.9801", + "description": [ + "Set the value of myName to your name by typing your name in quotes.", + "Currently myName is empty. Type in your name and hit the submit button.", + "Look at the ourName example if you get stuck." + ], + "tests": [ + "expect(myName).to.be.a(\"string\");", + "expect(myName).length.not.to.be(0);" + ], + "challengeSeed": [ + "// ourName = \"Free Code Camp\";", + "myName = \"\";" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c443eddfaeb5bdef", + "name": "Start Using Variables", + "difficulty": "9.9802", + "description": [ + "Now, use the var keyword to create a variable called myName. Set its value to your name.", + "Variables are used to store values.", + "Be sure to use lowercase and uppercase letters properly. JavaScript variables are written in camel case. An example of camel case is: camelCase.", + "Look at the ourName example if you get stuck." + ], + "tests": [ + "expect(myName).to.be.a(\"string\");", + "expect(myName).length.not.to.be(0);" + ], + "challengeSeed": [ + "// var ourName = \"Free Code Camp\";", + "" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c444eddfaeb5bdef", + "name": "Define Your First and Last Name", + "difficulty": "9.9803", + "description": [ + "Now, use the var keyword to create a variable called myFirstName and set its value to your first name. Then set a variable called myLastName to your last name.", + "Variables are used to store values.", + "Be sure to use lowercase and uppercase letters properly. JavaScript variables are written in lower camel case. An example of lower camel case is: lowerCamelCase.", + "Look at the ourFirstName and ourLastName examples if you get stuck." + ], + "tests": [ + "expect(myFirstName).to.be.a(\"string\");", + "expect(myFirstName).length.not.to.be(0);", + "expect(myLastName).to.be.a(\"string\");", + "expect(myLastName).length.not.to.be(0);" + ], + "challengeSeed": [ + "// var ourFirstName = \"Free\";", + "// var ourLastName = \"Code Camp\";", + "", + "" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c445eddfaeb5bdef", + "name": "Combine Two Strings into One String", + "difficulty": "9.9804", + "description": [ + "Make a variable called myName by adding the string of your first name to the string of your last name.", + "Strings can be combined in a process called concatenation.", + "Be sure to include a space at the end of your first string. Otherwise the two strings will not have a space between them.", + "Be sure to use lowercase and uppercase letters properly. JavaScript variables are written in lower camel case. An example of lower camel case is: lowerCamelCase.", + "Look at the ourName example if you get stuck." + ], + "tests": [ + "expect(myName).to.be.a(\"string\");", + "expect(myName).length.not.to.be(0);", + "expect((/\\s+/).test(myName)).to.be.true;" + ], + "challengeSeed": [ + "// var ourName = \"Free \" + \"Code Camp\";", + "", + "" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c446eddfaeb5bdef", + "name": "Combine Two Variables into One Variable", + "difficulty": "9.9805", + "description": [ + "Make the variables myFirstName, myLastName, and myFullName. Concatenate my myFirstName to myLastName to create myFullName.", + "Strings can be combined in a process called concatenation.", + "Be sure to include a space at the end of myFirstName. Otherwise myFullName will not contain a space between your first and last names.", + "Be sure to use lowercase and uppercase letters properly. JavaScript variables are written in lower camel case. An example of lower camel case is: lowerCamelCase.", + "Look at the ourFullName example if you get stuck." + ], + "tests": [ + "expect(myFirstName).to.be.a(\"string\");", + "expect(myLastName).to.be.a(\"string\");", + "expect(myFullName).to.be.a(\"string\");", + "expect(myFullName).length.not.to.be(0);", + "expect((/\\s+/).test(myFullName)).to.be.true;" + ], + "challengeSeed": [ + "// var ourFirstName = \"Free \";", + "// var ourLastName = \"Code Camp\";", + "// var ourFullName = ourFirstName + ourLastName;", + "" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c447eddfaeb5bdef", + "name": "Concatenate Both Variables and Strings into the Same Variable", + "difficulty": "9.9806", + "description": [ + "Make the variables myFirstName, myLastName, and myFullName. Concatenate my myFirstName to myLastName to create myFullName, but this time add the space as a separate string, not as part of myFirstName or myLastName.", + "Strings can be combined in a process called concatenation.", + "Be sure to use lowercase and uppercase letters properly. JavaScript variables are written in lower camel case. An example of lower camel case is: lowerCamelCase.", + "Look at the ourFullName example if you get stuck." + ], + "tests": [ + "expect(myFirstName).to.be.a(\"string\");", + "expect(myLastName).to.be.a(\"string\");", + "expect(myFullName).to.be.a(\"string\");", + "expect(myFullName).length.not.to.be(0);", + "expect((/\\s+/).test(myFullName)).to.be.true;" + ], + "challengeSeed": [ + "// var ourFirstName = \"Free\";", + "// var ourLastName = \"Code Camp\";", + "// var ourFullName = ourFirstName + \" \" + ourLastName;", + "" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c448eddfaeb5bdef", + "name": "Check the Length Property of a String Variable", + "difficulty": "9.9809", + "description": [ + "Use the .length property to count the number of characters in the lastNameLength variable.", + "For example, if we created a variable var firstName = \"Julie\", we could find out how long the string \"Julie\" is by using the firstName.length property." + ], + "tests": [ + "expect(lastNameLength).to.equal(4);" + ], + "challengeSeed": [ + "var firstName = \"Madeline\";", + "", + "var firstNameLength = firstName.length;", + "", + "var lastName = \"Chen\";", + "", + "var lastNameLength = lastName;", + "", + "", + "", + "// You can ignore this.", + "// We use this to show you the value of your variable in your output box.", + "// We'll learn about functions soon.", + "function returnValue(lastNameLength) {", + " return lastNameLength;", + "}", + "returnValue(lastNameLength);" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c549eddfaeb5bdef", + "name": "Use Bracket Notation to Find the First Character in a String", + "difficulty": "9.9810", + "description": [ + "Use bracket notation to find the first character in a the firstLetterOfLastName variable.", + "Bracket notation is a way to get a character at a specific index within a string.", + "Computers don't start counting at 1 like humans do. They start at 0.", + "For example, the character at index 0 in the word \"Julie\" is \"J\". So if var firstName = \"Julie\", you can get the value of the first letter of the string by using firstName[0].", + "Try looking at the firstLetterOfFirstName variable declaration if you get stuck." + ], + "tests": [ + "expect(firstLetterOfLastName).to.equal('C');" + ], + "challengeSeed": [ + "var firstName = \"Madeline\";", + "", + "var firstLetterOfFirstName = firstName[0];", + "", + "var lastName = \"Chen\";", + "", + "var firstLetterOfLastName = lastName;", + "", + "", + "// You can ignore this.", + "// We use this to show you the value of your variable in your output box.", + "// We'll learn about functions soon.", + "function returnValue(firstLetterOfLastName) {", + " return firstLetterOfLastName;", + "}", + "returnValue(firstLetterOfLastName);" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c450eddfaeb5bdef", + "name": "Use Bracket Notation to Find the Nth Character in a String", + "difficulty": "9.9811", + "description": [ + "Use bracket notation to find the 3rd character in the lastName variable.", + "Bracket notation is a way to get a character at a specific index within a string.", + "Computers don't start counting at 1 like humans do. They start at 0.", + "For example, the character at index 0 in the word \"Julie\" is \"J\". So if var firstName = \"Julie\", you can get the value of the first letter of the string by using firstName[0].", + "Try looking at the secondLetterOfFirstName variable declaration if you get stuck." + ], + "tests": [ + "expect(thirdLetterOfLastName).to.equal('e');" + ], + "challengeSeed": [ + "var firstName = \"Madeline\";", + "", + "var secondLetterOfFirstName = firstName[1];", + "", + "var lastName = \"Chen\";", + "", + "var thirdLetterOfLastName = lastName;", + "", + "", + "// You can ignore this.", + "// We use this to show you the value of your variable in your output box.", + "// We'll learn about functions soon.", + "function returnValue(thirdLetterOfLastName) {", + " return thirdLetterOfLastName;", + "}", + "returnValue(thirdLetterOfLastName);" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c451eddfaeb5bdef", + "name": "Use Bracket Notation to Find the Last Character in a String", + "difficulty": "9.9812", + "description": [ + "Use bracket notation to find the last character in the lastName variable.", + "For example, the character at index 0 in the word \"Julie\" is \"J\". So if var firstName = \"Julie\", you can get the value of the first letter of the string by using firstName[0].", + "In order to get the last letter of a string, you can subtract one from the string's length.", + "For example, if var firstName = \"Julie\", you can get the value of the last letter of the string by using firstName[firstName.length - 1].", + "Try looking at the lastLetterOfLastName variable declaration if you get stuck." + ], + "tests": [ + "expect(lastLetterOfLastName).to.equal('n');" + ], + "challengeSeed": [ + "var firstName = \"Madeline\";", + "", + "var lastLetterOfFirstName = firstName[firstName.length - 1];", + "", + "var lastName = \"Chen\";", + "", + "var lastLetterOfLastName = lastName;", + "", + "", + "// You can ignore this.", + "// We use this to show you the value of your variable in your output box.", + "// We'll learn about functions soon.", + "function returnValue(lastLetterOfLastName) {", + " return lastLetterOfLastName;", + "}", + "returnValue(lastLetterOfLastName);" + ], + "challengeType": 1 + }, + { + "_id": "bd7123c9c452eddfaeb5bdef", + "name": "Use Bracket Notation to Find the Nth to Last Character in a String", + "difficulty": "9.9813", + "description": [ + "Use bracket notation to find the second-to-last character in the lastName variable.", + "For example, the character at index 0 in the word \"Julie\" is \"J\". So if var firstName = \"Julie\", you can get the value of the first letter of the string by using firstName[0].", + "In order to get the last letter of a string, you can subtract one from the string's length.", + "For example, if var firstName = \"Julie\", you can get the value of the third-to-last letter of the string by using firstName[firstName.length - 3].", + "Try looking at the lastLetterOfLastName variable declaration if you get stuck." + ], + "tests": [ + "expect(secondToLastLetterOfLastName).to.equal('e');" + ], + "challengeSeed": [ + "var firstName = \"Madeline\";", + "", + "var thirdToLastLetterOfFirstName = firstName[firstName.length - 2];", + "", + "var lastName = \"Chen\";", + "", + "var secondToLastLetterOfLastName = lastName;", + "", + "", + "// You can ignore this.", + "// We use this to show you the value of your variable in your output box.", + "// We'll learn about functions soon.", + "function returnValue(secondToLastLetterOfLastName) {", + " return secondToLastLetterOfLastName;", + "}", + "returnValue(secondToLastLetterOfLastName);" + ], + "challengeType": 1 } -] +] \ No newline at end of file From b0099efe6eb739d81126f640d8f5a985cc94a9a4 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 14:19:04 -0700 Subject: [PATCH 50/57] updates to javascript waypoints --- seed_data/coursewares.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index ac2226e5c2..5c92aa249c 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -3592,5 +3592,26 @@ "returnValue(secondToLastLetterOfLastName);" ], "challengeType": 1 + }, + { + "_id": "bd7123c9c453eddfaeb5bdef", + "name": "Add two numbers", + "difficulty": "9.9814", + "description": [ + "Set the variable sum2 to equal 5 plus 3.", + "For example, the character at index 0 in the word \"Julie\" is \"J\". So if var firstName = \"Julie\", you can get the value of the first letter of the string by using firstName[0].", + "In order to get the last letter of a string, you can subtract one from the string's length.", + "For example, if var firstName = \"Julie\", you can get the value of the third-to-last letter of the string by using firstName[firstName.length - 3].", + "Try looking at the lastLetterOfLastName variable declaration if you get stuck." + ], + "tests": [ + "expect(sum2).to.equal(8);" + ], + "challengeSeed": [ + "var sum1 = 3 + 3;", + "", + "var sum2 = 0;" + ], + "challengeType": 1 } ] \ No newline at end of file From 81ce249dfccd01bc7c651dc868372b6e041c2f71 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 14:56:13 -0700 Subject: [PATCH 51/57] tiny fix for a bonfire --- seed_data/bonfires.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index cb0c3585cf..19d1bf5e80 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -50,7 +50,7 @@ "Factorials are often represented with the shorthand notation n!", "For example: 5! = 1 * 2 * 3 * 4 * 5 = 120f" ], - "challengeSeed": "function factorialize(num) {\n return num;\r\n}\n\nfactorialize(5);", + "challengeSeed": "function factorialize(num) {\n return num;\r\n}\n\nfactorialize(5);", "MDNlinks" : ["Arithmetic Operators"] }, { @@ -253,7 +253,7 @@ "assert.deepEqual(where([{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], 'should return with multiples');" ], "MDNlinks" : ["Global Object", "Object.hasOwnProperty()", "Object.keys()"] - }, + }, { "_id":"a39963a4c10bc8b4d4f06d7e", "name":"Seek and Destroy", From 44ffc715de4215873e880e4ab6789afc6a6cf1c9 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 15:29:14 -0700 Subject: [PATCH 52/57] add nonprofit.json seed data from scrapped nonprofit branch --- seed_data/nonprofits.json | 294 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 seed_data/nonprofits.json diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json new file mode 100644 index 0000000000..b22ffbfd1e --- /dev/null +++ b/seed_data/nonprofits.json @@ -0,0 +1,294 @@ +[ + { + "id": "bd7157d8c441cbafaeb5bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Website", + "Donor Managment System", + "Inventory Management System", + "Volunteer Management System", + "Forms" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "We help the many less-fortunate Jewish families in our community, by providing them with nutritious food and energy to grow, learn, work, and give them hope for a better and brighter future.", + "websiteLink": "http://chasdeikaduri.org/", + "stakeholderName": "Jonathan Tebeka", + "stakeholderEmail": "jonathan@chasdeikaduri.org", + "name": "Chasdei Kaduri", + "endUser": "Clients, donors, and admin.", + "approvedDeliverables": [ + "Website, Donor Management System, Inventory Management System, Volunteer Management System, Forms" + ], + "projectDescription": "Campers will create a system will integrate the food inventory, donor and delivery driver management systems as well as replace the current application system with a custom form solution. System will include a more streamlined operations management, with user printable lists of inventory, drivers, and deliveries.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c7e02f2c173c37015b2f36/604x309/00580a0567a4b3afda29d52b09e7e829/rQQ6zwq31Uya8ie9QHC-MlvfXxqftm9UPPe524JUhmwSEaZjQ7oL7U1tVoHLUj-gVUwM-7uzBGFsAXD_A_cx_JyAZP4Td-GMBJ-AebJNRAQP0m0v253eKMkURp63aG4%3Ds0-d-e1-ft.png", + "imageUrl": "http://chasdeikaduri.org/images/523455_516325865106850_1885515210_n.jpg", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "200" + }, + { + "id": "bd7158d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Other" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "We connect simple technology with last mile communities to reduce poverty.", + "websiteLink": "http://kopernik.info/", + "stakeholderName": "Amber Gregory", + "stakeholderEmail": "amber.gregory@kopernik.info", + "name": "Kopernik", + "endUser": "Women in rural Indonesia.", + "approvedDeliverables": [ + "Other" + ], + "projectDescription": "Campers will create a Chrome browser extension to preserve sales data from a form, and upload in batches as the internet connection allows.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d29f1e4c726fd765fa87ef/54d29f6388812dd367a243ab/x/018d9d3be5439870c56cccba5b3aa8bf/kopernik-logo-global.png", + "imageUrl": "http://kopernik.info/sites/default/files/updates/Presenting_the_low_carbon_t.jpg", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "100" + }, + { + "id": "bd6274d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Other" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "No More Craptions seeks to improve upon automatic captioning on YouTube videos, by allowing users to edit captions to videos, even if they do not own the content.", + "websiteLink": "http://nomorecraptions.com/", + "stakeholderName": "Michael Lockrey", + "stakeholderEmail": "michael.lockrey@gmail.com", + "name": "No More Craptions", + "endUser": "Hearing impaired users, and those who want to correct poor captions.", + "approvedDeliverables": [ + "Other" + ], + "projectDescription": "Campers will create a full stack JavaScript solution to allow users to edit captions from YouTube videos.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1da1ba1fe2e325df5fc34/54c1dd31226b8111794d132f/x/b0402135d9ecce6d4ab45c4b5e5aeaa0/Turning-online-horse-manure-into-strawberry-jam---since-2009.png", + "imageUrl": "", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "200" + }, + { + "id": "bd1326d9c245cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Website" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "We distribute biodegradable toothbrushes globally to children in need.", + "websiteLink": "http://www.operationbrush.org/", + "stakeholderName": "Dane Jonas", + "stakeholderEmail": "DaneJonas@operationbrush.org", + "name": "Operation Brush", + "endUser": "Donors", + "approvedDeliverables": [ + "Website" + ], + "projectDescription": "Campers will create a mobile responsive website for the organization, with donation capabilities.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d9810307b159a4d9027aa2/54d981bfe5eb145560fbb769/x/cf7f318bfe4aee631b0d0eeef272225c/logo.png", + "imageUrl": "http://www.operationbrush.org/images/temp/hands1.png", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "100" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Community Management Tool" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "We are the largest roller derby league in the world with around 250 adults and 150 junior skater members plus 500+ volunteers.", + "websiteLink": "http://www.rosecityrollers.com/about/our-charities/", + "stakeholderName": "Charity Kuahiwinui", + "stakeholderEmail": "insurance@rosecityrollers.com", + "name": "Rose City Rollers", + "endUser": "Administrators, Coaches, and Volunteers", + "approvedDeliverables": [ + "Community Management Tool" + ], + "projectDescription": "Campers will create a volunteer management system with multi-user access and reporting capabilities.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1daf2d72d8eb868910b60/54c1dd4ecffcb09fc52b68a1/x/a8148f08769b449217e433bab8f39ddd/RCR-color.jpg", + "imageUrl": "http://www.rosecityrollers.com/wp-content/uploads/2015/01/BZ7_5923-X3-675x375.jpg", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "200" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Website" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "Save a Child's Heart provides urgently needed pediatric heart surgery and follow-up care for indigent children from developing countries", + "websiteLink": "http://www.saveachildsheart.com/global/young-leadership-program/", + "stakeholderName": "Shier Ziser", + "stakeholderEmail": "Shier_z@hotmail.com", + "name": "Save a Child's Heart", + "endUser": "Donors", + "approvedDeliverables": [ + "Website" + ], + "projectDescription": "Campers will create a single page fundraising website. In exchange for a donation, a user can customize a graphical 'heart' in someone's name or anonymously. The page will display all of the hearts on a 'wall of hearts.'", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/548b36629137780091a973cc/666x666/6c7a366ffb659649f6377d4a431687cd/country-logos-1-300dpi.jpg", + "imageUrl": "http://www.saveachildsheart.com/wp-content/uploads/2013/10/7.2.5_Internation_Photograohy_Exhibition.jpg", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "100" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Website" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "Savvy Cyber Kids enables youth to be empowered with technology by providing age appropriate resources and education.", + "websiteLink": "http://savvycyberkids.org/", + "stakeholderName": "Ben Halpert", + "stakeholderEmail": "info@savvycyberkids.org ", + "name": "Savvy Cyber Kids", + "endUser": "Donors", + "approvedDeliverables": [ + "Website" + ], + "projectDescription": "Campers will create a website where potential donors can view which schools already have the Savvy Cyber Kids books, and donate books to those schools that do not.", + "logoUrl": "https://trello-attachments.s3.amazonaws.com/54ee3c7bf205562680177b59/218x190/1dc460de4edc9fdd4b481b24e93cfb23/logo.png", + "imageUrl": "http://www.privatewifi.com/wp-content/uploads/2014/10/Halpert.jpg", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "200" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Other" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "Transcendent Pathways ", + "websiteLink": "http://transcendentpathways.org/", + "stakeholderName": "Mark Ackerley", + "stakeholderEmail": "mackerley.music@gmail.com", + "name": "Transcendent Pathways", + "endUser": "Medical Facilities, Musicians", + "approvedDeliverables": [ + "Other" + ], + "projectDescription": "Campers will build a website where medical facilities can list music therapy time slots, and musicians can sign up to fill these slots.", + "logoUrl": "http://static1.squarespace.com/static/521b8957e4b024f66a58b214/t/521b8e9de4b093a8696eb9b8/1398718364447/?format=750w", + "imageUrl": "https://trello-attachments.s3.amazonaws.com/54fdb0328917ca64e9e8a79f/54fdc3b710f67caf6da14719/x/49fbe0012179bf254928f3f2a44810b4/Screen_2BShot_2B2013-08-26_2Bat_2B1.32.35_2BPM.png", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "200" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [ + "Other" + ], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "Timeraiser is a volunteer matching fair, a silent art auction, and a night out on the town. The big Timeraiser twist is rather than bid money on artwork, participants bid volunteer hours. ", + "websiteLink": "http://www.timeraiser.ca/", + "stakeholderName": "Stephanie McAllister", + "stakeholderEmail": "stephanie@timeraiser.ca", + "name": "Timeraiser", + "endUser": "Eventgoers", + "approvedDeliverables": [ + "Other" + ], + "projectDescription": "Campers will build a mobile responsive web form to allow Timeraiser eventgoers to select which nonprofit organizations they're interested in volunteering with. System will have Salesforce integration and reporting capabilities.", + "logoUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/1277176.png?480", + "imageUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/______________4571248_orig.png", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "100" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "string", + "websiteLink": "string", + "stakeholderName": "string", + "stakeholderEmail": "string", + "name": "string", + "endUser": "string", + "approvedDeliverables": [], + "projectDescription": "string", + "logoUrl": "string", + "imageUrl": "string", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "string" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "string", + "websiteLink": "string", + "stakeholderName": "string", + "stakeholderEmail": "string", + "name": "string", + "endUser": "string", + "approvedDeliverables": [], + "projectDescription": "string", + "logoUrl": "string", + "imageUrl": "string", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "string" + }, + { + "id": "bd1325d8c464cbafaeb4bdef", + "registeredNonprofit": true, + "requestedDeliverables": [], + "existingUserbase": true, + "acceptJavascript": true, + "agreeToTerms": true, + "whatDoesNonprofitDo": "string", + "websiteLink": "string", + "stakeholderName": "string", + "stakeholderEmail": "string", + "name": "string", + "endUser": "string", + "approvedDeliverables": [], + "projectDescription": "string", + "logoUrl": "string", + "imageUrl": "string", + "interestedCampers": [], + "confirmedCampers": [], + "estimatedHours": "string" + } +] From f0ae47dcf2e11aaf225f808d8106bae0331a764e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 16:47:49 -0700 Subject: [PATCH 53/57] Fix show all bonfire bug where it wouldn't show while unauthenticated; add sr-only class to completed bonfires --- controllers/bonfire.js | 11 +++++++---- views/partials/bonfires.jade | 14 +++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 5a4e7904c3..b2d72d2e00 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -12,10 +12,13 @@ var _ = require('lodash'), */ exports.showAllBonfires = function(req, res) { - var completedBonfires = req.user.completedBonfires.map(function(elem) { - return elem._id; - }); - + if(req.user) { + var completedBonfires = req.user.completedBonfires.map(function (elem) { + return elem._id; + }); + } else { + completedBonfires = []; + } var noDuplicateBonfires = R.uniq(completedBonfires); var data = {}; data.bonfireList = resources.allBonfireNames(); diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 2c219e089b..3958fd1905 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -11,14 +11,18 @@ h3 }) .success( function(data) { + var docfrag = document.createDocumentFragment(); for (var i = 0; i < data.bonfireList.length; i++) { - var li = document.createElement('li'); + var li = document.createElement("li"); var linkedName = getLinkedName(data.bonfireList[i].name); - if (R.contains(data.bonfireList[i]._id, data.completedList)) { + if (data.completedList.length > 0 && R.contains(data.bonfireList[i]._id, data.completedList)) { + $(li).html("completed" + data.bonfireList[i].name + ""); $(li).addClass('strikethrough'); + } else { + $(li).html("" + data.bonfireList[i].name + ""); } - $(li).html("" + data.bonfireList[i].name + ""); - $(li).appendTo($('#bonfireList')); - } + docfrag.appendChild(li); + }; + $('#bonfireList').append(docfrag); }); From aa01a896a50846ca9eecedea92f4400ffc3f7314 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 17:09:12 -0700 Subject: [PATCH 54/57] Add show all challenges button to all challenge views and refactor bonfire to use same modal and button ids --- controllers/courseware.js | 1 + public/css/main.less | 4 ++++ public/js/main.js | 2 +- views/bonfire/show.jade | 2 +- views/coursewares/showHTML.jade | 11 ++++++++++- views/coursewares/showJS.jade | 8 ++++++++ views/coursewares/showVideo.jade | 15 ++++++++++++--- views/partials/challenges.jade | 32 ++++++++++++++++++++++++++------ views/partials/coursewares.jade | 23 ----------------------- 9 files changed, 63 insertions(+), 35 deletions(-) delete mode 100644 views/partials/coursewares.jade diff --git a/controllers/courseware.js b/controllers/courseware.js index 82987ea7a7..b218997959 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -11,6 +11,7 @@ var _ = require('lodash'), */ exports.showAllCoursewares = function(req, res) { + console.log('i made it!'); var completedCoursewares = req.user.completedCoursewares.map(function(elem) { return elem._id; }); diff --git a/public/css/main.less b/public/css/main.less index ad76627718..4ed3704882 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -836,6 +836,10 @@ iframe.iphone { max-height: 110px; } +.button-spacer { + padding: 3px 0 2px 0; +} + .spacer { padding: 15px 0 15px 0; } diff --git a/public/js/main.js b/public/js/main.js index 9e57f48afb..de082f167a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -179,7 +179,7 @@ $(document).ready(function() { }); $('#showAllButton').on('click', function() { - $('#all-bonfires-dialog').modal('show'); + $('#all-challenges-dialog').modal('show'); }); $('.next-challenge-button').on('click', function() { diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index f72ef0dc4e..99ee61ba12 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -139,7 +139,7 @@ block content - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - #all-bonfires-dialog.modal(tabindex='-1') + #all-challenges-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation .modal-content .modal-header.all-list-header Bonfires diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 7ad3530f9b..e4ac72909e 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -39,7 +39,7 @@ block content | Less information br - if (user) - a.btn.btn-primary.btn-lg.btn-block#next-courseware-button + a.btn.btn-primary.btn-big.btn-block#next-courseware-button | Go to my next challenge br | (ctrl + enter) @@ -49,6 +49,8 @@ block content a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. var userLoggedIn = false; + .button-spacer + #showAllButton.btn.btn-info.btn-big.btn-block Show all challenges br ul#testSuite.list-group br @@ -83,3 +85,10 @@ block content span.completion-icon.ion-checkmark-circled.text-primary a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress script(src="/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js") + #all-challenges-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Challenges + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/challenges diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index 3e33ee4a4b..3d54d61c42 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -35,6 +35,7 @@ block content span.ion-arrow-up-b | Less information #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) + #showAllButton.btn.btn-info.btn-big.btn-block Show all challenges br form.code .form-group.codeMirrorView @@ -74,3 +75,10 @@ block content = phrase - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + #all-challenges-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Challenges + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/challenges diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index b6264c395a..9f35202412 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -13,15 +13,17 @@ block content iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') br - if (user) - a.btn.btn-primary.btn-lg.btn-block#completed-courseware I've completed this challenge (ctrl + enter) + a.btn.btn-primary.btn-big.btn-block#completed-courseware I've completed this challenge (ctrl + enter) script. var userLoggedIn = true; - else - a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress + a.btn.btn-big.signup-btn.btn-block(href='/login') Sign in so you can save your progress script. var userLoggedIn = false; br - script(type="text/javascript"). + .button-spacer + #showAllButton.btn.btn-info.btn-big.btn-block Show all challenges + script(type="text/javascript"). var tests = !{JSON.stringify(tests)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; @@ -52,3 +54,10 @@ block content $('#complete-courseware-dialog').modal('show'); } }); + #all-challenges-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Challenges + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/challenges diff --git a/views/partials/challenges.jade b/views/partials/challenges.jade index 0c959ff122..bea0d0a786 100644 --- a/views/partials/challenges.jade +++ b/views/partials/challenges.jade @@ -1,8 +1,28 @@ h3 - ol(start='0') - for challenge in challenges - li - a(href="/challenges/#{challenge.challengeNumber}", class="#{ (cc && cc[challenge.challengeNumber] > 0) ? 'strikethrough' : '' }") #{challenge.name} - |   (#{challenge.time} mins) + ol#coursewareList + script(src='/js/lib/ramda/ramda.min.js') + script. + var getLinkedName = function getLinkedName(name) { + return name.toLowerCase().replace(/\s/g, '-'); + } + $.ajax({ + url: '/challenges/getCoursewareList', + type: 'GET' + }) + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.coursewareList.length; i++) { + var li = document.createElement("li"); + var linkedName = getLinkedName(data.coursewareList[i].name); + if (data.completedList.length > 0 && R.contains(data.coursewareList[i]._id, data.completedList)) { + $(li).html("completed" + data.coursewareList[i].name + ""); + $(li).addClass('strikethrough'); + } else { + $(li).html("" + data.coursewareList[i].name + ""); + } -a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((points && points < 54) || (!points)) ? 'disabled' : '' }") I'm done with all the challenges! \ No newline at end of file + docfrag.appendChild(li); + }; + $('#coursewareList').append(docfrag); + }); diff --git a/views/partials/coursewares.jade b/views/partials/coursewares.jade deleted file mode 100644 index 8218b82c7a..0000000000 --- a/views/partials/coursewares.jade +++ /dev/null @@ -1,23 +0,0 @@ -h3 - ol#coursewareList - script. - var getLinkedName = function getLinkedName(name) { - return name.toLowerCase().replace(/\s/g, '-'); - } - $.ajax({ - url: '/coursewares/getCoursewareList', - type: 'GET' - }) - .success( - function(data) { - for (var i = 0; i < data.coursewareList.length; i++) { - var li = document.createElement('li'); - var linkedName = getLinkedName(data.coursewareList[i].name); - if (R.contains(data.coursewareList[i]._id, data.completedList)) { - $(li).addClass('strikethrough'); - } - $(li).html("" + data.coursewareList[i].name + ""); - $(li).appendTo($('#coursewareList')); - - } - }); From 97c978dc7c364de215955b11b3a94a50aface996 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 29 Mar 2015 18:13:04 -0700 Subject: [PATCH 55/57] Make Basejumps show up properly and get their github routes persisting in the database. --- controllers/courseware.js | 21 ++++++++++++++++---- seed_data/coursewares.json | 4 ++-- views/coursewares/showZiplineOrBasejump.jade | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 82987ea7a7..ec22472bed 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -151,7 +151,22 @@ exports.returnIndividualCourseware = function(req, res, next) { coursewareHash: courseware._id, challengeType: courseware.challengeType }); - } + }, + + 4: function() { + res.render('coursewares/showZiplineOrBasejump', { + title: courseware.name, + dashedName: dashedName, + name: courseware.name, + details: courseware.description, + video: courseware.challengeSeed[0], + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewareHash: courseware._id, + challengeType: courseware.challengeType + }); + } }; return challengeType[courseware.challengeType](); @@ -262,7 +277,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { var isCompletedDate = Math.round(+new Date()); var coursewareHash = req.body.coursewareInfo.coursewareHash; var solutionLink = req.body.coursewareInfo.publicURL; - var githubLink = req.body.coursewareInfo.challengeType === 4 + var githubLink = req.body.coursewareInfo.challengeType === '4' ? req.body.coursewareInfo.githubURL : true; if (!solutionLink || !githubLink) { req.flash('errors', { @@ -285,8 +300,6 @@ exports.completedZiplineOrBasejump = function (req, res, next) { } pairedWith = pairedWith.pop(); - - req.user.completedCoursewares.push({ _id: coursewareHash, completedWith: pairedWith._id, diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 5c92aa249c..6364c3f6a4 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -789,7 +789,7 @@ "Once it has loaded, install all of MEAN.js's packages by clicking into Cloud 9's terminal at the bottom and running npm install.", "Once that command has finished, set up MongoDB and start it by copying and pasting this code into Cloud 9's terminal: mkdir data && echo 'mongod --bind_ip=$IP --dbpath=data --nojournal --rest \"$@\"' > mongod && chmod a+x mongod && ./mongod. You don't worry if you don't understand what that means. From now on, you'll just need to run this command to start MongoDB: ./mongod." ], - "challengeType": 3, + "challengeType": 4, "tests": [] }, { @@ -3614,4 +3614,4 @@ ], "challengeType": 1 } -] \ No newline at end of file +] diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade index d547ab3019..ed54ecb721 100644 --- a/views/coursewares/showZiplineOrBasejump.jade +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -57,7 +57,7 @@ block content if (user) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - + - if (user.progressTimestamps.length > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") i.fa.fa-twitter   From 64c4bfd566c3047c05656cf60c6f67cdda6051a5 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 12:55:31 -0700 Subject: [PATCH 56/57] remove a leftover console.log --- controllers/courseware.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index b218997959..2fa3465633 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -11,8 +11,7 @@ var _ = require('lodash'), */ exports.showAllCoursewares = function(req, res) { - console.log('i made it!'); - var completedCoursewares = req.user.completedCoursewares.map(function(elem) { + var completedCoursewares = req.user.completedCoursewares.map(function(elem) { return elem._id; }); From 83a9223dcc95c81fe3ec6ce2cf061522f3f4fbb2 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 13:09:24 -0700 Subject: [PATCH 57/57] respond to @BerkeleyTrue feedback --- controllers/bonfire.js | 5 ++--- controllers/courseware.js | 9 ++++++--- views/partials/bonfires.jade | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index b2d72d2e00..a753b55177 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -12,12 +12,11 @@ var _ = require('lodash'), */ exports.showAllBonfires = function(req, res) { + var completedBonfires = []; if(req.user) { - var completedBonfires = req.user.completedBonfires.map(function (elem) { + completedBonfires = req.user.completedBonfires.map(function (elem) { return elem._id; }); - } else { - completedBonfires = []; } var noDuplicateBonfires = R.uniq(completedBonfires); var data = {}; diff --git a/controllers/courseware.js b/controllers/courseware.js index 2fa3465633..87165dc9c3 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -11,9 +11,12 @@ var _ = require('lodash'), */ exports.showAllCoursewares = function(req, res) { - var completedCoursewares = req.user.completedCoursewares.map(function(elem) { - return elem._id; - }); + var completedList = []; + if(req.user) { + completedList = req.user.completedList.map(function (elem) { + return elem._id; + }); + } var noDuplicatedCoursewares = R.uniq(completedCoursewares); var data = {}; diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 3958fd1905..ffb5fec419 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -3,6 +3,8 @@ h3 script(src='/js/lib/ramda/ramda.min.js') script. var getLinkedName = function getLinkedName(name) { + // ensure name is a string + name = name + ''; return name.toLowerCase().replace(/\s/g, '-'); } $.ajax({