diff --git a/app.js b/app.js index 165061db3d..b1f65d4e37 100755 --- a/app.js +++ b/app.js @@ -36,25 +36,26 @@ var express = require('express'), /** * Controllers (route handlers). */ - homeController = require('./controllers/home'), - resourcesController = require('./controllers/resources'), - userController = require('./controllers/user'), - nonprofitController = require('./controllers/nonprofits'), - bonfireController = require('./controllers/bonfire'), - coursewareController = require('./controllers/courseware'), - fieldGuideController = require('./controllers/fieldGuide'), - challengeMapController = require('./controllers/challengeMap'), + homeController = require('./controllers/home'), + resourcesController = require('./controllers/resources'), + userController = require('./controllers/user'), + nonprofitController = require('./controllers/nonprofits'), + bonfireController = require('./controllers/bonfire'), + coursewareController = require('./controllers/courseware'), + fieldGuideController = require('./controllers/fieldGuide'), + challengeMapController = require('./controllers/challengeMap'), + challengeController = require('./controllers/challenge'), /** * Stories */ - storyController = require('./controllers/story'), + storyController = require('./controllers/story'), - /** - * API keys and Passport configuration. - */ - secrets = require('./config/secrets'), - passportConf = require('./config/passport'); + /** + * API keys and Passport configuration. + */ + secrets = require('./config/secrets'), + passportConf = require('./config/passport'); /** * Create Express server. @@ -537,17 +538,24 @@ app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide); * Courseware related routes */ -app.get('/challenges/', coursewareController.returnNextCourseware); +app.get('/getstuff', challengeController.getStuff); + + +app.get('/challenges/next-challenge', challengeController.returnNextChallenge); app.get( - '/challenges/:coursewareName', - coursewareController.returnIndividualCourseware + '/challenges/:challengeName', + challengeController.returnIndividualChallenge ); -app.post('/completed-courseware/', coursewareController.completedCourseware); +app.get('/challenges/', challengeController.returnCurrentChallenge); +// todo refactor these routes +app.post('/completed-challenge/', challengeController.completedChallenge); app.post('/completed-zipline-or-basejump', - coursewareController.completedZiplineOrBasejump); + challengeController.completedZiplineOrBasejump); + +app.post('/completed-bonfire', challengeController.completedBonfire); // Unique Check API route app.get('/api/checkUniqueUsername/:username', diff --git a/controllers/challenge.js b/controllers/challenge.js index 9062945916..3894006e96 100644 --- a/controllers/challenge.js +++ b/controllers/challenge.js @@ -40,42 +40,107 @@ exports.showAllChallenges = function(req, res) { res.send(data); }; +exports.getStuff = function(req, res, next) { + res.send({withNames: challengeMapWithNames, withIds: challengeMapWithIds}); +}; + exports.returnNextChallenge = function(req, res, next) { if (!req.user) { return res.redirect('../challenges/learn-how-free-code-camp-works'); } + var completed = req.user.completedChallenges.map(function (elem) { + return elem._id; + }); + + req.user.uncompletedChallenges = resources.allChallengeIds() + .filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.save(); // find the user's current challenge and block // look in that block and find the index of their current challenge // if index + 1 <= block.challenges.length - 1 // serve index + 1 challenge // otherwise increment block key and serve the first challenge in that block var nextChallengeName; + var nextChallengeId; + var nextChallengeBlock; + var challengeId = req.user.currentChallenge.challengeId; var challengeBlock = req.user.currentChallenge.challengeBlock; + debug('this is the user challenge block', challengeBlock); var indexOfChallenge = challengeMapWithIds[challengeBlock] .indexOf(challengeId); - if (indexOfChallenge - <= challengeMapWithIds[challengeBlock].length - 1) { + debug('Logging first two challenge blocks for sanity', challengeMapWithIds[0], challengeMapWithIds[1]); + + + debug('these are the damn keys on challengeMapWithIds...', Object.keys(challengeMapWithIds)); + if (indexOfChallenge + 1 + < challengeMapWithIds[challengeBlock].length) { + debug('advancing to next challange in current block'); nextChallengeName = challengeMapWithNames[challengeBlock][indexOfChallenge + 1]; - } else if (typeof challengeMapWithIds[challengeBlock + 1] !== 'undefined') { - nextChallengeName = R.head(challengeMapWithNames[challengeBlock + 1]); + nextChallengeId = challengeMapWithIds[challengeBlock][indexOfChallenge + 1]; + nextChallengeBlock = challengeBlock; + } else if (typeof challengeMapWithIds[++challengeBlock] !== 'undefined') { + debug('Advancing to next block'); + nextChallengeName = R.head(challengeMapWithNames[challengeBlock]); + nextChallengeId = R.head(challengeMapWithNames[challengeBlock]); + nextChallengeBlock = challengeBlock; } else { + debug('completed all challenges'); req.flash('errors', { msg: 'It looks like you have finished all of our challenges.' + ' Great job! Now on to helping nonprofits!' }); - nextChallengeName = R.head(challengeMapWithNames['0'].challenges); + nextChallengeName = R.head(challengeMapWithNames[0].challenges); + nextChallengeId = R.head(challengeMapWithNames[0].challenges); + nextChallengeBlock = 0; } - var nameString = nextChallengeName.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../challenges' + nameString); + req.user.currentChallenge = { + challengeId: nextChallengeId, + challengeName: nextChallengeName, + challengeBlock: nextChallengeBlock + }; + req.user.save(); + var nameString = nextChallengeName.trim() + .toLowerCase() + .replace(/[^\w\s]/g, '') + .replace(/\s/g, '-') + debug('this is the namestring we\'re going to look up', nameString); + return res.redirect('../challenges/' + nameString); +}; + +exports.returnCurrentChallenge = function(req, res, next) { + debug('these are the damn keys on challengeMapWithIds...', Object.keys(challengeMapWithIds)); + debug('sanity check', challengeMapWithIds[0], challengeMapWithIds[1]); + if (!req.user) { + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + if (!req.user.currentChallenge) { + req.user.currentChallenge = {}; + req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0]; + req.user.currentChallenge.challengeName = challengeMapWithNames['0'][0]; + req.user.currentChallenge.challengeBlock = '0'; + req.user.save(); + return res.redirect('../challenges/learn-how-free-code-camp-works'); + } + var nameString = req.user.currentChallenge.challengeName.trim() + .toLowerCase() + .replace(/[^\w\s]/g, '') + .replace(/\s/g, '-'); + debug('this is the namestring we\'re going to look up', nameString); + return res.redirect('../challenges/' + nameString); }; exports.returnIndividualChallenge = function(req, res, next) { var dashedName = req.params.challengeName; var challengeName = dashedName.replace(/\-/g, ' '); + debug('looking for %s', challengeName); Challenge.find({'name': new RegExp(challengeName, 'i')}, function(err, challengeFromMongo) { @@ -84,6 +149,7 @@ exports.returnIndividualChallenge = function(req, res, next) { } // Handle not found if (challengeFromMongo.length < 1) { + debug(challengeFromMongo); req.flash('errors', { msg: '404: We couldn\'t find a challenge with that name. ' + 'Please double check the name.' @@ -91,6 +157,7 @@ exports.returnIndividualChallenge = function(req, res, next) { return res.redirect('/challenges'); } var challenge = challengeFromMongo.pop(); + debug(challenge); // Redirect to full name if the user only entered a partial var dashedNameFull = challenge.name.toLowerCase().replace(/\s/g, '-'); @@ -186,6 +253,148 @@ exports.returnIndividualChallenge = function(req, res, next) { }); }; +exports.completedBonfire = function(req, res, next) { + +}; + +exports.completedChallenge = function (req, res, next) { + + var isCompletedDate = Math.round(+new Date()); + var challengeId = req.body.challengeInfo.challengeId; + + + req.user.completedChallenges.push({ + _id: challengeId, + completedDate: isCompletedDate, + name: req.body.challengeInfo.challengeName, + solution: null, + githubLink: null, + verified: true + }); + var index = req.user.uncompletedChallenges.indexOf(challengeId); + + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedChallenges.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + res.sendStatus(200); + } + }); +}; + +exports.completedZiplineOrBasejump = function (req, res, next) { + debug('Inside controller for completed zipline or basejump with data %s', + req.body.coursewareInfo); + var isCompletedWith = req.body.coursewareInfo.completedWith || false; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + var solutionLink = req.body.coursewareInfo.publicURL; + var githubLink = req.body.coursewareInfo.challengeType === '4' + ? req.body.coursewareInfo.githubURL : true; + if (!solutionLink || !githubLink) { + req.flash('errors', { + msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + + 'your work.' + }); + return res.sendStatus(403); + } + + if (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWithFromMongo) { + if (err) { + return next(err); + } else { + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + var pairedWith = pairedWithFromMongo.pop(); + + req.user.completedCoursewares.push({ + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: pairedWith._id, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false + }); + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + debug('this is the user object returned %s,' + + ' this is the req.user._id %s, ' + + 'this is the pairedWith._id %s', user, req.user._id, pairedWith._id); + debug(req.user._id.toString() === pairedWith._id.toString()); + if (req.user._id.toString() === pairedWith._id.toString()) { + return res.sendStatus(200); + } + index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); + pairedWith.uncompletedCoursewares.splice(index, 1); + + } + + pairedWith.completedCoursewares.push({ + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false + }); + pairedWith.save(function (err, paired) { + if (err) { + return next(err); + } + if (user && paired) { + return res.sendStatus(200); + } + }); + }); + } + }); + } else { + + req.user.completedCoursewares.push({ + _id: coursewareHash, + name: req.body.coursewareInfo.coursewareName, + completedWith: null, + completedDate: isCompletedDate, + solution: solutionLink, + githubLink: githubLink, + verified: false + }); + + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); + if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + return res.sendStatus(200); + } + }); + } +}; + /* challengeBlock { 0: { diff --git a/controllers/resources.js b/controllers/resources.js index b286a394ec..21a6d20f16 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -28,7 +28,8 @@ var async = require('async'), */ var allBonfireIds, allBonfireNames, allCoursewareIds, allCoursewareNames, allFieldGuideIds, allFieldGuideNames, allNonprofitNames, - allBonfireIndexesAndNames, challengeMap; + allBonfireIndexesAndNames, challengeMap, challengeMapWithIds, + challengeMapWithNames, allChallengeIds; /** * GET / @@ -46,55 +47,73 @@ Array.zip = function(left, right, combinerFunction) { return results; }; -buildChallengeMap = function() { - challengeMap = {}; - fs.readdir(__dirname + '../seed_data/challenges', function(err, files) { - if (err) { - debug(err); - } else { - var keyCounter = 0; - files = files.sort(function(a, b) { - return a.order < b.order ? a : b; - }); - files.forEach(function(file) { - challengeMap[keyCounter++] = file; - }); - } - }); -}; +(function() { + if (!challengeMap) { + var localChallengeMap = {}; + var files = fs.readdirSync(__dirname + '/../seed_data/challenges'); + var keyCounter = 0; + files = files.map(function (file) { + return require(__dirname + + '/../seed_data/challenges/' + file); + }); + files = files.sort(function (a, b) { + return a.order - b.order; + }); + files.forEach(function (file) { + localChallengeMap[keyCounter++] = file; + }); + challengeMap = _.cloneDeep(localChallengeMap); + } +})(); + module.exports = { getChallengeMapWithIds: function() { - // TODO finish this - if (challengeMap === null) { - buildChallengeMap(); - } - var challengeMapWithIds = {}; - Object.keys(challengeMap). - forEach(function(key) { - var onlyIds = challengeMap[key].challenges.map(function(elem) { - return elem.challengeId; + if (challengeMapWithIds) { + return challengeMapWithIds; + } else { + challengeMapWithIds = {}; + Object.keys(challengeMap).forEach(function (key) { + var onlyIds = challengeMap[key].challenges.map(function (elem) { + return elem._id; }); challengeMapWithIds[key] = onlyIds; }); - return challengeMapWithIds; + return challengeMapWithIds; + } + }, + + allChallengeIds: function() { + + if (allChallengeIds) { + return allChallengeIds; + } else { + allChallengeIds = []; + Object.keys(challengeMapWithIds).forEach(function(key) { + allChallengeIds.push(challengeMapWithIds[key].challenges); + }); + allChallengeIds = R.flatten(allChallengeIds); + } + return allChallengeIds; }, getChallengeMapWithNames: function() { - var challengeMapWithNames = {}; - Object.keys(challengeMap). - forEach(function(key) { - var onlyNames = challengeMap[key].challenges.map(function(elem) { - return elem.challengeName; + if (challengeMapWithNames) { + return challengeMapWithNames; + } else { + challengeMapWithNames = {}; + Object.keys(challengeMap). + forEach(function (key) { + var onlyNames = challengeMap[key].challenges.map(function (elem) { + return elem.name; + }); + challengeMapWithNames[key] = onlyNames; }); - challengeMapWithNames[key] = onlyNames; - }); - return challengeMapWithNames; + return challengeMapWithNames; + } }, - - sitemap: function sitemap(req, res, next) { var appUrl = 'http://www.freecodecamp.com'; var now = moment(new Date()).format('YYYY-MM-DD'); diff --git a/models/User.js b/models/User.js index 686468c8d7..6ddc3f7112 100644 --- a/models/User.js +++ b/models/User.js @@ -150,7 +150,19 @@ var userSchema = new mongoose.Schema({ finishedWaypoints: { type: Boolean, default: false }, sendMonthlyEmail: { type: Boolean, default: true }, challengesHash: {}, - currentChallenge: {} + currentChallenge: {}, + completedChallenges: [ + { + completedDate: Long, + _id: String, + name: String, + completedWith: String, + solution: String, + githubLink: String, + verified: Boolean + } + ], + uncompletedChallenges: Array }); /** diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.1.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.1.js index d32e54428e..2534a2456a 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.1.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.1.js @@ -1,69 +1,69 @@ var widgets = []; var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { - lineNumbers: true, - mode: "javascript", - theme: 'monokai', - runnable: true, - lint: true, - matchBrackets: true, - autoCloseBrackets: true, - scrollbarStyle: 'null', - lineWrapping: true, - gutters: ["CodeMirror-lint-markers"], - onKeyEvent: doLinting + lineNumbers: true, + mode: "javascript", + theme: 'monokai', + runnable: true, + lint: true, + matchBrackets: true, + autoCloseBrackets: true, + scrollbarStyle: 'null', + lineWrapping: true, + gutters: ["CodeMirror-lint-markers"], + onKeyEvent: doLinting }); var editor = myCodeMirror; editor.setSize("100%", "auto"); // Hijack tab key to enter two spaces intead editor.setOption("extraKeys", { - Tab: function(cm) { - if (cm.somethingSelected()){ - cm.indentSelection("add"); - } else { - var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); - cm.replaceSelection(spaces); - } - }, - "Shift-Tab": function(cm) { - if (cm.somethingSelected()){ - cm.indentSelection("subtract"); - } else { - var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); - cm.replaceSelection(spaces); - } - }, - "Ctrl-Enter": function() { - bonfireExecute(); - return false; + Tab: function(cm) { + if (cm.somethingSelected()){ + cm.indentSelection("add"); + } else { + var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); + cm.replaceSelection(spaces); } + }, + "Shift-Tab": function(cm) { + if (cm.somethingSelected()){ + cm.indentSelection("subtract"); + } else { + var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); + cm.replaceSelection(spaces); + } + }, + "Ctrl-Enter": function() { + bonfireExecute(); + return false; + } }); var attempts = 0; if (attempts) { - attempts = 0; + attempts = 0; } var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { - lineNumbers: false, - mode: "text", - theme: 'monokai', - readOnly: 'nocursor', - lineWrapping: true + lineNumbers: false, + mode: "text", + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true }); codeOutput.setValue('/**\n' + -' * Your output will go here.\n' + ' * Console.log() -type statements\n' + -' * will appear in your browser\'s\n' + ' * DevTools JavaScript console.\n' + -' */'); + ' * Your output will go here.\n' + ' * Console.log() -type statements\n' + + ' * will appear in your browser\'s\n' + ' * DevTools JavaScript console.\n' + + ' */'); codeOutput.setSize("100%", "100%"); var info = editor.getScrollInfo(); var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; if (info.top + info.clientHeight < after) - editor.scrollTo(null, after - info.clientHeight + 3); + editor.scrollTo(null, after - info.clientHeight + 3); var editorValue; @@ -73,9 +73,9 @@ var tests = tests || []; var allSeeds = ''; (function() { - challengeSeed.forEach(function(elem) { - allSeeds += elem + '\n'; - }); + challengeSeed.forEach(function(elem) { + allSeeds += elem + '\n'; + }); })(); editorValue = allSeeds; @@ -84,52 +84,52 @@ editorValue = allSeeds; myCodeMirror.setValue(editorValue); function doLinting () { - editor.operation(function () { - for (var i = 0; i < widgets.length; ++i) - editor.removeLineWidget(widgets[i]); - widgets.length = 0; - JSHINT(editor.getValue()); - for (var i = 0; i < JSHINT.errors.length; ++i) { - var err = JSHINT.errors[i]; - if (!err) continue; - var msg = document.createElement("div"); - var icon = msg.appendChild(document.createElement("span")); - icon.innerHTML = "!!"; - icon.className = "lint-error-icon"; - msg.appendChild(document.createTextNode(err.reason)); - msg.className = "lint-error"; - widgets.push(editor.addLineWidget(err.line - 1, msg, { - coverGutter: false, - noHScroll: true - })); - } - }); + editor.operation(function () { + for (var i = 0; i < widgets.length; ++i) + editor.removeLineWidget(widgets[i]); + widgets.length = 0; + JSHINT(editor.getValue()); + for (var i = 0; i < JSHINT.errors.length; ++i) { + var err = JSHINT.errors[i]; + if (!err) continue; + var msg = document.createElement("div"); + var icon = msg.appendChild(document.createElement("span")); + icon.innerHTML = "!!"; + icon.className = "lint-error-icon"; + msg.appendChild(document.createTextNode(err.reason)); + msg.className = "lint-error"; + widgets.push(editor.addLineWidget(err.line - 1, msg, { + coverGutter: false, + noHScroll: true + })); + } + }); }; $('#submitButton').on('click', function () { - bonfireExecute(); + bonfireExecute(); }); function bonfireExecute() { - attempts++; - ga('send', 'event', 'Challenge', 'ran-code', challengeName); - userTests= null; - $('#codeOutput').empty(); - var userJavaScript = myCodeMirror.getValue(); - userJavaScript = removeComments(userJavaScript); - userJavaScript = scrapeTests(userJavaScript); - // simple fix in case the user forgets to invoke their function + attempts++; + ga('send', 'event', 'Challenge', 'ran-code', challengeName); + userTests= null; + $('#codeOutput').empty(); + var userJavaScript = myCodeMirror.getValue(); + userJavaScript = removeComments(userJavaScript); + userJavaScript = scrapeTests(userJavaScript); + // simple fix in case the user forgets to invoke their function - submit(userJavaScript, function(cls, message) { - if (cls) { - codeOutput.setValue(message.error); - runTests('Error', null); - } else { - codeOutput.setValue(message.output); - message.input = removeLogs(message.input); - runTests(null, message); - } - }); + submit(userJavaScript, function(cls, message) { + if (cls) { + codeOutput.setValue(message.error); + runTests('Error', null); + } else { + codeOutput.setValue(message.output); + message.input = removeLogs(message.input); + runTests(null, message); + } + }); } @@ -139,108 +139,108 @@ var testSalt = Math.random(); var scrapeTests = function(userJavaScript) { - // insert tests from mongo - for (var i = 0; i < tests.length; i++) { - userJavaScript += '\n' + tests[i]; + // insert tests from mongo + for (var i = 0; i < tests.length; i++) { + userJavaScript += '\n' + tests[i]; + } + + var counter = 0; + var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/); + var match = regex.exec(userJavaScript); + while (match != null) { + var replacement = '//' + counter + testSalt; + userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length); + + if (!userTests) { + userTests= []; } + userTests.push({"text": match[0], "line": counter, "err": null}); + counter++; + match = regex.exec(userJavaScript); + } - var counter = 0; - var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/); - var match = regex.exec(userJavaScript); - while (match != null) { - var replacement = '//' + counter + testSalt; - userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length); - - if (!userTests) { - userTests= []; - } - userTests.push({"text": match[0], "line": counter, "err": null}); - counter++; - match = regex.exec(userJavaScript); - } - - return userJavaScript; + return userJavaScript; }; function removeComments(userJavaScript) { - var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|\/\/[^\n]*/g); - return userJavaScript.replace(regex, ''); + var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|\/\/[^\n]*/g); + return userJavaScript.replace(regex, ''); } function removeLogs(userJavaScript) { - return userJavaScript.replace(/(console\.[\w]+\s*\(.*\;)/g, ''); + return userJavaScript.replace(/(console\.[\w]+\s*\(.*\;)/g, ''); } var pushed = false; var createTestDisplay = function() { - if (pushed) { - userTests.pop(); + if (pushed) { + userTests.pop(); + } + for (var i = 0; i < userTests.length;i++) { + var test = userTests[i]; + var testDoc = document.createElement("div"); + if (test.err != null) { + console.log('Should be displaying bad tests'); + $(testDoc) + .html("
Hello Paragraph
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08802", @@ -101,7 +105,8 @@ "", "Hello Paragraph
", "-->" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08804", @@ -127,7 +132,8 @@ "", "Hello Paragraph
", "-->" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08833", @@ -151,7 +157,8 @@ "Hello Paragraph
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fed1348bd9aedf08833", @@ -176,7 +183,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08803", @@ -195,7 +203,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08805", @@ -218,7 +227,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aecf08806", @@ -248,7 +258,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aefe08806", @@ -274,7 +285,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08806", @@ -300,7 +312,8 @@ "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aede08807", @@ -329,7 +342,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08807", @@ -361,7 +375,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08808", @@ -399,7 +414,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08809", @@ -440,7 +456,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { @@ -478,7 +495,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9acdf08812", @@ -516,7 +534,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9bedf08813", @@ -559,7 +578,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08814", @@ -607,7 +627,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08815", @@ -654,7 +675,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08816", @@ -704,7 +726,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aede08817", @@ -757,7 +780,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { @@ -809,7 +833,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08820", @@ -861,7 +886,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { "_id": "bad87fee1348bd9aedf08818", @@ -913,7 +939,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { @@ -966,7 +993,8 @@ "", "Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.
", "Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.
" - ] + ], + "challengeType": 0 }, { @@ -1025,7 +1053,8 @@ "