From fb3447c366f648d7154874fb8b2652729bd5680f Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Mon, 25 May 2015 16:02:25 -0400 Subject: [PATCH 1/3] Update challengeMap display, more consistent dasherization --- controllers/challenge.js | 13 +++++-- controllers/challengeMap.js | 2 +- controllers/fieldGuide.js | 15 +++++--- controllers/resources.js | 17 ++++++++-- seed_data/challenges/basic-html5-and-css.json | 2 +- views/challengeMap/show.jade | 34 ++++++++++--------- 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/controllers/challenge.js b/controllers/challenge.js index bbda541e63..06f33e704a 100644 --- a/controllers/challenge.js +++ b/controllers/challenge.js @@ -135,7 +135,8 @@ exports.returnCurrentChallenge = function(req, res, next) { } var nameString = req.user.currentChallenge.challengeName.trim() .toLowerCase() - .replace(/\s/g, '-'); + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-]/gi, ''); req.user.save(function(err) { if (err) { return next(err); @@ -147,7 +148,10 @@ exports.returnCurrentChallenge = function(req, res, next) { exports.returnIndividualChallenge = function(req, res, next) { var dashedName = req.params.challengeName; - var challengeName = dashedName.replace(/\-/g, ' '); + var challengeName = dashedName.replace(/\-/g, ' ') + .split(' ') + .slice(1) + .join(' '); Challenge.find({'name': new RegExp(challengeName, 'i')}, function(err, challengeFromMongo) { @@ -164,7 +168,10 @@ exports.returnIndividualChallenge = function(req, res, next) { } var challenge = challengeFromMongo.pop(); // Redirect to full name if the user only entered a partial - var dashedNameFull = challenge.name.toLowerCase().replace(/\s/g, '-'); + var dashedNameFull = challenge.name + .toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-]/gi, ''); if (dashedNameFull !== dashedName) { return res.redirect('../challenges/' + dashedNameFull); } else { diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js index 945c5e9aee..aef68cc4ce 100644 --- a/controllers/challengeMap.js +++ b/controllers/challengeMap.js @@ -23,7 +23,7 @@ module.exports = { var noDuplicatedChallenges = R.uniq(completedList); - var challengeList = resources.allChallenges(); + var challengeList = resources.getChallengeMapForDisplay(); var completedChallengeList = noDuplicatedChallenges .map(function(challenge) { return challenge._id; diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index 2d0809b6a3..49e4d80dab 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -5,7 +5,8 @@ var R = require('ramda'), exports.returnIndividualFieldGuide = function(req, res, next) { var dashedName = req.params.fieldGuideName; - var fieldGuideName = dashedName.replace(/\-/g, ' '); + var fieldGuideName = dashedName.replace(/\-/g, ' ') + .replace(/[^a-z0-9\s]/gi, ''); if (req.user) { var completed = req.user.completedFieldGuides; @@ -39,7 +40,9 @@ exports.returnIndividualFieldGuide = function(req, res, next) { var fieldGuide = R.head(fieldGuideFromMongo); var dashedNameFull = - fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + fieldGuide.name.toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-]/gi, ''); if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); @@ -68,7 +71,7 @@ exports.showAllFieldGuides = function(req, res) { exports.returnNextFieldGuide = function(req, res, next) { if (!req.user) { - return res.redirect('/field-guide/how-do-i-use-this-guide?'); + return res.redirect('/field-guide/how-do-i-use-this-guide'); } var displayedFieldGuides = @@ -89,9 +92,11 @@ exports.returnNextFieldGuide = function(req, res, next) { ].join('') }); } - return res.redirect('../field-guide/how-do-i-use-this-guide?'); + return res.redirect('../field-guide/how-do-i-use-this-guide'); } - var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); + var nameString = fieldGuide.name.toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-]/gi, ''); return res.redirect('../field-guide/' + nameString); }); }; diff --git a/controllers/resources.js b/controllers/resources.js index 8d852292a4..0fcef198ab 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -28,7 +28,7 @@ var async = require('async'), * Cached values */ var allFieldGuideIds, allFieldGuideNames, allNonprofitNames, - challengeMap, challengeMapWithIds, + challengeMap, challengeMapForDisplay, challengeMapWithIds, challengeMapWithNames, allChallengeIds, allChallenges; /** @@ -67,13 +67,24 @@ Array.zip = function(left, right, combinerFunction) { }); challengeMap = _.cloneDeep(localChallengeMap); } - //todo remove this debug - debug(challengeMap); })(); module.exports = { + getChallengeMapForDisplay: function() { + if (!challengeMapForDisplay) { + challengeMapForDisplay = {}; + Object.keys(challengeMap).forEach(function(key) { + challengeMapForDisplay[key] = { + name: challengeMap[key].name, + challenges: challengeMap[key].challenges + } + }); + } + return challengeMapForDisplay; + }, + getChallengeMapWithIds: function() { if (!challengeMapWithIds) { challengeMapWithIds = {}; diff --git a/seed_data/challenges/basic-html5-and-css.json b/seed_data/challenges/basic-html5-and-css.json index a3827c4395..1ba903a877 100644 --- a/seed_data/challenges/basic-html5-and-css.json +++ b/seed_data/challenges/basic-html5-and-css.json @@ -1,5 +1,5 @@ { - "name": "Waypoint: Basic HTML5 and CSS", + "name": "Basic HTML5 and CSS", "order" : 0.002, "challenges": [ { diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 43f06a04d3..0cb1ace524 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -23,23 +23,25 @@ block content span.text-primary #{daysRunning}   | days ago. .spacer - h3.negative-15 - ol - for challenge in challengeList - if completedChallengeList.indexOf(challenge._id) > -1 - .row - .hidden-xs.col-sm-3.col-md-2.text-primary.ion-checkmark-circled.padded-ionic-icon.text-center - .col-xs-12.col-sm-9.col-md-10 - li.faded - a(href="/challenges/#{challenge.name}")= challenge.name + .negative-15 + for challengeBlock in challengeList + h3 #{challengeBlock.name} + ol + for challenge in challengeBlock.challenges + if completedChallengeList.indexOf(challenge._id) > -1 + .row + .hidden-xs.col-sm-3.col-md-2.text-primary.ion-checkmark-circled.padded-ionic-icon.text-center.large-p + .col-xs-12.col-sm-9.col-md-10 + li.faded.large-p + a(href="/challenges/#{challenge.name}")= challenge.name - else - .row - .hidden-xs.col-sm-3.col-md-2 - span - .col-xs-12.col-sm-9.col-md-10 - li - a(href="/challenges/#{challenge.name}")= challenge.name + else + .row + .hidden-xs.col-sm-3.col-md-2 + span + .col-xs-12.col-sm-9.col-md-10 + li.large-p + a(href="/challenges/#{challenge.name}")= challenge.name h2 span.ion-ios-heart   Nonprofit Projects (800 hours of real-world experience)* From 1c4b40c8e6ee616aa60cf25d900d5670d8c6bd9a Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Mon, 25 May 2015 17:27:27 -0400 Subject: [PATCH 2/3] Update to bonfires. Successfully completing triggers a save. "Paired with" form field moved to bonfire view. Controller hardened for checking pair as button is no longer disabled for unfound username --- controllers/challenge.js | 72 ++++++++++++------- .../coursewaresJSFramework_0.0.2.js | 28 ++++++-- public/js/main.js | 21 +----- views/coursewares/showBonfire.jade | 28 ++++---- 4 files changed, 84 insertions(+), 65 deletions(-) diff --git a/controllers/challenge.js b/controllers/challenge.js index 06f33e704a..76e273f681 100644 --- a/controllers/challenge.js +++ b/controllers/challenge.js @@ -324,44 +324,62 @@ exports.completedBonfire = function (req, res, next) { req.user.uncompletedChallenges.splice(index, 1); } pairedWith = pairedWith.pop(); + if (pairedWith) { - index = pairedWith.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedChallenges.splice(index, 1); + 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 + }); } + // 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 + }); - 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 - }); req.user.save(function (err, user) { if (err) { return next(err); } - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { + if (pairedWith) { + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + res.send(true); + } + }); + } else { + if (user) { res.send(true); } - }); + } }); } }); diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.2.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.2.js index 01f09d8f19..3abe01c1dc 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.2.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.2.js @@ -239,10 +239,28 @@ var runTests = function(err, data) { function showCompletion() { var time = Math.floor(Date.now()) - started; ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +', Attempts: ' + attempts); - $('#complete-courseware-dialog').modal('show'); - $('#complete-courseware-dialog').keydown(function(e) { - if (e.ctrlKey && e.keyCode == 13) { - $('#next-courseware-button').click(); + var bonfireSolution = myCodeMirror.getValue(); + var didCompleteWith = $('#completed-with').val() || null; + $.post( + '/completed-bonfire/', + { + challengeInfo: { + challengeId: challenge_Id, + challengeName: challenge_Name, + completedWith: didCompleteWith, + challengeType: challengeType, + solution: bonfireSolution + } + }, function(res) { + if (res) { + $('#complete-courseware-dialog').modal('show'); + $('#complete-courseware-dialog').keydown(function (e) { + if (e.ctrlKey && e.keyCode == 13) { + $('#next-courseware-button').click(); + } + }); + } } - }); + ); + } diff --git a/public/js/main.js b/public/js/main.js index ef692b4d42..ed6c881f99 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -215,29 +215,10 @@ $(document).ready(function() { }); break; case challengeTypes.BONFIRE: - var bonfireSolution = myCodeMirror.getValue(); - var didCompleteWith = $('#completed-with').val() || null; - $.post( - '/completed-bonfire/', - { - challengeInfo: { - challengeId: challenge_Id, - challengeName: challenge_Name, - completedWith: didCompleteWith, - challengeType: challengeType, - solution: bonfireSolution - } - }, - function(res) { - if (res) { - window.location.href = '/challenges/next-challenge'; - } - } - ); + window.location.href = '/challenges/next-challenge'; default: break; } - } }); diff --git a/views/coursewares/showBonfire.jade b/views/coursewares/showBonfire.jade index e68b9d4115..00787fee1d 100644 --- a/views/coursewares/showBonfire.jade +++ b/views/coursewares/showBonfire.jade @@ -16,7 +16,7 @@ block content script(type='text/javascript', src='/js/lib/jailed/jailed.js') script(type='text/javascript', src='/js/lib/coursewares/sandbox.js') - .row + .row(ng-controller="pairedWithController") .col-xs-12.col-sm-12.col-md-4.bonfire-top #testCreatePanel h1#bonfire-name.text-center= name @@ -79,6 +79,18 @@ block content span.ion-arrow-up-b | Less information + 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") + label(for="existingUser") If you're pairing with someone, enter their FreeCodeCamp username here + input.form-control#completed-with(name="existingUser", placeholder="Pair Username", existing-username='', ng-model="existingUser", autofocus) + .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 #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) .button-spacer .btn-group.input-group.btn-group-justified @@ -122,23 +134,13 @@ block content .modal-content .modal-header.challenge-list-header= compliment a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body(ng-controller="pairedWithController") + .modal-body .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") - input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser", autofocus) - .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-courseware-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) Go to my next challenge (ctrl + enter) - if (user.progressTimestamps.length > 2) From f7252be9daa3026dc629090fdb584fe1efc500be Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Mon, 25 May 2015 17:30:10 -0400 Subject: [PATCH 3/3] Minor view cleanup. Closes #478 --- views/coursewares/showBonfire.jade | 2 -- 1 file changed, 2 deletions(-) diff --git a/views/coursewares/showBonfire.jade b/views/coursewares/showBonfire.jade index 00787fee1d..560a9d3008 100644 --- a/views/coursewares/showBonfire.jade +++ b/views/coursewares/showBonfire.jade @@ -127,8 +127,6 @@ block content textarea#codeEditor(autofocus=true, style='display: none;') script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.2.js') - - #complete-courseware-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content