From 2ec36848253763b864b1fa54c13bbdf01613631f Mon Sep 17 00:00:00 2001 From: Rex Schrader Date: Thu, 18 Jun 2015 10:01:43 -0700 Subject: [PATCH 01/20] Add w3.org reference for HTML Entities --- seed_data/bonfireMDNlinks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 9a9ea86741..bedd51b2db 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -12,6 +12,7 @@ var links = "Currying": "https://leanpub.com/javascript-allonge/read#pabc", "Smallest Common Multiple": "https://www.mathsisfun.com/least-common-multiple.html", "Permutations": "https://www.mathsisfun.com/combinatorics/combinations-permutations.html", + "HTML Entities": "http://dev.w3.org/html5/html-author/charref", // ========= GLOBAL OBJECTS "Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", From 7540fe935190cd88129e5ff04c2260c3ebbbc6ae Mon Sep 17 00:00:00 2001 From: Rex Schrader Date: Thu, 18 Jun 2015 10:30:06 -0700 Subject: [PATCH 02/20] Additional test, HTML Entities Link, Clarify Text --- seed_data/challenges/basic-bonfires.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/seed_data/challenges/basic-bonfires.json b/seed_data/challenges/basic-bonfires.json index 6033a8937e..5c62a354b1 100644 --- a/seed_data/challenges/basic-bonfires.json +++ b/seed_data/challenges/basic-bonfires.json @@ -1065,7 +1065,7 @@ "name": "Bonfire: Convert HTML Entities", "difficulty": "2.07", "description": [ - "Convert the characters \"&\", \"<\", \">\", '\"', and \"'\", in a string to their corresponding HTML entities.", + "Convert the characters \"&\", \"<\", \">\", '\"' (double quote), and \"'\" (apostrophe), in a string to their corresponding HTML entities.", "Remember to use RSAP if you get stuck. Try to pair program. Write your own code." ], "challengeSeed": [ @@ -1078,10 +1078,12 @@ ], "tests": [ "assert.strictEqual(convert('Dolce & Gabbana'), 'Dolce & Gabbana', 'should escape characters');", + "assert.strictEqual('Submit', '<input type="submit">Submit</input>', 'should escape characters');", "assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');" ], "MDNlinks": [ - "RegExp" + "RegExp", + "HTML Entities" ], "challengeType": 5, "nameCn": "", From 7a510d6bc626b830fbf6451757c6b78c80491047 Mon Sep 17 00:00:00 2001 From: Rex Schrader Date: Thu, 18 Jun 2015 10:33:34 -0700 Subject: [PATCH 03/20] Add w3.org reference for HTML Entities --- seed_data/bonfireMDNlinks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js index 9a9ea86741..bedd51b2db 100644 --- a/seed_data/bonfireMDNlinks.js +++ b/seed_data/bonfireMDNlinks.js @@ -12,6 +12,7 @@ var links = "Currying": "https://leanpub.com/javascript-allonge/read#pabc", "Smallest Common Multiple": "https://www.mathsisfun.com/least-common-multiple.html", "Permutations": "https://www.mathsisfun.com/combinatorics/combinations-permutations.html", + "HTML Entities": "http://dev.w3.org/html5/html-author/charref", // ========= GLOBAL OBJECTS "Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", From 60354d24e62779a09fb18a303beb3829f4f4eff4 Mon Sep 17 00:00:00 2001 From: Andrew Cay Date: Fri, 19 Jun 2015 17:47:21 -0700 Subject: [PATCH 04/20] Auto Save, Removed Stamps +more Removed stamps so code stays stored until code has been reset. --> Added a feature which returns if the storage has been saved since last edit. *codeStorage.hasSaved()* --> Changed "localBonfire" to *codeStorage* --- .../coursewaresJSFramework_0.0.6.js | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js index 16073e2554..c15a737e22 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js @@ -42,7 +42,7 @@ editor.setOption("extraKeys", { /* Local Storage Update System By Andrew Cay(Resto) - localBonfire: singleton object that contains properties and methods related to + codeStorage: singleton object that contains properties and methods related to dealing with the localStorage system. The keys work off of the variable challenge_name to make unique identifiers per bonfire @@ -50,54 +50,49 @@ editor.setOption("extraKeys", { Added anonymous version checking system incase of future updates to the system Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage */ -var localBonfire = { +var codeStorage = { version: 0.01, keyVersion:"saveVersion", - keyStamp: challenge_Name + 'Stamp', keyValue: challenge_Name + 'Val', - stampExpireTime: (1000 *60) *60 *24, - updateWait: 1500,// 1.5 seconds + updateWait: 2000,// 2 seconds updateTimeoutId: null }; -localBonfire.getEditorValue = function(){ - return localStorage.getItem(localBonfire.keyValue); +// Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere") +codeStorage.hasSaved = function(){ + return ( updateTimeoutId === null ); }; -localBonfire.getStampTime = function(){ - //localstorage always saves as strings. - return Number.parseInt( localStorage.getItem(localBonfire.keyStamp) ); +codeStorage.getEditorValue = function(){ + return localStorage.getItem(codeStorage.keyValue); }; -localBonfire.isAlive = function(){// returns true if IDE was edited within expire time - return ( Date.now() - localBonfire.getStampTime() < localBonfire.stampExpireTime ); -}; -localBonfire.updateStorage = function(){ +codeStorage.updateStorage = function(){ if(typeof(Storage) !== undefined) { - var stamp = Date.now(), - value = editor.getValue(); - localStorage.setItem(localBonfire.keyValue, value); - localStorage.setItem(localBonfire.keyStamp, stamp); + var value = editor.getValue(); + localStorage.setItem(codeStorage.keyValue, value); } else { + var debugging = false; if( debugging ){ console.log('no web storage'); } } - localBonfire.updateTimeoutId = null; + codeStorage.updateTimeoutId = null; }; // ANONYMOUS 1 TIME UPDATE VERSION (function(){ var savedVersion = localStorage.getItem('saveVersion'); if( savedVersion === null ){ - localStorage.setItem(localBonfire.keyVersion, localBonfire.version);//just write current version + localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version }else{ //do checking if not current version - if( savedVersion !== localBonfire.version ){ + if( savedVersion !== codeStorage.version ){ //update version } } })(); +/// Update local save when editor has changed editor.on('keyup', function(codMir, event){ - window.clearTimeout(localBonfire.updateTimeoutId); - localBonfire.updateTimeoutId = window.setTimeout(localBonfire.updateStorage, localBonfire.updateWait); + window.clearTimeout(codeStorage.updateTimeoutId); + codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait); }); var attempts = 0; @@ -107,7 +102,7 @@ if (attempts) { var resetEditor = function() { editor.setValue(allSeeds); - localBonfire.updateStorage(); + codeStorage.updateStorage(); }; var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { @@ -145,7 +140,7 @@ var allSeeds = ''; }); })(); -editorValue = (localBonfire.isAlive())? localBonfire.getEditorValue() : allSeeds; +editorValue = (codeStorage.isAlive())? codeStorage.getEditorValue() : allSeeds; myCodeMirror.setValue(editorValue); From fe1b3f4d2a3141326c7d8593d3e05e830b3e6cf1 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 19 Jun 2015 17:53:59 -0700 Subject: [PATCH 05/20] fix broken test from recent pull request --- seed/challenges/basic-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/basic-bonfires.json b/seed/challenges/basic-bonfires.json index 6a36355e62..11274ec1a7 100644 --- a/seed/challenges/basic-bonfires.json +++ b/seed/challenges/basic-bonfires.json @@ -1110,7 +1110,7 @@ "assert.strictEqual(convert('Sixty > twelve'), 'Sixty > twelve', 'should escape characters');", "assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in "quotation marks"', 'should escape characters');", "assert.strictEqual(convert(\"Shindler's List\"), 'Shindler's List', 'should escape characters');", - "assert.strictEqual('Submit', '<input type="submit">Submit</input>', 'should escape characters');", + "assert.strictEqual(convert('<>'), '<>', 'should escape characters');", "assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');" ], "MDNlinks": [ From 11305c152b93613d4390dd5f5c528d7519e735b1 Mon Sep 17 00:00:00 2001 From: Andrew Cay Date: Fri, 19 Jun 2015 18:02:49 -0700 Subject: [PATCH 06/20] Update coursewaresJSFramework_0.0.6.js --- .../js/lib/coursewares/coursewaresJSFramework_0.0.6.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js index c15a737e22..dcda001377 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js @@ -55,12 +55,16 @@ var codeStorage = { keyVersion:"saveVersion", keyValue: challenge_Name + 'Val', updateWait: 2000,// 2 seconds - updateTimeoutId: null + updateTimeoutId: null, + eventArray: []//for saves }; // Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere") codeStorage.hasSaved = function(){ return ( updateTimeoutId === null ); }; +codeStorage.onSave = function(func){ + codeStorage.eventArray.push(func); +}; codeStorage.getEditorValue = function(){ return localStorage.getItem(codeStorage.keyValue); }; @@ -75,6 +79,9 @@ codeStorage.updateStorage = function(){ } } codeStorage.updateTimeoutId = null; + codeStorage.eventArray.forEach(function(func){ + func(); + }); }; // ANONYMOUS 1 TIME UPDATE VERSION (function(){ From 437a564e343639ba13708b2edf0a0b84d37b8969 Mon Sep 17 00:00:00 2001 From: Andrew Cay Date: Fri, 19 Jun 2015 18:22:57 -0700 Subject: [PATCH 07/20] Changed dependency on challenge_key to function --- .../coursewaresJSFramework_0.0.6.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js index dcda001377..7d7867fd78 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js @@ -53,10 +53,10 @@ editor.setOption("extraKeys", { var codeStorage = { version: 0.01, keyVersion:"saveVersion", - keyValue: challenge_Name + 'Val', + keyValue: null,//where the value of the editor is saved updateWait: 2000,// 2 seconds updateTimeoutId: null, - eventArray: []//for saves + eventArray: []//for firing saves }; // Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere") codeStorage.hasSaved = function(){ @@ -65,6 +65,9 @@ codeStorage.hasSaved = function(){ codeStorage.onSave = function(func){ codeStorage.eventArray.push(func); }; +codeStorage.setSaveKey = function(key){ + codeStorage.keyValue = key + 'Val'; +}; codeStorage.getEditorValue = function(){ return localStorage.getItem(codeStorage.keyValue); }; @@ -83,25 +86,29 @@ codeStorage.updateStorage = function(){ func(); }); }; -// ANONYMOUS 1 TIME UPDATE VERSION +//Update Version (function(){ var savedVersion = localStorage.getItem('saveVersion'); if( savedVersion === null ){ localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version }else{ - //do checking if not current version if( savedVersion !== codeStorage.version ){ - //update version + //Update version } } })(); + + +///Set everything up one page /// Update local save when editor has changed -editor.on('keyup', function(codMir, event){ +codeStorage.setSaveKey(challenge_Name); +editor.on('keyup', function(){ window.clearTimeout(codeStorage.updateTimeoutId); codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait); }); + var attempts = 0; if (attempts) { attempts = 0; From 88286d366fac5c65135413caf69f9797167b45f4 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 19 Jun 2015 18:31:21 -0700 Subject: [PATCH 08/20] fix a bunch of github issues regarding waypoints --- seed/challenges/basic-html5-and-css.json | 5 +++-- seed/challenges/bootstrap.json | 2 +- seed/challenges/get-set-for-free-code-camp.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/seed/challenges/basic-html5-and-css.json b/seed/challenges/basic-html5-and-css.json index ccd2fdaa5f..24b1c71939 100644 --- a/seed/challenges/basic-html5-and-css.json +++ b/seed/challenges/basic-html5-and-css.json @@ -943,7 +943,8 @@ "In addition to pixels, you can also specify a border-radius using a percentage." ], "tests": [ - "assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent, making it perfectly circular.')" + "assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent, making it perfectly circular.')", + "assert(editor.match(/50%/g), 'Be sure to use a percentage instead of a pixel value.')" ], "challengeSeed": [ "", @@ -1613,7 +1614,7 @@ "For example: <form action=\"/url-where-you-want-to-submit-form-data\"></form>." ], "tests": [ - "assert($('form').length > 0, 'Wrap your text input element within a form element.')", + "assert($('form') && $('form').children('input') && $('form').children('input').length > 0, 'Wrap your text input element within a form element.')", "assert($('form').attr('action'), 'Your form element should have an action attribute.')", "assert(editor.match(/<\\/form>/g) && editor.match(/
/g).length === editor.match(/form element has a closing tag.')", "assert(editor.match(/\\/submit-cat-photo/ig), 'Make sure your form action is set to /submit-cat-photo.')" diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index 9f2b704153..41a7229731 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -1264,7 +1264,7 @@ "tests": [ "assert($('button[type=\\'submit\\']').hasClass('btn btn-primary'), 'Give the submit button in your form the classes \"btn btn-primary\".')", "assert($('button[type=\\'submit\\']:has(i.fa.fa-paper-plane)').length > 0, 'Add a <i class=\"fa fa-paper-plane\"></i> within your submit button element.')", - "assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text input in your form the class \"form-control\".')", + "assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text input in your form the class \"form-control\".')", "assert(editor.match(/<\\/i>/g) && editor.match(/<\\/i/g).length > 3, 'Make sure each of your i elements has a closing tag.')" ], "challengeSeed": [ diff --git a/seed/challenges/get-set-for-free-code-camp.json b/seed/challenges/get-set-for-free-code-camp.json index 3a9d366102..2aaac7aff8 100644 --- a/seed/challenges/get-set-for-free-code-camp.json +++ b/seed/challenges/get-set-for-free-code-camp.json @@ -195,7 +195,7 @@ "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 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.", From 883a561d7313b11270fed8e274c723cd0b2b5662 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Fri, 19 Jun 2015 21:24:04 -0700 Subject: [PATCH 09/20] finishing touches on calculator --- public/js/calculator.js | 4 ++++ public/js/main_0.0.2.js | 2 -- seed/challenges/basic-html5-and-css.json | 2 +- server/views/resources/calculator.jade | 11 +++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/public/js/calculator.js b/public/js/calculator.js index 8786b78bea..9cd991a9e7 100644 --- a/public/js/calculator.js +++ b/public/js/calculator.js @@ -154,6 +154,10 @@ $(document).ready(function () { }, 1000); }); + d3.selectAll("#chart").on("click", function () { + change(); + }); + function change() { if ($("body").data("state") === "stacked") { transitionGrouped(); diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 7fce4bdaba..65ec9f170c 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -331,8 +331,6 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); - $('#reset-button').on('click', resetEditor); - var commentSubmitButtonHandler = function commentSubmitButtonHandler() { $('#comment-button').unbind('click'); var data = $('#comment-box').val(); diff --git a/seed/challenges/basic-html5-and-css.json b/seed/challenges/basic-html5-and-css.json index 24b1c71939..7fdae6da09 100644 --- a/seed/challenges/basic-html5-and-css.json +++ b/seed/challenges/basic-html5-and-css.json @@ -1541,7 +1541,7 @@ ], "tests": [ "assert($('input[placeholder]').length > 0, 'Add a placeholder attribute text input element.')", - "assert($('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')" + "assert($('input') && $('input').attr('placeholder') && $('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')" ], "challengeSeed": [ "", diff --git a/server/views/resources/calculator.jade b/server/views/resources/calculator.jade index 5e25352139..4373653140 100644 --- a/server/views/resources/calculator.jade +++ b/server/views/resources/calculator.jade @@ -3,6 +3,7 @@ block content script(src="../../../js/calculator.js") .row .col-xs-12.col-sm-10.col-md-8.col-lg-6.col-sm-offset-1.col-md-offset-2.col-lg-offset-3 + h1.text-center Coding Bootcamp Cost Calculator h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be: #city-buttons .spacer @@ -102,3 +103,13 @@ block content a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here | . li.large-li Free Code Camp. We don't charge tuition or garnish wages. We're fully online so you don't have to move. We're self-paced so you don't have to quit your job. Thus, your true cost of attending Free Code Camp will be $0. + .spacer + .row + .col-xs-12.col-sm-4.col-md-3 + img.img-responsive.testimonial-image(src='https://www.evernote.com/l/AHRIBndcq-5GwZVnSy1_D7lskpH4OcJcUKUB/image.png') + .col-xs-12.col-sm-8.col-md-9 + h3 Built by Suzanne Atkinson + p.large-p Suzanne is an emergency medicine physician, triathlon coach and web developer from Pittsburgh. You should   + a(href='https://twitter.com/intent/user?screen_name=SteelCityCoach' target='_blank') follow her on Twitter + | . + .spacer From 43ae70465bf347817c736e0e024be53c4ac337ec Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 11:43:12 -0700 Subject: [PATCH 10/20] refactor returnIndividualBonfires --- server/boot/challenge.js | 219 ++++++++++++++----------------------- server/utils/middleware.js | 10 ++ 2 files changed, 90 insertions(+), 139 deletions(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index bddf9118c4..4b3d23e0e0 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -32,7 +32,9 @@ var R = require('ramda'), utils = require('../utils'), - userMigration = require('../utils/middleware').userMigration; + saveUser = require('../utils/rx').saveUser, + userMigration = require('../utils/middleware').userMigration, + ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo; var challengeMapWithNames = utils.getChallengeMapWithNames(); var challengeMapWithIds = utils.getChallengeMapWithIds(); @@ -51,23 +53,30 @@ module.exports = function(app) { // the follow routes are covered by userMigration router.use(userMigration); - router.get('/challenges/next-challenge', returnNextChallenge); - router.get('/challenges/:challengeName', returnIndividualChallenge); - router.get('/challenges/', returnCurrentChallenge); router.get('/map', challengeMap); + router.get( + '/challenges/next-challenge', + ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'), + returnNextChallenge + ); + + router.get('/challenges/:challengeName', returnIndividualChallenge); + + router.get( + '/challenges/', + ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'), + returnCurrentChallenge + ); app.use(router); function returnNextChallenge(req, res, next) { - if (!req.user) { - return res.redirect('../challenges/learn-how-free-code-camp-works'); - } var completed = req.user.completedChallenges.map(function (elem) { return elem.id; }); req.user.uncompletedChallenges = utils.allChallengeIds() - .filter(function (elem) { + .filter(function(elem) { if (completed.indexOf(elem) === -1) { return elem; } @@ -100,18 +109,17 @@ module.exports = function(app) { nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges); } - req.user.save(function(err) { - if (err) { - return next(err); - } - return res.redirect('../challenges/' + nextChallengeName); - }); + saveUser(req.user) + .subscribe( + function() {}, + next, + function() { + res.redirect('../challenges/' + nextChallengeName); + } + ); } function returnCurrentChallenge(req, res, next) { - if (!req.user) { - return res.redirect('../challenges/learn-how-free-code-camp-works'); - } var completed = req.user.completedChallenges.map(function (elem) { return elem.id; }); @@ -133,12 +141,14 @@ module.exports = function(app) { var nameString = req.user.currentChallenge.dashedName; - req.user.save(function(err) { - if (err) { - return next(err); - } - return res.redirect('../challenges/' + nameString); - }); + saveUser(req.user) + .subscribe( + function() {}, + next, + function() { + res.redirect('../challenges/' + nameString); + } + ); } function returnIndividualChallenge(req, res, next) { @@ -152,8 +162,10 @@ module.exports = function(app) { // Handle not found if (!challenge) { 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 the name `' + + dashedName + + '` Please double check the name.' }); return res.redirect('/challenges'); } @@ -167,8 +179,9 @@ module.exports = function(app) { map(function (key) { return challengeMapWithIds[key] .filter(function (elem) { - return String(elem) === String(challenge.id); - }).map(function () { + return String(elem) === challenge.id; + }) + .map(function () { return key; }); }) @@ -176,120 +189,48 @@ module.exports = function(app) { }; } - var challengeType = { - 0: function() { - res.render('coursewares/showHTML', { - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - brief: challenge.description[0], - details: challenge.description.slice(1), - tests: challenge.tests, - challengeSeed: challenge.challengeSeed, - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - challengeId: challenge.id, - environment: utils.whichEnvironment(), - challengeType: challenge.challengeType - }); - }, - - 1: function() { - res.render('coursewares/showJS', { - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - brief: challenge.description[0], - details: challenge.description.slice(1), - tests: challenge.tests, - challengeSeed: challenge.challengeSeed, - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - challengeId: challenge.id, - challengeType: challenge.challengeType - }); - }, - - 2: function() { - res.render('coursewares/showVideo', { - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - details: challenge.description, - tests: challenge.tests, - video: challenge.challengeSeed[0], - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - challengeId: challenge.id, - challengeType: challenge.challengeType - }); - }, - - 3: function() { - res.render('coursewares/showZiplineOrBasejump', { - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - details: challenge.description, - video: challenge.challengeSeed[0], - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - challengeId: challenge.id, - challengeType: challenge.challengeType - }); - }, - - 4: function() { - res.render('coursewares/showZiplineOrBasejump', { - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - details: challenge.description, - video: challenge.challengeSeed[0], - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - challengeId: challenge.id, - challengeType: challenge.challengeType - }); - }, - - 5: function() { - res.render('coursewares/showBonfire', { - completedWith: null, - title: challenge.name, - dashedName: dashedName, - name: challenge.name, - difficulty: Math.floor(+challenge.difficulty), - brief: challenge.description.shift(), - details: challenge.description, - tests: challenge.tests, - challengeSeed: challenge.challengeSeed, - verb: utils.randomVerb(), - phrase: utils.randomPhrase(), - compliment: utils.randomCompliment(), - bonfires: challenge, - challengeId: challenge.id, - MDNkeys: challenge.MDNlinks, - MDNlinks: getMDNLinks(challenge.MDNlinks), - challengeType: challenge.challengeType - }); - } + var commonLocals = { + title: challenge.name, + dashedName: dashedName, + name: challenge.name, + details: challenge.description.slice(1), + tests: challenge.tests, + challengeSeed: challenge.challengeSeed, + verb: utils.randomVerb(), + phrase: utils.randomPhrase(), + compliment: utils.randomCompliment(), + challengeId: challenge.id, + challengeType: challenge.challengeType, + // video challenges + video: challenge.challengeSeed[0], + // bonfires specific + difficulty: Math.floor(+challenge.difficulty), + brief: challenge.description.shift(), + bonfires: challenge, + MDNkeys: challenge.MDNlinks, + MDNlinks: getMDNLinks(challenge.MDNlinks), + // htmls specific + environment: utils.whichEnvironment() }; - if (req.user) { - req.user.save(function (err) { - if (err) { - return next(err); + + var challengeView = { + 0: 'coursewares/showHTML', + 1: 'coursewares/showJS', + 2: 'coursewares/showVideo', + 3: 'coursewares/showZiplineOrBasejump', + 4: 'coursewares/showZiplineOrBasejump', + 5: 'coursewares/showBonfire' + }; + + saveUser(req.user) + .subscribe( + function() {}, + next, + function() { + var view = challengeView[challenge.challengeType]; + res.render(view, commonLocals); } - return challengeType[challenge.challengeType](); - }); - } else { - return challengeType[challenge.challengeType](); - } + ); }); } diff --git a/server/utils/middleware.js b/server/utils/middleware.js index 5d2d12741c..5af207e513 100644 --- a/server/utils/middleware.js +++ b/server/utils/middleware.js @@ -33,3 +33,13 @@ exports.userMigration = function userMigration(req, res, next) { ); return next(); }; + +exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) { + return function(req, res, next) { + if (req.user) { + return next(); + } + return res.redirect(url); + }; +}; + From dd3d44505493bf3b6ecaab8ce1a965fa7708674a Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 20 Jun 2015 18:12:12 -0700 Subject: [PATCH 11/20] add new chat challenge --- seed/challenges/get-set-for-free-code-camp.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/seed/challenges/get-set-for-free-code-camp.json b/seed/challenges/get-set-for-free-code-camp.json index 2aaac7aff8..379bc26f6e 100644 --- a/seed/challenges/get-set-for-free-code-camp.json +++ b/seed/challenges/get-set-for-free-code-camp.json @@ -45,18 +45,20 @@ "name": "Waypoint: Join Our Chat Room", "dashedName": "waypoint-join-our-chat-room", "difficulty": 0.002, - "challengeSeed": ["124555254"], + "challengeSeed": ["131321596"], "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 to pair program with.", - "Make sure your Free Code Camp account includes your email address. Please note that the email address you use will be invisible to the public, but Slack will make it visible to other campers in our slack chat rooms. You can do this here: http://freecodecamp.com/account.", - "Click this link, which will email you can invite to Free Code Camp's Slack chat rooms: 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 our chat rooms, so put your best foot forward.", - "Now enter the General chat room and introduce yourself to our chat room by typing: \"Hello world!\".", + "Create an account with GitHub here: https://github.com/join.", + "Click the pixel art in the upper right hand corner of GitHub, then choose settings. Upload a picture of yourself. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward. You can add your city and your personal website if you have one.", + "Now follow this link to enter our Welcome chat room: https://gitter.im/FreeCodeCamp/welcome.", + "Once you're in our Welcome chat room, introduce yourself by saying : \"Hello world!\".", "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", + "This is the best room for new campers, but feel free to join other chat rooms as well. Our main chat room: https://gitter.im/FreeCodeCamp/FreeCodeCamp.", "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 download a desktop or mobile chat application here: https://gitter.im/apps", "You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner.", - "In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?" + "In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?", + "Now you're ready to move on. Click the \"I've completed this challenge\" button to move on to your next challenge." ], "challengeType": 2, "tests": [], From 4d2b0844ae040775eef1541096f25051c756339d Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 13:35:26 -0700 Subject: [PATCH 12/20] fix use regex to find challenges --- server/boot/challenge.js | 46 +++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 4b3d23e0e0..3c7d85f8c8 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -31,6 +31,7 @@ */ var R = require('ramda'), + debug = require('debug')('freecc:challenges'), utils = require('../utils'), saveUser = require('../utils/rx').saveUser, userMigration = require('../utils/middleware').userMigration, @@ -42,6 +43,18 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames(); var getMDNLinks = utils.getMDNLinks; +var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i; +function dasherize(name) { + return ('' + name) + .toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-\.]/gi, ''); +} + +function unDasherize(name) { + return ('' + name).replace(/\-/g, ' '); +} + module.exports = function(app) { var router = app.loopback.Router(); var Challenge = app.models.Challenge; @@ -56,7 +69,7 @@ module.exports = function(app) { router.get('/map', challengeMap); router.get( '/challenges/next-challenge', - ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'), + ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'), returnNextChallenge ); @@ -64,7 +77,7 @@ module.exports = function(app) { router.get( '/challenges/', - ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'), + ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'), returnCurrentChallenge ); @@ -114,7 +127,7 @@ module.exports = function(app) { function() {}, next, function() { - res.redirect('../challenges/' + nextChallengeName); + res.redirect('/challenges/' + nextChallengeName); } ); } @@ -146,30 +159,43 @@ module.exports = function(app) { function() {}, next, function() { - res.redirect('../challenges/' + nameString); + res.redirect('/challenges/' + nameString); } ); } function returnIndividualChallenge(req, res, next) { - var dashedName = req.params.challengeName; + var origChallengeName = req.params.challengeName; + var unDashedName = unDasherize(origChallengeName); + var challengeName = challangesRegex.test(unDashedName) ? + // remove first word if matches + unDashedName.split(' ').slice(1).join(' ') : + unDashedName; + + debug('looking for ', challengeName); Challenge.findOne( - { where: { dashedName: dashedName }}, + { where: { name: { like: challengeName, options: 'i' } } }, function(err, challenge) { if (err) { return next(err); } // Handle not found if (!challenge) { + debug('did not find challenge for ' + origChallengeName); req.flash('errors', { msg: '404: We couldn\'t find a challenge with the name `' + - dashedName + + origChallengeName + '` Please double check the name.' }); return res.redirect('/challenges'); } // Redirect to full name if the user only entered a partial + if (dasherize(challenge.name) !== origChallengeName) { + debug('redirecting to fullname'); + return res.redirect('/challenges/' + dasherize(challenge.name)); + } + if (req.user) { req.user.currentChallenge = { challengeId: challenge.id, @@ -191,7 +217,7 @@ module.exports = function(app) { var commonLocals = { title: challenge.name, - dashedName: dashedName, + dashedName: origChallengeName, name: challenge.name, details: challenge.description.slice(1), tests: challenge.tests, @@ -243,7 +269,7 @@ module.exports = function(app) { if (isCompletedWith) { User.find({ - where: { 'profile.username': isCompletedWith.toLowerCase() }, + where: { username: isCompletedWith.toLowerCase() }, limit: 1 }, function (err, pairedWith) { if (err) { return next(err); } @@ -382,7 +408,7 @@ module.exports = function(app) { if (isCompletedWith) { User.find({ - where: { 'profile.username': isCompletedWith.toLowerCase() }, + where: { username: isCompletedWith.toLowerCase() }, limit: 1 }, function (err, pairedWithFromMongo) { if (err) { return next(err); } From 03ea412b4a0837122fefb3e87ebecb0814ae1081 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 19:52:37 -0700 Subject: [PATCH 13/20] major challenges refactor commented out resetEditor in main remove heatmap timout --- package.json | 1 + public/js/main_0.0.2.js | 2 + server/boot/challenge.js | 377 +++++++++++++++------------------ server/boot/user.js | 2 +- server/views/account/show.jade | 98 ++------- 5 files changed, 198 insertions(+), 282 deletions(-) diff --git a/package.json b/package.json index 4863f4d47a..7fe7c38bd8 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "node-slack": "0.0.7", "node-uuid": "^1.4.3", "nodemailer": "~1.3.0", + "object.assign": "^3.0.0", "passport-facebook": "^2.0.0", "passport-google-oauth2": "^0.1.6", "passport-linkedin-oauth2": "^1.2.1", diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 65ec9f170c..1b0e0812a1 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -331,6 +331,8 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); + // $('#reset-button').on('click', resetEditor); + var commentSubmitButtonHandler = function commentSubmitButtonHandler() { $('#comment-button').unbind('click'); var data = $('#comment-box').val(); diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 3c7d85f8c8..16e2ba371a 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -31,9 +31,15 @@ */ var R = require('ramda'), + Rx = require('rx'), + assign = require('object.assign'), debug = require('debug')('freecc:challenges'), utils = require('../utils'), + + // this would be so much cleaner with destructering... saveUser = require('../utils/rx').saveUser, + observableQueryFromModel = require('../utils/rx').observableQueryFromModel, + userMigration = require('../utils/middleware').userMigration, ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo; @@ -55,6 +61,16 @@ function unDasherize(name) { return ('' + name).replace(/\-/g, ' '); } +function updateUserProgress(user, challengeId, completedChallenge) { + var index = user.uncompletedChallenges.indexOf(challengeId); + if (index > -1) { + user.progressTimestamps.push(Date.now()); + user.uncompletedChallenges.splice(index, 1); + } + user.completedChallenges.push(completedChallenge); + return user; +} + module.exports = function(app) { var router = app.loopback.Router(); var Challenge = app.models.Challenge; @@ -143,6 +159,7 @@ module.exports = function(app) { return elem; } }); + if (!req.user.currentChallenge) { req.user.currentChallenge = {}; req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0]; @@ -201,11 +218,11 @@ module.exports = function(app) { challengeId: challenge.id, challengeName: challenge.name, dashedName: challenge.dashedName, - challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds). - map(function (key) { + challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds) + .map(function (key) { return challengeMapWithIds[key] .filter(function (elem) { - return String(elem) === challenge.id; + return elem === ('' + challenge.id); }) .map(function () { return key; @@ -261,143 +278,124 @@ module.exports = function(app) { } function completedBonfire(req, res, next) { - var isCompletedWith = req.body.challengeInfo.completedWith || ''; - var isCompletedDate = Math.round(+new Date()); + debug('compltedBonfire'); + var completedWith = req.body.challengeInfo.completedWith || false; var challengeId = req.body.challengeInfo.challengeId; - var isSolution = req.body.challengeInfo.solution; - var challengeName = req.body.challengeInfo.challengeName; - if (isCompletedWith) { - User.find({ - where: { username: isCompletedWith.toLowerCase() }, - limit: 1 - }, function (err, pairedWith) { - if (err) { return next(err); } + var challengeData = { + id: challengeId, + name: req.body.challengeInfo.challengeName, + completedDate: Math.round(+new Date()), + solution: req.body.challengeInfo.solution, + challengeType: 5 + }; - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - pairedWith = pairedWith.pop(); + observableQueryFromModel( + User, + 'findOne', + { where: { username: ('' + completedWith).toLowerCase() } } + ) + .doOnNext(function(pairedWith) { + debug('paired with ', pairedWith); if (pairedWith) { - - index = pairedWith.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedChallenges.splice(index, 1); - - } - - pairedWith.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: req.user.id, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: pairedWith.id, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); + updateUserProgress( + pairedWith, + challengeId, + assign({ completedWith: req.user.id }, challengeData) + ); } - // User said they paired, but pair wasn't found - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - req.user.save(function (err, user) { - if (err) { return next(err); } - - if (pairedWith) { - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { - return res.send(true); - } - }); - } else if (user) { - res.send(true); + }) + .withLatestFrom( + Rx.Observable.just(req.user), + function(pairedWith, user) { + debug('yo'); + return { + user: user, + pairedWith: pairedWith + }; + } + ) + // side effects should always be done in do's and taps + .doOnNext(function(dats) { + updateUserProgress( + dats.user, + challengeId, + dats.pairedWith ? + // paired programmer found and adding to data + assign({ completedWith: dats.pairedWith.id }, challengeData) : + // user said they paired, but pair wasn't found + challengeData + ); + }) + // not iterate users + .flatMap(function(dats) { + debug('flatmap'); + return Rx.Observable.from([dats.user, dats.pairedWith]); + }) + // save user + .flatMap(function(user) { + // save user will do nothing if user is falsey + debug('saving user', user.username); + return saveUser(user); + }) + .subscribe( + function(user) { + debug('onNext'); + if (user) { + debug('user %s saved', user.username); } - }); - }); - } else { - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err) { - if (err) { return next(err); } - res.send(true); - }); - } + }, + next, + function() { + debug('completed'); + return res.status(200).send(true); + } + ); } function completedChallenge(req, res, next) { - var isCompletedDate = Math.round(+new Date()); + var completedDate = Math.round(+new Date()); var challengeId = req.body.challengeInfo.challengeId; - req.user.completedChallenges.push({ - id: challengeId, - completedDate: isCompletedDate, - name: req.body.challengeInfo.challengeName, - solution: null, - githubLink: null, - verified: true - }); - var index = req.user.uncompletedChallenges.indexOf(challengeId); - - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err, user) { - if (err) { - return next(err); + updateUserProgress( + req.user, + challengeId, + { + id: challengeId, + completedDate: completedDate, + name: req.body.challengeInfo.challengeName, + solution: null, + githubLink: null, + verified: true } - if (user) { - res.sendStatus(200); - } - }); + ); + + saveUser(req.user) + .subscribe( + function() { }, + next, + function() { + res.sendStatus(200); + } + ); } function completedZiplineOrBasejump(req, res, next) { - var isCompletedWith = req.body.challengeInfo.completedWith || false; - var isCompletedDate = Math.round(+new Date()); + var completedWith = req.body.challengeInfo.completedWith || false; + var completedDate = Math.round(+new Date()); var challengeId = req.body.challengeInfo.challengeId; var solutionLink = req.body.challengeInfo.publicURL; - var githubLink = req.body.challengeInfo.challengeType === '4' - ? req.body.challengeInfo.githubURL : true; + + var githubLink = req.body.challengeInfo.challengeType === '4' ? + req.body.challengeInfo.githubURL : + true; + var challengeType = req.body.challengeInfo.challengeType === '4' ? - 4 : 3; + 4 : + 3; + if (!solutionLink || !githubLink) { req.flash('errors', { msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + @@ -406,93 +404,64 @@ module.exports = function(app) { return res.sendStatus(403); } - if (isCompletedWith) { - User.find({ - where: { username: isCompletedWith.toLowerCase() }, - limit: 1 - }, function (err, pairedWithFromMongo) { - if (err) { return next(err); } - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); + var challengeData = { + id: challengeId, + name: req.body.challengeInfo.challengeName, + completedDate: completedDate, + solution: solutionLink, + githubLink: githubLink, + challengeType: challengeType, + verified: false + }; + + observableQueryFromModel( + User, + 'findOne', + { where: { username: completedWith.toLowerCase() } } + ) + .doOnNext(function(pairedWith) { + if (pairedWith) { + updateUserProgress( + pairedWith, + challengeId, + assign({ completedWith: req.user.id }, challengeData) + ); } - var pairedWith = pairedWithFromMongo.pop(); - - req.user.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.challengeName, - completedWith: pairedWith.id, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - - req.user.save(function (err, user) { - if (err) { return next(err); } - - if (req.user.id.toString() === pairedWith.id.toString()) { - return res.sendStatus(200); + }) + .withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) { + return { + user: user, + pairedWith: pairedWith + }; + }) + .doOnNext(function(dats) { + updateUserProgress( + dats.user, + challengeId, + dats.pairedWith ? + assign({ completedWith: dats.pairedWith.id }, challengeData) : + challengeData + ); + }) + .flatMap(function(dats) { + return Rx.Observable.from([dats.user, dats.pairedWith]); + }) + // save users + .flatMap(function(user) { + // save user will do nothing if user is falsey + return saveUser(user); + }) + .subscribe( + function(user) { + if (user) { + debug('user %s saved', user.username); } - index = pairedWith.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedChallenges.splice(index, 1); - - } - - pairedWith.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.coursewareName, - completedWith: req.user.id, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { - return res.sendStatus(200); - } - }); - }); - }); - } else { - - req.user.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err, user) { - if (err) { - return next(err); + }, + next, + function() { + return res.status(200).send(true); } - // NOTE(berks): under certain conditions this will not close - // the response. - if (user) { - return res.sendStatus(200); - } - }); - } + ); } function challengeMap(req, res, next) { diff --git a/server/boot/user.js b/server/boot/user.js index f36fbe56a2..4575a6decf 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -4,7 +4,7 @@ var _ = require('lodash'), crypto = require('crypto'), nodemailer = require('nodemailer'), moment = require('moment'), - //debug = require('debug')('freecc:cntr:userController'), + // debug = require('debug')('freecc:cntr:userController'), secrets = require('../../config/secrets'); diff --git a/server/views/account/show.jade b/server/views/account/show.jade index 0f7add7096..f1edc4d3c7 100644 --- a/server/views/account/show.jade +++ b/server/views/account/show.jade @@ -47,88 +47,32 @@ block content .background-svg.img-center .points-on-top = "[ " + (progressTimestamps.length) + " ]" - - - .row - .col-xs-12 - if (website1Title && website1Link && website1Image) - .row - .col-xs-12.col-md-5 - 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') - i.fa.icon-beaker - | Try it out - br - if (website1Title && website1Link && !website1Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website1Title - a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website2Title && website2Link && website2Image) - .row - .col-xs-12.col-md-5 - 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') - i.fa.icon-beaker - | Try it out - br - if (website2Title && website2Link && !website2Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website2Title - a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website3Title && website3Link && website3Image) - .row - .col-xs-12.col-md-5 - 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') - i.fa.icon-beaker - | Try it out - if (website3Title && website3Link && !website3Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website3Title - a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') - i.fa.icon-beaker - | Try it out - .spacer .hidden-xs.hidden-sm.col-md-12 #cal-heatmap.d3-centered script. $(document).ready(function () { - setTimeout(function () { - var cal = new CalHeatMap(); - var calendar = !{JSON.stringify(calender)}; - cal.init({ - itemSelector: "#cal-heatmap", - domain: "month", - subDomain: "x_day", - domainGutter: 10, - data: calendar, - cellSize: 15, - align: 'center', - cellRadius: 3, - cellPadding: 2, - tooltip: true, - range: 6, - start: new Date().setDate(new Date().getDate() - 150), - legendColors: ["#cccccc", "#215f1e"], - legend: [1, 2, 3], - label: { - position: "top" - } - }); - }, 300); + var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + cal.init({ + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "x_day", + domainGutter: 10, + data: calendar, + cellSize: 15, + align: 'center', + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 6, + start: new Date().setDate(new Date().getDate() - 150), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3], + label: { + position: "top" + } + }); }); .row .hidden-xs.col-sm-12.text-center From 049233c0a190233181904fe27e5fee637cd24ece Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 19:55:22 -0700 Subject: [PATCH 14/20] fix add server/utils/rx to commit --- server/utils/rx.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 server/utils/rx.js diff --git a/server/utils/rx.js b/server/utils/rx.js new file mode 100644 index 0000000000..8a4003c00c --- /dev/null +++ b/server/utils/rx.js @@ -0,0 +1,25 @@ +var Rx = require('rx'); +var debug = require('debug')('freecc:rxUtils'); + +exports.saveUser = function saveUser(user) { + return new Rx.Observable.create(function(observer) { + if (!user || typeof user.save !== 'function') { + debug('no user or save method'); + observer.onNext(); + return observer.onCompleted(); + } + user.save(function(err, savedUser) { + if (err) { + return observer.onError(err); + } + debug('user saved'); + observer.onNext(savedUser); + observer.onCompleted(); + }); + }); +}; + +exports.observableQueryFromModel = + function observableQueryFromModel(Model, method, query) { + return Rx.Observable.fromNodeCallback(Model[method], Model)(query); + }; From 9fb3c0bff71a75b5316f7951d96a8dc3dcc7745e Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 20:43:36 -0700 Subject: [PATCH 15/20] fix use secrets.db instead of process.env.MONGOHQ in datasource.local.js this should eliminate timeout issue. I'd bet my bottom dollar. --- server/config.local.js | 2 +- server/datasources.local.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/config.local.js b/server/config.local.js index c781159696..9b9fc132d6 100644 --- a/server/config.local.js +++ b/server/config.local.js @@ -17,4 +17,4 @@ module.exports = { clientID: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET } -}; \ No newline at end of file +}; diff --git a/server/datasources.local.js b/server/datasources.local.js index 276a4e8dee..8ad9d08abc 100644 --- a/server/datasources.local.js +++ b/server/datasources.local.js @@ -3,8 +3,8 @@ var secrets = require('../config/secrets'); module.exports = { db: { connector: 'mongodb', - connectionTimeout: 15000, - url: process.env.MONGOHQ_URL + connectionTimeout: 5000, + url: secrets.db }, mail: { connector: 'mail', From f17c442018d06ad574e5f1687c6c376a25ec1026 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 20:54:39 -0700 Subject: [PATCH 16/20] fix should be story not store --- server/boot/randomAPIs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 7dd89b1b4f..4a2e767884 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -15,7 +15,7 @@ module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; var Challenge = app.models.Challenge; - var Story = app.models.Store; + var Story = app.models.Story; var FieldGuide = app.models.FieldGuide; var Nonprofit = app.models.Nonprofit; From 0623ace259f96542f5d5760da5032bad77794311 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 21:15:41 -0700 Subject: [PATCH 17/20] fix sitemap query's make rx mapping async --- server/boot/randomAPIs.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 4a2e767884..4c460d5172 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -193,15 +193,15 @@ module.exports = function(app) { users: function(callback) { User.find( { - where: { 'profile.username': { nlike: '' } }, - fields: { 'profile.username': true } + where: { username: { nlike: '' } }, + fields: { username: true } }, function(err, users) { if (err) { debug('User err: ', err); callback(err); } else { - Rx.Observable.from(users) + Rx.Observable.from(users, null, null, Rx.Scheduler.default) .map(function(user) { return user.username; }) @@ -224,7 +224,7 @@ module.exports = function(app) { debug('Challenge err: ', err); callback(err); } else { - Rx.Observable.from(challenges) + Rx.Observable.from(challenges, null, null, Rx.Scheduler.default) .map(function(challenge) { return challenge.name; }) @@ -244,7 +244,7 @@ module.exports = function(app) { debug('Story err: ', err); callback(err); } else { - Rx.Observable.from(stories) + Rx.Observable.from(stories, null, null, Rx.Scheduler.default) .map(function(story) { return story.link; }) @@ -265,7 +265,7 @@ module.exports = function(app) { debug('User err: ', err); callback(err); } else { - Rx.Observable.from(nonprofits) + Rx.Observable.from(nonprofits, null, null, Rx.Scheduler.default) .map(function(nonprofit) { return nonprofit.name; }) @@ -285,7 +285,12 @@ module.exports = function(app) { debug('User err: ', err); callback(err); } else { - Rx.Observable.from(fieldGuides) + Rx.Observable.from( + fieldGuides, + null, + null, + Rx.Scheduler.default + ) .map(function(fieldGuide) { return fieldGuide.name; }) @@ -301,7 +306,7 @@ module.exports = function(app) { if (err) { return next(err); } - setTimeout(function() { + process.nextTick(function() { res.header('Content-Type', 'application/xml'); res.render('resources/sitemap', { appUrl: appUrl, @@ -312,7 +317,7 @@ module.exports = function(app) { nonprofits: results.nonprofits, fieldGuides: results.fieldGuides }); - }, 0); + }); } ); } @@ -383,7 +388,7 @@ module.exports = function(app) { } function unsubscribe(req, res, next) { - User.findOne({ email: req.params.email }, function(err, user) { + User.findOne({ where: { email: req.params.email } }, function(err, user) { if (user) { if (err) { return next(err); From 742a49b70dc38c8d20da29680512475cbb10c505 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 22:48:59 -0700 Subject: [PATCH 18/20] fix mainjs resetEditor --- .../coursewaresJSFramework_0.0.6.js | 96 +++++++++++-------- public/js/main_0.0.2.js | 1 - server/boot/challenge.js | 1 - server/views/coursewares/showBonfire.jade | 2 +- 4 files changed, 55 insertions(+), 45 deletions(-) diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js index 7d7867fd78..ae7237ef5d 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js @@ -1,3 +1,7 @@ +$(document).ready(function() { + $('#reset-button').on('click', resetEditor); +}); + var widgets = []; var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { lineNumbers: true, @@ -41,61 +45,68 @@ editor.setOption("extraKeys", { /* - Local Storage Update System By Andrew Cay(Resto) - codeStorage: singleton object that contains properties and methods related to - dealing with the localStorage system. - The keys work off of the variable challenge_name to make unique identifiers per bonfire + Local Storage Update System By Andrew Cay(Resto) + codeStorage: singleton object that contains properties and methods related to + dealing with the localStorage system. + The keys work off of the variable challenge_name to make unique identifiers per bonfire - Two extra functionalities: - Added anonymous version checking system incase of future updates to the system - Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage + Two extra functionalities: + Added anonymous version checking system incase of future updates to the system + Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage */ var codeStorage = { - version: 0.01, - keyVersion:"saveVersion", - keyValue: null,//where the value of the editor is saved - updateWait: 2000,// 2 seconds - updateTimeoutId: null, - eventArray: []//for firing saves + version: 0.01, + keyVersion:"saveVersion", + keyValue: null,//where the value of the editor is saved + updateWait: 2000,// 2 seconds + updateTimeoutId: null, + eventArray: []//for firing saves }; // Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere") codeStorage.hasSaved = function(){ - return ( updateTimeoutId === null ); + return ( updateTimeoutId === null ); }; codeStorage.onSave = function(func){ - codeStorage.eventArray.push(func); + codeStorage.eventArray.push(func); }; codeStorage.setSaveKey = function(key){ - codeStorage.keyValue = key + 'Val'; + codeStorage.keyValue = key + 'Val'; }; codeStorage.getEditorValue = function(){ - return localStorage.getItem(codeStorage.keyValue); + return ('' + localStorage.getItem(codeStorage.keyValue)); }; + +codeStorage.isAlive = function() { + var val = this.getEditorValue() + return val !== 'null' && + val !== 'undefined' && + (val && val.length > 0); +} codeStorage.updateStorage = function(){ - if(typeof(Storage) !== undefined) { - var value = editor.getValue(); - localStorage.setItem(codeStorage.keyValue, value); - } else { - var debugging = false; - if( debugging ){ - console.log('no web storage'); - } - } - codeStorage.updateTimeoutId = null; - codeStorage.eventArray.forEach(function(func){ - func(); - }); + if(typeof(Storage) !== undefined) { + var value = editor.getValue(); + localStorage.setItem(codeStorage.keyValue, value); + } else { + var debugging = false; + if( debugging ){ + console.log('no web storage'); + } + } + codeStorage.updateTimeoutId = null; + codeStorage.eventArray.forEach(function(func){ + func(); + }); }; //Update Version (function(){ - var savedVersion = localStorage.getItem('saveVersion'); - if( savedVersion === null ){ - localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version - }else{ - if( savedVersion !== codeStorage.version ){ - //Update version - } - } + var savedVersion = localStorage.getItem('saveVersion'); + if( savedVersion === null ){ + localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version + }else{ + if( savedVersion !== codeStorage.version ){ + //Update version + } + } })(); @@ -104,8 +115,8 @@ codeStorage.updateStorage = function(){ /// Update local save when editor has changed codeStorage.setSaveKey(challenge_Name); editor.on('keyup', function(){ - window.clearTimeout(codeStorage.updateTimeoutId); - codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait); + window.clearTimeout(codeStorage.updateTimeoutId); + codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait); }); @@ -114,9 +125,10 @@ if (attempts) { attempts = 0; } -var resetEditor = function() { +var resetEditor = function resetEditor() { editor.setValue(allSeeds); codeStorage.updateStorage(); + }; var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { @@ -150,7 +162,7 @@ var tests = tests || []; var allSeeds = ''; (function() { challengeSeed.forEach(function(elem) { - allSeeds += elem + '\n'; + allSeeds += elem + '\n'; }); })(); diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 1b0e0812a1..1b41b9066b 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -331,7 +331,6 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); - // $('#reset-button').on('click', resetEditor); var commentSubmitButtonHandler = function commentSubmitButtonHandler() { $('#comment-button').unbind('click'); diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 16e2ba371a..02cecc639e 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -335,7 +335,6 @@ module.exports = function(app) { // save user .flatMap(function(user) { // save user will do nothing if user is falsey - debug('saving user', user.username); return saveUser(user); }) .subscribe( diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade index c28f20e322..a52578dec0 100644 --- a/server/views/coursewares/showBonfire.jade +++ b/server/views/coursewares/showBonfire.jade @@ -84,7 +84,7 @@ block content label.negative-10.btn.btn-primary.btn-block#submitButton i.fa.fa-play |   Run code (ctrl + enter) - #resetButton.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code + #trigger-reset-modal.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code if (user && user.sentSlackInvite) .button-spacer .btn-group.input-group.btn-group-justified From 8f445b58f241ec3e87a9a64e2e8f7a5498e0efb0 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 23:11:33 -0700 Subject: [PATCH 19/20] fix chat links go to Gitter Room --- server/views/partials/navbar.jade | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade index 317ce661d1..e35b9c5e8e 100644 --- a/server/views/partials/navbar.jade +++ b/server/views/partials/navbar.jade @@ -13,12 +13,8 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height a(href='/challenges') Learn li a(href='/map') Map - if (user && user.sentSlackInvite) - li - a(href='/chat', target='_blank') Chat - else - li - a(href='/challenges/waypoint-join-our-chat-room') Chat + li + a(href='//gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat li a(href='/stories') News li From 629262dd0d126ca1b0ff58da6b6871cd1d7fb78c Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 23:15:10 -0700 Subject: [PATCH 20/20] fix /chat should go to gitterim --- server/boot/randomAPIs.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 4c460d5172..7b658a36df 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -323,13 +323,7 @@ module.exports = function(app) { } function chat(req, res) { - if (req.user && req.user.progressTimestamps.length > 5) { - res.redirect('http://freecodecamp.slack.com'); - } else { - res.render('resources/chat', { - title: 'Watch us code live on Twitch.tv' - }); - } + res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp'); } function bootcampCalculator(req, res) {