diff --git a/.travis.yml b/.travis.yml index b6bd11e2c2..22cfb84161 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - - 'node' - - '1.6.4' + - '4.2.1' sudo: false diff --git a/client/commonFramework.js b/client/commonFramework.js index 2505fe08f4..d55ea45c1e 100644 --- a/client/commonFramework.js +++ b/client/commonFramework.js @@ -659,7 +659,11 @@ function ctrlEnterClickHandler(e) { // ctrl + enter if (e.ctrlKey && e.keyCode === 13) { $('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler); - $('#submit-challenge').click(); + if ($('#submit-challenge').length > 0) { + $('#submit-challenge').click(); + } else { + window.location = '/challenges/next-challenge?id=' + common.challengeId; + } } } @@ -781,7 +785,7 @@ var scrapeTests = function(userJavaScript) { }; function removeComments(userJavaScript) { - var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|\/\/[^\n]*/g); + var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|([ \n]\/\/[^\n]*)/g); return userJavaScript.replace(regex, ''); } @@ -892,12 +896,32 @@ var runTests = function(err, data) { // step challenge common.init.push((function() { var stepClass = '.challenge-step'; + var prevBtnClass = '.challenge-step-btn-prev'; var nextBtnClass = '.challenge-step-btn-next'; var actionBtnClass = '.challenge-step-btn-action'; var finishBtnClass = '.challenge-step-btn-finish'; var submitBtnId = '#challenge-step-btn-submit'; var submitModalId = '#challenge-step-modal'; + function getPreviousStep($challengeSteps) { + var length = $challengeSteps.length; + var $prevStep = false; + var prevStepIndex = 0; + $challengeSteps.each(function(index) { + var $step = $(this); + if ( + !$step.hasClass('hidden') && + index + 1 !== length + ) { + prevStepIndex = index - 1; + } + }); + + $prevStep = $challengeSteps[prevStepIndex]; + + return $prevStep; + } + function getNextStep($challengeSteps) { var length = $challengeSteps.length; var $nextStep = false; @@ -917,11 +941,36 @@ common.init.push((function() { return $nextStep; } + function handlePrevStepClick(e) { + e.preventDefault(); + var prevStep = getPreviousStep($(stepClass)); + $(this) + .parent() + .removeClass('fadeOutLeft') + .addClass('animated fadeOutRight fast-animation') + .delay(250) + .queue(function(prev) { + $(this).addClass('hidden'); + if (prevStep) { + $(prevStep) + .removeClass('hidden') + .removeClass('slideInRight') + .addClass('animated slideInLeft fast-animation') + .delay(500) + .queue(function(prev) { + prev(); + }); + } + prev(); + }); + } + function handleNextStepClick(e) { e.preventDefault(); var nextStep = getNextStep($(stepClass)); $(this) .parent() + .removeClass('fadeOutRight') .addClass('animated fadeOutLeft fast-animation') .delay(250) .queue(function(next) { @@ -929,10 +978,10 @@ common.init.push((function() { if (nextStep) { $(nextStep) .removeClass('hidden') + .removeClass('slideInLeft') .addClass('animated slideInRight fast-animation') .delay(500) .queue(function(next) { - $(this).removeClass('slideInRight'); next(); }); } @@ -1035,6 +1084,7 @@ common.init.push((function() { } return function($) { + $(prevBtnClass).click(handlePrevStepClick); $(nextBtnClass).click(handleNextStepClick); $(actionBtnClass).click(handleActionClick); $(finishBtnClass).click(handleFinishClick); diff --git a/client/less/main.less b/client/less/main.less index 94c0e1d77a..59adf6fb44 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -694,6 +694,24 @@ iframe.iphone { transition: background .2s ease-in-out, border .2s ease-in-out; } +.btn-warning-ghost { + background: transparent; + color: @brand-warning; + + /* CSS Transition */ + -webkit-transition: background .2s ease-in-out, border .2s ease-in-out; + -moz-transition: background .2s ease-in-out, border .2s ease-in-out; + -ms-transition: background .2s ease-in-out, border .2s ease-in-out; + -o-transition: background .2s ease-in-out, border .2s ease-in-out; + transition: background .2s ease-in-out, border .2s ease-in-out; +} + +.population-table { + @media (max-width: 767px) { + font-size: 16px; + } +} + @media (max-width: 991px) { .navbar-header { float: none; diff --git a/common/models/challenge.json b/common/models/challenge.json index 81d6af6eef..729cb97625 100644 --- a/common/models/challenge.json +++ b/common/models/challenge.json @@ -86,6 +86,9 @@ "solutions": { "type": "array", "default": [] + }, + "releasedOn": { + "type": "string" } }, "validations": [], diff --git a/package.json b/package.json index 38cdb03f2a..0271f59bf6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,11 @@ "prestart-production": "bower cache clean && bower install && gulp build", "start-production": "node pm2Start", "lint": "eslint --ext=.js,.jsx .", - "test": "gulp test-challenges" + "lint-challenges": "jsonlint -q seed/challenges/*.json", + "lint-nonprofits": "jsonlint -q seed/nonprofits.json", + "test-challenges": "babel-node seed/test-challenges.js | tnyan", + "pretest": "npm run lint-challenges && npm run lint-nonprofits", + "test": "npm run test-challenges" }, "license": "(BSD-3-Clause AND CC-BY-SA-4.0)", "dependencies": { @@ -127,11 +131,14 @@ "chai": "~1.10.0", "envify": "^3.4.0", "istanbul": "^0.3.15", + "jsonlint": "^1.6.2", "loopback-explorer": "^1.7.2", "loopback-testing": "^1.1.0", "mocha": "~2.0.1", "multiline": "~1.0.1", "supertest": "~0.15.0", + "tap-nyan": "0.0.2", + "tape": "^4.2.2", "vinyl-source-stream": "^1.1.0" } } diff --git a/seed/challenge-migration.js b/seed/challenge-migration.js deleted file mode 100644 index 7ef359781f..0000000000 --- a/seed/challenge-migration.js +++ /dev/null @@ -1,44 +0,0 @@ -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'); - -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/challengeMapping.json b/seed/challengeMapping.json deleted file mode 100644 index db6686d266..0000000000 --- a/seed/challengeMapping.json +++ /dev/null @@ -1,226 +0,0 @@ -[ - { - "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": "" - } -] diff --git a/seed/challenges/advanced-bonfires.json b/seed/challenges/advanced-bonfires.json index 04ab7c18b2..115b236eba 100644 --- a/seed/challenges/advanced-bonfires.json +++ b/seed/challenges/advanced-bonfires.json @@ -157,7 +157,6 @@ "Global Object" ], "solutions": [ - "var VALUES = [1, 5, 10, 25, 100, 500, 1000, 2000, 10000];\n\nfunction drawer(price, cash, cid) {\n cash = ~~(cash * 100);\n price = ~~(price * 100);\n var diff = cash-price;\n cid.forEach(function(c) {\n c[1] = ~~(c[1] * 100);\n });\n var totalCid = cid.reduce(function(a, c) {\n return a + c[1];\n }, 0);\n if (diff > totalCid) {\n return \"Insufficient Funds\";\n }\n if (diff === totalCid) {\n return \"Closed\";\n }\n \n var change = []; \n var index = cid.length;\n while (diff > 0 && --index > -1) {\n var t = 0;\n var value = VALUES[index];\n while (diff >= value && cid[index][1] > 0) {\n t += value;\n cid[index][1] -= value;\n diff -= value;\n }\n if (t) {\n change.push([cid[index][0], t/100]);\n }\n console.log(JSON.stringify(change));\n }\n // Here is your change, ma'am.\n return change;\n}\n\n// Example cash-in-drawer array:\n// [['PENNY', 1.01],\n// ['NICKEL', 2.05],\n// ['DIME', 3.10],\n// ['QUARTER', 4.25],\n// ['ONE', 90.00],\n// ['FIVE', 55.00],\n// ['TEN', 20.00],\n// ['TWENTY', 60.00],\n// ['ONE HUNDRED', 100.00]]\n\ndrawer(19.50, 20.00, [['PENNY', 1.01], ['NICKEL', 2.05], ['DIME', 3.10], ['QUARTER', 4.25], ['ONE', 90.00], ['FIVE', 55.00], ['TEN', 20.00], ['TWENTY', 60.00], ['ONE HUNDRED', 100.00]]);\n" ], "type": "bonfire", "challengeType": 5, diff --git a/seed/challenges/angularjs.json b/seed/challenges/angularjs.json index 905c120f33..c55221e0fe 100644 --- a/seed/challenges/angularjs.json +++ b/seed/challenges/angularjs.json @@ -14,7 +14,12 @@ ], "type": "waypoint", "challengeType": 2, - "tests": [], + "tests": [ + "assert(true, 'No test needed');" + ], + "solutions": [ + "/* no test needed */" + ], "nameCn": "", "descriptionCn": [], "nameFr": "", @@ -37,7 +42,12 @@ ], "type": "waypoint", "challengeType": 2, - "tests": [], + "tests": [ + "assert(true, 'No test needed');" + ], + "solutions": [ + "/* no test needed */" + ], "nameCn": "", "descriptionCn": [], "nameFr": "", @@ -60,7 +70,12 @@ ], "type": "waypoint", "challengeType": 2, - "tests": [], + "tests": [ + "assert(true, 'No test needed');" + ], + "solutions": [ + "/* no test needed */" + ], "nameCn": "", "descriptionCn": [], "nameFr": "", @@ -82,7 +97,12 @@ ], "type": "waypoint", "challengeType": 2, - "tests": [], + "tests": [ + "assert(true, 'No test needed');" + ], + "solutions": [ + "/* no test needed */" + ], "nameCn": "", "descriptionCn": [], "nameFr": "", @@ -105,7 +125,12 @@ ], "type": "waypoint", "challengeType": 2, - "tests": [], + "tests": [ + "assert(true, 'No test needed');" + ], + "solutions": [ + "/* no test needed */" + ], "nameCn": "", "descriptionCn": [], "nameFr": "", diff --git a/seed/challenges/basic-bonfires.json b/seed/challenges/basic-bonfires.json index 8ac6cbcae7..505b91fd5e 100644 --- a/seed/challenges/basic-bonfires.json +++ b/seed/challenges/basic-bonfires.json @@ -116,7 +116,7 @@ "Arithmetic Operators" ], "solutions": [ - "function factorialize(num) {\n return num === 1 ? 1 : num * factorialize(num-1);\n}\n\nfactorialize(5);\n" + "function factorialize(num) {\n return num < 1 ? 1 : num * factorialize(num-1);\n}\n\nfactorialize(5);\n" ], "type": "bonfire", "challengeType": 5, @@ -169,7 +169,6 @@ "String.toLowerCase()" ], "solutions": [ - "function palindrome(str) {\n var a = str.toLowerCase().replace(/[^a-z]/g, '');\n console.log(a.split('').reverse().join(''));\n return a == a.split('').reverse().join('');\n}\n\n\n\npalindrome(\"eye\");\npalindrome(\"A man, a plan, a canal. Panama\");\n" ], "type": "bonfire", "challengeType": 5, @@ -243,8 +242,8 @@ "titleCase(\"I'm a little tea pot\");" ], "tests": [ - "assert(typeof(titleCase(\"I'm a little tea pot\")) === \"string\", 'message: titleCase() should return a string.');", - "assert(titleCase(\"I'm a little tea pot\") === \"I'm A Little Tea Pot\", 'message: titleCase(\"I'm a little tea pot\") should return \"I'm A Little Tea Pot\".');", + "assert(typeof(titleCase(\"I'm a little tea pot\")) === \"string\", 'message: titleCase() should return a string.');", + "assert(titleCase(\"I'm a little tea pot\") === \"I'm A Little Tea Pot\", 'message: titleCase(\"I'm a little tea pot\") should return \"I'm A Little Tea Pot\".');", "assert(titleCase(\"sHoRt AnD sToUt\") === \"Short And Stout\", 'message: titleCase(\"sHoRt AnD sToUt\") should return \"Short And Stout\".');", "assert(titleCase(\"HERE IS MY HANDLE HERE IS MY SPOUT\") === \"Here Is My Handle Here Is My Spout\", 'message: titleCase(\"HERE IS MY HANDLE HERE IS MY SPOUT\") should return \"Here Is My Handle Here Is My Spout\".');" ], @@ -273,7 +272,6 @@ "description": [ "Return an array consisting of the largest number from each provided sub-array. For simplicity, the provided array will contain exactly 4 sub-arrays.", "Remember, you can iterate through an array with a simple for loop, and access each member with array syntax arr[i] .", - "If you are writing your own Chai.js tests, be sure to use a deep equal statement instead of an equal statement when comparing arrays.", "Remember to use Read-Search-Ask if you get stuck. Write your own code." ], "challengeSeed": [ @@ -418,7 +416,6 @@ "String.slice()" ], "solutions": [ - "function truncate(str, num) {\n if (str.length > num) {\n return str.substring(0, num-3) + '...';\n }\n return str;\n}\n\ntruncate('A-tisket a-tasket A green and yellow basket', 11);\n" ], "type": "bonfire", "challengeType": 5, @@ -664,9 +661,7 @@ "MDNlinks": [ "Array.sort()" ], - "solutions": [ - "function where(arr, num) {\n // Find my place in this sorted array.\n return num;\n}\n\nwhere([40, 60], 50);\n" - ], + "solutions": [], "tests": [ "assert(where([10, 20, 30, 40, 50], 35) === 3, 'message: where([10, 20, 30, 40, 50], 35) should return 3.');", "assert(where([10, 20, 30, 40, 50], 30) === 2, 'message: where([10, 20, 30, 40, 50], 30) should return 2.');", diff --git a/seed/challenges/basic-javascript.json b/seed/challenges/basic-javascript.json index 14769b9818..c8663c4f27 100644 --- a/seed/challenges/basic-javascript.json +++ b/seed/challenges/basic-javascript.json @@ -653,7 +653,7 @@ "In JavaScript, we can divide up our code into reusable parts called functions.", "Here's an example of a function:", "function functionName(a, b) {", - "  return a + b;", + "  return a + b;", "}", "After writing the above lines in our code, we can then pass values to our function and the result following the return statement will be returned.", "For example, we can pass numbers 4 and 2 by “calling” the function later in our code like this: functionName(4, 2).", @@ -694,10 +694,10 @@ "Objects are similar to arrays, except that instead of using indexes to access and modify their data, you access the data in objects through what are called properties.", "Here's a sample object:", "var cat = {", - "  \"name\": \"Whiskers\",", - "  \"legs\": 4,", - "  \"tails\": 1,", - "  \"enemies\": [\"Water\", \"Dogs\"]", + "  \"name\": \"Whiskers\",", + "  \"legs\": 4,", + "  \"tails\": 1,", + "  \"enemies\": [\"Water\", \"Dogs\"]", "};", "", "Objects are useful for storing data in a structured way, and can represent real world objects, like a cat.", @@ -741,10 +741,10 @@ "After you've created a JavaScript object, you can update its properties at any time just like you would update any other variable.", "For example, let's look at ourDog:", "var ourDog = {", - "  \"name\": \"Camper\",", - "  \"legs\": 4,", - "  \"tails\": 1,,", - "  \"friends\": [\"everything!\"]", + "  \"name\": \"Camper\",", + "  \"legs\": 4,", + "  \"tails\": 1,,", + "  \"friends\": [\"everything!\"]", "};", "Since he's a particularly happy dog, let's change his name to \"Happy Camper\". Here's how we update his object's name property:", "ourDog.name = \"Happy Camper\";", @@ -868,16 +868,16 @@ "The most common type of JavaScript loop is called a \"for loop\" because it runs \"for\" a specific number of times.", "For loops are declared with three optional expressions seperated by semicolons:", "for([initialization]; [condition]; [final-expression])", - "The initialization statement is executed one time only before the loop starts. It is typically used to define and setup your loop varaible.", - "The condition statement is evaluated at the beginning of every loop and will continue as long as it evalutes true. When condition is false at the start of the loop, the loop will stop executing. This means if condition starts as false, your loop will never execute.", + "The initialization statement is executed one time only before the loop starts. It is typically used to define and setup your loop variable.", + "The condition statement is evaluated at the beginning of every loop iteration and will continue as long as it evalutes to true. When condition is false at the start of the iteration, the loop will stop executing. This means if condition starts as false, your loop will never execute.", "The final-expression is executed at the end of each loop iteration, prior to the next condition check and is usually used to increment or decrement your loop counter.", - "We'll initialize with i = 0 and loop while our condition i < 5 is true. We'll increment i by 1 each loop with i++ as our final-expression.", + "In the following example we initialize with i = 0 and iterate while our condition i < 5 is true. We'll increment i by 1 in each loop iteration with i++ as our final-expression.", "var ourArray = [];", "for(var i = 0; i < 5; i++) {", - "  ourArray.push(i);", + "  ourArray.push(i);", "}", - "ourArray will now contain [0,1,2,3,4] ", - "Let's try getting a for loop to work by pushing values to an array." + "ourArray will now contain [0,1,2,3,4].", + "Let's try getting a for loop to work by pushing values to an array." ], "tests": [ "assert(editor.getValue().match(/for\\s*\\(/g).length > 1, 'message: You should be using a for loop for this.');", @@ -912,7 +912,7 @@ "We'll start at i = 0 and loop while i < 10. We'll increment i by 2 each loop with i += 2.", "var ourArray = [];", "for(var i = 0; i < 10; i += 2) {", - "  ourArray.push(i);", + "  ourArray.push(i);", "}", "ourArray will now contain [0,2,4,6,8] ", "Let's change our initialization and final-expression so we can count by odd numbers.", @@ -925,7 +925,7 @@ "challengeSeed":[ "var ourArray = [];", "", - "for(var i = 1; i < 10; i += 2){", + "for(var i = 0; i < 10; i += 2){", " ourArray.push(i);", "}", "", @@ -952,7 +952,7 @@ "We'll start at i = 10 and loop while i > 0. We'll decrement i by 2 each loop with i -= 2.", "var ourArray = [];", "for(var i = 10; i > 0; i -= 2) {", - "  ourArray.push(i);", + "  ourArray.push(i);", "}", "ourArray will now contain [10,8,6,4,2]", "Let's change our initialization and final-expression so we can count backward by twos for numbers.", @@ -992,8 +992,8 @@ "var ourArray = [];", "var i = 0;", "while(i < 5) {", - "  ourArray.push(i);", - "  i++;", + "  ourArray.push(i);", + "  i++;", "}", "Let's try getting a while loop to work by pushing values to an array.", "Push the numbers 0 through 4 to myArray using a while loop." @@ -1093,7 +1093,7 @@ "Here's the formula we'll use. Take a moment to read it and try to understand what this code is doing:", "Math.floor(Math.random() * (max - min + 1)) + min", "Define two variables: myMin and myMax, and set them both equal to numbers.", - "Then create a function called myFunction that returns a random number that's greater than or equal to myMin, and is less than myMax." + "Then create a function called myFunction that returns a random number that's greater than or equal to myMin, and is less than or equal to myMax." ], "tests": [ "assert(myFunction() >= myMin, 'message: The random number generated by myFunction should be greater than or equal to your minimum number, myMin.');", @@ -1140,9 +1140,9 @@ "if statements require some sort of boolean condition to evaluate.", "For example:", "if (1 === 2) {", - "  return true;", + "  return true;", "} else {", - "  return false;", + "  return false;", "}", "Let's use if and else statements to make a coin-flip game.", "Create if and else statements to return the string \"heads\" if the flip variable is zero, or else return the string \"tails\" if the flip variable is not zero." @@ -1183,7 +1183,7 @@ "i means that we want to ignore the case (uppercase or lowercase) when searching for the pattern.", "Regular expressions are written by surrounding the pattern with / symbols.", "Let's try selecting all the occurrences of the word and in the string Ada Lovelace and Charles Babbage designed the first computer and the software that would have run on it.", - "We can do this by replacing the . part of our regular expression with the current regular expression with the word and." + "We can do this by replacing the . part of our regular expression with the word and." ], "tests": [ "assert(test==2, 'message: Your regular expression should find two occurrences of the word and.');", @@ -1460,7 +1460,7 @@ "If all three numbers match, we should return the number that we have in three of slots or leave it as null.", "Let's create an if statement with multiple conditions in order to check whether all numbers are equal.", "if(slotOne !== slotTwo || slotTwo !== slotThree){", - "  return null;", + "  return null;", "}" ], "tests": [ diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index 4306f0b5f7..913d4a26db 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -183,7 +183,8 @@ "<h2 class=\"red-text text-center\">your text</h2>" ], "tests": [ - "assert($(\"h2\").hasClass(\"text-center\"), 'Your h2 element should be centered by applying the class text-center')" + "assert($(\"h2\").hasClass(\"text-center\"), 'Your h2 element should be centered by applying the class text-center')", + "assert($(\"h2\").hasClass(\"red-text\"), 'Your h2 element should still have the class red-text')" ], "challengeSeed": [ "", diff --git a/seed/challenges/html5-and-css.json b/seed/challenges/html5-and-css.json index 4372a5cf6b..6ceb796a9c 100644 --- a/seed/challenges/html5-and-css.json +++ b/seed/challenges/html5-and-css.json @@ -381,7 +381,7 @@ "</style>", "Inside that style element, you can create a CSS selector for all h2 elements. For example, if you wanted all h2 elements to be red, your style element would look like this:", "<style>", - "  h2 {color: red;}", + "  h2 {color: red;}", "</style>", "Note that it's important to have both opening and closing curly braces ({ and }) around each element's style. You also need to make sure your element's style is between the opening and closing style tags. Finally, be sure to add the semicolon to the end of each of your element's styles.", "Delete your h2 element's style attribute and instead create a CSS style element. Add the necessary CSS to turn all h2 elements blue." @@ -390,7 +390,7 @@ "assert(!$(\"h2\").attr(\"style\"), 'Remove the style attribute from your h2 element.')", "assert($(\"style\") && $(\"style\").length > 1, 'Create a style element.')", "assert($(\"h2\").css(\"color\") === \"rgb(0, 0, 255)\", 'Your h2 element should be blue.')", - "assert(editor.match(/h2\\s*\\{\\s*color:\\s*blue;\\s*\\}/g), 'Ensure that your stylesheet h2 declaration is valid with a semicolon and closing brace')", + "assert(editor.match(/h2\\s*\\{\\s*color\\s*:\\s*blue;\\s*\\}/g), 'Ensure that your stylesheet h2 declaration is valid with a semicolon and closing brace')", "assert(editor.match(/<\\/style>/g) && editor.match(/<\\/style>/g).length === (editor.match(//g) || []).length, 'Make sure all your style elements are valid and have a closing tag.')" ], "challengeSeed": [ @@ -436,9 +436,9 @@ "Classes are reusable styles that can be added to HTML elements.", "Here's an example CSS class declaration:", "<style>", - "  .blue-text {", - "    color: blue;", - "  }", + "  .blue-text {", + "    color: blue;", + "  }", "</style>", "You can see that we've created a CSS class called blue-text within the <style> tag.", "You can apply a class to an HTML element like this:", @@ -504,7 +504,7 @@ "Remember that you can attach classes to HTML elements by using class=\"your-class-here\" within the relevant element's opening tag.", "Remember that CSS class selectors require a period at the beginning like this:", ".blue-text {", - "  color: blue;", + "  color: blue;", "}", "But also remember that class declarations don't use a period, like this:", "<h2 class=\"blue-text\">CatPhotoApp<h2>", @@ -556,7 +556,7 @@ "description": [ "Font size is controlled by the font-size CSS property, like this:", "h1 {", - "  font-size: 30px;", + "  font-size: 30px;", "}", "See if you can figure out how to give both of your p elements the font-size of 16 pixels (16px). You can do this inside the same <style> tag that we created for your red-text class.", "Create a second p element with the following kitty ipsum text: Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.", @@ -611,7 +611,7 @@ "You can set an element's font by using the font-family property.", "For example, if you wanted to set your h2 element's font to Sans-serif, you would use the following CSS:", "h2 {", - "  font-family: Sans-serif;", + "  font-family: Sans-serif;", "}", "Make all of your p elements use the Monospace font." ], @@ -715,7 +715,7 @@ "When one font isn't available, you can tell the browser to \"degrade\" to another font.", "For example, if you wanted an element to use the Helvetica font, but also degrade to the Sans-Serif font when Helvetica wasn't available, you could use this CSS style:", "p {", - "  font-family: Helvetica, Sans-Serif;", + "  font-family: Helvetica, Sans-Serif;", "}", "Now comment out your call to Google Fonts, so that the Lobster font isn't available. Notice how it degrades to the Monospace font." ], @@ -832,9 +832,9 @@ "CSS has a property called width that controls an element's width. Just like with fonts, we'll use px (pixels) to specify the image's width.", "For example, if we wanted to create a CSS class called larger-image that gave HTML elements a width of 500 pixels, we'd use:", "<style>", - "  .larger-image {", - "    width: 500px;", - "  }", + "  .larger-image {", + "    width: 500px;", + "  }", "</style>", "Create a class called smaller-image and use it to resize the image so that it's only 100 pixels wide." ], @@ -892,11 +892,11 @@ "CSS borders have properties like style, color and width", "For example, if we wanted to create a red, 5 pixel border around an HTML element, we could use this class:", "<style>", - "  .thin-red-border {", - "    border-color: red;", - "    border-width: 5px;", - "    border-style: solid;", - "  }", + "  .thin-red-border {", + "    border-color: red;", + "    border-width: 5px;", + "    border-style: solid;", + "  }", "</style>", "Create a class called thick-green-border that puts a 10-pixel-wide green border with a style of solid around an HTML element, and apply that class to your cat photo.", "Remember that you can apply multiple classes to an element by separating each class with a space within its class attribute. For example:", @@ -1172,7 +1172,7 @@ "assert($(\"a\").text().match(/cat\\sphotos/gi), 'Your a element should have the anchor text of \"cat photos\"')", "assert($(\"p\") && $(\"p\").length > 2, 'Create a new p element around your a element.')", "assert($(\"a[href=\\\"http://www.freecatphotoapp.com\\\"]\").parent().is(\"p\"), 'Your a element should be nested within your new p element.')", - "assert($(\"p\").text().match(/^\\s*View\\smore\\s/gi), 'Your p element should have the text \"View more \" (with a space after it).')", + "assert($(\"a[href=\\\"http://www.freecatphotoapp.com\\\"]\").parent().text().match(/^\\s*View\\smore\\s/gi), 'Your p element should have the text \"View more \" (with a space after it).')", "assert(!$(\"a\").text().match(/View\\smore/gi), 'Your a element should not have the text \"View more\".')", "assert(editor.match(/<\\/p>/g) && editor.match(/

/g).length === editor.match(/

p elements has a closing tag.')", "assert(editor.match(/<\\/a>/g) && editor.match(//g).length === editor.match(/a elements has a closing tag.')" @@ -1453,11 +1453,11 @@ "Unordered lists start with a <ul> element. Then they contain some number of <li> elements.", "For example: ", "<ul>", - "  <li>milk</li>", - "  <li>cheese</li>", + "  <li>milk</li>", + "  <li>cheese</li>", "</ul>", "would create a bullet point-style list of \"milk\" and \"cheese\".", - "Replace your p elements with an unordered list of three things that cats love." + "Remove the last two p elements and create an unordered list of three things that cats love at the bottom of the page." ], "tests": [ "assert($(\"ul\").length > 0, 'Create a ul element.')", @@ -1530,8 +1530,8 @@ "Ordered lists start with a <ol> element. Then they contain some number of <li> elements.", "For example:", "<ol>", - "  <li>Garfield</li>", - "  <li>Sylvester</li>", + "  <li>Garfield</li>", + "  <li>Sylvester</li>", "</ol>", "would create a numbered list of \"Garfield\" and \"Sylvester\".", "Create an ordered list of the top 3 things cats hate the most." @@ -2024,7 +2024,7 @@ "All related radio buttons should have the same name attribute.", "Here's an example of a radio button:", "<label><input type=\"radio\" name=\"indoor-outdoor\"> Indoor</label>", - "Add to your form a pair of radio buttons. Each radio button should be nested within its own label element. They should share a common name attribute. One should have the option of indoor and the other should have the option of outdoor." + "Add a pair of radio buttons to your form. One should have the option of indoor and the other should have the option of outdoor." ], "tests": [ "assert($('input[type=\"radio\"]').length > 1, 'Your page should have two radio button elements.')", @@ -2386,7 +2386,7 @@ "You can set an element's background color with the background-color property.", "For example, if you wanted an element's background color to be green, you'd put this within your style element:", ".green-background {", - "  background-color: green;", + "  background-color: green;", "}", "Create a class called gray-background with the background-color of gray. Assign this class to your div element." ], @@ -2566,7 +2566,7 @@ "One cool thing about id attributes is that, like classes, you can style them using CSS.", "Here's an example of how you can take your element with the id attribute of cat-photo-element and give it the background color of green. In your style element:", "#cat-photo-element {", - "  background-color: green;", + "  background-color: green;", "}", "Note that inside your style element, you always reference classes by putting a . in front of their names. You always reference ids by putting a # in front of their names.", "Try giving your form, which now has the id attribute of cat-photo-form, a green background." @@ -3153,7 +3153,7 @@ "We can prove that the body element exists here by giving it a background-color of black.", "We can do this by adding the following to our style element:", "body {", - "  background-color: black;", + "  background-color: black;", "}" ], "tests": [ @@ -3316,7 +3316,7 @@ "Leave the blue-text and pink-text classes on your h1 element.", "Create a CSS declaration for your orange-text id in your style element. Here's an example of what this looks like:", "#brown-text {", - "  color: brown;", + "  color: brown;", "}" ], "tests": [ diff --git a/seed/challenges/intermediate-bonfires.json b/seed/challenges/intermediate-bonfires.json index 9ae783a1a5..6d90d9fdd7 100644 --- a/seed/challenges/intermediate-bonfires.json +++ b/seed/challenges/intermediate-bonfires.json @@ -31,7 +31,7 @@ "Array.reduce()" ], "solutions": [ - "function sumAll(arr) {\n var sum = 0;\n arr.sort(function(a,b) {return a-b;});\n for (var i = arr[0]; i <= arr[1]; i++) {\n sum += i; \n }\n return sum;\n}\n\nsumAll([1, 4]);\n" + "function sumAll(arr) {\n var sum = 0;\n arr.sort(function(a,b) {return a-b;});\n for (var i = arr[0]; i <= arr[1]; i++) {\n sum += i; \n }\n return sum;\n}" ], "type": "bonfire", "challengeType": 5, @@ -80,7 +80,7 @@ "Array.concat()" ], "solutions": [ - "function diff(arr1, arr2) {\n var newArr = [];\n var h1 = Object.create(null);\n arr1.forEach(function(e) {\n h1[e] = e;\n });\n \n var h2 = Object.create(null);\n arr2.forEach(function(e) {\n h2[e] = e;\n });\n \n Object.keys(h1).forEach(function(e) {\n if (!(e in h2)) newArr.push(h1[e]);\n });\n Object.keys(h2).forEach(function(e) {\n if (!(e in h1)) newArr.push(h2[e]);\n });\n // Same, same; but different.\n return newArr;\n}\n\ndiff([1, 2, 3, 5], [1, 2, 3, 4, 5]);\n" + "function diff(arr1, arr2) {\n var newArr = [];\n var h1 = Object.create(null);\n arr1.forEach(function(e) {\n h1[e] = e;\n });\n \n var h2 = Object.create(null);\n arr2.forEach(function(e) {\n h2[e] = e;\n });\n \n Object.keys(h1).forEach(function(e) {\n if (!(e in h2)) newArr.push(h1[e]);\n });\n Object.keys(h2).forEach(function(e) {\n if (!(e in h1)) newArr.push(h2[e]);\n });\n // Same, same; but different.\n return newArr;\n}" ], "type": "bonfire", "challengeType": 5, @@ -138,7 +138,7 @@ "Array.join()" ], "solutions": [ - "function convert(num) {\n var ref = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]];\n var res = [];\n ref.forEach(function(p) {\n while (num >= p[1]) {\n res.push(p[0]);\n num -= p[1];\n }\n });\n return res.join('');\n}\n\nconvert(36);\n" + "function convert(num) {\n var ref = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]];\n var res = [];\n ref.forEach(function(p) {\n while (num >= p[1]) {\n res.push(p[0]);\n num -= p[1];\n }\n });\n return res.join('');\n}" ], "type": "bonfire", "challengeType": 5, @@ -181,7 +181,7 @@ "Object.keys()" ], "solutions": [ - "function where(collection, source) {\n var arr = [];\n var keys = Object.keys(source);\n collection.forEach(function(e) {\n if(keys.every(function(key) {return e[key] === source[key];})) {\n arr.push(e); \n }\n });\n return arr;\n}\n\nwhere([{ first: 'Romeo', last: 'Montague' }, { first: 'Mercutio', last: null }, { first: 'Tybalt', last: 'Capulet' }], { last: 'Capulet' });\n" + "function where(collection, source) {\n var arr = [];\n var keys = Object.keys(source);\n collection.forEach(function(e) {\n if(keys.every(function(key) {return e[key] === source[key];})) {\n arr.push(e); \n }\n });\n return arr;\n}" ], "type": "bonfire", "challengeType": 5, @@ -227,7 +227,7 @@ "Array.join()" ], "solutions": [ - "function replace(str, before, after) {\n if (before.charAt(0) === before.charAt(0).toUpperCase()) {\n after = after.charAt(0).toUpperCase() + after.substring(1);\n } else {\n after = after.charAt(0).toLowerCase() + after.substring(1);\n }\n return str.replace(before, after);\n}\n\nreplace(\"A quick brown fox jumped over the lazy dog\", \"jumped\", \"leaped\");\n" + "function myReplace(str, before, after) {\n if (before.charAt(0) === before.charAt(0).toUpperCase()) {\n after = after.charAt(0).toUpperCase() + after.substring(1);\n } else {\n after = after.charAt(0).toLowerCase() + after.substring(1);\n }\n return str.replace(before, after);\n}" ], "type": "bonfire", "challengeType": 5, @@ -273,7 +273,7 @@ "String.split()" ], "solutions": [ - "function translate(str) {\n if (isVowel(str.charAt(0))) return str + \"way\";\n var front = [];\n str = str.split('');\n while (str.length && !isVowel(str[0])) {\n front.push(str.shift());\n }\n return [].concat(str, front).join('') + 'ay';\n}\n\nfunction isVowel(c) {\n return ['a', 'e', 'i', 'o', 'u'].indexOf(c.toLowerCase()) !== -1;\n}\n\ntranslate(\"consonant\");\n" + "function translate(str) {\n if (isVowel(str.charAt(0))) return str + \"way\";\n var front = [];\n str = str.split('');\n while (str.length && !isVowel(str[0])) {\n front.push(str.shift());\n }\n return [].concat(str, front).join('') + 'ay';\n}\n\nfunction isVowel(c) {\n return ['a', 'e', 'i', 'o', 'u'].indexOf(c.toLowerCase()) !== -1;\n}" ], "type": "bonfire", "challengeType": 5, @@ -316,7 +316,7 @@ "String.split()" ], "solutions": [ - "var lookup = Object.create(null);\nlookup.A = 'T';\nlookup.T = 'A';\nlookup.C = 'G';\nlookup.G = 'C';\n\nfunction pair(str) {\n return str.split('').map(function(p) {return [p, lookup[p]];});\n}\n\npair(\"GCG\");\n" + "var lookup = Object.create(null);\nlookup.A = 'T';\nlookup.T = 'A';\nlookup.C = 'G';\nlookup.G = 'C';\n\nfunction pair(str) {\n return str.split('').map(function(p) {return [p, lookup[p]];});\n}" ], "type": "bonfire", "challengeType": 5, @@ -357,7 +357,7 @@ "String.fromCharCode()" ], "solutions": [ - "function fearNotLetter(str) {\n var s = str.split('').map(function(c) {return c.charCodeAt(0);});\n for (var i = 1; i < s.length; i++) {\n if (s[i]-1 != s[i-1]) {\n return String.fromCharCode(s[i]-1);\n }\n }\n}\n\nfearNotLetter('abce');\n" + "function fearNotLetter(str) {\n var s = str.split('').map(function(c) {return c.charCodeAt(0);});\n for (var i = 1; i < s.length; i++) {\n if (s[i]-1 != s[i-1]) {\n return String.fromCharCode(s[i]-1);\n }\n }\n}" ], "type": "bonfire", "challengeType": 5, @@ -402,7 +402,7 @@ "Boolean Objects" ], "solutions": [ - "function boo(bool) {\n // What is the new fad diet for ghost developers? The Boolean.\n return typeof(bool) === \"boolean\";\n}\n\nboo(null);\n" + "function boo(bool) {\n // What is the new fad diet for ghost developers? The Boolean.\n return typeof(bool) === \"boolean\";\n}\n\nboo(null);" ], "type": "bonfire", "challengeType": 5, @@ -445,7 +445,7 @@ "Array.reduce()" ], "solutions": [ - "function unite(arr1, arr2, arr3) {\n return [].slice.call(arguments).reduce(function(a, b) {\n return [].concat(a, b.filter(function(e) {return a.indexOf(e) === -1;}));\n }, []);\n}\n\nunite([1, 2, 3], [5, 2, 1, 4], [2, 1]);\n" + "function unite(arr1, arr2, arr3) {\n return [].slice.call(arguments).reduce(function(a, b) {\n return [].concat(a, b.filter(function(e) {return a.indexOf(e) === -1;}));\n }, []);\n}" ], "type": "bonfire", "challengeType": 5, @@ -489,7 +489,7 @@ "HTML Entities" ], "solutions": [ - "var MAP = { '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''};\n\nfunction convert(str) {\n return str.replace(/[&<>\"']/g, function(c) {\n return MAP[c];\n });\n}\n\nconvert('Dolce & Gabbana');\n" + "var MAP = { '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''};\n\nfunction convert(str) {\n return str.replace(/[&<>\"']/g, function(c) {\n return MAP[c];\n });\n}" ], "type": "bonfire", "challengeType": 5, @@ -531,7 +531,7 @@ "String.replace()" ], "solutions": [ - "function spinalCase(str) {\n // \"It's such a fine line between stupid, and clever.\"\n // --David St. Hubbins\n str = str.replace(/([a-z](?=[A-Z]))/g, '$1 ');\n return str.toLowerCase().replace(/\\ |\\_/g, '-');\n}\n\nspinalCase('This Is Spinal Tap');\n" + "function spinalCase(str) {\n // \"It's such a fine line between stupid, and clever.\"\n // --David St. Hubbins\n str = str.replace(/([a-z](?=[A-Z]))/g, '$1 ');\n return str.toLowerCase().replace(/\\ |\\_/g, '-');\n}" ], "type": "bonfire", "challengeType": 5, @@ -574,7 +574,7 @@ "Remainder" ], "solutions": [ - "function sumFibs(num) {\n var a = 1; \n var b = 1;\n var s = 0;\n while (a <= num) {\n if (a % 2 !== 0) { \n s += a; \n }\n a = [b, b=b+a][0];\n }\n return s;\n}\n\nsumFibs(4);\n" + "function sumFibs(num) {\n var a = 1; \n var b = 1;\n var s = 0;\n while (a <= num) {\n if (a % 2 !== 0) { \n s += a; \n }\n a = [b, b=b+a][0];\n }\n return s;\n}" ], "type": "bonfire", "challengeType": 5, @@ -615,7 +615,7 @@ "Array.push()" ], "solutions": [ - "function eratosthenesArray(n) {\n var primes = [];\n if (n > 2) {\n var half = n>>1;\n var sieve = Array(half);\n for (var i = 1, limit = Math.sqrt(n)>>1; i <= limit; i++) {\n if (!sieve[i]) {\n for (var step = 2*i+1, j = (step*step)>>1; j < half; j+=step) {\n sieve[j] = true;\n }\n }\n }\n primes.push(2);\n for (var p = 1; p < half; p++) {\n if (!sieve[p]) primes.push(2*p+1);\n }\n }\n return primes;\n}\n\nfunction sumPrimes(num) {\n return eratosthenesArray(num+1).reduce(function(a,b) {return a+b;}, 0);\n}\n\nsumPrimes(10);\n" + "function eratosthenesArray(n) {\n var primes = [];\n if (n > 2) {\n var half = n>>1;\n var sieve = Array(half);\n for (var i = 1, limit = Math.sqrt(n)>>1; i <= limit; i++) {\n if (!sieve[i]) {\n for (var step = 2*i+1, j = (step*step)>>1; j < half; j+=step) {\n sieve[j] = true;\n }\n }\n }\n primes.push(2);\n for (var p = 1; p < half; p++) {\n if (!sieve[p]) primes.push(2*p+1);\n }\n }\n return primes;\n}\n\nfunction sumPrimes(num) {\n return eratosthenesArray(num+1).reduce(function(a,b) {return a+b;}, 0);\n}\n\nsumPrimes(10);" ], "type": "bonfire", "challengeType": 5, @@ -657,7 +657,7 @@ "Smallest Common Multiple" ], "solutions": [ - "function gcd(a, b) {\n while (b !== 0) {\n a = [b, b = a % b][0];\n }\n return a;\n}\n\nfunction lcm(a, b) {\n return (a * b) / gcd(a, b);\n}\n\nfunction smallestCommons(arr) {\n arr.sort(function(a,b) {return a-b;});\n var rng = [];\n for (var i = arr[0]; i <= arr[1]; i++) {\n rng.push(i);\n }\n return rng.reduce(lcm);\n}\n\n\nsmallestCommons([1,5]);\n" + "function gcd(a, b) {\n while (b !== 0) {\n a = [b, b = a % b][0];\n }\n return a;\n}\n\nfunction lcm(a, b) {\n return (a * b) / gcd(a, b);\n}\n\nfunction smallestCommons(arr) {\n arr.sort(function(a,b) {return a-b;});\n var rng = [];\n for (var i = arr[0]; i <= arr[1]; i++) {\n rng.push(i);\n }\n return rng.reduce(lcm);\n}" ], "type": "bonfire", "challengeType": 5, @@ -695,7 +695,7 @@ "Array.filter()" ], "solutions": [ - "function find(arr, func) {\n var num;\n arr.some(function(e) {\n if (func(e)) {\n num = e;\n return true;\n }\n });\n return num;\n}\n\nfind([1, 2, 3, 4], function(num){ return num % 2 === 0; });\n" + "function find(arr, func) {\n var num;\n arr.some(function(e) {\n if (func(e)) {\n num = e;\n return true;\n }\n });\n return num;\n}" ], "type": "bonfire", "challengeType": 5, @@ -736,7 +736,7 @@ "Array.shift()" ], "solutions": [ - "(function drop(arr, func) {\n // Drop them elements.\n while (arr.length && !func(arr[0])) {\n arr.shift();\n }\n return arr;\n}\n\ndrop([1, 2, 3], function(n) {return n < 3; });\n)" + "function drop(arr, func) {\n // Drop them elements.\n while (arr.length && !func(arr[0])) {\n arr.shift();\n }\n return arr;\n}" ], "type": "bonfire", "challengeType": 5, @@ -776,7 +776,7 @@ "Array.isArray()" ], "solutions": [ - "function steamroller(arr) {\n if (!Array.isArray(arr)) {\n return [arr];\n }\n var out = [];\n arr.forEach(function(e) {\n steamroller(e).forEach(function(v) {\n out.push(v);\n });\n });\n return out;\n}\n\nsteamroller([1, [2], [3, [[4]]]]);\n" + "function steamroller(arr) {\n if (!Array.isArray(arr)) {\n return [arr];\n }\n var out = [];\n arr.forEach(function(e) {\n steamroller(e).forEach(function(v) {\n out.push(v);\n });\n });\n return out;\n}" ], "type": "bonfire", "challengeType": 5, @@ -815,7 +815,7 @@ "String.fromCharCode()" ], "solutions": [ - "function binaryAgent(str) {\n return str.split(' ').map(function(s) { return parseInt(s, 2); }).map(function(b) { return String.fromCharCode(b);}).join('');\n}\n\nbinaryAgent('01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111');\n" + "function binaryAgent(str) {\n return str.split(' ').map(function(s) { return parseInt(s, 2); }).map(function(b) { return String.fromCharCode(b);}).join('');\n}" ], "type": "bonfire", "challengeType": 5, @@ -858,7 +858,7 @@ "assert.strictEqual(every([{\"single\": \"double\"}, {\"single\": NaN}], \"single\"), false, 'message: every([{\"single\": \"double\"}, {\"single\": NaN}], \"single\") should return false');" ], "solutions": [ - "function every(collection, pre) {\n // Does everyone have one of these?\n return collection.every(function(e) { return e[pre]; });\n}\n\nevery([{'user': 'Tinky-Winky', 'sex': 'male'}, {'user': 'Dipsy', 'sex': 'male'}, {'user': 'Laa-Laa', 'sex': 'female'}, {'user': 'Po', 'sex': 'female'}], 'sex');\n" + "function every(collection, pre) {\n // Does everyone have one of these?\n return collection.every(function(e) { return e[pre]; });\n}" ], "type": "bonfire", "challengeType": 5, @@ -904,8 +904,7 @@ "Arguments object" ], "solutions": [ - "function add() {\n if (arguments.length == 1) {\n var a = arguments[0];\n if (!isNumber(a)) return;\n return function(b) {\n if (!isNumber(b)) return;\n return a+b;\n };\n }\n if (![].slice.call(arguments).every(isNumber)) return;\n return arguments[0] + arguments[1];\n}\n \nfunction isNumber(obj) {\n return toString.call(obj) == '[object Number]';\n}\n\nadd(2,3);\n", - "function add() {\n var a = arguments[0];\n if (toString.call(a) !== '[object Number]') return; \n if (arguments.length === 1) {\n return function(b) {\n if (toString.call(b) !== '[object Number]') return;\n return a + b;\n };\n }\n var b = arguments[1];\n if (toString.call(b) !== '[object Number]') return; \n return a + arguments[1];\n}\n\nadd(2,3);\n" + "function add() {\n var a = arguments[0];\n if (toString.call(a) !== '[object Number]') return; \n if (arguments.length === 1) {\n return function(b) {\n if (toString.call(b) !== '[object Number]') return;\n return a + b;\n };\n }\n var b = arguments[1];\n if (toString.call(b) !== '[object Number]') return; \n return a + arguments[1];\n}" ], "type": "bonfire", "challengeType": 5, diff --git a/seed/challenges/intermediate-ziplines.json b/seed/challenges/intermediate-ziplines.json index 0d2afbcb5c..454a7eb72f 100644 --- a/seed/challenges/intermediate-ziplines.json +++ b/seed/challenges/intermediate-ziplines.json @@ -8,7 +8,7 @@ "title": "Show the Local Weather", "challengeSeed": ["126415127"], "description": [ - "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/FreeCodeCamp/pen/avqvgJ.", + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/FreeCodeCamp/full/avqvgJ.", "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", "Rule #2: You may use whichever libraries or APIs you need.", "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", @@ -64,7 +64,7 @@ "User Story: As a user, if Free Code Camp is streaming, I can see additional details about what they are streaming.", "Bonus User Story: As a user, I can search through the streams listed.", "Bonus User Story: As a user, I will see a placeholder notification if a streamer has closed their Twitch account. You can verify this works by adding brunofin and comster404 to your array of Twitch streamers.", - "Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp.", + "Hint: See an example call to Twitch.tv's JSONP API at https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Zipline-Use-the-Twitchtv-JSON-API.", "Hint: The relevant documentation about this API call is here: https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel.", "Hint: Here's an array of the Twitch.tv usernames of people who regularly stream coding: [\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]", "Remember to use Read-Search-Ask if you get stuck.", diff --git a/seed/challenges/jquery.json b/seed/challenges/jquery.json index 37a0899018..d96a3df286 100644 --- a/seed/challenges/jquery.json +++ b/seed/challenges/jquery.json @@ -59,7 +59,7 @@ "jQuery often selects an HTML element with a selector, then does something to that element.", "For example, let's make all of your button elements bounce. Just add this code inside your document ready function:", "$(\"button\").addClass(\"animated bounce\")", - "Note that we've already included both the jQuery library and the Animate.css library in your code editor. So you are using jQuery to apply the Animate.css bounce class to your button elements." + "Note that we've already included both the jQuery library and the Animate.css library in the background so that you can use them in the editor. So you are using jQuery to apply the Animate.css bounce class to your button elements." ], "tests": [ "assert($(\"button\").hasClass(\"animated\") && $(\"button\").hasClass(\"bounce\"), 'Use the jQuery addClass() function to give the classes animated and bounce to your button elements.')", @@ -645,27 +645,29 @@ "", "", "", - "

", - "

jQuery Playground

", - "
", - "
", - "

#left-well

", - "
", - " ", - " ", - " ", + "", + "
", + "

jQuery Playground

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

#left-well

", + "
", + " ", + " ", + " ", + "
", "
", - "
", - "
", - "

#right-well

", - "
", - " ", - " ", - " ", + "
", + "

#right-well

", + "
", + " ", + " ", + " ", + "
", "
", "
", "
", - "
" + "" ], "type": "waypoint", "challengeType": 0 diff --git a/seed/challenges/json-apis-and-ajax.json b/seed/challenges/json-apis-and-ajax.json index e9520979ff..5d3b0a5e6f 100644 --- a/seed/challenges/json-apis-and-ajax.json +++ b/seed/challenges/json-apis-and-ajax.json @@ -7,7 +7,7 @@ "id": "bb000000000000000000001", "title": "Trigger Click Events with jQuery", "description": [ - "In this section, we'll learn how to get data from APIs. APIs - or Application Interfaces - are tools that computers use to communicate with one another.", + "In this section, we'll learn how to get data from APIs. APIs - or Application Programming Interfaces - are tools that computers use to communicate with one another.", "We'll also learn how to update HTML with the data we get from these APIs using a technology called Ajax.", "First, let's review what the $(document).ready() function does. This function makes it so all code inside of it only runs once our page loads.", "Let's make our \"Get Message\" button change the text of the element with the class message.", @@ -31,21 +31,21 @@ "", "", "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here", - "
", - "
", - "
", - "
", - " ", - "
", - "
", + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here", + "
", + "
", + "
", + "
", + " ", + "
", + "
", "
" ], "challengeType": 0, @@ -58,7 +58,7 @@ "When our click event happens, we can use Ajax to update an HTML element.", "Let's make it so that when a user clicks the \"Get Message\" button, we change the text of the element with the class message to say \"Here is the message\".", "We can do this by adding the following code within our click event:", - "  $(\".message\").html(\"Here is the message\");" + "  $(\".message\").html(\"Here is the message\");" ], "tests": [ "assert(editor.match(/\\$\\s*?\\(\\s*?(?:'|\")\\.message(?:'|\")\\s*?\\)\\s*?\\.html\\s*?\\(\\s*?(?:'|\")Here\\sis\\sthe\\smessage(?:'|\")\\s*?\\);/gi), 'Clicking the \"Get Message\" button should give the element with the class message the text \"Here is the message\".')" @@ -68,30 +68,29 @@ " $(document).ready(function() {", " $(\"#getMessage\").on(\"click\", function(){", " // Only change code below this line.", - "", + " ", " // Only change code above this line.", " });", " });", "fcces", "", "", - "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here", - "
", - "
", - "
", - "
", - " ", - "
", - "
", + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here", + "
", + "
", + "
", + "
", + " ", + "
", + "
", "
" ], "challengeType": 0, @@ -102,55 +101,55 @@ "title": "Get JSON with the jQuery getJSON Method", "description": [ "You can also request data from an external source. This is where APIs come into play.", - "Remember that APIs - or Application Interfaces - are tools that computers use to communicate with one another.", + "Remember that APIs - or Application Programming Interfaces - are tools that computers use to communicate with one another.", "Most web APIs transfer data in a format called JSON. JSON stands for JavaScript Object Notation.", "You've already been using JSON whenever you create a JavaScript object. JSON is nothing more than object properties and their current values, sandwiched between a { and a }.", "These properties and their values are often referred to as \"key-value pairs\".", "Let's get the JSON from Free Code Camp's Cat Photo API.", "Here's the code you can put in your click event to do this:", - "  $.getJSON(\"/json/cats.json?callback=\", function( json ) {", - "    $(\".message\").html(JSON.stringify(json))", - "  });", + "  $.getJSON(\"/json/cats.json\", function(json) {", + "    $(\".message\").html(JSON.stringify(json));", + "  });", "Once you've added this, click the \"Get Message\" button. Your Ajax function will replace the \"The message will go here\" text with the raw JSON output from the Free Code Camp Cat Photo API." ], "tests": [ "assert(editor.match(/\\$\\s*?\\(\\s*?(\\\"|\\')\\#getMessage(\\\"|\\')\\s*?\\)\\s*?\\.\\s*?on\\s*?\\(\\s*?(\\\"|\\')click(\\\"|\\')\\s*?\\,\\s*?function\\s*?\\(\\s*?\\)\\s*?\\{/gi), 'You should have a click handler on the getMessage button to trigger the AJAX request.')", - "assert(editor.match(/\\s*?\\}\\s*?\\)\\s*?\\;/gi), 'You should have at least on closing set of brackets and parenthesis.')", + "assert(editor.match(/\\s*?\\}\\s*?\\)\\s*?\\;/gi), 'You should have at least one closing set of brackets and parenthesis.')", "assert(editor.match(/\\s*?\\}\\s*?\\)\\s*?\\;/gi) && editor.match(/\\,\\s*?function\\s*?\\(\\s*?\\w*?\\s*?\\)\\s*?\\{/gi) && editor.match(/\\s*?\\}\\s*?\\)\\s*?\\;/gi).length === editor.match(/\\s*?function\\s*?\\(\\s*?\\w*?\\s*?\\)\\s*?\\{/gi).length, 'Each callback function should have a closing set of brackets and parenthesis.')", - "assert(editor.match(/\\$\\s*?\\.\\s*?getJSON\\s*?\\(\\s*?\"\\\/json\\\/cats\\.json\\?callback\\=\"\\s*?\\,\\s*?function\\s*?\\(\\s*?json\\s*?\\)\\s*?\\{/gi), 'You should be making use of the getJSON method given in the description to load data from the json file.')", - "assert(editor.match(/\\$\\s*?\\(\\s*?\\\"\\.message\\\"\\s*?\\)\\s*?\\.\\s*?html\\s*?\\(\\s*?JSON\\s*?\\.\\s*?stringify\\s*?\\(\\s*?json\\s*?\\)\\s*?\\)/gi), 'Don\\'t forget to make the .html change the contents of the message box so that it contains the result of the getJSON.')" + "assert(editor.match(/\\$\\s*?\\.\\s*?getJSON\\s*?\\(\\s*?(\\\"|\\')\\\/json\\\/cats\\.json(\\\"|\\')\\s*?\\,\\s*?function\\s*?\\(\\s*?json\\s*?\\)\\s*?\\{/gi), 'You should be making use of the getJSON method given in the description to load data from the json file.')", + "assert(editor.match(/\\$\\s*?\\(\\s*?(\\\"|\\')\\.message(\\\"|\\')\\s*?\\)\\s*?\\.\\s*?html\\s*?\\(\\s*?JSON\\s*?\\.\\s*?stringify\\s*?\\(\\s*?json\\s*?\\)\\s*?\\)/gi), 'Don\\'t forget to make the .html change the contents of the message box so that it contains the result of the getJSON.')" ], "challengeSeed": [ "fccss", - " $(document).ready(function() {", - " ", - " $(\"#getMessage\").on(\"click\", function(){", - " // Only change code below this line.", + " $(document).ready(function() {", "", + " $(\"#getMessage\").on(\"click\", function(){", + " // Only change code below this line.", + " ", + " ", + " ", + " // Only change code above this line.", + " });", "", - "", - " // Only change code above this line.", - " });", - " ", - " });", + " });", "fcces", "", "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here", - "
", - "
", - "
", - "
", - " ", - "
", - "
", + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here", + "
", + "
", + "
", + "
", + " ", + "
", + "
", "
" ], "challengeType": 0, @@ -166,9 +165,12 @@ "Then, let's loop through our JSON, adding more HTML to that variable. When the loop is finished, we'll render it.", "Here's the code that does this:", "json.map(function(val) {", - "  html = html + \"<div class = 'cat'>\"", - "  html = html + '<div>' + val + '</div>';", - "  html = html + \"</div><br/>\"", + "  var keys = Object.keys(val);", + "  html += \"<div class = 'cat'>\";", + "  keys.map(function(key) {", + "    html += \"<b>\" + key + \"</b>: \" + val[key] + \"<br>\";", + "  });", + "  html += \"</div><br>\";", "});" ], "tests": [ @@ -179,18 +181,13 @@ " $(document).ready(function() {", "", " $(\"#getMessage\").on(\"click\", function() {", - "   $.getJSON(\"/json/cats.json?callback=\", function( json ) {", + "   $.getJSON(\"/json/cats.json\", function(json) {", "", + " var html = \"\";", " // Only change code below this line.", - "", - "", - "", - "", - "", - "", - "", - "", - "", + " ", + " ", + " ", " // Only change code above this line.", "", "     $(\".message\").html(html);", @@ -201,20 +198,20 @@ "fcces", "", "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here","
", - "
", - "
", - "
", - " ", - "
", - "
", + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here","
", + "
", + "
", + "
", + " ", + "
", + "
", "
" ], "challengeType": 0, @@ -225,35 +222,34 @@ "title": "Render Images from Data Sources", "description": [ "In the JSON that we receive data from Free Code Camp's Cat Photo API.", - "When we're looping through these strings, let's check whether they are links. If it is, instead of outputing the image link, let's render the image.", + "We've seen from the last two lessons that each object in our JSON array contains an imageLink key with a value that is the url of a cat's image.", + "When we're looping through these objects, let's use this imageLink property to display this image in an img element.", "Here's the code that does this:", - "if(val.match(\"http\")) {", - "  html = html + '<img src = \"' + val + '\">';", - "}" + "  html += \"<img src = '\" + val.imageLink + \"'>\";" ], "tests": [ - "assert(editor.match(/val.match/gi), 'You should have checked whether the strings contain links.')" + "assert(editor.match(/val.imageLink/gi), 'You should have used the imageLink property to display the images.')" ], "challengeSeed": [ "fccss", " $(document).ready(function() {", "", " $(\"#getMessage\").on(\"click\", function() {", - "  $.getJSON(\"/json/cats.json?callback=\", function( json ) {", + "  $.getJSON(\"/json/cats.json\", function(json) {", "", " var html = \"\";", "", " json.map(function(val) {", "", - " html = html + \"
\"", + " html += \"
\";", "", - " // Only change code below this line.", + " // Only change code below this line.", + " ", + " ", + " ", + " // Only change code above this line.", "", - "", - "", - " // Only change code above this line.", - "", - " html = html + \"
\"", + " html += \"
\";", "", " });", "", @@ -265,21 +261,21 @@ "fcces", "", "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here", - "
", - "
", - "
", - "
", - " ", - "
", - "
", + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here", + "
", + "
", + "
", + "
", + " ", + "
", + "
", "
" ], "challengeType": 0, @@ -293,7 +289,7 @@ "Let's filter out the cat who's \"id\" key has a value of 1.", "Here's the code to do this:", "json = json.filter(function(val) {", - "  return(val.id !== 1);", + "  return(val.id !== 1);", "});" ], "tests": [ @@ -304,25 +300,23 @@ " $(document).ready(function() {", "", " $(\"#getMessage\").on(\"click\", function() {", - "   $.getJSON(\"/json/cats.json?callback=\", function( json ) {", + "   $.getJSON(\"/json/cats.json\", function(json) {", "", " var html = \"\";", "", " // Only change code below this line.", - "", - "", - "", + " ", + " ", + " ", " // Only change code above this line.", "", " json.map(function(val){", "", - "    val = \"\" ", + " html += \"
\"", "", - " html = html + \"
\"", + " html += \"\"", "", - " html = html + '
' + val + '
';", - "", - " html = html + \"
\"", + " html += \"
\"", "", " });", "", @@ -333,25 +327,23 @@ " });", "fcces", "", - "", "
", - "
", - "

Cat Photo Finder

", - "
", - "
", - "
", - " The message will go here", - "
", - "
", - "
", - "
", - " ", - "
", - "
", - "
", - "" + "
", + "

Cat Photo Finder

", + "
", + "
", + "
", + " The message will go here", + "
", + "
", + "
", + "
", + " ", + "
", + "
", + "
" ], "challengeType": 0, "type": "waypoint" @@ -362,11 +354,13 @@ "description": [ "Another cool thing we can do is access our user's current location. Every browser has a built in navigator that can give us this information.", "The navigator will get our user's current longitude and latitude.", + "You will see a prompt to allow or block this site from knowing your current location. The challenge can be completed either way, as long as the code is correct.", + "By selecting allow you will see the text on the output phone change to your latitude and longitude", "Here's some code that does this:", "if (navigator.geolocation) {", - "  navigator.geolocation.getCurrentPosition(function(position) {", - "    $(\"#data\").html(\"latitiude\" + position.coords.latitude + \"longitude\" + position.coords.longitude);", - "  });", + "  navigator.geolocation.getCurrentPosition(function(position) {", + "    $(\"#data\").html(\"latitude: \" + position.coords.latitude + \"<br>longitude: \" + position.coords.longitude);", + "  });", "}" ], "tests": [ @@ -375,9 +369,9 @@ "challengeSeed": [ "fccss", " // Only change code below this line.", - "", - "", - "", + " ", + " ", + " ", " // Only change code above this line.", "fcces", "
", diff --git a/seed/challenges/object-oriented-and-functional-programming.json b/seed/challenges/object-oriented-and-functional-programming.json index 95c6d73b85..810fc81323 100644 --- a/seed/challenges/object-oriented-and-functional-programming.json +++ b/seed/challenges/object-oriented-and-functional-programming.json @@ -54,9 +54,9 @@ "We are also able to create objects using constructor functions.", "Here's an example of a constructor function:", "var Car = function() {", - "  this.wheels = 4;", - "  this.engines = 1;", - "  this.seats = 1;", + "  this.wheels = 4;", + "  this.engines = 1;", + "  this.seats = 1;", "};", "Give your myMotorBike object a wheels, engines and seats attribute and set them to numbers.", "You may be confused by the this keyword here. Don't worry, we will get to that very soon." @@ -189,8 +189,8 @@ "title":"Iterate over Arrays with .map", "description":[ "The map method is a convenient way to iterate through arrays. Here's an example usage:", - "var timesFour = array.map(function(val){", - "  return val*4;", + "var timesFour = oldArray.map(function(val){", + "  return val * 4;", "});", "", "The map method will iterate through every element of the array, creating a new array with values that have been modified by the callback function, and return it.", @@ -228,7 +228,7 @@ "reduce has an optional second argument which can be used to set the initial value of the accumulator. If no initial value is specified it will be the first array element and currentVal will start with the second array element.", "Here is an example of reduce being used to subtract all the values of an array:", "var singleVal = array.reduce(function(previousVal, currentVal) {", - "  return previousVal - currentVal;", + "  return previousVal - currentVal;", "}, 0);", "Use the reduce method to sum all the values in array and assign it to singleVal." ], @@ -263,7 +263,7 @@ "The following code is an example of using filter to remove array elements that are not even numbers:", "Note: We omit the second and third arguments since we only need the value", "array = array.filter(function(val) {", - "  return val % 2 === 0;", + "  return val % 2 === 0;", "});", "Use filter to remove all elements from array that are greater than 5." ], @@ -296,7 +296,7 @@ "Here is an example of using sort with a compare function that will sort the elements from smallest to largest number:", "var array = [1, 12, 21, 2];", "array.sort(function(a, b) {", - "  return a - b;", + "  return a - b;", "});", "Use sort to sort array from largest to smallest." ], @@ -330,7 +330,7 @@ "tests": [ "assert.deepEqual(array, [7,6,5,4,3,2,1], 'message: You should reverse the array.');", "assert(editor.getValue().match(/\\.reverse\\s*\\(\\)/gi), 'message: You should use the reverse method.');", - "assert(editor.getValue().match(/\\[1\\,2\\,3\\,4\\,5\\,6\\,7/gi), 'message: You should only be using revserse to modify array.');" + "assert(editor.getValue().match(/\\[1\\,2\\,3\\,4\\,5\\,6\\,7/gi), 'message: You should only be using reverse to modify array.');" ], "challengeSeed": [ "var array = [1,2,3,4,5,6,7];", diff --git a/seed/getChallenges.js b/seed/getChallenges.js new file mode 100644 index 0000000000..f86a3fc8c6 --- /dev/null +++ b/seed/getChallenges.js @@ -0,0 +1,19 @@ +var fs = require('fs'); +var path = require('path'); + + +function getFilesFor(dir) { + return fs.readdirSync(path.join(__dirname, '/' + dir)); +} + +module.exports = function getChallenges() { + try { + return getFilesFor('challenges') + .map(function(file) { + return require('./challenges/' + file); + }); + } catch (e) { + console.log('error', e); + return []; + } +}; diff --git a/seed/index.js b/seed/index.js index 8300b97245..c11360d6a7 100644 --- a/seed/index.js +++ b/seed/index.js @@ -2,29 +2,23 @@ require('babel/register'); require('dotenv').load(); -var fs = require('fs'), - Rx = require('rx'), +var Rx = require('rx'), _ = require('lodash'), - path = require('path'), + getChallenges = require('./getChallenges'), app = require('../server/server'); -function getFilesFor(dir) { - return fs.readdirSync(path.join(__dirname, '/' + dir)); -} var Challenge = app.models.Challenge; -var challenges = getFilesFor('challenges'); var destroy = Rx.Observable.fromNodeCallback(Challenge.destroyAll, Challenge); var create = Rx.Observable.fromNodeCallback(Challenge.create, Challenge); destroy() - .flatMap(function() { return Rx.Observable.from(challenges); }) - .flatMap(function(file) { - var challengeSpec = require('./challenges/' + file); + .flatMap(function() { return Rx.Observable.from(getChallenges()); }) + .flatMap(function(challengeSpec) { var order = challengeSpec.order; var block = challengeSpec.name; var isBeta = !!challengeSpec.isBeta; - console.log('parsed %s successfully', file); + console.log('parsed %s successfully', block); // challenge file has no challenges... if (challengeSpec.challenges.length === 0) { diff --git a/seed/jobs.json b/seed/jobs.json deleted file mode 100644 index ddaa6c3830..0000000000 --- a/seed/jobs.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "id": "bd7167d8c441cbafaeb5bdef", - "email": "Ada_Gerlach@gmail.com", - "company": "Livestream", - "country": "Singapore", - "city": "Morar berg", - "locale": "Morar berg, Singapore", - "isPaid": true, - "isApproved": true, - "isHighlighted": false, - "state": "South Dakota", - "howToApply": "foofoo foof ooffo http://foo.com/apply", - "position": "Junior Backend Developer (Node.js)", - "logo": "https://s3.amazonaws.com/prod-heroku/greenhouse_job_boards/logos/000/001/189/resized/livestream_logo-rgb_standard-cc718e67ce1a0f6fa400f609bdefe605.png?1429547161", - "description": "Live life one inhalation and one exhalation at a time. May you be healthy. Let the muscles in your neck and shoulders relax. May you be safe. Reflect on the fragility and preciousness of life. Empty your mind; be formless, shapeless like water. May you be at peace. Take a deep breath. You can do what you set out to do; yes, you can. You can do what you set out to do; yes, you can. Watch each breath appear and disappear, just breathing. Give yourself a break. Live life one inhalation and one exhalation at a time. Give yourself a break. Just acknowledge what's there and let be. You can get through this. Open your heart to change, forgiveness and lovingkindness. Love is the first seed of the soul. It will be ok. Impermanence and change is a powerful teacher and teaching. Bring love into your heart, into your breath and into your being. Briefly notice any emotions, thoughts or sensations that may be driving fear and anxiety and let them be. This discomfort will pass. Bring love into your heart, into your breath and into your being. Take a deep breath. Reflect on the fragility and preciousness of life." - }, - { - "id": "bd7167d8c442cbafaeb5bdef", - "email": "Ada_Gerlach@gmail.com", - "company": "Adobe", - "country": "Singapore", - "city": "Morar berg", - "locale": "Morar berg, Singapore", - "isPaid": true, - "isApproved": true, - "isHighlighted": false, - "state": "South Dakota", - "howToApply": "foofoo foof ooffo http://foo.com/apply", - "position": "Junior JavaScript Engineer", - "logo": "http://cdn-3.famouslogos.us/images/adobe-logo.jpg", - "description": "Live life one inhalation and one exhalation at a time. May you be healthy. Let the muscles in your neck and shoulders relax. May you be safe. Reflect on the fragility and preciousness of life. Empty your mind; be formless, shapeless like water. May you be at peace. Take a deep breath. You can do what you set out to do; yes, you can. You can do what you set out to do; yes, you can. Watch each breath appear and disappear, just breathing. Give yourself a break. Live life one inhalation and one exhalation at a time. Give yourself a break. Just acknowledge what's there and let be. You can get through this. Open your heart to change, forgiveness and lovingkindness. Love is the first seed of the soul. It will be ok. Impermanence and change is a powerful teacher and teaching. Bring love into your heart, into your breath and into your being. Briefly notice any emotions, thoughts or sensations that may be driving fear and anxiety and let them be. This discomfort will pass. Bring love into your heart, into your breath and into your being. Take a deep breath. Reflect on the fragility and preciousness of life." - }, - { - "id": "bd7167d8c443cbafaeb5bdef", - "company": "Bookspan", - "email": "Ada_Gerlach@gmail.com", - "country": "Singapore", - "city": "Morar berg", - "locale": "Morar berg, Singapore", - "isPaid": true, - "isApproved": true, - "isHighlighted": true, - "howToApply": "foofoo foof ooffo http://foo.com/apply", - "state": "South Dakota", - "position": "Full Stack JavaScript Developer (Junior)", - "logo": "https://tm-prod.global.ssl.fastly.net/uploaded/companies/227/small_logo.png?v=db9dbe", - "description": "Live life one inhalation and one exhalation at a time. May you be healthy. Let the muscles in your neck and shoulders relax. May you be safe. Reflect on the fragility and preciousness of life. Empty your mind; be formless, shapeless like water. May you be at peace. Take a deep breath. You can do what you set out to do; yes, you can. You can do what you set out to do; yes, you can. Watch each breath appear and disappear, just breathing. Give yourself a break. Live life one inhalation and one exhalation at a time. Give yourself a break. Just acknowledge what's there and let be. You can get through this. Open your heart to change, forgiveness and lovingkindness. Love is the first seed of the soul. It will be ok. Impermanence and change is a powerful teacher and teaching. Bring love into your heart, into your breath and into your being. Briefly notice any emotions, thoughts or sensations that may be driving fear and anxiety and let them be. This discomfort will pass. Bring love into your heart, into your breath and into your being. Take a deep breath. Reflect on the fragility and preciousness of life." - }, - { - "id": "bd7167d8c444cbafaeb5bdef", - "email": "Ada_Gerlach@gmail.com", - "company": "Good Eggs", - "country": "Singapore", - "city": "Morar berg", - "locale": "Morar berg, Singapore", - "isPaid": true, - "isApproved": true, - "isHighlighted": false, - "howToApply": "foofoo foof ooffo http://foo.com/apply", - "state": "South Dakota", - "position": "Full Stack Developer", - "logo": "https://d1qb2nb5cznatu.cloudfront.net/startups/i/72165-64efbd521cdfe3357c811758f5436e7d-medium_jpg.jpg", - "description": "Live life one inhalation and one exhalation at a time. May you be healthy. Let the muscles in your neck and shoulders relax. May you be safe. Reflect on the fragility and preciousness of life. Empty your mind; be formless, shapeless like water. May you be at peace. Take a deep breath. You can do what you set out to do; yes, you can. You can do what you set out to do; yes, you can. Watch each breath appear and disappear, just breathing. Give yourself a break. Live life one inhalation and one exhalation at a time. Give yourself a break. Just acknowledge what's there and let be. You can get through this. Open your heart to change, forgiveness and lovingkindness. Love is the first seed of the soul. It will be ok. Impermanence and change is a powerful teacher and teaching. Bring love into your heart, into your breath and into your being. Briefly notice any emotions, thoughts or sensations that may be driving fear and anxiety and let them be. This discomfort will pass. Bring love into your heart, into your breath and into your being. Take a deep breath. Reflect on the fragility and preciousness of life." - } -] diff --git a/seed/storyCleanup.js b/seed/storyCleanup.js deleted file mode 100644 index 2598526c8a..0000000000 --- a/seed/storyCleanup.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Created by nathanleniz on 4/25/15. - */ -require('dotenv').load(); -var mongodb = require('mongodb'), - Story = require('../models/Story.js'), - secrets = require('../config/secrets'); - mongoose = require('mongoose'); - -mongoose.connect(secrets.db); - -function storyLinkCleanup(cb) { - console.log('headLineCleanup'); - var i = 1; - var stream = Story.find({}).skip(0).limit(0).batchSize(20000).stream(); - - stream.on('data', function (story) { - console.log(i++); - this.pause(); - story.storyLink = story.storyLink. - replace(/[^a-z0-9\s]/gi, ''). - replace(/\s+/g, ' '). - toLowerCase(). - trim(); - story.save(function (err) { - if (err) { - console.log('woops'); - } - this.resume(); - }.bind(this)); - }) - .on('error', function (err) { - console.error(err); - }).on('close', function () { - console.log('done with set'); - stream.destroy(); - cb(); - }); -} - -function done() { - console.log('Migration script has completed'); - process.exit(0); -} - - -storyLinkCleanup(done); diff --git a/seed/test-challenges.js b/seed/test-challenges.js new file mode 100644 index 0000000000..93a994c30c --- /dev/null +++ b/seed/test-challenges.js @@ -0,0 +1,112 @@ +/* eslint-disable no-eval, no-process-exit */ +import _ from 'lodash'; +import { Observable } from 'rx'; +import tape from 'tape'; +import getChallenges from './getChallenges'; + + +function createIsAssert(t, isThing) { + const { assert } = t; + return function() { + const args = [...arguments]; + args[0] = isThing(args[0]); + assert.apply(t, args); + }; +} + +function fillAssert(t) { + const assert = t.assert; + + assert.isArray = createIsAssert(t, _.isArray); + assert.isBoolean = createIsAssert(t, _.isBoolean); + assert.isString = createIsAssert(t, _.isString); + assert.isNumber = createIsAssert(t, _.isNumber); + assert.isUndefined = createIsAssert(t, _.isUndefined); + + assert.deepEqual = t.deepEqual; + assert.equal = t.equal; + assert.strictEqual = t.equal; + + assert.sameMembers = function sameMembers() { + const [ first, second, ...args] = arguments; + assert.apply( + t, + [ + _.difference(first, second).length === 0 && + _.difference(second, first).length === 0 + ].concat(args) + ); + }; + + assert.includeMembers = function includeMembers() { + const [ first, second, ...args] = arguments; + assert.apply(t, [_.difference(second, first).length === 0].concat(args)); + }; + + assert.match = function match() { + const [value, regex, ...args] = arguments; + assert.apply(t, [regex.test(value)].concat(args)); + }; + + return assert; +} + +function createTest({ title, tests = [], solutions = [] }) { + const plan = tests.length; + return Observable.fromCallback(tape)(title) + .doOnNext(t => solutions.length ? t.plan(plan) : t.end()) + .flatMap(t => { + if (solutions.length <= 0) { + t.comment('No solutions for ' + title); + return Observable.just({ + title, + type: 'missing' + }); + } + + return Observable.just(t) + .map(fillAssert) + /* eslint-disable no-unused-vars */ + // assert is used within the eval + .doOnNext(assert => { + /* eslint-enable no-unused-vars */ + solutions.forEach(solution => { + tests.forEach(test => { + try { + eval(solution + ';;' + test); + } catch (e) { + t.fail(e); + } + }); + }); + }) + .map(() => ({ title })); + }); +} + +Observable.from(getChallenges()) + .flatMap(challengeSpec => { + return Observable.from(challengeSpec.challenges); + }) + .flatMap(challenge => { + return createTest(challenge); + }) + .map(({ title, type }) => { + if (type === 'missing') { + return title; + } + return false; + }) + .filter(title => !!title) + .toArray() + .subscribe( + (noSolutions) => { + console.log( + '# These challenges have no solutions\n- [ ] ' + + noSolutions.join('\n- [ ] ') + ); + }, + err => { throw err; }, + () => process.exit(0) + ); + diff --git a/seed/userMigration.js b/seed/userMigration.js deleted file mode 100644 index 350a57ffe3..0000000000 --- a/seed/userMigration.js +++ /dev/null @@ -1,137 +0,0 @@ -/*eslint-disable block-scoped-var */ -require('dotenv').load(); -var User = require('../models/User.js'), - secrets = require('../config/secrets'), - mongoose = require('mongoose'), - R = require('ramda'), - ziplines = require('./challenges/ziplines.json'), - basejumps = require('./challenges/basejumps.json'); - -mongoose.connect(secrets.db); - -var ziplineIds = ziplines.challenges.map(function(elem) { - return elem.id; -}); -var basejumpIds = basejumps.challenges.map(function(elem) { - return elem.id; -}); -var ziplineAndBaseJumpIds = R.concat(ziplineIds, basejumpIds); - -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++); - 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); - }); - } - */ - user.needsMigration = false; - user.completedChallenges = user.completedChallenges.map(function(elem) { - if (ziplineAndBaseJumpIds.indexOf(elem.id) > 0) { - return ({ - id: elem.id, - name: elem.name, - completedWith: elem.completedWith, - completedDate: elem.completedDate, - solution: elem.solution, - githubLink: elem.githubLink, - verified: elem.verified, - challengeType: typeof elem.githubLink === 'boolean' ? 4 : 3 - }); - } else { - return elem; - } - }); - - 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(); - cb(); - }); -} - -migrateIt(); diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 13e0563589..3ca5c69dca 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -35,12 +35,14 @@ const dasherize = utils.dasherize; const unDasherize = utils.unDasherize; const getMDNLinks = utils.getMDNLinks; +/* function makeChallengesUnique(challengeArr) { // clone and reverse challenges // then filter by unique id's // then reverse again return _.uniq(challengeArr.slice().reverse(), 'id').reverse(); } +*/ function numberWithCommas(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } @@ -535,6 +537,11 @@ module.exports = function(app) { if (completedChallenges.indexOf(challenge.id) !== -1) { challenge.completed = true; } + if (typeof challenge.releasedOn !== 'undefined' && + moment(challenge.releasedOn, 'MMM MMMM DD, YYYY').diff(moment(), + 'days') >= -30) { + challenge.markNew = true; + } return challenge; }) // group challenges by block | returns a stream of observables @@ -556,7 +563,7 @@ module.exports = function(app) { dashedName: dasherize(blockArray[0].block), challenges: blockArray, completed: completedCount / blockArray.length * 100, - time: blockArray[0] && blockArray[0].time || "???" + time: blockArray[0] && blockArray[0].time || '???' }; }) .filter(({ name }) => name !== 'Hikes') @@ -579,9 +586,12 @@ module.exports = function(app) { res.render('challengeMap/show', { blocks, daysRunning, + globalCompletedCount: numberWithCommas( + 5612952 + (Math.floor((Date.now() - 1446268581061) / 3000)) + ), camperCount, lastCompleted, - title: "A map of all Free Code Camp's Challenges" + title: "A Map to Learn to Code and Become a Software Engineer" }); }, next diff --git a/server/boot/home.js b/server/boot/home.js index 511c9aedf4..0177fa1edc 100644 --- a/server/boot/home.js +++ b/server/boot/home.js @@ -1,7 +1,7 @@ import { defaultProfileImage } from '../../common/utils/constantStrings.json'; const message = - 'Learn to Code and Build Projects for Nonprofits'; + 'Learn to Code and Help Nonprofits'; module.exports = function(app) { var router = app.loopback.Router(); diff --git a/server/boot/labs.js b/server/boot/labs.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index f15cbde478..997731055a 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -28,7 +28,6 @@ module.exports = function(app) { router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm); router.get('/nonprofits', nonprofits); router.get('/nonprofits-form', nonprofitsForm); - router.get('/our-sponsors', sponsors); router.get('/unsubscribe/:email', unsubscribe); router.get('/unsubscribed', unsubscribed); router.get('/get-started', getStarted); @@ -185,14 +184,14 @@ module.exports = function(app) { function showLabs(req, res) { res.render('resources/labs', { - title: 'Projects Built by Free Code Camp Students', + title: 'Projects Built by Free Code Camp Software Engineers', projects: labs }); } function showTestimonials(req, res) { res.render('resources/stories', { - title: 'Stories from Happy Free Code Camp Campers', + title: 'Testimonials from Happy Free Code Camp Students who got Software Engineer Jobs', stories: testimonials }); } @@ -207,15 +206,9 @@ module.exports = function(app) { }); } - function sponsors(req, res) { - res.render('sponsors/sponsors', { - title: 'The Sponsors who make Free Code Camp Possible' - }); - } - function nonprofits(req, res) { res.render('resources/nonprofits', { - title: 'A guide to our Nonprofit Projects' + title: 'Your Nonprofit Can Get Pro Bono Code' }); } diff --git a/server/boot/story.js b/server/boot/story.js index c1f188db81..a883a30280 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -109,7 +109,7 @@ module.exports = function(app) { function hot(req, res) { return res.render('stories/index', { - title: 'Hot stories currently trending on Camper News', + title: 'Top Stories on Camper News', page: 'hot' }); } diff --git a/server/boot/user.js b/server/boot/user.js index 3dac4a9a19..d1d7df35f3 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -128,7 +128,7 @@ module.exports = function(app) { return res.redirect('/'); } res.render('account/signin', { - title: 'Free Code Camp Login' + title: 'Sign in to Free Code Camp using a Social Media Account' }); } @@ -142,7 +142,7 @@ module.exports = function(app) { return res.redirect('/'); } res.render('account/email-signin', { - title: 'Sign in to your Free Code Camp Account' + title: 'Sign in to Free Code Camp using your Email Address' }); } @@ -151,7 +151,7 @@ module.exports = function(app) { return res.redirect('/'); } res.render('account/email-signup', { - title: 'Create Your Free Code Camp Account' + title: 'Sign up for Free Code Camp using your Email Address' }); } @@ -222,7 +222,7 @@ module.exports = function(app) { }); res.render('account/show', { - title: 'Camper ' + profileUser.username + '\'s portfolio', + title: 'Camper ' + profileUser.username + '\'s Code Portfolio', username: profileUser.username, name: profileUser.name, @@ -397,7 +397,7 @@ module.exports = function(app) { return res.render('account/forgot'); } res.render('account/reset', { - title: 'Password Reset', + title: 'Reset your Password', accessToken: req.accessToken.id }); } diff --git a/server/views/challengeMap/show.jade b/server/views/challengeMap/show.jade index c33348c259..81e80307a1 100644 --- a/server/views/challengeMap/show.jade +++ b/server/views/challengeMap/show.jade @@ -10,27 +10,36 @@ block content else img.img-responsive.img-center.border-radius-5(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') .col-xs-12.col-md-8.col-md-offset-2 - h2.text-center - span.text-primary #{camperCount}   - | campers have joined our community - br - | since we launched   - span.text-primary #{daysRunning}   - | days ago. + h2 + table.population-table.img-center + tr + td Established:  + td + span.text-primary #{daysRunning}  + | days ago + tr + td Population:  + td + span.text-primary #{camperCount}  + | campers + tr + td Completed:  + td + span.text-primary #{globalCompletedCount}  + | challenges + + + .spacer - if (user && user.progressTimestamps.length > 5) - #tshirt-notice.col-xs-12.col-md-8.col-md-offset-2.hidden - h2.text-center Get our first-edition t-shirt. - br - span.text-success Only available until Oct 29! - img.thumbnail.img-center.img-responsive(src="http://i.imgur.com/o07uuOL.png") - p.text-justify Our community has voted. Get our winning design emblazoned on a durable, American-made American Apparel shirt (available in women's and men's sizes).  - a(href="https://teespring.com/free-code-camp-shirt-eu" target="_blank") Also ships from Europe - | . - a.button.btn.btn-block.signup-btn(href="https://teespring.com/get-free-code-camp-t-shirt" target="_blank") Get yours + if (user && user.progressTimestamps.length > 100) + #map-notice.col-xs-12.col-md-8.col-md-offset-2.hidden + h2.text-center We have a subreddit + img.thumbnail.img-center.img-responsive(src="http://i.imgur.com/lyd0bfM.jpg") + h4.text-center We welcome you to come ask questions and share your thoughts with our entire open source community. + a.button.btn.btn-block.btn-primary(href="https://reddit.com/r/freecodecamp" target="_blank") Check it out .button-spacer .text-center - a#hideTshirtNoticeButton(href='#') Hide this forever + a#hide-map-notice-button(href='#') Hide this .spacer .row .col-xs-12.col-sm-8.col-sm-offset-2 @@ -120,6 +129,10 @@ block content span.capitalize= challenge.type + ': ' span= challenge.title span.sr-only= " Incomplete" + if challenge.markNew + span.text-danger.small     + strong + em NEW if (challengeBlock.completed === 100) .button-spacer @@ -131,14 +144,14 @@ block content var username = !{JSON.stringify(user && user.username || '')}; var lastCompleted = !{JSON.stringify(lastCompleted || false)} $(document).ready(function () { - if (!localStorage || !localStorage.hideTshirtNotice) { - $("#tshirt-notice").removeClass("hidden"); + if (!localStorage || !localStorage.hideRedditNotice) { + $("#map-notice").removeClass("hidden"); } - $("#hideTshirtNoticeButton").on("click", function() { - $("#tshirt-notice").addClass('animated fadeOut'); + $("#hide-map-notice-button").on("click", function() { + $("#map-notice").addClass('animated fadeOut'); setTimeout(function() { - $("#tshirt-notice").hide(); + $("#map-notice").hide(); }, 1000); - localStorage.hideTshirtNotice = "true"; + localStorage.hideRedditNotice = "true"; }); }); diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade index 92432a3bf4..5a2f6c0a11 100644 --- a/server/views/coursewares/showBonfire.jade +++ b/server/views/coursewares/showBonfire.jade @@ -108,7 +108,7 @@ block content i.fa.fa-twitter   = phrase else - a.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge + #next-challenge.btn.btn-lg.btn-primary.btn-block Go to my next challenge (ctrl + enter) #reset-modal.modal(tabindex='-1') .modal-dialog.animated.fadeInUp.fast-animation .modal-content diff --git a/server/views/coursewares/showHTML.jade b/server/views/coursewares/showHTML.jade index ad2213e803..d07dd7748d 100644 --- a/server/views/coursewares/showHTML.jade +++ b/server/views/coursewares/showHTML.jade @@ -98,7 +98,7 @@ block content if(user) #submit-challenge.animated.fadeIn.btn.btn-lg.btn-primary.btn-block Submit and go to my next challenge (ctrl + enter) else - a.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge + #next-challenge.btn.btn-lg.btn-primary.btn-block Go to my next challenge (ctrl + enter) include ../partials/challenge-modals script. document.addEventListener('gitter-sidecar-ready', function(e) { diff --git a/server/views/coursewares/showJS.jade b/server/views/coursewares/showJS.jade index 887e44c964..4cfe47bf96 100644 --- a/server/views/coursewares/showJS.jade +++ b/server/views/coursewares/showJS.jade @@ -95,7 +95,7 @@ block content if (user) #submit-challenge.animated.fadeIn.btn.btn-lg.btn-primary.btn-block Submit and go to my next challenge (ctrl + enter) else - a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge + #next-challenge.animated.fadeIn.btn.btn-lg.btn-primary.btn-block Go to my next challenge (ctrl + enter) include ../partials/challenge-modals script. var MDNlinks = !{JSON.stringify(MDNlinks)}; diff --git a/server/views/coursewares/showStep.jade b/server/views/coursewares/showStep.jade index 74bb6705f5..b87811cb80 100644 --- a/server/views/coursewares/showStep.jade +++ b/server/views/coursewares/showStep.jade @@ -10,8 +10,12 @@ block content p.large-p!= step[2] if step[3] a.btn.btn-block.btn-primary.challenge-step-btn-action(id='#{index}' href='#{step[3]}' target='_blank') Open link in new tab + .button-spacer if index + 1 === description.length .btn.btn-block.btn-primary.challenge-step-btn-finish(id='last' class=step[3] ? 'disabled' : '') Finish challenge + else if index !== 0 + .btn.btn-block.btn-primary.challenge-step-btn-next(id='#{index}' class=step[3] ? 'disabled' : '') Go to my next step + .btn.btn-block.btn-warning-ghost.challenge-step-btn-prev(id='#{index - 1}') Go to my previous step else .btn.btn-block.btn-primary.challenge-step-btn-next(id='#{index}' class=step[3] ? 'disabled' : '') Go to my next step #challenge-step-modal.modal(tabindex='-1') diff --git a/server/views/resources/sitemap.jade b/server/views/resources/sitemap.jade index edd0367e6a..6b4907a199 100644 --- a/server/views/resources/sitemap.jade +++ b/server/views/resources/sitemap.jade @@ -26,17 +26,41 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") priority= 0.9 url - loc http://www.freecodecamp.com/coding-bootcamp-cost-calculator + loc http://www.freecodecamp.com/labs changefreq weekly lastmod= now priority= 0.9 + url + loc http://www.freecodecamp.com/stories + changefreq weekly + lastmod= now + priority= 0.9 + + url + loc http://www.freecodecamp.com/twitch + changefreq weekly + lastmod= now + priority= 0.9 + + url + loc http://www.freecodecamp.com/jobs + changefreq weekly + lastmod= now + priority= 0.9 + + url + loc http://www.freecodecamp.com/coding-bootcamp-cost-calculator + changefreq monthly + lastmod= now + priority= 0.9 + //- User each user in users url loc #{appUrl}/#{user} lastmod= now - changefreq daily + changefreq weekly priority= 0.5 each challenge in challenges @@ -44,18 +68,18 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") loc #{appUrl}/challenges/#{challenge.replace(/\s/g, '-')} lastmod= now changefreq weekly - priority= 0.5 + priority= 0.9 each story in stories url loc #{appUrl}/news/#{story.replace(/\s/g, '-')} lastmod= now - changefreq daily - priority= 0.9 + changefreq weekly + priority= 0.5 each nonprofit in nonprofits url loc #{appUrl}/nonprofits/#{nonprofit.replace(/\s/g, '-')} lastmod= now - changefreq daily + changefreq weekly priority= 0.9 diff --git a/test/basic_routes.js b/test/basic_routes.js deleted file mode 100644 index ac7d190787..0000000000 --- a/test/basic_routes.js +++ /dev/null @@ -1,45 +0,0 @@ -var request = require('supertest'); -var app = require('../server/server.js'); - -describe('#ROUTES', function () { - - describe('GET /', function () { - it('should return 200 OK', (done) => { - request(app) - .get('/') - .expect(200, done); - }); - }); - - describe('GET /signin', function () { - it('should return 200 OK', (done) => { - request(app) - .get('/signin') - .expect(200, done); - }); - }); - - describe('GET /email-signup', function () { - it('should return 200 OK', (done) => { - request(app) - .get('/email-signup') - .expect(200, done); - }); - }); - - describe('GET /random-url', function () { - it('should return 302', (done) => { - request(app) - .get('/reset') - .expect(302, done); - }); - }); - - describe('GET /camperName', function () { - it('should return 200', (done) => { - request(app) - .get('/terakilobyte') - .expect(200, done); - }); - }); -}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 097dfe9134..0000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---reporter spec ---timeout 5000 \ No newline at end of file