From 2e8c6461e62c4e43757b4904ca673f5c84ce0c1f Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 19 Feb 2015 17:18:44 -0800 Subject: [PATCH 001/125] Start work on D3 cal-heatmap --- app.js | 7 ++++++- views/account/show.jade | 26 ++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app.js b/app.js index dff8270448..8d3d8703e9 100644 --- a/app.js +++ b/app.js @@ -139,11 +139,16 @@ var trusted = [ '*.ionicframework.com', 'https://syndication.twitter.com', '*.youtube.com', + '*.jsdelivr.net' ]; app.use(helmet.contentSecurityPolicy({ defaultSrc: trusted, - scriptSrc: ['*.optimizely.com', '*.aspnetcdn.com'].concat(trusted), + scriptSrc: [ + '*.optimizely.com', + '*.aspnetcdn.com', + '*.d3js.org', + ].concat(trusted), 'connect-src': [ 'ws://*.rafflecopter.com', 'wss://*.rafflecopter.com', diff --git a/views/account/show.jade b/views/account/show.jade index 8194c4b107..a9f4e5e5af 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -98,15 +98,17 @@ block content | Try it out - if (ch[0] > 0) - .col-sm-12 - table.table.table-striped - thead - tr - th Challenge - th Date Finished - for challenge in challenges - if ch[challenge.challengeNumber] > 0 - tr - td= challenges[challenge.challengeNumber].name - td= moment(ch[challenge.challengeNumber], 'X').format("MMM DD, YYYY") - br \ No newline at end of file + html. + + + + + .col-xs-12 + p= ch + .embed-responsive.embed-responsive-16by9 + #cal-heatmap + script. + var cal = new CalHeatMap(); + cal.init({ + "domain": "day" + }); \ No newline at end of file From 80485a9ad61fc973d613fa1c14a3321c59198a9b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 20 Feb 2015 22:32:39 -0800 Subject: [PATCH 002/125] one in 100 page loads will show dinosaur in the free code camp graphic --- views/resources/learn-to-code.jade | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 22e1ff5bbc..cb09a48796 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -1,6 +1,9 @@ extends ../layout-wide block content - img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') + if (Math.random() > 0.98) + img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner-dino.png') + else + img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') br .text-center a.btn.btn-cta.signup-btn.next-challenge-button(href="/challenges") Take me to my next challenge From 74f6fc6763958d297b9a92424d17b1a841c9e66e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 20 Feb 2015 23:55:19 -0800 Subject: [PATCH 003/125] continue work trying to get d3 to work --- controllers/user.js | 25 ++++++++++++++++++++++++- views/account/show.jade | 19 ++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index c7947d53a9..b5e73e9472 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -152,6 +152,15 @@ exports.postEmailSignup = function(req, res, next) { }); }; +/** + * For Calendar display + */ + +exports.getStreak = function(req, res) { + var completedStreak = req.user.challengesHash; + +} + /** * GET /account * Profile page. @@ -226,6 +235,20 @@ exports.returnUser = function(req, res, next) { if (user[0]) { var user = user[0]; Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { + var calendarData = Object.keys(user.challengesHash).map(function(key){return user.challengesHash[key]}); + calendarData.sort(function(a , b) { + return a - b; + }); + calendarData = calendarData.filter(function(elem) { + return elem !== 0; + }); + var data = {}; + calendarData.pop(); + for (i = 0; i < calendarData.length; i++) { + var timestamp = 'timestamp' + i; + data[timestamp] = calendarData[i]; + }; + data = {timestamp0: new Date(2015, 3, 15)} res.render('account/show', { title: 'Camper: ', username: user.profile.username, @@ -248,7 +271,7 @@ exports.returnUser = function(req, res, next) { website3Title: user.portfolio.website3Title, website3Image: user.portfolio.website3Image, challenges: c, - ch: user.challengesHash, + calender: data, moment: moment }); }); diff --git a/views/account/show.jade b/views/account/show.jade index a9f4e5e5af..096c0d2569 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -97,18 +97,31 @@ block content i.fa.icon-beaker | Try it out - - if (ch[0] > 0) html. .col-xs-12 - p= ch .embed-responsive.embed-responsive-16by9 #cal-heatmap script. var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + console.log(calendar); cal.init({ - "domain": "day" + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "day", + data: calendar, + dataType: "json", + range: 10, + start: new Date(2015, 0), + itemName: "", + legend: [5, 10, 15, 20, 25, 30], + legendColors: { + empty: "#ededed", + min: "#40ffd8", + max: "#f20013" + } }); \ No newline at end of file From 53bbe90290fb8706b93ee908f6aaad5d6dc74982 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Feb 2015 21:45:25 -0800 Subject: [PATCH 004/125] find a son format that works with d3 --- controllers/user.js | 16 +++------------- views/account/show.jade | 10 +++------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index b5e73e9472..7bce16c40c 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -235,20 +235,10 @@ exports.returnUser = function(req, res, next) { if (user[0]) { var user = user[0]; Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { - var calendarData = Object.keys(user.challengesHash).map(function(key){return user.challengesHash[key]}); - calendarData.sort(function(a , b) { - return a - b; - }); - calendarData = calendarData.filter(function(elem) { - return elem !== 0; - }); - var data = {}; - calendarData.pop(); - for (i = 0; i < calendarData.length; i++) { - var timestamp = 'timestamp' + i; - data[timestamp] = calendarData[i]; + data = { + "1424569155": 1, + "1424519155": 1 }; - data = {timestamp0: new Date(2015, 3, 15)} res.render('account/show', { title: 'Camper: ', username: user.profile.username, diff --git a/views/account/show.jade b/views/account/show.jade index 096c0d2569..c763656311 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -1,5 +1,7 @@ extends ../layout block content + script. + var challengeName = 'Profile View'; .col-xs-12.col-sm-12.col-md-12 .panel.panel-info .panel-heading.text-center @@ -114,14 +116,8 @@ block content domain: "month", subDomain: "day", data: calendar, - dataType: "json", range: 10, start: new Date(2015, 0), itemName: "", - legend: [5, 10, 15, 20, 25, 30], - legendColors: { - empty: "#ededed", - min: "#40ffd8", - max: "#f20013" - } + legend: [5, 10, 15, 20, 25, 30] }); \ No newline at end of file From 150226f4cf50497111828afd5eaf31ea0ab42694 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Feb 2015 22:50:46 -0800 Subject: [PATCH 005/125] add all old challenges to our coursewares.json file after normalization --- seed_data/challenge-hashes | 201 ------- seed_data/challenges.json | 653 ----------------------- seed_data/coursewares.json | 1010 ++++++++++++++++++++++++++++++------ 3 files changed, 859 insertions(+), 1005 deletions(-) delete mode 100644 seed_data/challenge-hashes delete mode 100644 seed_data/challenges.json diff --git a/seed_data/challenge-hashes b/seed_data/challenge-hashes deleted file mode 100644 index 64932960ca..0000000000 --- a/seed_data/challenge-hashes +++ /dev/null @@ -1,201 +0,0 @@ -/* - - - -"aceca143b92049a4392a859e" -"ce9394f67d413734758e27e4" -"1369953ef6f03098cb60e2f7" -"fa229986db0716662e63163a" -"0a6207dfc9ac12f223b4e686" -"2e538b339cbcb7437e61d71f" -"21de1104b8ea7c6a382442d9" -"0aa3497514cb1d8a0ed04933" -"3a45068415f5a20a5d4a2039" -"6a73ec9b86f0e742f6c6ea76" -"06dc21940748badcdb29561e" -"0f2341ef36740b5c2b9d830c" -"dc4cc7ce1fd0418859b10e0b" -"1fcda9b5e0bf502c8b770374" -"482879c42188cde1f9ad3d8f" -"3d680bd03fd291028af57f4f" -"6b444fe5aa97359238bd3c0d" -"72d6ad274e72ffcd399e05cb" -"e864a710647bb6883c9ec617" -"024dcd8cc0178de7daa4f59f" -"ded24fb937a3fd60dc624677" -"9ad4c3b2aebe7282e973e5df" -"d16c66ecc656c82899a2d427" -"ee3e4bf39b840609dd88f5af" -"9a10d27eb6ab6f5030bdbcd9" -"b4cca5ecdeca1eebc0dc4183" -"ccdf1ef407864df62b03331e" -"1baa0329f02e41a5517b11a6" -"b5b33e5ecf6c02a5780ac419" -"84b690f53cd6cbbbf4a361f5" -"4e3b1b9d41ff11bdf3722ffe" -"1fd45e64c43cd640abcf8a76" -"c8bd32cdd943c88fb8a74934" -"9e5440269d09b71f07002333" -"5080016b8b68f40f7476c341" -"f762d45cc870d9f18c6b7024" -"cf21358ab1245c7033504b42" -"41eff7cb0f2fd8ec9e0cbf68" -"a344c63de6a00feea5c541e0" -"7ab3c18c0815d7b1f02e8768" -"a6f36e4387e2e1e25af4e000" -"9064af1431c1ff3aaa15a0d6" -"50946066aaba43a294155ab5" -"70e82e77857317d6936ecdcc" -"dc644dc20d5b02ffd7c8cc94" -"92af1e9f1813619d820dec4d" -"9d4a077fdaaa24b78a54cee9" -"cfd5fa7aea14fb294bd4f5ff" -"068541b6c5130ca541d6c05c" -"39f4d1f12142d583b0318265" -"4a201b8fa69d11cb669dca8c" -"6fa6bba2e334dd2c9e5a6911" -"30ff082b6a28558f16743fea" -"afed31c4dc2ddf056e263acb" -"b2431ecf36f0b780959aa03d" -"54e42938ab6fa923a5fbfbff" -"48d50e3ff5e96f152d912ebf" -"6c7aad2ee996132f76630c05" -"2705f0482774977fa9b5c7bc" -"c09d6682c7a144d9afd4bfab" -"1a0a30c55dd9bc2fff4e2aac" -"30c22b502e80afd144d79120" -"8f882989bbc98c68b6c7347e" -"888bf771b63a5f64e6b696ed" -"959aa13a2e6439fd5d29c5fe" -"d03d311ca64bf59a2404dcd0" -"7a650f3e4308a097d7883b1f" -"54c207808ac349fa34ed24a9" -"3cb12e3ea31fe15ba9d234e5" -"0d9f8123e52f791ae172f627" -"387518b1bb96bd918e7d0b91" -"27198d8918c9802ac0b06a58" -"7a519a552f723c6b1fdc9389" -"4240096651a50f888765ed60" -"8ba94f78cc44cfb82b3678d2" -"1cf7c3bda6ec7952923bc90f" -"cd54811e976edbc798a87c24" -"e4ee17912f5d7d80d41dc52d" -"572346dd1874a0c749c3a031" -"cf700031502680580ff25980" -"7054518832e5c7efde04a58c" -"7716c6b6a3dcc9c5df79a262" -"f4d1084c692f5a07c25a1b47" -"817b80892a78fc6f83eab4eb" -"ae0e0adda6d063d3fd2c9499" -"33184c427ecaab77b5332521" -"c6c4da707e3eed987eb65ba6" -"40f9801913c0cb9800960bf7" -"af11cf78e8340f172ba76bbe" -"c1daea28b5996b8e8800e300" -"7312b3234b5f2f50887597b9" -"3e4361c5356b30f8ce833268" -"e6fc8827b9aa96fc91035ac2" -"11658fe51b23319533c72106" -"34207fe236870fd855392ef6" -"dc970a2b17aa1432d180d078" -"2f1686ad711b5ac3d98905b5" -"ed20f4bf782e10ec26c7b3b1" -"cf4d6241df36b0bbcea401e2" -"97e414eb00338bd7bd5d1b31" -"bcfebb743729d4fc51b0db49" -"52782c1eaa15e050ac748b18" -"57f65a6cd2eb0a2745fc90ee" -"fcc569e80808c6cd84c839cc" -"2447f8e0e3ccfd895a619695" -"600687fe6332b2e2b20d6a84" -"0f8a972361baddb3ac64b6d9" -"f768594ab352cf8e2282b615" -"3163b25e25b0d6859df17751" -"c73d68f0cf77aa113337fcde" -"cc16163137f13f70decbcb5c" -"4adbf6c7ac2004ee8ae20f55" -"c2779f78a0e57dcee471cefd" -"bf44903cd2fca4ae4daf0818" -"ca8013372d58365e21bd0428" -"e308901ca272ad447dca965a" -"ec8cefe29832cd726185119f" -"36bf51da3f32b1e9550f3b15" -"aefff6dffdf534a6c9601f70" -"ce237971a71127f9ce747232" -"99f4d97fae16ccbeb8c26d46" -"c9f6480328ec4b63b1034826" -"ce10bb6e80b09533cb21c9a5" -"311a54aaf062b29422ac0b02" -"b2073bec873b295c04e9fb7e" -"7b7d94035d63c9c0598b242b" -"e19e9a0487f1bd3eabdb777c" -"eeafe5169a2afd9ce3979698" -"96c7a1f9fb73468fdb770034" -"77e607be2498160d3ceeff60" -"c784fa6b1fd7d9aedb05b607" -"a76319bca87e0b57bc79064f" -"a08db36785c51e5b92a6e92f" -"0933ab4a366d57fd1f1c0a48" -"17663f17a835cfe0bca64f4a" -"aee8323d1de634fc42c11d90" -"682044caf09a068e26c11cab" -"1346fdbe3c6fcb808b99cdf6" -"26fd8f47fe4791e2dd07b2e0" -"b1da27dd129216c2ead2e162" -"618adebe192e896eb1501708" -"2628d9eeb38db68c0b255a75" -"1fb78eda5deb8972cc357a9b" -"2fdebb85a401de75bb580a02" -"c59eae24bee36f63c938723d" -"fb66b55d5617e27ca64836a3" -"55994022872e01fd41d8ec7b" -"53ae83b5490e1cf55625ff3d" -"c44975deeb67b96e6cc37eb5" -"94c64d5ff48ca6487f5e0b07" -"e34e7c1a3d70678955a5df8e" -"6426d30a923dec6377132c47" -"d67811849354ded9d34c9ece" -"01e7ae81207cfd230cb02747" -"e24558477f94bcdd82f16ebe" -"4370f6dcc2f53bd5abed7899" -"679b1145cd3072fbcd513bcb" -"4dafd3fd84e7a337ef3e9933" -"2aca3d41cb148603b4a2d70b" -"5e5aa8c960d024109543c577" -"c5f5f313c8a7056bbcdb7639" -"a7b2aaa4cf85fce98cdeb314" -"e556c0d14bc0ce9cc7bde0ca" -"922a8284880a7c879d846f90" -"b146cf2fbf206ca48687e9b6" -"fbdc52bb7c7620bd4508e5a3" -"23d79a262a81459e75576cc2" -"a26721706d3b9f971cef3ce6" -"fb3801ed1b056a3ab4bbfdc1" -"d5cafae617f1485826de335c" -"e9650dfc8b570f9d33381012" -"453c804c7ce82e83305cd751" -"3eaab01bdd1393be8d461777" -"2b6d9ddd4754b71d92ed31b7" -"2338e9af6acab9fcf5327f6a" -"588e7405c30c346ed2ea5e44" -"eec8837994054fb2043b6166" -"0877406795956ca9eb13fea1" -"1dea30e852067eec48a24035" -"ee716ce45ea812167ed2497a" -"9621d5aeaf8de9b2f58977bd" -"e9bb838723083bac7f83d602" -"ef64da08bbe787891d47c31b" -"e01b6616cc6cf7819172f7f3" -"17c821c34fca587daec981fc" -"d0c4b7153dcb3e6af47bcd98" -"5188eb00680051c35cd6e0f0" -"9fc7b68e736a1f4931226ad8" -"b5eac897471f125224a3e594" -"c1d0f890a02c85a9320f96d5" -"7e3b1ca1b1b1e990367f0cc2" -"bff5ff77174c43f7c681e0bd" -"ddeb794121937d07a887c9e6" -"9db902e930f25fbacc125087" -"4f82d277b47ae9c98095f068" -"24f999491fa46cd194c7fd35" -*/ \ No newline at end of file diff --git a/seed_data/challenges.json b/seed_data/challenges.json deleted file mode 100644 index 7286158d48..0000000000 --- a/seed_data/challenges.json +++ /dev/null @@ -1,653 +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", - "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": [ - "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." - ] - }, - { - "name": "Try Node.js Events", - "time": 45, - "video": "114684206", - "challengeNumber": 37, - "steps": [ - "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": [ - "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": [ - "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": [ - "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": [ - "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": [ - "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": [ - "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." - ] - }, - { - - "name": "Setup Express.js Middleware", - "time": 45, - "video": "114684535", - "challengeNumber": 44, - "steps": [ - "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": [ - "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": [ - "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": [ - "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/coursewares.json b/seed_data/coursewares.json index 716f24acb2..94e1f3560b 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -1,9 +1,760 @@ [ + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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."] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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."] + }, + { + "_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."] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd8124d8c441eddfaeb5bdef", + "name": "Learn Loops", + "difficulty": 0.20, + "challengeSeed": "114597348", + "description" : [ + "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", + "This will introduce us to loops, a fundamental feature of every programming language.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ and complete Week 2." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd8125d8c441eddfaeb5bdef", + "name": "Learn Computer Hardware", + "difficulty": 0.21, + "challengeSeed": "114597347", + "description" : [ + "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", + "This challenge will also give you an understanding of how bits and bytes work.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ and complete Week 3." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd8126d8c441eddfaeb5bdef", + "name": "Learn Computer Networking", + "difficulty": 0.22, + "challengeSeed": "114604811", + "description" : [ + "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", + "Particularly important, you will learn about networks and TCP/IP - the protocol that powers the internet.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ and complete Week 4." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd8127d8c441eddfaeb5bdef", + "name": "Learn Boolean Logic", + "difficulty": 0.23, + "challengeSeed": "114604812", + "description" : [ + "Now we'll do some more table exercises and learn boolean logic.", + "We'll also learn the difference between digital data and analog data.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ and complete Week 5." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd8128d8c441eddfaeb5bdef", + "name": "Learn Computer Security", + "difficulty": 0.24, + "challengeSeed": "114604813", + "description" : [ + "We're almost done with Stanford's Introduction to Computer Science course!", + "We'll learn about one of the most important inventions of the 20th century - spreadsheets.", + "We'll also learn about Computer Security and some of the more common vulnerabilities software systems have.", + "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z229/z213/ and complete Week 6, the final week of the course." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7129d8c441eddfaeb5bdef", + "name": "Build an Adventure Game", + "difficulty": 0.25, + "challengeSeed": "114604814", + "description" : [ + "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", + "We're going to work through Codecademy's famous interactive JavaScript course.", + "This course will teach us some JavaScript fundamentals while guiding us through the process of building interesting web apps, all within Codecademy's learner-friendly environment!", + "Go to http://www.codecademy.com/courses/getting-started-v2/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-x9DnD/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7130d8c441eddfaeb5bdef", + "name": "Build Rock Paper Scissors", + "difficulty": 0.26, + "challengeSeed": "114604815", + "description" : [ + "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-6LzGd/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-Bthev-mskY8/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7131d8c441eddfaeb5bdef", + "name": "Learn JavaScript For Loops", + "difficulty": 0.27, + "challengeSeed": "114614220", + "description" : [ + "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-NhsaT/0/1web and complete both the both For and While loop section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-XEDZA/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7132d8c441eddfaeb5bdef", + "name": "Learn JavaScript While Loops", + "difficulty": 0.28, + "challengeSeed": "114612889", + "description" : [ + "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-mrTNH-6VIZ9/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7133d8c441eddfaeb5bdef", + "name": "Learn Control Flow", + "difficulty": 0.29, + "challengeSeed": "114612888", + "description" : [ + "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", + "We'll also learn some advanced \"Control Flow\" principals, such as ways we can exit loops early.", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-qDwp0/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-ZA2rb/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7134d8c441eddfaeb5bdef", + "name": "Build a Contact List", + "difficulty": 0.30, + "challengeSeed": "114612887", + "description" : [ + "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", + "Go to http://www.codecademy.com/courses/javascript-beginner-en-9Sgpi/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/javascript-beginner-en-3bmfN/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7135d8c441eddfaeb5bdef", + "name": "Build an Address Book", + "difficulty": 0.31, + "challengeSeed": "114612885", + "description" : [ + "Let's learn more about objects.", + "Go to http://www.codecademy.com/courses/spencer-sandbox/0/1 and complete the section.", + "Be sure to also complete this section: http://www.codecademy.com/courses/building-an-address-book/0/1?curriculum_id=506324b3a7dffd00020bf661." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7136d8c441eddfaeb5bdef", + "name": "Build a Cash Register", + "difficulty": 0.32, + "challengeSeed": "114612882", + "description" : [ + "In this final Codecademy section, we'll learn even more about JavaScript objects.", + "Go to http://www.codecademy.com/courses/objects-ii/0/1 and complete this section.", + "Be sure to also complete the final section: http://www.codecademy.com/courses/close-the-super-makert/0/1." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7139d8c441eddfaeb5bdef", + "name": "Pair Program on Bonfires", + "difficulty": 0.35, + "challengeSeed": "119657641", + "description" : [ + "OK, we're finally ready to start pair programming!", + "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", + "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", + "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", + "We have a special chat room for people ready to pair program. Go to 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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7141d8c441eddfaeb5bdef", + "name": "Get Started with Node.js", + "difficulty": 0.37, + "challengeSeed": "114686471", + "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", + "name": "Try Node.js Events", + "difficulty": 0.38, + "challengeSeed": "114684206", + "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.", + "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/2/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7143d8c441eddfaeb5bdef", + "name": "Try Node.js Streams", + "difficulty": 0.39, + "challengeSeed": "114684209", + "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", + "name": "Learn how Node.js Modules Work", + "difficulty": 0.40, + "challengeSeed": "114684213", + "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.", + "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/4/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7145d8c441eddfaeb5bdef", + "name": "Start an Express.js Server", + "difficulty": 0.41, + "challengeSeed": "114684247", + "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", + "name": "Use Socket.io", + "difficulty": 0.42, + "challengeSeed": "114684530", + "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", + "name": "Use Redis to Persist Data", + "difficulty": 0.43, + "challengeSeed": "114684532", + "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", + "name": "Dive Deeper into Express.js", + "difficulty": 0.44, + "challengeSeed": "114684533", + "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", + "name": "Setup Express.js Middleware", + "difficulty": 0.45, + "challengeSeed": "114684535", + "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", + "name": "Take Advantage of Parameters", + "difficulty": 0.46, + "challengeSeed": "114684537", + "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", + "name": "Add the Body Parser", + "difficulty": 0.47, + "challengeSeed": "114684720", + "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", + "name": "Configure Routes in Express.js", + "difficulty": 0.48, + "challengeSeed": "114684724", + "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", + "name": "Try MongoDB", + "difficulty": 0.49, + "challengeSeed": "114685061", + "description" : [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7154d8c441eddfaeb5bdef", + "name": "Get Started with Angular.js", + "difficulty": 0.50, + "challengeSeed": "114684726", + "description" : [ + "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", + "In this course, we'll build a virtual shop entirely in Angular.js.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/1/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7155d8c441eddfaeb5bdef", + "name": "Apply Angular.js Directives", + "difficulty": 0.51, + "challengeSeed": "114684727", + "description" : [ + "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", + "Let's learn how these powerful directives work, and how to use them to make your web apps more dynamic", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7156d8c441eddfaeb5bdef", + "name": "Power Forms with Angular.js", + "difficulty": 0.52, + "challengeSeed": "114684729", + "description" : [ + "One area where Angular.js really shines is its powerful web forms.", + "Learn how to create reactive Angular.js forms, including real-time form validation.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/3/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7157d8c441eddfaeb5bdef", + "name": "Customize Angular.js Directives", + "difficulty": 0.53, + "challengeSeed": "114685062", + "description" : [ + "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/4/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id" : "bd7158d8c441eddfaeb5bdef", + "name": "Create Angular.js Services", + "difficulty": 0.54, + "challengeSeed": "114685060", + "description" : [ + "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", + "We'll learn how to use services in this final Code School Angular.js challenge.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/5/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [] + }, { "_id" : "bd7123c8c441eddfaeb5bdef", "name": "Start our Challenges", - "difficulty": "0.00", + "difficulty": "1.00", "description": [ "Welcome to Free Code Camp's first challenge! Click on the button below for further instructions.", "Awesome. Now you can read the rest of this challenge's instructions.", @@ -25,7 +776,7 @@ { "_id" : "bad87fee1348bd9aedf0887a", "name": "Use the h2 Element", - "difficulty" : "0.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.", @@ -45,7 +796,7 @@ { "_id" : "bad87fee1348bd9aedf08801", "name": "Use the P Element", - "difficulty" : "0.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.", @@ -64,7 +815,7 @@ { "_id" : "bad87fee1348bd9aeaf08801", "name": "Add a Line Break to Visually Separate Elements", - "difficulty" : "0.03", + "difficulty" : "1.03", "description": [ "Add a line break between the <h2> and <p> elements.", "You can create an line break element with <br/>.", @@ -84,7 +835,7 @@ { "_id" : "bad87fee1348bd9aedf08802", "name": "Uncomment HTML", - "difficulty" : "0.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.", @@ -108,7 +859,7 @@ { "_id" : "bad87fee1348bd9aedf08804", "name": "Comment out HTML", - "difficulty" : "0.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 -->.", @@ -133,7 +884,7 @@ { "_id" : "bad87fee1348bd9aedf08833", "name": "Use Lorem Ipsum Text as a Placeholder", - "difficulty" : "0.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.", @@ -154,7 +905,7 @@ { "_id" : "bad87fee1348bd9aedf08803", "name": "Change the Color of Text", - "difficulty" : "0.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.", @@ -175,7 +926,7 @@ { "_id" : "bad87fee1348bd9aedf08805", "name": "Create a Style Tag for CSS", - "difficulty" : "0.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.", @@ -199,7 +950,7 @@ { "_id" : "bad87fee1348bd9aecf08806", "name": "Use a CSS Class to Style an Element", - "difficulty" : "0.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.", @@ -226,7 +977,7 @@ { "_id" : "bad87fee1348bd9aeff08806", "name": "Use a CSS Class to Style Multiple Elements", - "difficulty" : "0.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." @@ -255,7 +1006,7 @@ { "_id" : "bad87fee1348bd9aedf08806", "name": "Change the Font Size of an Element", - "difficulty" : "0.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.", @@ -282,7 +1033,7 @@ { "_id" : "bad87fee1348bd9aedf08807", "name": "Import a Google Font", - "difficulty" : "0.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.", @@ -314,7 +1065,7 @@ { "_id" : "bad87fee1348bd9aedf08808", "name": "Specify How Fonts Should Degrade", - "difficulty" : "0.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.", @@ -343,7 +1094,7 @@ { "_id" : "bad87fee1348bd9aedf08809", "name": "Using Important to Override Styles", - "difficulty" : "0.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.", @@ -382,7 +1133,7 @@ { "_id" : "bad87fee1348bd9aedf08811", "name": "Use rgb Codes for Precise Colors", - "difficulty" : "0.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.", @@ -412,7 +1163,7 @@ { "_id" : "bad87fee1348bd9aedf08810", "name": "Use Hex Codes for Precise Colors", - "difficulty" : "0.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.", @@ -443,7 +1194,7 @@ { "_id" : "bad87fee1348bd9bedf08810", "name": "Use Shortened 3 Digit Hex Codes", - "difficulty" : "0.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." @@ -467,9 +1218,9 @@ }, { - "_id" : "bad87fee1348bd9aedf08811", + "_id" : "bad88fee1348bd9aedf08811", "name": "Set the Alpha of a Color with rgba", - "difficulty" : "0.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.", @@ -499,7 +1250,7 @@ { "_id" : "bad87fee1348bd9aedf08812", "name": "Add an Image to your Website", - "difficulty" : "0.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.", @@ -526,7 +1277,7 @@ { "_id" : "bad87fee1348bd9acdf08812", "name": "Specify an Image Size", - "difficulty" : "0.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.", @@ -558,7 +1309,7 @@ { "_id" : "bad87fee1348bd9bedf08813", "name": "Add a Border Around an Element", - "difficulty" : "0.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.", @@ -596,7 +1347,7 @@ { "_id" : "bad87fee1348bd9aedf08814", "name": "Add Rounded Corners with a Border Radius", - "difficulty" : "0.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.", @@ -634,7 +1385,7 @@ { "_id" : "bad87fee1348bd9aedf08815", "name": "Make an Image Circular with a Border Radius", - "difficulty" : "0.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." @@ -671,7 +1422,7 @@ { "_id" : "bad87fee1348bd9aedf08816", "name": "Use an Anchor Tag to Link to an External Page", - "difficulty" : "0.23", + "difficulty" : "1.23", "description": [ "Create an anchor tag hyperlink that links to freecodecamp.com", "hyperlinks link your users to other URLs (web addresses).", @@ -709,7 +1460,7 @@ { "_id" : "bad87fee1348bd9aedf08817", "name": "Make Named Anchors using the Hash Symbol", - "difficulty" : "0.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.", @@ -746,7 +1497,7 @@ { "_id" : "bad87fee1348bd9aedf08820", "name": "Turn an Image into a Link", - "difficulty" : "0.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.", @@ -780,7 +1531,7 @@ { "_id" : "bad87fee1348bd9aedf08818", "name": "Add Alt Text to an image", - "difficulty" : "0.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.", @@ -813,7 +1564,7 @@ { "_id" : "bad88fee1348bd9aedf08825", "name": "Adjusting the Padding of an Element", - "difficulty" : "0.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.", @@ -865,7 +1616,7 @@ { "_id" : "bad87fee1348bd9aedf08822", "name": "Adjust the Margin of an Element", - "difficulty" : "0.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.", @@ -919,7 +1670,7 @@ { "_id" : "bad87fee1348bd9aedf08823", "name": "Add a Negative Margin to an Element", - "difficulty" : "0.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.", @@ -972,7 +1723,7 @@ { "_id" : "bad87fee1348bd9aedf08824", "name": "Add Different Padding to Each Side of an Element TEST", - "difficulty" : "0.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.", @@ -1026,7 +1777,7 @@ { "_id" : "bad87fee1248bd9aedf08824", "name": "Add Different a Margin to Each Side of an Element TEST", - "difficulty" : "0.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.", @@ -1080,7 +1831,7 @@ { "_id" : "bad87fee1348bd9aedf08826", "name": "Use Clockwise Notation to Specify an Element's Padding", - "difficulty" : "0.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;.", @@ -1131,7 +1882,7 @@ { "_id" : "bad87fee1348bd9acde08812", "name": "Use Bootstrap for Responsive Images", - "difficulty" : "0.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.", @@ -1167,7 +1918,7 @@ { "_id" : "bad87fee1348bd8acde08812", "name": "Center Text with Bootstrap", - "difficulty" : "0.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.", @@ -1195,7 +1946,7 @@ { "_id" : "bad87fee1348cd8acde08812", "name": "Create a Button", - "difficulty" : "0.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." @@ -1224,7 +1975,7 @@ { "_id" : "bad87fee1348cd8acdf08812", "name": "Create a Bootstrap Button", - "difficulty" : "0.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." @@ -1254,7 +2005,7 @@ { "_id" : "bad87fee1348cd8acef08812", "name": "Create a Block Element Bootstrap Button", - "difficulty" : "0.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.", @@ -1285,7 +2036,7 @@ { "_id" : "bae87fee1348cd8acef08812", "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "0.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.", @@ -1315,9 +2066,9 @@ }, { - "_id" : "bad87fee1348cd8acef08812", + "_id" : "bad88fee1348cd8acef08812", "name": "Color a Bootstrap Button with Button Primary", - "difficulty" : "0.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.", @@ -1348,7 +2099,7 @@ { "_id" : "bad87fee1348ce8acef08812", "name": "Warn your Users of a Dangerous Action with the Bootstrap Button Danger Class", - "difficulty" : "0.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.", @@ -1379,7 +2130,7 @@ { "_id" : "bad88fee1348ce8acef08812", "name": "Use the Bootstrap Grid to Put Two Elements Side By Side", - "difficulty" : "0.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.", @@ -1419,7 +2170,7 @@ { "_id" : "bad89fee1348ce8acef08812", "name": "Wrap Side By Side Elements in a Bootstrap Row", - "difficulty" : "0.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.", @@ -1459,7 +2210,7 @@ { "_id" : "bad87fee1348bd9aedf08813", "name": "Add Alt Text to an Image TEST", - "difficulty" : "0.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.", @@ -1485,7 +2236,7 @@ { "_id" : "bad87fee1348bd9aedf08827", "name": "Create an Bulleted Unordered List", - "difficulty" : "0.44", + "difficulty" : "1.44", "description": [ "", "" @@ -1503,7 +2254,7 @@ { "_id" : "bad87fee1348bd9aedf08828", "name": "Created a Numbered Ordered List", - "difficulty" : "0.45", + "difficulty" : "1.45", "description": [ "", "" @@ -1521,7 +2272,7 @@ { "_id" : "bad87fee1348bd9aedf08829", "name": "Create a Text Field", - "difficulty" : "0.46", + "difficulty" : "1.46", "description": [ "", "" @@ -1539,7 +2290,7 @@ { "_id" : "bad87fee1348bd9aedf08830", "name": "Use HTML5 to Make a Field Required", - "difficulty" : "0.47", + "difficulty" : "1.47", "description": [ "", "" @@ -1557,7 +2308,7 @@ { "_id" : "bad87fee1348bd9aedf08831", "name": "Use HTML5 to Specify an Input Type", - "difficulty" : "0.49", + "difficulty" : "1.49", "description": [ "", "" @@ -1575,7 +2326,7 @@ { "_id" : "bad87fee1348bd9aedf08832", "name": "Create a Text Area", - "difficulty" : "0.50", + "difficulty" : "1.50", "description": [ "", "" @@ -1593,7 +2344,7 @@ { "_id" : "bad87fee1348bd9aedf08834", "name": "Create a Set of Radio Buttons", - "difficulty" : "0.51", + "difficulty" : "1.51", "description": [ "", "" @@ -1611,7 +2362,7 @@ { "_id" : "bad87fee1348bd9aedf08835", "name": "Create a Set of Checkboxes", - "difficulty" : "0.52", + "difficulty" : "1.52", "description": [ "", "" @@ -1629,7 +2380,7 @@ { "_id" : "bad87fee1348bd9aedf08836", "name": "Create a HTML Form", - "difficulty" : "0.53", + "difficulty" : "1.53", "description": [ "", "" @@ -1647,7 +2398,7 @@ { "_id" : "bad87fee1348bd9aedf08841", "name": "Change the background of element", - "difficulty" : "0.54", + "difficulty" : "1.54", "description": [ "", "" @@ -1665,7 +2416,7 @@ { "_id" : "bad87fee1348bd9aedf08842", "name": "Make an element translucent", - "difficulty" : "0.55", + "difficulty" : "1.55", "description": [ "", "" @@ -1683,7 +2434,7 @@ { "_id" : "bad87fee1348bd9aedf08844", "name": "Add a Drop Shadow", - "difficulty" : "0.56", + "difficulty" : "1.56", "description": [ "", "" @@ -1701,7 +2452,7 @@ { "_id" : "bad87fee1348bd9aedf08845", "name": "Make a Navbar", - "difficulty" : "0.57", + "difficulty" : "1.57", "description": [ "", "" @@ -1719,7 +2470,7 @@ { "_id" : "bad87fee1348bd9aedf08847", "name": "Add a Logo to a Navbar", - "difficulty" : "0.58", + "difficulty" : "1.58", "description": [ "", "" @@ -1737,7 +2488,7 @@ { "_id" : "bad87fee1348bd9aedf08848", "name": "Make a Footer", - "difficulty" : "0.59", + "difficulty" : "1.59", "description": [ "", "" @@ -1755,7 +2506,7 @@ { "_id" : "bad87fee1348bd9aedf08849", "name": "Use Icons as Links", - "difficulty" : "0.60", + "difficulty" : "1.60", "description": [ "", "" @@ -1773,7 +2524,7 @@ { "_id" : "bad87fee1348bd9aedf08850", "name": "Add Hover Effects to Icons", - "difficulty" : "0.61", + "difficulty" : "1.61", "description": [ "", "" @@ -1791,7 +2542,7 @@ { "_id" : "bad87fee1348bd9aedf08851", "name": "Add Depth to a Page with a Well", - "difficulty" : "0.62", + "difficulty" : "1.62", "description": [ "", "" @@ -1809,7 +2560,7 @@ { "_id" : "bad87fee1348bd9aedf08852", "name": "Add an ID to a Button", - "difficulty" : "0.52", + "difficulty" : "1.52", "description": [ "", "" @@ -1827,7 +2578,7 @@ { "_id" : "bad87fee1348bd9aedf08853", "name": "Fire a Modal by Clicking a Button", - "difficulty" : "0.63", + "difficulty" : "1.63", "description": [ "", "" @@ -1845,7 +2596,7 @@ { "_id" : "bad87fee1348bd9aedf08854", "name": "Style a Modal with a Header", - "difficulty" : "0.64", + "difficulty" : "1.64", "description": [ "", "" @@ -1863,7 +2614,7 @@ { "_id" : "bad87fee1348bd9aedf08855", "name": "Style a Modal with a Body", - "difficulty" : "0.65", + "difficulty" : "1.65", "description": [ "", "" @@ -1881,7 +2632,7 @@ { "_id" : "bad87fee1348bd9aedf08856", "name": "Make a Modal Dismissable", - "difficulty" : "0.66", + "difficulty" : "1.66", "description": [ "", "" @@ -1899,7 +2650,7 @@ { "_id" : "bad87fee1348bd9aedf08857", "name": "Create an Accordian Menu", - "difficulty" : "0.67", + "difficulty" : "1.67", "description": [ "", "" @@ -1917,7 +2668,7 @@ { "_id" : "bad87fee1348bd9aedf08863", "name": "Add a Gradient to a Button", - "difficulty" : "0.68", + "difficulty" : "1.68", "description": [ "", "" @@ -1935,7 +2686,7 @@ { "_id" : "bad87fee1348bd9aedf08864", "name": "Adjust the Line Height of Text", - "difficulty" : "0.69", + "difficulty" : "1.69", "description": [ "", "" @@ -1953,7 +2704,7 @@ { "_id" : "bad87fee1348bd9aedf08866", "name": "", - "difficulty" : "0.70", + "difficulty" : "1.70", "description": [ "", "" @@ -1971,7 +2722,7 @@ { "_id" : "bad87fee1348bd9aedf08867", "name": "", - "difficulty" : "0.71", + "difficulty" : "1.71", "description": [ "", "" @@ -1989,7 +2740,7 @@ { "_id" : "bad87fee1348bd9aedf08868", "name": "", - "difficulty" : "0.711", + "difficulty" : "1.711", "description": [ "", "" @@ -2007,7 +2758,7 @@ { "_id" : "bad87fee1348bd9aedf08869", "name": "", - "difficulty" : "0.712", + "difficulty" : "1.712", "description": [ "", "" @@ -2025,7 +2776,7 @@ { "_id" : "bad87fee1348bd9aedf08870", "name": "", - "difficulty" : "0.713", + "difficulty" : "1.713", "description": [ "", "" @@ -2043,7 +2794,7 @@ { "_id" : "bad87fee1348bd9aedf08871", "name": "", - "difficulty" : "0.714", + "difficulty" : "1.714", "description": [ "", "" @@ -2061,7 +2812,7 @@ { "_id" : "bad87fee1348bd9aedf08872", "name": "", - "difficulty" : "0.72", + "difficulty" : "1.72", "description": [ "", "" @@ -2079,7 +2830,7 @@ { "_id" : "bad87fee1348bd9aedf08873", "name": "", - "difficulty" : "0.73", + "difficulty" : "1.73", "description": [ "", "" @@ -2097,7 +2848,7 @@ { "_id" : "bad87fee1348bd9aedf08874", "name": "", - "difficulty" : "0.74", + "difficulty" : "1.74", "description": [ "", "" @@ -2115,7 +2866,7 @@ { "_id" : "bad87fee1348bd9aedf08875", "name": "", - "difficulty" : "0.75", + "difficulty" : "1.75", "description": [ "", "" @@ -2133,7 +2884,7 @@ { "_id" : "bad87fee1348bd9aedf08876", "name": "", - "difficulty" : "0.76", + "difficulty" : "1.76", "description": [ "", "" @@ -2151,7 +2902,7 @@ { "_id" : "bad87fee1348bd9aedf08877", "name": "", - "difficulty" : "0.77", + "difficulty" : "1.77", "description": [ "", "" @@ -2169,7 +2920,7 @@ { "_id" : "bad87fee1348bd9aedf08878", "name": "", - "difficulty" : "0.78", + "difficulty" : "1.78", "description": [ "", "" @@ -2187,7 +2938,7 @@ { "_id" : "bad87fee1348bd9aedf08879", "name": "", - "difficulty" : "0.79", + "difficulty" : "1.79", "description": [ "", "" @@ -2205,7 +2956,7 @@ { "_id" : "bad87fee1348bd9aedf08880", "name": "", - "difficulty" : "0.80", + "difficulty" : "1.80", "description": [ "", "" @@ -2223,7 +2974,7 @@ { "_id" : "bad87fee1348bd9aedf08881", "name": "", - "difficulty" : "0.81", + "difficulty" : "1.81", "description": [ "", "" @@ -2241,7 +2992,7 @@ { "_id" : "bad87fee1348bd9aedf08882", "name": "", - "difficulty" : "0.82", + "difficulty" : "1.82", "description": [ "", "" @@ -2259,7 +3010,7 @@ { "_id" : "bad87fee1348bd9aedf08883", "name": "", - "difficulty" : "0.83", + "difficulty" : "1.83", "description": [ "", "" @@ -2277,7 +3028,7 @@ { "_id" : "bad87fee1348bd9aedf08884", "name": "", - "difficulty" : "0.84", + "difficulty" : "1.84", "description": [ "", "" @@ -2295,7 +3046,7 @@ { "_id" : "bad87fee1348bd9aedf08885", "name": "", - "difficulty" : "0.85", + "difficulty" : "1.85", "description": [ "", "" @@ -2313,7 +3064,7 @@ { "_id" : "bad87fee1348bd9aedf08886", "name": "", - "difficulty" : "0.86", + "difficulty" : "1.86", "description": [ "", "" @@ -2331,7 +3082,7 @@ { "_id" : "bad87fee1348bd9aedf08887", "name": "", - "difficulty" : "0.87", + "difficulty" : "1.87", "description": [ "", "" @@ -2349,7 +3100,7 @@ { "_id" : "bad87fee1348bd9aedf08888", "name": "", - "difficulty" : "0.88", + "difficulty" : "1.88", "description": [ "", "" @@ -2367,7 +3118,7 @@ { "_id" : "bad87fee1348bd9aedf08889", "name": "", - "difficulty" : "0.89", + "difficulty" : "1.89", "description": [ "", "" @@ -2385,7 +3136,7 @@ { "_id" : "bad87fee1348bd9aedf08890", "name": "", - "difficulty" : "0.90", + "difficulty" : "1.90", "description": [ "", "" @@ -2403,7 +3154,7 @@ { "_id" : "bad87fee1348bd9aedf08891", "name": "", - "difficulty" : "0.91", + "difficulty" : "1.91", "description": [ "", "" @@ -2421,7 +3172,7 @@ { "_id" : "bad87fee1348bd9aedf08892", "name": "", - "difficulty" : "0.92", + "difficulty" : "1.92", "description": [ "", "" @@ -2439,7 +3190,7 @@ { "_id" : "bad87fee1348bd9aedf08893", "name": "", - "difficulty" : "0.93", + "difficulty" : "1.93", "description": [ "", "" @@ -2457,7 +3208,7 @@ { "_id" : "bad87fee1348bd9aedf08894", "name": "", - "difficulty" : "0.94", + "difficulty" : "1.94", "description": [ "", "" @@ -2475,7 +3226,7 @@ { "_id" : "bad87fee1348bd9aedf08895", "name": "", - "difficulty" : "0.95", + "difficulty" : "1.95", "description": [ "", "" @@ -2493,7 +3244,7 @@ { "_id" : "bad87fee1348bd9aedf08896", "name": "", - "difficulty" : "0.96", + "difficulty" : "1.96", "description": [ "", "" @@ -2511,7 +3262,7 @@ { "_id" : "bad87fee1348bd9aedf08897", "name": "", - "difficulty" : "0.97", + "difficulty" : "1.97", "description": [ "", "" @@ -2529,7 +3280,7 @@ { "_id" : "bad87fee1348bd9aedf08898", "name": "", - "difficulty" : "0.98", + "difficulty" : "1.98", "description": [ "", "" @@ -2547,25 +3298,7 @@ { "_id" : "bad87fee1348bd9aedf08899", "name": "", - "difficulty" : "0.99", - "description": [ - "", - "" - ], - "tests": [ - "expect($('h1')).to.have.class('text-center');", - "expect($('h1')).to.have.text('hello world');" - ], - "challengeSeed": [ - "

hello world

" - ], - "challengeType": 0 - }, - - { - "_id" : "bad87fee1348bd9aedf08100", - "name": "", - "difficulty" : "1.00", + "difficulty" : "1.99", "description": [ "", "" @@ -2580,32 +3313,7 @@ "challengeType": 0 }, { - "_id" : "bd7123d8c441eddfaeb5bdef", - "name": "Learn how Free Code Camp Works", - "difficulty": 9.99, - "description": [ - "Watch this 90 second 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 \"Go to my next challenge\" button to move on to your next challenge." - ], - "tests": [ - "" - ], - "challengeSeed": [ - "114486344" - ], - "challengeType" : 2 - }, - { - "_id": "bd7123c8c441eddfaeb5bdef", + "_id": "bd8123c8c441eddfaeb5bdef", "name": "Meet Booleans", "difficulty": "9.98", "description": [ From a197e99b1e294e0077469d462b360e79206312eb Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 16:27:38 +0900 Subject: [PATCH 006/125] Starting bonfires view partial, ajax call works, need to work into view --- app.js | 4 ++++ controllers/bonfire.js | 14 +++++++++++--- views/partials/bonfires.jade | 25 +++++++++++++++++++------ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index bbc0e6192a..9ed01a4f1e 100644 --- a/app.js +++ b/app.js @@ -301,6 +301,8 @@ app.get('/api/trello', resourcesController.trelloCalls); /** * Bonfire related routes */ +app.get('/bonfires/pooproute', bonfireController.poopRoute); +app.get('/bonfires/getBonfireList', bonfireController.showAllBonfires); app.get('/playground', bonfireController.index); app.get('/bonfires', bonfireController.returnNextBonfire); app.get('/bonfire-json-generator', bonfireController.returnGenerator); @@ -315,6 +317,8 @@ app.get('/bonfire', function(req, res) { res.redirect(301, '/playground'); }); + + app.post('/completed-bonfire/', bonfireController.completedBonfire); /** diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 83431a390d..d18ca1a8bd 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -8,11 +8,19 @@ var _ = require('lodash'), * Bonfire controller */ -exports.bonfireNames = function(req, res) { - res.render('bonfires/showList', { - bonfireList: resources.allBonfireNames() +exports.showAllBonfires = function(req, res) { + var completedBonfires = req.user.completedBonfires.map(function(elem) { + return elem._id; }); + var data = {}; + data.bonfireList = resources.allBonfireNames(); + //data.completedList = completedBonfires; + res.send(data); }; +// FIXME: remove this +exports.poopRoute = function(req, res) { + res.render('partials/bonfires.jade'); +} exports.index = function(req, res) { res.render('bonfire/show.jade', { diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index cabd9ecf93..c856ea9bf6 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -1,7 +1,20 @@ h3 - ol(start='0') - for bonfire in bonfires - li - a(href="/bonfires/#{bonfire.bonfireNumber}", class="#{ (cc && cc[bonfire.bonfireNumber] > 0) ? 'strikethrough' : '' }") #{bonfire.name} - |   (Level #{bonfire.difficulty}) -a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((cc && cc[53] === 0) || (!cc)) ? 'disabled' : '' }") I'm done with all the challenges! \ No newline at end of file + ol#bonfireList + script. + var getLinkedName = function getLinkedName(name) { + return name.toLowerCase().replace(/\s/g, '-'); + } + $.ajax({ + url: '/bonfires/getBonfireList', + type: 'GET' + }) + .success( + function(data) { + for (var i = 0; i < data.bonfireList.length; i++) { + var li = document.createElement('li'); + var linkedName = getLinkedName(data.bonfireList[i]); + $(li) + .html("" + data.bonfireList[i] + ""); + $(li).appendTo($('#bonfireList')); + } + }); From 90caed3369b8925d81b4e3292be08458a678f2e8 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 16:46:55 +0900 Subject: [PATCH 007/125] More UX improvements --- public/css/lib/bootstrap/variables.less | 2 +- public/css/main.less | 11 +++++++++++ public/js/main.js | 10 ++++++++-- views/bonfire/show.jade | 15 ++++++++------- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/public/css/lib/bootstrap/variables.less b/public/css/lib/bootstrap/variables.less index 3de76507c7..01e26f3433 100755 --- a/public/css/lib/bootstrap/variables.less +++ b/public/css/lib/bootstrap/variables.less @@ -16,7 +16,7 @@ @brand-primary: #215f1e; @brand-success: #457E86; -@brand-info: #5bc0de; +@brand-info: #4A2B0F; @brand-warning: #f0ad4e; @brand-danger: #d9534f; diff --git a/public/css/main.less b/public/css/main.less index cf8b467ed8..5a15dc0f8b 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -543,6 +543,17 @@ thead { padding-left: 50px; } +.all-list-header { + background-color: #4A2B0F; + color: #eee; + font-size: 36px; + text-align: center; + margin-bottom: -30px; + border-radius: 5px 5px 0px 0px; + padding-left: 50px; + +} + .closing-x { color: #eee; font-size: 50px; diff --git a/public/js/main.js b/public/js/main.js index 7976c4f6d1..07a805ae9b 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -74,6 +74,11 @@ $(document).ready(function() { editor.focus(); }); + $('#all-bonfires-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { editor.focus(); }); @@ -92,13 +97,14 @@ $(document).ready(function() { } }) } - }) + }); + $('.all-challenges').on('click', function() { $('#all-challenges-dialog').modal('show'); }); - $('.all-bonfires').on('click', function() { + $('#showAllButton').on('click', function() { $('#all-bonfires-dialog').modal('show'); }); diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index bfa7fdbd77..4c1d217b4c 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -79,6 +79,7 @@ block content span.ion-help-circled | Less information #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) + #showAllButton.btn.btn-info.btn-big.btn-block Show all coding challenges br form.code .form-group.codeMirrorView @@ -133,11 +134,11 @@ 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') - // .modal-dialog.animated.fadeInUp.fast-animation - // .modal-content - // .modal-header.challenge-list-header Bonfires - // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - // .modal-body - // include ../partials/bonfires + #all-bonfires-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Bonfires + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/bonfires From b5a449cb1cdf8dc9c1e317f009f6ef2b58d1605a Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 17:36:43 +0900 Subject: [PATCH 008/125] Cross off completed bonfires in list of all bonfires in modal --- app.js | 2 +- controllers/bonfire.js | 11 +++++------ controllers/resources.js | 10 +++++++--- views/partials/bonfires.jade | 9 ++++++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app.js b/app.js index 9ed01a4f1e..530d5b2c01 100644 --- a/app.js +++ b/app.js @@ -301,7 +301,7 @@ app.get('/api/trello', resourcesController.trelloCalls); /** * Bonfire related routes */ -app.get('/bonfires/pooproute', bonfireController.poopRoute); + app.get('/bonfires/getBonfireList', bonfireController.showAllBonfires); app.get('/playground', bonfireController.index); app.get('/bonfires', bonfireController.returnNextBonfire); diff --git a/controllers/bonfire.js b/controllers/bonfire.js index d18ca1a8bd..e36be01ccd 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'), + R = require('ramda'); /** * Bonfire controller @@ -12,15 +13,13 @@ exports.showAllBonfires = function(req, res) { var completedBonfires = req.user.completedBonfires.map(function(elem) { return elem._id; }); + + var noDuplicateBonfires = R.uniq(completedBonfires); var data = {}; data.bonfireList = resources.allBonfireNames(); - //data.completedList = completedBonfires; + data.completedList = noDuplicateBonfires; res.send(data); }; -// FIXME: remove this -exports.poopRoute = function(req, res) { - res.render('partials/bonfires.jade'); -} exports.index = function(req, res) { res.render('bonfire/show.jade', { diff --git a/controllers/resources.js b/controllers/resources.js index 99844a3095..e02680289e 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -206,14 +206,18 @@ module.exports = { return bonfires.map(function(elem) { return { name: elem.name, - difficulty: elem.difficulty + difficulty: elem.difficulty, + _id: elem._id } }) .sort(function(a, b) { return a.difficulty - b.difficulty; }) - .map(function(elem) { - return elem.name; + .map (function(elem) { + return { + name : elem.name, + _id: elem._id + } }); }, diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index c856ea9bf6..3dfb6da030 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -12,9 +12,12 @@ h3 function(data) { for (var i = 0; i < data.bonfireList.length; i++) { var li = document.createElement('li'); - var linkedName = getLinkedName(data.bonfireList[i]); - $(li) - .html("" + data.bonfireList[i] + ""); + var linkedName = getLinkedName(data.bonfireList[i].name); + if (R.contains(data.bonfireList[i]._id, data.completedList)) { + $(li).addClass('strikethrough'); + } + $(li).html("" + data.bonfireList[i].name + ""); $(li).appendTo($('#bonfireList')); + } }); From 3e65298e35873055b17e6fecf7cbe1fc29b3ab1a Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 18:23:50 +0900 Subject: [PATCH 009/125] All coursewares list now displays properly, coursewares properly direct to the follow on, courseware controller now properly finds the next courseware --- app.js | 1 + controllers/courseware.js | 21 +++++++++++++++------ controllers/resources.js | 12 ++++++++---- public/js/main.js | 20 ++++++++++++++------ seed_data/seed.js | 24 +++--------------------- views/coursewares/showHTML.jade | 11 +++++++++++ views/coursewares/showJS.jade | 10 +++++----- views/partials/bonfires.jade | 2 +- views/partials/coursewares.jade | 23 +++++++++++++++++++++++ 9 files changed, 81 insertions(+), 43 deletions(-) create mode 100644 views/partials/coursewares.jade diff --git a/app.js b/app.js index 530d5b2c01..846acd690b 100644 --- a/app.js +++ b/app.js @@ -326,6 +326,7 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); */ app.get('/coursewares/', coursewareController.returnNextCourseware); +app.get('/coursewares/getCoursewareList', coursewareController.showAllCoursewares); app.get( '/coursewares/:coursewareName', coursewareController.returnIndividualCourseware diff --git a/controllers/courseware.js b/controllers/courseware.js index 58e57cef7d..4dcf0e1cb3 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -2,16 +2,23 @@ var _ = require('lodash'), debug = require('debug')('freecc:cntr:courseware'), Courseware = require('./../models/Courseware'), User = require('./../models/User'), - resources = require('./resources'); + resources = require('./resources'), + R = require('ramda'); /** * Courseware controller */ -exports.coursewareNames = function(req, res) { - res.render('coursewares/showList', { - coursewareList: resources.allCoursewareNames() +exports.showAllCoursewares = function(req, res) { + 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); }; exports.returnNextCourseware = function(req, res) { @@ -29,13 +36,15 @@ exports.returnNextCourseware = function(req, res) { }); req.user.save(); - var uncompletedCoursewares = req.user.uncompletedCoursewares; + var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); - var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares[0]}); + + var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); displayedCoursewares.exec(function(err, courseware) { if (err) { next(err); } + debug('This is the courseware object returned from mongo', courseware); courseware = courseware.pop(); if (courseware === undefined) { req.flash('errors', { diff --git a/controllers/resources.js b/controllers/resources.js index e02680289e..b8c7be6a4e 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -239,15 +239,19 @@ module.exports = { return coursewares.map(function(elem) { return { name: elem.name, - difficulty: elem.difficulty + difficulty: elem.difficulty, + _id: elem._id } }) .sort(function(a, b) { return a.difficulty - b.difficulty; }) - .map(function(elem) { - return elem.name; - }); + .map (function(elem) { + return { + name : elem.name, + _id: elem._id + } + }); }, whichEnvironment: function() { return process.env.NODE_ENV; diff --git a/public/js/main.js b/public/js/main.js index 07a805ae9b..d9162a53c9 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -78,6 +78,14 @@ $(document).ready(function() { editor.focus(); }); + $('#showAllCoursewares').on('click', function() { + $('#all-coursewares-dialog').modal('show'); + }); + + $('#all-coursewares-dialog').on('hidden.bs.modal', function() { + editor.focus(); + }); + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { editor.focus(); @@ -90,12 +98,12 @@ $(document).ready(function() { coursewareInfo: { coursewareHash: passedCoursewareHash } - }, - function(res) { - if (res) { - window.location.href = '/coursewares' - } - }) + }).success( + function() { + window.location.href = '/coursewares'; + } + ) + } }); diff --git a/seed_data/seed.js b/seed_data/seed.js index 6549c3cc71..6e2b34794d 100644 --- a/seed_data/seed.js +++ b/seed_data/seed.js @@ -1,17 +1,15 @@ require('dotenv').load(); -var Challenge = require('../models/Challenge.js'), - Bonfire = require('../models/Bonfire.js'), +var Bonfire = require('../models/Bonfire.js'), Courseware = require('../models/Courseware.js'), mongoose = require('mongoose'), secrets = require('../config/secrets'), - challenges = require('./challenges.json'), coursewares = require('./coursewares.json'), bonfires = require('./bonfires.json'); mongoose.connect(secrets.db); var counter = 0; -var offerings = 3; +var offerings = 2; var CompletionMonitor = function() { counter++; @@ -22,24 +20,8 @@ var CompletionMonitor = function() { } else { process.exit(0); } -} +}; -Challenge.remove({}, function(err, data) { - if (err) { - console.error(err); - } else { - console.log('Deleted ', data); - } - Challenge.create(challenges, function(err, data) { - if (err) { - console.log(err); - } else { - console.log('Saved ', data); - } - CompletionMonitor(); - }); - console.log('challenges'); -}); Bonfire.remove({}, function(err, data) { if (err) { diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 676aadf774..b54649a318 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -18,6 +18,8 @@ block content script(src='/js/lib/codemirror/mode/xml/xml.js') script(src='/js/lib/codemirror/mode/css/css.js') script(src='/js/lib/codemirror/mode/htmlmixed/htmlmixed.js') + script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js") + .row.courseware-height .col-xs-12.col-sm-12.col-md-3.col-lg-3 .well @@ -40,6 +42,7 @@ block content span.ion-help-circled | Less information br + .btn.btn-info#showAllCoursewares - if (cc) a.btn.btn-primary.btn-lg.btn-block#next-courseware-button @@ -88,3 +91,11 @@ 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-coursewares-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/coursewares diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index 3570432d41..4e4a364565 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -47,7 +47,7 @@ block content script(type="text/javascript"). var tests = !{JSON.stringify(tests)}; var challengeSeed = !{JSON.stringify(challengeSeed)}; - var passedBonfireHash = !{JSON.stringify(coursewareHash)}; + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; var started = Math.floor(Date.now() / 1000); .col-xs-12.col-sm-12.col-md-8 @@ -67,10 +67,10 @@ block content span.completion-icon.ion-checkmark-circled.text-primary - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") - i.fa.fa-twitter   - = phrase + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{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 \ No newline at end of file diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 3dfb6da030..33fb57c7b9 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -16,7 +16,7 @@ h3 if (R.contains(data.bonfireList[i]._id, data.completedList)) { $(li).addClass('strikethrough'); } - $(li).html("" + data.bonfireList[i].name + ""); + $(li).html("" + data.bonfireList[i].name + ""); $(li).appendTo($('#bonfireList')); } diff --git a/views/partials/coursewares.jade b/views/partials/coursewares.jade new file mode 100644 index 0000000000..8218b82c7a --- /dev/null +++ b/views/partials/coursewares.jade @@ -0,0 +1,23 @@ +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 36cf4196f53671a8b37b16cc56a100d568fe3e75 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Feb 2015 01:27:27 -0800 Subject: [PATCH 010/125] fix some messed up json --- seed_data/coursewares.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 94e1f3560b..7df5cf5a3f 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -65,7 +65,10 @@ "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."] + "Go to https://dash.generalassemb.ly/projects/annas-website-1 and get started with your first project." + ], + "challengeType": 2, + "tests": [] }, { "_id" : "bd7128d8c441eddfaeb5bdef", @@ -89,7 +92,10 @@ "challengeSeed": "114578438", "description" : [ "Ready for some more HTML and CSS fundamentals?", - "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project."] + "Go to https://dash.generalassemb.ly/projects/eshas-restaurant-1 and complete the third project." + ], + "challengeType": 2, + "tests": [] }, { "_id" : "bd7110d8c441eddfaeb5bdef", @@ -116,7 +122,10 @@ "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."] + "Go to https://dash.generalassemb.ly/projects/cotbots-1 and complete the fourth project." + ], + "challengeType": 2, + "tests": [] }, { "_id" : "bd7112d8c441eddfaeb5bdef", @@ -240,7 +249,7 @@ "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": [] }, From ddf96c2ef259f84ae42719be1396dbf3c87749f0 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 19:35:55 +0900 Subject: [PATCH 011/125] Add modal popups to all courseware views --- controllers/courseware.js | 4 ++-- views/coursewares/showHTML.jade | 31 ++++++++++++++++++------------- views/coursewares/showVideo.jade | 10 +++++----- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 4dcf0e1cb3..7be7833b77 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -44,12 +44,12 @@ exports.returnNextCourseware = function(req, res) { if (err) { next(err); } - debug('This is the courseware object returned from mongo', courseware); + courseware = courseware.pop(); if (courseware === undefined) { req.flash('errors', { msg: "It looks like you've completed all the courses we have available. Good job!" - }) + }); return res.redirect('../coursewares/start-our-challenges'); } nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index b54649a318..25e1fbddd9 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -45,7 +45,7 @@ block content .btn.btn-info#showAllCoursewares - if (cc) - a.btn.btn-primary.btn-lg.btn-block#next-courseware-button + a.btn.btn-primary.btn-lg.btn-block#complete-courseware | Go to my next challenge br | (ctrl + enter) @@ -78,19 +78,24 @@ block content img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") iframe.iphone#preview + #complete-courseware-dialog.modal(tabindex='-1') + .modal-dialog.animated.zoomIn.fast-animation + .modal-content + .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) - #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 - .text-center - .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") + 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%20Challenge:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{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 #all-coursewares-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 88099ddc42..764d3b47de 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -13,7 +13,7 @@ block content iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') br - if (cc) - a.btn.btn-primary.btn-lg.btn-block#next-courseware-button Go to my next challenge (ctrl + enter) + a.btn.btn-primary.btn-lg.btn-block#completed-courseware I've completed this challenge (ctrl + enter) script. var userLoggedIn = true; - else @@ -36,7 +36,7 @@ block content .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - if (points && points > 2) a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") i.fa.fa-twitter   @@ -44,6 +44,6 @@ block content - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress h1 #{name} - script. - var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file + script. + var challengeName = !{JSON.stringify(name)}; + var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file From dc4aa40a32e498fa4483c46daea1a1ccdac620ca Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Mon, 23 Feb 2015 07:47:43 +0900 Subject: [PATCH 012/125] Add bitly url shortening example to showHTML view --- app.js | 3 ++- public/js/main.js | 2 +- views/coursewares/showHTML.jade | 23 ++++++++++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 846acd690b..f7bb248ebb 100644 --- a/app.js +++ b/app.js @@ -144,7 +144,8 @@ var trusted = [ 'https://*.togetherjs.com', 'wss://hub.togetherjs.com', '*.ytimg.com', - 'wss://fcctogether.herokuapp.com' + 'wss://fcctogether.herokuapp.com', + '*.bitly.com' ]; app.use(helmet.contentSecurityPolicy({ diff --git a/public/js/main.js b/public/js/main.js index d9162a53c9..c8b4d39d7a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -66,7 +66,7 @@ $(document).ready(function() { completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); }); - $('#completed-courseware').on('click', function() { + $('#complete-courseware').on('click', function() { $('#complete-courseware-dialog').modal('show'); }); diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 25e1fbddd9..cb884bf531 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -68,11 +68,16 @@ block content var challengeName = !{JSON.stringify(name)}; var prodOrDev = !{JSON.stringify(environment)}; var started = Math.floor(Date.now() / 1000); + var url = ''; + var dashed = !{JSON.stringify(dashedName)}; + console.log(dashed); + .col-xs-12.col-sm-12.col-md-5.col-lg-6 #mainEditorPanel form.code .form-group.codeMirrorView textarea#codeEditor(autofocus=true) + script(src='/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js') .col-md-4.col-lg-3 .hidden-xs.hidden-sm img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") @@ -82,8 +87,8 @@ block content .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") + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body .text-center .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary @@ -91,9 +96,10 @@ block content 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%20Challenge:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") + 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 @@ -104,3 +110,14 @@ block content a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body include ../partials/coursewares + script. + $.ajax({ + url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fchallenges%2F' + dashed + '&format=txt' + }) + .success( + function(data) { + console.log(data); + url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; + $('.btn-twitter').attr('href', url); + } + ); From fc1d560a8954eca579dbf6096e2ebc52fdb92caf Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Feb 2015 18:16:26 -0800 Subject: [PATCH 013/125] start refactoring points into progressTimestamps which is essentially points with timestamps --- controllers/bonfire.js | 12 +++++------ controllers/courseware.js | 10 ++++----- controllers/resources.js | 42 ++++++++++++++------------------------ models/User.js | 2 ++ views/account/show.jade | 2 +- views/partials/navbar.jade | 2 +- 6 files changed, 30 insertions(+), 40 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index e36be01ccd..79f3eb4a75 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -32,7 +32,7 @@ exports.index = function(req, res) { tests: [], challengeSeed: '', cc: req.user ? req.user.bonfiresHash : undefined, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliments: resources.randomCompliment(), @@ -112,7 +112,7 @@ exports.returnIndividualBonfire = function(req, res, next) { tests: bonfire.tests, challengeSeed: bonfire.challengeSeed, cc: !!req.user, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -179,7 +179,7 @@ exports.testBonfire = function(req, res) { tests: bonfireTests, challengeSeed: bonfireChallengeSeed, cc: req.user ? req.user.bonfiresHash : undefined, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -236,14 +236,14 @@ exports.completedBonfire = function (req, res) { } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - req.user.points++; + req.user.progressTimestamps.push(Date.now() / 1000 | 0); req.user.uncompletedBonfires.splice(index, 1) } pairedWith = pairedWith.pop(); index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - pairedWith.points++; + pairedWith.progressTimestamps.push(Date.now() / 1000 | 0); pairedWith.uncompletedBonfires.splice(index, 1); } @@ -285,7 +285,7 @@ exports.completedBonfire = function (req, res) { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { - req.user.points++; + req.user.progressTimestamps.push(Date.now() / 1000 | 0); req.user.uncompletedBonfires.splice(index, 1) } diff --git a/controllers/courseware.js b/controllers/courseware.js index 4dcf0e1cb3..94d651467c 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -92,7 +92,7 @@ exports.returnIndividualCourseware = function(req, res, next) { tests: courseware.tests, challengeSeed: courseware.challengeSeed, cc: !!req.user, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -111,7 +111,7 @@ exports.returnIndividualCourseware = function(req, res, next) { tests: courseware.tests, challengeSeed: courseware.challengeSeed, cc: !!req.user, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -130,7 +130,7 @@ exports.returnIndividualCourseware = function(req, res, next) { tests: courseware.tests, video: courseware.challengeSeed[0], cc: !!req.user, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -168,7 +168,7 @@ exports.testCourseware = function(req, res) { challengeSeed: coursewareChallengeSeed, challengeEntryPoint: coursewareEntryPoint, cc: req.user ? req.user.coursewaresHash : undefined, - points: req.user ? req.user.points : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, verb: resources.randomVerb(), phrase: resources.randomPhrase(), compliment: resources.randomCompliment(), @@ -224,7 +224,7 @@ exports.completedCourseware = function (req, res) { var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); if (index > -1) { - req.user.points++; + req.user.progressTimestamps.push(Date.now() / 1000 | 0); req.user.uncompletedCoursewares.splice(index, 1) } diff --git a/controllers/resources.js b/controllers/resources.js index b8c7be6a4e..f5912f7c80 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -22,33 +22,6 @@ module.exports = { }); }, - stats: function stats(req, res) { - var date1 = new Date("10/15/2014"); - var date2 = new Date(); - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); - var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); - client.get('https://trello.com/1/boards/BA3xVpz9/cards?key=' + secrets.trello.key, function(trello, response) { - var nonprofitProjects = (trello && trello.length) || 15; - User.count({'points': {'$gt': 2}}, function(err, c3) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 9}}, function(err, c10) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 29}}, function(err, c30) { if (err) { debug('User err: ', err); next(err); } - User.count({'points': {'$gt': 53}}, function(err, all) { if (err) { debug('User err: ', err); next(err); } - res.render('resources/stats', { - title: 'Free Code Camp Stats:', - daysRunning: daysRunning, - nonprofitProjects: nonprofitProjects, - c3: c3, - c10: c10, - c30: c30, - all: all - }); - }); - }); - }); - }); - }); - }, - deployAWebsite: function deployAWebsite(req, res) { res.render('resources/deploy-a-website', { title: 'Deploy a Dynamic Website in 7 Minutes' @@ -145,6 +118,21 @@ module.exports = { about: function(req, res) { var date1 = new Date("10/15/2014"); var date2 = new Date(); + var progressTimestamps = req.user.progressTimestamps; + var now = Date.now() / 1000 | 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]); + } + } + 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; diff --git a/models/User.js b/models/User.js index 5ad54fd610..09f6a069a3 100644 --- a/models/User.js +++ b/models/User.js @@ -21,7 +21,9 @@ var userSchema = new mongoose.Schema({ type: Number, default: 0 }, + progressTimestamps: { type: Array, default: [] }, challengesCompleted: { type: Array, default: [] }, + pointsNeedMigration: { type: Boolean, default: true }, challengesHash: { 0: { type: Number, diff --git a/views/account/show.jade b/views/account/show.jade index c763656311..b998729f08 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -44,7 +44,7 @@ block content .col-xs-12.col-sm-12.col-md-3.text-center .background-svg.img-center .points-on-top - = "[ " + points + " ]" + = "[ " + progressTimestamps.length + " ]" .row diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index addf25e823..b4829baadc 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -25,7 +25,7 @@ else li if (user.profile.username) - a(href='/' + user.profile.username) [ #{user.points} ] + a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] else a(href='/account') [ #{user.points} ] .hidden-xs From 120cc113b95f8025ce2a2d671faa8742c6bedf69 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Feb 2015 23:47:30 -0800 Subject: [PATCH 014/125] current work on d3 --- controllers/user.js | 16 +++++++++++----- public/css/main.less | 5 +++++ views/account/show.jade | 16 +++++++++------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index 7bce16c40c..3048f7e97a 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -235,10 +235,15 @@ exports.returnUser = function(req, res, next) { if (user[0]) { var user = user[0]; Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { - data = { - "1424569155": 1, - "1424519155": 1 - }; + data = {}; + progressTimestamps = user.progressTimestamps; + // dummy data to experiment with visualizations + progressTimestamps = [1417117319, 1384091493, 1367893914, 1411547157, 1366875140, 1382614404, 1374973026, 1363495510, 1372229313, 1389795294, 1393820136, 1395425437, 1383366211, 1402063449, 1368384561, 1413460738, 1390013511, 1408510076, 1395530419, 1391588683, 1410480320, 1360219531, 1367248635, 1408531181, 1374214772, 1424038529, 1387468139, 1381934158, 1409278748, 1390696161, 1415933043, 1389573689, 1395703336, 1401223291, 1375539279, 1371229698, 1371990948, 1422236826, 1363017438, 1359619855, 1364850739, 1401982108, 1381270295, 1420063854, 1406540493, 1409122251, 1360775035, 1367712723, 1395305605, 1382037418, 1378402477, 1377563090, 1398930836, 1417371909, 1377417393, 1423763002, 1357511908, 1377375961, 1388374304, 1406416407, 1399463258, 1422593990, 1383434425, 1420200570, 1379435518, 1414512582, 1416263148, 1398635260, 1381815565, 1369178539, 1378414973, 1394409827, 1398463526, 1379564971, 1385849279, 1392899666, 1367053659, 1417730793, 1400112915, 1379923357, 1417768487, 1415779985, 1416150640, 1399820237, 1370498715, 1374800622, 1363924512, 1402497668, 1400146327, 1362456746, 1394935898, 1414980963, 1413942775, 1367606840, 1387144705, 1407906392, 1417213587, 1422640891, 1414033139, 1365323522, 1424661148]; + for (i = 0; i < progressTimestamps.length; i++) { + data[progressTimestamps[i].toString()] = 1; + } + + res.render('account/show', { title: 'Camper: ', username: user.profile.username, @@ -250,7 +255,8 @@ exports.returnUser = function(req, res, next) { twitterHandle: user.profile.twitterHandle, bio: user.profile.bio, picture: user.profile.picture, - points: user.points, + progressTimestamps: req.user.progressTimestamps, + points: user.progressTimestamps, website1Link: user.portfolio.website1Link, website1Title: user.portfolio.website1Title, website1Image: user.portfolio.website1Image, diff --git a/public/css/main.less b/public/css/main.less index a43896dec7..fd76f5a891 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -734,6 +734,11 @@ iframe.iphone { } +#cal-heatmap { + margin: 0 auto; + background-color: #fefefe; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/account/show.jade b/views/account/show.jade index b998729f08..43b59ee3f1 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -105,19 +105,21 @@ block content .col-xs-12 - .embed-responsive.embed-responsive-16by9 - #cal-heatmap + #cal-heatmap script. var cal = new CalHeatMap(); var calendar = !{JSON.stringify(calender)}; - console.log(calendar); cal.init({ itemSelector: "#cal-heatmap", domain: "month", subDomain: "day", data: calendar, - range: 10, - start: new Date(2015, 0), - itemName: "", - legend: [5, 10, 15, 20, 25, 30] + cellSize: 15, + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 4, + start: new Date().setDate(new Date().getDate() - 90), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3] }); \ No newline at end of file From a89139119acb6ed7c1c4902a3ebb4af881f37e52 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 25 Feb 2015 14:22:42 -0800 Subject: [PATCH 015/125] update navbar link to challenges --- views/partials/navbar.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index b4829baadc..0e0e6a8ee1 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -9,7 +9,7 @@ .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown li - a(href='/challenges') Challenges + a(href='/courseware') Challenges li a(href='/chat') Chat li @@ -25,7 +25,7 @@ else li if (user.profile.username) - a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] + a(href='/' + user.profile.username) [ #{user.points} ] else a(href='/account') [ #{user.points} ] .hidden-xs From 6aaebbf34e5c35a2f2297e617471ddfadf37e946 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 12:12:48 +0900 Subject: [PATCH 016/125] Clean up show all bonfires modal --- views/partials/bonfires.jade | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index a42060ce79..be321383d2 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -21,10 +21,5 @@ h3 } }); - ol(start='0') - for bonfire in bonfires - li - a(href="/bonfires/#{bonfire.bonfireNumber}", class="#{ (cc && cc[bonfire.bonfireNumber] > 0) ? 'strikethrough' : '' }") #{bonfire.name} - |   (Level #{bonfire.difficulty}) -a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((cc && cc[53] === 0) || (!cc)) ? 'disabled' : '' }") I'm done with all the challenges! + From b35f5ca59c87f3b71d352820894f3b2c79f0a374 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 13:42:02 +0900 Subject: [PATCH 017/125] Working on unifying coursewares view and logic --- controllers/resources.js | 9 +- controllers/user.js | 953 +++++++++++++++++------------------ views/account/show.jade | 15 +- views/partials/bonfires.jade | 2 +- 4 files changed, 473 insertions(+), 506 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index f246707f44..fb38abeeeb 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -177,13 +177,13 @@ module.exports = { req.user.save(); } } - var date1 = new Date("10/15/2014"); + var date1 = new Date('10/15/2014'); var date2 = new Date(); var progressTimestamps = req.user.progressTimestamps; var now = Date.now() / 1000 | 0; if (req.user.pointsNeedMigration) { var challengesHash = req.user.challengesHash; - for(var key in challengesHash) { + for (var key in challengesHash) { if (challengesHash[key] > 0) { req.user.progressTimestamps.push(challengesHash[key]); } @@ -270,6 +270,11 @@ module.exports = { }); }, + getAllCourses: function() { + "use strict"; + return coursewares; + }, + allCoursewareIds: function() { return coursewares.map(function(elem) { return { diff --git a/controllers/user.js b/controllers/user.js index e30485cfb0..b2411c76e5 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1,14 +1,14 @@ var _ = require('lodash'), - async = require('async'), - crypto = require('crypto'), - nodemailer = require('nodemailer'), - passport = require('passport'), - User = require('../models/User'), - secrets = require('../config/secrets'), - moment = require('moment'), - Challenge = require('./../models/Challenge'), - debug = require('debug')('freecc:cntr:challenges'), - resources = require('./resources'); + async = require('async'), + crypto = require('crypto'), + nodemailer = require('nodemailer'), + passport = require('passport'), + User = require('../models/User'), + secrets = require('../config/secrets'), + moment = require('moment'), + Challenge = require('./../models/Challenge'), + debug = require('debug')('freecc:cntr:challenges'), + resources = require('./resources'); @@ -18,10 +18,10 @@ var _ = require('lodash'), */ exports.getSignin = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/signin', { - title: 'Free Code Camp Login' - }); + if (req.user) return res.redirect('/'); + res.render('account/signin', { + title: 'Free Code Camp Login' + }); }; /** @@ -30,28 +30,28 @@ exports.getSignin = function(req, res) { */ exports.postSignin = function(req, res, next) { - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password cannot be blank').notEmpty(); + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password cannot be blank').notEmpty(); - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/signin'); + if (errors) { + req.flash('errors', errors); + return res.redirect('/signin'); + } + + passport.authenticate('local', function(err, user, info) { + if (err) return next(err); + if (!user) { + req.flash('errors', { msg: info.message }); + return res.redirect('/signin'); } - - passport.authenticate('local', function(err, user, info) { - if (err) return next(err); - if (!user) { - req.flash('errors', { msg: info.message }); - return res.redirect('/signin'); - } - req.logIn(user, function(err) { - if (err) return next(err); - req.flash('success', { msg: 'Success! You are logged in.' }); - res.redirect(req.session.returnTo || '/'); - }); - })(req, res, next); + req.logIn(user, function(err) { + if (err) return next(err); + req.flash('success', { msg: 'Success! You are logged in.' }); + res.redirect(req.session.returnTo || '/'); + }); + })(req, res, next); }; /** @@ -60,8 +60,8 @@ exports.postSignin = function(req, res, next) { */ exports.signout = function(req, res) { - req.logout(); - res.redirect('/'); + req.logout(); + res.redirect('/'); }; /** @@ -70,10 +70,10 @@ exports.signout = function(req, res) { */ exports.getEmailSignin = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/email-signin', { - title: 'Sign in to your Free Code Camp Account' - }); + if (req.user) return res.redirect('/'); + res.render('account/email-signin', { + title: 'Sign in to your Free Code Camp Account' + }); }; /** @@ -82,10 +82,10 @@ exports.getEmailSignin = function(req, res) { */ exports.getEmailSignup = function(req, res) { - if (req.user) return res.redirect('/'); - res.render('account/email-signup', { - title: 'Create Your Free Code Camp Account' - }); + if (req.user) return res.redirect('/'); + res.render('account/email-signup', { + title: 'Create Your Free Code Camp Account' + }); }; /** @@ -94,93 +94,93 @@ exports.getEmailSignup = function(req, res) { */ exports.postEmailSignup = function(req, res, next) { - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); + if (errors) { + req.flash('errors', errors); + return res.redirect('/email-signup'); + } + + var possibleUserData = req.body; + + if (possibleUserData.password.length < 8) { + req.flash('errors', { + msg: 'Your password is too short' + }); + return res.redirect('email-signup'); + } + + if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { + req.flash('errors', { + msg: 'Your username must be between 5 and 20 characters' + }); + return res.redirect('email-signup'); + } + + + var user = new User({ + email: req.body.email.trim(), + password: req.body.password, + profile : { + username: req.body.username.trim(), + picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' + } + }); + + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); + } + + if (existingEmail) { + req.flash('errors', { + msg: 'Account with that email address already exists.' + }); + return res.redirect('/email-signup'); + } + User.findOne({'profile.username': req.body.username }, function(err, existingUsername) { + if (err) { + return next(err); + } + if (existingUsername) { + req.flash('errors', { + msg: 'Account with that username already exists.' + }); return res.redirect('/email-signup'); - } + } - var possibleUserData = req.body; - - if (possibleUserData.password.length < 8) { - req.flash('errors', { - msg: 'Your password is too short' + user.save(function(err) { + if (err) { return next(err); } + req.logIn(user, function(err) { + if (err) { return next(err); } + res.redirect('/email-signup'); }); - return res.redirect('email-signup'); - } - - if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { - req.flash('errors', { - msg: 'Your username must be between 5 and 20 characters' - }); - return res.redirect('email-signup'); - } - - - var user = new User({ - email: req.body.email.trim(), - password: req.body.password, - profile : { - username: req.body.username.trim(), - picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' + }); + 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; } + }); }); - - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - - if (existingEmail) { - req.flash('errors', { - msg: 'Account with that email address already exists.' - }); - return res.redirect('/email-signup'); - } - User.findOne({'profile.username': req.body.username }, function(err, existingUsername) { - if (err) { - return next(err); - } - if (existingUsername) { - req.flash('errors', { - msg: 'Account with that username already exists.' - }); - return res.redirect('/email-signup'); - } - - user.save(function(err) { - if (err) { return next(err); } - req.logIn(user, function(err) { - if (err) { return next(err); } - res.redirect('/email-signup'); - }); - }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', - "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", - 'Good luck with the challenges!\n\n', - '- the Volunteer Camp Counselor Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } - }); - }); - }); + }); }; /** @@ -198,9 +198,9 @@ exports.getStreak = function(req, res) { */ exports.getAccount = function(req, res) { - res.render('account/account', { - title: 'Manage your Free Code Camp Account' - }); + res.render('account/account', { + title: 'Manage your Free Code Camp Account' + }); }; /** @@ -208,9 +208,9 @@ exports.getAccount = function(req, res) { */ exports.getAccountAngular = function(req, res) { - res.json({ - user: req.user - }); + res.json({ + user: req.user + }); }; /** @@ -218,26 +218,26 @@ exports.getAccountAngular = function(req, res) { */ exports.checkUniqueUsername = function(req, res) { - User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { - if (data == 1) { - return res.send(true); - } else { - return res.send(false); - } - }); + User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { + if (data == 1) { + return res.send(true); + } else { + return res.send(false); + } + }); }; /** * Existing username check */ exports.checkExistingUsername = function(req, res) { - User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { - if (data === 1) { - return res.send(true); - } else { - return res.send(false); - } - }); + User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { + if (data === 1) { + return res.send(true); + } else { + return res.send(false); + } + }); }; /** @@ -245,13 +245,13 @@ exports.checkExistingUsername = function(req, res) { */ exports.checkUniqueEmail = function(req, res) { - User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { - if (data == 1) { - return res.send(true); - } else { - return res.send(false); - } - }); + User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { + if (data === 1) { + return res.send(true); + } else { + return res.send(false); + } + }); }; @@ -265,43 +265,41 @@ exports.returnUser = function(req, res, next) { if (err) { debug('Username err: ', err); next(err); } if (user[0]) { var user = user[0]; - Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { - data = {}; - progressTimestamps = user.progressTimestamps; - // dummy data to experiment with visualizations - progressTimestamps = [1417117319, 1384091493, 1367893914, 1411547157, 1366875140, 1382614404, 1374973026, 1363495510, 1372229313, 1389795294, 1393820136, 1395425437, 1383366211, 1402063449, 1368384561, 1413460738, 1390013511, 1408510076, 1395530419, 1391588683, 1410480320, 1360219531, 1367248635, 1408531181, 1374214772, 1424038529, 1387468139, 1381934158, 1409278748, 1390696161, 1415933043, 1389573689, 1395703336, 1401223291, 1375539279, 1371229698, 1371990948, 1422236826, 1363017438, 1359619855, 1364850739, 1401982108, 1381270295, 1420063854, 1406540493, 1409122251, 1360775035, 1367712723, 1395305605, 1382037418, 1378402477, 1377563090, 1398930836, 1417371909, 1377417393, 1423763002, 1357511908, 1377375961, 1388374304, 1406416407, 1399463258, 1422593990, 1383434425, 1420200570, 1379435518, 1414512582, 1416263148, 1398635260, 1381815565, 1369178539, 1378414973, 1394409827, 1398463526, 1379564971, 1385849279, 1392899666, 1367053659, 1417730793, 1400112915, 1379923357, 1417768487, 1415779985, 1416150640, 1399820237, 1370498715, 1374800622, 1363924512, 1402497668, 1400146327, 1362456746, 1394935898, 1414980963, 1413942775, 1367606840, 1387144705, 1407906392, 1417213587, 1422640891, 1414033139, 1365323522, 1424661148]; - for (i = 0; i < progressTimestamps.length; i++) { - data[progressTimestamps[i].toString()] = 1; - } + var data = {}; + var progressTimestamps = user.progressTimestamps; + for (var i = 0; i < progressTimestamps.length; i++) { + data[progressTimestamps[i].toString()] = 1; + } - res.render('account/show', { - title: 'Camper: ', - username: user.profile.username, - name: user.profile.name, - location: user.profile.location, - githubProfile: user.profile.githubProfile, - linkedinProfile: user.profile.linkedinProfile, - codepenProfile: user.profile.codepenProfile, - twitterHandle: user.profile.twitterHandle, - bio: user.profile.bio, - picture: user.profile.picture, - progressTimestamps: req.user.progressTimestamps, - points: user.progressTimestamps, - website1Link: user.portfolio.website1Link, - website1Title: user.portfolio.website1Title, - website1Image: user.portfolio.website1Image, - website2Link: user.portfolio.website2Link, - website2Title: user.portfolio.website2Title, - website2Image: user.portfolio.website2Image, - website3Link: user.portfolio.website3Link, - website3Title: user.portfolio.website3Title, - website3Image: user.portfolio.website3Image, - challenges: c, - calender: data, - moment: moment - }); + res.render('account/show', { + title: 'Camper ' + user.profile.username + '\'s portfolio', + username: user.profile.username, + name: user.profile.name, + location: user.profile.location, + githubProfile: user.profile.githubProfile, + linkedinProfile: user.profile.linkedinProfile, + codepenProfile: user.profile.codepenProfile, + twitterHandle: user.profile.twitterHandle, + bio: user.profile.bio, + picture: user.profile.picture, + progressTimestamps: user.progressTimestamps, + points: user.progressTimestamps.length, + website1Link: user.portfolio.website1Link, + website1Title: user.portfolio.website1Title, + website1Image: user.portfolio.website1Image, + website2Link: user.portfolio.website2Link, + website2Title: user.portfolio.website2Title, + website2Image: user.portfolio.website2Image, + website3Link: user.portfolio.website3Link, + website3Title: user.portfolio.website3Title, + website3Image: user.portfolio.website3Image, + challenges: resources.getAllCourses(), + ch: user.challengesHash, + calender: data, + moment: moment }); + } else { req.flash('errors', { msg: "404: We couldn't find a page with that url. Please double check the link." @@ -309,45 +307,6 @@ exports.returnUser = function(req, res, next) { return res.redirect('/'); } }); - - User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { - if (err) { debug('Username err: ', err); next(err); } - if (user[0]) { - var user = user[0]; - Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { - res.render('account/show', { - title: 'Camper ' + user.profile.username + '\'s portfolio', - username: user.profile.username, - name: user.profile.name, - location: user.profile.location, - githubProfile: user.profile.githubProfile, - linkedinProfile: user.profile.linkedinProfile, - codepenProfile: user.profile.codepenProfile, - twitterHandle: user.profile.twitterHandle, - bio: user.profile.bio, - picture: user.profile.picture, - points: user.points, - website1Link: user.portfolio.website1Link, - website1Title: user.portfolio.website1Title, - website1Image: user.portfolio.website1Image, - website2Link: user.portfolio.website2Link, - website2Title: user.portfolio.website2Title, - website2Image: user.portfolio.website2Image, - website3Link: user.portfolio.website3Link, - website3Title: user.portfolio.website3Title, - website3Image: user.portfolio.website3Image, - challenges: c, - ch: user.challengesHash, - moment: moment - }); - }); - } else { - req.flash('errors', { - msg: "404: We couldn't find a page with that url. Please double check the link." - }); - return res.redirect('/'); - } - }); }; @@ -357,20 +316,20 @@ exports.returnUser = function(req, res, next) { */ exports.updateProgress = function(req, res) { - User.findById(req.user.id, function(err, user) { - if (err) return next(err); - user.email = req.body.email || ''; - user.profile.name = req.body.name || ''; - user.profile.gender = req.body.gender || ''; - user.profile.location = req.body.location || ''; - user.profile.website = req.body.website || ''; + User.findById(req.user.id, function(err, user) { + if (err) return next(err); + user.email = req.body.email || ''; + user.profile.name = req.body.name || ''; + user.profile.gender = req.body.gender || ''; + user.profile.location = req.body.location || ''; + user.profile.website = req.body.website || ''; - user.save(function(err) { - if (err) return next(err); - req.flash('success', { msg: 'Profile information updated.' }); - res.redirect('/account'); - }); + user.save(function(err) { + if (err) return next(err); + req.flash('success', { msg: 'Profile information updated.' }); + res.redirect('/account'); }); + }); }; /** @@ -380,77 +339,77 @@ exports.updateProgress = function(req, res) { exports.postUpdateProfile = function(req, res, next) { - User.findById(req.user.id, function(err, user) { - if (err) return next(err); - var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/account'); - } + User.findById(req.user.id, function(err, user) { + if (err) return next(err); + var errors = req.validationErrors(); + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - var user = req.user; - if (existingEmail && existingEmail.email != user.email) { - req.flash('errors', { - msg: "An account with that email address already exists." - }); - return res.redirect('/account'); - } - User.findOne({ 'profile.username': req.body.username }, function(err, existingUsername) { - if (err) { - return next(err); - } - var user = req.user; - if (existingUsername && existingUsername.profile.username !== user.profile.username) { - req.flash('errors', { - msg: 'An account with that username already exists.' - }); - return res.redirect('/account'); - } - user.email = req.body.email.trim() || ''; - user.profile.name = req.body.name.trim() || ''; - user.profile.username = req.body.username.trim() || ''; - user.profile.location = req.body.location.trim() || ''; - user.profile.githubProfile = req.body.githubProfile.trim() || ''; - user.profile.linkedinProfile = req.body.linkedinProfile.trim() || ''; - user.profile.codepenProfile = req.body.codepenProfile.trim() || ''; - user.profile.twitterHandle = req.body.twitterHandle.trim() || ''; - user.profile.bio = req.body.bio.trim() || ''; - user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; - user.portfolio.website1Title = req.body.website1Title.trim() || ''; - user.portfolio.website1Link = req.body.website1Link.trim() || ''; - user.portfolio.website1Image = req.body.website1Image.trim() || ''; - user.portfolio.website2Title = req.body.website2Title.trim() || ''; - user.portfolio.website2Link = req.body.website2Link.trim() || ''; - user.portfolio.website2Image = req.body.website2Image.trim() || ''; - user.portfolio.website3Title = req.body.website3Title.trim() || ''; - user.portfolio.website3Link = req.body.website3Link.trim() || ''; - user.portfolio.website3Image = req.body.website3Image.trim() || ''; - - - user.save(function (err) { - if (err) { - return next(err); - } - resources.updateUserStoryPictures( - user._id.toString(), - user.profile.picture, - user.profile.username, - function(err) { - if (err) { return next(err); } - req.flash('success', { - msg: 'Profile information updated.' - }); - res.redirect('/account'); - } - ); - }); - }); + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); + } + var user = req.user; + if (existingEmail && existingEmail.email != user.email) { + req.flash('errors', { + msg: "An account with that email address already exists." }); + return res.redirect('/account'); + } + User.findOne({ 'profile.username': req.body.username }, function(err, existingUsername) { + if (err) { + return next(err); + } + var user = req.user; + if (existingUsername && existingUsername.profile.username !== user.profile.username) { + req.flash('errors', { + msg: 'An account with that username already exists.' + }); + return res.redirect('/account'); + } + user.email = req.body.email.trim() || ''; + user.profile.name = req.body.name.trim() || ''; + user.profile.username = req.body.username.trim() || ''; + user.profile.location = req.body.location.trim() || ''; + user.profile.githubProfile = req.body.githubProfile.trim() || ''; + user.profile.linkedinProfile = req.body.linkedinProfile.trim() || ''; + user.profile.codepenProfile = req.body.codepenProfile.trim() || ''; + user.profile.twitterHandle = req.body.twitterHandle.trim() || ''; + user.profile.bio = req.body.bio.trim() || ''; + user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; + user.portfolio.website1Title = req.body.website1Title.trim() || ''; + user.portfolio.website1Link = req.body.website1Link.trim() || ''; + user.portfolio.website1Image = req.body.website1Image.trim() || ''; + user.portfolio.website2Title = req.body.website2Title.trim() || ''; + user.portfolio.website2Link = req.body.website2Link.trim() || ''; + user.portfolio.website2Image = req.body.website2Image.trim() || ''; + user.portfolio.website3Title = req.body.website3Title.trim() || ''; + user.portfolio.website3Link = req.body.website3Link.trim() || ''; + user.portfolio.website3Image = req.body.website3Image.trim() || ''; + + + user.save(function (err) { + if (err) { + return next(err); + } + resources.updateUserStoryPictures( + user._id.toString(), + user.profile.picture, + user.profile.username, + function(err) { + if (err) { return next(err); } + req.flash('success', { + msg: 'Profile information updated.' + }); + res.redirect('/account'); + } + ); + }); + }); }); + }); }; /** @@ -459,29 +418,29 @@ exports.postUpdateProfile = function(req, res, next) { */ exports.postUpdatePassword = function(req, res, next) { - req.assert('password', 'Password must be at least 4 characters long').len(4); - req.assert('confirmPassword', 'Passwords do not match') - .equals(req.body.password); + req.assert('password', 'Password must be at least 4 characters long').len(4); + req.assert('confirmPassword', 'Passwords do not match') + .equals(req.body.password); - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/account'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } + User.findById(req.user.id, function(err, user) { + if (err) { return next(err); } - user.password = req.body.password; + user.password = req.body.password; - user.save(function(err) { - if (err) { return next(err); } + user.save(function(err) { + if (err) { return next(err); } - req.flash('success', { msg: 'Password has been changed.' }); - res.redirect('/account'); - }); + req.flash('success', { msg: 'Password has been changed.' }); + res.redirect('/account'); }); + }); }; /** @@ -490,12 +449,12 @@ exports.postUpdatePassword = function(req, res, next) { */ exports.postDeleteAccount = function(req, res, next) { - User.remove({ _id: req.user.id }, function(err) { - if (err) { return next(err); } - req.logout(); - req.flash('info', { msg: 'Your account has been deleted.' }); - res.redirect('/'); - }); + User.remove({ _id: req.user.id }, function(err) { + if (err) { return next(err); } + req.logout(); + req.flash('info', { msg: 'Your account has been deleted.' }); + res.redirect('/'); + }); }; /** @@ -504,22 +463,22 @@ exports.postDeleteAccount = function(req, res, next) { */ exports.getOauthUnlink = function(req, res, next) { - var provider = req.params.provider; - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } + var provider = req.params.provider; + User.findById(req.user.id, function(err, user) { + if (err) { return next(err); } - user[provider] = undefined; - user.tokens = - _.reject(user.tokens, function(token) { - return token.kind === provider; - }); + user[provider] = undefined; + user.tokens = + _.reject(user.tokens, function(token) { + return token.kind === provider; + }); - user.save(function(err) { - if (err) { return next(err); } - req.flash('info', { msg: provider + ' account has been unlinked.' }); - res.redirect('/account'); - }); + user.save(function(err) { + if (err) { return next(err); } + req.flash('info', { msg: provider + ' account has been unlinked.' }); + res.redirect('/account'); }); + }); }; /** @@ -528,25 +487,25 @@ exports.getOauthUnlink = function(req, res, next) { */ exports.getReset = function(req, res) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('/forgot'); - } - res.render('account/reset', { - title: 'Password Reset', - token: req.params.token - }); + if (req.isAuthenticated()) { + return res.redirect('/'); + } + User + .findOne({ resetPasswordToken: req.params.token }) + .where('resetPasswordExpires').gt(Date.now()) + .exec(function(err, user) { + if (err) { return next(err); } + if (!user) { + req.flash('errors', { + msg: 'Password reset token is invalid or has expired.' }); + return res.redirect('/forgot'); + } + res.render('account/reset', { + title: 'Password Reset', + token: req.params.token + }); + }); }; /** @@ -555,72 +514,72 @@ exports.getReset = function(req, res) { */ exports.postReset = function(req, res, next) { - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('back'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('back'); + } - async.waterfall([ - function(done) { - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('back'); - } - - user.password = req.body.password; - user.resetPasswordToken = undefined; - user.resetPasswordExpires = undefined; - - user.save(function(err) { - if (err) { return done(err); } - req.logIn(user, function(err) { - done(err, user); - }); - }); - }); - }, - function(user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } + async.waterfall([ + function(done) { + User + .findOne({ resetPasswordToken: req.params.token }) + .where('resetPasswordExpires').gt(Date.now()) + .exec(function(err, user) { + if (err) { return next(err); } + if (!user) { + req.flash('errors', { + msg: 'Password reset token is invalid or has expired.' }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Your Free Code Camp password has been changed', - text: [ - 'Hello,\n\n', - 'This email is confirming that you requested to', - 'reset your password for your Free Code Camp account.', - 'This is your email:', - user.email, - '\n' - ].join(' ') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('success', { - msg: 'Success! Your password has been changed.' - }); - done(); + return res.redirect('back'); + } + + user.password = req.body.password; + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + + user.save(function(err) { + if (err) { return done(err); } + req.logIn(user, function(err) { + done(err, user); }); + }); + }); + }, + function(user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password } - ], function(err) { - if (err) { return next(err); } - res.redirect('/'); - }); + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Your Free Code Camp password has been changed', + text: [ + 'Hello,\n\n', + 'This email is confirming that you requested to', + 'reset your password for your Free Code Camp account.', + 'This is your email:', + user.email, + '\n' + ].join(' ') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('success', { + msg: 'Success! Your password has been changed.' + }); + done(); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/'); + }); }; /** @@ -629,12 +588,12 @@ exports.postReset = function(req, res, next) { */ exports.getForgot = function(req, res) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - res.render('account/forgot', { - title: 'Forgot Password' - }); + if (req.isAuthenticated()) { + return res.redirect('/'); + } + res.render('account/forgot', { + title: 'Forgot Password' + }); }; /** @@ -643,80 +602,80 @@ exports.getForgot = function(req, res) { */ exports.postForgot = function(req, res, next) { - var errors = req.validationErrors(); + var errors = req.validationErrors(); - if (errors) { - req.flash('errors', errors); - return res.redirect('/forgot'); - } + if (errors) { + req.flash('errors', errors); + return res.redirect('/forgot'); + } - async.waterfall([ - function(done) { - crypto.randomBytes(16, function(err, buf) { - if (err) { return done(err); } - var token = buf.toString('hex'); - done(null, token); - }); - }, - function(token, done) { - User.findOne({ - email: req.body.email.toLowerCase() - }, function(err, user) { - if (err) { return done(err); } - if (!user) { - req.flash('errors', { - msg: 'No account with that email address exists.' - }); - return res.redirect('/forgot'); - } - - user.resetPasswordToken = token; - user.resetPasswordExpires = Date.now() + 3600000; // 1 hour - - user.save(function(err) { - if (err) { return done(err); } - done(null, token, user); - }); - }); - }, - function(token, user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Reset your Free Code Camp password', - text: [ - 'You are receiving this email because you (or someone else)\n', - 'requested we reset your Free Code Camp account\'s password.\n\n', - 'Please click on the following link, or paste this into your\n', - 'browser to complete the process:\n\n', - 'http://', - req.headers.host, - '/reset/', - token, - '\n\n', - 'If you did not request this, please ignore this email and\n', - 'your password will remain unchanged.\n' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('info', { - msg: 'An e-mail has been sent to ' + - user.email + - ' with further instructions.' - }); - done(null, 'done'); - }); + async.waterfall([ + function(done) { + crypto.randomBytes(16, function(err, buf) { + if (err) { return done(err); } + var token = buf.toString('hex'); + done(null, token); + }); + }, + function(token, done) { + User.findOne({ + email: req.body.email.toLowerCase() + }, function(err, user) { + if (err) { return done(err); } + if (!user) { + req.flash('errors', { + msg: 'No account with that email address exists.' + }); + return res.redirect('/forgot'); } - ], function(err) { - if (err) { return next(err); } - res.redirect('/forgot'); - }); + + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + + user.save(function(err) { + if (err) { return done(err); } + done(null, token, user); + }); + }); + }, + function(token, user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Reset your Free Code Camp password', + text: [ + 'You are receiving this email because you (or someone else)\n', + 'requested we reset your Free Code Camp account\'s password.\n\n', + 'Please click on the following link, or paste this into your\n', + 'browser to complete the process:\n\n', + 'http://', + req.headers.host, + '/reset/', + token, + '\n\n', + 'If you did not request this, please ignore this email and\n', + 'your password will remain unchanged.\n' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('info', { + msg: 'An e-mail has been sent to ' + + user.email + + ' with further instructions.' + }); + done(null, 'done'); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/forgot'); + }); }; diff --git a/views/account/show.jade b/views/account/show.jade index cd880b7dfe..bd1d21b93b 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -41,7 +41,7 @@ block content .col-xs-12.col-sm-12.col-md-3.text-center .background-svg.img-center .points-on-top - = "[ " + progressTimestamps.length + " ]" + = "[ " + points + " ]" .row @@ -95,14 +95,17 @@ block content a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') i.fa.icon-beaker | Try it out - - html. - - - + // + html. + + + .col-xs-12 #cal-heatmap + script(src="//d3js.org/d3.v3.min.js") + script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") + link(rel="stylesheet", href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css") script. var cal = new CalHeatMap(); var calendar = !{JSON.stringify(calender)}; diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index be321383d2..bfada7da53 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -21,5 +21,5 @@ h3 } }); - + From cd89ad1574bd11f47d3eb87a0c3ccdb2d9970a56 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 13:50:31 +0900 Subject: [PATCH 018/125] Change complete-challenge to completed-challenge --- public/js/main.js | 2 +- views/coursewares/showVideo.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index 9c874abfd8..22f983766b 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -74,7 +74,7 @@ $(document).ready(function() { completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); }); - $('#complete-courseware').on('click', function() { + $('#completed-courseware').on('click', function() { $('#complete-courseware-dialog').modal('show'); }); diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 129f2e4581..e80b8cc179 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -36,7 +36,7 @@ block content .animated.zoomInDown.delay-half span.completion-icon.ion-checkmark-circled.text-primary - if (cc) - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) + 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   From be91849d20928c7bfee8439782a8f12a46b99950 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 14:28:00 +0900 Subject: [PATCH 019/125] coursewares properly push timestamps to user object --- app.js | 14 +------------ controllers/courseware.js | 10 +++++---- public/js/main.js | 35 +++++++++++++++++--------------- views/coursewares/showVideo.jade | 2 +- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/app.js b/app.js index 0c2546d754..29a3c7966b 100644 --- a/app.js +++ b/app.js @@ -447,19 +447,7 @@ app.get('/sitemap.xml', resourcesController.sitemap); * and updates user.challengesHash & user.challengesCompleted * */ -app.post('/completed-challenge', function (req, res) { - req.user.challengesHash[parseInt(req.body.challengeNumber)] = - Math.round(+new Date() / 1000); - var timestamp = req.user.challengesHash; - var points = 0; - for (var key in timestamp) { - if (timestamp[key] > 0 && req.body.challengeNumber < 54) { - points += 1; - } - } - req.user.points = points; - req.user.save(); -}); + /** * OAuth sign-in routes. diff --git a/controllers/courseware.js b/controllers/courseware.js index 2cf3390d46..9c32518c80 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -216,16 +216,18 @@ exports.completedCourseware = function (req, res) { var isCompletedDate = Math.round(+new Date() / 1000); var coursewareHash = req.body.coursewareInfo.coursewareHash; + debug('this is the coursewarehash we got', coursewareHash); req.user.completedCoursewares.push({ _id: coursewareHash, completedDate: isCompletedDate }); - var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); - if (index > -1) { + 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.uncompletedCoursewares.splice(index, 1) + req.user.uncompletedCoursewares.splice(index, 1); } req.user.save(function (err, user) { @@ -233,7 +235,7 @@ exports.completedCourseware = function (req, res) { throw err; } if (user) { - res.send(true) + res.send(true); } }); }; \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 22f983766b..1543899859 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -30,19 +30,19 @@ $(document).ready(function() { .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/' - }); - } - }); + //$('.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) { @@ -105,18 +105,21 @@ $(document).ready(function() { editor.focus(); }); $('#next-courseware-button').on('click', function() { + console.log(passedCoursewareHash); if ($('.signup-btn-nav').length < 1) { $.post( - '/completed-courseware', + '/completed-courseware/', { coursewareInfo: { coursewareHash: passedCoursewareHash } }).success( - function() { + function(res) { + if (res) { window.location.href = '/coursewares'; + } } - ) + ); } }); diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index e80b8cc179..d55993d2b1 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -30,7 +30,7 @@ block content .modal-dialog.animated.zoomIn.fast-animation .modal-content .modal-header.challenge-list-header= compliment - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body(ng-controller="pairedWithController") .text-center .animated.zoomInDown.delay-half From a2e2e6bfa2f6108a3bc8a2a9ce9e745f3507d448 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 15:44:18 +0900 Subject: [PATCH 020/125] Improve profile view on devices, store name in completedCoursewares for display in account/show --- controllers/courseware.js | 6 ++++-- controllers/user.js | 2 +- public/js/main.js | 4 +++- views/account/show.jade | 28 ++++++++++++++-------------- views/coursewares/showVideo.jade | 1 + 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 9c32518c80..f830358af8 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -216,11 +216,13 @@ exports.completedCourseware = function (req, res) { var isCompletedDate = Math.round(+new Date() / 1000); var coursewareHash = req.body.coursewareInfo.coursewareHash; + debug('this is the coursewarehash we got', coursewareHash); req.user.completedCoursewares.push({ _id: coursewareHash, - completedDate: isCompletedDate + completedDate: isCompletedDate, + name: req.body.coursewareInfo.coursewareName }); var index = req.user.completedCoursewares.indexOf(coursewareHash); @@ -238,4 +240,4 @@ exports.completedCourseware = function (req, res) { res.send(true); } }); -}; \ No newline at end of file +}; diff --git a/controllers/user.js b/controllers/user.js index b2411c76e5..cfcd8c5430 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -294,7 +294,7 @@ exports.returnUser = function(req, res, next) { website3Link: user.portfolio.website3Link, website3Title: user.portfolio.website3Title, website3Image: user.portfolio.website3Image, - challenges: resources.getAllCourses(), + challenges: user.completedCoursewares, ch: user.challengesHash, calender: data, moment: moment diff --git a/public/js/main.js b/public/js/main.js index 1543899859..1fe3f29747 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -75,6 +75,7 @@ $(document).ready(function() { }); $('#completed-courseware').on('click', function() { + console.log('trying to show modal'); $('#complete-courseware-dialog').modal('show'); }); @@ -111,7 +112,8 @@ $(document).ready(function() { '/completed-courseware/', { coursewareInfo: { - coursewareHash: passedCoursewareHash + coursewareHash: passedCoursewareHash, + coursewareName: passedCoursewareName } }).success( function(res) { diff --git a/views/account/show.jade b/views/account/show.jade index bd1d21b93b..911d316cdd 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -101,7 +101,7 @@ block content - .col-xs-12 + .hidden-xs.col-sm-12 #cal-heatmap script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") @@ -115,6 +115,7 @@ block content subDomain: "day", data: calendar, cellSize: 15, + align: 'center', cellRadius: 3, cellPadding: 2, tooltip: true, @@ -124,17 +125,16 @@ block content legend: [1, 2, 3] }); - - if (ch[0] > 0) - .col-sm-12 - table.table.table-striped - thead - tr - th Challenge - th Date Finished - for challenge in challenges - if ch[challenge.challengeNumber] > 0 - tr - td= challenges[challenge.challengeNumber].name - td= moment(ch[challenge.challengeNumber], 'X').format("MMM DD, YYYY") - br + + .col-sm-12 + table.table.table-striped + thead + tr + th Challenge + th Date Finished + for challenge in challenges + tr + td= challenge.name + td= moment(challenge.completedDate, 'X').format("MMM DD, YYYY") + br diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index d55993d2b1..278a19609f 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -25,6 +25,7 @@ block content 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 From 6ad7c223138f04ebd83fcfa6a79a446f76647bb6 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 16:34:06 +0900 Subject: [PATCH 021/125] Working on codemirror display issue in html coursewares --- controllers/resources.js | 30 +++++++++++++++++++ models/User.js | 24 --------------- .../coursewaresHCJQFramework_v0.1.1.js | 6 ++-- views/account/show.jade | 5 ---- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index 3a7e096b74..6f3951f9cb 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -21,6 +21,17 @@ var async = require('async'), * Resources. */ +Array.zip = function(left, right, combinerFunction) { + var counter, + results = []; + + for (counter = 0; counter < Math.min(left.length, right.length); counter++) { + results.push(combinerFunction(left[counter],right[counter])); + } + + return results; +}; + module.exports = { privacy: function privacy(req, res) { res.render('resources/privacy', { @@ -195,6 +206,25 @@ module.exports = { req.user.progressTimestamps.push(challengesHash[key]); } } + + var timeStamps = []; + R.keys(req.user.challengesHash).forEach(function(key) { + "use strict"; + timeStamps.push({timeStamp: challengesHash[key]}); + }); + + 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(); } diff --git a/models/User.js b/models/User.js index 09f6a069a3..33d19618cc 100644 --- a/models/User.js +++ b/models/User.js @@ -240,30 +240,6 @@ var userSchema = new mongoose.Schema({ 53: { type: Number, default: 0 - }, - 54: { - type: Number, - default: 0 - }, - 55: { - type: Number, - default: 0 - }, - 56: { - type: Number, - default: 0 - }, - 57: { - type: Number, - default: 0 - }, - 58: { - type: Number, - default: 0 - }, - 59: { - type: Number, - default: 0 } }, profile: { diff --git a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js index d7928afc20..0234b5d314 100644 --- a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js +++ b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js @@ -3,12 +3,11 @@ */ var widgets = []; -var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { +var editor = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { lineNumbers: true, mode: "text/html", theme: 'monokai', runnable: true, - //lint: true, matchBrackets: true, autoCloseBrackets: true, scrollbarStyle: 'null', @@ -16,7 +15,6 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") gutters: ["CodeMirror-lint-markers"], onKeyEvent: doLinting }); -var editor = myCodeMirror; // Hijack tab key to insert two spaces instead @@ -117,7 +115,7 @@ var allSeeds = ''; }); })(); -myCodeMirror.setValue(allSeeds); +editor.setValue('test'); function doLinting () { editor.operation(function () { diff --git a/views/account/show.jade b/views/account/show.jade index 911d316cdd..fecdcae731 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -95,11 +95,6 @@ block content a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') i.fa.icon-beaker | Try it out - // - html. - - - .hidden-xs.col-sm-12 #cal-heatmap From ac511982a4357e3af5d87b5d045b6f6d1f7f6dc4 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 18:42:19 +0900 Subject: [PATCH 022/125] User facing challenge mentions now point to coursewares, remove completed view from account/show --- app.js | 24 +------ controllers/courseware.js | 10 +-- controllers/user.js | 1 - public/css/main.less | 7 +- .../coursewaresHCJQFramework_v0.1.1.js | 6 +- public/js/main.js | 2 +- views/account/show.jade | 18 +---- views/coursewares/showHTML.jade | 71 +++++-------------- 8 files changed, 33 insertions(+), 106 deletions(-) diff --git a/app.js b/app.js index 29a3c7966b..7f15966228 100644 --- a/app.js +++ b/app.js @@ -372,18 +372,6 @@ app.post( storyController.upvote ); -/** - * Challenge related routes - */ -app.get( - '/challenges/', - challengesController.returnNextChallenge -); -app.get( - '/challenges/:challengeNumber', - challengesController.returnChallenge -); - app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); @@ -422,10 +410,10 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); * Courseware related routes */ -app.get('/coursewares/', coursewareController.returnNextCourseware); -app.get('/coursewares/getCoursewareList', coursewareController.showAllCoursewares); +app.get('/challenges/', coursewareController.returnNextCourseware); +app.get('/challenges/getCoursewareList', coursewareController.showAllCoursewares); app.get( - '/coursewares/:coursewareName', + '/challenges/:coursewareName', coursewareController.returnIndividualCourseware ); app.post('/completed-courseware/', coursewareController.completedCourseware); @@ -441,12 +429,6 @@ app.post('/account/delete', userController.postDeleteAccount); app.get('/account/unlink/:provider', userController.getOauthUnlink); app.get('/sitemap.xml', resourcesController.sitemap); -/** - * API examples routes. - * accepts a post request. the challenge id req.body.challengeNumber - * and updates user.challengesHash & user.challengesCompleted - * - */ /** diff --git a/controllers/courseware.js b/controllers/courseware.js index f830358af8..4d992d310e 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -23,7 +23,7 @@ exports.showAllCoursewares = function(req, res) { exports.returnNextCourseware = function(req, res) { if (!req.user) { - return res.redirect('../coursewares/start-our-challenges'); + return res.redirect('../challenges/learn-how-free-code-camp-works'); } var completed = req.user.completedCoursewares.map(function (elem) { return elem._id; @@ -50,10 +50,10 @@ exports.returnNextCourseware = function(req, res) { req.flash('errors', { msg: "It looks like you've completed all the courses we have available. Good job!" }); - return res.redirect('../coursewares/start-our-challenges'); + return res.redirect('../challenges/learn-how-free-code-camp-works'); } nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../coursewares/' + nameString); + return res.redirect('../challenges/' + nameString); }); }; @@ -71,14 +71,14 @@ exports.returnIndividualCourseware = function(req, res, next) { req.flash('errors', { msg: "404: We couldn't find a challenge with that name. Please double check the name." }); - return res.redirect('/coursewares') + 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('../coursewares/' + dashedNameFull); + return res.redirect('../challenges/' + dashedNameFull); } var challengeType = { diff --git a/controllers/user.js b/controllers/user.js index 4b41af4c45..50ccf2ba47 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -297,7 +297,6 @@ exports.returnUser = function(req, res, next) { website3Link: user.portfolio.website3Link, website3Title: user.portfolio.website3Title, website3Image: user.portfolio.website3Image, - challenges: user.completedCoursewares, ch: user.challengesHash, calender: data, moment: moment diff --git a/public/css/main.less b/public/css/main.less index f022a1ac43..5b136853e5 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -864,8 +864,11 @@ iframe.iphone { } #cal-heatmap { - margin: 0 auto; - background-color: #fefefe; + width: 361px; +} + +.cal-heatmap-container { + background-color: #EEEEEE; } //uncomment this to see the dimensions of all elements outlined in red diff --git a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js index 0234b5d314..d572181512 100644 --- a/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js +++ b/public/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js @@ -34,10 +34,6 @@ editor.setOption("extraKeys", { var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(spaces); } - }, - "Ctrl-Enter": function() { - bonfireExecute(); - return false; } }); @@ -115,7 +111,7 @@ var allSeeds = ''; }); })(); -editor.setValue('test'); +editor.setValue(allSeeds); function doLinting () { editor.operation(function () { diff --git a/public/js/main.js b/public/js/main.js index 1fe3f29747..e108d8c9ac 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -118,7 +118,7 @@ $(document).ready(function() { }).success( function(res) { if (res) { - window.location.href = '/coursewares'; + window.location.href = '/challenges'; } } ); diff --git a/views/account/show.jade b/views/account/show.jade index fecdcae731..f762aba3c3 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -97,7 +97,7 @@ block content | Try it out .hidden-xs.col-sm-12 - #cal-heatmap + #cal-heatmap.img-center script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") link(rel="stylesheet", href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css") @@ -118,18 +118,4 @@ block content start: new Date().setDate(new Date().getDate() - 90), legendColors: ["#cccccc", "#215f1e"], legend: [1, 2, 3] - }); - - - .col-sm-12 - table.table.table-striped - thead - tr - th Challenge - th Date Finished - for challenge in challenges - tr - td= challenge.name - td= moment(challenge.completedDate, 'X').format("MMM DD, YYYY") - br - + }); \ No newline at end of file diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index f0e58001eb..8f17094831 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -18,8 +18,6 @@ block content script(src='/js/lib/codemirror/mode/xml/xml.js') script(src='/js/lib/codemirror/mode/css/css.js') script(src='/js/lib/codemirror/mode/htmlmixed/htmlmixed.js') - script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js") - .row.courseware-height .col-xs-12.col-sm-12.col-md-3.col-lg-3 .well @@ -40,23 +38,19 @@ block content span.ion-arrow-up-b | Less information br - .btn.btn-info#showAllCoursewares - - if (cc) - a.btn.btn-primary.btn-lg.btn-block#complete-courseware + a.btn.btn-primary.btn-lg.btn-block#next-courseware-button | Go to my next challenge br | (ctrl + enter) script. var userLoggedIn = true; - - else a.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - script. - var userLoggedIn = false; + script. + var userLoggedIn = false; br ul#testSuite.list-group - br script(type="text/javascript"). $('#next-courseware-button').attr('disabled', 'disabled'); @@ -64,60 +58,27 @@ block content var challengeSeed = !{JSON.stringify(challengeSeed)}; var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; var challengeName = !{JSON.stringify(name)}; + var passedCoursewareName = challengeName; var prodOrDev = !{JSON.stringify(environment)}; var started = Math.floor(Date.now() / 1000); - var url = ''; - var dashed = !{JSON.stringify(dashedName)}; - console.log(dashed); - .col-xs-12.col-sm-12.col-md-5.col-lg-6 #mainEditorPanel form.code .form-group.codeMirrorView - - script(src='/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js') textarea#codeEditor(autofocus=true, style='display: none;') - .col-md-4.col-lg-3 .hidden-xs.hidden-sm img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") iframe.iphone#preview - - #complete-courseware-dialog.modal(tabindex='-1') - .modal-dialog.animated.zoomIn.fast-animation - .modal-content - .modal-header.challenge-list-header= compliment - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - .text-center - .animated.zoomInDown.delay-half - span.completion-icon.ion-checkmark-circled.text-primary - - if (cc) - - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-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(target="_blank") - i.fa.fa-twitter   - = phrase - - - else - a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - - #all-coursewares-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/coursewares - script. - $.ajax({ - url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fchallenges%2F' + dashed + '&format=txt' - }) - .success( - function(data) { - console.log(data); - url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; - $('.btn-twitter').attr('href', url); - } - ); + #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 + .text-center + .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 From fa0462da4c0311e61a6ae35c922cd5483f63bdbf Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 21 Mar 2015 18:58:14 +0900 Subject: [PATCH 023/125] point courseware reference in navbar to challenges --- views/partials/navbar.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 1402f96826..f74d5dc487 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -12,7 +12,7 @@ .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown li - a(href='/courseware') Challenges + a(href='/challenges') Challenges li a(href='/chat') Chat li From 0eb15269bef5b45ca9b77fbc7cba98fefb832f43 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Mar 2015 16:48:48 -0700 Subject: [PATCH 024/125] 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 025/125] 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 026/125] 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 d0a40dc2026a68ef6540adfe908393b1edd09553 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 01:16:02 -0700 Subject: [PATCH 027/125] fix intermittent variable declaration error in camper news view --- views/stories/index.jade | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/views/stories/index.jade b/views/stories/index.jade index c5b0c2de34..19aa25dc5e 100644 --- a/views/stories/index.jade +++ b/views/stories/index.jade @@ -1,9 +1,14 @@ extends ../layout block content script(src='/js/lib/moment/moment.js') + if (user) + script. + var user = !{JSON.stringify(user)}; + else + script. + var user = undefined; script. var challengeName = 'Camper News'; - var user = !{JSON.stringify(user)}; var page = !{JSON.stringify(page)}; .panel.panel-info .panel-heading.text-center Camper News From 9633a2dbe07a16a0c7e66b0ea040e7c4cc423a47 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 01:28:40 -0700 Subject: [PATCH 028/125] get hotkey working in showVideo --- public/js/main.js | 6 +++--- views/coursewares/showVideo.jade | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index e108d8c9ac..9a903a1d08 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -74,14 +74,14 @@ $(document).ready(function() { completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); }); + $('#completed-courseware').on('click', function() { - console.log('trying to show modal'); $('#complete-courseware-dialog').modal('show'); }); $('#complete-courseware-dialog').on('keypress', function(e) { - if (e.which === 13 || e === 13) { - $('#next-courseware-button').click(); + if (e.ctrlKey && e.keyCode == 13) { + $('#next-courseware-button').click(); } }); diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 278a19609f..214b3c2d13 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -47,4 +47,9 @@ block content h1 #{name} script. var challengeName = !{JSON.stringify(name)}; - var passedCoursewareHash = !{JSON.stringify(coursewareHash)}; \ No newline at end of file + 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 6e0da758e015347f4ec0ff2d6ca4777648fa37df Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 01:38:04 -0700 Subject: [PATCH 029/125] finish wiring in buttons and hotkeys --- public/js/lib/coursewares/coursewaresJSFramework.js | 2 +- views/coursewares/showHTML.jade | 1 - views/coursewares/showJS.jade | 9 +++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/js/lib/coursewares/coursewaresJSFramework.js b/public/js/lib/coursewares/coursewaresJSFramework.js index ee89e7eaf4..03e474da62 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework.js +++ b/public/js/lib/coursewares/coursewaresJSFramework.js @@ -240,7 +240,7 @@ function showCompletion() { $('#complete-courseware-dialog').modal('show'); $('#complete-courseware-dialog').keydown(function(e) { if (e.ctrlKey && e.keyCode == 13) { - $('.next-bonfire-button').click(); + $('#next-courseware-button').click(); } }); } \ No newline at end of file diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 8f2a457ca6..8f17094831 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -66,7 +66,6 @@ block content form.code .form-group.codeMirrorView textarea#codeEditor(autofocus=true, style='display: none;') - script(src='/js/lib/coursewares/coursewaresHCJQFramework_v0.1.1.js') .col-md-4.col-lg-3 .hidden-xs.hidden-sm img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index f06876ab4e..cf0e7a80fe 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() / 1000); .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel @@ -65,9 +66,9 @@ block content span.completion-icon.ion-checkmark-circled.text-primary - if (cc) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next challenge (ctrl + enter) - - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") - i.fa.fa-twitter   - = phrase + - if (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 \ 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 030/125] 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 031/125] 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 032/125] 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 f9523beb2934b82337f4b1a713368263fc8ead8a Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Mar 2015 18:22:07 -0700 Subject: [PATCH 033/125] update copy on show all bonfires button --- views/bonfire/show.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index da09c0a208..8ea9007701 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -77,7 +77,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 coding challenges + #showAllButton.btn.btn-info.btn-big.btn-block Show all bonfires br form.code .form-group.codeMirrorView From ac8487a4fc5fde2d4b6775bea430777c7986fa6d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 23 Mar 2015 08:33:59 -0700 Subject: [PATCH 034/125] 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 035/125] 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 d0a00428d57ad9f36e8ab300a2b080ce66c0fef1 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 24 Mar 2015 18:26:02 +0900 Subject: [PATCH 036/125] 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 037/125] 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 bcec96e427462c5069d72bc2d900c375b15717df Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 24 Mar 2015 19:08:26 -0700 Subject: [PATCH 038/125] 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 039/125] 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 040/125] 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 041/125] 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 042/125] 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 043/125] 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 044/125] 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 94b6cae3ece95cd7bfebc6954da55e004299f6c2 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 27 Mar 2015 12:36:21 -0700 Subject: [PATCH 045/125] 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 046/125] 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 047/125] 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 048/125] 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 049/125] 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 050/125] 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 0a07c1e3972798ad54696481372707bfeaccdcb9 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 28 Mar 2015 17:38:11 +0900 Subject: [PATCH 051/125] 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 052/125] 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 053/125] 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 054/125] 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 1fc3cffdbffebc11cfcc603d266a2d32e95b58d1 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 29 Mar 2015 20:39:41 +0900 Subject: [PATCH 055/125] 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 056/125] 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 057/125] 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 058/125] 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 059/125] 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 060/125] 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 061/125] 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 062/125] 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 063/125] 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 064/125] 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 065/125] 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 066/125] 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 067/125] 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({ From 660214f87e8f4f7d0c58b5d603e886d96f921405 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 13:48:54 -0700 Subject: [PATCH 068/125] able to seed and view wiki content --- app.js | 15 ++++++- controllers/basejumps.js | 21 --------- controllers/bonfire.js | 1 - controllers/challenges.js | 61 -------------------------- controllers/resources.js | 6 --- controllers/wiki.js | 40 +++++++++++++++++ controllers/ziplines.js | 21 --------- models/Basejump.js | 22 ---------- models/Courseware.js | 4 +- models/Story.js | 2 +- models/Wiki.js | 15 +++++++ models/Zipline.js | 21 --------- seed_data/seed.js | 21 ++++++++- seed_data/wikis.json | 25 +++++++++++ views/resources/control-shortcuts.jade | 22 ---------- views/wiki/show.jade | 10 +++++ 16 files changed, 126 insertions(+), 181 deletions(-) delete mode 100644 controllers/basejumps.js delete mode 100644 controllers/challenges.js create mode 100644 controllers/wiki.js delete mode 100644 controllers/ziplines.js delete mode 100644 models/Basejump.js create mode 100644 models/Wiki.js delete mode 100644 models/Zipline.js create mode 100644 seed_data/wikis.json delete mode 100644 views/resources/control-shortcuts.jade create mode 100644 views/wiki/show.jade diff --git a/app.js b/app.js index f22e0a2aee..08058238d7 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,7 @@ var express = require('express'), nonprofitController = require('./controllers/nonprofits'), bonfireController = require('./controllers/bonfire'), coursewareController = require('./controllers/courseware'), + wikiController = require('./controllers/wiki'), /** * Stories @@ -243,8 +244,6 @@ app.get('/guide-to-our-nonprofit-projects', resourcesController.guideToOurNonpro app.get('/chromebook', resourcesController.chromebook); app.get('/deploy-a-website', resourcesController.deployAWebsite); app.get('/gmail-shortcuts', resourcesController.gmailShortcuts); -app.get('/control-shortcuts', resourcesController.controlShortcuts); -app.get('/control-shortcuts', resourcesController.deployAWebsite); app.get('/nodeschool-challenges', resourcesController.nodeSchoolChallenges); app.get('/stats', function(req, res) { res.redirect(301, '/learn-to-code'); @@ -425,7 +424,19 @@ app.get('/bonfire', function(req, res) { res.redirect(301, '/playground'); }); +/** + * Wiki related routes + */ +app.get( + '/wiki/:wikiName', + wikiController.returnIndividualWiki +); + +app.get( + '/wiki', + wikiController.showAllWikis +); app.post('/completed-bonfire/', bonfireController.completedBonfire); diff --git a/controllers/basejumps.js b/controllers/basejumps.js deleted file mode 100644 index e9687201a6..0000000000 --- a/controllers/basejumps.js +++ /dev/null @@ -1,21 +0,0 @@ -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/bonfire.js b/controllers/bonfire.js index a753b55177..4f11a99a23 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -3,7 +3,6 @@ var _ = require('lodash'), Bonfire = require('./../models/Bonfire'), User = require('./../models/User'), resources = require('./resources'), - MDNlinks = require('./../seed_data/bonfireMDNlinks'), R = require('ramda'); MDNlinks = require('./../seed_data/bonfireMDNlinks'); diff --git a/controllers/challenges.js b/controllers/challenges.js deleted file mode 100644 index 6658e18e13..0000000000 --- a/controllers/challenges.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * GET / - * Challenges. - */ -var _ = require('lodash'), - debug = require('debug')('freecc:cntr:challenges'), - Challenge = require('./../models/Challenge'), - resources = require('./resources'); - -var highestChallengeNumber = 53; - - -exports.returnNextChallenge = function(req, res) { - if (req.user) { - ch = req.user.challengesHash; - if (req.user.challengesHash[0] > 0) { - var max = Object.keys(ch).reduce(function(max, key) { - return (max === undefined || ch[key] > ch[max]) ? +key : max; - }); - nextChallenge = max + 1; - res.redirect('challenges/' + nextChallenge); - } else { - res.redirect('challenges/0'); - } - } else { - return res.redirect('../challenges/0'); - } -}; - -exports.returnChallenge = function(req, res) { - var challengeNumber = parseInt(req.params.challengeNumber) || 0; - if (challengeNumber === 2) { - return res.redirect('../challenges/3'); - } - if (challengeNumber > highestChallengeNumber) { - req.flash('errors', { - msg: "It looks like you've either completed all the challenges we have available or requested a challenge we don't have." - }); - return res.redirect('../challenges/0'); - } - Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) { - if (err) { - debug('Challenge err: ', err); - next(err); - } - res.render('challenges/show', { - title: 'Challenge: ' + c[challengeNumber].name, - name: c[challengeNumber].name, - video: c[challengeNumber].video, - time: c[challengeNumber].time, - steps: c[challengeNumber].steps, - number: challengeNumber, - cc: req.user ? req.user.challengesHash : undefined, - points: req.user ? req.user.points : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - challenges: c - }); - }); -}; diff --git a/controllers/resources.js b/controllers/resources.js index 1c1da1d056..adb4606a72 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -108,12 +108,6 @@ module.exports = { }); }, - 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' diff --git a/controllers/wiki.js b/controllers/wiki.js new file mode 100644 index 0000000000..6172e0f225 --- /dev/null +++ b/controllers/wiki.js @@ -0,0 +1,40 @@ +var _ = require('lodash'), + debug = require('debug')('freecc:cntr:wiki'), + Wiki = require('./../models/Wiki'), + resources = require('./resources'), + R = require('ramda'); + +exports.returnIndividualWiki = function(req, res, next) { + var dashedName = req.params.wikiName; + + var wikiName = dashedName.replace(/\-/g, ' '); + + Wiki.find({'name': new RegExp(wikiName, 'i')}, function(err, wiki) { + if (err) { + next(err); + } + + if (wiki.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a wiki entry with that name. Please double check the name." + }); + + return res.redirect('/wiki'); + } + + wiki = wiki.pop(); + var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../wiki/' + dashedNameFull); + } + res.render('wiki/show', { + title: wiki.name, + description: wiki.description.join('') + }); + }); +}; + +exports.showAllWikis = function(req, res) { + data.wikiList = resources.allWikiNames(); + res.send(data); +}; diff --git a/controllers/ziplines.js b/controllers/ziplines.js deleted file mode 100644 index e9687201a6..0000000000 --- a/controllers/ziplines.js +++ /dev/null @@ -1,21 +0,0 @@ -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/models/Basejump.js b/models/Basejump.js deleted file mode 100644 index 15eddc5dac..0000000000 --- a/models/Basejump.js +++ /dev/null @@ -1,22 +0,0 @@ -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/Courseware.js b/models/Courseware.js index aa4f8ca5d4..66e67af545 100644 --- a/models/Courseware.js +++ b/models/Courseware.js @@ -16,7 +16,7 @@ var coursewareSchema = new mongoose.Schema({ tests: Array, challengeSeed: Array, completionMessage: String, // Congratulations! You've finished our HTML and CSS track! - challengeType: Number // 0 = html, 1 = javascript only, 2 = video + challengeType: Number // 0 = html, 1 = javascript only, 2 = video, 3 = bonfire, 4 = zipline, 5 = basejump }); -module.exports = mongoose.model('Courseware', coursewareSchema); \ No newline at end of file +module.exports = mongoose.model('Courseware', coursewareSchema); diff --git a/models/Story.js b/models/Story.js index d5d163ccf1..13f279de18 100644 --- a/models/Story.js +++ b/models/Story.js @@ -46,4 +46,4 @@ var storySchema = new mongoose.Schema({ } }); -module.exports = mongoose.model('Story', storySchema); \ No newline at end of file +module.exports = mongoose.model('Story', storySchema); diff --git a/models/Wiki.js b/models/Wiki.js new file mode 100644 index 0000000000..e7eb8a183b --- /dev/null +++ b/models/Wiki.js @@ -0,0 +1,15 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +var wikiSchema = new mongoose.Schema({ + name: { + type: String, + unique: false + }, + description: { + type: Array, + unique: false + } +}); + +module.exports = mongoose.model('Wiki', wikiSchema); diff --git a/models/Zipline.js b/models/Zipline.js deleted file mode 100644 index a8d49fd173..0000000000 --- a/models/Zipline.js +++ /dev/null @@ -1,21 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -/** - * - * @type {exports.Schema} - */ - - -var ziplineSchema = new mongoose.Schema({ - name: { - type: String, - unique: true - }, - picture: String, - video: String, - codepenLink: String, - details: Array -}); - -module.exports = mongoose.model('Zipline', ziplineSchema); diff --git a/seed_data/seed.js b/seed_data/seed.js index fcdb461528..f58fab7204 100644 --- a/seed_data/seed.js +++ b/seed_data/seed.js @@ -1,15 +1,17 @@ require('dotenv').load(); var Bonfire = require('../models/Bonfire.js'), Courseware = require('../models/Courseware.js'), + Wiki = require('../models/Wiki.js'), mongoose = require('mongoose'), secrets = require('../config/secrets'), coursewares = require('./coursewares.json'), + wikis = require('./wikis.json') bonfires = require('./bonfires.json'); mongoose.connect(secrets.db); var counter = 0; -var offerings = 2; +var offerings = 3; var CompletionMonitor = function() { counter++; @@ -56,3 +58,20 @@ Courseware.remove({}, function(err, data) { }); console.log('coursewares'); }); + +Wiki.remove({}, function(err, data) { + if (err) { + console.error(err); + } else { + console.log('Deleted ', data); + } + Wiki.create(wikis, function(err, data) { + if (err) { + console.log(err); + } else { + console.log('Saved ', data); + } + CompletionMonitor(); + }); + console.log('wikis'); +}); diff --git a/seed_data/wikis.json b/seed_data/wikis.json new file mode 100644 index 0000000000..3dec5421f8 --- /dev/null +++ b/seed_data/wikis.json @@ -0,0 +1,25 @@ +[ + { + "_id": "bd7158d9c442eddfaeb5bdef", + "name": "Global Control Shortcuts for Mac", + "description": [ + "

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

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

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

", + "
Start learning to code (it's free)
" + ] + } +] diff --git a/views/resources/control-shortcuts.jade b/views/resources/control-shortcuts.jade deleted file mode 100644 index 25b43a1011..0000000000 --- a/views/resources/control-shortcuts.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Global Control Shortcuts for Mac - h2 These Global Control Shortcuts for Mac will save you hours by speeding up your typing - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/107073108') - .text-left - h3 These global shortcuts work everywhere on a Mac: - ul - li Control + F = Forward - li Control + B = Backward - li Control + N = Next Line - li Control + P = Previous Line - li Control + H = Backspace - li Control + D = Delete - li Control + A = Beginning of Line - li Control + E = End of Line - li Control + K = Kill line - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/wiki/show.jade b/views/wiki/show.jade new file mode 100644 index 0000000000..6a44d6eaba --- /dev/null +++ b/views/wiki/show.jade @@ -0,0 +1,10 @@ +extends ../layout +block content + script. + var challengeName = 'Wiki View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center + h1= title + .panel-body + h1!= description From 23a3e235a740fcf099cd90a9e8d004b486f5a80e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 15:15:07 -0700 Subject: [PATCH 069/125] Update routes for all static pages and leave redirects for existing routes to get to new wiki routes --- app.js | 127 ++++++-- controllers/resources.js | 95 ++---- seed_data/wikis.json | 276 ++++++++++++++++++ views/resources/chromebook.jade | 10 - views/resources/deploy-a-website.jade | 25 -- views/resources/gmail-shortcuts.jade | 24 -- .../guide-to-our-nonprofit-projects.jade | 98 ------- views/resources/install-screenhero.jade | 13 - views/resources/javascript-in-your-inbox.jade | 12 - views/resources/live-pair-programming.jade | 56 ---- views/resources/nodeschool-challenges.jade | 22 -- .../nonprofit-project-instructions.jade | 23 -- views/resources/privacy.jade | 40 --- .../programmer-interview-questions-app.jade | 202 ------------- views/resources/stats.jade | 3 - views/wiki/show.jade | 8 +- 16 files changed, 412 insertions(+), 622 deletions(-) delete mode 100644 views/resources/chromebook.jade delete mode 100644 views/resources/deploy-a-website.jade delete mode 100644 views/resources/gmail-shortcuts.jade delete mode 100644 views/resources/guide-to-our-nonprofit-projects.jade delete mode 100644 views/resources/install-screenhero.jade delete mode 100644 views/resources/javascript-in-your-inbox.jade delete mode 100644 views/resources/live-pair-programming.jade delete mode 100644 views/resources/nodeschool-challenges.jade delete mode 100644 views/resources/nonprofit-project-instructions.jade delete mode 100644 views/resources/privacy.jade delete mode 100644 views/resources/programmer-interview-questions-app.jade delete mode 100644 views/resources/stats.jade diff --git a/app.js b/app.js index 08058238d7..2831eb851c 100644 --- a/app.js +++ b/app.js @@ -234,57 +234,121 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 })); */ app.get('/', homeController.index); -app.get('/privacy', resourcesController.privacy); + +app.get('/privacy', function(req, res) { + res.redirect(301, "/wiki/free-code-camp's-privacy-policy"); +}); + +app.get('/nonprofit-project-instructions', function(req, res) { + res.redirect(301, "/wiki/free-code-camp's-privacy-policy"); +}); + app.get('/jquery-exercises', resourcesController.jqueryExercises); + app.get('/chat', resourcesController.chat); -app.get('/live-pair-programming', resourcesController.livePairProgramming); -app.get('/install-screenhero', resourcesController.installScreenHero); -app.get('/javascript-in-your-inbox', resourcesController.javaScriptInYourInbox); -app.get('/guide-to-our-nonprofit-projects', resourcesController.guideToOurNonprofitProjects); -app.get('/chromebook', resourcesController.chromebook); -app.get('/deploy-a-website', resourcesController.deployAWebsite); -app.get('/gmail-shortcuts', resourcesController.gmailShortcuts); -app.get('/nodeschool-challenges', resourcesController.nodeSchoolChallenges); + +app.get('/live-pair-programming', function(req, res) { + res.redirect(301, '/wiki/live-stream-pair-programming-on-twitch.tv'); +}); + +app.get('/install-screenhero', function(req, res) { + res.redirect(301, '/wiki/install-screenhero'); +}); + +app.get('/guide-to-our-nonprofit-projects', function(req, res) { + res.redirect(301, '/wiki/a-guide-to-our-nonprofit-projects'); +}); + +app.get('/chromebook', function(req, res) { + res.redirect(301, '/wiki/chromebook'); +}); + +app.get('/deploy-a-website', function(req, res) { + res.redirect(301, '/wiki/deploy-a-website'); +}); + +app.get('/gmail-shortcuts', function(req, res) { + res.redirect(301, '/wiki/gmail-shortcuts'); +}); + +app.get('/nodeschool-challenges', function(req, res) { + res.redirect(301, '/wiki/nodeschool-challenges'); +}); + app.get('/stats', function(req, res) { res.redirect(301, '/learn-to-code'); }); -app.get('/news', function(req, res) { - res.redirect(301, '/stories/hot'); -}); -app.get('/learn-to-code', resourcesController.about); + app.get('/about', function(req, res) { res.redirect(301, '/learn-to-code'); }); + +app.get('/learn-to-code', resourcesController.about); + +app.get('/news', function(req, res) { + res.redirect(301, '/stories/hot'); +}); + app.get('/signin', userController.getSignin); + app.get('/login', function(req, res) { res.redirect(301, '/signin'); }); + app.post('/signin', userController.postSignin); + app.get('/signout', userController.signout); + app.get('/logout', function(req, res) { res.redirect(301, '/signout'); }); + app.get('/forgot', userController.getForgot); + app.post('/forgot', userController.postForgot); + app.get('/reset/:token', userController.getReset); + app.post('/reset/:token', userController.postReset); + app.get('/email-signup', userController.getEmailSignup); + app.get('/email-signin', userController.getEmailSignin); + app.post('/email-signup', userController.postEmailSignup); + app.post('/email-signin', userController.postSignin); + +/** + * Nonprofit Project routes. + */ + 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/are-there-people-that-are-already-benefiting-from-your-services', nonprofitController.areTherePeopleThatAreAlreadyBenefitingFromYourServices); + app.get('/nonprofits/in-exchange-we-ask', nonprofitController.inExchangeWeAsk); + app.get('/nonprofits/ok-with-javascript', nonprofitController.okWithJavaScript); + app.get('/nonprofits/how-can-free-code-camp-help-you', nonprofitController.howCanFreeCodeCampHelpYou); + app.get('/nonprofits/what-does-your-nonprofit-do', nonprofitController.whatDoesYourNonprofitDo); + app.get('/nonprofits/link-us-to-your-website', nonprofitController.linkUsToYourWebsite); + app.get('/nonprofits/tell-us-your-name', nonprofitController.tellUsYourName); + app.get('/nonprofits/tell-us-your-email', nonprofitController.tellUsYourEmail); + app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', nonprofitController.yourNonprofitProjectApplicationHasBeenSubmitted); + app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); app.get( @@ -297,11 +361,6 @@ app.post( passportConf.isAuthenticated, contactController.postDoneWithFirst100Hours ); -app.get( - '/nonprofit-project-instructions', - passportConf.isAuthenticated, - resourcesController.nonprofitProjectInstructions -); app.post( '/update-progress', passportConf.isAuthenticated, @@ -309,7 +368,7 @@ app.post( ); /** - * Main routes. + * Camper News routes. */ app.get( '/stories/hotStories', @@ -395,6 +454,7 @@ app.post( ); app.all('/account', passportConf.isAuthenticated); + app.get('/account/api', userController.getAccountAngular); /** @@ -402,7 +462,9 @@ app.get('/account/api', userController.getAccountAngular); */ app.get('/api/github', resourcesController.githubCalls); + app.get('/api/blogger', resourcesController.bloggerCalls); + app.get('/api/trello', resourcesController.trelloCalls); /** @@ -410,16 +472,24 @@ app.get('/api/trello', resourcesController.trelloCalls); */ app.get('/bonfires/getBonfireList', bonfireController.showAllBonfires); + app.get('/playground', bonfireController.index); + app.get('/bonfires', bonfireController.returnNextBonfire); + app.get('/bonfire-json-generator', bonfireController.returnGenerator); + app.post('/bonfire-json-generator', bonfireController.generateChallenge); + app.get('/bonfire-challenge-generator', bonfireController.publicGenerator); + app.post('/bonfire-challenge-generator', bonfireController.testBonfire) + app.get( '/bonfires/:bonfireName', bonfireController.returnIndividualBonfire ); + app.get('/bonfire', function(req, res) { res.redirect(301, '/playground'); }); @@ -445,24 +515,36 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); */ app.get('/challenges/', coursewareController.returnNextCourseware); + app.get('/challenges/getCoursewareList', coursewareController.showAllCoursewares); + app.get( '/challenges/:coursewareName', coursewareController.returnIndividualCourseware ); + app.post('/completed-courseware/', coursewareController.completedCourseware); + app.post('/completed-zipline-or-basejump', coursewareController.completedZiplineOrBasejump); // Unique Check API route app.get('/api/checkUniqueUsername/:username', userController.checkUniqueUsername); + app.get('/api/checkExistingUsername/:username', userController.checkExistingUsername); + app.get('/api/checkUniqueEmail/:email', userController.checkUniqueEmail); + app.get('/account', userController.getAccount); + app.post('/account/profile', userController.postUpdateProfile); + app.post('/account/password', userController.postUpdatePassword); + app.post('/account/delete', userController.postDeleteAccount); + app.get('/account/unlink/:provider', userController.getOauthUnlink); + app.get('/sitemap.xml', resourcesController.sitemap); /** * OAuth sign-in routes. @@ -474,6 +556,7 @@ var passportOptions = { }; app.get('/auth/twitter', passport.authenticate('twitter')); + app.get( '/auth/twitter/callback', passport.authenticate('twitter', { @@ -507,6 +590,7 @@ app.get( ); app.get('/auth/github', passport.authenticate('github')); + app.get( '/auth/github/callback', passport.authenticate('github', passportOptions), function (req, res) { @@ -518,6 +602,7 @@ app.get( '/auth/google', passport.authenticate('google', {scope: 'profile email'}) ); + app.get( '/auth/google/callback', passport.authenticate('google', passportOptions), function (req, res) { @@ -525,10 +610,6 @@ app.get( } ); -app.get('/induce-vomiting', function(req, res, next) { - next(new Error('vomiting induced')); -}); - // put this route last app.get( '/:username', diff --git a/controllers/resources.js b/controllers/resources.js index adb4606a72..8fa984e6ff 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -78,87 +78,18 @@ module.exports = { }); }, - 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" }); }, - nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { - res.render('resources/nonprofit-project-instructions', { - title: 'Nonprofit Project Instructions' - }); - }, - - gmailShortcuts: function gmailShortcuts(req, res) { - res.render('resources/gmail-shortcuts', { - title: 'These Gmail Shortcuts will save you Hours' - }); - }, - - guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { - res.render('resources/guide-to-our-nonprofit-projects', { - title: 'A guide to our Nonprofit Projects' - }); - }, - - chromebook: function chromebook(req, res) { - res.render('resources/chromebook', { - title: 'Win a Chromebook' - }); - }, - jqueryExercises: function jqueryExercises(req, res) { res.render('resources/jquery-exercises', { title: 'jQuery Exercises' }); }, - livePairProgramming: function(req, res) { - res.render('resources/live-pair-programming', { - title: 'Live Pair Programming' - }); - }, - - installScreenHero: function(req, res) { - res.render('resources/install-screenhero', { - title: 'Install ScreenHero' - }); - }, - - javaScriptInYourInbox: function(req, res) { - res.render('resources/javascript-in-your-inbox', { - title: 'JavaScript in your Inbox' - }); - }, - - nodeSchoolChallenges: function(req, res) { - res.render('resources/nodeschool-challenges', { - title: 'NodeSchool Challenges' - }); - }, - - 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)); - }); - }, - 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) { @@ -177,6 +108,7 @@ module.exports = { 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); } @@ -270,7 +202,6 @@ module.exports = { }); }, - randomPhrase: function() { var phrases = resources.phrases; return phrases[Math.floor(Math.random() * phrases.length)]; @@ -300,6 +231,7 @@ module.exports = { return elem._id; }); }, + allBonfireNames: function() { return bonfires.map(function(elem) { return { @@ -319,6 +251,25 @@ module.exports = { }); }, + 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; @@ -338,6 +289,7 @@ module.exports = { return elem._id; }); }, + allCoursewareNames: function() { return coursewares.map(function(elem) { return { @@ -356,9 +308,11 @@ module.exports = { }; }); }, + whichEnvironment: function() { return process.env.NODE_ENV; }, + getURLTitle: function(url, callback) { (function () { var result = {title: '', image: '', url: '', description: ''}; @@ -379,6 +333,7 @@ module.exports = { }); })(); }, + updateUserStoryPictures: function(userId, picture, username, cb) { var counter = 0, diff --git a/seed_data/wikis.json b/seed_data/wikis.json index 3dec5421f8..f356bfdfff 100644 --- a/seed_data/wikis.json +++ b/seed_data/wikis.json @@ -21,5 +21,281 @@ " ", "Start learning to code (it's free)
" ] + }, + { + "_id": "bd7158d9c443eddfaeb5bdef", + "name": "Win a Chromebook for Christmas", + "description": [ + "

Thanks to everyone who participated in our Chromebook giveaway.

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

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


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

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

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

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

", + "

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

", + "

We will add you to our  Nonprofit Project Trello board.

", + "

Starting with the end in mind

", + "

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

", + "

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

", + "

Choosing your first Nonprofit Project

", + "

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

", + "

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

", + "

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

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

", + "

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

", + "

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

", + "

Finalizing the Project

", + "

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

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

", + "

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

", + "

Nonprofit Stakeholders

", + "

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

", + "

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

", + "

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

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

", + "

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

", + "

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

", + "

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

", + "

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

", + "

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

", + "

Working with your Pair

", + "

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

", + "

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

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

", + "

Setting up your Development Environment

", + "

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

", + "

The benefits of using this virtual machine are as follows:

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

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

", + "

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

", + "

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

", + "

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

", + "

Hosting Apps

", + "

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

", + "

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

", + "

Maintaining Apps

", + "

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

", + "

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

", + "

Office Hours

", + "

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

", + "

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

", + "

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

", + "

Pledging to finish the project

", + "

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

", + "

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

", + "

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

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

Download for Mac

", + "

Download for Windows

", + "

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

", + "

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

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

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

Live Pair Programming

", + "

", + "

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

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

Previous Live Pair Programming Sessions

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

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

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

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

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

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

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

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

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

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

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

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


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

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

", + "

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

", + "

Please do the following immediately:

", + "

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

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

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

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

", + "

Personally Identifiable Information

", + "

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

", + "

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

", + "

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

", + "

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

", + "

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

", + "

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

", + "

Anonymous Information

", + "

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

", + "

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

", + "

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

", + "

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

", + "

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

", + "

Security

", + "

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

", + "

Surveys

", + "

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

", + "

Copyright

", + "

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

", + "

Contacting Us

", + "

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

", + "

Links to Other Web sites

", + "

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

", + "

Data Retention

", + "

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

", + "

Business Transfers

", + "

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

", + "

Your California Privacy Rights

", + "

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

", + "

Acceptance of Privacy Policy Terms and Conditions

", + "

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

", + "

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

", + "
" + ] } ] diff --git a/views/resources/chromebook.jade b/views/resources/chromebook.jade deleted file mode 100644 index 620d4698ce..0000000000 --- a/views/resources/chromebook.jade +++ /dev/null @@ -1,10 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Win a Chromebook - h2 Thanks to everyone who participated in our Chromebook giveaway. - script(src='//widget-prime.rafflecopter.com/launch.js') - a#rcwidget_a7khonhd.rcptr(href='http://www.rafflecopter.com/rafl/display/d70901b10/', rel='nofollow', data-raflid='d70901b10', data-theme='classic', data-template='') a Rafflecopter giveaway - h2 Ready to learn to code? - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/deploy-a-website.jade b/views/resources/deploy-a-website.jade deleted file mode 100644 index bd5a156fb4..0000000000 --- a/views/resources/deploy-a-website.jade +++ /dev/null @@ -1,25 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Deploy a Dynamic Website in 7 Minutes - h2 Here's a fast and easy way to deploy a dynamic website to the internet - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/115194017') - .text-left - h3 Use these links: - ul - li - a(href='http://www.atom.io' target='_blank') http://www.atom.io - |   - free text editor - li - a(href='http://www.startbootstrap.com' target='_blank') http://www.startbootstrap.com - |   - free responsive (Bootstrap) templates - li - a(href='http://www.powr.io' target='_blank') http://www.powr.io - |   - great plugins - li - a(href='http://www.bitballoon.com' target='_blank') http://www.bitballoon.com - |   - drag-and-drop deployment - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/gmail-shortcuts.jade b/views/resources/gmail-shortcuts.jade deleted file mode 100644 index 637d187108..0000000000 --- a/views/resources/gmail-shortcuts.jade +++ /dev/null @@ -1,24 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Gmail Shortcuts - h2 These Gmail Shortcuts will save you hours and help you get to Zero Inbox - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/115194016') - .text-left - h3 The shortcuts: - ul - li j - move down - li k - move up - li o - open - li r - reply - li a - reply all - li f - forward - li c - compose - li x - select - li e - archive - li ! - mark spam - li z - undo - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/guide-to-our-nonprofit-projects.jade b/views/resources/guide-to-our-nonprofit-projects.jade deleted file mode 100644 index 82b038cd55..0000000000 --- a/views/resources/guide-to-our-nonprofit-projects.jade +++ /dev/null @@ -1,98 +0,0 @@ -extends ../layout -block content - .jumbotron - h1.hug-top.text-center A Guide to our Nonprofit Projects - br - p Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp challenges and Bonfire challenges, you'll begin this process. - p Once you've finished all the challenges, click the "I'm done with all the challenges" button, which will become enabled. We will prompt you for your email address, then give you further instructions on our   - a(href="http://www.freecodecamp.com/nonprofit-project-instructions") Nonprofit Projects Instructions page - | . - p We will add you to our   - a(href="https://trello.com/b/BA3xVpz9/nonprofit-projects") Nonprofit Project Trello board - | . - h2 Starting with the end in mind - p Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction). - p You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects. - h2 Choosing your first Nonprofit Project - p We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates. - p Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week (on the   - a(href="http://goo.gl/forms/f61dLt67t8") form you filled out - | ). If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks. - p Our team of nonprofit project camp counselors will match you and your pair based on: - ol - li Your estimated time commitment (10, 20 or 40 hours per week) - li Your time zone - li The nonprofit projects you've chosen - li Prior coding experience (we'd like both campers to be able to contribute equally) - p We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace. - p You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project Trello cards. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one. - h2 Finalizing the Project - p Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process: - ol - li We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above. - li We'll call the stakeholder to confirm once again that he or she agrees with our   - a(href="freecodecamp.com/nonprofits") terms   - | and has signed our   - a(href="http://goo.gl/forms/0YKkd9bpcR") Nonprofit Project Stakeholder Pledge - | . - li We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder. - li If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project. - p This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time. - h2 Nonprofit Stakeholders - p Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a "stakeholder" - an authorative person who understands the organization and its needs for this particular project. - p Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs. - p When you and your pair first speak with your nonprofit stakeholder, you'll: - ul - li talk at length to better understand their needs. - li create a new Trello board and use it to prioritize what needs to be built. - li and establish deadlines based on your weekly time commitment, and how long you think each task will take. - p It's notoriously difficult to estimate how long building software projects will take, so feel free to ask camp counselors for help. - p You'll continue to meet with your stakeholder at least twice a month in your project's Gitter channel. - p You should also ask questions in your project's Gitter channel as they come up throughout the week, and your stakeholder can answer them asynchronously. - p Getting "blocked" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter. - p Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio! - h2 Working with your Pair - p You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time. - p Here are our recommended ways of collaborating: - ul - li • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email. - li • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them. - li • Screen Hero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming. - li • Write clear and readable code, commit messages, branch names, and pull request messages. - h2 Setting up your Development Environment - p We've created a custom virtual machine image with Ubuntu Linux, Git, Team Viewer, the MEAN Stack and all its dependencies. You can run this virtual image on any computer with at least 2 gigabytes of RAM and 16 gigabytes of hard drive space. - p The benefits of using this virtual machine are as follows: - ul - li • Everyone else on Free Code Camp is using this image, so we can all help you troubleshoot various problems that may arise. - li • When you pair program, you and your pair will have the exact same environment, which means you will both feel comfortable on each other's machines. - li • You can install the image on any computer without worrying about messing up the computer's original data or configuration. - li • Even if you end up using Windows or Mac OSX for development later, your server will almost certainly run Linux, so it's worth getting used to Linux. - li • Even experienced developers encounter hangups when setting up a development environment. This virtual machine image will remove this tedious process. - p Install a bit torrent client, then   - a(href="http://mgnet.me/ZOQk0rd") download our virtual machine image - | . - p Please note that even though Bit Torrent is often used to download content illegally, all the content on our image is open source and perfectly legal to redistribute. - p Once you've downloaded the file,   - a(href="https://www.virtualbox.org/wiki/Downloads") download VirtualBox   - | and follow   - a(href="http://techathlon.com/how-to-run-a-vmdk-file-in-oracle-virtualbox/") this tutorial   - | to open the image in VirtualBox. You'll want to assign the virtual machine at least two gigabytes of ram. - p Now you have your own Linux development environment. You can shut it down when you're not using it and it will save its state. Please continue to seed the file in bit torrent so that other campers can download it as well. Enjoy! - h2 Hosting Apps - p Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away. - p If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them. - h2 Maintaining Apps - p Once you complete a nonprofit project, your obligation to its stakeholder is finished. You goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical "super user"). - p While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers. - h2 Office Hours - p Quincy Larson and/or Michael Johnson will be in the   - a(href="https://gitter.im/FreeCodeCamp/NonprofitProjects") Gitter Nonprofit Project Channel   - | every Monday and Thursday from 9 - 10 p.m. EST. - p Our goal is to make the discussion as public as possible so all our campers can benefit from each camper’s questions. - p If necessary, we can also hop on Screen Hero with you to help you with issues more specific to your project. - h2 Pledging to finish the project - p Your nonprofit stakeholder, your pair, and the volunteer camp counselor team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one. - p To confirm that you understand the seriousness of this commitment, we require that all campers   - a(href="http://goo.gl/forms/ZMn96z2QqY") sign this pledge   - | before starting on their nonprofit projects. - p There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, "Real artists ship." And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career. \ No newline at end of file diff --git a/views/resources/install-screenhero.jade b/views/resources/install-screenhero.jade deleted file mode 100644 index cf0881ada2..0000000000 --- a/views/resources/install-screenhero.jade +++ /dev/null @@ -1,13 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Install ScreenHero - h2 - a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjowLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLnppcD9zb3VyY2U9d2ViIn0=") Download for Mac - h2 - a(href="http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjoxLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLXNldHVwLmV4ZSJ9") Download for Windows - p You can learn more about using Screen Hero by taking   - a(href="http://www.freecodecamp.com/challenges/34") Challenge 34. - p Screen Hero was recently acquired by a collaboration tool called Slack. It's still available and free, but will go away in the indefinite future. Discuss alternatives on our   - a(href="http://forum.freecodecamp.com/t/replacing-screen-hero/992") Screen Hero replacement thread - | . \ No newline at end of file diff --git a/views/resources/javascript-in-your-inbox.jade b/views/resources/javascript-in-your-inbox.jade deleted file mode 100644 index 4bc92a5025..0000000000 --- a/views/resources/javascript-in-your-inbox.jade +++ /dev/null @@ -1,12 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top Win a Chromebook - h2 Sign up for Inbox.js - JavaScript challenges in your inbox - and enter to win a Chromebook! - img.image-responsive(src="https://s3.amazonaws.com/freecodecamp/chromebook.jpg" alt="HP Chromebook 11") - script(src='//widget-prime.rafflecopter.com/launch.js') - a#rcwidget_a7khonhd.rcptr(href='http://www.rafflecopter.com/rafl/display/d70901b10/', rel='nofollow', data-raflid='d70901b10', data-theme='classic', data-template='') a Rafflecopter giveaway - .animated.zoomInDown.delay-10 - p Finished signing up for the giveaway? - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br diff --git a/views/resources/live-pair-programming.jade b/views/resources/live-pair-programming.jade deleted file mode 100644 index ba2a4c0094..0000000000 --- a/views/resources/live-pair-programming.jade +++ /dev/null @@ -1,56 +0,0 @@ -extends ../layout-wide -block content - - .text-center - h1 Live Pair Programming - - - h2#next-session - - h2 Watch the live stream below or on our   - a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel - | . - .row - .col-md-8.col-xs-12 - .embed-responsive.embed-responsive-16by9 - iframe(src='http://www.twitch.tv/freecodecamp/embed', frameborder='0', scrolling='no') - .col-md-4.col-xs-12 - .visible-sm.visible-xs - .embed-responsive.embed-responsive-16by9 - iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') - .visible-md.visible-lg - .embed-responsive.embed-responsive-twitch-chat - iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') - h1 Previous Live Pair Programming Sessions - .col-xs-12 - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE - .embed-responsive.embed-responsive-16by9.big-break - iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA') - h3.wrappable link:   - a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA - h3 Got 3 minutes? Learn to code with us! - a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) - br - br - br - br - br - script(src="/js/lib/moment/moment.js") - script(src="/js/lib/moment/nextTuesday.js") - script. - $('#next-session').text(nextSession()); diff --git a/views/resources/nodeschool-challenges.jade b/views/resources/nodeschool-challenges.jade deleted file mode 100644 index cc8f48f736..0000000000 --- a/views/resources/nodeschool-challenges.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layout -block content - .jumbotron.text-center - h1.hug-top NodeSchool Challenges - h2 Learn Node.js, NPM, Express.js, and advanced JavaScript like Functional Programming and Promises - br - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/122719685') - .text-left - h3 Here are the NodeSchool courses you should complete: - ol - li - a(href='http://runnable.com/VQuO_Y4BbkhaOOsV/nodeschool-io-s-learnyounode-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's LearnYouNode course - li - a(href='http://runnable.com/VQt7deuMe6RZ3Gcl/nodeschool-io-s-learn-to-npm-course-running-in-your-browser-for-node-js-and-hello-world' target='_blank') NodeSchool.io's Learn-to-NPM course - li - a(href='http://runnable.com/VQufnaRAlaNc9JuM/nodeschool-io-s-express-js-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's Express.js course - li - a(href='http://runnable.com/VQuZjvia8Gxcqkpy/nodeschool-io-s-functional-programming-in-javascript-course-available-in-your-browser-for-node-js-and-freecodecamp' target='_blank') NodeSchool.io's Functional Programming in JavaScript course - li - a(href='http://runnable.com/VQunH26qdytcbLBg/nodeschool-io-s-promise-it-won-t-hurt-promises-course-available-in-your-browser-for-node-js-javascript-and-freecodecamp' target='_blank') NodeSchool.io's Promise It Won't Hurt Promises course - br \ No newline at end of file diff --git a/views/resources/nonprofit-project-instructions.jade b/views/resources/nonprofit-project-instructions.jade deleted file mode 100644 index 3043e41668..0000000000 --- a/views/resources/nonprofit-project-instructions.jade +++ /dev/null @@ -1,23 +0,0 @@ -extends ../layout -block content - .jumbotron - .col-sm-offset-1 - h1 Nonprofit projects - h3 It's time to apply what you've learned here at Free Code Camp. - h3 By the end of this process, you'll have a portfolio of live apps being used by real people. - h3 Please do the following immediately: - h4 - ol - li Complete this form:   - a(href="http://goo.gl/forms/f61dLt67t8" target="_blank") http://goo.gl/forms/f61dLt67t8 - | . - li Read this document, which will answer many questions you may have about our nonprofit projects:   - a(href="/guide-to-our-nonprofit-projects" target="_blank") http://freecodecamp.com/guide-to-our-nonprofit-projects - | . - li We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you. - li Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp "exit test". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look:  - a(href="mailto:team@freecodecamp.com") team@freecodecamp.com - | . - h4 Please email us if you have further questions:   - a(href="mailto:team@freecodecamp.com") team@freecodecamp.com - | . \ No newline at end of file diff --git a/views/resources/privacy.jade b/views/resources/privacy.jade deleted file mode 100644 index 54278c5b57..0000000000 --- a/views/resources/privacy.jade +++ /dev/null @@ -1,40 +0,0 @@ -extends ../layout -block content - .jumbotron - h2.big-text Privacy - html. -

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

-

Personally Identifiable Information

-

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

-

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

-

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

-

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

-

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

-

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

-

Anonymous Information

-

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

-

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

-

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

-

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

-

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

-

Security

-

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

-

Surveys

-

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

-

Copyright

-

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

-

Contacting Us

-

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

-

Links to Other Web sites

-

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

-

Data Retention

-

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

-

Business Transfers

-

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

-

Your California Privacy Rights

-

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

-

Acceptance of Privacy Policy Terms and Conditions

-

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

-

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

- script. - var challengeName = 'Privacy'; diff --git a/views/resources/programmer-interview-questions-app.jade b/views/resources/programmer-interview-questions-app.jade deleted file mode 100644 index 14dbada372..0000000000 --- a/views/resources/programmer-interview-questions-app.jade +++ /dev/null @@ -1,202 +0,0 @@ -extends ../layout -block content - script. - $(document).ready(function () { - var directions = { - 0: "To get started, open your Chrome DevTools. The #next-exercise button is disabled below. Try using jQuery's .attr() method to turn the disabled attribute to false.", - 1: "Move the .target element from #location1 to #location2.", - 2: "Change the background color of .target to red.", - 3: "Change the background color of the even-numbered targets to red.", - 4: "Change the background color of the target4 to red.", - 5: "Clone the target2 in #location1 so that it also exists in #location2.", - 6: "Remove the target3 from element from #location1.", - 7: "Check the following checkboxes using jQuery.", - 8: "Make the text input field read-only.", - 9: "Select the target2 option in the select box.", - 10: "Add the following css classes to .target: 'animated' and 'hinge'.", - 11: "Use jQuery to read the data of .target.", - 12: "Use 'length' to count the number of child elements in #location1, then display that value in #location2.", - 13: "There's an element hidden in #location1. Show it using jQuery, and then click on it." - }; - var hint = { - 0: "$('#next-exercise').attr('disabled', false);", - 1: "$('.target').appendTo('#location2');", - 2: "$('.target').css('background', 'red');", - 3: "$('.target:even').css('background', 'red');", - 4: "$('.target:nth-child(4)').css('background', 'red');", - 5: "$('.target:nth-child(2)').clone().appendTo($('#location2'));", - 6: "$('.target:nth-child(3)').remove();", - 7: "$('#location1 input').attr('checked', 'true')", - 8: "$('#location1 input').attr('readonly', 'true')", - 9: "$('#location1 select').val('target2');", - 10: "$('.target').addClass('animated hinge');", - 11: "$('.target').data();", - 12: "$('#location2').text($('#location1').children().length)", - 13: "$('#finished-button').show().click();" - }; - var elements = { - 0: "", - 1: "
.target
", - 2: "
.target
", - 3: "
target0
target1
target2
target3
target4
", - 4: "
target1
target2
target3
target4
target5
", - 5: "
target1
target2
target3
target4
target5
", - 6: "
target1
target2
target3
target4
target5
", - 7: "checkbox1
checkbox2", - 8: "", - 9: "", - 10: "
.target
", - 11: "
.target
", - 12: "
target1
target2
target3
target4
target5
", - 13: "
Finish!
" - }; - - function refreshEverything() { - $('#directions').text("Exercise " + currentExercise + ": " + directions[currentExercise]); - $('#location1').html(elements[currentExercise]); - $('#hint').text(hint[currentExercise]); - handleExerciseTransition(); - } - - $('#exercise-directory').on('click', 'li', event, function () { - currentExercise = $(this).index(); - event.preventDefault(); - refreshEverything(event); - }); - $('#next-exercise').on('click', event, function () { - ++currentExercise; - event.preventDefault(); - refreshEverything(event); - }); - $('#solution-button').on('click', function () { - $('#hint-modal').modal({backdrop: "static"}); - }); - $('#location1').on('click', '#finished-button', function () { - $('#finished-modal').modal({backdrop: "static"}); - }); - function handleExerciseTransition() { - if (currentExercise === 0) { - $('#next-exercise').attr('disabled', true); - } else { - $('#next-exercise').attr('disabled', false); - } - if (currentExercise === 2 || currentExercise === 6) { - $('#location2 .target').remove(); - } - if (currentExercise === 13) { - $('#location2').text(''); - $('#finished-button').hide(); - $('#next-exercise').attr('disabled', true); - } - } - - var currentExercise = 0; - refreshEverything(currentExercise); - }); - style. - #directions { - text-align: left; - font-size: 15px; - } - - .well { - text-align: left; - height: 200px; - } - - #exercise-directory { - font-size: 20px; - } - - #current-exercise { - text-size: 250px; - } - html. - - - diff --git a/views/resources/stats.jade b/views/resources/stats.jade deleted file mode 100644 index 71659d344a..0000000000 --- a/views/resources/stats.jade +++ /dev/null @@ -1,3 +0,0 @@ -extends ../layout -block content - include ../partials/stats \ No newline at end of file diff --git a/views/wiki/show.jade b/views/wiki/show.jade index 6a44d6eaba..9143e6a592 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -7,4 +7,10 @@ block content .panel-heading.text-center h1= title .panel-body - h1!= description + div!= description + .row + .col-xs-12.text-center + if !user + h3 Got 3 minutes? Learn to code with us! + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) + .spacer From 106a10edcaca310c2730881d542dc8625807257c Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 15:55:00 -0700 Subject: [PATCH 070/125] add a modal that allows one to scroll through all wiki pages --- app.js | 4 +++- controllers/resources.js | 19 +++++-------------- controllers/wiki.js | 1 + seed_data/wikis.json | 6 +++--- views/partials/wikis.jade | 24 ++++++++++++++++++++++++ views/wiki/show.jade | 12 +++++++++++- 6 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 views/partials/wikis.jade diff --git a/app.js b/app.js index 2831eb851c..1cd47c6a8e 100644 --- a/app.js +++ b/app.js @@ -473,6 +473,8 @@ app.get('/api/trello', resourcesController.trelloCalls); app.get('/bonfires/getBonfireList', bonfireController.showAllBonfires); +app.get('/wiki/getWikiList', wikiController.showAllWikis); + app.get('/playground', bonfireController.index); app.get('/bonfires', bonfireController.returnNextBonfire); @@ -483,7 +485,7 @@ app.post('/bonfire-json-generator', bonfireController.generateChallenge); app.get('/bonfire-challenge-generator', bonfireController.publicGenerator); -app.post('/bonfire-challenge-generator', bonfireController.testBonfire) +app.post('/bonfire-challenge-generator', bonfireController.testBonfire); app.get( '/bonfires/:bonfireName', diff --git a/controllers/resources.js b/controllers/resources.js index 8fa984e6ff..b173c4c54d 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -3,12 +3,14 @@ var async = require('async'), Challenge = require('./../models/Challenge'), Bonfire = require('./../models/Bonfire'), Story = require('./../models/Story'), + Wiki = require('./../models/Wiki'), 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'), + wikis = require('../seed_data/wikis.json'), moment = require('moment'), https = require('https'), debug = require('debug')('freecc:cntr:resources'), @@ -251,23 +253,12 @@ module.exports = { }); }, - allBonfireNames: function() { - return bonfires.map(function(elem) { + allWikiNames: function() { + return wikis.map(function(elem) { return { - name: elem.name, - difficulty: elem.difficulty, - _id: elem._id + name: elem.name } }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map (function(elem) { - return { - name : elem.name, - _id: elem._id - } - }); }, getAllCourses: function() { diff --git a/controllers/wiki.js b/controllers/wiki.js index 6172e0f225..f7bf1a22c4 100644 --- a/controllers/wiki.js +++ b/controllers/wiki.js @@ -35,6 +35,7 @@ exports.returnIndividualWiki = function(req, res, next) { }; exports.showAllWikis = function(req, res) { + var data = {}; data.wikiList = resources.allWikiNames(); res.send(data); }; diff --git a/seed_data/wikis.json b/seed_data/wikis.json index f356bfdfff..853b3391bd 100644 --- a/seed_data/wikis.json +++ b/seed_data/wikis.json @@ -3,7 +3,8 @@ "_id": "bd7158d9c442eddfaeb5bdef", "name": "Global Control Shortcuts for Mac", "description": [ - "

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

", + "
", + "

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

", "
", "
", "

These global shortcuts work everywhere on a Mac:", @@ -18,8 +19,7 @@ "
  • Control + E = End of Line
  • ", "
  • Control + K = Kill line
  • ", " ", - "

    ", - "
    Start learning to code (it's free)
    " + "
    " ] }, { diff --git a/views/partials/wikis.jade b/views/partials/wikis.jade new file mode 100644 index 0000000000..be4fd2b3cd --- /dev/null +++ b/views/partials/wikis.jade @@ -0,0 +1,24 @@ +h3 + ol#bonfireList + script(src='/js/lib/ramda/ramda.min.js') + script. + var getLinkedName = function getLinkedName(name) { + // ensure name is a string + name = name + ''; + return name.toLowerCase().replace(/\s/g, '-'); + } + $.ajax({ + url: '/wiki/getWikiList', + type: 'GET' + }) + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.wikiList.length; i++) { + var li = document.createElement("li"); + var linkedName = getLinkedName(data.wikiList[i].name); + $(li).html("" + data.wikiList[i].name + ""); + docfrag.appendChild(li); + }; + $('#bonfireList').append(docfrag); + }); diff --git a/views/wiki/show.jade b/views/wiki/show.jade index 9143e6a592..c8bc601c78 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -8,9 +8,19 @@ block content h1= title .panel-body div!= description + .spacer + .text-center + #showAllButton.btn.btn-info.btn-big Show all Wiki Articles + .spacer .row .col-xs-12.text-center if !user - h3 Got 3 minutes? Learn to code with us! a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) .spacer + #all-challenges-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Wiki Articles + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/wikis From cdc5af4194221d66b71ec04a0f41afd1cb1f484a Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 16:30:12 -0700 Subject: [PATCH 071/125] fix the challenges show all challenges button --- controllers/courseware.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 6e716c79cb..b7ba94b077 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -13,12 +13,11 @@ var _ = require('lodash'), exports.showAllCoursewares = function(req, res) { var completedList = []; if(req.user) { - completedList = req.user.completedList.map(function (elem) { + completedList = req.user.completedCoursewares.map(function (elem) { return elem._id; }); } - - var noDuplicatedCoursewares = R.uniq(completedCoursewares); + var noDuplicatedCoursewares = R.uniq(completedList); var data = {}; data.coursewareList = resources.allCoursewareNames(); data.completedList = noDuplicatedCoursewares; From acea52dc1b3272a5969691197c693563b9f255b5 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 18:02:50 -0700 Subject: [PATCH 072/125] make further improvements to wiki and start new show view for nonprofits --- app.js | 16 +++- controllers/nonprofits.js | 45 ++++++++--- controllers/wiki.js | 30 +++++++- package.json | 2 +- seed_data/wikis.json | 134 +++++++++++++++++++++++++++++++++ views/nonprofits/show.jade | 26 +++++++ views/partials/nonprofits.jade | 24 ++++++ views/partials/wikis.jade | 6 +- 8 files changed, 267 insertions(+), 16 deletions(-) create mode 100644 views/nonprofits/show.jade create mode 100644 views/partials/nonprofits.jade diff --git a/app.js b/app.js index 1cd47c6a8e..3d6e59fcc9 100644 --- a/app.js +++ b/app.js @@ -351,6 +351,16 @@ app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', non app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); +app.get( + '/nonprofits/:nonprofitName', + nonprofitController.returnIndividualNonprofit +); + +app.get( + '/nonprofits', + nonprofitController.showAllNonprofits +); + app.get( '/done-with-first-100-hours', passportConf.isAuthenticated, @@ -496,6 +506,8 @@ app.get('/bonfire', function(req, res) { res.redirect(301, '/playground'); }); +app.post('/completed-bonfire/', bonfireController.completedBonfire); + /** * Wiki related routes */ @@ -507,10 +519,9 @@ app.get( app.get( '/wiki', - wikiController.showAllWikis + wikiController.returnHomeWiki ); -app.post('/completed-bonfire/', bonfireController.completedBonfire); /** * Courseware related routes @@ -548,6 +559,7 @@ app.post('/account/delete', userController.postDeleteAccount); app.get('/account/unlink/:provider', userController.getOauthUnlink); app.get('/sitemap.xml', resourcesController.sitemap); + /** * OAuth sign-in routes. */ diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 4ceda4de1e..502dd05b56 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -1,17 +1,8 @@ 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) { @@ -99,3 +90,39 @@ exports.otherSolutions = function(req, res) { title: 'Here are some other possible solutions for you' }); }; + +exports.returnIndividualNonprofit = function(req, res, next) { + var dashedName = req.params.nonprofitName; + + var nonprofitName = dashedName.replace(/\-/g, ' '); + + Nonprofit.find({'name': new RegExp(nonprofitName, 'i')}, function(err, nonprofit) { + if (err) { + next(err); + } + + if (nonprofit.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a nonprofit with that name. Please double check the name." + }); + + return res.redirect('/nonprofits'); + } + + nonprofit = nonprofit.pop(); + var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../nonprofit/' + dashedNameFull); + } + res.render('nonprofits/show', { + title: nonprofit.name, + description: nonprofit.description.join('') + }); + }); +}; + +exports.showAllNonprofits = function(req, res) { + var data = {}; + data.nonprofitList = resources.allNonprofitNames(); + res.send(data); +}; diff --git a/controllers/wiki.js b/controllers/wiki.js index f7bf1a22c4..360b748409 100644 --- a/controllers/wiki.js +++ b/controllers/wiki.js @@ -23,7 +23,7 @@ exports.returnIndividualWiki = function(req, res, next) { } wiki = wiki.pop(); - var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-'); + var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); if (dashedNameFull != dashedName) { return res.redirect('../wiki/' + dashedNameFull); } @@ -39,3 +39,31 @@ exports.showAllWikis = function(req, res) { data.wikiList = resources.allWikiNames(); res.send(data); }; + +exports.returnHomeWiki = function(req, res) { + var dashedName = req.params.wikiName; + + Wiki.find({'name': 'A Guide to our Wiki'}, function(err, wiki) { + if (err) { + next(err); + } + + if (wiki.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a wiki entry with that name. Please double check the name." + }); + + return res.redirect('/wiki'); + } + + wiki = wiki.pop(); + var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull != dashedName) { + return res.redirect('../wiki/' + dashedNameFull); + } + res.render('wiki/show', { + title: wiki.name, + description: wiki.description.join('') + }); + }); +}; diff --git a/package.json b/package.json index b535c5739b..f3209e0e29 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "method-override": "^2.3.0", "moment": "^2.8.4", "mongodb": "^1.4.33", - "mongoose": "^4.0.0", + "mongoose": "^4.0.1", "mongoose-long": "0.0.2", "morgan": "^1.5.0", "newrelic": "^1.13.3", diff --git a/seed_data/wikis.json b/seed_data/wikis.json index 853b3391bd..1236041247 100644 --- a/seed_data/wikis.json +++ b/seed_data/wikis.json @@ -1,4 +1,138 @@ [ + { + "_id": "bd7158d9c441eddfaeb5bdef", + "name": "A Guide to our Wiki", + "description": [ + "
    ", + "

    We have a variety of resources to answer your many questions.

    ", + "

    Click the \"Show all Wiki Articles\" button below and browse the topics we cover

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

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

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

    ", + "

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

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

    ", + "

    You'll spend the last half of Free Code Camp using Agile Methodologies and Full Stack JavaScript to build projects for nonprofits:", + "

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

    ", + "
    " + ] + }, + { + "_id": "bd7158d9c439eddfaeb5bdef", + "name": "Will I be ready to get a software engineer job after this?", + "description": [ + "
    ", + "

    At the end of Free Code Camp, you will have pair programmed around 1,000 hours with dozens of other students, built a portfolio of projects that people are actively using, and a roster of glowing references from nonprofits you've helped. This is more coding than most coding bootcamps provide, and on average, 75% of bootcamp graduates get software engineering jobs within 6 months, and earn an average annual salary of $76,000.

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

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

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

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

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

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

    ", + "

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

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

    We are completely free for both students and for nonprofits.

    ", + "

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

    ", + "

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

    ", + "

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

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

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

    ", + "

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

    ", + "
    " + ] + }, { "_id": "bd7158d9c442eddfaeb5bdef", "name": "Global Control Shortcuts for Mac", diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade new file mode 100644 index 0000000000..d5628c415b --- /dev/null +++ b/views/nonprofits/show.jade @@ -0,0 +1,26 @@ +extends ../layout +block content + script. + var challengeName = 'Nonprofits View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center + h1= title + .panel-body + div!= description + .spacer + .text-center + #showAllButton.btn.btn-info.btn-big Show all Nonprofits Projects + .spacer + .row + .col-xs-12.text-center + if !user + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) + .spacer + #all-challenges-dialog.modal(tabindex='-1') + .modal-dialog.animated.fadeInUp.fast-animation + .modal-content + .modal-header.all-list-header Nonprofit Projects + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/nonprofits diff --git a/views/partials/nonprofits.jade b/views/partials/nonprofits.jade new file mode 100644 index 0000000000..1ffcb445c8 --- /dev/null +++ b/views/partials/nonprofits.jade @@ -0,0 +1,24 @@ +h3 + ol#nonprofitsList + script(src='/js/lib/ramda/ramda.min.js') + script. + var getLinkedName = function getLinkedName(name) { + // ensure name is a string + name = name + ''; + return name.toLowerCase().replace(/\s/g, '-'); + } + $.ajax({ + url: '/wiki/getNonprofitsList', + type: 'GET' + }) + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.nonprofitsList.length; i++) { + var li = document.createElement("li"); + var linkedName = getLinkedName(data.nonprofitsList[i].name); + $(li).html("" + data.wikiList[i].name + ""); + docfrag.appendChild(li); + }; + $('#nonprofitsList').append(docfrag); + }); diff --git a/views/partials/wikis.jade b/views/partials/wikis.jade index be4fd2b3cd..99ec5c00b9 100644 --- a/views/partials/wikis.jade +++ b/views/partials/wikis.jade @@ -1,11 +1,11 @@ h3 - ol#bonfireList + ol#wikiList 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, '-'); + return name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); } $.ajax({ url: '/wiki/getWikiList', @@ -20,5 +20,5 @@ h3 $(li).html("" + data.wikiList[i].name + ""); docfrag.appendChild(li); }; - $('#bonfireList').append(docfrag); + $('#wikiList').append(docfrag); }); From ca404e66e8bed5bfc356302f57707d9a5e1c31fb Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 30 Mar 2015 20:08:29 -0700 Subject: [PATCH 073/125] close to getting all nonprofits to render --- app.js | 2 + controllers/nonprofits.js | 11 ++- controllers/resources.js | 10 +++ models/Nonprofit.js | 6 -- seed_data/nonprofits.json | 127 ++------------------------------- seed_data/seed.js | 23 +++++- views/nonprofits/show.jade | 12 +++- views/partials/nonprofits.jade | 4 +- 8 files changed, 60 insertions(+), 135 deletions(-) diff --git a/app.js b/app.js index 3d6e59fcc9..8151112f31 100644 --- a/app.js +++ b/app.js @@ -361,6 +361,8 @@ app.get( nonprofitController.showAllNonprofits ); +app.get('/nonprofits/getNonprofitsList', nonprofitController.showAllNonprofits); + app.get( '/done-with-first-100-hours', passportConf.isAuthenticated, diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 502dd05b56..309ce34c60 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -1,4 +1,5 @@ var async = require('async'), + Nonprofit = require('./../models/Nonprofit'), resources = require('./resources.json'), secrets = require('./../config/secrets'), moment = require('moment'), @@ -116,13 +117,19 @@ exports.returnIndividualNonprofit = function(req, res, next) { } res.render('nonprofits/show', { title: nonprofit.name, - description: nonprofit.description.join('') + logoUrl: nonprofit.logoUrl, + projectDescription: nonprofit.projectDescription, + approvedDeliverables: nonprofit.approvedDeliverables, + websiteLink: nonprofit.websiteLink, + imageUrl: nonprofit.imageUrl, + whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo }); }); }; exports.showAllNonprofits = function(req, res) { var data = {}; - data.nonprofitList = resources.allNonprofitNames(); + data.nonprofitsList = resources.allNonprofitNames(); + console.log('here I am rock you like a hurricane', data); res.send(data); }; diff --git a/controllers/resources.js b/controllers/resources.js index b173c4c54d..d9dba69330 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -4,11 +4,13 @@ var async = require('async'), Bonfire = require('./../models/Bonfire'), Story = require('./../models/Story'), Wiki = require('./../models/Wiki'), + Nonprofit = require('./../models/Nonprofit'), Comment = require('./../models/Comment'), resources = require('./resources.json'), steps = resources.steps, secrets = require('./../config/secrets'), bonfires = require('../seed_data/bonfires.json'), + nonprofits = require('../seed_data/nonprofits.json'), coursewares = require('../seed_data/coursewares.json'), wikis = require('../seed_data/wikis.json'), moment = require('moment'), @@ -261,6 +263,14 @@ module.exports = { }) }, + allNonprofitNames: function() { + return nonprofits.map(function(elem) { + return { + name: elem.name + } + }) + }, + getAllCourses: function() { "use strict"; return coursewares; diff --git a/models/Nonprofit.js b/models/Nonprofit.js index 8fcd9ee973..b1f09c340c 100644 --- a/models/Nonprofit.js +++ b/models/Nonprofit.js @@ -9,11 +9,7 @@ var secrets = require('../config/secrets'); var nonprofitSchema = new mongoose.Schema({ name: String, - registeredNonprofit: true, requestedDeliverables: Array, - existingUserbase: true, - acceptJavascript: true, - agreeToTerms: true, whatDoesNonprofitDo: String, websiteLink: String, stakeholderName: String, @@ -23,8 +19,6 @@ var nonprofitSchema = new mongoose.Schema({ projectDescription: String, logoUrl: String, imageUrl: String, - interestedCampers: Array, - confirmedCampers: Array, estimatedHours: String }); diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json index b22ffbfd1e..a4d7332cce 100644 --- a/seed_data/nonprofits.json +++ b/seed_data/nonprofits.json @@ -1,7 +1,6 @@ [ { "id": "bd7157d8c441cbafaeb5bdef", - "registeredNonprofit": true, "requestedDeliverables": [ "Website", "Donor Managment System", @@ -9,9 +8,6 @@ "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", @@ -24,19 +20,13 @@ "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", @@ -49,19 +39,13 @@ "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", @@ -74,19 +58,13 @@ "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", @@ -99,19 +77,13 @@ "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, + "id": "bd1325d8c464cbafaeb5bdef", "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", @@ -124,19 +96,13 @@ "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, + "id": "bd1325d8c464cbafaeb6bdef", "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", @@ -149,19 +115,13 @@ "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", @@ -174,20 +134,14 @@ "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, + "id": "bd1325d8c464cbafaeb7bdef", "requestedDeliverables": [ "Other" ], - "existingUserbase": true, - "acceptJavascript": true, - "agreeToTerms": true, - "whatDoesNonprofitDo": "Transcendent Pathways ", + "whatDoesNonprofitDo": "Transcendent Pathways", "websiteLink": "http://transcendentpathways.org/", "stakeholderName": "Mark Ackerley", "stakeholderEmail": "mackerley.music@gmail.com", @@ -199,19 +153,13 @@ "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, + "id": "bd1325d8c464cbafaeb8bdef", "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", @@ -224,71 +172,6 @@ "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" } ] diff --git a/seed_data/seed.js b/seed_data/seed.js index f58fab7204..5a131cc4d9 100644 --- a/seed_data/seed.js +++ b/seed_data/seed.js @@ -2,16 +2,18 @@ require('dotenv').load(); var Bonfire = require('../models/Bonfire.js'), Courseware = require('../models/Courseware.js'), Wiki = require('../models/Wiki.js'), + Nonprofit = require('../models/Nonprofit.js'), mongoose = require('mongoose'), secrets = require('../config/secrets'), coursewares = require('./coursewares.json'), - wikis = require('./wikis.json') + wikis = require('./wikis.json'), + nonprofits = require('./nonprofits.json'), bonfires = require('./bonfires.json'); mongoose.connect(secrets.db); var counter = 0; -var offerings = 3; +var offerings = 4; var CompletionMonitor = function() { counter++; @@ -75,3 +77,20 @@ Wiki.remove({}, function(err, data) { }); console.log('wikis'); }); + +Nonprofit.remove({}, function(err, data) { + if (err) { + console.error(err); + } else { + console.log('Deleted ', data); + } + Nonprofit.create(nonprofits, function(err, data) { + if (err) { + console.log(err); + } else { + console.log('Saved ', data); + } + CompletionMonitor(); + }); + console.log('nonprofits'); +}); diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index d5628c415b..c9b5386df7 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -7,7 +7,17 @@ block content .panel-heading.text-center h1= title .panel-body - div!= description + h3= whatDoesNonprofitDo + for deliverable in approvedDeliverables + p= deliverable + p Website: + a(href=websiteLink)= websiteLink + p Project Description: + = projectDescription + p Estimated Hours Per Camper + = estimatedHours + img.img-responsive(src=imageUrl) + img.img-responsive(src=logoUrl) .spacer .text-center #showAllButton.btn.btn-info.btn-big Show all Nonprofits Projects diff --git a/views/partials/nonprofits.jade b/views/partials/nonprofits.jade index 1ffcb445c8..480ca27df4 100644 --- a/views/partials/nonprofits.jade +++ b/views/partials/nonprofits.jade @@ -8,7 +8,7 @@ h3 return name.toLowerCase().replace(/\s/g, '-'); } $.ajax({ - url: '/wiki/getNonprofitsList', + url: '/nonprofits/getNonprofitsList', type: 'GET' }) .success( @@ -17,7 +17,7 @@ h3 for (var i = 0; i < data.nonprofitsList.length; i++) { var li = document.createElement("li"); var linkedName = getLinkedName(data.nonprofitsList[i].name); - $(li).html("" + data.wikiList[i].name + ""); + $(li).html("" + data.nonprofitsList[i].name + ""); docfrag.appendChild(li); }; $('#nonprofitsList').append(docfrag); From 46d185a503a547c826ec9c9cbac39e036f042a84 Mon Sep 17 00:00:00 2001 From: Geoff Storbeck Date: Mon, 30 Mar 2015 23:27:09 -0400 Subject: [PATCH 074/125] added 5 new bonfires --- seed_data/bonfires.json | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index c298c97db7..df3fe15d5d 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -302,6 +302,68 @@ "assert.deepEqual(diff([], ['snuffleupagus', 'cookie monster', 'elmo']), ['snuffleupagus', 'cookie monster', 'elmo'], 'empty array');" ] }, + { + "_id": "a7f4d8f2483413a6ce226cac", + "name": "Roman Numeral Converter", + "tests": [ + "expect(convert(\"xii\")).to.equal(12);", + "expect(convert(\"XLII\")).to.equal(42);", + "expect(convert(9)).to.equal(\"IX\");" + ], + "difficulty": "2.02", + "description": [ + "Convert between roman numerals and numbers depending on the input given.", + "If input is given a roman numeral return the integer equivalent", + "If input is given in an integer give the roman numeral equivalent" + ], + "challengeSeed": "function convert(num) {\n return num;\r\n}\n\nconvert(36);" + }, + { + "_id": "a0b5010f579e69b815e7c5d6", + "name": "Search and Replace", + "tests": [ + "expect(replace(\"Let's go to the store\", \"store\", \"mall\")).to.equal(\"Let's go to the mall\");", + "expect(replace(\"He's sleeping on the couch\", \"sleeping\", \"sitting\")).to.equal(\"He's sitting on the couch\");", + "expect(replace(\"This has a spellngi error\", \"spellngi\", \"spelling\")).to.equal(\"This has a spelling error\");", + "expect(replace(\"His name is Tom\", \"Tom\", \"john\")).to.equal(\"His name is John\");" + ], + "difficulty": "2.03", + "description": [ + "Perform a search and replace on a string", + "Use the second argument as the before and the third as the after", + "Note: Keep the same capitalization on the new word as the old word" + ], + "challengeSeed": "function replace(str, before, after) {\n return str;\r\n}\n\nreplace(\"A quick brown fox jumped over the lazy dog\", \"jumped\", \"leaped\");" + }, + { + "_id": "aa7697ea2477d1316795783b", + "name": "Pig Latin", + "tests": [ + "expect(translate(\"California\")).to.equal(\"Aliforniacay\");", + "expect(translate(\"paragraphs\")).to.equal(\"aragraphspay\");", + "expect(translate(\"algorithm\")).to.equal(\"algorithmway\");" + ], + "difficulty": "2.04", + "description": [ + "Translate the provided string to pig latin" + ], + "challengeSeed": "function translate(str) {\n return str;\r\n}\n\ntranslate(\"consonant\");" + }, + { + "_id": "afd15382cdfb22c9efe8b7de", + "name": "DNA Pairing", + "tests": [ + "assert.deepEqual(pair(\"ATCGA\"),[['A','T'],['T','A'],['C','G'],['G','C'],['A','T']], 'should return the dna pair');", + "assert.deepEqual(pair(\"TTGAG\"),[['T','A'],['T','A'],['G','C'],['A','T'],['G','C']], 'should return the dna pair');", + "assert.deepEqual(pair(\"CTCTA\"),[['C','G'],['T','A'],['C','G'],['T','A'],['A','T']], 'should return the dna pair');" + ], + "difficulty": "2.05", + "description": [ + "Translate the DNA string and create a 2D array of base pairs", + "Return the provided character as the first element in each array." + ], + "challengeSeed": "function pair(str) {\n return str;\r\n}\n\npair(\"GCG\");" + }, { "_id": "af7588ade1100bde429baf20", "name" : "Missing letters", @@ -669,5 +731,26 @@ "expect(permAlone('abfdefa')).to.equal(2640);", "expect(permAlone('zzzzzzzz')).to.equal(0);" ] + }, + { + "_id": "a19f0fbe1872186acd434d5a", + "name": "Friendly Date Ranges", + "difficulty": "4.05", + "description": [ + "Implement a way of converting two dates into a more friendly date range that could be presented to a user.", + "It must not show any redundant information in the date range.", + "For example, if the year and month are the same then only the day range should be displayed.", + "Secondly, if the starting year is the current year, and the ending year can be inferred by the reader, the year should be omitted.", + "Input date is formatted as YYYY-MM-DD" + ], + "challengeSeed": "function friendly(str) {\n return str;\n}\n\nfriendly(['2015-07-01', '2015-07-04']);", + "tests": [ + "assert.deepEqual(friendly(['2015-07-01', '2015-07-04']), ['July 1st','4th'], 'ending month should be omitted since it is already mentioned');", + "assert.deepEqual(friendly(['2015-12-01', '2016-02-03']), ['December 1st','February 3rd'], 'one month apart can be inferred it is the next year');", + "assert.deepEqual(friendly(['2015-12-01', '2017-02-03']), ['December 1st, 2015','February 3rd, 2017']);", + "assert.deepEqual(friendly(['2016-03-01', '2016-05-05']), ['March 1st','May 5th, 2016']);", + "assert.deepEqual(friendly(['2017-01-01', '2017-01-01']), ['January 1st, 2017'], 'since we do not duplicate only return once');", + "assert.deepEqual(friendly(['2022-09-05', '2023-09-04']), ['September 5th, 2022','September 4th, 2023']);" + ] } ] From f72774d412759dae9b05f1ee823bf9ea76fdcf0a Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 31 Mar 2015 13:32:39 -0700 Subject: [PATCH 075/125] minor wording improvement to a zipline. --- seed_data/coursewares.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 6364c3f6a4..4b8cf821f0 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -767,7 +767,8 @@ "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.", + "In addition to each Trello card's description,(which is currently shown), you will also want to show it's name.", + "You'll also want to render each card's labels. Render each label with the card's name as the label's text and each label's color as the label's color.", "Hint: you will need to write a jQuery loop that which dynamically inserts <li> elements. You will do this using jQuery's each function, described here: https://api.jquery.com/each/.", "Style the output to make it look visually appealing. Give it a custom font, color scheme, and a clear visual separation between data elements.", "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." From 003f6c6c0a31886800c735dde421d06763a0ba58 Mon Sep 17 00:00:00 2001 From: Geoff Storbeck Date: Tue, 31 Mar 2015 19:07:22 -0400 Subject: [PATCH 076/125] Updated language and added more test cases --- seed_data/bonfires.json | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index df3fe15d5d..85f2521c33 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -239,7 +239,7 @@ "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');" ] - }, + }, { "_id":"a39963a4c10bc8b4d4f06d7e", "name":"Seek and Destroy", @@ -306,15 +306,16 @@ "_id": "a7f4d8f2483413a6ce226cac", "name": "Roman Numeral Converter", "tests": [ - "expect(convert(\"xii\")).to.equal(12);", - "expect(convert(\"XLII\")).to.equal(42);", - "expect(convert(9)).to.equal(\"IX\");" + "expect(convert(12)).to.equal(\"XII\");", + "expect(convert(5)).to.equal(\"V\");", + "expect(convert(9)).to.equal(\"IX\");", + "expect(convert(29)).to.equal(\"XXIX\");", + "expect(convert(16)).to.equal(\"XVI\");" ], "difficulty": "2.02", "description": [ - "Convert between roman numerals and numbers depending on the input given.", - "If input is given a roman numeral return the integer equivalent", - "If input is given in an integer give the roman numeral equivalent" + "Convert the number be a roman numeral.", + "All roman numerals answers should be provided in upper-case." ], "challengeSeed": "function convert(num) {\n return num;\r\n}\n\nconvert(36);" }, @@ -322,16 +323,18 @@ "_id": "a0b5010f579e69b815e7c5d6", "name": "Search and Replace", "tests": [ - "expect(replace(\"Let's go to the store\", \"store\", \"mall\")).to.equal(\"Let's go to the mall\");", - "expect(replace(\"He's sleeping on the couch\", \"sleeping\", \"sitting\")).to.equal(\"He's sitting on the couch\");", + "expect(replace(\"Let us go to the store\", \"store\", \"mall\")).to.equal(\"Let us go to the mall\");", + "expect(replace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\")).to.equal(\"He is Sitting on the couch\");", "expect(replace(\"This has a spellngi error\", \"spellngi\", \"spelling\")).to.equal(\"This has a spelling error\");", - "expect(replace(\"His name is Tom\", \"Tom\", \"john\")).to.equal(\"His name is John\");" + "expect(replace(\"His name is Tom\", \"Tom\", \"john\")).to.equal(\"His name is John\");", + "expect(replace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\")).to.equal(\"Let us get back to more Bonfires\");" ], "difficulty": "2.03", "description": [ - "Perform a search and replace on a string", - "Use the second argument as the before and the third as the after", - "Note: Keep the same capitalization on the new word as the old word" + "Perform a search and replace on the sentence using the arguments provided and return the new sentence.", + "First argument is the sentence the perform the search and replace on.", + "Second argument is the word that you will be replacing (before).", + "Third argument is what you will be replacing the second argument with (after)." ], "challengeSeed": "function replace(str, before, after) {\n return str;\r\n}\n\nreplace(\"A quick brown fox jumped over the lazy dog\", \"jumped\", \"leaped\");" }, @@ -339,13 +342,17 @@ "_id": "aa7697ea2477d1316795783b", "name": "Pig Latin", "tests": [ - "expect(translate(\"California\")).to.equal(\"Aliforniacay\");", + "expect(translate(\"california\")).to.equal(\"aliforniacay\");", "expect(translate(\"paragraphs\")).to.equal(\"aragraphspay\");", - "expect(translate(\"algorithm\")).to.equal(\"algorithmway\");" + "expect(translate(\"glove\")).to.equal(\"oveglay\");", + "expect(translate(\"algorithm\")).to.equal(\"algorithmway\");", + "expect(translate(\"eight\")).to.equal(\"eightway\");" ], "difficulty": "2.04", "description": [ - "Translate the provided string to pig latin" + "Translate the provided string to pig latin.", + "Pig Latin takes the first consonant (or consonant cluster) of an English word, moves it to the end of the word and suffixes an \"ay\".", + "If a word begins with a vowel you just add \"way\" to the end." ], "challengeSeed": "function translate(str) {\n return str;\r\n}\n\ntranslate(\"consonant\");" }, @@ -359,7 +366,8 @@ ], "difficulty": "2.05", "description": [ - "Translate the DNA string and create a 2D array of base pairs", + "The DNA strand is missing the pairing element. Match each character with the missing element and return the results as a 2d array.", + "Base pairs are a pair of AT and CG. Match the missing element to the provided character.", "Return the provided character as the first element in each array." ], "challengeSeed": "function pair(str) {\n return str;\r\n}\n\npair(\"GCG\");" From fa74a1b454e99491561e3e1e4bcc7aec93869378 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 31 Mar 2015 17:38:33 -0700 Subject: [PATCH 077/125] got show all nonprofits button working and improved nonprofit show view --- app.js | 15 ++---- controllers/nonprofits.js | 13 +++-- seed_data/nonprofits.json | 48 +++++++++-------- .../how-can-free-code-camp-help-you.jade | 18 +++---- views/nonprofits/show.jade | 53 ++++++++++++++----- views/partials/nonprofits.jade | 24 ++++----- 6 files changed, 101 insertions(+), 70 deletions(-) diff --git a/app.js b/app.js index 8151112f31..b69cf246c4 100644 --- a/app.js +++ b/app.js @@ -323,9 +323,9 @@ app.post('/email-signin', userController.postSignin); * Nonprofit Project routes. */ -app.get('/nonprofits', contactController.getNonprofitsForm); - -app.post('/nonprofits', contactController.postNonprofitsForm); +//app.get('/nonprofits', contactController.getNonprofitsForm); +// +//app.post('/nonprofits', contactController.postNonprofitsForm); app.get('/nonprofits/home', nonprofitController.nonprofitsHome); @@ -351,18 +351,13 @@ app.get('/nonprofits/your-nonprofit-project-application-has-been-submitted', non app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); +app.get('/nonprofits/getNonprofitList', nonprofitController.showAllNonprofits); + app.get( '/nonprofits/:nonprofitName', nonprofitController.returnIndividualNonprofit ); -app.get( - '/nonprofits', - nonprofitController.showAllNonprofits -); - -app.get('/nonprofits/getNonprofitsList', nonprofitController.showAllNonprofits); - app.get( '/done-with-first-100-hours', passportConf.isAuthenticated, diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 309ce34c60..3f13b5cd95 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -1,6 +1,6 @@ var async = require('async'), Nonprofit = require('./../models/Nonprofit'), - resources = require('./resources.json'), + resources = require('./resources'), secrets = require('./../config/secrets'), moment = require('moment'), debug = require('debug')('freecc:cntr:resources'), @@ -118,8 +118,16 @@ exports.returnIndividualNonprofit = function(req, res, next) { res.render('nonprofits/show', { title: nonprofit.name, logoUrl: nonprofit.logoUrl, + estimatedHours: nonprofit.estimatedHours, projectDescription: nonprofit.projectDescription, - approvedDeliverables: nonprofit.approvedDeliverables, + approvedOther: nonprofit.approvedDeliverables.indexOf('other') > -1, + approvedWebsite: nonprofit.approvedDeliverables.indexOf('website') > -1, + approvedDonor: nonprofit.approvedDeliverables.indexOf('donor') > -1, + approvedInventory: nonprofit.approvedDeliverables.indexOf('inventory') > -1, + approvedVolunteer: nonprofit.approvedDeliverables.indexOf('volunteer') > -1, + approvedForm: nonprofit.approvedDeliverables.indexOf('form') > -1, + approvedCommunity: nonprofit.approvedDeliverables.indexOf('community') > -1, + approvedELearning: nonprofit.approvedDeliverables.indexOf('eLearning') > -1, websiteLink: nonprofit.websiteLink, imageUrl: nonprofit.imageUrl, whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo @@ -130,6 +138,5 @@ exports.returnIndividualNonprofit = function(req, res, next) { exports.showAllNonprofits = function(req, res) { var data = {}; data.nonprofitsList = resources.allNonprofitNames(); - console.log('here I am rock you like a hurricane', data); res.send(data); }; diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json index a4d7332cce..5c894d6217 100644 --- a/seed_data/nonprofits.json +++ b/seed_data/nonprofits.json @@ -2,11 +2,11 @@ { "id": "bd7157d8c441cbafaeb5bdef", "requestedDeliverables": [ - "Website", - "Donor Managment System", - "Inventory Management System", - "Volunteer Management System", - "Forms" + "website", + "donor", + "inventory", + "volunteer", + "form" ], "whatDoesNonprofitDo": "We help the many less-fortunate Jewish families in our community, by providing them with nutritious food and energy to grow, learn, work, and give them hope for a better and brighter future.", "websiteLink": "http://chasdeikaduri.org/", @@ -15,7 +15,11 @@ "name": "Chasdei Kaduri", "endUser": "Clients, donors, and admin.", "approvedDeliverables": [ - "Website, Donor Management System, Inventory Management System, Volunteer Management System, Forms" + "website", + "donor", + "inventory", + "volunteer", + "form" ], "projectDescription": "Campers will create a system will integrate the food inventory, donor and delivery driver management systems as well as replace the current application system with a custom form solution. System will include a more streamlined operations management, with user printable lists of inventory, drivers, and deliveries.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c7e02f2c173c37015b2f36/604x309/00580a0567a4b3afda29d52b09e7e829/rQQ6zwq31Uya8ie9QHC-MlvfXxqftm9UPPe524JUhmwSEaZjQ7oL7U1tVoHLUj-gVUwM-7uzBGFsAXD_A_cx_JyAZP4Td-GMBJ-AebJNRAQP0m0v253eKMkURp63aG4%3Ds0-d-e1-ft.png", @@ -25,7 +29,7 @@ { "id": "bd7158d8c464cbafaeb4bdef", "requestedDeliverables": [ - "Other" + "other" ], "whatDoesNonprofitDo": "We connect simple technology with last mile communities to reduce poverty.", "websiteLink": "http://kopernik.info/", @@ -34,7 +38,7 @@ "name": "Kopernik", "endUser": "Women in rural Indonesia.", "approvedDeliverables": [ - "Other" + "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", @@ -44,7 +48,7 @@ { "id": "bd6274d8c464cbafaeb4bdef", "requestedDeliverables": [ - "Other" + "other" ], "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/", @@ -53,7 +57,7 @@ "name": "No More Craptions", "endUser": "Hearing impaired users, and those who want to correct poor captions.", "approvedDeliverables": [ - "Other" + "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", @@ -63,7 +67,7 @@ { "id": "bd1326d9c245cbafaeb4bdef", "requestedDeliverables": [ - "Website" + "website" ], "whatDoesNonprofitDo": "We distribute biodegradable toothbrushes globally to children in need.", "websiteLink": "http://www.operationbrush.org/", @@ -72,7 +76,7 @@ "name": "Operation Brush", "endUser": "Donors", "approvedDeliverables": [ - "Website" + "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", @@ -82,7 +86,7 @@ { "id": "bd1325d8c464cbafaeb5bdef", "requestedDeliverables": [ - "Community Management Tool" + "community" ], "whatDoesNonprofitDo": "We are the largest roller derby league in the world with around 250 adults and 150 junior skater members plus 500+ volunteers.", "websiteLink": "http://www.rosecityrollers.com/about/our-charities/", @@ -91,7 +95,7 @@ "name": "Rose City Rollers", "endUser": "Administrators, Coaches, and Volunteers", "approvedDeliverables": [ - "Community Management Tool" + "community" ], "projectDescription": "Campers will create a volunteer management system with multi-user access and reporting capabilities.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1daf2d72d8eb868910b60/54c1dd4ecffcb09fc52b68a1/x/a8148f08769b449217e433bab8f39ddd/RCR-color.jpg", @@ -101,7 +105,7 @@ { "id": "bd1325d8c464cbafaeb6bdef", "requestedDeliverables": [ - "Website" + "website" ], "whatDoesNonprofitDo": "Save a Child's Heart provides urgently needed pediatric heart surgery and follow-up care for indigent children from developing countries", "websiteLink": "http://www.saveachildsheart.com/global/young-leadership-program/", @@ -110,7 +114,7 @@ "name": "Save a Child's Heart", "endUser": "Donors", "approvedDeliverables": [ - "Website" + "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", @@ -120,7 +124,7 @@ { "id": "bd1325d8c464cbafaeb4bdef", "requestedDeliverables": [ - "Website" + "website" ], "whatDoesNonprofitDo": "Savvy Cyber Kids enables youth to be empowered with technology by providing age appropriate resources and education.", "websiteLink": "http://savvycyberkids.org/", @@ -129,7 +133,7 @@ "name": "Savvy Cyber Kids", "endUser": "Donors", "approvedDeliverables": [ - "Website" + "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", @@ -139,7 +143,7 @@ { "id": "bd1325d8c464cbafaeb7bdef", "requestedDeliverables": [ - "Other" + "other" ], "whatDoesNonprofitDo": "Transcendent Pathways", "websiteLink": "http://transcendentpathways.org/", @@ -148,7 +152,7 @@ "name": "Transcendent Pathways", "endUser": "Medical Facilities, Musicians", "approvedDeliverables": [ - "Other" + "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", @@ -158,7 +162,7 @@ { "id": "bd1325d8c464cbafaeb8bdef", "requestedDeliverables": [ - "Other" + "other" ], "whatDoesNonprofitDo": "Timeraiser is a volunteer matching fair, a silent art auction, and a night out on the town. The big Timeraiser twist is rather than bid money on artwork, participants bid volunteer hours. ", "websiteLink": "http://www.timeraiser.ca/", @@ -167,7 +171,7 @@ "name": "Timeraiser", "endUser": "Eventgoers", "approvedDeliverables": [ - "Other" + "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", 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 3aeec6490a..8c9b9678a5 100644 --- a/views/nonprofits/how-can-free-code-camp-help-you.jade +++ b/views/nonprofits/how-can-free-code-camp-help-you.jade @@ -7,27 +7,27 @@ block content .text-left.form-inline h3.col-xs-12.col-sm-offset-5.checkbox-table .col-xs-12 - input.checkbox(type='checkbox', id='websites') + input.checkbox(type='checkbox', id='website') label.ion-android-globe   Websites .col-xs-12 - input.checkbox(type='checkbox', id='donationSystems') + input.checkbox(type='checkbox', id='donation') label.ion-card   Donation Systems .col-xs-12 - input.checkbox(type='checkbox', id='volunteerSystems') + input.checkbox(type='checkbox', id='volunteer') label.ion-android-calendar   Volunteer Systems .col-xs-12 - input.checkbox(type='checkbox', id='inventorySystems') + input.checkbox(type='checkbox', id='inventory') label.ion-ios-box   Inventory Systems .col-xs-12 - input.checkbox(type='checkbox', id='eLearningPlatforms') + input.checkbox(type='checkbox', id='eLearning') label.ion-university   E-learning Platforms .col-xs-12 - input.checkbox(type='checkbox', id='webForms') + input.checkbox(type='checkbox', id='form') label.ion-ios-list   Web Forms .col-xs-12 - input.checkbox(type='checkbox', id='communityTools') + input.checkbox(type='checkbox', id='community') label.ion-ios-people   Community Tools .col-xs-12 - input.checkbox(type='checkbox', id='otherTools') + input.checkbox(type='checkbox', id='other') label.ion-settings   Other tools - button#next-step.btn.btn-primary.btn-big.btn-block(type='submit') I've selected all that apply and am ready to move on \ 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 diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index c9b5386df7..67df6efb79 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -7,21 +7,46 @@ block content .panel-heading.text-center h1= title .panel-body - h3= whatDoesNonprofitDo - for deliverable in approvedDeliverables - p= deliverable - p Website: - a(href=websiteLink)= websiteLink - p Project Description: - = projectDescription - p Estimated Hours Per Camper - = estimatedHours - img.img-responsive(src=imageUrl) - img.img-responsive(src=logoUrl) - .spacer - .text-center - #showAllButton.btn.btn-info.btn-big Show all Nonprofits Projects + .col-xs-12.col-sm-10.col-sm-offset-1 + .row + .col-xs-12 + img.img-center.img-responsive(src=imageUrl) + .spacer + .row + .col-xs-12.col-sm-4 + img.img-responsive(src=logoUrl) + .col-xs-12.col-sm-8 + h4= whatDoesNonprofitDo + h4 + a(href=websiteLink)= websiteLink + .col-xs-12.col-sm-10.col-sm-offset-1 + .row + h4 #{projectDescription} (About #{estimatedHours} hours per camper) + .col-xs-12.col-sm-10.col-sm-offset-1 + .row.text-center + h3 This project involves building: + if (approvedWebsite) + .ion-android-globe   Website + if (approvedDonor) + .ion-card   Donor Management System + if (approvedInventory) + .ion-ios-box   Inventory Management System + if (approvedVolunteer) + .ion-android-calendar   Volunteer Management System + if (approvedForm) + .ion-ios-list   Webform + if (approvedCommunity) + .ion-ios-people   Community Management System + if (approvedELearning) + .ion-university   E-learning Platform + if (approvedOther) + .ion-settings   Other tools + .spacer + .col-xs-12.col-sm-8.col-sm-offset-2 + .text-center + #interestedInProject.btn.btn-primary.btn-big.btn-block I'm interested in building this project + #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects .row .col-xs-12.text-center if !user diff --git a/views/partials/nonprofits.jade b/views/partials/nonprofits.jade index 480ca27df4..c7a9db255a 100644 --- a/views/partials/nonprofits.jade +++ b/views/partials/nonprofits.jade @@ -8,17 +8,17 @@ h3 return name.toLowerCase().replace(/\s/g, '-'); } $.ajax({ - url: '/nonprofits/getNonprofitsList', + url: '/nonprofits/getNonprofitList', type: 'GET' }) - .success( - function(data) { - var docfrag = document.createDocumentFragment(); - for (var i = 0; i < data.nonprofitsList.length; i++) { - var li = document.createElement("li"); - var linkedName = getLinkedName(data.nonprofitsList[i].name); - $(li).html("" + data.nonprofitsList[i].name + ""); - docfrag.appendChild(li); - }; - $('#nonprofitsList').append(docfrag); - }); + .success( + function(data) { + var docfrag = document.createDocumentFragment(); + for (var i = 0; i < data.nonprofitsList.length; i++) { + var li = document.createElement("li"); + var linkedName = getLinkedName(data.nonprofitsList[i].name); + $(li).html("" + data.nonprofitsList[i].name + ""); + docfrag.appendChild(li); + }; + $('#nonprofitsList').append(docfrag); + }); From 1248cde97ff295ff672e86f7c5ebaf34e62525d2 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 1 Apr 2015 22:31:08 -0700 Subject: [PATCH 078/125] continue work on nonprofit show views --- app.js | 2 + controllers/nonprofits.js | 16 +++++++- models/Nonprofit.js | 4 +- models/NonprofitInterest.js | 16 ++++++++ public/css/main.less | 6 +++ views/nonprofits/show.jade | 79 ++++++++++++++++++++----------------- 6 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 models/NonprofitInterest.js diff --git a/app.js b/app.js index b69cf246c4..ebfd208cfe 100644 --- a/app.js +++ b/app.js @@ -353,6 +353,8 @@ app.get('/nonprofits/other-solutions', nonprofitController.otherSolutions); app.get('/nonprofits/getNonprofitList', nonprofitController.showAllNonprofits); +app.get('/nonprofits/interested-in-nonprofit/:nonprofitName', nonprofitController.interestedInNonprofit); + app.get( '/nonprofits/:nonprofitName', nonprofitController.returnIndividualNonprofit diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 3f13b5cd95..69adb54c26 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -116,6 +116,7 @@ exports.returnIndividualNonprofit = function(req, res, next) { return res.redirect('../nonprofit/' + dashedNameFull); } res.render('nonprofits/show', { + dashedName: dashedNameFull, title: nonprofit.name, logoUrl: nonprofit.logoUrl, estimatedHours: nonprofit.estimatedHours, @@ -130,7 +131,8 @@ exports.returnIndividualNonprofit = function(req, res, next) { approvedELearning: nonprofit.approvedDeliverables.indexOf('eLearning') > -1, websiteLink: nonprofit.websiteLink, imageUrl: nonprofit.imageUrl, - whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo + whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, + interestedCampers: nonprofit.interestedCampers }); }); }; @@ -140,3 +142,15 @@ exports.showAllNonprofits = function(req, res) { data.nonprofitsList = resources.allNonprofitNames(); res.send(data); }; + +exports.interestedInNonprofit = function(req, res) { + + if (req.user.uncompletedBonfires !== [] && req.user.uncompletedCoursewares !== []) { + Nonprofit.find({name: req.params.nonprofitName.replace(/-/, ' ')}, function(err, nonprofit) { + if (err) { return next(err); } + nonprofit.interestedCampers.push({"name": req.user.username, "picture": req.user.picture, "timeOfInterest": Date.now()}); + req.flash('success', { msg: 'Thanks for expressing interest in this nonprofit project!' }); + }); + } + res.redirect('back'); +}; diff --git a/models/Nonprofit.js b/models/Nonprofit.js index b1f09c340c..4a1c76e574 100644 --- a/models/Nonprofit.js +++ b/models/Nonprofit.js @@ -19,7 +19,9 @@ var nonprofitSchema = new mongoose.Schema({ projectDescription: String, logoUrl: String, imageUrl: String, - estimatedHours: String + estimatedHours: String, + interestedCampers: [], + confirmedCampers: [] }); module.exports = mongoose.model('Nonprofit', nonprofitSchema); diff --git a/models/NonprofitInterest.js b/models/NonprofitInterest.js new file mode 100644 index 0000000000..44a3fd9b6d --- /dev/null +++ b/models/NonprofitInterest.js @@ -0,0 +1,16 @@ +var mongoose = require('mongoose'); +var secrets = require('../config/secrets'); + +var nonprofitInterestSchema = new mongoose.Schema({ + nonprofitId: + name: { + type: String, + unique: false + }, + description: { + type: Array, + unique: false + } +}); + +module.exports = mongoose.model('Wiki', wikiSchema); diff --git a/public/css/main.less b/public/css/main.less index 4ed3704882..5649f43ca4 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -888,6 +888,12 @@ iframe.iphone { margin-left: 10px; } +.interested-camper-image { + height: 50px; + width: 50px; + padding: 5px; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index 67df6efb79..4491541b10 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -7,45 +7,52 @@ block content .panel-heading.text-center h1= title .panel-body - .col-xs-12.col-sm-10.col-sm-offset-1 - .row - .col-xs-12 - img.img-center.img-responsive(src=imageUrl) - .spacer - .row - .col-xs-12.col-sm-4 - img.img-responsive(src=logoUrl) - .col-xs-12.col-sm-8 - h4= whatDoesNonprofitDo - h4 - a(href=websiteLink)= websiteLink - .col-xs-12.col-sm-10.col-sm-offset-1 - .row - h4 #{projectDescription} (About #{estimatedHours} hours per camper) - .col-xs-12.col-sm-10.col-sm-offset-1 - .row.text-center - h3 This project involves building: - if (approvedWebsite) - .ion-android-globe   Website - if (approvedDonor) - .ion-card   Donor Management System - if (approvedInventory) - .ion-ios-box   Inventory Management System - if (approvedVolunteer) - .ion-android-calendar   Volunteer Management System - if (approvedForm) - .ion-ios-list   Webform - if (approvedCommunity) - .ion-ios-people   Community Management System - if (approvedELearning) - .ion-university   E-learning Platform - if (approvedOther) - .ion-settings   Other tools - + .row + .col-xs-12.col-sm-10.col-sm-offset-1 + .row + .col-xs-12 + img.img-center.img-responsive(src=imageUrl) + .spacer + .row + .col-xs-12.col-sm-4 + img.img-responsive(src=logoUrl) + .col-xs-12.col-sm-8 + .col-xs-12 + h4= whatDoesNonprofitDo + h4 + a(href=websiteLink)= websiteLink + h3 Project Description: + .col-xs-12 + h4 #{projectDescription} (About #{estimatedHours} hours per camper) + h3 This project involves building: + .col-xs-12 + if (approvedWebsite) + .ion-android-globe   Website + if (approvedDonor) + .ion-card   Donor Management System + if (approvedInventory) + .ion-ios-box   Inventory Management System + if (approvedVolunteer) + .ion-android-calendar   Volunteer Management System + if (approvedForm) + .ion-ios-list   Webform + if (approvedCommunity) + .ion-ios-people   Community Management System + if (approvedELearning) + .ion-university   E-learning Platform + if (approvedOther) + .ion-settings   Other tools .spacer + .col-xs-12.text-center + h3 Interested campers: + for interestedCamper in interestedCampers + a(href='/' + interestedCamper.username class="interested-camper-image") + img.profile-picture.float-right(src=interestedUser.profile.picture) + .col-xs-12.text-center + h3 Assigned campers: .col-xs-12.col-sm-8.col-sm-offset-2 .text-center - #interestedInProject.btn.btn-primary.btn-big.btn-block I'm interested in building this project + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects .row .col-xs-12.text-center From 36c43cc73aeb1cdebdb2d0c704e3f890daf35c37 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 2 Apr 2015 13:42:15 -0700 Subject: [PATCH 079/125] add jQuery to check for broken images and replace them with standard image. Remove gravatar logic. --- controllers/nonprofits.js | 2 +- models/User.js | 22 ++-------------------- public/js/main.js | 13 ++++++++++--- views/account/account.jade | 16 ++-------------- views/account/show.jade | 10 +++++----- views/partials/navbar.jade | 8 +++----- 6 files changed, 23 insertions(+), 48 deletions(-) diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 69adb54c26..d409cf1fbd 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -3,7 +3,7 @@ var async = require('async'), resources = require('./resources'), secrets = require('./../config/secrets'), moment = require('moment'), - debug = require('debug')('freecc:cntr:resources'), + debug = require('debug')('freecc:cntr:nonprofits'), R = require('ramda'); exports.nonprofitsHome = function(req, res) { diff --git a/models/User.js b/models/User.js index 43014a8cc2..e322e74bd9 100644 --- a/models/User.js +++ b/models/User.js @@ -346,7 +346,8 @@ var userSchema = new mongoose.Schema({ name: String, completedWith: String, solution: String, - githubLink: String + githubLink: String, + verified: Boolean } ], currentStreak: { @@ -390,23 +391,4 @@ userSchema.methods.comparePassword = function(candidatePassword, cb) { }); }; -/** - * Helper method for getting user's gravatar. - */ - -userSchema.methods.gravatar = function(size) { - if (!size) { size = 200; } - - if (!this.email) { - return 'https://gravatar.com/avatar/?s=' + size + '&d=retro'; - } - - var md5 = crypto - .createHash('md5') - .update(this.email) - .digest('hex'); - - return 'https://gravatar.com/avatar/' + md5 + '?s=' + size + '&d=retro'; -}; - module.exports = mongoose.model('User', userSchema); diff --git a/public/js/main.js b/public/js/main.js index de082f167a..20ad8023c7 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -75,6 +75,10 @@ $(document).ready(function() { }); + $("img").error(function () { + $(this).unbind("error").attr("src", "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"); + }); + $('#completed-courseware').on('click', function() { $('#complete-courseware-dialog').modal('show'); }); @@ -114,7 +118,8 @@ $(document).ready(function() { { coursewareInfo: { coursewareHash: passedCoursewareHash, - coursewareName: passedCoursewareName + coursewareName: passedCoursewareName, + verified: true } }).success( function(res) { @@ -135,7 +140,8 @@ $(document).ready(function() { coursewareName: passedCoursewareName, completedWith: didCompleteWith, publicURL: publicURL, - challengeType: challengeType + challengeType: challengeType, + verified: false } }).success( function() { @@ -158,7 +164,8 @@ $(document).ready(function() { completedWith: didCompleteWith, publicURL: publicURL, githubURL: githubURL, - challengeType: challengeType + challengeType: challengeType, + verified: false } }).success(function() { window.location.href = '/challenges'; diff --git a/views/account/account.jade b/views/account/account.jade index 80424c1b56..3e8a5b834a 100644 --- a/views/account/account.jade +++ b/views/account/account.jade @@ -176,15 +176,11 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.portfolio.website1Image', placeholder='http://www.example.com/image.jpg', ng-pattern="/[\.](jpg|png|jpeg|gif)\s?$/") + input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.portfolio.website1Image', placeholder='http://www.example.com/image.jpg') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.url && !profileForm.website1Image.$pristine") alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .col-sm-4.col-sm-offset-5.flat-top h3 Second Portfolio Project @@ -209,15 +205,11 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.portfolio.website2Image', placeholder='http://www.example.com/image.jpg', ng-pattern="/[\.](jpg|png|jpeg|gif)\s?$/") + input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.portfolio.website2Image', placeholder='http://www.example.com/image.jpg') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.url && !profileForm.website2Image.$pristine") alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .col-sm-4.col-sm-offset-5.flat-top h3 Third Portfolio Project @@ -248,10 +240,6 @@ block content alert(type='danger') span.ion-close-circled | Please enter a valid URL format (http://www.example.com/image.jpg). - .col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Image.$error.pattern") - alert(type='danger') - span.ion-close-circled - | The image URL must end in .jpg, .png, .jpeg or .gif. .form-group .col-sm-offset-5.col-sm-4 diff --git a/views/account/show.jade b/views/account/show.jade index 6dee1e6e41..b4279b18e0 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -49,7 +49,7 @@ block content if (website1Title && website1Link && website1Image) .row .col-xs-12.col-md-5 - img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title") + img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website1Title a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank') @@ -66,7 +66,7 @@ block content if (website2Title && website2Link && website2Image) .row .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title") + img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website2Title a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank') @@ -83,7 +83,7 @@ block content if (website3Title && website3Link && website3Image) .row .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title") + img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}") .col-xs-12.col-md-7 h3.text-center.wrappable.flat-top= website3Title a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') @@ -97,7 +97,7 @@ block content | Try it out .hidden-xs.col-sm-12 - #cal-heatmap.img-center + #cal-heatmap.img-center.hidden script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") link(rel="stylesheet", href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css") @@ -131,4 +131,4 @@ block content .center-block. #{currentStreak} .center-block. - Current Streak \ No newline at end of file + Current Streak diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index f74d5dc487..4f4b3865fc 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -26,11 +26,9 @@ else li if (user.profile.username) - a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] - else - a(href='/account') [ #{user.points} ] + a(href='/account') [ #{user.progressTimestamps.length} ] .hidden-xs if user.profile.picture if (user.profile.username) @@ -42,7 +40,7 @@ else if (user.profile.username) a(href='/' + user.profile.username) - img.profile-picture.float-right(src='#{user.gravatar(60)}') + img.profile-picture.float-right(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') else a(href='/account') - img.profile-picture.float-right(src='#{user.gravatar(60)}') \ No newline at end of file + img.profile-picture.float-right(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') From 97e59d4568c00b51de58d02cb659469779189e21 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 2 Apr 2015 14:07:00 -0700 Subject: [PATCH 080/125] add verified flag to challenges so we can tell which ziplines and basejumps have been verified --- controllers/courseware.js | 12 ++++++++---- public/js/main.js | 6 ++---- views/account/show.jade | 20 ++++++-------------- views/partials/universal-head.jade | 4 +++- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index b7ba94b077..753e935641 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -253,7 +253,8 @@ exports.completedCourseware = function (req, res, next) { completedDate: isCompletedDate, name: req.body.coursewareInfo.coursewareName, solution: null, - githubLink: null + githubLink: null, + verified: true }); var index = req.user.completedCoursewares.indexOf(coursewareHash); @@ -307,7 +308,8 @@ exports.completedZiplineOrBasejump = function (req, res, next) { completedWith: pairedWith._id, completedDate: isCompletedDate, solution: solutionLink, - githubLink: githubLink + githubLink: githubLink, + verified: false }); req.user.save(function (err, user) { @@ -333,7 +335,8 @@ exports.completedZiplineOrBasejump = function (req, res, next) { completedWith: req.user._id, completedDate: isCompletedDate, solution: solutionLink, - githubLink: githubLink + githubLink: githubLink, + verified: false }); pairedWith.save(function (err, paired) { if (err) { @@ -353,7 +356,8 @@ exports.completedZiplineOrBasejump = function (req, res, next) { completedWith: null, completedDate: isCompletedDate, solution: solutionLink, - githubLink: githubLink + githubLink: githubLink, + verified: false }); var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); diff --git a/public/js/main.js b/public/js/main.js index 20ad8023c7..0c12a55854 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -118,8 +118,7 @@ $(document).ready(function() { { coursewareInfo: { coursewareHash: passedCoursewareHash, - coursewareName: passedCoursewareName, - verified: true + coursewareName: passedCoursewareName } }).success( function(res) { @@ -140,8 +139,7 @@ $(document).ready(function() { coursewareName: passedCoursewareName, completedWith: didCompleteWith, publicURL: publicURL, - challengeType: challengeType, - verified: false + challengeType: challengeType } }).success( function() { diff --git a/views/account/show.jade b/views/account/show.jade index b4279b18e0..34e0a6e864 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -97,7 +97,7 @@ block content | Try it out .hidden-xs.col-sm-12 - #cal-heatmap.img-center.hidden + #cal-heatmap.img-center script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") link(rel="stylesheet", href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css") @@ -119,16 +119,8 @@ block content legendColors: ["#cccccc", "#215f1e"], legend: [1, 2, 3] }); - .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 + .row + .hidden-xs.col-sm-12.text-center + .row + h3.col-sm-6.text-right Longest Streak: #{longestStreak} + h3.col-sm-6.text-left Current Streak #{currentStreak} diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 10308e0065..4928e35797 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,6 +1,8 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") +script(src="//d3js.org/d3.v3.min.js") +script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') @@ -14,4 +16,4 @@ script. })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-55446531-1', 'auto'); ga('require', 'displayfeatures'); - ga('send', 'pageview'); \ No newline at end of file + ga('send', 'pageview'); From 44c5da8f729b742631abe4eeac77cfbaa219d63d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 2 Apr 2015 14:23:56 -0700 Subject: [PATCH 081/125] improve the streak counter which previously wasn't defaulting to one day --- controllers/user.js | 7 +++++-- views/account/show.jade | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index 5f925b370c..8b44838c03 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -302,6 +302,9 @@ exports.returnUser = function(req, res, next) { data[(progressTimestamps[i] / 1000).toString()] = 1; } + user.currentStreak = user.currentStreak || 1; + user.longestStreak = user.longestStreak || 1; + res.render('account/show', { title: 'Camper ' + user.profile.username + '\'s portfolio', username: user.profile.username, @@ -327,8 +330,8 @@ exports.returnUser = function(req, res, next) { ch: user.challengesHash, calender: data, moment: moment, - longestStreak: user.longestStreak, - currentStreak: user.currentStreak + longestStreak: user.longestStreak + (user.longestStreak === 1 ? " day" : " days"), + currentStreak: user.currentStreak + (user.currentStreak === 1 ? " day" : " days") }); } else { diff --git a/views/account/show.jade b/views/account/show.jade index 34e0a6e864..1ab23dd6ad 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -123,4 +123,4 @@ block content .hidden-xs.col-sm-12.text-center .row h3.col-sm-6.text-right Longest Streak: #{longestStreak} - h3.col-sm-6.text-left Current Streak #{currentStreak} + h3.col-sm-6.text-left Current Streak: #{currentStreak} From 4aea12abca23c5c72e5fda013a4b35cbe0653c43 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 2 Apr 2015 15:53:38 -0700 Subject: [PATCH 082/125] make slight modifications to the d3 calendar heatmap on the portfolio page --- views/account/show.jade | 7 +++---- views/nonprofits/show.jade | 2 +- views/partials/css-cdns.jade | 14 +++++++++++++- views/partials/universal-head.jade | 2 -- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/views/account/show.jade b/views/account/show.jade index 1ab23dd6ad..49348ce1fb 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -100,7 +100,6 @@ block content #cal-heatmap.img-center script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") - link(rel="stylesheet", href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css") script. var cal = new CalHeatMap(); var calendar = !{JSON.stringify(calender)}; @@ -121,6 +120,6 @@ block content }); .row .hidden-xs.col-sm-12.text-center - .row - h3.col-sm-6.text-right Longest Streak: #{longestStreak} - h3.col-sm-6.text-left Current Streak: #{currentStreak} + .row.text-primary + h4.col-sm-6.text-right Longest Streak: #{longestStreak} + h4.col-sm-6.text-left Current Streak: #{currentStreak} diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index 4491541b10..80e0ffeb92 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -23,7 +23,7 @@ block content a(href=websiteLink)= websiteLink h3 Project Description: .col-xs-12 - h4 #{projectDescription} (About #{estimatedHours} hours per camper) + h4.negative-15 #{projectDescription} (About #{estimatedHours} hours per camper) h3 This project involves building: .col-xs-12 if (approvedWebsite) diff --git a/views/partials/css-cdns.jade b/views/partials/css-cdns.jade index 6f76033d43..3aade4da1e 100644 --- a/views/partials/css-cdns.jade +++ b/views/partials/css-cdns.jade @@ -32,4 +32,16 @@ script. var raf = requestAnimationFrame || mozRequestAnimationFrame || webkitRequestAnimationFrame || msRequestAnimationFrame; if (raf) raf(cb); - else window.addEventListener('load', cb); \ No newline at end of file + else window.addEventListener('load', cb); + + var cb = function () { + var l = document.createElement('link'); + l.rel = 'stylesheet'; + l.href = '//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css'; + var h = document.getElementsByTagName('head')[0]; + h.parentNode.insertBefore(l, h); + }; + var raf = requestAnimationFrame || mozRequestAnimationFrame || + webkitRequestAnimationFrame || msRequestAnimationFrame; + if (raf) raf(cb); + else window.addEventListener('load', cb); diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 4928e35797..27ef5ba82e 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,8 +1,6 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") -script(src="//d3js.org/d3.v3.min.js") -script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') From 3cff4e9879ab9ed9a2850a175954fd98cf17b653 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 2 Apr 2015 17:23:45 -0700 Subject: [PATCH 083/125] add logic to add campers to nonprofit projects of interest to them --- controllers/nonprofits.js | 35 +++++++++++++++++++++------ models/Nonprofit.js | 2 +- views/account/show.jade | 2 -- views/nonprofits/show.jade | 38 ++++++++++++++++++++---------- views/partials/universal-head.jade | 2 ++ 5 files changed, 57 insertions(+), 22 deletions(-) diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index d409cf1fbd..91aadfc037 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -115,6 +115,20 @@ exports.returnIndividualNonprofit = function(req, res, next) { if (dashedNameFull != dashedName) { return res.redirect('../nonprofit/' + dashedNameFull); } + var buttonActive = false; + if (req.user) { + if (req.user.uncompletedBonfires.length === 0) { + if (req.user.completedCoursewares.length > 63) { + var hasShownInterest = nonprofit.interestedCampers.filter(function ( obj ) { + return obj.username === req.user.profile.username; + }); + console.log(hasShownInterest); + if (hasShownInterest.length === 0) { + buttonActive = true; + } + } + } + } res.render('nonprofits/show', { dashedName: dashedNameFull, title: nonprofit.name, @@ -132,7 +146,9 @@ exports.returnIndividualNonprofit = function(req, res, next) { websiteLink: nonprofit.websiteLink, imageUrl: nonprofit.imageUrl, whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, - interestedCampers: nonprofit.interestedCampers + interestedCampers: nonprofit.interestedCampers, + assignedCampers: nonprofit.assignedCampers, + buttonActive: buttonActive }); }); }; @@ -144,13 +160,18 @@ exports.showAllNonprofits = function(req, res) { }; exports.interestedInNonprofit = function(req, res) { - - if (req.user.uncompletedBonfires !== [] && req.user.uncompletedCoursewares !== []) { - Nonprofit.find({name: req.params.nonprofitName.replace(/-/, ' ')}, function(err, nonprofit) { + if (req.user) { + Nonprofit.findOne({name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i')}, function(err, nonprofit) { if (err) { return next(err); } - nonprofit.interestedCampers.push({"name": req.user.username, "picture": req.user.picture, "timeOfInterest": Date.now()}); - req.flash('success', { msg: 'Thanks for expressing interest in this nonprofit project!' }); + nonprofit.interestedCampers.push({"username": req.user.profile.username, + "picture": req.user.profile.picture, + "timeOfInterest": Date.now() + }); + nonprofit.save(function(err) { + if (err) { return done(err); } + req.flash('success', { msg: "Thanks for expressing interest in this nonprofit project! We've added you to this project as an interested camper!" }); + res.redirect('back'); + }); }); } - res.redirect('back'); }; diff --git a/models/Nonprofit.js b/models/Nonprofit.js index 4a1c76e574..39f77085e0 100644 --- a/models/Nonprofit.js +++ b/models/Nonprofit.js @@ -6,7 +6,7 @@ var secrets = require('../config/secrets'); * @type {exports.Schema} */ - +var Long = mongoose.Types.Long; var nonprofitSchema = new mongoose.Schema({ name: String, requestedDeliverables: Array, diff --git a/views/account/show.jade b/views/account/show.jade index 49348ce1fb..0389bb0283 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -98,8 +98,6 @@ block content .hidden-xs.col-sm-12 #cal-heatmap.img-center - script(src="//d3js.org/d3.v3.min.js") - script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") script. var cal = new CalHeatMap(); var calendar = !{JSON.stringify(calender)}; diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index 80e0ffeb92..ed90b630c6 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -21,11 +21,13 @@ block content h4= whatDoesNonprofitDo h4 a(href=websiteLink)= websiteLink + .spacer h3 Project Description: .col-xs-12 h4.negative-15 #{projectDescription} (About #{estimatedHours} hours per camper) + .spacer h3 This project involves building: - .col-xs-12 + h4.negative-15.col-xs-12 if (approvedWebsite) .ion-android-globe   Website if (approvedDonor) @@ -42,18 +44,30 @@ block content .ion-university   E-learning Platform if (approvedOther) .ion-settings   Other tools + if (interestedCampers && interestedCampers.length > 0) + h3 Interested campers: + .col-xs-12.text-left + for interestedCamper in interestedCampers + a(href='/' + interestedCamper.username class="interested-camper-image") + img.profile-picture.float-right(src=interestedCamper.picture) + if (assignedCampers && assignedCampers.length > 0) + h3 Assigned campers: + .col-xs-12.text-left + for assignedCamper in assignedCampers + a(href='/' + assignedCamper.username class="interested-camper-image") + img.profile-picture.float-right(src=assignedCamper.picture) .spacer - .col-xs-12.text-center - h3 Interested campers: - for interestedCamper in interestedCampers - a(href='/' + interestedCamper.username class="interested-camper-image") - img.profile-picture.float-right(src=interestedUser.profile.picture) - .col-xs-12.text-center - h3 Assigned campers: - .col-xs-12.col-sm-8.col-sm-offset-2 - .text-center - a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project - #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects + if (!buttonActive) + .col-xs-12.col-sm-8.col-sm-offset-2 + .text-center + a.btn.btn-primary.btn-big.btn-block.disabled(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project * + p * Complete all our Bonfires, 10 Ziplines, and 3 Basejumps to unlock this. + #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects + if (buttonActive) + .col-xs-12.col-sm-8.col-sm-offset-2 + .text-center + a.btn.btn-primary.btn-big.btn-block(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project + #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects .row .col-xs-12.text-center if !user diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 27ef5ba82e..4928e35797 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,6 +1,8 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") +script(src="//d3js.org/d3.v3.min.js") +script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') From fbeff58477509a503fb1ecf80daa075435e4eb45 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 3 Apr 2015 20:05:53 -0700 Subject: [PATCH 084/125] build a functional challenge map and change navbar links --- app.js | 6 ++- controllers/challengeMap.js | 69 +++++++++++++++++++++++++ controllers/resources.js | 83 +++++++++++++++--------------- models/Courseware.js | 2 +- views/account/show.jade | 38 ++++++++------ views/challengeMap/show.jade | 43 ++++++++++++++++ views/partials/navbar.jade | 20 ++++--- views/partials/universal-head.jade | 2 - 8 files changed, 193 insertions(+), 70 deletions(-) create mode 100644 controllers/challengeMap.js create mode 100644 views/challengeMap/show.jade diff --git a/app.js b/app.js index ebfd208cfe..8c7f203fb0 100644 --- a/app.js +++ b/app.js @@ -43,8 +43,10 @@ var express = require('express'), bonfireController = require('./controllers/bonfire'), coursewareController = require('./controllers/courseware'), wikiController = require('./controllers/wiki'), + challengeMapController = require('./controllers/challengeMap'), - /** + + /** * Stories */ storyController = require('./controllers/story'), @@ -247,6 +249,8 @@ app.get('/jquery-exercises', resourcesController.jqueryExercises); app.get('/chat', resourcesController.chat); +app.get('/challenge-map', challengeMapController.challengeMap); + app.get('/live-pair-programming', function(req, res) { res.redirect(301, '/wiki/live-stream-pair-programming-on-twitch.tv'); }); diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js new file mode 100644 index 0000000000..8e0ae56cf5 --- /dev/null +++ b/controllers/challengeMap.js @@ -0,0 +1,69 @@ +var async = require('async'), + User = require('../models/User'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Wiki = require('./../models/Wiki'), + Nonprofit = require('./../models/Nonprofit'), + Comment = require('./../models/Comment'), + Courseware = require('./../models/Courseware'), + resources = require('./resources'), + steps = resources.steps, + secrets = require('./../config/secrets'), + bonfires = require('../seed_data/bonfires.json'), + nonprofits = require('../seed_data/nonprofits.json'), + coursewares = require('../seed_data/coursewares.json'), + wikis = require('../seed_data/wikis.json'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); + +module.exports = { + challengeMap: function challengeMap(req, res) { + var completedBonfires = []; + var completedList = []; + + if (req.user) { + completedBonfires = req.user.completedBonfires.map(function (elem) { + return elem._id; + }); + } + + if (req.user) { + completedList = req.user.completedCoursewares.map(function (elem) { + return elem._id; + }); + } + + var noDuplicateBonfires = R.uniq(completedBonfires); + var noDuplicatedCoursewares = R.uniq(completedList); + + bonfireList = resources.allBonfireNames(); + completedBonfireList = noDuplicateBonfires; + coursewareList = resources.allCoursewareNames(); + completedCoursewareList = noDuplicatedCoursewares; + waypoints = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 2) { return challenge } + }); + ziplines = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 3) { return challenge } + }); + basejumps = coursewareList.filter(function(challenge) { + if (challenge.challengeType === 4) { return challenge } + }); + console.log('completed', completedCoursewareList); + console.log('waypoints', waypoints); + + res.render('challengeMap/show', { + title: "A map of all Free Code Camp's Challenges", + bonfires: bonfireList, + waypoints: waypoints, + ziplines: ziplines, + basejumps: basejumps, + completedBonfireList: completedBonfireList, + completedCoursewareList: completedCoursewareList + }); + } +}; diff --git a/controllers/resources.js b/controllers/resources.js index d9dba69330..4b39ceb9ca 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -134,46 +134,45 @@ module.exports = { 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 (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); } @@ -196,7 +195,7 @@ module.exports = { } res.render('resources/learn-to-code', { - title: 'About Free Code Camp and Our Team of Volunteers', + title: 'About Free Code Camp', daysRunning: daysRunning, c3: numberWithCommas(c3), all: all, @@ -296,6 +295,7 @@ module.exports = { return { name: elem.name, difficulty: elem.difficulty, + challengeType: elem.challengeType, _id: elem._id }; }) @@ -305,6 +305,7 @@ module.exports = { .map (function(elem) { return { name: elem.name, + challengeType: elem.challengeType, _id: elem._id }; }); diff --git a/models/Courseware.js b/models/Courseware.js index 66e67af545..64309a6540 100644 --- a/models/Courseware.js +++ b/models/Courseware.js @@ -16,7 +16,7 @@ var coursewareSchema = new mongoose.Schema({ tests: Array, challengeSeed: Array, completionMessage: String, // Congratulations! You've finished our HTML and CSS track! - challengeType: Number // 0 = html, 1 = javascript only, 2 = video, 3 = bonfire, 4 = zipline, 5 = basejump + challengeType: Number // 0 = html, 1 = javascript only, 2 = video, 3 = zipline, 4 = basejump }); module.exports = mongoose.model('Courseware', coursewareSchema); diff --git a/views/account/show.jade b/views/account/show.jade index 0389bb0283..2291e6868f 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -41,7 +41,7 @@ block content .col-xs-12.col-sm-12.col-md-3.text-center .background-svg.img-center .points-on-top - = "[ " + user.progressTimestamps.length + " ]" + = "[ " + (user ? user.progressTimestamps.length : 0) + " ]" .row @@ -98,23 +98,27 @@ block content .hidden-xs.col-sm-12 #cal-heatmap.img-center + script(src="//d3js.org/d3.v3.min.js") + script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") script. - var cal = new CalHeatMap(); - var calendar = !{JSON.stringify(calender)}; - cal.init({ - itemSelector: "#cal-heatmap", - domain: "month", - subDomain: "day", - data: calendar, - cellSize: 15, - align: 'center', - cellRadius: 3, - cellPadding: 2, - tooltip: true, - range: 4, - start: new Date().setDate(new Date().getDate() - 90), - legendColors: ["#cccccc", "#215f1e"], - legend: [1, 2, 3] + $(document).ready(function() { + var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + cal.init({ + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "day", + data: calendar, + cellSize: 15, + align: 'center', + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 4, + start: new Date().setDate(new Date().getDate() - 90), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3] + }); }); .row .hidden-xs.col-sm-12.text-center diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade new file mode 100644 index 0000000000..77fe2291ce --- /dev/null +++ b/views/challengeMap/show.jade @@ -0,0 +1,43 @@ +extends ../layout +block content + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center + h1 Challenge Map + .panel-body + h3 Waypoints + ol.col-md-offset-2 + for waypoint in waypoints + if completedCoursewareList.indexOf(waypoint._id) > -1 + li.strikethrough + a(href="/challenges/#{waypoint.name}")= waypoint.name + else + li + a(href="/challenges/#{waypoint.name}")= waypoint.name + h3 Bonfires + ol.col-md-offset-2 + for bonfire in bonfires + if completedBonfireList.indexOf(bonfire._id) > -1 + li.strikethrough + a(href="/bonfires/#{bonfire.name}")= bonfire.name + else + li + a(href="/bonfires/#{bonfire.name}")= bonfire.name + h3 Ziplines + ol.col-md-offset-2 + for zipline in ziplines + if completedCoursewareList.indexOf(zipline._id) > -1 + li.strikethrough + a(href="/challenges/#{zipline.name}")= zipline.name + else + li + a(href="/challenges/#{zipline.name}")= zipline.name + h3 Basejumps + ol.col-md-offset-2 + for basejump in basejumps + if completedCoursewareList.indexOf(basejump._id) > -1 + li.strikethrough + a(href="/challenges/#{basejump.name}")= basejump.name + else + li + a(href="/challenges/#{basejump.name}")= basejump.name diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 4f4b3865fc..719644207e 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -12,13 +12,15 @@ .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown li - a(href='/challenges') Challenges - li - a(href='/chat') Chat + a(href='/challenge-map') Challenges + if (user && user.progressTimestamps.length > 5) + li + a(href='https://gitter.im/freecodecamp/freecodecamp') Chat + else + li + a(href='/chat') Chat li a(href='/stories/hot') News - li - a(href='/bonfires') Bonfires if !user li       li @@ -26,9 +28,11 @@ else li if (user.profile.username) + a(href='/' + user.profile.username) [ #{user.progressTimestamps.length} ] + else - a(href='/account') [ #{user.progressTimestamps.length} ] + a(href='/account') [ #{user.points} ] .hidden-xs if user.profile.picture if (user.profile.username) @@ -40,7 +44,7 @@ else if (user.profile.username) a(href='/' + user.profile.username) - img.profile-picture.float-right(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') + img.profile-picture.float-right(src='#{user.gravatar(60)}') else a(href='/account') - img.profile-picture.float-right(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') + img.profile-picture.float-right(src='#{user.gravatar(60)}') diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 4928e35797..27ef5ba82e 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,8 +1,6 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") -script(src="//d3js.org/d3.v3.min.js") -script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') From c9b4a90f5715deb7136a8b8696ceec575054f6d9 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 3 Apr 2015 20:50:27 -0700 Subject: [PATCH 085/125] more improvements to look of challengeMap --- controllers/challengeMap.js | 2 - public/css/main.less | 5 ++ views/challengeMap/show.jade | 90 +++++++++++++++++++++--------------- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js index 8e0ae56cf5..f8cad437cf 100644 --- a/controllers/challengeMap.js +++ b/controllers/challengeMap.js @@ -53,8 +53,6 @@ module.exports = { basejumps = coursewareList.filter(function(challenge) { if (challenge.challengeType === 4) { return challenge } }); - console.log('completed', completedCoursewareList); - console.log('waypoints', waypoints); res.render('challengeMap/show', { title: "A map of all Free Code Camp's Challenges", diff --git a/public/css/main.less b/public/css/main.less index 5649f43ca4..ddd7d6d095 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -894,6 +894,11 @@ iframe.iphone { padding: 5px; } +.svg-challenge-map { + fill: #333; + height: 40px; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 77fe2291ce..78a93f1791 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -5,39 +5,57 @@ block content .panel-heading.text-center h1 Challenge Map .panel-body - h3 Waypoints - ol.col-md-offset-2 - for waypoint in waypoints - if completedCoursewareList.indexOf(waypoint._id) > -1 - li.strikethrough - a(href="/challenges/#{waypoint.name}")= waypoint.name - else - li - a(href="/challenges/#{waypoint.name}")= waypoint.name - h3 Bonfires - ol.col-md-offset-2 - for bonfire in bonfires - if completedBonfireList.indexOf(bonfire._id) > -1 - li.strikethrough - a(href="/bonfires/#{bonfire.name}")= bonfire.name - else - li - a(href="/bonfires/#{bonfire.name}")= bonfire.name - h3 Ziplines - ol.col-md-offset-2 - for zipline in ziplines - if completedCoursewareList.indexOf(zipline._id) > -1 - li.strikethrough - a(href="/challenges/#{zipline.name}")= zipline.name - else - li - a(href="/challenges/#{zipline.name}")= zipline.name - h3 Basejumps - ol.col-md-offset-2 - for basejump in basejumps - if completedCoursewareList.indexOf(basejump._id) > -1 - li.strikethrough - a(href="/challenges/#{basejump.name}")= basejump.name - else - li - a(href="/challenges/#{basejump.name}")= basejump.name + .col-md-offset-2 + h2 + span.fa.fa-flag + |   Waypoints + + .col-xs-12 + h3.negative-15 + ol + for waypoint in waypoints + if completedCoursewareList.indexOf(waypoint._id) > -1 + li.strikethrough + a(href="/challenges/#{waypoint.name}")= waypoint.name + else + li + a(href="/challenges/#{waypoint.name}")= waypoint.name + h2 + span.ion-bonfire + |   Bonfires + .col-xs-12 + h3.negative-15 + ol + for bonfire in bonfires + if completedBonfireList.indexOf(bonfire._id) > -1 + li.strikethrough + a(href="/bonfires/#{bonfire.name}")= bonfire.name + else + li + a(href="/bonfires/#{bonfire.name}")= bonfire.name + h2 + span.fa.fa-angle-double-right + |   Ziplines + .col-xs-12 + h3.negative-15 + ol + for zipline in ziplines + if completedCoursewareList.indexOf(zipline._id) > -1 + li.strikethrough + a(href="/challenges/#{zipline.name}")= zipline.name + else + li + a(href="/challenges/#{zipline.name}")= zipline.name + h2 + span.fa.fa-level-down + |   Basejumps + .col-xs-12 + h3.negative-15 + ol + for basejump in basejumps + if completedCoursewareList.indexOf(basejump._id) > -1 + li.strikethrough + a(href="/challenges/#{basejump.name}")= basejump.name + else + li + a(href="/challenges/#{basejump.name}")= basejump.name From e6272d4c7e34f382683450b9f14c1ec8bfb8ab45 Mon Sep 17 00:00:00 2001 From: Geoff Storbeck Date: Sat, 4 Apr 2015 12:22:29 -0400 Subject: [PATCH 086/125] Forgot comma on the end of the new entry to bonfireMDNlinks.js --- seed_data/bonfireMDNlinks.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index e604b63919..4c053f9b65 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -1,7 +1,7 @@ // MDN Links /* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links. - + 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. @@ -14,6 +14,10 @@ var links = "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", + // ========= GLOBAL OBJECT METHODS + "parseInt()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt", + + // ========= PROPERTIES/MISC "String.length" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length", "Arguments object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments", @@ -57,7 +61,7 @@ var links = "Array.sort()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort", "Array.splice()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice", "Array.toString()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString", - + // ======== 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" From 9d746f7d497b3d7c0ee1cb1b435f31af3a516967 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 00:01:51 -0700 Subject: [PATCH 087/125] add zipline and basejump to portfolio view, and bonfires with solutions(not yet writing name to bonfireCompletion record on user --- controllers/bonfire.js | 6 +- controllers/courseware.js | 3 + controllers/user.js | 7 +- models/User.js | 225 +-- public/js/main.js | 22 +- seed_data/coursewares.json | 3031 +----------------------------------- views/account/show.jade | 31 + 7 files changed, 134 insertions(+), 3191 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 4f11a99a23..da6b8c7d41 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -113,7 +113,6 @@ exports.returnIndividualBonfire = function(req, res, next) { 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(), @@ -252,6 +251,7 @@ exports.completedBonfire = function (req, res, next) { var isCompletedDate = Math.round(+new Date()); var bonfireHash = req.body.bonfireInfo.bonfireHash; var isSolution = req.body.bonfireInfo.solution; + var bonfireName = req.body.bonfireInfo.bonfireName; if (isCompletedWith) { var paired = User.find({'profile.username': isCompletedWith @@ -276,6 +276,7 @@ exports.completedBonfire = function (req, res, next) { pairedWith.completedBonfires.push({ _id: bonfireHash, + name: bonfireName, completedWith: req.user._id, completedDate: isCompletedDate, solution: isSolution @@ -283,6 +284,7 @@ exports.completedBonfire = function (req, res, next) { req.user.completedBonfires.push({ _id: bonfireHash, + name: bonfireName, completedWith: pairedWith._id, completedDate: isCompletedDate, solution: isSolution @@ -304,8 +306,10 @@ exports.completedBonfire = function (req, res, next) { } }); } else { + console.log('look here!', bonfireName); req.user.completedBonfires.push({ _id: bonfireHash, + name: bonfireName, completedWith: null, completedDate: isCompletedDate, solution: isSolution diff --git a/controllers/courseware.js b/controllers/courseware.js index 753e935641..08cb36a680 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -305,6 +305,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { req.user.completedCoursewares.push({ _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, completedWith: pairedWith._id, completedDate: isCompletedDate, solution: solutionLink, @@ -332,6 +333,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { pairedWith.completedCoursewares.push({ _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, completedWith: req.user._id, completedDate: isCompletedDate, solution: solutionLink, @@ -353,6 +355,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { req.user.completedCoursewares.push({ _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, completedWith: null, completedDate: isCompletedDate, solution: solutionLink, diff --git a/controllers/user.js b/controllers/user.js index 8b44838c03..d1b829a1ec 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -304,7 +304,9 @@ exports.returnUser = function(req, res, next) { user.currentStreak = user.currentStreak || 1; user.longestStreak = user.longestStreak || 1; - + challenges = user.completedCoursewares.filter(function ( obj ) { + return !!obj.solution; + }); res.render('account/show', { title: 'Camper ' + user.profile.username + '\'s portfolio', username: user.profile.username, @@ -327,7 +329,8 @@ exports.returnUser = function(req, res, next) { website3Link: user.portfolio.website3Link, website3Title: user.portfolio.website3Title, website3Image: user.portfolio.website3Image, - ch: user.challengesHash, + challenges: challenges, + bonfires: user.completedBonfires, calender: data, moment: moment, longestStreak: user.longestStreak + (user.longestStreak === 1 ? " day" : " days"), diff --git a/models/User.js b/models/User.js index e322e74bd9..cc80440cc3 100644 --- a/models/User.js +++ b/models/User.js @@ -19,231 +19,7 @@ var userSchema = new mongoose.Schema({ github: String, linkedin: String, tokens: Array, - points: { - type: Number, - default: 0 - }, progressTimestamps: [], - challengesCompleted: { type: Array, default: [] }, - pointsNeedMigration: { type: Boolean, default: true }, - challengesHash: { - 0: { - type: Number, - default: 0 - }, - 1: { - type: Number, - default: 0 - }, - 2: { - type: Number, - default: 0 - }, - 3: { - type: Number, - default: 0 - }, - 4: { - type: Number, - default: 0 - }, - 5: { - type: Number, - default: 0 - }, - 6: { - type: Number, - default: 0 - }, - 7: { - type: Number, - default: 0 - }, - 8: { - type: Number, - default: 0 - }, - 9: { - type: Number, - default: 0 - }, - 10: { - type: Number, - default: 0 - }, - 11: { - type: Number, - default: 0 - }, - 12: { - type: Number, - default: 0 - }, - 13: { - type: Number, - default: 0 - }, - 14: { - type: Number, - default: 0 - }, - 15: { - type: Number, - default: 0 - }, - 16: { - type: Number, - default: 0 - }, - 17: { - type: Number, - default: 0 - }, - 18: { - type: Number, - default: 0 - }, - 19: { - type: Number, - default: 0 - }, - 20: { - type: Number, - default: 0 - }, - 21: { - type: Number, - default: 0 - }, - 22: { - type: Number, - default: 0 - }, - 23: { - type: Number, - default: 0 - }, - 24: { - type: Number, - default: 0 - }, - 25: { - type: Number, - default: 0 - }, - 26: { - type: Number, - default: 0 - }, - 27: { - type: Number, - default: 0 - }, - 28: { - type: Number, - default: 0 - }, - 29: { - type: Number, - default: 0 - }, - 30: { - type: Number, - default: 0 - }, - 31: { - type: Number, - default: 0 - }, - 32: { - type: Number, - default: 0 - }, - 33: { - type: Number, - default: 0 - }, - 34: { - type: Number, - default: 0 - }, - 35: { - type: Number, - default: 0 - }, - 36: { - type: Number, - default: 0 - }, - 37: { - type: Number, - default: 0 - }, - 38: { - type: Number, - default: 0 - }, - 39: { - type: Number, - default: 0 - }, - 40: { - type: Number, - default: 0 - }, - 41: { - type: Number, - default: 0 - }, - 42: { - type: Number, - default: 0 - }, - 43: { - type: Number, - default: 0 - }, - 44: { - type: Number, - default: 0 - }, - 45: { - type: Number, - default: 0 - }, - 46: { - type: Number, - default: 0 - }, - 47: { - type: Number, - default: 0 - }, - 48: { - type: Number, - default: 0 - }, - 49: { - type: Number, - default: 0 - }, - 50: { - type: Number, - default: 0 - }, - 51: { - type: Number, - default: 0 - }, - 52: { - type: Number, - default: 0 - }, - 53: { - type: Number, - default: 0 - } - }, profile: { username: { type: String, @@ -333,6 +109,7 @@ var userSchema = new mongoose.Schema({ completedBonfires: [ { _id: String, + name: String, completedWith: String, completedDate: Long, solution: String diff --git a/public/js/main.js b/public/js/main.js index 0c12a55854..72227d9609 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -30,31 +30,16 @@ $(document).ready(function() { .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, + bonfireName: title, + completedWith: didCompleteWith, solution: bonfireSolution, bonfireHash: thisBonfireHash } @@ -70,8 +55,9 @@ $(document).ready(function() { $('.next-bonfire-button').on('click', function() { var bonfireSolution = myCodeMirror.getValue(); var thisBonfireHash = passedBonfireHash || null; + var bonfireName = title; var didCompleteWith = $('#completed-with').val() || null; - completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash); + completedBonfire(didCompleteWith, bonfireName, bonfireSolution, thisBonfireHash); }); diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 4b8cf821f0..ebec8b18af 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -40,7 +40,7 @@ }, { "_id": "bd7126d8c441eddfaeb5bdef", - "name": "Join Our Forum", + "name": "Try Camper News", "difficulty": 0.03, "challengeSeed": "115275066", "description": [ @@ -58,10 +58,25 @@ "challengeType": 2, "tests": [] }, + { + "_id": "bd7137d8c441eddfaeb5bdef", + "name": "Get Help the Hacker Way", + "difficulty": 0.04, + "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).", + "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.", + "Go to http://stackoverflow.com/ and view the recent questions.", + "Now you have several ways of getting help when you're stuck." + ], + "challengeType": 2, + "tests": [] + }, { "_id": "bd7127d8c441eddfaeb5bdef", "name": "Build a Personal Website", - "difficulty": 0.04, + "difficulty": 0.05, "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.", @@ -73,7 +88,7 @@ { "_id": "bd7128d8c441eddfaeb5bdef", "name": "Build a Responsive Blog Theme", - "difficulty": 0.05, + "difficulty": 0.06, "challengeSeed": "114578441", "description": [ "Next, let's learn about responsive web design and continue learning about HTML and CSS.", @@ -88,7 +103,7 @@ { "_id": "bd8129d8c441eddfaeb5bdef", "name": "Build a Small Business Website", - "difficulty": 0.06, + "difficulty": 0.07, "challengeSeed": "114578438", "description": [ "Ready for some more HTML and CSS fundamentals?", @@ -100,7 +115,7 @@ { "_id": "bd7110d8c441eddfaeb5bdef", "name": "Tweak HTML and CSS in CodePen", - "difficulty": 0.07, + "difficulty": 0.08, "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!", @@ -118,7 +133,7 @@ { "_id": "bd7111d8c441eddfaeb5bdef", "name": "Build a CSS Robot", - "difficulty": 0.08, + "difficulty": 0.09, "challengeSeed": "114578436", "description": [ "Now let's learn some more CSS, and a small amount of a JavaScript-based tool called jQuery.", @@ -130,7 +145,7 @@ { "_id": "bd7112d8c441eddfaeb5bdef", "name": "Get Started with jQuery", - "difficulty": 0.09, + "difficulty": 0.10, "challengeSeed": "114578435", "description": [ "jQuery is a powerful tool for manipulating HTML elements.", @@ -145,7 +160,7 @@ { "_id": "bd7113d8c441eddfaeb5bdef", "name": "Traverse the DOM", - "difficulty": 0.10, + "difficulty": 0.11, "challengeSeed": "114591805", "description": [ "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", @@ -157,7 +172,7 @@ { "_id": "bd7114d8c441eddfaeb5bdef", "name": "Work with the DOM", - "difficulty": 0.11, + "difficulty": 0.12, "challengeSeed": "114591804", "description": [ "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", @@ -169,7 +184,7 @@ { "_id": "bd7115d8c441eddfaeb5bdef", "name": "Listen for DOM Events", - "difficulty": 0.12, + "difficulty": 0.13, "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", @@ -181,7 +196,7 @@ { "_id": "bd7116d8c441eddfaeb5bdef", "name": "Use jQuery for Styling", - "difficulty": 0.13, + "difficulty": 0.14, "challengeSeed": "114591801", "description": [ "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", @@ -217,59 +232,6 @@ "challengeType": 2, "tests": [] }, - { - "_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." - ], - "challengeType": 2, - "tests": [] - }, - { - "_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." - ], - "challengeType": 2, - "tests": [] - }, - { - "_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." - ], - "challengeType": 2, - "tests": [] - }, { "_id": "bd7123d8c441eddfaeb5bdef", "name": "Learn Basic Computer Science", @@ -456,23 +418,6 @@ "challengeType": 2, "tests": [] }, - { - "_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." - ], - "challengeType": 2, - "tests": [] - }, { "_id": "bd7138d8c441eddfaeb5bdef", "name": "Learn Regular Expressions", @@ -487,47 +432,6 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7139d8c441eddfaeb5bdef", - "name": "Pair Program on Bonfires", - "difficulty": 0.35, - "challengeSeed": "119657641", - "description": [ - "OK, we're finally ready to start pair programming!", - "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", - "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", - "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", - "We have a special chat room for people ready to pair program. Go to 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." - ], - "challengeType": 2, - "tests": [] - }, - { - "_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." - ], - "challengeType": 2, - "tests": [] - }, { "_id": "bd7141d8c441eddfaeb5bdef", "name": "Get Started with Node.js", @@ -682,18 +586,6 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7153d8c441eddfaeb5bdef", - "name": "Try MongoDB", - "difficulty": 0.49, - "challengeSeed": "114685061", - "description": [ - "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", - "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." - ], - "challengeType": 2, - "tests": [] - }, { "_id": "bd7154d8c441eddfaeb5bdef", "name": "Get Started with Angular.js", @@ -758,9 +650,62 @@ "challengeType": 2, "tests": [] }, + { + "_id": "bd7153d8c441eddfaeb5bdef", + "name": "Try MongoDB", + "difficulty": 0.49, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + + { + "_id": "bd7140d8c441eddfaeb5bdef", + "name": "Manage Source Code with Git", + "difficulty": 0.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." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7139d8c441eddfaeb5bdef", + "name": "Pair Program on Bonfires", + "difficulty": 0.35, + "challengeSeed": "119657641", + "description": [ + "OK, we're finally ready to start pair programming!", + "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", + "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", + "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", + "We have a special chat room for people ready to pair program. Go to 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!", + "Mark this challenge as complete and move on to the Bonfires." + ], + "challengeType": 2, + "tests": [] + }, { "_id": "bd7158d8c442eddfaeb5bdef", - "name": "Zipline Trello API Integration", + "name": "Zipline: Trello API Integration", "difficulty": 0.55, "challengeSeed": "123488494", "description": [ @@ -778,7 +723,7 @@ }, { "_id": "bd7158d8c443eddfaeb5bdef", - "name": "Basejump Hello World in Cloud 9", + "name": "Basejump: Hello World in Cloud 9", "difficulty": 0.56, "challengeSeed": "123488494", "description": [ @@ -795,16 +740,11 @@ }, { "_id": "bd7123c8c441eddfaeb5bdef", - "name": "Start our Challenges", - "difficulty": "1.00", + "name": "HTML CSS and JavaScript Sandbox", + "difficulty": 1.00, "description": [ - "Welcome to Free Code Camp's first challenge! Click on the button below for further instructions.", - "Awesome. Now you can read the rest of this challenge's instructions.", - "You can edit code in the text editor we've embedded into this web page.", - "Do you see the code in the text editor that says <h1>hello</h1>? That's an HTML element.", - "Most HTML elements have an opening tag and a closing tag. Opening tags look like this: <h1>. Closing tags look like this: </h1>. Note that the only difference between opening and closing tags is that closing tags have a slash after their opening angle bracket.", - "Once you've completed the challenge, the \"Go to my next challenge\" button will become enabled. Click it - or press control and enter at the same time - to advance to the next challenge.", - "To enable the \"Go to my next challenge\" button on this exercise, change the h1 tag's text to say \"Hello World\" instead of \"Hello\"." + "This is our custom waypoint sandbox.", + "This is for testing out future functionality." ], "tests": [ "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;" @@ -813,2806 +753,5 @@ "

    Hello

    " ], "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf0887a", - "name": "Use the h2 Element", - "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.", - "This element tells the browser how to render the text that it contains.", - "h2 elements are slightly smaller than h1 elements. There are also h3, h4, h5 and h6 elements." - ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;", - "expect((/cat(\\s)+photo(\\s)+app/gi).test($('h2').text())).to.be.true;" - ], - "challengeSeed": [ - "

    hello world

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08801", - "name": "Use the P Element", - "difficulty": "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.", - "You can create a p element like so: <p>I'm a p tag!</p>" - ], - "tests": [ - "expect((/hello(\\s)+paragraph/gi).test($('p').text())).to.be.true;" - ], - "challengeSeed": [ - "

    hello world

    ", - "

    hello html

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

    hello world

    ", - "

    hello html

    ", - "

    hello paragraph

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08802", - "name": "Uncomment HTML", - "difficulty": "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.", - "Commenting is also a convenient way to make code inactive without having to delete it entirely.", - "You can start a comment with <!-- and end a comment with -->." - ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;" - ], - "challengeSeed": [ - "", - "", - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08809", - "name": "Using Important to Override Styles", - "difficulty": "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.", - "If there's a conflict in the CSS, the browser will use whichever style declaration is closest to the bottom of the CSS document (whichever declaration comes last). Note that in-line style declarations are the final authority in how an HTML element will be rendered.", - "There's one way to ensure that an element is rendered with a certain style, regardless of where that declaration is located. That one way is to use !important.", - "Look at the example in the editor's style tag to see how you can use !important.", - "Now see if you can make sure the h2 element is rendered in the color red without removing the blue-text class, doing an in-line styling, or changing the sequence of CSS class declarations." - ], - "tests": [ - "expect($('h2')).to.have.class('urgently-red');", - "expect($('h2')).to.have.class('blue-text');", - "expect($('h2')).to.have.css('color', '#ff0000');" - ], - "challengeSeed": [ - "", - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

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

    hello world

    ", - "

    cat photo app

    ", - "

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

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

    hello world

    ", - "

    cat photo app

    ", - "

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

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9bedf08810", - "name": "Use Shortened 3 Digit Hex Codes", - "difficulty": "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." - ], - "tests": [ - "expect($('h2')).to.have.css('color', '#ff0000');", - "expect($('h2')).to.have.class('red-text');" - ], - "challengeSeed": [ - "", - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

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

    hello world

    ", - "

    cat photo app

    ", - "

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

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08812", - "name": "Add an Image to your Website", - "difficulty": "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.", - "An example of this would be <img src=\"www.your-image-source.com/your-image.jpg\"/>. Note that in most cases, img elements are self-closing.", - "Try it with this image: http://bit.ly/cutegraycat." - ], - "tests": [ - "expect($('img').attr('src')).to.equal('http://bit.ly/cutegraycat');" - ], - "challengeSeed": [ - "", - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9acdf08812", - "name": "Specify an Image Size", - "difficulty": "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.", - "CSS has an attribute called width that controls an element's width. Just like with fonts, we'll use pixels(px) to specify the images width.", - "Create a class called narrow-image and added it to the image element. Change the width to 200 pixels." - ], - "tests": [ - "expect($('img')).to.have.class('narrow-image');", - "expect($('img')).to.have.css('width', '200px')" - ], - "challengeSeed": [ - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9bedf08813", - "name": "Add a Border Around an Element", - "difficulty": "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.", - "We've created an example border around your h1 element. See if you can add a 10-pixel-wide green border around your cat photo." - ], - "tests": [ - "expect($('img')).to.have.class('thick-green-border');", - "expect($('img')).to.have.css('border-color', '#00ff00');", - "expect($('img')).to.have.css('border-width', '10px');" - ], - "challengeSeed": [ - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08814", - "name": "Add Rounded Corners with a Border Radius", - "difficulty": "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.", - "You can specify a border-radius with pixels. This will affect how rounded the corners are. Add this attribute to your thick-green-border class and set it to 10 pixels." - ], - "tests": [ - "expect($('img')).to.have.class('thick-green-border');", - "expect($('img')).to.have.css('border-radius', '10px');" - ], - "challengeSeed": [ - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08815", - "name": "Make an Image Circular with a Border Radius", - "difficulty": "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." - ], - "tests": [ - "expect($('img')).to.have.css('border-radius', '50%');" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "

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

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

    cat photo app

    ", - "

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

    ", - "", - "
    ", - "This is a link to Google", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08817", - "name": "Make Named Anchors using the Hash Symbol", - "difficulty": "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.", - "This is also handy when you're changing the behavior of your link using jQuery, which we'll learn about later.", - "Replace your link to freecodecamp.com's href with a hash symbol to turn it into a named anchor." - ], - "tests": [ - "expect((/this link leads nowhere/gi).test($('a').text())).to.be.true;", - "expect($('a').filter(function(index) { return /#/gi.test($('a')[index]); }).length).to.eql(1);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "

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

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

    cat photo app

    ", - "", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08818", - "name": "Add Alt Text to an image", - "difficulty": "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.", - "In short, every image on your website should have alt text!", - "You can add alt text right in the image tag, like we've done here with the \"cute white cat\" image." - ], - "tests": [ - "expect($('img').filter(function(){ return /cat/gi.test(this.alt) }).length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "\"a", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad88fee1348bd9aedf08825", - "name": "Adjusting the Padding of an Element", - "difficulty": "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.", - "Here, we can see that the green box and the red box and the green box are nested within the yellow box. Note that the red box has more padding than the green box.", - "When you increase the green box's padding, it will increase the distance between the word \"padding\" and the border around the text." - ], - "tests": [ - "expect($('.green-box')).to.have.css('padding', '20px')" - ], - "challengeSeed": [ - "", - "
    margin
    ", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08822", - "name": "Adjust the Margin of an Element", - "difficulty": "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.", - "Here, we can see that the green box and the red box and the green box are nested within the yellow box. Note that the red box has more margin than the green box, making it appear smaller.", - "When you increase the green box's padding, it will increase the distance between its border and surrounding elements." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin', '20px')" - ], - "challengeSeed": [ - "", - "
    margin
    ", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08823", - "name": "Add a Negative Margin to an Element", - "difficulty": "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.", - "If you set an element's margin to a negative value, the element will grow larger.", - "Try to set the margin to a negative value like the one for the red box." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin', '-15px')" - ], - "challengeSeed": [ - "", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08824", - "name": "Add Different Padding to Each Side of an Element TEST", - "difficulty": "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.", - "CSS allows you to control the padding of an element on all four sides with padding-top, padding-right, padding-bottom, and padding-left attributes." - ], - "tests": [ - "expect($('.green-box')).to.have.css('padding-bottom', '20px')", - "expect($('.green-box')).to.have.css('padding-left', '40px')" - ], - "challengeSeed": [ - "", - "
    margin
    ", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1248bd9aedf08824", - "name": "Add Different a Margin to Each Side of an Element TEST", - "difficulty": "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.", - "CSS allows you to control the margin of an element on all four sides with margin-top, margin-right, margin-bottom, and margin-left attributes." - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin-bottom', '20px')", - "expect($('.green-box')).to.have.css('margin-left', '40px')" - ], - "challengeSeed": [ - "", - "
    margin
    ", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08826", - "name": "Use Clockwise Notation to Specify an Element's Padding", - "difficulty": "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;.", - "These four values work like a clock: top, right, bottom, left, and will produce the exact same result as using the side-specific padding instructions.", - "You can also use this notation for margins!" - ], - "tests": [ - "expect($('.green-box')).to.have.css('margin-bottom', '20px')", - "expect($('.green-box')).to.have.css('margin-left', '40px')" - ], - "challengeSeed": [ - "", - "
    margin
    ", - "", - "
    ", - "
    padding
    ", - "
    padding
    ", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9acde08812", - "name": "Use Bootstrap for Responsive Images", - "difficulty": "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.", - "Fortunately, there's a Responsive CSS Framework called written by Twitter called Bootstrap. You can add Bootstrap to any app just by including it with <link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/> at the top of your HTML, but we've gone ahead and included it for you here.", - "Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name Responsive Design.", - "Now all you need to do is add the img-responsive class to your image." - ], - "tests": [ - "expect($('img')).to.have.class('img-responsive');" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "This is a link to Google", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd8acde08812", - "name": "Center Text with Bootstrap", - "difficulty": "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.", - "Note that you can add several classes to the same element by seperating each of them with a space, like this: <h2 class=\"text-red text-center\">your text</h2>." - ], - "tests": [ - "expect($('h2')).to.have.class('text-center');" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348cd8acde08812", - "name": "Create a Button", - "difficulty": "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." - ], - "tests": [ - "expect((/delete/gi).test($('button').text())).to.be.true;" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    " - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348cd8acdf08812", - "name": "Create a Bootstrap Button", - "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." - ], - "tests": [ - "expect($('.btn').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348cd8acef08812", - "name": "Create a Block Element Bootstrap Button", - "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.", - "Note that these buttons still need the btn class." - ], - "tests": [ - "expect($('.btn-block').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bae87fee1348cd8acef08812", - "name": "Color a Bootstrap Button with Button Primary", - "difficulty": "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.", - "Note that these buttons still need the btn class." - ], - "tests": [ - "expect($('.btn-block').length).to.eql(2);", - "expect($('.btn-primary').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad88fee1348cd8acef08812", - "name": "Color a Bootstrap Button with Button Primary", - "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.", - "Note that these buttons still need the btn and btn-block classes." - ], - "tests": [ - "expect($('.btn-primary').length).to.eql(2);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348ce8acef08812", - "name": "Warn your Users of a Dangerous Action with the Bootstrap Button Danger Class", - "difficulty": "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.", - "Note that this button still needs the btn and btn-block classes." - ], - "tests": [ - "expect($('.btn-danger').length).to.eql(1);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad88fee1348ce8acef08812", - "name": "Use the Bootstrap Grid to Put Two Elements Side By Side", - "difficulty": "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.", - "The row class is applied to a div, and the buttons themselves can be nested within it." - ], - "tests": [ - "expect($('.row').length).to.eql(2);", - "expect($('.col-xs-12').length).to.eql(4);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad89fee1348ce8acef08812", - "name": "Wrap Side By Side Elements in a Bootstrap Row", - "difficulty": "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.", - "The row class is applied to a div, and the buttons themselves can be nested within it." - ], - "tests": [ - "expect($('.row').length).to.eql(2);", - "expect($('.col-xs-6').length).to.eql(4);" - ], - "challengeSeed": [ - "", - "

    cat photo app

    ", - "", - "
    ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - "", - "
    ", - "" - ], - "challengeType": 0 - }, - { - "_id": "bad87fee1348bd9aedf08813", - "name": "Add Alt Text to an Image TEST", - "difficulty": "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.", - "You can add alt text right in the img element like this: <img src=\"www.your-image-source.com/your-image.jpg\" alt=\"your alt text\"/>." - ], - "tests": [ - "expect((/cat/gi).test($('img').attr('alt')).to.be.true;" - ], - "challengeSeed": [ - "", - "

    hello world

    ", - "

    cat photo app

    ", - "

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

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

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

    hello world

    " - ], - "challengeType": 0 - }, - { - "_id": "bd7123c9c441eddfaeb5bdef", - "name": "Meet Booleans", - "difficulty": "9.98", - "description": [ - "Return true", - "Some additional directions" - ], - "tests": [ - "expect(welcomeToBooleans()).to.be.a(\"boolean\");", - "expect(welcomeToBooleans()).to.be.true;" - ], - "challengeSeed": [ - "function welcomeToBooleans() {\n // Good luck!\n return false;\n}\n\nwelcomeToBooleans();" - ], - "challengeType": 1 - }, - { - "_id": "bd7123c9c441eddfaeb5bdef", - "name": "Meet Booleans", - "difficulty": "9.98", - "description": [ - "Return true", - "Some additional directions" - ], - "tests": [ - "expect(welcomeToBooleans()).to.be.a(\"boolean\");", - "expect(welcomeToBooleans()).to.be.true;" - ], - "challengeSeed": [ - "function welcomeToBooleans() {", - "// Good luck!", - "return false;", - "}", - "", - "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 - }, - { - "_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 } ] diff --git a/views/account/show.jade b/views/account/show.jade index 2291e6868f..6b4a3d2c8e 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -125,3 +125,34 @@ block content .row.text-primary h4.col-sm-6.text-right Longest Streak: #{longestStreak} h4.col-sm-6.text-left Current Streak: #{currentStreak} + + - if (challenges.length > 0) + .col-sm-12 + table.table.table-striped + thead + tr + th Challenge + th Date Completed + th Link + for challenge in challenges + tr + td= challenge.name + td= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") + td + a(href=challenge.solution) View my solution + + br + - if (bonfires.length > 0) + .col-sm-12 + table.table.table-striped + thead + tr + th Bonfire + th Date Completed + th Link + for bonfire in bonfires + tr + td= bonfire.name + td= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY") + td= bonfire.solution + br From c8b72499abb27cda2597d1529d215218ede00145 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 00:16:23 -0700 Subject: [PATCH 088/125] dynamically redirect chat link in navbar --- controllers/resources.js | 10 +++++++--- views/partials/navbar.jade | 10 ++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index 4b39ceb9ca..32b6f99924 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -83,9 +83,13 @@ module.exports = { }, chat: function chat(req, res) { - res.render('resources/chat', { - title: "Enter Free Code Camp's Chat Rooms" - }); + if (req.user && req.user.progressTimestamps.length > 5) { + res.redirect('http://gitter.im/freecodecamp/freecodecamp'); + } else { + res.render('resources/chat', { + title: "Enter Free Code Camp's Chat Rooms" + }); + } }, jqueryExercises: function jqueryExercises(req, res) { diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 719644207e..340f15cf8a 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -13,14 +13,12 @@ ul.nav.navbar-nav.navbar-right.hamburger-dropdown li a(href='/challenge-map') Challenges - if (user && user.progressTimestamps.length > 5) - li - a(href='https://gitter.im/freecodecamp/freecodecamp') Chat - else - li - a(href='/chat') Chat + li + a(href='/chat' target='_blank') Chat li a(href='/stories/hot') News + li + a(href='/wiki') Wiki if !user li       li From 60fee9457425253a28952a7979dbe05fae19101d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 16:44:07 -0700 Subject: [PATCH 089/125] create infrastructure for advancing through wiki --- app.js | 13 +++----- controllers/resources.js | 11 ++++++ controllers/wiki.js | 72 ++++++++++++++++++++++++++++------------ models/User.js | 2 ++ public/js/main.js | 22 ++++++++++++ views/wiki/show.jade | 5 ++- 6 files changed, 94 insertions(+), 31 deletions(-) diff --git a/app.js b/app.js index 8c7f203fb0..f2a65b08c2 100644 --- a/app.js +++ b/app.js @@ -515,15 +515,12 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); * Wiki related routes */ -app.get( - '/wiki/:wikiName', - wikiController.returnIndividualWiki -); -app.get( - '/wiki', - wikiController.returnHomeWiki -); +app.get('/wiki/:wikiName', wikiController.returnIndividualWiki); + +app.get('/wiki', wikiController.returnNextWiki); + +app.post('/completed-wiki/', wikiController.completedWiki); /** diff --git a/controllers/resources.js b/controllers/resources.js index 32b6f99924..e8eb803609 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -239,6 +239,17 @@ module.exports = { }); }, + allWikiIds: function() { + return wikis.map(function(elem) { + return { + _id: elem._id, + } + }) + .map(function(elem) { + return elem._id; + }); + }, + allBonfireNames: function() { return bonfires.map(function(elem) { return { diff --git a/controllers/wiki.js b/controllers/wiki.js index 360b748409..dae71a3998 100644 --- a/controllers/wiki.js +++ b/controllers/wiki.js @@ -29,6 +29,7 @@ exports.returnIndividualWiki = function(req, res, next) { } res.render('wiki/show', { title: wiki.name, + wikiId: wiki._id, description: wiki.description.join('') }); }); @@ -40,30 +41,57 @@ exports.showAllWikis = function(req, res) { res.send(data); }; -exports.returnHomeWiki = function(req, res) { - var dashedName = req.params.wikiName; +exports.returnNextWiki = function(req, res, next) { + if (!req.user) { + return res.redirect('../wiki/a-guide-to-our-wiki'); + } - Wiki.find({'name': 'A Guide to our Wiki'}, function(err, wiki) { - if (err) { - next(err); - } + var completed = req.user.completedWikis; - if (wiki.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a wiki entry with that name. Please double check the name." - }); + req.user.uncompletedWikis = resources.allWikiIds().filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.save(); - return res.redirect('/wiki'); - } + var uncompletedWikis = req.user.uncompletedWikis; - wiki = wiki.pop(); - var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../wiki/' + dashedNameFull); - } - res.render('wiki/show', { - title: wiki.name, - description: wiki.description.join('') - }); - }); + var displayedWikis = Wiki.find({'_id': uncompletedWikis[0]}); + displayedWikis.exec(function(err, wiki) { + if (err) { + return next(err); + } + wiki = wiki.pop(); + if (wiki === undefined) { + req.flash('errors', { + msg: "It looks like you've read all our current Wiki entries. Let us know if you'd like to contribute to our wiki!" + }); + return res.redirect('../wiki/a-guide-to-our-wiki'); + } + var nameString = wiki.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../wiki/' + nameString); + }); +}; + +exports.completedWiki = function (req, res, next) { + debug('params in completedWiki', req.params); + var wikiId = req.body.wikiInfo.wikiId; + + req.user.completedWikis.push(wikiId); + + var index = req.user.uncompletedWikis.indexOf(wikiId); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedWikis.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + res.send(true); + } + }); }; diff --git a/models/User.js b/models/User.js index cc80440cc3..d511c4a897 100644 --- a/models/User.js +++ b/models/User.js @@ -127,6 +127,8 @@ var userSchema = new mongoose.Schema({ verified: Boolean } ], + completedWikis: [], + uncompletedWikis: [], currentStreak: { type: Number, default: 0 diff --git a/public/js/main.js b/public/js/main.js index 72227d9609..06d2c1b8ed 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -52,6 +52,23 @@ $(document).ready(function() { } } + function completedWiki(wikiId) { + if ($('.signup-btn-nav').length < 1) { + $.post( + '/completed-wiki', + { + wikiInfo: { + wikiId: wikiId + } + }, + function(res) { + if (res) { + window.location.href = '/wiki' + } + }); + } + } + $('.next-bonfire-button').on('click', function() { var bonfireSolution = myCodeMirror.getValue(); var thisBonfireHash = passedBonfireHash || null; @@ -61,6 +78,11 @@ $(document).ready(function() { }); + $('.next-wiki-button').on('click', function() { + var wikiId = $('#wikiId').text(); + completedWiki(wikiId); + }); + $("img").error(function () { $(this).unbind("error").attr("src", "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"); }); diff --git a/views/wiki/show.jade b/views/wiki/show.jade index c8bc601c78..8fe78b801d 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -10,7 +10,9 @@ block content div!= description .spacer .text-center - #showAllButton.btn.btn-info.btn-big Show all Wiki Articles + .next-wiki-button.btn.btn-primary.btn-big Take me to the next article + .ten-pixel-break + #showAllButton.btn.btn-info.btn-big Show all wiki articles .spacer .row .col-xs-12.text-center @@ -24,3 +26,4 @@ block content a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body include ../partials/wikis + #wikiId.hidden= wikiId From 32f9a61d5d77b6aa38b970e0f6e8dc92d1d1c6d3 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 17:17:31 -0700 Subject: [PATCH 090/125] add hotkey to wiki pages --- views/wiki/show.jade | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/views/wiki/show.jade b/views/wiki/show.jade index 8fe78b801d..6b0a7d0b09 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -7,12 +7,17 @@ block content .panel-heading.text-center h1= title .panel-body - div!= description + .row + .col-xs-12 + div!= description .spacer - .text-center - .next-wiki-button.btn.btn-primary.btn-big Take me to the next article - .ten-pixel-break - #showAllButton.btn.btn-info.btn-big Show all wiki articles + .spacer + .spacer + .col-xs-12.col-sm-6.col-sm-offset-3 + .text-center + .next-wiki-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) + .ten-pixel-break + #showAllButton.btn.btn-info.btn-big.btn-block Show me all wiki articles .spacer .row .col-xs-12.text-center @@ -27,3 +32,11 @@ block content .modal-body include ../partials/wikis #wikiId.hidden= wikiId + script. + $(document).ready(function() { + $('body').keydown(function(e) { + if (e.ctrlKey && e.keyCode == 13) { + $('.next-wiki-button').click(); + } + }); + }); From ef57ec58c2b8cffb2b69866cb29f9c5a6b3d49cd Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 17:35:08 -0700 Subject: [PATCH 091/125] improve wikis with hotkey and update the flash to allow links --- controllers/wiki.js | 4 ++-- public/css/main.less | 4 ++++ views/account/show.jade | 2 -- views/partials/flash.jade | 37 +++++++++++++++--------------- views/partials/universal-head.jade | 2 ++ 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/controllers/wiki.js b/controllers/wiki.js index dae71a3998..578c782079 100644 --- a/controllers/wiki.js +++ b/controllers/wiki.js @@ -64,8 +64,8 @@ exports.returnNextWiki = function(req, res, next) { } wiki = wiki.pop(); if (wiki === undefined) { - req.flash('errors', { - msg: "It looks like you've read all our current Wiki entries. Let us know if you'd like to contribute to our wiki!" + req.flash('success', { + msg: "You've read all our current Wiki entries. You can contribute to our Wiki here." }); return res.redirect('../wiki/a-guide-to-our-wiki'); } diff --git a/public/css/main.less b/public/css/main.less index ddd7d6d095..a660554f09 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -899,6 +899,10 @@ iframe.iphone { height: 40px; } +.alert a { + text-decoration: underline; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/views/account/show.jade b/views/account/show.jade index 6b4a3d2c8e..53888f8549 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -98,8 +98,6 @@ block content .hidden-xs.col-sm-12 #cal-heatmap.img-center - script(src="//d3js.org/d3.v3.min.js") - script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") script. $(document).ready(function() { var cal = new CalHeatMap(); diff --git a/views/partials/flash.jade b/views/partials/flash.jade index cab3d7de09..415005645f 100644 --- a/views/partials/flash.jade +++ b/views/partials/flash.jade @@ -1,18 +1,19 @@ -if messages.errors - .alert.alert-danger.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for error in messages.errors - div= error.msg -if messages.info - .alert.alert-info.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for info in messages.info - div= info.msg -if messages.success - .alert.alert-success.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for success in messages.success - div= success.msg \ No newline at end of file +.col-xs-12 + if messages.errors + .alert.alert-danger.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for error in messages.errors + div!= error.msg + if messages.info + .alert.alert-info.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for info in messages.info + div!= info.msg + if messages.success + .alert.alert-success.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for success in messages.success + div!= success.msg diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 27ef5ba82e..4928e35797 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,6 +1,8 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") +script(src="//d3js.org/d3.v3.min.js") +script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') From 840930d9671ecee154756d4056fe73ed58df78e6 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 5 Apr 2015 18:37:58 -0700 Subject: [PATCH 092/125] get bonfire solutions showing up in the view; eliminate a lot of old views in favor of new challenge map --- app.js | 6 +-- public/js/main.js | 26 +++-------- views/account/show.jade | 25 +++++----- views/bonfire/show.jade | 10 +--- views/challenges/show.jade | 10 +--- views/coursewares/showHTML.jade | 9 ---- views/coursewares/showJS.jade | 8 ---- views/coursewares/showVideo.jade | 8 ---- views/nonprofits/show.jade | 2 +- views/partials/bonfires.jade | 30 ------------ views/partials/challenges.jade | 28 ----------- views/partials/faq.jade | 75 ------------------------------ views/partials/navbar.jade | 2 +- views/partials/stats.jade | 0 views/partials/universal-head.jade | 1 - views/resources/learn-to-code.jade | 6 +-- views/wiki/show.jade | 2 +- 17 files changed, 29 insertions(+), 219 deletions(-) delete mode 100644 views/partials/bonfires.jade delete mode 100644 views/partials/challenges.jade delete mode 100644 views/partials/faq.jade delete mode 100644 views/partials/stats.jade diff --git a/app.js b/app.js index f2a65b08c2..1038c73400 100644 --- a/app.js +++ b/app.js @@ -249,7 +249,7 @@ app.get('/jquery-exercises', resourcesController.jqueryExercises); app.get('/chat', resourcesController.chat); -app.get('/challenge-map', challengeMapController.challengeMap); +app.get('/map', challengeMapController.challengeMap); app.get('/live-pair-programming', function(req, res) { res.redirect(301, '/wiki/live-stream-pair-programming-on-twitch.tv'); @@ -484,8 +484,6 @@ app.get('/api/trello', resourcesController.trelloCalls); * Bonfire related routes */ -app.get('/bonfires/getBonfireList', bonfireController.showAllBonfires); - app.get('/wiki/getWikiList', wikiController.showAllWikis); app.get('/playground', bonfireController.index); @@ -529,8 +527,6 @@ app.post('/completed-wiki/', wikiController.completedWiki); app.get('/challenges/', coursewareController.returnNextCourseware); -app.get('/challenges/getCoursewareList', coursewareController.showAllCoursewares); - app.get( '/challenges/:coursewareName', coursewareController.returnIndividualCourseware diff --git a/public/js/main.js b/public/js/main.js index 06d2c1b8ed..5d2fc5e571 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -30,7 +30,7 @@ $(document).ready(function() { .addClass('animated fadeInDown'); }); - function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash) { + function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash, bonfireName) { $('#complete-bonfire-dialog').modal('show'); // Only post to server if there is an authenticated user if ($('.signup-btn-nav').length < 1) { @@ -38,10 +38,10 @@ $(document).ready(function() { '/completed-bonfire', { bonfireInfo: { - bonfireName: title, completedWith: didCompleteWith, solution: bonfireSolution, - bonfireHash: thisBonfireHash + bonfireHash: thisBonfireHash, + bonfireName: bonfireName } }, function(res) { @@ -72,9 +72,9 @@ $(document).ready(function() { $('.next-bonfire-button').on('click', function() { var bonfireSolution = myCodeMirror.getValue(); var thisBonfireHash = passedBonfireHash || null; - var bonfireName = title; + var bonfireName = $('#bonfire-name').text(); var didCompleteWith = $('#completed-with').val() || null; - completedBonfire(didCompleteWith, bonfireName, bonfireSolution, thisBonfireHash); + completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash, bonfireName); }); @@ -99,18 +99,6 @@ $(document).ready(function() { editor.focus(); }); - $('#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(); - }); - $('#complete-courseware-dialog').on('hidden.bs.modal', function() { editor.focus(); }); @@ -188,11 +176,11 @@ $(document).ready(function() { $('.all-challenges').on('click', function() { - $('#all-challenges-dialog').modal('show'); + $('#show-all-dialog').modal('show'); }); $('#showAllButton').on('click', function() { - $('#all-challenges-dialog').modal('show'); + $('#show-all-dialog').modal('show'); }); $('.next-challenge-button').on('click', function() { diff --git a/views/account/show.jade b/views/account/show.jade index 53888f8549..dab650715e 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -97,6 +97,7 @@ block content | Try it out .hidden-xs.col-sm-12 + script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") #cal-heatmap.img-center script. $(document).ready(function() { @@ -129,14 +130,14 @@ block content table.table.table-striped thead tr - th Challenge - th Date Completed - th Link + th.col-xs-4 Challenge + th.col-xs-2 Completed + th.col-xs-6 Link for challenge in challenges tr - td= challenge.name - td= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") - td + td.col-xs-4= challenge.name + td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") + td.col-xs-6 a(href=challenge.solution) View my solution br @@ -145,12 +146,12 @@ block content table.table.table-striped thead tr - th Bonfire - th Date Completed - th Link + th.col-xs-4 Bonfire + th.col-xs-2 Completed + th.col-xs-6 Solution for bonfire in bonfires tr - td= bonfire.name - td= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY") - td= bonfire.solution + td.col-xs-4= bonfire.name + td.col-xs-2= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY") + td.col-xs-6= bonfire.solution br diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 99ee61ba12..fa9f918f43 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -21,7 +21,7 @@ block content .row .col-xs-12.col-sm-12.col-md-4.bonfire-top #testCreatePanel - h1.text-center= name + h1#bonfire-name.text-center= name h2.text-center .bonfire-flames Difficulty:  if (difficulty == "0") @@ -82,7 +82,6 @@ block content | 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 form.code .form-group.codeMirrorView @@ -139,13 +138,6 @@ block content - 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 Bonfires - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - include ../partials/bonfires script. var MDNlinks = !{JSON.stringify(MDNlinks)}; if (!MDNlinks.length) { diff --git a/views/challenges/show.jade b/views/challenges/show.jade index a0c81d2a7b..8f9e69bc8f 100644 --- a/views/challenges/show.jade +++ b/views/challenges/show.jade @@ -36,13 +36,7 @@ block content = phrase - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - #all-challenges-dialog.modal(tabindex='-1') - .modal-dialog.animated.fadeInUp.fast-animation - .modal-content - .modal-header.challenge-list-header Challenges - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - include ../partials/challenges + script. $.ajax({ url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fchallenges%2F' + !{JSON.stringify(number)} + '&format=txt' @@ -53,4 +47,4 @@ block content url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; $('.btn-twitter').attr('href', url); } - ); \ No newline at end of file + ); diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index e4ac72909e..840056724d 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -50,8 +50,6 @@ block content script. var userLoggedIn = false; .button-spacer - #showAllButton.btn.btn-info.btn-big.btn-block Show all challenges - br ul#testSuite.list-group br script(type="text/javascript"). @@ -85,10 +83,3 @@ 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 3d54d61c42..3e33ee4a4b 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -35,7 +35,6 @@ 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 @@ -75,10 +74,3 @@ 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 9f35202412..901f11bfbe 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -22,7 +22,6 @@ block content var userLoggedIn = false; br .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)}; @@ -54,10 +53,3 @@ 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/nonprofits/show.jade b/views/nonprofits/show.jade index ed90b630c6..31297684f2 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -73,7 +73,7 @@ block content if !user a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) .spacer - #all-challenges-dialog.modal(tabindex='-1') + #show-all-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation .modal-content .modal-header.all-list-header Nonprofit Projects diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade deleted file mode 100644 index ffb5fec419..0000000000 --- a/views/partials/bonfires.jade +++ /dev/null @@ -1,30 +0,0 @@ -h3 - ol#bonfireList - script(src='/js/lib/ramda/ramda.min.js') - script. - var getLinkedName = function getLinkedName(name) { - // ensure name is a string - name = name + ''; - return name.toLowerCase().replace(/\s/g, '-'); - } - $.ajax({ - url: '/bonfires/getBonfireList', - type: 'GET' - }) - .success( - function(data) { - var docfrag = document.createDocumentFragment(); - for (var i = 0; i < data.bonfireList.length; i++) { - var li = document.createElement("li"); - var linkedName = getLinkedName(data.bonfireList[i].name); - 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 + ""); - } - - docfrag.appendChild(li); - }; - $('#bonfireList').append(docfrag); - }); diff --git a/views/partials/challenges.jade b/views/partials/challenges.jade deleted file mode 100644 index bea0d0a786..0000000000 --- a/views/partials/challenges.jade +++ /dev/null @@ -1,28 +0,0 @@ -h3 - 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 + ""); - } - - docfrag.appendChild(li); - }; - $('#coursewareList').append(docfrag); - }); diff --git a/views/partials/faq.jade b/views/partials/faq.jade deleted file mode 100644 index 19144be470..0000000000 --- a/views/partials/faq.jade +++ /dev/null @@ -1,75 +0,0 @@ -.panel.panel-info - .panel-heading.landing-panel-heading.text-center Frequently Asked Questions - .panel-body - .landing-panel-body - .row - .text-left.col-xs-12.col-md-10.col-md-offset-1 - h2 What will I learn, and in what sequence? - ul - p.landing-p First, you'll learn basic web design tools like: - ul - li.landing-p • HTML - the structure of web pages - li.landing-p • CSS - the visual style of web pages - li.landing-p • Bootstrap - a "responsive design" tool that helps your websites look great on tablets and phones - li.landing-p • jQuery - an easy tool for controlling content in the browser - li.landing-p • Chrome DevTools - a tool for understanding and debugging websites, right in your browser - p.landing-p Then you'll learn computer science and the art of programming: - ul - li.landing-p • JavaScript - the one programming language that all web browsers use - li.landing-p • Algorithms - step-by-step recipes for getting things done - li.landing-p • Automated Testing - write tests to test the limits of your code - p.landing-p You'll spend the last half of Free Code Camp using Agile Methodologies and Full Stack JavaScript to build projects for nonprofits: - ul - li.landing-p • Agile - a set of software development principles that focus the design and production of a project on the needs of its users - li.landing-p • Git - a version control system for saving and sharing your projects - li.landing-p • MongoDB - a popular non-relational database - li.landing-p • Angular.js - a tool for making exciting web interfaces - li.landing-p • Express.js - a powerful web development framework - li.landing-p • Node.js - a JavaScript-based web server - h2 Will I be ready to get a software engineer job after this? - ul - p.landing-p At the end of Free Code Camp, you will have pair programmed around 1,000 hours with dozens of other students, built a portfolio of projects that people are actively using, and a roster of glowing references from nonprofits you've helped. This is more coding than most coding bootcamps provide, and on average, 75% of bootcamp graduates get software engineering jobs within 6 months, and earn an average annual salary of $76,000. - img.img-center.img-responsive(src="https://s3.amazonaws.com/freecodecamp/table-of-earnings.png" alt="A chart showing the average earnings of coding bootcamp graduates") - h2 How long does Free Code Camp take? - ul - p.landing-p It takes about 1,000 hours of coding to develop the skills you'll need to get an entry level software engineering job. Most coding bootcamps try to jam all this into 12 weeks of intensive study. Free Code Camp is fully online, and there will always be other people at your skill level that you can pair program with, so you can learn at your own pace. Here are some example coding schedules: - table.table - thead - th Time budgeted - th.hidden-xs Hours per week - th Weeks to complete - tr.info - td Weekends - td.hidden-xs 10 hours/week - td 100 weeks (2 years) - tr.success - td Nights and Weekends - td.hidden-xs 20 hours/week - td 50 weeks (1 year) - tr.warning - td Full time - td.hidden-xs 40 hours/week - td 25 weeks (6 months) - tr.danger - td Traditional Bootcamp Pacing - td.hidden-xs 80 hours/week - td 12 weeks (3 months) - h2 Why does Free Code Camp use JavaScript instead of Ruby or Python? - ul - p.landing-p Like JavaScript, Ruby and Python are high-level scripting languages that can be used for full stack web development. But even if you learned these languages, you'd still need to learn JavaScript. That's because JavaScript is the only language that runs in web browsers. JavaScript has been around for 20 years, and it is still growing in popularity. Because of this, JavaScript has more tools and online learning resources than any other language. - img.img-center.img-responsive(src="https://s3.amazonaws.com/freecodecamp/github-repo-growth.png", style="max-height: 355px;" alt="A chart showing the volume of new GitHub repositories by year, with JavaScript growing and most languages declining.") - br - h2 How will I learn all this? - ul - p.landing-p By pair programming with other Free Code Camp students on our coding challenges. Eventually, you'll work with people at nonprofits to build real-life software solutions. - h2 What is 'pair programming', and what's so special about it? - ul - p.landing-p Pair programming is where two people code together on one computer. You discuss different approaches to solving problems, and keep each other motivated. The result is better code than either of you could have written by yourselves. Because of its benefits, many engineers pair program full time. And it's the best way to learn coding. Thanks to tools that allow two people to share mouse and keyboard inputs, you can pair program with a friend without needing to be in the same room. - h2 Is this really free? Do you claim part of my first year's salary like some bootcamps do? - ul - p.landing-p Our name is Free Code Camp. We are a free code camp. If you had to pay us (or sign over future earnings), we'd have to change our name. And we are not going to do that. - h2 Does Free Code Camp have an application process? - ul - p.landing-p Unlike coding bootcamps, anyone can study at Free Code Camp. We're not going to tell you that you can't become a software engineer. We believe the only person who should be able to tell you that is you. If you persevere, and keep working through our challenges and nonprofit projects, you will become an employable software engineer. - br - br diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 340f15cf8a..a71cb7a0fd 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -12,7 +12,7 @@ .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown li - a(href='/challenge-map') Challenges + a(href='/map') Map li a(href='/chat' target='_blank') Chat li diff --git a/views/partials/stats.jade b/views/partials/stats.jade deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index 4928e35797..e9560000d0 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -2,7 +2,6 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") script(src="//d3js.org/d3.v3.min.js") -script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 12a8706ba2..3d2b986f0e 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -39,7 +39,7 @@ block content script. var challengeName = 'Learn to code' .row - .col-xs-12.col-sm-12.col-md-6 + .col-xs-12 .panel.panel-info .panel-heading.landing-panel-heading.text-center Announcements .panel-body @@ -76,8 +76,6 @@ block content .col-xs-12.github-and-twitter-button-text html. - .col-xs-12.col-sm-12.col-md-6 - include ../partials/faq //#announcementModal.modal(tabindex='-1') // .modal-dialog // .modal-content @@ -95,4 +93,4 @@ block content // $('#announcementModal').modal('show'); // localStorage.campWideMeeting = "true"; // } - // }); \ No newline at end of file + // }); diff --git a/views/wiki/show.jade b/views/wiki/show.jade index 6b0a7d0b09..c00c95c96c 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -24,7 +24,7 @@ block content if !user a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) .spacer - #all-challenges-dialog.modal(tabindex='-1') + #show-all-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation .modal-content .modal-header.all-list-header Wiki Articles From bd7c5310b1303231b1e6c19b0dc6087c7bcc3113 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 7 Apr 2015 00:11:31 -0700 Subject: [PATCH 093/125] improve the footer --- app.js | 2 + controllers/courseware.js | 1 + controllers/resources.js | 8 +++- controllers/story.js | 68 ++++++++++++++++++++++++++++++ public/css/main.less | 11 ++++- seed_data/coursewares.json | 18 -------- views/account/show.jade | 8 ++-- views/partials/flash.jade | 39 ++++++++--------- views/partials/footer.jade | 16 +++---- views/resources/learn-to-code.jade | 36 ++++++++-------- views/resources/twitch.jade | 59 ++++++++++++++++++++++++++ 11 files changed, 196 insertions(+), 70 deletions(-) create mode 100644 views/resources/twitch.jade diff --git a/app.js b/app.js index 1038c73400..39ef393fe9 100644 --- a/app.js +++ b/app.js @@ -249,6 +249,8 @@ app.get('/jquery-exercises', resourcesController.jqueryExercises); app.get('/chat', resourcesController.chat); +app.get('/twitch', resourcesController.twitch); + app.get('/map', challengeMapController.challengeMap); app.get('/live-pair-programming', function(req, res) { diff --git a/controllers/courseware.js b/controllers/courseware.js index 08cb36a680..0c91997672 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -364,6 +364,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { }); var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); + console.log('index here', index) if (index > -1) { req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); diff --git a/controllers/resources.js b/controllers/resources.js index e8eb803609..ec1d7466f2 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -87,7 +87,7 @@ module.exports = { res.redirect('http://gitter.im/freecodecamp/freecodecamp'); } else { res.render('resources/chat', { - title: "Enter Free Code Camp's Chat Rooms" + title: "Watch us code live on Twitch.tv" }); } }, @@ -98,6 +98,12 @@ module.exports = { }); }, + twitch: function twitch(req, res) { + res.render('resources/twitch', { + title: "Enter Free Code Camp's Chat Rooms" + }); + }, + githubCalls: function(req, res) { var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { diff --git a/controllers/story.js b/controllers/story.js index 27bbf47916..f788670330 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -9,6 +9,7 @@ var R = require('ramda'), mongodb = require('mongodb'), MongoClient = mongodb.MongoClient, secrets = require('../config/secrets'), + nodemailer = require('nodemailer'), sanitizeHtml = require('sanitize-html'); function hotRank(timeValue, rank) { @@ -384,6 +385,7 @@ exports.commentSubmit = function(req, res, next) { topLevel: true, commentOn: Date.now() }); + commentSave(comment, Story, res, next); }; @@ -438,8 +440,74 @@ function commentSave(comment, Context, res, next) { res.send(true); }); } + User.findOne({'profile.username': associatedStory.author.username}, function(err, recipient) { + if (err) { + return next(err); + } + Comment.findById(associatedStory._id, function(err, originalStory) { + if (err) { + return next(err); + } + debug('is it a comment?', originalStory); + if (!originalStory) { + Story.findById(associatedStory.associatedPost, function(err, originalStory) { + debug('is it a story?', originalStory); + if (err) { + return next(err); + } + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + console.log('1!'); + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: originalStory.author.username + " replied to you on Camper News!", + text: [ + "Here.", + '- the Volunteer Camp Counselor Team' + ].join('') + }; + console.log('2!'); + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + done(null, null); + }); + }); + } else { + console.log('definitely a comment'); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + console.log('1!'); + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: originalStory.author.username + " replied to you on Camper News!", + text: [ + "Here.", + '- the Volunteer Camp Counselor Team' + ].join('') + }; + console.log('2!'); + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + done(null, null); + }); + } + }); + }); }); } catch (e) { + debug('hey there\'s error'); // delete comment return next(err); } diff --git a/public/css/main.less b/public/css/main.less index a660554f09..3b9f63a04e 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -608,16 +608,23 @@ thead { .fcc-footer { width: 100%; + height: 50px; text-align: center; background-color: #4a2b0f; - height: 40px; + padding: 12px; bottom: 0; left: 0; position: absolute; a { - font-size: 28px; + font-size: 20px; color: #eee; + margin-left: 0px; + margin-right: 0px; + padding-left: 10px; + padding-right: 10px; &:hover { + padding-top: 14px; + padding-bottom: 14px; color: #4a2b0f; background-color: #eee; text-decoration: none; diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index ebec8b18af..c80ad7a291 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -112,24 +112,6 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7110d8c441eddfaeb5bdef", - "name": "Tweak HTML and CSS in CodePen", - "difficulty": 0.08, - "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", diff --git a/views/account/show.jade b/views/account/show.jade index dab650715e..8f3aa810ae 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 diff --git a/views/partials/flash.jade b/views/partials/flash.jade index 415005645f..e69a6d69bc 100644 --- a/views/partials/flash.jade +++ b/views/partials/flash.jade @@ -1,19 +1,20 @@ -.col-xs-12 - if messages.errors - .alert.alert-danger.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for error in messages.errors - div!= error.msg - if messages.info - .alert.alert-info.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for info in messages.info - div!= info.msg - if messages.success - .alert.alert-success.fade.in - button.close(type='button', data-dismiss='alert') - span.ion-close-circled - for success in messages.success - div!= success.msg +.row + .col-xs-12 + if messages.errors + .alert.alert-danger.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for error in messages.errors + div!= error.msg + if messages.info + .alert.alert-info.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for info in messages.info + div!= info.msg + if messages.success + .alert.alert-success.fade.in + button.close(type='button', data-dismiss='alert') + span.ion-close-circled + for success in messages.success + div!= success.msg diff --git a/views/partials/footer.jade b/views/partials/footer.jade index 8be0eefa28..3004dcbcf1 100644 --- a/views/partials/footer.jade +++ b/views/partials/footer.jade @@ -1,15 +1,15 @@ .fcc-footer .col-xs-12.hidden-xs.hidden-sm - a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')  Blog   - a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank')  Twitch   - a.ion-social-github(href="http://github.com/freecodecamp", target='_blank')  Github   - a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter   - a.ion-information-circled(href="/learn-to-code")  About   - a.ion-locked(href="/privacy")  Privacy   + a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')  Blog   + a.ion-social-twitch-outline(href="/twitch")  Twitch  + a.ion-social-github(href="http://github.com/freecodecamp", target='_blank')  Github   + a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter   + a.ion-information-circled(href="/learn-to-code")  About   + a.ion-locked(href="/privacy")  Privacy   .col-xs-12.visible-xs.visible-sm a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank') span.sr-only Free Code Camp's Blog - a.ion-social-twitch-outline(href="http://www.twitch.tv/freecodecamp", target='_blank') + a.ion-social-twitch-outline(href="/twitch") span.sr-only Free Code Camp Live Pair Programming on Twitch.tv a.ion-social-github(href="http://github.com/freecodecamp", target='_blank') span.sr-only Free Code Camp on GitHub @@ -18,4 +18,4 @@ a.ion-information-circled(href="/learn-to-code") span.sr-only About Free Code Camp a.ion-locked(href="/privacy") - span.sr-only Free Code Camp's Privacy Policy \ No newline at end of file + span.sr-only Free Code Camp's Privacy Policy diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 3d2b986f0e..723f89874f 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -76,21 +76,21 @@ block content .col-xs-12.github-and-twitter-button-text html. - //#announcementModal.modal(tabindex='-1') - // .modal-dialog - // .modal-content - // .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST - // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - // .modal-body - // h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   - // a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel - // | . - // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp - // a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! - //script. - // $(document).ready(function() { - // if (!localStorage || !localStorage.campWideMeeting) { - // $('#announcementModal').modal('show'); - // localStorage.campWideMeeting = "true"; - // } - // }); + #announcementModal.modal(tabindex='-1') + .modal-dialog + .modal-content + .modal-header.challenge-list-header Camp-wide Meeting on Saturday at Noon EST + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + h3.text-left We'll live-stream some of Free Code Camp's new features, and campers will show what they're building. Live Saturday, March 28 at Noon EST on our   + a(href='http://twitch.tv/freecodecamp', target='_blank') Twitch.tv channel + | . + a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='http://twitch.tv/freecodecamp', target='_blank') Take me to Twitch so I can follow Free Code Camp + a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up! + script. + $(document).ready(function() { + if (!localStorage || !localStorage.campWideMeeting) { + $('#announcementModal').modal('show'); + localStorage.campWideMeeting = "true"; + } + }); diff --git a/views/resources/twitch.jade b/views/resources/twitch.jade new file mode 100644 index 0000000000..e6c4ff4913 --- /dev/null +++ b/views/resources/twitch.jade @@ -0,0 +1,59 @@ +extends ../layout +block content + .col-xs-12 + .panel.panel-info + .panel-heading.text-center Watch us code on our Twitch.tv channel + .panel-body.text-center + h2 Watch the live stream below or on our   + a(href="http://twitch.tv/freecodecamp", target='_blank') Twitch.tv channel + | . + .spacer + .row + .col-md-8.col-xs-12 + .embed-responsive.embed-responsive-16by9 + iframe(src='http://www.twitch.tv/freecodecamp/embed', frameborder='0', scrolling='no') + .col-md-4.col-xs-12 + .visible-sm.visible-xs + .embed-responsive.embed-responsive-16by9 + iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') + .visible-md.visible-lg + .embed-responsive.embed-responsive-twitch-chat + iframe(src='http://www.twitch.tv/freecodecamp/chat?popout=', frameborder='0', scrolling='no') + .row + .col-xs-12 + h2 Check out our scheduled shows. You can add them to your calendar. + .embed-responsive.embed-responsive-16by9 + iframe.embed-responsive-item(src="https://www.google.com/calendar/embed?src=freecodecamp.com_r06116ile3o6ucpif7s0g281tc%40group.calendar.google.com&ctz=America/New_York&mode=AGENDA" style="border: 0" width="800" height="600" frameborder="0" scrolling="no") + .row + .col-xs-12 + h2 Here are some of our previous shows (you can full-screen them) + .row + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/_BErpDdmBOw') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/_BErpDdmBOw") http://www.youtube.com/watch/_BErpDdmBOw + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/Fn9HMn79KH0') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/Fn9HMn79KH0") http://www.youtube.com/watch/Fn9HMn79KH0 + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/S7iRBZJwOAs') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/S7iRBZJwOAs") http://www.youtube.com/watch/S7iRBZJwOAs + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/BHNRg39ZblE') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/BHNRg39ZblE") http://www.youtube.com/watch/BHNRg39ZblE + .col-xs-12.col-sm-12.col-md-6 + .embed-responsive.embed-responsive-16by9.big-break + iframe.embed-responsive-item(src='//www.youtube.com/embed/YDfkHlDmehA') + p.wrappable.negative-45 link:   + a(href="http://www.youtube.com/watch/YDfkHlDmehA") http://www.youtube.com/watch/YDfkHlDmehA + .row + .col-xs-12.text-center + if !user + a.btn.btn-cta.signup-btn.btn-primary(href="/login") Start learning to code (it's free) From df4c698177c6d8af79bd6b7eb98ef4053f32be67 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 7 Apr 2015 12:45:31 -0700 Subject: [PATCH 094/125] improve the challenge map --- seed_data/wikis.json | 29 +++++++-- views/challengeMap/show.jade | 115 +++++++++++++++++++---------------- views/partials/navbar.jade | 2 +- 3 files changed, 88 insertions(+), 58 deletions(-) diff --git a/seed_data/wikis.json b/seed_data/wikis.json index 1236041247..aa0165b7d9 100644 --- a/seed_data/wikis.json +++ b/seed_data/wikis.json @@ -1,11 +1,32 @@ [ { "_id": "bd7158d9c441eddfaeb5bdef", - "name": "A Guide to our Wiki", + "name": "How do I use this guide?", "description": [ "
    ", - "

    We have a variety of resources to answer your many questions.

    ", - "

    Click the \"Show all Wiki Articles\" button below and browse the topics we cover

    " + "

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

    ", + "

    You can click

    ", + "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " + ] + }, + { + "_id": "bd7158d9c541eddfaeb5bdef", + "name": "Will I be able to get a job after this?", + "description": [ + "
    ", + "

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

    ", + "

    You can click

    ", + "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " + ] + }, + { + "_id": "bd7158d9c542eddfaeb5bdef", + "name": "How do I use this guide?", + "description": [ + "
    ", + "

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

    ", + "

    You can click

    ", + "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " ] }, { @@ -44,7 +65,7 @@ }, { "_id": "bd7158d9c439eddfaeb5bdef", - "name": "Will I be ready to get a software engineer job after this?", + "name": "Will I be ready to get a coding job after this?", "description": [ "
    ", "

    At the end of Free Code Camp, you will have pair programmed around 1,000 hours with dozens of other students, built a portfolio of projects that people are actively using, and a roster of glowing references from nonprofits you've helped. This is more coding than most coding bootcamps provide, and on average, 75% of bootcamp graduates get software engineering jobs within 6 months, and earn an average annual salary of $76,000.

    ", diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 78a93f1791..32fea1f462 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -5,57 +5,66 @@ block content .panel-heading.text-center h1 Challenge Map .panel-body - .col-md-offset-2 - h2 - span.fa.fa-flag - |   Waypoints + .row + .col-xs-12.col-sm-12.col-md-8.col-md-offset-2 + h3 We strongly recommend you complete these in order. All of these must be completed before you can start working on nonprofit projects. + h2 + span.fa.fa-flag + |   Waypoints (web development lessons) - .col-xs-12 - h3.negative-15 - ol - for waypoint in waypoints - if completedCoursewareList.indexOf(waypoint._id) > -1 - li.strikethrough - a(href="/challenges/#{waypoint.name}")= waypoint.name - else - li - a(href="/challenges/#{waypoint.name}")= waypoint.name - h2 - span.ion-bonfire - |   Bonfires - .col-xs-12 - h3.negative-15 - ol - for bonfire in bonfires - if completedBonfireList.indexOf(bonfire._id) > -1 - li.strikethrough - a(href="/bonfires/#{bonfire.name}")= bonfire.name - else - li - a(href="/bonfires/#{bonfire.name}")= bonfire.name - h2 - span.fa.fa-angle-double-right - |   Ziplines - .col-xs-12 - h3.negative-15 - ol - for zipline in ziplines - if completedCoursewareList.indexOf(zipline._id) > -1 - li.strikethrough - a(href="/challenges/#{zipline.name}")= zipline.name - else - li - a(href="/challenges/#{zipline.name}")= zipline.name - h2 - span.fa.fa-level-down - |   Basejumps - .col-xs-12 - h3.negative-15 - ol - for basejump in basejumps - if completedCoursewareList.indexOf(basejump._id) > -1 - li.strikethrough - a(href="/challenges/#{basejump.name}")= basejump.name - else - li - a(href="/challenges/#{basejump.name}")= basejump.name + .col-xs-12 + h3.negative-15 + ol + for waypoint in waypoints + if completedCoursewareList.indexOf(waypoint._id) > -1 + li.strikethrough + a(href="/challenges/#{waypoint.name}")= waypoint.name + else + li + a(href="/challenges/#{waypoint.name}")= waypoint.name + h2 + span.ion-bonfire + |   Bonfires (algorithm practice) + .col-xs-12 + h3.negative-15 + ol + for bonfire in bonfires + if completedBonfireList.indexOf(bonfire._id) > -1 + li.strikethrough + a(href="/bonfires/#{bonfire.name}")= bonfire.name + else + li + a(href="/bonfires/#{bonfire.name}")= bonfire.name + h2 + span.fa.fa-angle-double-right + |   Ziplines (front end development practice) + .col-xs-12 + h3.negative-15 + ol + for zipline in ziplines + if completedCoursewareList.indexOf(zipline._id) > -1 + li.strikethrough + a(href="/challenges/#{zipline.name}")= zipline.name + else + li + a(href="/challenges/#{zipline.name}")= zipline.name + h2 + span.fa.fa-level-down + |   Basejumps (full stack development practice) + .col-xs-12 + h3.negative-15 + ol + for basejump in basejumps + if completedCoursewareList.indexOf(basejump._id) > -1 + li.strikethrough + a(href="/challenges/#{basejump.name}")= basejump.name + else + li + a(href="/challenges/#{basejump.name}")= basejump.name + h2 + span.ion-ios-heart + |   Nonprofit Projects + h3.negative-15 + ul + li + a(href="/nonprofits/index") Browse our nonprofit projects diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index a71cb7a0fd..17901f93ca 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -18,7 +18,7 @@ li a(href='/stories/hot') News li - a(href='/wiki') Wiki + a(href='/wiki') Field Guide if !user li       li From 751d25219e7d709237807e830f345fe8ae0efb57 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 7 Apr 2015 22:33:49 -0700 Subject: [PATCH 095/125] improve the wiki seed file and add strikethrough to read wikis --- controllers/wiki.js | 6 +- seed_data/coursewares.json | 431 +++++++++++++---------------- seed_data/wikis.json | 112 +++++--- views/account/show.jade | 39 +-- views/challengeMap/show.jade | 3 +- views/partials/universal-head.jade | 1 - views/partials/wikis.jade | 4 + views/stories/search-stories.jade | 4 +- views/wiki/show.jade | 2 +- 9 files changed, 299 insertions(+), 303 deletions(-) diff --git a/controllers/wiki.js b/controllers/wiki.js index 578c782079..04d95114ce 100644 --- a/controllers/wiki.js +++ b/controllers/wiki.js @@ -38,12 +38,14 @@ exports.returnIndividualWiki = function(req, res, next) { exports.showAllWikis = function(req, res) { var data = {}; data.wikiList = resources.allWikiNames(); + data.wikiIds = resources.allWikiIds(); + data.completedWikis = req.user.completedWikis; res.send(data); }; exports.returnNextWiki = function(req, res, next) { if (!req.user) { - return res.redirect('../wiki/a-guide-to-our-wiki'); + return res.redirect('../wiki/how-do-i-use-this-guide?'); } var completed = req.user.completedWikis; @@ -67,7 +69,7 @@ exports.returnNextWiki = function(req, res, next) { req.flash('success', { msg: "You've read all our current Wiki entries. You can contribute to our Wiki here." }); - return res.redirect('../wiki/a-guide-to-our-wiki'); + return res.redirect('../wiki/how-do-i-use-this-guide?'); } var nameString = wiki.name.toLowerCase().replace(/\s/g, '-'); return res.redirect('../wiki/' + nameString); diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index c80ad7a291..9deaa5e643 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -21,54 +21,101 @@ "tests": [] }, { - "_id": "bd7125d8c441eddfaeb5bdef", - "name": "Join Our Chat Room", + "_id": "bd7125d8c441eddfaeb5bdff", + "name": "Preview our Challenge Map", "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.", + "Before you start learning how to code, we'd like to introduce you to a few things.", + "In the next 15 minutes, we'll introduce you to our challenges, chat rooms, field guide, portfolios and news site. Then we'll teach you how to get help if you get stuck somewhere.", + "First let's look at our Challenge Map. Click on the \"Map\" button in the upper right hand corner. This map shows all the challenges that will teach you how to code.", + "You should complete all these challenges in order.", + "Once you finish these Waypoint challenges, you'll move on to Bonfires (algorithm practice), then Ziplines (front end development practice) and finally Basejumps (full stack development practice). After that, you'll start building projects for nonprofits.", + "All of these challenges can be completed in a single one- or two-hour sitting.", + "This challenge map is just for your reference. When you return to FreeCodeCamp.com, we'll automatically redirect you to the next challenge that you should be doing." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7125d8c441eddfaeb5bd0f", + "name": "Join Our Chat Room", + "difficulty": 0.03, + "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 and get help.", "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." + "You can also access this chatroom by clicking the \"Chat\" button in the upper right hand corner." ], "challengeType": 2, "tests": [] }, { - "_id": "bd7126d8c441eddfaeb5bdef", + "_id": "bd7125d8c441eddfaeb5bd1f", + "name": "Browse our Field Guide", + "difficulty": 0.04, + "challengeSeed": "114627322", + "description": [ + "Free Code Camp has an up-to-date field guide that will answer your many questions.", + "Click the \"Field Guide\" button in the upper right hand corner.", + "You can browse the field guide at your convenience. Most of its articles take less than 1 minute to read.", + "When you click the Field Guide button, it will always take you back to whichever article you were last reading" + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7125d8c441eddfaeb5bd2f", + "name": "Customize your Porfolio Page", + "difficulty": 0.05, + "challengeSeed": "114627322", + "description": [ + "You and all your fellow campers have portfolio pages.", + "To see your portfolio page, click your picture in the upper right hand corner.", + "Your portfolio page will automatically show off your progress through Free Code Camp.", + "Click the \"Update my portfolio page or manage my account\" button", + "You can link to your Github, Twitter and LinkedIn accounts. If you've already built some websites, you can link to them here as well.", + "Be sure to click the \"Update my Bio\" or \"Update my Social Links\" button to save this new information to your portfolio page." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7126d8c441eddfaeb5bd3f", "name": "Try Camper News", - "difficulty": 0.03, + "difficulty": 0.06, "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." + "Camper News is the best place for our campers to share and discuss helpful links.", + "Click \"News\" in the upper right hand corner.", + "You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.", + "You can upvote links. This will push the link up the rankings of hot links.", + "You an also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.", + "You can also submit links. You can modify the link's headline and also leave an initial comment about the link.", + "You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.", + "When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link." ], "challengeType": 2, "tests": [] }, { "_id": "bd7137d8c441eddfaeb5bdef", - "name": "Get Help the Hacker Way", + "name": "Learn how to Get Help", "difficulty": 0.04, "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).", - "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.", - "Go to http://stackoverflow.com/ and view the recent questions.", - "Now you have several ways of getting help when you're stuck." + "Let's cover one last thing before you start working through our lessons: how to get help.", + "Any time you get stuck or don't know what to do next, follow this simple algorithm (procedure): RSAP (Read, Search, Ask, Post).", + "First, R - Read the documentation or error message. A key skill that good coders have is the ability to interpret and then follow instructions.", + "Next, S - Search Google. Good Google queries take a lot of practice. When you search Google, you usually want to include the language or framework you're using. You also want to limit the results to a recent period.", + "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", + "Finally, P - Post on Stack Overflow. Before you attempt to do this, re-read Stack Overflow's guide to asking a good question: http://stackoverflow.com/help/how-to-ask.", + "Now you have a clear algorithm to follow when you need help!" ], "challengeType": 2, "tests": [] @@ -112,22 +159,10 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7111d8c441eddfaeb5bdef", - "name": "Build a CSS Robot", - "difficulty": 0.09, - "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.10, + "difficulty": 0.08, "challengeSeed": "114578435", "description": [ "jQuery is a powerful tool for manipulating HTML elements.", @@ -142,7 +177,7 @@ { "_id": "bd7113d8c441eddfaeb5bdef", "name": "Traverse the DOM", - "difficulty": 0.11, + "difficulty": 0.09, "challengeSeed": "114591805", "description": [ "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", @@ -154,7 +189,7 @@ { "_id": "bd7114d8c441eddfaeb5bdef", "name": "Work with the DOM", - "difficulty": 0.12, + "difficulty": 0.10, "challengeSeed": "114591804", "description": [ "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", @@ -166,7 +201,7 @@ { "_id": "bd7115d8c441eddfaeb5bdef", "name": "Listen for DOM Events", - "difficulty": 0.13, + "difficulty": 0.11, "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", @@ -178,7 +213,7 @@ { "_id": "bd7116d8c441eddfaeb5bdef", "name": "Use jQuery for Styling", - "difficulty": 0.14, + "difficulty": 0.12, "challengeSeed": "114591801", "description": [ "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", @@ -190,7 +225,7 @@ { "_id": "bd7117d8c441eddfaeb5bdef", "name": "Build a MadLibs Game", - "difficulty": 0.14, + "difficulty": 0.13, "challengeSeed": "114591799", "description": [ "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", @@ -203,7 +238,7 @@ { "_id": "bd7118d8c441eddfaeb5bdef", "name": "Discover Chrome's DevTools", - "difficulty": 0.15, + "difficulty": 0.14, "challengeSeed": "110752743", "description": [ "It's time to learn the most powerful tool your browser has - the Development Tools!", @@ -217,7 +252,7 @@ { "_id": "bd7123d8c441eddfaeb5bdef", "name": "Learn Basic Computer Science", - "difficulty": 0.19, + "difficulty": 0.15, "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.", @@ -231,7 +266,7 @@ { "_id": "bd8124d8c441eddfaeb5bdef", "name": "Learn Loops", - "difficulty": 0.20, + "difficulty": 0.16, "challengeSeed": "114597348", "description": [ "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", @@ -244,7 +279,7 @@ { "_id": "bd8125d8c441eddfaeb5bdef", "name": "Learn Computer Hardware", - "difficulty": 0.21, + "difficulty": 0.17, "challengeSeed": "114597347", "description": [ "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", @@ -257,7 +292,7 @@ { "_id": "bd8126d8c441eddfaeb5bdef", "name": "Learn Computer Networking", - "difficulty": 0.22, + "difficulty": 0.18, "challengeSeed": "114604811", "description": [ "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", @@ -270,7 +305,7 @@ { "_id": "bd8127d8c441eddfaeb5bdef", "name": "Learn Boolean Logic", - "difficulty": 0.23, + "difficulty": 0.19, "challengeSeed": "114604812", "description": [ "Now we'll do some more table exercises and learn boolean logic.", @@ -283,7 +318,7 @@ { "_id": "bd8128d8c441eddfaeb5bdef", "name": "Learn Computer Security", - "difficulty": 0.24, + "difficulty": 0.20, "challengeSeed": "114604813", "description": [ "We're almost done with Stanford's Introduction to Computer Science course!", @@ -297,7 +332,7 @@ { "_id": "bd7129d8c441eddfaeb5bdef", "name": "Build an Adventure Game", - "difficulty": 0.25, + "difficulty": 0.21, "challengeSeed": "114604814", "description": [ "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", @@ -312,7 +347,7 @@ { "_id": "bd7130d8c441eddfaeb5bdef", "name": "Build Rock Paper Scissors", - "difficulty": 0.26, + "difficulty": 0.22, "challengeSeed": "114604815", "description": [ "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", @@ -325,7 +360,7 @@ { "_id": "bd7131d8c441eddfaeb5bdef", "name": "Learn JavaScript For Loops", - "difficulty": 0.27, + "difficulty": 0.23, "challengeSeed": "114614220", "description": [ "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", @@ -338,7 +373,7 @@ { "_id": "bd7132d8c441eddfaeb5bdef", "name": "Learn JavaScript While Loops", - "difficulty": 0.28, + "difficulty": 0.24, "challengeSeed": "114612889", "description": [ "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", @@ -350,7 +385,7 @@ { "_id": "bd7133d8c441eddfaeb5bdef", "name": "Learn Control Flow", - "difficulty": 0.29, + "difficulty": 0.25, "challengeSeed": "114612888", "description": [ "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", @@ -364,7 +399,7 @@ { "_id": "bd7134d8c441eddfaeb5bdef", "name": "Build a Contact List", - "difficulty": 0.30, + "difficulty": 0.26, "challengeSeed": "114612887", "description": [ "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", @@ -377,7 +412,7 @@ { "_id": "bd7135d8c441eddfaeb5bdef", "name": "Build an Address Book", - "difficulty": 0.31, + "difficulty": 0.27, "challengeSeed": "114612885", "description": [ "Let's learn more about objects.", @@ -390,7 +425,7 @@ { "_id": "bd7136d8c441eddfaeb5bdef", "name": "Build a Cash Register", - "difficulty": 0.32, + "difficulty": 0.28, "challengeSeed": "114612882", "description": [ "In this final Codecademy section, we'll learn even more about JavaScript objects.", @@ -403,7 +438,7 @@ { "_id": "bd7138d8c441eddfaeb5bdef", "name": "Learn Regular Expressions", - "difficulty": 0.34, + "difficulty": 0.29, "challengeSeed": "112547802", "description": [ "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", @@ -414,164 +449,10 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7141d8c441eddfaeb5bdef", - "name": "Get Started with Node.js", - "difficulty": 0.37, - "challengeSeed": "114686471", - "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", - "name": "Try Node.js Events", - "difficulty": 0.38, - "challengeSeed": "114684206", - "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.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/2/video/1 and complete the section." - ], - "challengeType": 2, - "tests": [] - }, - { - "_id": "bd7143d8c441eddfaeb5bdef", - "name": "Try Node.js Streams", - "difficulty": 0.39, - "challengeSeed": "114684209", - "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", - "name": "Learn how Node.js Modules Work", - "difficulty": 0.40, - "challengeSeed": "114684213", - "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.", - "Go to http://campus.codeschool.com/courses/real-time-web-with-node-js/level/4/video/1 and complete the section." - ], - "challengeType": 2, - "tests": [] - }, - { - "_id": "bd7145d8c441eddfaeb5bdef", - "name": "Start an Express.js Server", - "difficulty": 0.41, - "challengeSeed": "114684247", - "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", - "name": "Use Socket.io", - "difficulty": 0.42, - "challengeSeed": "114684530", - "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", - "name": "Use Redis to Persist Data", - "difficulty": 0.43, - "challengeSeed": "114684532", - "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", - "name": "Dive Deeper into Express.js", - "difficulty": 0.44, - "challengeSeed": "114684533", - "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", - "name": "Setup Express.js Middleware", - "difficulty": 0.45, - "challengeSeed": "114684535", - "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", - "name": "Take Advantage of Parameters", - "difficulty": 0.46, - "challengeSeed": "114684537", - "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", - "name": "Add the Body Parser", - "difficulty": 0.47, - "challengeSeed": "114684720", - "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", - "name": "Configure Routes in Express.js", - "difficulty": 0.48, - "challengeSeed": "114684724", - "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": "bd7154d8c441eddfaeb5bdef", "name": "Get Started with Angular.js", - "difficulty": 0.50, + "difficulty": 0.30, "challengeSeed": "114684726", "description": [ "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", @@ -584,7 +465,7 @@ { "_id": "bd7155d8c441eddfaeb5bdef", "name": "Apply Angular.js Directives", - "difficulty": 0.51, + "difficulty": 0.31, "challengeSeed": "114684727", "description": [ "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", @@ -597,7 +478,7 @@ { "_id": "bd7156d8c441eddfaeb5bdef", "name": "Power Forms with Angular.js", - "difficulty": 0.52, + "difficulty": 0.32, "challengeSeed": "114684729", "description": [ "One area where Angular.js really shines is its powerful web forms.", @@ -610,7 +491,7 @@ { "_id": "bd7157d8c441eddfaeb5bdef", "name": "Customize Angular.js Directives", - "difficulty": 0.53, + "difficulty": 0.33, "challengeSeed": "114685062", "description": [ "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", @@ -622,7 +503,7 @@ { "_id": "bd7158d8c441eddfaeb5bdef", "name": "Create Angular.js Services", - "difficulty": 0.54, + "difficulty": 0.34, "challengeSeed": "114685060", "description": [ "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", @@ -633,9 +514,45 @@ "tests": [] }, { - "_id": "bd7153d8c441eddfaeb5bdef", - "name": "Try MongoDB", - "difficulty": 0.49, + "_id": "bd7153d8c441eddfaeb5bdff", + "name": "Start a Node.js Server", + "difficulty": 0.35, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd0f", + "name": "Manage Packages with NPM", + "difficulty": 0.35, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd1f", + "name": "Build Web Apps with Express.js", + "difficulty": 0.36, + "challengeSeed": "114685061", + "description": [ + "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", + "Go to http://try.mongodb.org/ and work through their interactive MongoDB tutorial." + ], + "challengeType": 2, + "tests": [] + }, + { + "_id": "bd7153d8c441eddfaeb5bd2f", + "name": "Manage Data with MongoDB", + "difficulty": 0.37, "challengeSeed": "114685061", "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", @@ -648,7 +565,7 @@ { "_id": "bd7140d8c441eddfaeb5bdef", "name": "Manage Source Code with Git", - "difficulty": 0.36, + "difficulty": 0.38, "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.", @@ -661,7 +578,7 @@ { "_id": "bd7139d8c441eddfaeb5bdef", "name": "Pair Program on Bonfires", - "difficulty": 0.35, + "difficulty": 0.39, "challengeSeed": "119657641", "description": [ "OK, we're finally ready to start pair programming!", @@ -679,16 +596,28 @@ "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!", + "Pair program as much as possible with different campers until you've completed all the Bonfire, Zipline and Basejump challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!", "Mark this challenge as complete and move on to the Bonfires." ], "challengeType": 2, "tests": [] }, { - "_id": "bd7158d8c442eddfaeb5bdef", - "name": "Zipline: Trello API Integration", - "difficulty": 0.55, + "_id": "bd7158d8c442eddfaeb5bdff", + "name": "Zipline: Integrate with Twitch's API", + "difficulty": 1.01, + "challengeSeed": "123488494", + "description": [ + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id": "bd7158d8c442eddfaeb5bd0f", + "name": "Zipline: Integrate with Trello's API", + "difficulty": 1.02, "challengeSeed": "123488494", "description": [ "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", @@ -704,36 +633,48 @@ "tests": [] }, { - "_id": "bd7158d8c443eddfaeb5bdef", - "name": "Basejump: Hello World in Cloud 9", - "difficulty": 0.56, + "_id": "bd7158d8c442eddfaeb5bd1f", + "name": "Zipline: Integrate with a Weather API", + "difficulty": 1.03, "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." + "Go to CodePen http://codepen.io/FreeCodeCamp/pen/gbEmJr and click the \"fork\" button. This will create a \"fork\", or copy of the file, which you can then edit yourself.", + "When you are finished, click the \"I've completed this challenge\" button and include a link to your Codepen. If you pair programmed, you should also include the Free Code Camp username of your pair. We will take a look at your code and eventually give you feedback. In the meantime, please move on to your next challenge." + ], + "challengeType": 3, + "tests": [] + }, + { + "_id": "bd7158d8c443eddfaeb5bdef", + "name": "Basejump: Build a Voting App", + "difficulty": 2.01, + "challengeSeed": "123488494", + "description": [ + ], "challengeType": 4, "tests": [] }, { - "_id": "bd7123c8c441eddfaeb5bdef", - "name": "HTML CSS and JavaScript Sandbox", - "difficulty": 1.00, + "_id": "bd7158d8c443eddfaeb5bdff", + "name": "Basejump: Build a Yelp App", + "difficulty": 2.02, + "challengeSeed": "123488494", "description": [ - "This is our custom waypoint sandbox.", - "This is for testing out future functionality." + ], - "tests": [ - "expect((/hello(\\s)+world/gi).test($('h1').text())).to.be.true;" + "challengeType": 4, + "tests": [] + }, + { + "_id": "bd7158d8c443eddfaeb5bd0f", + "name": "Basejump: Build a Pintrest Clone", + "difficulty": 2.03, + "challengeSeed": "123488494", + "description": [ + ], - "challengeSeed": [ - "

    Hello

    " - ], - "challengeType": 0 + "challengeType": 4, + "tests": [] } ] diff --git a/seed_data/wikis.json b/seed_data/wikis.json index aa0165b7d9..1ca02b4be0 100644 --- a/seed_data/wikis.json +++ b/seed_data/wikis.json @@ -4,29 +4,94 @@ "name": "How do I use this guide?", "description": [ "
    ", - "

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

    ", - "

    You can click

    ", - "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " + "

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

    ", + "

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

    ", + "

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

    ", + "
    " ] }, { - "_id": "bd7158d9c541eddfaeb5bdef", - "name": "Will I be able to get a job after this?", + "_id": "bd7158d9c441eddfaeb5bdff", + "name": "What is Free Code Camp?", "description": [ "
    ", - "

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

    ", - "

    You can click

    ", - "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " + "

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

    ", + "

    We help our campers (students):

    ", + "

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

    ", + "
    " ] }, { - "_id": "bd7158d9c542eddfaeb5bdef", - "name": "How do I use this guide?", + "_id": "bd7158d9c441eddfaeb5bd1f", + "name": "Why do I need Free Code Camp?", "description": [ "
    ", - "

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

    ", - "

    You can click

    ", - "

    Click the \"Show all Articles\" button below and browse the topics we cover

    " + "

    Learning to code is hard.

    ", + "

    Most people who successfully learn to code:

    ", + "

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

    ", + "

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

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

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

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

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

    ", + "", + "

    Here are the facts:

    ", + "

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

    ", + "
    " ] }, { @@ -50,7 +115,7 @@ "
  • • Automated Testing - write tests to test the limits of your code
  • ", " ", "

    ", - "

    You'll spend the last half of Free Code Camp using Agile Methodologies and Full Stack JavaScript to build projects for nonprofits:", + "

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

      ", "
    • • Agile - a set of software development principles that focus the design and production of a project on the needs of its users
    • ", "
    • • Git - a version control system for saving and sharing your projects
    • ", @@ -63,17 +128,6 @@ "
    " ] }, - { - "_id": "bd7158d9c439eddfaeb5bdef", - "name": "Will I be ready to get a coding job after this?", - "description": [ - "
    ", - "

    At the end of Free Code Camp, you will have pair programmed around 1,000 hours with dozens of other students, built a portfolio of projects that people are actively using, and a roster of glowing references from nonprofits you've helped. This is more coding than most coding bootcamps provide, and on average, 75% of bootcamp graduates get software engineering jobs within 6 months, and earn an average annual salary of $76,000.

    ", - " \"A", - "
    ", - "
    " - ] - }, { "_id": "bd7158d9c434eddfaeb5bdef", "name": "How long does Free Code Camp take?", @@ -177,14 +231,6 @@ "
    " ] }, - { - "_id": "bd7158d9c443eddfaeb5bdef", - "name": "Win a Chromebook for Christmas", - "description": [ - "

    Thanks to everyone who participated in our Chromebook giveaway.

    ", - "a Rafflecopter giveaway" - ] - }, { "_id": "bd7158d9c445eddfaeb5bdef", "name": "Gmail Zero Inbox Shortcuts", diff --git a/views/account/show.jade b/views/account/show.jade index 8f3aa810ae..8dd1314227 100644 --- a/views/account/show.jade +++ b/views/account/show.jade @@ -10,7 +10,7 @@ block content if (user && user.profile.username === username) .col-xs-12 .text-center - a.btn.btn-big.btn-primary(href="/account") Update my public portfolio or manage my account + a.btn.btn-big.btn-primary(href="/account") Update my portfolio page or manage my account br .row .col-xs-12 @@ -97,27 +97,30 @@ block content | Try it out .hidden-xs.col-sm-12 + script(src="//d3js.org/d3.v3.min.js") script(src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js") #cal-heatmap.img-center script. $(document).ready(function() { - var cal = new CalHeatMap(); - var calendar = !{JSON.stringify(calender)}; - cal.init({ - itemSelector: "#cal-heatmap", - domain: "month", - subDomain: "day", - data: calendar, - cellSize: 15, - align: 'center', - cellRadius: 3, - cellPadding: 2, - tooltip: true, - range: 4, - start: new Date().setDate(new Date().getDate() - 90), - legendColors: ["#cccccc", "#215f1e"], - legend: [1, 2, 3] - }); + setTimeout(function() { + var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + cal.init({ + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "day", + data: calendar, + cellSize: 15, + align: 'center', + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 4, + start: new Date().setDate(new Date().getDate() - 90), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3] + }); + }, 300); }); .row .hidden-xs.col-sm-12.text-center diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 32fea1f462..0491412974 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -7,7 +7,8 @@ block content .panel-body .row .col-xs-12.col-sm-12.col-md-8.col-md-offset-2 - h3 We strongly recommend you complete these in order. All of these must be completed before you can start working on nonprofit projects. + h3 You must complete all of these challenges before you can start working on nonprofit projects. + h3 You should complete these in order from top to bottom. h2 span.fa.fa-flag |   Waypoints (web development lessons) diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade index e9560000d0..27ef5ba82e 100644 --- a/views/partials/universal-head.jade +++ b/views/partials/universal-head.jade @@ -1,7 +1,6 @@ script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") -script(src="//d3js.org/d3.v3.min.js") include meta title #{title} | Free Code Camp meta(charset='utf-8') diff --git a/views/partials/wikis.jade b/views/partials/wikis.jade index 99ec5c00b9..705d2a5683 100644 --- a/views/partials/wikis.jade +++ b/views/partials/wikis.jade @@ -17,7 +17,11 @@ h3 for (var i = 0; i < data.wikiList.length; i++) { var li = document.createElement("li"); var linkedName = getLinkedName(data.wikiList[i].name); + if (data.completedWikis.indexOf(data.wikiIds[i]) > -1) { + $(li).html("" + data.wikiList[i].name + ""); + } else { $(li).html("" + data.wikiList[i].name + ""); + } docfrag.appendChild(li); }; $('#wikiList').append(docfrag); diff --git a/views/stories/search-stories.jade b/views/stories/search-stories.jade index 9574bf4b9f..84813fc470 100644 --- a/views/stories/search-stories.jade +++ b/views/stories/search-stories.jade @@ -2,7 +2,7 @@ .spacer h1.text-center Search is coming soon .input-group - input#searchArea.big-text-field.field-responsive.form-control(type='text', placeholder='Search our stories', autofocus) + input#searchArea.big-text-field.field-responsive.form-control(type='text', placeholder='Search our links', autofocus) span.input-group-btn button.disabled#searchbutton.btn.btn-big.btn-primary.btn-responsive(type='button') Search .spacer @@ -70,4 +70,4 @@ script. } }); - } \ No newline at end of file + } diff --git a/views/wiki/show.jade b/views/wiki/show.jade index c00c95c96c..1f9782a2e9 100644 --- a/views/wiki/show.jade +++ b/views/wiki/show.jade @@ -17,7 +17,7 @@ block content .text-center .next-wiki-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) .ten-pixel-break - #showAllButton.btn.btn-info.btn-big.btn-block Show me all wiki articles + #showAllButton.btn.btn-info.btn-big.btn-block Show me all articles .spacer .row .col-xs-12.text-center From 1c7d0b11b69d039bfd04d6256b9ca39a830e4109 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 7 Apr 2015 23:39:52 -0700 Subject: [PATCH 096/125] pass the original story URL to all comments for notification email --- controllers/resources.js | 5 --- controllers/story.js | 79 ++++++++++--------------------------- models/Comment.js | 6 ++- public/js/main.js | 2 +- views/partials/wikis.jade | 1 + views/stories/comments.jade | 1 + views/stories/show.jade | 1 + 7 files changed, 29 insertions(+), 66 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index ec1d7466f2..f4db69018f 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -291,11 +291,6 @@ module.exports = { }) }, - getAllCourses: function() { - "use strict"; - return coursewares; - }, - allCoursewareIds: function() { return coursewares.map(function(elem) { return { diff --git a/controllers/story.js b/controllers/story.js index f788670330..02ff3f2493 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -154,6 +154,7 @@ exports.returnIndividualStory = function(req, res, next) { res.render('stories/index', { title: story.headline, link: story.link, + originalStoryLink: dashedName, author: story.author, description: story.description, rank: story.upVotes.length, @@ -377,6 +378,7 @@ exports.commentSubmit = function(req, res, next) { } var comment = new Comment({ associatedPost: data.associatedPost, + originalStoryLink: originalStoryLink, body: sanitizedBody, rank: 0, upvotes: 0, @@ -391,7 +393,6 @@ exports.commentSubmit = function(req, res, next) { exports.commentOnCommentSubmit = function(req, res, next) { var data = req.body.data; - if (req.user._id.toString() !== data.author.userId.toString()) { return next(new Error('Not authorized')); } @@ -412,6 +413,7 @@ exports.commentOnCommentSubmit = function(req, res, next) { body: sanitizedBody, rank: 0, upvotes: 0, + originalStoryLink: data.originalStoryLink, author: data.author, comments: [], topLevel: false, @@ -444,66 +446,25 @@ function commentSave(comment, Context, res, next) { if (err) { return next(err); } - Comment.findById(associatedStory._id, function(err, originalStory) { - if (err) { - return next(err); - } - debug('is it a comment?', originalStory); - if (!originalStory) { - Story.findById(associatedStory.associatedPost, function(err, originalStory) { - debug('is it a story?', originalStory); - if (err) { - return next(err); - } - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - console.log('1!'); - var mailOptions = { - to: recipient.email, - from: 'Team@freecodecamp.com', - subject: originalStory.author.username + " replied to you on Camper News!", - text: [ - "Here.", - '- the Volunteer Camp Counselor Team' - ].join('') - }; - console.log('2!'); - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } - done(null, null); - }); - }); - } else { - console.log('definitely a comment'); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - console.log('1!'); - var mailOptions = { - to: recipient.email, - from: 'Team@freecodecamp.com', - subject: originalStory.author.username + " replied to you on Camper News!", - text: [ - "Here.", - '- the Volunteer Camp Counselor Team' - ].join('') - }; - console.log('2!'); - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } - done(null, null); - }); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password } }); + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: associatedStory.author.username + " replied to you on Camper News!", + text: [ + "Here.", + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); }); }); } catch (e) { diff --git a/models/Comment.js b/models/Comment.js index a3a35fe6fd..fc805594f7 100644 --- a/models/Comment.js +++ b/models/Comment.js @@ -6,6 +6,10 @@ var commentSchema = new mongoose.Schema({ type: String, required: true }, + originalStoryLink: { + type: String, + default: '' + }, body: { type: String, default: '' @@ -36,4 +40,4 @@ module.exports = mongoose.model('Comment', commentSchema); type: mongoose.Schema.Types.ObjectId, ref: 'User' }, - */ \ No newline at end of file + */ diff --git a/public/js/main.js b/public/js/main.js index 5d2fc5e571..9773a633a5 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -281,6 +281,7 @@ $(document).ready(function() { { data: { associatedPost: storyId, + originalStoryLink: originalStoryLink, body: data, author: { picture: user.profile.picture, @@ -295,7 +296,6 @@ $(document).ready(function() { .done(function (data, textStatus, xhr) { window.location.reload(); }); - }; $('#comment-button').on('click', commentSubmitButtonHandler); diff --git a/views/partials/wikis.jade b/views/partials/wikis.jade index 705d2a5683..6e6d817fd1 100644 --- a/views/partials/wikis.jade +++ b/views/partials/wikis.jade @@ -16,6 +16,7 @@ h3 var docfrag = document.createDocumentFragment(); for (var i = 0; i < data.wikiList.length; i++) { var li = document.createElement("li"); + // strike through wikis previously read var linkedName = getLinkedName(data.wikiList[i].name); if (data.completedWikis.indexOf(data.wikiIds[i]) > -1) { $(li).html("" + data.wikiList[i].name + ""); diff --git a/views/stories/comments.jade b/views/stories/comments.jade index 546d843a28..b122fcb6b6 100644 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -96,6 +96,7 @@ { data: { associatedPost: commentId, + originalStoryLink: originalStoryLink, body: $('#comment-to-comment-textinput').val(), author: { picture: user.profile.picture, diff --git a/views/stories/show.jade b/views/stories/show.jade index dac2842b0b..0d0cf9539a 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -1,6 +1,7 @@ .spacer script. var storyId = !{JSON.stringify(id)}; + var originalStoryLink = !{JSON.stringify(originalStoryLink)}; var comments = !{JSON.stringify(comments)}; var upVotes = !{JSON.stringify(upVotes)}; var image = !{JSON.stringify(image)}; From 6242f322d7bcca743448ef6be7898138d8b1d47c Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 00:30:51 -0700 Subject: [PATCH 097/125] still working on passing original author email all the way down the comment chain --- controllers/story.js | 40 +++++++++++++++++++++++++++++++------ models/Comment.js | 4 ++++ models/Story.js | 4 ++++ public/js/main.js | 6 +++++- views/stories/comments.jade | 4 +++- views/stories/show.jade | 1 + 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 02ff3f2493..c0eb5cfa8d 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -155,6 +155,7 @@ exports.returnIndividualStory = function(req, res, next) { title: story.headline, link: story.link, originalStoryLink: dashedName, + originalStoryAuthorEmail: story.author.email, author: story.author, description: story.description, rank: story.upVotes.length, @@ -378,7 +379,8 @@ exports.commentSubmit = function(req, res, next) { } var comment = new Comment({ associatedPost: data.associatedPost, - originalStoryLink: originalStoryLink, + originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, body: sanitizedBody, rank: 0, upvotes: 0, @@ -414,6 +416,7 @@ exports.commentOnCommentSubmit = function(req, res, next) { rank: 0, upvotes: 0, originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, author: data.author, comments: [], topLevel: false, @@ -446,6 +449,30 @@ function commentSave(comment, Context, res, next) { if (err) { return next(err); } + if (data.originalStoryAuthorEmail !== recipient.email) { + console.log("in mailer", data); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: data.originalStoryAuthorEmail, + from: 'Team@freecodecamp.com', + subject: associatedStory.author.username + " replied to your post on Camper News", + text: [ + "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", + "You can keep this conversation going.", + "Just head back to the discussion here: http://freecodecamp.com/stories/"+ comment.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); + } var transporter = nodemailer.createTransport({ service: 'Mandrill', auth: { @@ -456,11 +483,13 @@ function commentSave(comment, Context, res, next) { var mailOptions = { to: recipient.email, from: 'Team@freecodecamp.com', - subject: associatedStory.author.username + " replied to you on Camper News!", + subject: associatedStory.author.username + " replied to your post on Camper News", text: [ - "Here.", - '- the Volunteer Camp Counselor Team' - ].join('') + "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", + "You can keep this conversation going.", + "Just head back to the discussion here: http://freecodecamp.com/stories/"+ comment.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') }; transporter.sendMail(mailOptions, function(err) { if (err) { return err; } @@ -468,7 +497,6 @@ function commentSave(comment, Context, res, next) { }); }); } catch (e) { - debug('hey there\'s error'); // delete comment return next(err); } diff --git a/models/Comment.js b/models/Comment.js index fc805594f7..4a1e15506d 100644 --- a/models/Comment.js +++ b/models/Comment.js @@ -10,6 +10,10 @@ var commentSchema = new mongoose.Schema({ type: String, default: '' }, + originalStoryAuthorEmail: { + type: String, + default: '' + }, body: { type: String, default: '' diff --git a/models/Story.js b/models/Story.js index 13f279de18..7b60674845 100644 --- a/models/Story.js +++ b/models/Story.js @@ -23,6 +23,10 @@ var storySchema = new mongoose.Schema({ type: String, unique: false }, + originalStoryAuthorEmail: { + type: String, + default: '' + }, rank: { type: Number, default: -Infinity diff --git a/public/js/main.js b/public/js/main.js index 9773a633a5..00081f0f4b 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -250,10 +250,12 @@ $(document).ready(function() { timePosted: Date.now(), description: description, storyMetaDescription: storyMetaDescription, + originalStoryAuthorEmail: user.email, rank: 1, upVotes: [userDataForUpvote], author: { picture: user.profile.picture, + email: user.email, userId: user._id, username: user.profile.username }, @@ -282,11 +284,13 @@ $(document).ready(function() { data: { associatedPost: storyId, originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: data, author: { picture: user.profile.picture, userId: user._id, - username: user.profile.username + username: user.profile.username, + email: user.email } } }) diff --git a/views/stories/comments.jade b/views/stories/comments.jade index b122fcb6b6..a5df2c97ed 100644 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -97,11 +97,13 @@ data: { associatedPost: commentId, originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: $('#comment-to-comment-textinput').val(), author: { picture: user.profile.picture, userId: user._id, - username: user.profile.username + username: user.profile.username, + email: user.email } } }) diff --git a/views/stories/show.jade b/views/stories/show.jade index 0d0cf9539a..0d093775c1 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -2,6 +2,7 @@ script. var storyId = !{JSON.stringify(id)}; var originalStoryLink = !{JSON.stringify(originalStoryLink)}; + var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)}; var comments = !{JSON.stringify(comments)}; var upVotes = !{JSON.stringify(upVotes)}; var image = !{JSON.stringify(image)}; From 889de9c8752d2ba35d28edf62dfe295926cd5847 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 00:42:33 -0700 Subject: [PATCH 098/125] build other solutions page of links for nonprofits --- views/nonprofits/other-solutions.jade | 62 ++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/views/nonprofits/other-solutions.jade b/views/nonprofits/other-solutions.jade index b9a19a3b36..752728e772 100644 --- a/views/nonprofits/other-solutions.jade +++ b/views/nonprofits/other-solutions.jade @@ -2,4 +2,64 @@ extends ../layout block content .jumbotron.text-center h1.hug-top Here are some other solutions we recommend - .spacer \ No newline at end of file + .spacer + h2 Please note that Free Code Camp is not partnered with, nor do we receive a referral fee from, any of the following providers. We simply want to help guide you towards a solution for your organization. + h3 Skills-based Volunteer Organizations + ul + li + a(href='http://www.volunteermatch.com') Volunteer Match + li + a(href='http://www.catchafire.com') Catchafire + h3 DIY Websites + ul + li + a(href='http://www.wix.com') Wix + li + a(href='http://www.squarespace.com/') Square Space + + h3 Donor and Volunteer Management Systems + ul + li + a(href='https://www.thedatabank.com/') The Data Bank + li + a(href='http://www.donorsnap.com/') DonorSnap + li + a(href='http://www.donorperfect.com/') Donor Perfect + li + a(href='https://www.blackbaud.com/fundraising-crm/etapestry-donor-management') eTapestry + li + a(href='http://www.z2systems.com/') NeonCRM + li + a(href='http://www.regpacks.com/volunteer-management/') Regpack + li + a(href='http://sumac.com/') Sumac + li + a(href='http://www.volgistics.com/') Volgistics + h3 Inventory Management Systems + ul + li + a(href='https://www.ezofficeinventory.com/industries/non-profits') EZ Office Inventory + li + a(href='https://www.ordoro.com') Ordoro + li + a(href='http://www.unleashedsoftware.com') Unleashed + h3 E-Learning platforms + ul + li + a(href='http://www.dokeos.com/') Dokeos + li + a(href='http://www.efrontlearning.net/') eFront + li + a(href='https://moodle.org/') Moodle + li + a(href='https://sakaiproject.org/') Sakai + h3 Community Management + ul + li + a(href='https://civicrm.org/') CiviCRM + li + a(href='http://tcmgr.com/') Total Community Manager + h3 Electronic Forms + ul + li + a(href='http://www.google.com/forms') Google Forms From 61e07041a749c4e00c2ea7185bd0dbac087f547b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 13:16:16 -0700 Subject: [PATCH 099/125] camper news reply emails go out to original story poster and poster of replied-to comment, works with old stories as well --- controllers/story.js | 63 ++++++++++++++++++++----------------- views/stories/comments.jade | 1 + 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index c0eb5cfa8d..15c14b97cf 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -155,7 +155,7 @@ exports.returnIndividualStory = function(req, res, next) { title: story.headline, link: story.link, originalStoryLink: dashedName, - originalStoryAuthorEmail: story.author.email, + originalStoryAuthorEmail: story.author.email || "", author: story.author, description: story.description, rank: story.upVotes.length, @@ -345,7 +345,8 @@ exports.storySubmission = function(req, res, next) { comments: [], image: data.image, storyLink: storyLink, - metaDescription: data.storyMetaDescription + metaDescription: data.storyMetaDescription, + originalStoryAuthorEmail: req.user.email }); req.user.progressTimestamps.push(Date.now()); @@ -449,8 +450,7 @@ function commentSave(comment, Context, res, next) { if (err) { return next(err); } - if (data.originalStoryAuthorEmail !== recipient.email) { - console.log("in mailer", data); + if (data.originalStoryAuthorEmail && (data.originalStoryAuthorEmail !== recipient.email)) { var transporter = nodemailer.createTransport({ service: 'Mandrill', auth: { @@ -459,41 +459,46 @@ function commentSave(comment, Context, res, next) { } }); var mailOptions = { - to: data.originalStoryAuthorEmail, + to: data.originalStoryAuthorEmail + ',' + recipient.email, from: 'Team@freecodecamp.com', subject: associatedStory.author.username + " replied to your post on Camper News", text: [ "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", "You can keep this conversation going.", - "Just head back to the discussion here: http://freecodecamp.com/stories/"+ comment.originalStoryLink, + "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink, '- the Free Code Camp Volunteer Team' ].join('\n') }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } + transporter.sendMail(mailOptions, function (err) { + if (err) { + return err; + } }); - } - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password + } else { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: associatedStory.author.username + " replied to your post on Camper News", + text: [ + "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", + "You can keep this conversation going.", + "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + transporter.sendMail(mailOptions, function (err) { + if (err) { + return err; + } + }); } - }); - var mailOptions = { - to: recipient.email, - from: 'Team@freecodecamp.com', - subject: associatedStory.author.username + " replied to your post on Camper News", - text: [ - "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", - "You can keep this conversation going.", - "Just head back to the discussion here: http://freecodecamp.com/stories/"+ comment.originalStoryLink, - '- the Free Code Camp Volunteer Team' - ].join('\n') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } - }); }); }); } catch (e) { diff --git a/views/stories/comments.jade b/views/stories/comments.jade index a5df2c97ed..f21f43ac3a 100644 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -92,6 +92,7 @@ }); var submitCommentToCommentHandler = function submitCommentToCommentHandler() { $('#submit-comment-to-comment').unbind('click'); + console.log('in comments.jade', originalStoryAuthorEmail); $.post('/stories/comment/' + commentId + '/comment', { data: { From c02fafa6a95ab04fb15bc268156f82387c4a41f0 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 14:36:05 -0700 Subject: [PATCH 100/125] add status, turn hours into integer, create a directory view of all nonprofits --- app.js | 6 ++---- controllers/nonprofits.js | 19 +++++++++++++++++-- models/Nonprofit.js | 5 +++-- seed_data/nonprofits.json | 27 ++++++++++++++++++--------- views/nonprofits/directory.jade | 19 +++++++++++++++++++ views/nonprofits/show.jade | 3 +-- 6 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 views/nonprofits/directory.jade diff --git a/app.js b/app.js index 39ef393fe9..27092125a1 100644 --- a/app.js +++ b/app.js @@ -329,11 +329,9 @@ app.post('/email-signin', userController.postSignin); * Nonprofit Project routes. */ -//app.get('/nonprofits', contactController.getNonprofitsForm); -// -//app.post('/nonprofits', contactController.postNonprofitsForm); +app.get('/nonprofits', nonprofitController.nonprofitsHome); -app.get('/nonprofits/home', nonprofitController.nonprofitsHome); +app.get('/nonprofits/directory', nonprofitController.nonprofitsDirectory); app.get('/nonprofits/are-you-with-a-registered-nonprofit', nonprofitController.areYouWithARegisteredNonprofit); diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index 91aadfc037..c6bf824905 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -12,6 +12,18 @@ exports.nonprofitsHome = function(req, res) { }); }; +exports.nonprofitsDirectory = function(req, res) { + Nonprofit.find({estimatedHours: { $gt: 0 } }, function(err, nonprofits) { + if (err) { + next(err); + } + res.render('nonprofits/directory', { + title: 'Nonprofits we help', + nonprofits: nonprofits + }); + }); +}; + exports.areYouWithARegisteredNonprofit = function(req, res) { res.render('nonprofits/are-you-with-a-registered-nonprofit', { title: 'Are you with a with a registered nonprofit', @@ -64,7 +76,7 @@ exports.linkUsToYourWebsite = function(req, res) { exports.tellUsYourEmail = function(req, res) { res.render('nonprofits/tell-us-your-email', { - title: 'Tell us your name', + title: 'Tell us your email', step: 8 }); }; @@ -77,7 +89,10 @@ exports.tellUsYourName = function(req, res) { }; exports.finishApplication = function(req, res) { - + res.render('nonprofits/you-have-finished-your-application', { + title: "You have finished your application", + step: 10 + }); }; exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { diff --git a/models/Nonprofit.js b/models/Nonprofit.js index 39f77085e0..8ea47ddea0 100644 --- a/models/Nonprofit.js +++ b/models/Nonprofit.js @@ -19,9 +19,10 @@ var nonprofitSchema = new mongoose.Schema({ projectDescription: String, logoUrl: String, imageUrl: String, - estimatedHours: String, + estimatedHours: 0, interestedCampers: [], - confirmedCampers: [] + confirmedCampers: [], + status: "evaluating" //"evaluating", "confirmed", "started", "completed", "aborted" }); module.exports = mongoose.model('Nonprofit', nonprofitSchema); diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json index 5c894d6217..eae8f08236 100644 --- a/seed_data/nonprofits.json +++ b/seed_data/nonprofits.json @@ -24,7 +24,8 @@ "projectDescription": "Campers will create a system will integrate the food inventory, donor and delivery driver management systems as well as replace the current application system with a custom form solution. System will include a more streamlined operations management, with user printable lists of inventory, drivers, and deliveries.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c7e02f2c173c37015b2f36/604x309/00580a0567a4b3afda29d52b09e7e829/rQQ6zwq31Uya8ie9QHC-MlvfXxqftm9UPPe524JUhmwSEaZjQ7oL7U1tVoHLUj-gVUwM-7uzBGFsAXD_A_cx_JyAZP4Td-GMBJ-AebJNRAQP0m0v253eKMkURp63aG4%3Ds0-d-e1-ft.png", "imageUrl": "http://chasdeikaduri.org/images/523455_516325865106850_1885515210_n.jpg", - "estimatedHours": "200" + "estimatedHours": 200, + "status": "started" }, { "id": "bd7158d8c464cbafaeb4bdef", @@ -43,7 +44,8 @@ "projectDescription": "Campers will create a Chrome browser extension to preserve sales data from a form, and upload in batches as the internet connection allows.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d29f1e4c726fd765fa87ef/54d29f6388812dd367a243ab/x/018d9d3be5439870c56cccba5b3aa8bf/kopernik-logo-global.png", "imageUrl": "http://kopernik.info/sites/default/files/updates/Presenting_the_low_carbon_t.jpg", - "estimatedHours": "100" + "estimatedHours": 100, + "status": "completed" }, { "id": "bd6274d8c464cbafaeb4bdef", @@ -62,7 +64,8 @@ "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": "", - "estimatedHours": "200" + "estimatedHours": 200, + "status": "completed" }, { "id": "bd1326d9c245cbafaeb4bdef", @@ -81,7 +84,8 @@ "projectDescription": "Campers will create a mobile responsive website for the organization, with donation capabilities.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d9810307b159a4d9027aa2/54d981bfe5eb145560fbb769/x/cf7f318bfe4aee631b0d0eeef272225c/logo.png", "imageUrl": "http://www.operationbrush.org/images/temp/hands1.png", - "estimatedHours": "100" + "estimatedHours": 100, + "status": "started" }, { "id": "bd1325d8c464cbafaeb5bdef", @@ -100,7 +104,8 @@ "projectDescription": "Campers will create a volunteer management system with multi-user access and reporting capabilities.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1daf2d72d8eb868910b60/54c1dd4ecffcb09fc52b68a1/x/a8148f08769b449217e433bab8f39ddd/RCR-color.jpg", "imageUrl": "http://www.rosecityrollers.com/wp-content/uploads/2015/01/BZ7_5923-X3-675x375.jpg", - "estimatedHours": "200" + "estimatedHours": 200, + "status": "started" }, { "id": "bd1325d8c464cbafaeb6bdef", @@ -119,7 +124,8 @@ "projectDescription": "Campers will create a single page fundraising website. In exchange for a donation, a user can customize a graphical 'heart' in someone's name or anonymously. The page will display all of the hearts on a 'wall of hearts.'", "logoUrl": "https://trello-attachments.s3.amazonaws.com/548b36629137780091a973cc/666x666/6c7a366ffb659649f6377d4a431687cd/country-logos-1-300dpi.jpg", "imageUrl": "http://www.saveachildsheart.com/wp-content/uploads/2013/10/7.2.5_Internation_Photograohy_Exhibition.jpg", - "estimatedHours": "100" + "estimatedHours": 100, + "status": "completed" }, { "id": "bd1325d8c464cbafaeb4bdef", @@ -138,7 +144,8 @@ "projectDescription": "Campers will create a website where potential donors can view which schools already have the Savvy Cyber Kids books, and donate books to those schools that do not.", "logoUrl": "https://trello-attachments.s3.amazonaws.com/54ee3c7bf205562680177b59/218x190/1dc460de4edc9fdd4b481b24e93cfb23/logo.png", "imageUrl": "http://www.privatewifi.com/wp-content/uploads/2014/10/Halpert.jpg", - "estimatedHours": "200" + "estimatedHours": 200, + "status": "started" }, { "id": "bd1325d8c464cbafaeb7bdef", @@ -157,7 +164,8 @@ "projectDescription": "Campers will build a website where medical facilities can list music therapy time slots, and musicians can sign up to fill these slots.", "logoUrl": "http://static1.squarespace.com/static/521b8957e4b024f66a58b214/t/521b8e9de4b093a8696eb9b8/1398718364447/?format=750w", "imageUrl": "https://trello-attachments.s3.amazonaws.com/54fdb0328917ca64e9e8a79f/54fdc3b710f67caf6da14719/x/49fbe0012179bf254928f3f2a44810b4/Screen_2BShot_2B2013-08-26_2Bat_2B1.32.35_2BPM.png", - "estimatedHours": "200" + "estimatedHours": 200, + "status": "started" }, { "id": "bd1325d8c464cbafaeb8bdef", @@ -176,6 +184,7 @@ "projectDescription": "Campers will build a mobile responsive web form to allow Timeraiser eventgoers to select which nonprofit organizations they're interested in volunteering with. System will have Salesforce integration and reporting capabilities.", "logoUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/1277176.png?480", "imageUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/______________4571248_orig.png", - "estimatedHours": "100" + "estimatedHours": 100, + "status": "started" } ] diff --git a/views/nonprofits/directory.jade b/views/nonprofits/directory.jade new file mode 100644 index 0000000000..309cad9287 --- /dev/null +++ b/views/nonprofits/directory.jade @@ -0,0 +1,19 @@ +extends ../layout +block content + script. + var challengeName = 'Nonprofits View'; + .col-xs-12.col-sm-12.col-md-12 + .panel.panel-info + .panel-heading.text-center Nonprofits We Help + .panel-body + for nonprofit in nonprofits + .spacer + .row + .col-xs-12.col-sm-3 + img.img-responsive(src=nonprofit.logoUrl) + .col-xs-12.col-sm-9 + h2.negative-15= nonprofit.name + h3.negative-15= nonprofit.whatDoesNonprofitDo + a.text-center.btn.btn-primary.btn-lg(href='/nonprofits/' + nonprofit.name.toLowerCase().replace(/\s/g, '-')) Read more + .spacer + .spacer diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index 31297684f2..8ef8fc8776 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -4,8 +4,7 @@ block content var challengeName = 'Nonprofits View'; .col-xs-12.col-sm-12.col-md-12 .panel.panel-info - .panel-heading.text-center - h1= title + .panel-heading.text-center= title .panel-body .row .col-xs-12.col-sm-10.col-sm-offset-1 From 29b4e3b97038e0fc8d6c762385be96602ca25261 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 14:57:08 -0700 Subject: [PATCH 101/125] get seed data seeding and improve application submitted button --- controllers/nonprofits.js | 14 +++++--------- models/Nonprofit.js | 2 +- seed_data/nonprofits.json | 18 +++++++++--------- views/nonprofits/show.jade | 3 ++- ...project-application-has-been-submitted.jade | 2 +- .../nonprofit-application-progress-bar.jade | 4 ++-- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index c6bf824905..a7871327a3 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -88,16 +88,11 @@ exports.tellUsYourName = function(req, res) { }); }; -exports.finishApplication = function(req, res) { - res.render('nonprofits/you-have-finished-your-application', { - title: "You have finished your application", - step: 10 - }); -}; - exports.yourNonprofitProjectApplicationHasBeenSubmitted = function(req, res) { res.render('nonprofits/your-nonprofit-project-application-has-been-submitted', { - title: 'Your Nonprofit Project application has been submitted!' + title: 'Your Nonprofit Project application has been submitted!', + step: 10, + getBackDay: moment().weekday(5).format('dddd') }); }; @@ -163,7 +158,8 @@ exports.returnIndividualNonprofit = function(req, res, next) { whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, interestedCampers: nonprofit.interestedCampers, assignedCampers: nonprofit.assignedCampers, - buttonActive: buttonActive + buttonActive: buttonActive, + currentStatus: nonprofit.currentStatus }); }); }; diff --git a/models/Nonprofit.js b/models/Nonprofit.js index 8ea47ddea0..34443461da 100644 --- a/models/Nonprofit.js +++ b/models/Nonprofit.js @@ -22,7 +22,7 @@ var nonprofitSchema = new mongoose.Schema({ estimatedHours: 0, interestedCampers: [], confirmedCampers: [], - status: "evaluating" //"evaluating", "confirmed", "started", "completed", "aborted" + currentStatus: String // "confirmed", "started", "completed", "aborted" }); module.exports = mongoose.model('Nonprofit', nonprofitSchema); diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json index eae8f08236..f208c4a510 100644 --- a/seed_data/nonprofits.json +++ b/seed_data/nonprofits.json @@ -25,7 +25,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c7e02f2c173c37015b2f36/604x309/00580a0567a4b3afda29d52b09e7e829/rQQ6zwq31Uya8ie9QHC-MlvfXxqftm9UPPe524JUhmwSEaZjQ7oL7U1tVoHLUj-gVUwM-7uzBGFsAXD_A_cx_JyAZP4Td-GMBJ-AebJNRAQP0m0v253eKMkURp63aG4%3Ds0-d-e1-ft.png", "imageUrl": "http://chasdeikaduri.org/images/523455_516325865106850_1885515210_n.jpg", "estimatedHours": 200, - "status": "started" + "currentStatus": "started" }, { "id": "bd7158d8c464cbafaeb4bdef", @@ -45,7 +45,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d29f1e4c726fd765fa87ef/54d29f6388812dd367a243ab/x/018d9d3be5439870c56cccba5b3aa8bf/kopernik-logo-global.png", "imageUrl": "http://kopernik.info/sites/default/files/updates/Presenting_the_low_carbon_t.jpg", "estimatedHours": 100, - "status": "completed" + "currentStatus": "completed" }, { "id": "bd6274d8c464cbafaeb4bdef", @@ -65,7 +65,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1da1ba1fe2e325df5fc34/54c1dd31226b8111794d132f/x/b0402135d9ecce6d4ab45c4b5e5aeaa0/Turning-online-horse-manure-into-strawberry-jam---since-2009.png", "imageUrl": "", "estimatedHours": 200, - "status": "completed" + "currentStatus": "completed" }, { "id": "bd1326d9c245cbafaeb4bdef", @@ -85,7 +85,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54d9810307b159a4d9027aa2/54d981bfe5eb145560fbb769/x/cf7f318bfe4aee631b0d0eeef272225c/logo.png", "imageUrl": "http://www.operationbrush.org/images/temp/hands1.png", "estimatedHours": 100, - "status": "started" + "currentStatus": "started" }, { "id": "bd1325d8c464cbafaeb5bdef", @@ -105,7 +105,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54c1daf2d72d8eb868910b60/54c1dd4ecffcb09fc52b68a1/x/a8148f08769b449217e433bab8f39ddd/RCR-color.jpg", "imageUrl": "http://www.rosecityrollers.com/wp-content/uploads/2015/01/BZ7_5923-X3-675x375.jpg", "estimatedHours": 200, - "status": "started" + "currentStatus": "started" }, { "id": "bd1325d8c464cbafaeb6bdef", @@ -125,7 +125,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/548b36629137780091a973cc/666x666/6c7a366ffb659649f6377d4a431687cd/country-logos-1-300dpi.jpg", "imageUrl": "http://www.saveachildsheart.com/wp-content/uploads/2013/10/7.2.5_Internation_Photograohy_Exhibition.jpg", "estimatedHours": 100, - "status": "completed" + "currentStatus": "completed" }, { "id": "bd1325d8c464cbafaeb4bdef", @@ -145,7 +145,7 @@ "logoUrl": "https://trello-attachments.s3.amazonaws.com/54ee3c7bf205562680177b59/218x190/1dc460de4edc9fdd4b481b24e93cfb23/logo.png", "imageUrl": "http://www.privatewifi.com/wp-content/uploads/2014/10/Halpert.jpg", "estimatedHours": 200, - "status": "started" + "currentStatus": "started" }, { "id": "bd1325d8c464cbafaeb7bdef", @@ -165,7 +165,7 @@ "logoUrl": "http://static1.squarespace.com/static/521b8957e4b024f66a58b214/t/521b8e9de4b093a8696eb9b8/1398718364447/?format=750w", "imageUrl": "https://trello-attachments.s3.amazonaws.com/54fdb0328917ca64e9e8a79f/54fdc3b710f67caf6da14719/x/49fbe0012179bf254928f3f2a44810b4/Screen_2BShot_2B2013-08-26_2Bat_2B1.32.35_2BPM.png", "estimatedHours": 200, - "status": "started" + "currentStatus": "started" }, { "id": "bd1325d8c464cbafaeb8bdef", @@ -185,6 +185,6 @@ "logoUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/1277176.png?480", "imageUrl": "http://www.timeraiser.ca/uploads/5/6/1/4/5614163/______________4571248_orig.png", "estimatedHours": 100, - "status": "started" + "currentStatus": "started" } ] diff --git a/views/nonprofits/show.jade b/views/nonprofits/show.jade index 8ef8fc8776..de4d735815 100644 --- a/views/nonprofits/show.jade +++ b/views/nonprofits/show.jade @@ -43,6 +43,7 @@ block content .ion-university   E-learning Platform if (approvedOther) .ion-settings   Other tools + h3 Project Status: #{currentStatus} if (interestedCampers && interestedCampers.length > 0) h3 Interested campers: .col-xs-12.text-left @@ -60,7 +61,7 @@ block content .col-xs-12.col-sm-8.col-sm-offset-2 .text-center a.btn.btn-primary.btn-big.btn-block.disabled(href='/nonprofits/interested-in-nonprofit/#{dashedName}') I'm interested in building this project * - p * Complete all our Bonfires, 10 Ziplines, and 3 Basejumps to unlock this. + p * Complete all our Bonfires, Ziplines, and Basejumps to unlock this. #showAllButton.btn.btn-info.btn-big.btn-block Show all Nonprofits Projects if (buttonActive) .col-xs-12.col-sm-8.col-sm-offset-2 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 7190fd14ec..1882ada3bc 100644 --- a/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade +++ b/views/nonprofits/your-nonprofit-project-application-has-been-submitted.jade @@ -3,4 +3,4 @@ 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 + h2 Thank you for reaching out to us. We’ll send you an email no later than #{getBackDay}. diff --git a/views/partials/nonprofit-application-progress-bar.jade b/views/partials/nonprofit-application-progress-bar.jade index 440bb67262..e9ed80c441 100644 --- a/views/partials/nonprofit-application-progress-bar.jade +++ b/views/partials/nonprofit-application-progress-bar.jade @@ -3,7 +3,7 @@ .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 +h3.gray-text.text-center Step #{step} of 10 script. @@ -36,4 +36,4 @@ script. } }); } - $('#preliminary-story-submit').on('click', preliminaryStorySubmit); \ No newline at end of file + $('#preliminary-story-submit').on('click', preliminaryStorySubmit); From 73b536de951c190df5b35136391966a2b9a2e283 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 15:05:24 -0700 Subject: [PATCH 102/125] update sitemap --- controllers/resources.js | 32 +++++++++++++++++++++++--------- views/resources/sitemap.jade | 24 ++++++++++++++++-------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index f4db69018f..8ec410f9dc 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -52,7 +52,7 @@ module.exports = { debug('User err: ', err); return next(err); } - Challenge.find({}, function (err, challenges) { + Courseware.find({}, function (err, challenges) { if (err) { debug('User err: ', err); return next(err); @@ -67,14 +67,28 @@ module.exports = { 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 + Nonprofit.find({}, function (err, nonprofits) { + if (err) { + debug('User err: ', err); + return next(err); + } + FieldGuide.find({}, function (err, fieldGuides) { + if (err) { + debug('User err: ', err); + return next(err); + } + res.header('Content-Type', 'application/xml'); + res.render('resources/sitemap', { + appUrl: appUrl, + now: now, + users: users, + challenges: challenges, + bonfires: bonfires, + stories: stories, + nonprofits: nonprofits, + fieldGuides: fieldGuides + }); + }); }); }); }); diff --git a/views/resources/sitemap.jade b/views/resources/sitemap.jade index c51b2a3c99..d2b4298424 100644 --- a/views/resources/sitemap.jade +++ b/views/resources/sitemap.jade @@ -105,7 +105,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Products each bonfire in bonfires url - loc #{appUrl}/#{bonfire.name.replace(/\s/, '-')} + loc #{appUrl}/bonfires/#{bonfire.name.replace(/\s/, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -113,7 +113,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Challenges each challenge in challenges url - loc #{appUrl}/#{challenge.challengeNumber} + loc #{appUrl}/challenges/#{challenge.name.replace(/\s/, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -121,15 +121,23 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Stories each story in stories url - loc #{appUrl}/#{story.storyLink} + loc #{appUrl}/stories/#{story.storyLink.name.replace(/\s/, '-')} lastmod= now changefreq daily priority= 0.9 -//- Nonprofit + //- Nonprofit each nonprofit in nonprofits url - loc #{appUrl}/#{nonprofit.nonprofitLink} - lastmod= now - changefreq daily - priority= 0.9 \ No newline at end of file + loc #{appUrl}/nonprofits/#{nonprofit.name.replace(/\s/, '-')} + lastmod= now + changefreq daily + priority= 0.9 + + //- Nonprofit + each fieldGuide in fieldGuides + url + loc #{appUrl}/field-guide/#{fieldGuide.name.replace(/\s/, '-')} + lastmod= now + changefreq daily + priority= 0.9 From 7da6f69cd85be2981d9799de3115015f542b8f5d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 17:18:51 -0700 Subject: [PATCH 103/125] start process of moving wiki over to field guide --- app.js | 30 +++--- controllers/challengeMap.js | 2 - controllers/fieldGuide.js | 99 +++++++++++++++++++ controllers/resources.js | 12 +-- controllers/wiki.js | 99 ------------------- models/{Wiki.js => FieldGuide.js} | 4 +- models/User.js | 4 +- public/js/main.js | 17 ++-- seed_data/{wikis.json => field-guides.json} | 0 seed_data/seed.js | 10 +- views/{wiki => field-guide}/show.jade | 10 +- .../partials/{wikis.jade => field-guide.jade} | 18 ++-- views/partials/navbar.jade | 2 +- 13 files changed, 153 insertions(+), 154 deletions(-) create mode 100644 controllers/fieldGuide.js delete mode 100644 controllers/wiki.js rename models/{Wiki.js => FieldGuide.js} (66%) rename seed_data/{wikis.json => field-guides.json} (100%) rename views/{wiki => field-guide}/show.jade (81%) rename views/partials/{wikis.jade => field-guide.jade} (51%) diff --git a/app.js b/app.js index 27092125a1..e522e412c3 100644 --- a/app.js +++ b/app.js @@ -42,7 +42,7 @@ var express = require('express'), nonprofitController = require('./controllers/nonprofits'), bonfireController = require('./controllers/bonfire'), coursewareController = require('./controllers/courseware'), - wikiController = require('./controllers/wiki'), + fieldGuideController = require('./controllers/fieldGuide'), challengeMapController = require('./controllers/challengeMap'), @@ -238,11 +238,11 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 })); app.get('/', homeController.index); app.get('/privacy', function(req, res) { - res.redirect(301, "/wiki/free-code-camp's-privacy-policy"); + res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); }); app.get('/nonprofit-project-instructions', function(req, res) { - res.redirect(301, "/wiki/free-code-camp's-privacy-policy"); + res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); }); app.get('/jquery-exercises', resourcesController.jqueryExercises); @@ -254,31 +254,31 @@ app.get('/twitch', resourcesController.twitch); app.get('/map', challengeMapController.challengeMap); app.get('/live-pair-programming', function(req, res) { - res.redirect(301, '/wiki/live-stream-pair-programming-on-twitch.tv'); + res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); }); app.get('/install-screenhero', function(req, res) { - res.redirect(301, '/wiki/install-screenhero'); + res.redirect(301, '/field-guide/install-screenhero'); }); app.get('/guide-to-our-nonprofit-projects', function(req, res) { - res.redirect(301, '/wiki/a-guide-to-our-nonprofit-projects'); + res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); }); app.get('/chromebook', function(req, res) { - res.redirect(301, '/wiki/chromebook'); + res.redirect(301, '/field-guide/chromebook'); }); app.get('/deploy-a-website', function(req, res) { - res.redirect(301, '/wiki/deploy-a-website'); + res.redirect(301, '/field-guide/deploy-a-website'); }); app.get('/gmail-shortcuts', function(req, res) { - res.redirect(301, '/wiki/gmail-shortcuts'); + res.redirect(301, '/field-guide/gmail-shortcuts'); }); app.get('/nodeschool-challenges', function(req, res) { - res.redirect(301, '/wiki/nodeschool-challenges'); + res.redirect(301, '/field-guide/nodeschool-challenges'); }); app.get('/stats', function(req, res) { @@ -484,7 +484,7 @@ app.get('/api/trello', resourcesController.trelloCalls); * Bonfire related routes */ -app.get('/wiki/getWikiList', wikiController.showAllWikis); +app.get('/field-guide/getFieldGuideList', fieldGuideController.showAllFieldGuides); app.get('/playground', bonfireController.index); @@ -510,15 +510,15 @@ app.get('/bonfire', function(req, res) { app.post('/completed-bonfire/', bonfireController.completedBonfire); /** - * Wiki related routes + * Field Guide related routes */ -app.get('/wiki/:wikiName', wikiController.returnIndividualWiki); +app.get('/field-guide/:fieldGuideName', fieldGuideController.returnIndividualFieldGuide); -app.get('/wiki', wikiController.returnNextWiki); +app.get('/field-guide', fieldGuideController.returnNextFieldGuide); -app.post('/completed-wiki/', wikiController.completedWiki); +app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide); /** diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js index f8cad437cf..b858bf7385 100644 --- a/controllers/challengeMap.js +++ b/controllers/challengeMap.js @@ -2,7 +2,6 @@ var async = require('async'), User = require('../models/User'), Bonfire = require('./../models/Bonfire'), Story = require('./../models/Story'), - Wiki = require('./../models/Wiki'), Nonprofit = require('./../models/Nonprofit'), Comment = require('./../models/Comment'), Courseware = require('./../models/Courseware'), @@ -12,7 +11,6 @@ var async = require('async'), bonfires = require('../seed_data/bonfires.json'), nonprofits = require('../seed_data/nonprofits.json'), coursewares = require('../seed_data/coursewares.json'), - wikis = require('../seed_data/wikis.json'), moment = require('moment'), https = require('https'), debug = require('debug')('freecc:cntr:resources'), diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js new file mode 100644 index 0000000000..49b79e6bd6 --- /dev/null +++ b/controllers/fieldGuide.js @@ -0,0 +1,99 @@ +var _ = require('lodash'), + debug = require('debug')('freecc:cntr:fieldGuide'), + FieldGuide = require('./../models/FieldGuide'), + resources = require('./resources'), + R = require('ramda'); + +exports.returnIndividualFieldGuide = function(req, res, next) { + var dashedName = req.params.fieldGuideName; + + var fieldGuideName = dashedName.replace(/\-/g, ' '); + + FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) { + if (err) { + next(err); + } + + if (fieldGuide.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a field guide entry with that name. Please double check the name." + }); + + return res.redirect('/field-guide'); + } + + fieldGuide = fieldGuide.pop(); + var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + if (dashedNameFull != dashedName) { + return res.redirect('../field-guide/' + dashedNameFull); + } + res.render('field-guide/show', { + title: fieldGuide.name, + fieldGuideId: fieldGuide._id, + description: fieldGuide.description.join('') + }); + }); +}; + +exports.showAllFieldGuides = function(req, res) { + var data = {}; + data.fieldGuideList = resources.allFieldGuideNames(); + data.fieldGuideIds = resources.allFieldGuideIds(); + data.completedFieldGuides = req.user.completedFieldGuides; + res.send(data); +}; + +exports.returnNextFieldGuide = function(req, res, next) { + if (!req.user) { + return res.redirect('../field-guide/how-do-i-use-this-guide?'); + } + + var completed = req.user.completedFieldGuides; + + req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.save(); + + var uncompletedFieldGuides = req.user.uncompletedFieldGuides; + + var displayedFieldGuides = FieldGuide.find({'_id': uncompletedFieldGuides[0]}); + displayedFieldGuides.exec(function(err, fieldGuide) { + if (err) { + return next(err); + } + fieldGuide = fieldGuide.pop(); + if (fieldGuide === undefined) { + req.flash('success', { + msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here." + }); + return res.redirect('../field-guide/how-do-i-use-this-guide?'); + } + var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../field-guide/' + nameString); + }); +}; + +exports.completedFieldGuide = function (req, res, next) { + debug('params in completedFieldGuide', req.params); + var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId; + + req.user.completedFieldGuides.push(fieldGuideId); + + var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedFieldGuides.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + res.send(true); + } + }); +}; diff --git a/controllers/resources.js b/controllers/resources.js index 8ec410f9dc..707b333fe7 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -3,7 +3,7 @@ var async = require('async'), Challenge = require('./../models/Challenge'), Bonfire = require('./../models/Bonfire'), Story = require('./../models/Story'), - Wiki = require('./../models/Wiki'), + FieldGuide = require('./../models/FieldGuide'), Nonprofit = require('./../models/Nonprofit'), Comment = require('./../models/Comment'), resources = require('./resources.json'), @@ -12,7 +12,7 @@ var async = require('async'), bonfires = require('../seed_data/bonfires.json'), nonprofits = require('../seed_data/nonprofits.json'), coursewares = require('../seed_data/coursewares.json'), - wikis = require('../seed_data/wikis.json'), + fieldGuides = require('../seed_data/field-guides.json'), moment = require('moment'), https = require('https'), debug = require('debug')('freecc:cntr:resources'), @@ -259,8 +259,8 @@ module.exports = { }); }, - allWikiIds: function() { - return wikis.map(function(elem) { + allFieldGuideIds: function() { + return fieldGuides.map(function(elem) { return { _id: elem._id, } @@ -289,8 +289,8 @@ module.exports = { }); }, - allWikiNames: function() { - return wikis.map(function(elem) { + allFieldGuideNames: function() { + return fieldGuides.map(function(elem) { return { name: elem.name } diff --git a/controllers/wiki.js b/controllers/wiki.js deleted file mode 100644 index 04d95114ce..0000000000 --- a/controllers/wiki.js +++ /dev/null @@ -1,99 +0,0 @@ -var _ = require('lodash'), - debug = require('debug')('freecc:cntr:wiki'), - Wiki = require('./../models/Wiki'), - resources = require('./resources'), - R = require('ramda'); - -exports.returnIndividualWiki = function(req, res, next) { - var dashedName = req.params.wikiName; - - var wikiName = dashedName.replace(/\-/g, ' '); - - Wiki.find({'name': new RegExp(wikiName, 'i')}, function(err, wiki) { - if (err) { - next(err); - } - - if (wiki.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a wiki entry with that name. Please double check the name." - }); - - return res.redirect('/wiki'); - } - - wiki = wiki.pop(); - var dashedNameFull = wiki.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); - if (dashedNameFull != dashedName) { - return res.redirect('../wiki/' + dashedNameFull); - } - res.render('wiki/show', { - title: wiki.name, - wikiId: wiki._id, - description: wiki.description.join('') - }); - }); -}; - -exports.showAllWikis = function(req, res) { - var data = {}; - data.wikiList = resources.allWikiNames(); - data.wikiIds = resources.allWikiIds(); - data.completedWikis = req.user.completedWikis; - res.send(data); -}; - -exports.returnNextWiki = function(req, res, next) { - if (!req.user) { - return res.redirect('../wiki/how-do-i-use-this-guide?'); - } - - var completed = req.user.completedWikis; - - req.user.uncompletedWikis = resources.allWikiIds().filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - req.user.save(); - - var uncompletedWikis = req.user.uncompletedWikis; - - var displayedWikis = Wiki.find({'_id': uncompletedWikis[0]}); - displayedWikis.exec(function(err, wiki) { - if (err) { - return next(err); - } - wiki = wiki.pop(); - if (wiki === undefined) { - req.flash('success', { - msg: "You've read all our current Wiki entries. You can contribute to our Wiki here." - }); - return res.redirect('../wiki/how-do-i-use-this-guide?'); - } - var nameString = wiki.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../wiki/' + nameString); - }); -}; - -exports.completedWiki = function (req, res, next) { - debug('params in completedWiki', req.params); - var wikiId = req.body.wikiInfo.wikiId; - - req.user.completedWikis.push(wikiId); - - var index = req.user.uncompletedWikis.indexOf(wikiId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedWikis.splice(index, 1); - } - - req.user.save(function (err, user) { - if (err) { - return next(err); - } - if (user) { - res.send(true); - } - }); -}; diff --git a/models/Wiki.js b/models/FieldGuide.js similarity index 66% rename from models/Wiki.js rename to models/FieldGuide.js index e7eb8a183b..b705f44774 100644 --- a/models/Wiki.js +++ b/models/FieldGuide.js @@ -1,7 +1,7 @@ var mongoose = require('mongoose'); var secrets = require('../config/secrets'); -var wikiSchema = new mongoose.Schema({ +var fieldGuideSchema = new mongoose.Schema({ name: { type: String, unique: false @@ -12,4 +12,4 @@ var wikiSchema = new mongoose.Schema({ } }); -module.exports = mongoose.model('Wiki', wikiSchema); +module.exports = mongoose.model('FieldGuide', fieldGuideSchema); diff --git a/models/User.js b/models/User.js index d511c4a897..36e9c87f78 100644 --- a/models/User.js +++ b/models/User.js @@ -127,8 +127,8 @@ var userSchema = new mongoose.Schema({ verified: Boolean } ], - completedWikis: [], - uncompletedWikis: [], + completedFieldGuides: [], + uncompletedFieldGuides: [], currentStreak: { type: Number, default: 0 diff --git a/public/js/main.js b/public/js/main.js index 00081f0f4b..14db8f7f1b 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -52,18 +52,18 @@ $(document).ready(function() { } } - function completedWiki(wikiId) { + function completedFieldGuide(fieldGuideId) { if ($('.signup-btn-nav').length < 1) { $.post( - '/completed-wiki', + '/completed-field-guide', { - wikiInfo: { - wikiId: wikiId + fieldGuideInfo: { + fieldGuideId: fieldGuideId } }, function(res) { if (res) { - window.location.href = '/wiki' + window.location.href = '/field-guide' } }); } @@ -78,9 +78,10 @@ $(document).ready(function() { }); - $('.next-wiki-button').on('click', function() { - var wikiId = $('#wikiId').text(); - completedWiki(wikiId); + $('.next-field-guide-button').on('click', function() { + console.log('click'); + var fieldGuideId = $('#fieldGuideId').text(); + completedFieldGuide(fieldGuideId); }); $("img").error(function () { diff --git a/seed_data/wikis.json b/seed_data/field-guides.json similarity index 100% rename from seed_data/wikis.json rename to seed_data/field-guides.json diff --git a/seed_data/seed.js b/seed_data/seed.js index 5a131cc4d9..95c35b328f 100644 --- a/seed_data/seed.js +++ b/seed_data/seed.js @@ -1,12 +1,12 @@ require('dotenv').load(); var Bonfire = require('../models/Bonfire.js'), Courseware = require('../models/Courseware.js'), - Wiki = require('../models/Wiki.js'), + FieldGuide = require('../models/FieldGuide.js'), Nonprofit = require('../models/Nonprofit.js'), mongoose = require('mongoose'), secrets = require('../config/secrets'), coursewares = require('./coursewares.json'), - wikis = require('./wikis.json'), + fieldGuides = require('./field-guides.json'), nonprofits = require('./nonprofits.json'), bonfires = require('./bonfires.json'); @@ -61,13 +61,13 @@ Courseware.remove({}, function(err, data) { console.log('coursewares'); }); -Wiki.remove({}, function(err, data) { +FieldGuide.remove({}, function(err, data) { if (err) { console.error(err); } else { console.log('Deleted ', data); } - Wiki.create(wikis, function(err, data) { + FieldGuide.create(fieldGuides, function(err, data) { if (err) { console.log(err); } else { @@ -75,7 +75,7 @@ Wiki.remove({}, function(err, data) { } CompletionMonitor(); }); - console.log('wikis'); + console.log('field guides'); }); Nonprofit.remove({}, function(err, data) { diff --git a/views/wiki/show.jade b/views/field-guide/show.jade similarity index 81% rename from views/wiki/show.jade rename to views/field-guide/show.jade index 1f9782a2e9..5a568642dc 100644 --- a/views/wiki/show.jade +++ b/views/field-guide/show.jade @@ -1,7 +1,7 @@ extends ../layout block content script. - var challengeName = 'Wiki View'; + var challengeName = 'Field Guide View'; .col-xs-12.col-sm-12.col-md-12 .panel.panel-info .panel-heading.text-center @@ -15,7 +15,7 @@ block content .spacer .col-xs-12.col-sm-6.col-sm-offset-3 .text-center - .next-wiki-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) + .next-fieldGuide-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) .ten-pixel-break #showAllButton.btn.btn-info.btn-big.btn-block Show me all articles .spacer @@ -30,13 +30,13 @@ block content .modal-header.all-list-header Wiki Articles a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body - include ../partials/wikis - #wikiId.hidden= wikiId + include ../partials/field-guide + #fieldGuideId.hidden= fieldGuideId script. $(document).ready(function() { $('body').keydown(function(e) { if (e.ctrlKey && e.keyCode == 13) { - $('.next-wiki-button').click(); + $('.next-field-guide-button').click(); } }); }); diff --git a/views/partials/wikis.jade b/views/partials/field-guide.jade similarity index 51% rename from views/partials/wikis.jade rename to views/partials/field-guide.jade index 6e6d817fd1..7af5c02fc9 100644 --- a/views/partials/wikis.jade +++ b/views/partials/field-guide.jade @@ -1,5 +1,5 @@ h3 - ol#wikiList + ol#fieldGuideList script(src='/js/lib/ramda/ramda.min.js') script. var getLinkedName = function getLinkedName(name) { @@ -8,22 +8,22 @@ h3 return name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); } $.ajax({ - url: '/wiki/getWikiList', + url: '/field-guide/getFieldGuideList', type: 'GET' }) .success( function(data) { var docfrag = document.createDocumentFragment(); - for (var i = 0; i < data.wikiList.length; i++) { + for (var i = 0; i < data.fieldGuideList.length; i++) { var li = document.createElement("li"); - // strike through wikis previously read - var linkedName = getLinkedName(data.wikiList[i].name); - if (data.completedWikis.indexOf(data.wikiIds[i]) > -1) { - $(li).html("" + data.wikiList[i].name + ""); + // strike through field guides previously read + var linkedName = getLinkedName(data.fieldGuideList[i].name); + if (data.completedFieldGuides.indexOf(data.fieldGuideIds[i]) > -1) { + $(li).html("" + data.fieldGuideList[i].name + ""); } else { - $(li).html("" + data.wikiList[i].name + ""); + $(li).html("" + data.fieldGuideList[i].name + ""); } docfrag.appendChild(li); }; - $('#wikiList').append(docfrag); + $('#fieldGuideList').append(docfrag); }); diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 17901f93ca..e85e2b20e0 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -18,7 +18,7 @@ li a(href='/stories/hot') News li - a(href='/wiki') Field Guide + a(href='/field-guide') Field Guide if !user li       li From 3b7ea3d9a4719aae8875ccbb8022e7629bd93323 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 8 Apr 2015 18:03:06 -0700 Subject: [PATCH 104/125] start work on challenge-migraiton --- seed_data/challenge-migration.js | 58 ++++++++ seed_data/challengeMapping.json | 226 +++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 seed_data/challenge-migration.js create mode 100644 seed_data/challengeMapping.json diff --git a/seed_data/challenge-migration.js b/seed_data/challenge-migration.js new file mode 100644 index 0000000000..6109f7775b --- /dev/null +++ b/seed_data/challenge-migration.js @@ -0,0 +1,58 @@ +require('dotenv').load(); +var bonfires = require('./bonfires.json'), + app = require('../server/server'), + mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + User = app.models.User, + UserIdentity = app.models.userIdentity, + oldUri='mongodb://localhost:27017/app30893198', + coursewares = require('./coursewares.json'); + +var counter = 0; +var offerings = 2; + +var CompletionMonitor = function() { + counter++; + console.log('call ' + counter); + + if (counter < offerings) { + return; + } else { + process.exit(0); + } +}; + +MongoClient.connect(oldUri, function(err, database) { + + database.collection('users').find({}).batchSize(20).toArray(function(err, users) { + if (users !== null && users.length !== 0) { + var mappedUserArray = users.map(function(user) { + Object.keys(user.profile).forEach(function(prop) { + user[prop] = user.profile[prop]; + }); + Object.keys(user.portfolio).forEach(function(prop) { + user[prop] = user.portfolio[prop]; + }); + + user.completedCoursewares = Object.keys(user.challengesHash) + .filter(function(key) { + return user.challengesHash[key] !== 0; + }) + .map(function(key) { + return({ + _id: coursewares[key].id, + completedDate: user.challengesHash[key] + }); + }); + + return user; + }); + User.create(mappedUserArray, function(err) { + if (err) { + console.log(err); + } + console.log("a batch finished"); + }); + } + }); +}); diff --git a/seed_data/challengeMapping.json b/seed_data/challengeMapping.json new file mode 100644 index 0000000000..db6686d266 --- /dev/null +++ b/seed_data/challengeMapping.json @@ -0,0 +1,226 @@ +[ + { + "oldNumber": "0", + "newId": "bd7124d8c441eddfaeb5bdef" + }, + { + "oldNumber": "1", + "newId": "bd7125d8c441eddfaeb5bd0f" + }, + { + "oldNumber": "2", + "newId": "" + }, + { + "oldNumber": "3", + "newId": "bd7127d8c441eddfaeb5bdef" + }, + { + "oldNumber": "4", + "newId": "bd7128d8c441eddfaeb5bdef" + }, + { + "oldNumber": "5", + "newId": "bd8129d8c441eddfaeb5bdef" + }, + { + "oldNumber": "6", + "newId": "" + }, + { + "oldNumber": "7", + "newId": "" + }, + { + "oldNumber": "8", + "newId": "bd7112d8c441eddfaeb5bdef" + }, + { + "oldNumber": "9", + "newId": "bd7113d8c441eddfaeb5bdef" + }, + { + "oldNumber": "10", + "newId": "bd7114d8c441eddfaeb5bdef" + }, + { + "oldNumber": "11", + "newId": "bd7115d8c441eddfaeb5bdef" + }, + { + "oldNumber": "12", + "newId": "bd7116d8c441eddfaeb5bdef" + }, + { + "oldNumber": "13", + "newId": "bd7117d8c441eddfaeb5bdef" + }, + { + "oldNumber": "14", + "newId": "bd7118d8c441eddfaeb5bdef" + }, + { + "oldNumber": "15", + "newId": "" + }, + { + "oldNumber": "16", + "newId": "" + }, + { + "oldNumber": "17", + "newId": "" + }, + { + "oldNumber": "18", + "newId": "" + }, + { + "oldNumber": "19", + "newId": "bd7123d8c441eddfaeb5bdef" + }, + { + "oldNumber": "20", + "newId": "bd8124d8c441eddfaeb5bdef" + }, + { + "oldNumber": "21", + "newId": "bd8126d8c441eddfaeb5bdef" + }, + { + "oldNumber": "22", + "newId": "bd8127d8c441eddfaeb5bdef" + }, + { + "oldNumber": "23", + "newId": "bd8128d8c441eddfaeb5bdef" + }, + { + "oldNumber": "24", + "newId": "bd7129d8c441eddfaeb5bdef" + }, + { + "oldNumber": "25", + "newId": "bd7130d8c441eddfaeb5bdef" + }, + { + "oldNumber": "26", + "newId": "bd7131d8c441eddfaeb5bdef" + }, + { + "oldNumber": "27", + "newId": "bd7132d8c441eddfaeb5bdef" + }, + { + "oldNumber": "28", + "newId": "bd7133d8c441eddfaeb5bdef" + }, + { + "oldNumber": "29", + "newId": "bd7134d8c441eddfaeb5bdef" + }, + { + "oldNumber": "30", + "newId": "bd7135d8c441eddfaeb5bdef" + }, + { + "oldNumber": "31", + "newId": "bd7136d8c441eddfaeb5bdef" + }, + { + "oldNumber": "32", + "newId": "" + }, + { + "oldNumber": "33", + "newId": "bd7138d8c441eddfaeb5bdef" + }, + { + "oldNumber": "34", + "newId": "bd7137d8c441eddfaeb5bdef" + }, + { + "oldNumber": "35", + "newId": "bd7140d8c441eddfaeb5bdef" + }, + { + "oldNumber": "36", + "newId": "" + }, + { + "oldNumber": "37", + "newId": "" + }, + { + "oldNumber": "38", + "newId": "" + }, + { + "oldNumber": "39", + "newId": "" + }, + { + "oldNumber": "40", + "newId": "" + }, + { + "oldNumber": "41", + "newId": "" + }, + { + "oldNumber": "42", + "newId": "" + }, + { + "oldNumber": "43", + "newId": "" + }, + { + "oldNumber": "44", + "newId": "" + }, + { + "oldNumber": "45", + "newId": "" + }, + { + "oldNumber": "46", + "newId": "" + }, + { + "oldNumber": "47", + "newId": "" + }, + { + "oldNumber": "48", + "newId": "bd7153d8c441eddfaeb5bd2f" + }, + { + "oldNumber": "49", + "newId": "bd7154d8c441eddfaeb5bdef" + }, + { + "oldNumber": "50", + "newId": "bd7155d8c441eddfaeb5bdef" + }, + { + "oldNumber": "51", + "newId": "bd7156d8c441eddfaeb5bdef" + }, + { + "oldNumber": "52", + "newId": "bd7157d8c441eddfaeb5bdef" + }, + { + "oldNumber": "53", + "newId": "bd7158d8c441eddfaeb5bdef" + }, + { + "oldNumber": "54", + "newId": "" + }, + { + "oldNumber": "55", + "newId": "" + } +] From 332d4f187515fca8f02638503ff640b4b8e0947f Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Thu, 9 Apr 2015 17:50:10 -0700 Subject: [PATCH 105/125] refactor story.js mailer and remove some cruft --- controllers/story.js | 32 ++++++-------------------------- models/NonprofitInterest.js | 16 ---------------- seed_data/field-guides.json | 4 ++-- views/field-guide/show.jade | 2 +- 4 files changed, 9 insertions(+), 45 deletions(-) delete mode 100644 models/NonprofitInterest.js diff --git a/controllers/story.js b/controllers/story.js index 15c14b97cf..b7f7511bba 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -450,7 +450,12 @@ function commentSave(comment, Context, res, next) { if (err) { return next(err); } + var recipients = ''; if (data.originalStoryAuthorEmail && (data.originalStoryAuthorEmail !== recipient.email)) { + recipients = data.originalStoryAuthorEmail + ',' + recipient.email; + } else { + recipients = recipient.email; + } var transporter = nodemailer.createTransport({ service: 'Mandrill', auth: { @@ -459,7 +464,7 @@ function commentSave(comment, Context, res, next) { } }); var mailOptions = { - to: data.originalStoryAuthorEmail + ',' + recipient.email, + to: recipients, from: 'Team@freecodecamp.com', subject: associatedStory.author.username + " replied to your post on Camper News", text: [ @@ -474,31 +479,6 @@ function commentSave(comment, Context, res, next) { return err; } }); - } else { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: recipient.email, - from: 'Team@freecodecamp.com', - subject: associatedStory.author.username + " replied to your post on Camper News", - text: [ - "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", - "You can keep this conversation going.", - "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink, - '- the Free Code Camp Volunteer Team' - ].join('\n') - }; - transporter.sendMail(mailOptions, function (err) { - if (err) { - return err; - } - }); - } }); }); } catch (e) { diff --git a/models/NonprofitInterest.js b/models/NonprofitInterest.js deleted file mode 100644 index 44a3fd9b6d..0000000000 --- a/models/NonprofitInterest.js +++ /dev/null @@ -1,16 +0,0 @@ -var mongoose = require('mongoose'); -var secrets = require('../config/secrets'); - -var nonprofitInterestSchema = new mongoose.Schema({ - nonprofitId: - name: { - type: String, - unique: false - }, - description: { - type: Array, - unique: false - } -}); - -module.exports = mongoose.model('Wiki', wikiSchema); diff --git a/seed_data/field-guides.json b/seed_data/field-guides.json index 1ca02b4be0..4ffdffa058 100644 --- a/seed_data/field-guides.json +++ b/seed_data/field-guides.json @@ -329,7 +329,7 @@ "", "

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

    ", "

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

    ", - "

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

    ", + "

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

    ", "

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

    ", "

    Hosting Apps

    ", "

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

    ", @@ -449,7 +449,7 @@ "

    ", "
      ", "
    1. Complete this form:  http://goo.gl/forms/f61dLt67t8.
    2. ", - "
    3. Read this document, which will answer many questions you may have about our nonprofit projects:  http://freecodecamp.com/wiki/guide-to-our-nonprofit-projects.
    4. ", + "
    5. Read this document, which will answer many questions you may have about our nonprofit projects:  http://freecodecamp.com/field-guide/guide-to-our-nonprofit-projects.
    6. ", "
    7. We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you.
    8. ", "
    9. Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp \"exit test\". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look: team@freecodecamp.com.
    10. ", "
    ", diff --git a/views/field-guide/show.jade b/views/field-guide/show.jade index 5a568642dc..ea20ce564f 100644 --- a/views/field-guide/show.jade +++ b/views/field-guide/show.jade @@ -27,7 +27,7 @@ block content #show-all-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation .modal-content - .modal-header.all-list-header Wiki Articles + .modal-header.all-list-header Field Guide Articles a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body include ../partials/field-guide From f85804f270552c75128746e18a6afa81c8c0a46b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 12 Apr 2015 02:06:38 -0400 Subject: [PATCH 106/125] start writing user migration for challenge completion --- models/User.js | 1 + seed_data/userMigration.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 seed_data/userMigration.js diff --git a/models/User.js b/models/User.js index 36e9c87f78..7a1e20826b 100644 --- a/models/User.js +++ b/models/User.js @@ -65,6 +65,7 @@ var userSchema = new mongoose.Schema({ default: '' } }, + portfolio: { website1Link: { type: String, diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js new file mode 100644 index 0000000000..834007ff21 --- /dev/null +++ b/seed_data/userMigration.js @@ -0,0 +1,35 @@ +require('dotenv').load(); +var mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + Server = require('mongodb').Server, + User = require('../models/User.js'), + newChallenges = require('./challengeMapping.json'), + secrets = require('../config/secrets'); + +var mongoClient = new MongoClient(new Server('localhost', 27017), {native_parser: true}); +var mongoose = require('mongoose'); +mongoose.connect(secrets.db); + +User.find(function(err, users) { + if (err) { console.log(err) } + users.forEach(function(user) { + console.log('in users'); + if (typeof user.challengesHash !== 'undefined') { + var oldChallenges = Object.keys(user.challengesHash).filter(function (challenge) { + return user.challengesHash[challenge]; + }).map(function (data) { + return ({ + challengeNum: data, + timeStamp: user.challengesHash[data] + }); + }); + oldChallenges.forEach(function (challenge) { + user.progressTimestamps.push(challenge.timeStamp); + }); + newChallenges = newChallenges.filter(function (elem) { + return elem.newId; + }); + console.log(newChallenges); + } + }); +}); From e98f445444d5f121e9851428de042ca8d7d469cf Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 13 Apr 2015 00:02:21 -0400 Subject: [PATCH 107/125] update the challenge sequencing --- seed_data/coursewares.json | 102 ++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 9deaa5e643..3a427acecc 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -105,8 +105,8 @@ }, { "_id": "bd7137d8c441eddfaeb5bdef", - "name": "Learn how to Get Help", - "difficulty": 0.04, + "name": "Get Help the Hacker Way with RSAP", + "difficulty": 0.07, "challengeSeed": "111500801", "description": [ "Let's cover one last thing before you start working through our lessons: how to get help.", @@ -123,7 +123,7 @@ { "_id": "bd7127d8c441eddfaeb5bdef", "name": "Build a Personal Website", - "difficulty": 0.05, + "difficulty": 0.08, "challengeSeed": "114627406", "description": [ "There are tons of interactive HTML and CSS tutorials out there, but Nathan Bashaw and Christine Bower's Dash tutorials - which they built for General Assembly - are our favorite.", @@ -135,7 +135,7 @@ { "_id": "bd7128d8c441eddfaeb5bdef", "name": "Build a Responsive Blog Theme", - "difficulty": 0.06, + "difficulty": 0.09, "challengeSeed": "114578441", "description": [ "Next, let's learn about responsive web design and continue learning about HTML and CSS.", @@ -150,7 +150,7 @@ { "_id": "bd8129d8c441eddfaeb5bdef", "name": "Build a Small Business Website", - "difficulty": 0.07, + "difficulty": 0.10, "challengeSeed": "114578438", "description": [ "Ready for some more HTML and CSS fundamentals?", @@ -162,7 +162,7 @@ { "_id": "bd7112d8c441eddfaeb5bdef", "name": "Get Started with jQuery", - "difficulty": 0.08, + "difficulty": 0.11, "challengeSeed": "114578435", "description": [ "jQuery is a powerful tool for manipulating HTML elements.", @@ -177,7 +177,7 @@ { "_id": "bd7113d8c441eddfaeb5bdef", "name": "Traverse the DOM", - "difficulty": 0.09, + "difficulty": 0.12, "challengeSeed": "114591805", "description": [ "Now let's learn more about DOM traversal - that is, moving from one HTML element to the next.", @@ -189,7 +189,7 @@ { "_id": "bd7114d8c441eddfaeb5bdef", "name": "Work with the DOM", - "difficulty": 0.10, + "difficulty": 0.13, "challengeSeed": "114591804", "description": [ "Let's learn some more advanced ways to use jQuery to manipulate the DOM.", @@ -201,7 +201,7 @@ { "_id": "bd7115d8c441eddfaeb5bdef", "name": "Listen for DOM Events", - "difficulty": 0.11, + "difficulty": 0.14, "challengeSeed": "114591802", "description": [ "Now let's learn how to use jQuery Listeners. These will \"listen\" for something to happen, and then trigger a subsequent event", @@ -213,7 +213,7 @@ { "_id": "bd7116d8c441eddfaeb5bdef", "name": "Use jQuery for Styling", - "difficulty": 0.12, + "difficulty": 0.15, "challengeSeed": "114591801", "description": [ "Finally, let's use jQuery to manipulate the way websites look by changing the CSS of elements.", @@ -225,7 +225,7 @@ { "_id": "bd7117d8c441eddfaeb5bdef", "name": "Build a MadLibs Game", - "difficulty": 0.13, + "difficulty": 0.16, "challengeSeed": "114591799", "description": [ "Now that we've built a foundation in jQuery, let's go back to Dash and do its last challenge.", @@ -235,24 +235,10 @@ "challengeType": 2, "tests": [] }, - { - "_id": "bd7118d8c441eddfaeb5bdef", - "name": "Discover Chrome's DevTools", - "difficulty": 0.14, - "challengeSeed": "110752743", - "description": [ - "It's time to learn the most powerful tool your browser has - the Development Tools!", - "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", - "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", - "Go to http://discover-devtools.codeschool.com and complete this short course." - ], - "challengeType": 2, - "tests": [] - }, { "_id": "bd7123d8c441eddfaeb5bdef", "name": "Learn Basic Computer Science", - "difficulty": 0.15, + "difficulty": 0.17, "challengeSeed": "114628241", "description": [ "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", @@ -266,7 +252,7 @@ { "_id": "bd8124d8c441eddfaeb5bdef", "name": "Learn Loops", - "difficulty": 0.16, + "difficulty": 0.18, "challengeSeed": "114597348", "description": [ "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", @@ -279,7 +265,7 @@ { "_id": "bd8125d8c441eddfaeb5bdef", "name": "Learn Computer Hardware", - "difficulty": 0.17, + "difficulty": 0.19, "challengeSeed": "114597347", "description": [ "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", @@ -292,7 +278,7 @@ { "_id": "bd8126d8c441eddfaeb5bdef", "name": "Learn Computer Networking", - "difficulty": 0.18, + "difficulty": 0.20, "challengeSeed": "114604811", "description": [ "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", @@ -305,7 +291,7 @@ { "_id": "bd8127d8c441eddfaeb5bdef", "name": "Learn Boolean Logic", - "difficulty": 0.19, + "difficulty": 0.21, "challengeSeed": "114604812", "description": [ "Now we'll do some more table exercises and learn boolean logic.", @@ -318,7 +304,7 @@ { "_id": "bd8128d8c441eddfaeb5bdef", "name": "Learn Computer Security", - "difficulty": 0.20, + "difficulty": 0.22, "challengeSeed": "114604813", "description": [ "We're almost done with Stanford's Introduction to Computer Science course!", @@ -332,7 +318,7 @@ { "_id": "bd7129d8c441eddfaeb5bdef", "name": "Build an Adventure Game", - "difficulty": 0.21, + "difficulty": 0.23, "challengeSeed": "114604814", "description": [ "Now that you understand some Computer Science fundamentals, let's focus on programming JavaScript!", @@ -347,7 +333,7 @@ { "_id": "bd7130d8c441eddfaeb5bdef", "name": "Build Rock Paper Scissors", - "difficulty": 0.22, + "difficulty": 0.24, "challengeSeed": "114604815", "description": [ "Now we'll learn how JavaScript functions work, and use them to build a simple Rock Paper Scissors game.", @@ -360,7 +346,7 @@ { "_id": "bd7131d8c441eddfaeb5bdef", "name": "Learn JavaScript For Loops", - "difficulty": 0.23, + "difficulty": 0.25, "challengeSeed": "114614220", "description": [ "Let's learn more about the loops that make virtually all programs possible - the \"For Loop\" and \"While Loop\". First, we'll learn the For Loop.", @@ -373,7 +359,7 @@ { "_id": "bd7132d8c441eddfaeb5bdef", "name": "Learn JavaScript While Loops", - "difficulty": 0.24, + "difficulty": 0.26, "challengeSeed": "114612889", "description": [ "Go to http://www.codecademy.com/courses/javascript-beginner-en-ASGIv/0/1 and complete the section.", @@ -385,7 +371,7 @@ { "_id": "bd7133d8c441eddfaeb5bdef", "name": "Learn Control Flow", - "difficulty": 0.25, + "difficulty": 0.27, "challengeSeed": "114612888", "description": [ "Much of human reasoning can be broken down into what we call Boolean Logic. Lucky for us, computers can think the same way! Let's learn how to instruct our computers by writing \"If Statements\" and \"Else Statements\".", @@ -399,7 +385,7 @@ { "_id": "bd7134d8c441eddfaeb5bdef", "name": "Build a Contact List", - "difficulty": 0.26, + "difficulty": 0.28, "challengeSeed": "114612887", "description": [ "Up to this point, you've been working mostly with strings and numbers. Now we're going to learn more complicated data structures, like \"Arrays\" and \"Objects\".", @@ -412,7 +398,7 @@ { "_id": "bd7135d8c441eddfaeb5bdef", "name": "Build an Address Book", - "difficulty": 0.27, + "difficulty": 0.29, "challengeSeed": "114612885", "description": [ "Let's learn more about objects.", @@ -425,7 +411,7 @@ { "_id": "bd7136d8c441eddfaeb5bdef", "name": "Build a Cash Register", - "difficulty": 0.28, + "difficulty": 0.30, "challengeSeed": "114612882", "description": [ "In this final Codecademy section, we'll learn even more about JavaScript objects.", @@ -435,10 +421,24 @@ "challengeType": 2, "tests": [] }, + { + "_id": "bd7118d8c441eddfaeb5bdef", + "name": "Discover Chrome's DevTools", + "difficulty": 0.31, + "challengeSeed": "110752743", + "description": [ + "It's time to learn the most powerful tool your browser has - the Development Tools!", + "If you aren't already using Chrome, you'll want to download it here: http://www.google.com/chrome/. While it's true that Firefox has a tool called Firebug that is very similar to Chrome's DevTools, we will use Chrome for this challenge.", + "Note that this course, jointly produced by Google and Code School, is technologically impressive, but occasionally buggy. If you encounter a bug, just ignore it and keep going.", + "Go to http://discover-devtools.codeschool.com and complete this short course." + ], + "challengeType": 2, + "tests": [] + }, { "_id": "bd7138d8c441eddfaeb5bdef", "name": "Learn Regular Expressions", - "difficulty": 0.29, + "difficulty": 0.32, "challengeSeed": "112547802", "description": [ "You can use a Regular Expression, or \"Regex\", to select specific types of characters in text.", @@ -452,7 +452,7 @@ { "_id": "bd7154d8c441eddfaeb5bdef", "name": "Get Started with Angular.js", - "difficulty": 0.30, + "difficulty": 0.33, "challengeSeed": "114684726", "description": [ "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", @@ -465,7 +465,7 @@ { "_id": "bd7155d8c441eddfaeb5bdef", "name": "Apply Angular.js Directives", - "difficulty": 0.31, + "difficulty": 0.34, "challengeSeed": "114684727", "description": [ "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", @@ -478,7 +478,7 @@ { "_id": "bd7156d8c441eddfaeb5bdef", "name": "Power Forms with Angular.js", - "difficulty": 0.32, + "difficulty": 0.35, "challengeSeed": "114684729", "description": [ "One area where Angular.js really shines is its powerful web forms.", @@ -491,7 +491,7 @@ { "_id": "bd7157d8c441eddfaeb5bdef", "name": "Customize Angular.js Directives", - "difficulty": 0.33, + "difficulty": 0.36, "challengeSeed": "114685062", "description": [ "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", @@ -503,7 +503,7 @@ { "_id": "bd7158d8c441eddfaeb5bdef", "name": "Create Angular.js Services", - "difficulty": 0.34, + "difficulty": 0.37, "challengeSeed": "114685060", "description": [ "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", @@ -516,7 +516,7 @@ { "_id": "bd7153d8c441eddfaeb5bdff", "name": "Start a Node.js Server", - "difficulty": 0.35, + "difficulty": 0.38, "challengeSeed": "114685061", "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", @@ -528,7 +528,7 @@ { "_id": "bd7153d8c441eddfaeb5bd0f", "name": "Manage Packages with NPM", - "difficulty": 0.35, + "difficulty": 0.39, "challengeSeed": "114685061", "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", @@ -540,7 +540,7 @@ { "_id": "bd7153d8c441eddfaeb5bd1f", "name": "Build Web Apps with Express.js", - "difficulty": 0.36, + "difficulty": 0.40, "challengeSeed": "114685061", "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", @@ -552,7 +552,7 @@ { "_id": "bd7153d8c441eddfaeb5bd2f", "name": "Manage Data with MongoDB", - "difficulty": 0.37, + "difficulty": 0.41, "challengeSeed": "114685061", "description": [ "MongoDB is a popular NoSQL (Not Only SQL) database used by many JavaScript apps.", @@ -565,7 +565,7 @@ { "_id": "bd7140d8c441eddfaeb5bdef", "name": "Manage Source Code with Git", - "difficulty": 0.38, + "difficulty": 0.42, "challengeSeed": "114635309", "description": [ "Revision Control Systems like Git ensure that, no matter how you experiment with your code, you can always roll back your app to a stable previous state.", @@ -578,7 +578,7 @@ { "_id": "bd7139d8c441eddfaeb5bdef", "name": "Pair Program on Bonfires", - "difficulty": 0.39, + "difficulty": 0.43, "challengeSeed": "119657641", "description": [ "OK, we're finally ready to start pair programming!", From 83c14cccc454419dcbfd415fa438189da89c7831 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 13 Apr 2015 01:03:26 -0400 Subject: [PATCH 108/125] continue work on user migration --- models/User.js | 5 ++-- seed_data/userMigration.js | 51 ++++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/models/User.js b/models/User.js index 7a1e20826b..8d089e1c89 100644 --- a/models/User.js +++ b/models/User.js @@ -65,7 +65,7 @@ var userSchema = new mongoose.Schema({ default: '' } }, - + challengesHash: {}, portfolio: { website1Link: { type: String, @@ -137,7 +137,8 @@ var userSchema = new mongoose.Schema({ longestStreak: { type: Number, default: 0 - } + }, + needsMigration: { type: Boolean, default: true } }); /** diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js index 834007ff21..38e74f5cb4 100644 --- a/seed_data/userMigration.js +++ b/seed_data/userMigration.js @@ -10,26 +10,33 @@ var mongoClient = new MongoClient(new Server('localhost', 27017), {native_parser var mongoose = require('mongoose'); mongoose.connect(secrets.db); -User.find(function(err, users) { - if (err) { console.log(err) } - users.forEach(function(user) { - console.log('in users'); - if (typeof user.challengesHash !== 'undefined') { - var oldChallenges = Object.keys(user.challengesHash).filter(function (challenge) { - return user.challengesHash[challenge]; - }).map(function (data) { - return ({ - challengeNum: data, - timeStamp: user.challengesHash[data] - }); - }); - oldChallenges.forEach(function (challenge) { - user.progressTimestamps.push(challenge.timeStamp); - }); - newChallenges = newChallenges.filter(function (elem) { - return elem.newId; - }); - console.log(newChallenges); - } - }); +var stream = User.find( { needsMigration: true }).batchSize(10000).stream(); +stream.on('data', function(user) { + console.log('test'); + user.needsMigration = true; + user.save(); +}).on('error', function(err) { + console.log(err); +}).on('close', function() { + console.log('done with set'); }); + //console.log(typeof(user.challengesHash)); + //if (user.challengesHash && typeof(user.challengesHash) === Object) { + // var oldChallenges = Object.keys(user.challengesHash).filter(function (challenge) { + // console.log(challenge); + // return user.challengesHash[challenge]; + // }).map(function (data) { + // return ({ + // challengeNum: data, + // timeStamp: user.challengesHash[data] + // }); + // }); + // oldChallenges.forEach(function (challenge) { + // user.progressTimestamps.push(challenge.timeStamp); + // }); + // newChallenges = newChallenges.filter(function (elem) { + // return elem.newId; + // }); + // console.log(newChallenges); + //}); +//}); From 11548bfcf90cac482bac0e08d6b414c13541d2d8 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 13 Apr 2015 02:41:42 -0400 Subject: [PATCH 109/125] add checkbox cross off effect to challenges and reword pairwise bonfire --- public/css/main.less | 4 ++++ public/js/main.js | 9 +++++++++ seed_data/bonfires.json | 8 ++++---- views/coursewares/showVideo.jade | 6 +++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index 3b9f63a04e..4695c29419 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -910,6 +910,10 @@ iframe.iphone { text-decoration: underline; } +.step-text { + margin-left: -10px; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/public/js/main.js b/public/js/main.js index 14db8f7f1b..47a533ad33 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -30,6 +30,15 @@ $(document).ready(function() { .addClass('animated fadeInDown'); }); + $('.challenge-list-checkbox').on('change', function() { + if ($(this).is(":checked")) { + $(this).parent().parent().children('.step-text').addClass('strikethrough text-primary'); + } + if (!$(this).is(":checked")) { + $(this).parent().parent().children('.step-text').removeClass('strikethrough text-primary'); + } + }); + function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash, bonfireName) { $('#complete-bonfire-dialog').modal('show'); // Only post to server if there is an authenticated user diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 444878ff01..71b66427db 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -495,7 +495,7 @@ "assert.deepEqual(steamroller([[['a']], [['b']]]), ['a', 'b'], 'should flatten nested arrays');", "assert.deepEqual(steamroller([1, [2], [3, [[4]]]]), [1, 2, 3, 4], 'should flatten nested arrays');", "assert.deepEqual(steamroller([1, [], [3, [[4]]]]), [1, 3, 4], 'should work with empty arrays');" - ], + ], "MDNlinks" : ["Array.isArray()"] }, { @@ -519,12 +519,12 @@ "difficulty": "2.16", "description": [ "Return the sum of all indices of elements of 'arr' that can be paired with one other element to form a sum that equals the value in the second argument 'arg'. If multiple sums are possible, return the smallest sum. Once an element has been used, it cannot be reused to pair with another.", - "For example, pairwise([1, 4, 2, 3, 0, 5], 5) should return 15 because every element can be paired with another element to equal 5.", + "For example, pairwise([1, 4, 2, 3, 0, 5], 7) should return 11 because 4, 2, 3 and 5 can be paired with each other to equal 7.", "pairwise([1, 3, 2, 4], 4) would only equal 1, because only the first two elements can be paired to equal 4, and the first element has an index of 0!" ], - "challengeSeed": "function pairwise(arr, arg) {\n return arg;\n}\n\npairwise([1,4,2,3,0,5], 5);", + "challengeSeed": "function pairwise(arr, arg) {\n return arg;\n}\n\npairwise([1,4,2,3,0,5], 7);", "tests": [ - "expect(pairwise([1, 4, 2, 3, 0, 5], 5)).to.equal(15);", + "expect(pairwise([1, 4, 2, 3, 0, 5], 7)).to.equal(11);", "expect(pairwise([1, 3, 2, 4], 4)).to.equal(1);", "expect(pairwise([1,1,1], 2)).to.equal(1);", "expect(pairwise([0, 0, 0, 0, 1, 1], 1)).to.equal(10);", diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 901f11bfbe..96e0f7a526 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -7,7 +7,11 @@ block content h4 ol for step in details - li!= step + .row + li + .col-xs-2 + input(type='checkbox' class='challenge-list-checkbox') + .col-xs-10.step-text!= step .col-xs-12.col-sm-12.col-md-8 .embed-responsive.embed-responsive-16by9 iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') From 96ac90274da1f67755d09f9d6883c76549829fac Mon Sep 17 00:00:00 2001 From: brndnb Date: Mon, 13 Apr 2015 21:30:08 -0400 Subject: [PATCH 110/125] Update User model to make migration easier and complete user migration script --- models/User.js | 2 - seed_data/userMigration.js | 81 +++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/models/User.js b/models/User.js index 8d089e1c89..cceb4ef65a 100644 --- a/models/User.js +++ b/models/User.js @@ -8,7 +8,6 @@ var userSchema = new mongoose.Schema({ email: { type: String, lowercase: true, - unique: true, trim: true, sparse: true }, @@ -23,7 +22,6 @@ var userSchema = new mongoose.Schema({ profile: { username: { type: String, - unique: true, sparse: true, lowercase: true, trim: true diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js index 38e74f5cb4..696ddb1deb 100644 --- a/seed_data/userMigration.js +++ b/seed_data/userMigration.js @@ -1,42 +1,61 @@ require('dotenv').load(); var mongodb = require('mongodb'), - MongoClient = mongodb.MongoClient, - Server = require('mongodb').Server, + User = require('../models/User.js'), newChallenges = require('./challengeMapping.json'), secrets = require('../config/secrets'); + mongoose = require('mongoose'); -var mongoClient = new MongoClient(new Server('localhost', 27017), {native_parser: true}); -var mongoose = require('mongoose'); mongoose.connect(secrets.db); -var stream = User.find( { needsMigration: true }).batchSize(10000).stream(); -stream.on('data', function(user) { - console.log('test'); - user.needsMigration = true; - user.save(); -}).on('error', function(err) { +var i = 1; +var stream = User.find({}).skip(0).limit(0).batchSize(1000).stream(); + +stream.on('data', function (user) { + if (user.challengesHash) { + this.pause(); + console.log(i++); + user.needsMigration = false; + var oldChallenges = Object.keys(user.challengesHash).filter(function (key) { + if (user.challengesHash[key]) { + user.progressTimestamps.push(user.challengesHash[key] * 1000); + } + return user.challengesHash[key]; + }); + + newChallenges.forEach(function (challenge) { + if (oldChallenges.indexOf(challenge.oldNumber) !== -1 && challenge.newId) { + user.completedCoursewares.push({ + _id: challenge.newId, + completedDate: user.challengesHash[challenge.oldNumber] * 1000 + }); + } + }); + + user.completedCoursewares.forEach(function (course) { + var indexOfCourse = user.uncompletedCoursewares.indexOf(course._id) !== -1; + if (indexOfCourse !== -1) { + user.uncompletedCoursewares.splice(indexOfCourse, 1); + } + }); + + user.completedBonfires.forEach(function (bonfire) { + bonfire.completedDate = bonfire.completedDate * 1000; + user.progressTimestamps.push(bonfire.completedDate); + }); + } + + var self = this; + user.save(function(err) { + if (err) { + console.log('woops'); + } + self.resume(); + }); +}).on('error', function (err) { console.log(err); -}).on('close', function() { +}).on('close', function () { console.log('done with set'); + stream.destroy(); + process.exit(0); }); - //console.log(typeof(user.challengesHash)); - //if (user.challengesHash && typeof(user.challengesHash) === Object) { - // var oldChallenges = Object.keys(user.challengesHash).filter(function (challenge) { - // console.log(challenge); - // return user.challengesHash[challenge]; - // }).map(function (data) { - // return ({ - // challengeNum: data, - // timeStamp: user.challengesHash[data] - // }); - // }); - // oldChallenges.forEach(function (challenge) { - // user.progressTimestamps.push(challenge.timeStamp); - // }); - // newChallenges = newChallenges.filter(function (elem) { - // return elem.newId; - // }); - // console.log(newChallenges); - //}); -//}); From 77b5265fdeabff52c670d397266da187ab3f1491 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Mon, 13 Apr 2015 21:56:51 -0500 Subject: [PATCH 111/125] start creating the basejump instructions --- seed_data/coursewares.json | 59 +++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 3a427acecc..4766748012 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -650,7 +650,64 @@ "difficulty": 2.01, "challengeSeed": "123488494", "description": [ - + "This Basejump is a great opportunity to pair program with a friend.", + "We'll build this Basejump on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.", + "If you don't already have Cloud 9 account, create one now at http://c9.io.", + "Now let's get your development environment ready for a new Angular-Fullstack application provided by Yeoman.", + "Open up http://c9.io and sign in to your account.", + "Click on Create New Workspace at the top right of the c9.io page.", + "Click on the Create a new workspace in the dropdown after you select the button.", + "Name your workspace to match your project name that you are working on.", + "Choose Node.js in the selection area below the name field", + "Click the Create button", + "Wait for the workspace to finish processing and select it on the left sidebar, below the Create New Workspace button", + "Click the Start Editing button.", + "In the lower right hand corner you should see a terminal window. In this window use the following commands. You don't need to know what these mean at this point.", + "rm -rf * && npm install -g yo grunt grunt-cli generator-angular-fullstack && yo angular-fullstack", + "Yeoman will prompt you to answer some questions. Answer them like this:", + "What would you like to write scripts with? JavaScript", + "What would you like to write markup with? HTML", + "What would you like to write stylesheets with? CSS", + "What Angular router would you like to use? ngRoute", + "Would you like to include Bootstrap? Yes", + "Would you like to include UI Bootstrap? Yes", + "Would you like to use MongoDB with Mongoose for data modeling? Yes", + "Would you scaffold out an authentication boilerplate? Yes", + "Would you like to include additional oAuth strategies? Twitter", + "Would you like to use socket.io? No", + "May bower anonymously report usage statistics to improve the tool over time? (Y/n) Y", + "You may get an error similar to ERR! EEXIST, open ‘/home/ubuntu/.npm. This is caused when Cloud9 runs out of memory and kills an install. If you get this, simply rerun yo angular-fullstack. You will then be asked a few questions regarding the re-install. Answer them as follows:", + "Existing .yo-rc configuration found, would you like to use it? (Y/n) Y", + "Overwrite client/favicon.ico? (Ynaxdh) Y", + "To finish the installation run the commands: bower install && npm install", + "To start MongoDB, run the following commands in your terminal: mkdir data && echo 'mongod --bind_ip=$IP --dbpath=data --nojournal --rest \"$@\"' > mongod && chmod a+x mongod && ./mongod", + "You will want to open up a new terminal to work from by clicking on the + icon and select New Terminal", + "Start the application by running the following command in your new terminal window: grunt serve", + "Wait for the following message to appear: xdg-open: no method available for opening 'http://localhost:8080' . Now you can open the internal Cloud9 browser. To launch the browser select Preview in the toolbar then select the dropdown option Preview Running Application.", + "Turn the folder in which your application is running into a Git repository by running the following commands: git init && git add . && git commit -am 'initial commit'.", + "Create a new Github repository by signing in to http://github.com and clicking on the + button next to your username in the upper-right hand side of your screen, then selecting \"New Repository\"", + "Enter a project name, then click the \"Create Repository\" button.", + "Find the \"...or push an existing repository from the command line\" section and click the Copy to Clipboard button beside it.", + "Paste the commands from your clipboard into the Cloud9 terminal prompt. This will push your changes to your repository on Cloud 9 up to Github.", + "Check back on your Github profile to verify the changes were successfully pushed up to Github.", + "Now let's push your code to Heroku. If you don't already have a Heroku account, create one at http://heroku.com. You shouldn't be charged for anything, but you will need to add your credit card information to your Heroku before you will be able to use Heroku's free MongoLab add on.", + "Before you publish to Heroku, you should free up as much memory as possible on Cloud9. In each of the Cloud9 terminal prompt tabs where MongoDB and Grunt are running, press the control + c hotkey to shut down these processes.", + "Run the following command in a Cloud9 terminal prompt tab: npm install grunt-contrib-imagemin --save-dev && npm install --save-dev && heroku login. At this point, the terminal will prompt you to log in to Heroku from the command line.", + "Now run yo angular-fullstack:heroku. You can choose a name for your Heroku project, or Heroku will create a random one for you. You can choose whether you want to deploy to servers the US or the EU.", + "Set the config flag for your Heroku environment and add MongoLab for your MongoDB instance by running the following command: cd ~/workspace/dist && heroku config:set NODE_ENV=production && heroku addons:add mongolab.", + "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", + "You can push these new commits to Github by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", + "Now that you've configured your environment, here are the specific User Stories you should implement for this Basejump:,", + "As an authenticated user, I should be able to keep my polls and come back later to access them.", + "As an authenticated user, I should be able to share my polls with my friends.", + "As an authenticated user, I should be able to see the aggregate results of my polls.", + "As an authenticated user, I should be able to delete polls that I decide I don't want anymore.", + "As an authenticated user, I should be able to create a poll with any number of possible items.", + "Here are some bonus User Stories you can try to implement once you've finished the above User Stories:", + "As an unauthenticated user, I should be able to see everyone's polls, but not be able to vote.", + "As an unauthenticated or authenticated user, I should be able to see the in chart form. (This could be implemented using Chart.js or Google Charts.)", + "As an authenticated user, if I don't like the options on a poll I can create a new option.", + "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it." ], "challengeType": 4, "tests": [] From 7912dc722d87da9792d7eef21f9387ee47548dcd Mon Sep 17 00:00:00 2001 From: brndnb Date: Tue, 14 Apr 2015 11:15:06 -0400 Subject: [PATCH 112/125] Update bonfire challenge in courseware to encourage people not to try to complete all bonfires back to back, and not to worry if new bonfires are added. --- seed_data/coursewares.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 3a427acecc..5b69c14fa9 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -585,7 +585,7 @@ "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!\"", + "We have a special chat room for people ready to pair program. Go to our Slack chatroom and navigate to the # letspair channel and type \"Hello Pair Programmers!\"", "If someone is available, they will be your \"pair\" - the person you pair programming with.", "If no one gets back to you in the first few minutes, don't worry. There will be lots of opportunities to pair program in the future.", "If someone does get back to you, private message them and ask for the email address they used to register Screen Hero.", @@ -597,7 +597,9 @@ "Once you you finish pair programming, end the session in Screen Hero session.", "Congratulations! You have completed your first pair programming session.", "Pair program as much as possible with different campers until you've completed all the Bonfire, Zipline and Basejump challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!", - "Mark this challenge as complete and move on to the Bonfires." + "Mark this challenge as complete and move on to the Bonfires.", + "Keep in mind, the Bonfires are a significant challenge in and of themselves. You are not expected to complete them in one sitting, or to complete them before moving on to our next challenges. Mix them in as you keep learning and have fun!", + "In order to participate in our non-profit projects you will need to have successfully completed all \"3 flame\" bonfires and below. Lasly, completing bonfires is not meant to be a sisyphean task. You won't be required to complete new bonfires that are of lower difficuly than you've already completed!" ], "challengeType": 2, "tests": [] From d1e417562425bf16478c20ab6954a7d09f9eac11 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 14 Apr 2015 13:43:10 -0700 Subject: [PATCH 113/125] additional work on basejumps --- seed_data/coursewares.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 4766748012..4c634a774e 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -650,7 +650,7 @@ "difficulty": 2.01, "challengeSeed": "123488494", "description": [ - "This Basejump is a great opportunity to pair program with a friend.", + "For this Basejump challenge, you'll build a fully functional voting app. By the time you've finished, your app will function similarly to this: http://voteplex.herokuapp.com/. This Basejump is a great opportunity to pair program with a friend.", "We'll build this Basejump on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.", "If you don't already have Cloud 9 account, create one now at http://c9.io.", "Now let's get your development environment ready for a new Angular-Fullstack application provided by Yeoman.", From d7a698f629c56a9f70be2c2b5c7a37082d9da3b6 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 14 Apr 2015 19:39:25 -0700 Subject: [PATCH 114/125] fix the field guide --- views/field-guide/show.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/field-guide/show.jade b/views/field-guide/show.jade index ea20ce564f..574abeffe8 100644 --- a/views/field-guide/show.jade +++ b/views/field-guide/show.jade @@ -15,7 +15,7 @@ block content .spacer .col-xs-12.col-sm-6.col-sm-offset-3 .text-center - .next-fieldGuide-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) + .next-field-guide-button.btn.btn-primary.btn-big.btn-block Next article (ctrl + enter) .ten-pixel-break #showAllButton.btn.btn-info.btn-big.btn-block Show me all articles .spacer From e0aa084c7a04d36d1e00d01b4ee9000a8fee652b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 14 Apr 2015 20:12:47 -0700 Subject: [PATCH 115/125] finish getting field-guide working and improve zipline show view --- public/css/main.less | 2 ++ public/js/main.js | 4 +--- seed_data/coursewares.json | 7 +++---- views/coursewares/showVideo.jade | 10 +++++----- views/coursewares/showZiplineOrBasejump.jade | 6 +++++- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index 4695c29419..cfbc54eeb6 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -912,6 +912,8 @@ iframe.iphone { .step-text { margin-left: -10px; + line-height: 120%; + padding-bottom: 10px; } //uncomment this to see the dimensions of all elements outlined in red diff --git a/public/js/main.js b/public/js/main.js index 47a533ad33..62a0a8473e 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -112,6 +112,7 @@ $(document).ready(function() { $('#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) { @@ -184,7 +185,6 @@ $(document).ready(function() { } }); - $('.all-challenges').on('click', function() { $('#show-all-dialog').modal('show'); }); @@ -198,7 +198,6 @@ $(document).ready(function() { window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); }); - // Bonfire instructions functions $('#more-info').on('click', function() { ga('send', 'event', 'Challenge', 'more-info', challengeName); @@ -241,7 +240,6 @@ $(document).ready(function() { }; $('#upvote').on('click', upvoteHandler); - var storySubmitButtonHandler = function storySubmitButtonHandler() { var link = $('#story-url').val(); diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index fef19f2ca4..2c989b173d 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -656,14 +656,13 @@ "We'll build this Basejump on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.", "If you don't already have Cloud 9 account, create one now at http://c9.io.", "Now let's get your development environment ready for a new Angular-Fullstack application provided by Yeoman.", - "Open up http://c9.io and sign in to your account.", - "Click on Create New Workspace at the top right of the c9.io page.", - "Click on the Create a new workspace in the dropdown after you select the button.", + "Open up http://c9.io and sign in to your account.", + "Click on Create New Workspace at the top right of the c9.io page, then click on the \"Create a new workspace\" popup that appears below it the button after you click on it.", "Name your workspace to match your project name that you are working on.", "Choose Node.js in the selection area below the name field", "Click the Create button", "Wait for the workspace to finish processing and select it on the left sidebar, below the Create New Workspace button", - "Click the Start Editing button.", + "Click the \"Start Editing\" button.", "In the lower right hand corner you should see a terminal window. In this window use the following commands. You don't need to know what these mean at this point.", "rm -rf * && npm install -g yo grunt grunt-cli generator-angular-fullstack && yo angular-fullstack", "Yeoman will prompt you to answer some questions. Answer them like this:", diff --git a/views/coursewares/showVideo.jade b/views/coursewares/showVideo.jade index 96e0f7a526..98c35261c7 100644 --- a/views/coursewares/showVideo.jade +++ b/views/coursewares/showVideo.jade @@ -7,11 +7,11 @@ block content h4 ol for step in details - .row - li - .col-xs-2 - input(type='checkbox' class='challenge-list-checkbox') - .col-xs-10.step-text!= step + .row + li + .col-xs-2 + input(type='checkbox' class='challenge-list-checkbox') + .col-xs-10.step-text!= step .col-xs-12.col-sm-12.col-md-8 .embed-responsive.embed-responsive-16by9 iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') diff --git a/views/coursewares/showZiplineOrBasejump.jade b/views/coursewares/showZiplineOrBasejump.jade index ed54ecb721..ffe853d4ba 100644 --- a/views/coursewares/showZiplineOrBasejump.jade +++ b/views/coursewares/showZiplineOrBasejump.jade @@ -7,7 +7,11 @@ block content h4 ol for step in details - li!= step + .row + li + .col-xs-2 + input(type='checkbox' class='challenge-list-checkbox') + .col-xs-10.step-text!= step .col-xs-12.col-sm-12.col-md-8 .embed-responsive.embed-responsive-16by9 iframe.embed-responsive-item(src='//player.vimeo.com/video/#{video}') From e033f224bc1d94602172c3c4ac128ab4f0aec84d Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 14 Apr 2015 20:20:38 -0700 Subject: [PATCH 116/125] fix challenge map to redirect to nonprofits --- seed_data/nonprofits.json | 20 -------------------- views/challengeMap/show.jade | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/seed_data/nonprofits.json b/seed_data/nonprofits.json index f208c4a510..ff432373ef 100644 --- a/seed_data/nonprofits.json +++ b/seed_data/nonprofits.json @@ -47,26 +47,6 @@ "estimatedHours": 100, "currentStatus": "completed" }, - { - "id": "bd6274d8c464cbafaeb4bdef", - "requestedDeliverables": [ - "other" - ], - "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": "", - "estimatedHours": 200, - "currentStatus": "completed" - }, { "id": "bd1326d9c245cbafaeb4bdef", "requestedDeliverables": [ diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 0491412974..8243874525 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -68,4 +68,4 @@ block content h3.negative-15 ul li - a(href="/nonprofits/index") Browse our nonprofit projects + a(href="/nonprofits/directory") Browse our nonprofit projects From ddb88643e05de4dd94ff47e18c0eba47dda9145b Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Tue, 14 Apr 2015 21:19:05 -0700 Subject: [PATCH 117/125] Nathan's updates to the userMigration script' --- seed_data/coursewares.json | 2 +- seed_data/userMigration.js | 17 ++++++++++++++++- views/partials/meta.jade | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 2c989b173d..123f31d6be 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -599,7 +599,7 @@ "Pair program as much as possible with different campers until you've completed all the Bonfire, Zipline and Basejump challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!", "Mark this challenge as complete and move on to the Bonfires.", "Keep in mind, the Bonfires are a significant challenge in and of themselves. You are not expected to complete them in one sitting, or to complete them before moving on to our next challenges. Mix them in as you keep learning and have fun!", - "In order to participate in our non-profit projects you will need to have successfully completed all \"3 flame\" bonfires and below. Lasly, completing bonfires is not meant to be a sisyphean task. You won't be required to complete new bonfires that are of lower difficuly than you've already completed!" + "In order to participate in our non-profit projects you will need to have successfully completed all \"3 flame\" bonfires and below. Lastly, completing bonfires is not meant to be a sisyphean task. You won't be required to complete new bonfires that are of lower difficuly than you've already completed!" ], "challengeType": 2, "tests": [] diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js index 696ddb1deb..c02a18385b 100644 --- a/seed_data/userMigration.js +++ b/seed_data/userMigration.js @@ -9,7 +9,22 @@ var mongodb = require('mongodb'), mongoose.connect(secrets.db); var i = 1; -var stream = User.find({}).skip(0).limit(0).batchSize(1000).stream(); +var stream = User.find({}).skip(0).limit(0).stream(); + +stream.on('data', function(user) { + console.log(i++); + user.save() +}) +.on('error', function (err) { + console.log(err); +}).on('close', function () { + console.log('done with set'); + stream.destroy(); +}); + +i = 1; + +var stream = User.find({'user.needsMigration': true}).skip(0).limit(0).stream(); stream.on('data', function (user) { if (user.challengesHash) { diff --git a/views/partials/meta.jade b/views/partials/meta.jade index ebaba6a9b2..6a061404fb 100644 --- a/views/partials/meta.jade +++ b/views/partials/meta.jade @@ -21,7 +21,7 @@ meta(name="twitter:creator", content="@freecodecamp") meta(name="twitter:url", content="http://www.freecodecamp.com") meta(name="twitter:site", content="@freecodecamp") meta(name="twitter:card", content="summary_large_image") -meta(name="twitter:image:src", content="https://s3.amazonaws.com/freecodecamp/challenges.png") +meta(name="twitter:image:src", content="https://s3.amazonaws.com/freecodecamp/1000-hours-of-coding.jpg") meta(name="twitter:title", content="Free Code Camp: a community of busy people learning to code") meta(name="twitter:description", content="We're a community of busy people learning to code by collaborating on projects for nonprofits. Learn Full-stack JavaScript, build a portfolio, and get great references with our online coding bootcamp.") meta(content="a40ee5d5dba3bb091ad783ebd2b1383f", name="p:domain_verify") @@ -54,4 +54,4 @@ link(rel="mstile", sizes="310x310", href="https://s3.amazonaws.com/freecodecamp/ link(rel="mstile", sizes="310x150", href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-310x150.png") link(rel="mstile", sizes="70x70", href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-70x70.png") link(rel="favicon", href="https://s3.amazonaws.com/freecodecamp/favicons/favicon.ico") -link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicons/favicon.ico') \ No newline at end of file +link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicons/favicon.ico') From 3748356079e0ae8f8ede0669bf6f95fda8dc46a9 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Wed, 15 Apr 2015 00:53:36 -0400 Subject: [PATCH 118/125] Update user migration script to ensure user model validity. --- seed_data/userMigration.js | 158 ++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 63 deletions(-) diff --git a/seed_data/userMigration.js b/seed_data/userMigration.js index c02a18385b..ffad7be0c4 100644 --- a/seed_data/userMigration.js +++ b/seed_data/userMigration.js @@ -1,6 +1,5 @@ require('dotenv').load(); var mongodb = require('mongodb'), - User = require('../models/User.js'), newChallenges = require('./challengeMapping.json'), secrets = require('../config/secrets'); @@ -8,69 +7,102 @@ var mongodb = require('mongodb'), mongoose.connect(secrets.db); -var i = 1; -var stream = User.find({}).skip(0).limit(0).stream(); +function userModelAssurity(cb) { + console.log('userModelAssurity'); + var i = 1; + var stream = User.find({}).skip(0).limit(0).batchSize(20000).stream(); -stream.on('data', function(user) { - console.log(i++); - user.save() -}) -.on('error', function (err) { - console.log(err); -}).on('close', function () { + stream.on('data', function (user) { + console.log(i++); + this.pause(); + user.needsMigration = true; + user.save(function (err) { + if (err) { + console.log('woops'); + } + this.resume(); + }.bind(this)); + }) + .on('error', function (err) { + console.log(err); + }).on('close', function () { + console.log('done with set'); + stream.destroy(); + cb(); + }); +} + +function migrateIt() { + console.log('migrateIt'); + var dones = 0; + var done = function() { + dones++; + if (dones === 2) { + process.exit(0); + } + if (dones === 1) { + userModelMigration(done); + } + }; + console.log('calling userModelAssurity'); + userModelAssurity(done); +} + +function userModelMigration(cb) { + + var i = 1; + + var stream = User.find({needsMigration: true}).skip(0).limit(0) + .batchSize(20000).stream(); + + stream.on('data', function (user) { + console.log(i++); + if (user.challengesHash) { + this.pause(); + user.needsMigration = false; + var oldChallenges = Object.keys(user.challengesHash).filter(function (key) { + if (user.challengesHash[key]) { + user.progressTimestamps.push(user.challengesHash[key] * 1000); + } + return user.challengesHash[key]; + }); + + newChallenges.forEach(function (challenge) { + if (oldChallenges.indexOf(challenge.oldNumber) !== -1 && challenge.newId) { + user.completedCoursewares.push({ + _id: challenge.newId, + completedDate: user.challengesHash[challenge.oldNumber] * 1000 + }); + } + }); + + user.completedCoursewares.forEach(function (course) { + var indexOfCourse = user.uncompletedCoursewares.indexOf(course._id) !== -1; + if (indexOfCourse !== -1) { + user.uncompletedCoursewares.splice(indexOfCourse, 1); + } + }); + + user.completedBonfires.forEach(function (bonfire) { + bonfire.completedDate = bonfire.completedDate * 1000; + user.progressTimestamps.push(bonfire.completedDate); + }); + } + + var self = this; + user.save(function (err) { + if (err) { + console.log('woops'); + } + self.resume(); + }); + }).on('error', function (err) { + console.log(err); + }).on('close', function () { console.log('done with set'); stream.destroy(); -}); - -i = 1; - -var stream = User.find({'user.needsMigration': true}).skip(0).limit(0).stream(); - -stream.on('data', function (user) { - if (user.challengesHash) { - this.pause(); - console.log(i++); - user.needsMigration = false; - var oldChallenges = Object.keys(user.challengesHash).filter(function (key) { - if (user.challengesHash[key]) { - user.progressTimestamps.push(user.challengesHash[key] * 1000); - } - return user.challengesHash[key]; - }); - - newChallenges.forEach(function (challenge) { - if (oldChallenges.indexOf(challenge.oldNumber) !== -1 && challenge.newId) { - user.completedCoursewares.push({ - _id: challenge.newId, - completedDate: user.challengesHash[challenge.oldNumber] * 1000 - }); - } - }); - - user.completedCoursewares.forEach(function (course) { - var indexOfCourse = user.uncompletedCoursewares.indexOf(course._id) !== -1; - if (indexOfCourse !== -1) { - user.uncompletedCoursewares.splice(indexOfCourse, 1); - } - }); - - user.completedBonfires.forEach(function (bonfire) { - bonfire.completedDate = bonfire.completedDate * 1000; - user.progressTimestamps.push(bonfire.completedDate); - }); - } - - var self = this; - user.save(function(err) { - if (err) { - console.log('woops'); - } - self.resume(); + cb(); }); -}).on('error', function (err) { - console.log(err); -}).on('close', function () { - console.log('done with set'); - stream.destroy(); - process.exit(0); -}); +} + +migrateIt(); From 22be5b90baf9cd97737e811f058413935ccc141f Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Wed, 15 Apr 2015 00:12:16 -0700 Subject: [PATCH 119/125] more improvements to the basejump challenges --- seed_data/coursewares.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 123f31d6be..8d4fda165f 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -677,7 +677,7 @@ "Would you like to include additional oAuth strategies? Twitter", "Would you like to use socket.io? No", "May bower anonymously report usage statistics to improve the tool over time? (Y/n) Y", - "You may get an error similar to ERR! EEXIST, open ‘/home/ubuntu/.npm. This is caused when Cloud9 runs out of memory and kills an install. If you get this, simply rerun yo angular-fullstack. You will then be asked a few questions regarding the re-install. Answer them as follows:", + "You may get an error similar to ERR! EEXIST, open ‘/home/ubuntu/.npm. This is caused when Cloud9 runs out of memory and kills an install. If you get this, simply re-run this process with the command yo angular-fullstack. You will then be asked a few questions regarding the re-install. Answer them as follows:", "Existing .yo-rc configuration found, would you like to use it? (Y/n) Y", "Overwrite client/favicon.ico? (Ynaxdh) Y", "To finish the installation run the commands: bower install && npm install", From 6341b1158bc8a61a8ba8488b843eaf4f8402d46d Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Wed, 15 Apr 2015 18:20:20 -0400 Subject: [PATCH 120/125] add eslint task to gulp to let us know how we need to write better code --- gulpfile.js | 13 ++++++++++--- package.json | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 70c59bf1fe..90426b048e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,7 +5,8 @@ var gulp = require('gulp'), sync = require('browser-sync'), reload = sync.reload, inject = require('gulp-inject'), - reloadDelay = 1000; + reloadDelay = 1000, + eslint = require('gulp-eslint'); var paths = { server: './app.js', @@ -54,11 +55,17 @@ gulp.task('sync', ['serve'], function() { sync.init(null, { proxy: 'http://localhost:3000', logLeval: 'debug', - files: ['public/**/*'], + files: ['public/js/lib/*/*.{js, jsx}'], port: 3001, open: true, reloadDelay: reloadDelay }); }); -gulp.task('default', ['serve', 'sync']); +gulp.task('lint', function() { + return gulp.src(['public/js/lib/**/*']) + .pipe(eslint()) + .pipe(eslint.format()); +}); + +gulp.task('default', ['lint', 'serve', 'sync']); diff --git a/package.json b/package.json index f3209e0e29..787a19c547 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "browser-sync": "^1.8.1", "chai": "^1.10.0", "gulp": "^3.8.8", + "gulp-eslint": "^0.9.0", "gulp-inject": "^1.0.2", "gulp-nodemon": "^1.0.4", "mocha": "^2.0.1", From b9372b9af6809b7cdd3bd118d0d0be9eebab231a Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 17 Apr 2015 00:39:48 -0400 Subject: [PATCH 121/125] Resolve merge conflicts using Webstorm's merge integration tool. v2 --- app.js | 22 +++++----------------- controllers/resources.js | 7 ------- controllers/story.js | 10 ---------- gulpfile.js | 2 +- 4 files changed, 6 insertions(+), 35 deletions(-) diff --git a/app.js b/app.js index 0a5f9e2359..d3ebb53dbb 100644 --- a/app.js +++ b/app.js @@ -370,11 +370,11 @@ app.post( passportConf.isAuthenticated, contactController.postDoneWithFirst100Hours ); -app.get( - '/nonprofit-project-instructions', - passportConf.isAuthenticated, - resourcesController.nonprofitProjectInstructions -); +//app.get( +// '/nonprofit-project-instructions', +// passportConf.isAuthenticated, +// resourcesController.nonprofitProjectInstructions +//); app.post( '/update-progress', passportConf.isAuthenticated, @@ -521,18 +521,6 @@ app.post( storyController.upvote ); -/** - * Challenge related routes - */ -app.get( - '/challenges/', - challengesController.returnNextChallenge -); -app.get( - '/challenges/:challengeNumber', - challengesController.returnChallenge -); - app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); diff --git a/controllers/resources.js b/controllers/resources.js index b8171ebf21..f690ce4d9e 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -149,13 +149,6 @@ module.exports = { var date1 = new Date("10/15/2014"); var date2 = new Date(); - - livePairProgramming: function(req, res) { - res.render('resources/live-pair-programming', { - title: 'Live Pair Programming' - }); - }, - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); var announcements = resources.announcements; diff --git a/controllers/story.js b/controllers/story.js index fc0e58862c..34b8693890 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -359,16 +359,6 @@ exports.storySubmission = function(req, res, next) { }); }; - story.save(function(err) { - if (err) { - return next(err); - } - res.send(JSON.stringify({ - storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() - })); - }); -}; - exports.commentSubmit = function(req, res, next) { var data = req.body.data; if (req.user._id.toString() !== data.author.userId.toString()) { diff --git a/gulpfile.js b/gulpfile.js index 90426b048e..4faab8e3ad 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -68,4 +68,4 @@ gulp.task('lint', function() { .pipe(eslint.format()); }); -gulp.task('default', ['lint', 'serve', 'sync']); +gulp.task('default', ['serve', 'sync']); From f1acc59f8204d9feb01e6e84204bbb80047b1f4a Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 17 Apr 2015 01:39:55 -0400 Subject: [PATCH 122/125] Fix bug where users could get points for completing the same challenge. --- controllers/resources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/resources.js b/controllers/resources.js index f690ce4d9e..5b5026bd17 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -98,7 +98,7 @@ module.exports = { chat: function chat(req, res) { if (req.user && req.user.progressTimestamps.length > 5) { - res.redirect('http://gitter.im/freecodecamp/freecodecamp'); + res.redirect('http://freecode.slack.com'); } else { res.render('resources/chat', { title: "Watch us code live on Twitch.tv" From edefd90268f767fce2009f8f8b6e66444afc9653 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 17 Apr 2015 01:40:32 -0400 Subject: [PATCH 123/125] Fix bug where users could get points for completing the same challenge. --- controllers/courseware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/courseware.js b/controllers/courseware.js index 08cb36a680..72b23b3cf6 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -258,7 +258,7 @@ exports.completedCourseware = function (req, res, next) { }); 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); } From dbc99eafb63dae2cf2efb7d425e13efe97df0ab1 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 17 Apr 2015 01:41:01 -0400 Subject: [PATCH 124/125] Update chat link and copy. --- seed_data/coursewares.json | 15 ++++++++------- views/partials/navbar.jade | 5 ++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 8d4fda165f..07e2ada181 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -41,16 +41,17 @@ "_id": "bd7125d8c441eddfaeb5bd0f", "name": "Join Our Chat Room", "difficulty": 0.03, - "challengeSeed": "114627322", + "challengeSeed": "124555254", "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 and get help.", - "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!\".", + "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.", + "Make sure your Free Code Camp account includes your email address. You can do this here: http://freecodecamp.com/account.", + "Click this link, which will email you an invite to Free Code Camp's Slack chat room: http://freecodecamp.com/api/slack.", + "Now check your email and click the link in the email from Slack", + "Complete the sign up process, then update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.", + "Now enter the general chat room and introduce yourself to our chat room by typing: \"hello world!\".", "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", - "You can also access this chatroom by clicking the \"Chat\" button in the upper right hand corner." + "You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner." ], "challengeType": 2, "tests": [] diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 3354c83e46..89ebadcb94 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -11,8 +11,7 @@ img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg.gz', alt='learn to code javascript at Free Code Camp logo') .collapse.navbar-collapse ul.nav.navbar-nav.navbar-right.hamburger-dropdown - li - a(href='/challenges') Challenges + li a(href='/map') Map @@ -21,7 +20,7 @@ a(href='/chat', target='_blank') Chat else li - a(href='/challenges/01') Chat + a(href='/challenges/join-our-chat-room') Chat li a(href='/stories/hot') News li From 04d9856d4fd88d897d08cf50bc24ea8d038bba65 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 17 Apr 2015 01:48:06 -0400 Subject: [PATCH 125/125] Remove vomit route. --- app.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app.js b/app.js index d3ebb53dbb..5c6835918c 100644 --- a/app.js +++ b/app.js @@ -675,10 +675,6 @@ app.get( } ); -app.get('/induce-vomiting', function(req, res, next) { - next(new Error('vomiting induced')); -}); - // put this route last app.get( '/:username',