diff --git a/.gitignore b/.gitignore
index a801149c52..9df97d6e25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ lib-cov
*.swp
.floo
.flooignore
+builtAssets/
*.env
pids
diff --git a/app.js b/app.js
index bd36ff727c..30df332f22 100755
--- a/app.js
+++ b/app.js
@@ -81,15 +81,15 @@ app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
-console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'production') {
app.all(/.*/, function (req, res, next) {
- var host = req.header("host");
+ var host = req.header('host');
+ var originalUrl = req['originalUrl'];
if (host.match(/^www\..*/i)) {
next();
} else {
- res.redirect(301, "http://www." + host);
+ res.redirect(301, "http://www." + host + originalUrl);
}
});
}
@@ -102,6 +102,8 @@ app.use(connectAssets({
path.join(__dirname, 'public/css'),
path.join(__dirname, 'public/js')
],
+ build: false,
+ buildDir: false,
helperContext: app.locals
}));
app.use(logger('dev'));
@@ -253,12 +255,8 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 }));
app.get('/', homeController.index);
-app.get('/privacy', function(req, res) {
- res.redirect(301, "/field-guide/free-code-camp's-privacy-policy");
-});
-
app.get('/nonprofit-project-instructions', function(req, res) {
- res.redirect(301, "/field-guide/free-code-camp's-privacy-policy");
+ res.redirect(301, '/field-guide/nonprofit-project-instructions');
});
app.get('/chat', resourcesController.chat);
@@ -299,9 +297,9 @@ app.get('/nodeschool-challenges', function(req, res) {
app.get('/news', function(req, res) {
res.redirect(301, '/stories/hot');
});
-app.get('/learn-to-code', resourcesController.about);
+app.get('/learn-to-code', challengeMapController.challengeMap);
app.get('/about', function(req, res) {
- res.redirect(301, '/learn-to-code');
+ res.redirect(301, '/map');
});
app.get('/signin', userController.getSignin);
@@ -384,17 +382,17 @@ app.post(
passportConf.isAuthenticated,
contactController.postDoneWithFirst100Hours
);
-//app.get(
-// '/nonprofit-project-instructions',
-// passportConf.isAuthenticated,
-// resourcesController.nonprofitProjectInstructions
-//);
+
app.post(
'/update-progress',
passportConf.isAuthenticated,
userController.updateProgress
);
+app.get('/privacy', function(req, res) {
+ res.redirect(301, '/field-guide/the-free-code-camp-privacy-policy');
+});
+
app.get('/api/slack', function(req, res) {
if (req.user) {
if (req.user.email) {
@@ -590,7 +588,7 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire);
app.get('/field-guide/:fieldGuideName', fieldGuideController.returnIndividualFieldGuide);
-app.get('/field-guide', fieldGuideController.returnNextFieldGuide);
+app.get('/field-guide/', fieldGuideController.returnNextFieldGuide);
app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide);
diff --git a/controllers/bonfire.js b/controllers/bonfire.js
index dbe60a0da0..68f22d9532 100644
--- a/controllers/bonfire.js
+++ b/controllers/bonfire.js
@@ -63,12 +63,12 @@ exports.returnNextBonfire = function(req, res, next) {
var uncompletedBonfires = req.user.uncompletedBonfires;
var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]});
- displayedBonfires.exec(function(err, bonfire) {
+ displayedBonfires.exec(function(err, bonfireFromMongo) {
if (err) {
return next(err);
}
- bonfire = bonfire.pop();
- if (bonfire === undefined) {
+ var bonfire = bonfireFromMongo.pop();
+ if (typeof bonfire === 'undefined') {
req.flash('errors', {
msg: "It looks like you've completed all the bonfires we have available. Good job!"
});
@@ -84,13 +84,13 @@ exports.returnIndividualBonfire = function(req, res, next) {
var bonfireName = dashedName.replace(/\-/g, ' ');
- Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfire) {
+ Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfireFromMongo) {
if (err) {
next(err);
}
- if (bonfire.length < 1) {
+ if (bonfireFromMongo.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a bonfire with that name. Please double check the name."
});
@@ -98,9 +98,9 @@ exports.returnIndividualBonfire = function(req, res, next) {
return res.redirect('/bonfires');
}
- bonfire = bonfire.pop();
+ var bonfire = bonfireFromMongo.pop();
var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-');
- if (dashedNameFull != dashedName) {
+ if (dashedNameFull !== dashedName) {
return res.redirect('../bonfires/' + dashedNameFull);
}
res.render('bonfire/show', {
@@ -326,7 +326,6 @@ exports.completedBonfire = function (req, res, next) {
return next(err);
}
if (user) {
- debug('Saving user');
res.send(true);
}
});
diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js
index b858bf7385..e10c7ab409 100644
--- a/controllers/challengeMap.js
+++ b/controllers/challengeMap.js
@@ -52,14 +52,31 @@ module.exports = {
if (challenge.challengeType === 4) { return challenge }
});
- res.render('challengeMap/show', {
- title: "A map of all Free Code Camp's Challenges",
- bonfires: bonfireList,
- waypoints: waypoints,
- ziplines: ziplines,
- basejumps: basejumps,
- completedBonfireList: completedBonfireList,
- completedCoursewareList: completedCoursewareList
+ function numberWithCommas(x) {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ }
+
+ var date1 = new Date("10/15/2014");
+ var date2 = new Date();
+ var timeDiff = Math.abs(date2.getTime() - date1.getTime());
+ var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
+
+ User.count({}, function (err, camperCount) {
+ if (err) {
+ debug('User err: ', err);
+ return next(err);
+ }
+ res.render('challengeMap/show', {
+ daysRunning: daysRunning,
+ camperCount: numberWithCommas(camperCount),
+ title: "A map of all Free Code Camp's Challenges",
+ bonfires: bonfireList,
+ waypoints: waypoints,
+ ziplines: ziplines,
+ basejumps: basejumps,
+ completedBonfireList: completedBonfireList,
+ completedCoursewareList: completedCoursewareList
+ });
});
}
};
diff --git a/controllers/challenges.js b/controllers/challenges.js
deleted file mode 100644
index 463edda920..0000000000
--- a/controllers/challenges.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * GET /
- * Challenges.
- */
-var _ = require('lodash'),
- debug = require('debug')('freecc:cntr:challenges'),
- Challenge = require('./../models/Challenge'),
- resources = require('./resources');
-
-var highestChallengeNumber = 53;
-
-
-exports.returnNextChallenge = function(req, res) {
- if (req.user) {
- ch = req.user.challengesHash;
- if (req.user.challengesHash[0] > 0) {
- var max = Object.keys(ch).reduce(function(max, key) {
- return (max === undefined || ch[key] > ch[max]) ? +key : max;
- });
- nextChallenge = max + 1;
- res.redirect('challenges/' + nextChallenge);
- } else {
- res.redirect('challenges/0');
- }
- } else {
- return res.redirect('../challenges/0');
- }
-};
-
-exports.returnChallenge = function(req, res, next) {
- var challengeNumber = parseInt(req.params.challengeNumber) || 0;
-
- if (challengeNumber > highestChallengeNumber) {
- req.flash('errors', {
- msg: "It looks like you've either completed all the challenges we have available or requested a challenge we don't have."
- });
- return res.redirect('../challenges/0');
- }
- Challenge.find({}, null, { sort: { challengeNumber: 1 } }, function(err, c) {
- if (err) {
- debug('Challenge err: ', err);
- return next(err);
- }
- res.render('challenges/show', {
- title: 'Challenge: ' + c[challengeNumber].name,
- name: c[challengeNumber].name,
- video: c[challengeNumber].video,
- time: c[challengeNumber].time,
- steps: c[challengeNumber].steps,
- number: challengeNumber,
- cc: req.user ? req.user.challengesHash : undefined,
- points: req.user ? req.user.points : undefined,
- verb: resources.randomVerb(),
- phrase: resources.randomPhrase(),
- compliment: resources.randomCompliment(),
- challenges: c
- });
- });
-};
diff --git a/controllers/courseware.js b/controllers/courseware.js
index a7d2b4af0c..267f09d6e5 100644
--- a/controllers/courseware.js
+++ b/controllers/courseware.js
@@ -28,6 +28,10 @@ exports.returnNextCourseware = function(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
+ if (req.user.finishedWaypoints && req.user.uncompletedBonfires.length > 0) {
+ return res.redirect('../bonfires')
+ }
+
var completed = req.user.completedCoursewares.map(function (elem) {
return elem._id;
});
@@ -50,14 +54,14 @@ exports.returnNextCourseware = function(req, res, next) {
}
courseware = courseware.pop();
- if (courseware === undefined) {
+ if (typeof courseware === 'undefined') {
req.flash('errors', {
msg: "It looks like you've completed all the courses we have " +
"available. Good job!"
});
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
- nameString = courseware.name.toLowerCase().replace(/\s/g, '-');
+ var nameString = courseware.name.toLowerCase().replace(/\s/g, '-');
return res.redirect('../challenges/' + nameString);
});
};
@@ -68,23 +72,23 @@ exports.returnIndividualCourseware = function(req, res, next) {
var coursewareName = dashedName.replace(/\-/g, ' ');
Courseware.find({'name': new RegExp(coursewareName, 'i')},
- function(err, courseware) {
+ function(err, coursewareFromMongo) {
if (err) {
next(err);
}
// Handle not found
- if (courseware.length < 1) {
+ if (coursewareFromMongo.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a challenge with that name. " +
"Please double check the name."
});
return res.redirect('/challenges');
}
- courseware = courseware.pop();
+ var courseware = coursewareFromMongo.pop();
// Redirect to full name if the user only entered a partial
var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-');
- if (dashedNameFull != dashedName) {
+ if (dashedNameFull !== dashedName) {
return res.redirect('../challenges/' + dashedNameFull);
}
@@ -247,6 +251,9 @@ exports.completedCourseware = function (req, res, next) {
var isCompletedDate = Math.round(+new Date());
var coursewareHash = req.body.coursewareInfo.coursewareHash;
+ if (coursewareHash === "bd7139d8c441eddfaeb5bdef") {
+ req.user.finishedWaypoints = true;
+ }
req.user.completedCoursewares.push({
_id: coursewareHash,
@@ -292,7 +299,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) {
if (isCompletedWith) {
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1);
- paired.exec(function (err, pairedWith) {
+ paired.exec(function (err, pairedWithFromMongo) {
if (err) {
return next(err);
} else {
@@ -301,7 +308,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedCoursewares.splice(index, 1);
}
- pairedWith = pairedWith.pop();
+ var pairedWith = pairedWithFromMongo.pop();
req.user.completedCoursewares.push({
_id: coursewareHash,
diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js
index d131e6436b..51f01e8529 100644
--- a/controllers/fieldGuide.js
+++ b/controllers/fieldGuide.js
@@ -9,12 +9,12 @@ exports.returnIndividualFieldGuide = function(req, res, next) {
var fieldGuideName = dashedName.replace(/\-/g, ' ');
- FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) {
+ FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuideFromMongo) {
if (err) {
next(err);
}
- if (fieldGuide.length < 1) {
+ if (fieldGuideFromMongo.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a field guide entry with that name. Please double check the name."
});
@@ -22,9 +22,9 @@ exports.returnIndividualFieldGuide = function(req, res, next) {
return res.redirect('/field-guide');
}
- fieldGuide = fieldGuide.pop();
+ var fieldGuide = R.head(fieldGuideFromMongo);
var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, '');
- if (dashedNameFull != dashedName) {
+ if (dashedNameFull !== dashedName) {
return res.redirect('../field-guide/' + dashedNameFull);
}
res.render('field-guide/show', {
@@ -42,7 +42,7 @@ exports.showAllFieldGuides = function(req, res) {
if (req.user && req.user.completedFieldGuides) {
data.completedFieldGuides = req.user.completedFieldGuides;
} else {
- data.completedFieldGuides = []
+ data.completedFieldGuides = [];
}
res.send(data);
};
@@ -54,22 +54,21 @@ exports.returnNextFieldGuide = function(req, res, next) {
var completed = req.user.completedFieldGuides;
- req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) {
+ var uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
});
+ req.user.uncompletedFieldGuides = uncompletedFieldGuides;
req.user.save();
- var uncompletedFieldGuides = req.user.uncompletedFieldGuides;
-
var displayedFieldGuides = FieldGuide.find({'_id': uncompletedFieldGuides[0]});
displayedFieldGuides.exec(function(err, fieldGuide) {
if (err) {
return next(err);
}
fieldGuide = fieldGuide.pop();
- if (fieldGuide === undefined) {
+ if (typeof fieldGuide === 'undefined') {
req.flash('success', {
msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here."
});
@@ -81,14 +80,13 @@ exports.returnNextFieldGuide = function(req, res, next) {
};
exports.completedFieldGuide = function (req, res, next) {
- debug('params in completedFieldGuide', req.params);
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
req.user.completedFieldGuides.push(fieldGuideId);
var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId);
if (index > -1) {
- req.user.progressTimestamps.push(Date.now() || 0);
+ req.user.progressTimestamps.push(Date.now());
req.user.uncompletedFieldGuides.splice(index, 1);
}
diff --git a/controllers/home.js b/controllers/home.js
index 836ad9e62b..686cd2a80c 100644
--- a/controllers/home.js
+++ b/controllers/home.js
@@ -5,7 +5,7 @@
exports.index = function(req, res) {
if (req.user) {
- res.redirect('/challenges/')
+ res.redirect('/map')
} else {
res.render('home', {
title: 'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits'
diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js
index a7871327a3..d3b824cf3e 100644
--- a/controllers/nonprofits.js
+++ b/controllers/nonprofits.js
@@ -132,7 +132,6 @@ exports.returnIndividualNonprofit = function(req, res, next) {
var hasShownInterest = nonprofit.interestedCampers.filter(function ( obj ) {
return obj.username === req.user.profile.username;
});
- console.log(hasShownInterest);
if (hasShownInterest.length === 0) {
buttonActive = true;
}
diff --git a/controllers/resources.js b/controllers/resources.js
index a3f32179c7..29e38c67c8 100644
--- a/controllers/resources.js
+++ b/controllers/resources.js
@@ -8,7 +8,6 @@ var async = require('async'),
Nonprofit = require('./../models/Nonprofit'),
Comment = require('./../models/Comment'),
resources = require('./resources.json'),
- steps = resources.steps,
secrets = require('./../config/secrets'),
bonfires = require('../seed_data/bonfires.json'),
nonprofits = require('../seed_data/nonprofits.json'),
@@ -21,6 +20,12 @@ var async = require('async'),
request = require('request'),
R = require('ramda');
+/**
+ * Cached values
+ */
+var allBonfireIds, allBonfireNames, allCoursewareIds, allCoursewareNames,
+ allFieldGuideIds, allFieldGuideNames, allNonprofitNames;
+
/**
* GET /
* Resources.
@@ -38,63 +43,106 @@ Array.zip = function(left, right, combinerFunction) {
};
module.exports = {
- privacy: function privacy(req, res) {
- res.render('resources/privacy', {
- title: 'Privacy'
- });
- },
-
sitemap: function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD');
- User.find({'profile.username': {'$ne': '' }}, function(err, users) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- Courseware.find({}, function (err, challenges) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- Bonfire.find({}, function (err, bonfires) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- Story.find({}, function (err, stories) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- Nonprofit.find({}, function (err, nonprofits) {
+
+ async.parallel({
+ users: function(callback) {
+ User.aggregate()
+ .group({_id: 1, usernames: { $addToSet: '$profile.username'}})
+ .match({'profile.username': { $ne: ''}})
+ .exec(function(err, users) {
if (err) {
debug('User err: ', err);
- return next(err);
+ callback(err);
+ } else {
+ callback(null, users[0].usernames);
}
- FieldGuide.find({}, function (err, fieldGuides) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- res.header('Content-Type', 'application/xml');
- res.render('resources/sitemap', {
- appUrl: appUrl,
- now: now,
- users: users,
- challenges: challenges,
- bonfires: bonfires,
- stories: stories,
- nonprofits: nonprofits,
- fieldGuides: fieldGuides
- });
- });
});
+ },
+
+ challenges: function (callback) {
+ Courseware.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, challenges) {
+ if (err) {
+ debug('Courseware err: ', err);
+ callback(err);
+ } else {
+ callback(null, challenges[0].names);
+ }
+ });
+ },
+ bonfires: function (callback) {
+ Bonfire.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, bonfires) {
+ if (err) {
+ debug('Bonfire err: ', err);
+ callback(err);
+ } else {
+ callback(null, bonfires[0].names);
+ }
});
- });
- });
- });
+ },
+ stories: function (callback) {
+ Story.aggregate()
+ .group({_id: 1, links: {$addToSet: '$link'}})
+ .exec(function (err, stories) {
+ if (err) {
+ debug('Story err: ', err);
+ callback(err);
+ } else {
+ callback(null, stories[0].links);
+ }
+ });
+ },
+ nonprofits: function (callback) {
+ Nonprofit.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, nonprofits) {
+ if (err) {
+ debug('User err: ', err);
+ callback(err);
+ } else {
+ callback(null, nonprofits[0].names);
+ }
+ });
+ },
+ fieldGuides: function (callback) {
+ FieldGuide.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, fieldGuides) {
+ if (err) {
+ debug('User err: ', err);
+ callback(err);
+ } else {
+ callback(null, fieldGuides[0].names);
+ }
+ });
+ }
+ }, function (err, results) {
+ if (err) {
+ return next(err);
+ } else {
+ setTimeout(function() {
+ res.header('Content-Type', 'application/xml');
+ res.render('resources/sitemap', {
+ appUrl: appUrl,
+ now: now,
+ users: results.users,
+ challenges: results.challenges,
+ bonfires: results.bonfires,
+ stories: results.stories,
+ nonprofits: results.nonprofits,
+ fieldGuides: results.fieldGuides
+ });
+ }, 0);
+ }
+ }
+ );
},
chat: function chat(req, res) {
@@ -161,132 +209,163 @@ module.exports = {
debug('User err: ', err);
return next(err);
}
- User.count({'points': {'$gt': 53}}, function (err, all) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
- res.render('resources/learn-to-code', {
- title: 'About Free Code Camp',
- daysRunning: daysRunning,
- c3: numberWithCommas(c3),
- announcements: announcements
- });
+ res.render('resources/learn-to-code', {
+ title: 'About Free Code Camp',
+ daysRunning: daysRunning,
+ c3: numberWithCommas(c3),
+ announcements: announcements
});
});
},
randomPhrase: function() {
- var phrases = resources.phrases;
- return phrases[Math.floor(Math.random() * phrases.length)];
+ return resources.phrases[Math.floor(
+ Math.random() * resources.phrases.length)];
},
randomVerb: function() {
- var verbs = resources.verbs;
- return verbs[Math.floor(Math.random() * verbs.length)];
+ return resources.verbs[Math.floor(
+ Math.random() * resources.verbs.length)];
},
randomCompliment: function() {
- var compliments = resources.compliments;
- return compliments[Math.floor(Math.random() * compliments.length)];
+ return resources.compliments[Math.floor(
+ Math.random() * resources.compliments.length)];
},
allBonfireIds: function() {
- return bonfires.map(function(elem) {
- return {
- _id: elem._id,
- difficulty: elem.difficulty
- }
- })
- .sort(function(a, b) {
- return a.difficulty - b.difficulty;
- })
- .map(function(elem) {
- return elem._id;
- });
+ if (allBonfireIds) {
+ return allBonfireIds;
+ } else {
+ allBonfireIds = bonfires.
+ map(function (elem) {
+ return {
+ _id: elem._id,
+ difficulty: elem.difficulty
+ };
+ }).
+ sort(function (a, b) {
+ return a.difficulty - b.difficulty;
+ }).
+ map(function (elem) {
+ return elem._id;
+ });
+ return allBonfireIds;
+ }
},
allFieldGuideIds: function() {
- return fieldGuides.map(function(elem) {
- return {
- _id: elem._id,
- }
- })
- .map(function(elem) {
- return elem._id;
- });
+ if (allFieldGuideIds) {
+ return allFieldGuideIds;
+ } else {
+ allFieldGuideIds = fieldGuides.
+ map(function (elem) {
+ return elem._id;
+ });
+ return allFieldGuideIds;
+ }
},
allBonfireNames: function() {
- return bonfires.map(function(elem) {
- return {
- name: elem.name,
- difficulty: elem.difficulty,
- _id: elem._id
- }
- })
- .sort(function(a, b) {
- return a.difficulty - b.difficulty;
- })
- .map (function(elem) {
- return {
- name : elem.name,
- _id: elem._id
- }
- });
+ if (allBonfireNames) {
+ return allBonfireNames;
+ } else {
+ allBonfireNames = bonfires.
+ map(function (elem) {
+ return {
+ name: elem.name,
+ difficulty: elem.difficulty,
+ _id: elem._id
+ };
+ }).
+ sort(function (a, b) {
+ return a.difficulty - b.difficulty;
+ }).
+ map(function (elem) {
+ return {
+ name: elem.name,
+ _id: elem._id
+ };
+ });
+ return allBonfireNames;
+ }
},
allFieldGuideNames: function() {
- return fieldGuides.map(function(elem) {
- return {
- name: elem.name
- }
- })
+ if (allFieldGuideNames) {
+ return allFieldGuideNames;
+ } else {
+ allFieldGuideNames = fieldGuides.
+ map(function (elem) {
+ return {
+ name: elem.name
+ };
+ });
+ return allFieldGuideNames;
+ }
},
allNonprofitNames: function() {
- return nonprofits.map(function(elem) {
- return {
- name: elem.name
- }
- })
+ if (allNonprofitNames) {
+ return allNonprofitNames;
+ } else {
+ allNonprofitNames = nonprofits.
+ map(function (elem) {
+ return {
+ name: elem.name
+ };
+ });
+ return allNonprofitNames;
+ }
},
allCoursewareIds: function() {
- return coursewares.map(function(elem) {
- return {
- _id: elem._id,
- difficulty: elem.difficulty
- };
- })
- .sort(function(a, b) {
- return a.difficulty - b.difficulty;
- })
- .map(function(elem) {
- return elem._id;
- });
+ if (allCoursewareIds) {
+ return allCoursewareIds;
+ } else {
+ allCoursewareIds = coursewares.
+ map(function (elem) {
+ return {
+ _id: elem._id,
+ difficulty: elem.difficulty
+ };
+ }).
+ sort(function (a, b) {
+ return a.difficulty - b.difficulty;
+ }).
+ map(function (elem) {
+ return elem._id;
+ });
+ return allCoursewareIds;
+ }
},
allCoursewareNames: function() {
- return coursewares.map(function(elem) {
- return {
- name: elem.name,
- difficulty: elem.difficulty,
- challengeType: elem.challengeType,
- _id: elem._id
- };
- })
- .sort(function(a, b) {
- return a.difficulty - b.difficulty;
- })
- .map (function(elem) {
- return {
- name: elem.name,
- challengeType: elem.challengeType,
- _id: elem._id
- };
- });
+ if (allCoursewareNames) {
+ return allCoursewareNames;
+ } else {
+ allCoursewareNames = coursewares.
+ map(function (elem) {
+ return {
+ name: elem.name,
+ difficulty: elem.difficulty,
+ challengeType: elem.challengeType,
+ _id: elem._id
+ };
+ }).
+ sort(function (a, b) {
+ return a.difficulty - b.difficulty;
+ }).
+ map(function (elem) {
+ return {
+ name: elem.name,
+ challengeType: elem.challengeType,
+ _id: elem._id
+ };
+ });
+ return allCoursewareNames;
+ }
},
whichEnvironment: function() {
@@ -302,8 +381,9 @@ module.exports = {
var metaDescription = $("meta[name='description']");
var metaImage = $("meta[property='og:image']");
var urlImage = metaImage.attr('content') ? metaImage.attr('content') : '';
+ var metaTitle = $('title');
var description = metaDescription.attr('content') ? metaDescription.attr('content') : '';
- result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ...";
+ result.title = metaTitle.text().length < 141 ? metaTitle.text() : metaTitle.text().slice(0, 137) + " ...";
result.image = urlImage;
result.description = description;
callback(null, result);
@@ -360,7 +440,9 @@ module.exports = {
});
}, foundStories);
async.parallel(tasks, function(err) {
- if (err) { return cb(err); }
+ if (err) {
+ return cb(err);
+ }
cb();
});
}
diff --git a/controllers/story.js b/controllers/story.js
index 9047279e3f..1f1bf90247 100755
--- a/controllers/story.js
+++ b/controllers/story.js
@@ -118,7 +118,7 @@ exports.preSubmit = function(req, res) {
exports.returnIndividualStory = function(req, res, next) {
var dashedName = req.params.storyName;
- var storyName = dashedName.replace(/\-/g, ' ');
+ var storyName = dashedName.replace(/\-/g, ' ').trim();
Story.find({'storyLink': storyName}, function(err, story) {
if (err) {
@@ -155,7 +155,7 @@ exports.returnIndividualStory = function(req, res, next) {
title: story.headline,
link: story.link,
originalStoryLink: dashedName,
- originalStoryAuthorEmail: story.author.email || "",
+ originalStoryAuthorEmail: story.author.email || '',
author: story.author,
description: story.description,
rank: story.upVotes.length,
@@ -321,9 +321,10 @@ exports.storySubmission = function(req, res, next) {
.replace(/\'/g, '')
.replace(/\"/g, '')
.replace(/,/g, '')
- .replace(/[^a-z0-9]/gi, ' ')
.replace(/\s+/g, ' ')
- .toLowerCase();
+ .replace(/[^a-z0-9\s]/gi, '')
+ .toLowerCase()
+ .trim();
var link = data.link;
if (link.search(/^https?:\/\//g) === -1) {
link = 'http://' + link;
@@ -334,7 +335,7 @@ exports.storySubmission = function(req, res, next) {
}
// if duplicate storyLink add unique number
- storyLink = (storyCount == 0) ? storyLink : storyLink + ' ' + storyCount;
+ storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount;
var link = data.link;
if (link.search(/^https?:\/\//g) === -1) {
@@ -398,7 +399,7 @@ exports.storySubmission = function(req, res, next) {
var comment = new Comment({
associatedPost: data.associatedPost,
originalStoryLink: data.originalStoryLink,
- originalStoryAuthorEmail: req.user.email,
+ originalStoryAuthorEmail: data.originalStoryAuthorEmail,
body: sanitizedBody,
rank: 0,
upvotes: 0,
@@ -496,53 +497,54 @@ exports.storySubmission = function(req, res, next) {
return next(err);
}
try {
- Context.find({'_id': comment.associatedPost}, function (err, associatedStory) {
+ // Based on the context retrieve the parent object of the comment (Story/Comment)
+ Context.find({'_id': data.associatedPost}, function (err, associatedContext) {
if (err) {
return next(err);
}
- associatedStory = associatedStory.pop();
- if (associatedStory) {
- associatedStory.comments.push(data._id);
- associatedStory.save(function (err) {
+ associatedContext = associatedContext.pop();
+ if (associatedContext) {
+ associatedContext.comments.push(data._id);
+ associatedContext.save(function (err) {
if (err) {
return next(err);
}
res.send(true);
});
}
- User.findOne({'profile.username': associatedStory.author.username}, function(err, recipient) {
+ // Find the author of the parent object
+ User.findOne({'profile.username': associatedContext.author.username}, function(err, recipient) {
if (err) {
return next(err);
}
- var recipients = '';
- if (data.originalStoryAuthorEmail && (data.originalStoryAuthorEmail !== recipient.email)) {
- recipients = data.originalStoryAuthorEmail + ',' + recipient.email;
- } else {
- recipients = recipient.email;
+ // If the emails of both authors differ, only then proceed with email notification
+ if (data.author.email && (data.author.email !== recipient.email)) {
+ var transporter = nodemailer.createTransport({
+ service: 'Mandrill',
+ auth: {
+ user: secrets.mandrill.user,
+ pass: secrets.mandrill.password
+ }
+ });
+
+ var mailOptions = {
+ to: recipient.email,
+ from: 'Team@freecodecamp.com',
+ subject: data.author.username + ' replied to your post on Camper News',
+ text: [
+ 'Just a quick heads-up: ' + data.author.username + ' replied to you on Camper News.',
+ 'You can keep this conversation going.',
+ 'Just head back to the discussion here: http://freecodecamp.com/stories/' + data.originalStoryLink,
+ '- the Free Code Camp Volunteer Team'
+ ].join('\n')
+ };
+
+ transporter.sendMail(mailOptions, function (err) {
+ if (err) {
+ return err;
+ }
+ });
}
- var transporter = nodemailer.createTransport({
- service: 'Mandrill',
- auth: {
- user: secrets.mandrill.user,
- pass: secrets.mandrill.password
- }
- });
- var mailOptions = {
- to: recipients,
- from: 'Team@freecodecamp.com',
- subject: associatedStory.author.username + " replied to your post on Camper News",
- text: [
- "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.",
- "You can keep this conversation going.",
- "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink,
- '- the Free Code Camp Volunteer Team'
- ].join('\n')
- };
- transporter.sendMail(mailOptions, function (err) {
- if (err) {
- return err;
- }
- });
});
});
} catch (e) {
diff --git a/controllers/user.js b/controllers/user.js
index 034716ddcb..c00a0a1f6d 100644
--- a/controllers/user.js
+++ b/controllers/user.js
@@ -253,7 +253,7 @@ exports.checkExistingUsername = function(req, res, next) {
exports.checkUniqueEmail = function(req, res, next) {
User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) {
if (err) { return next(err); }
- if (data == 1) {
+ if (data === 1) {
return res.send(true);
} else {
return res.send(false);
@@ -271,7 +271,7 @@ exports.returnUser = function(req, res, next) {
User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) {
if (err) { debug('Username err: ', err); next(err); }
if (user[0]) {
- var user = user[0];
+ user = user[0];
user.progressTimestamps = user.progressTimestamps.sort(function(a, b) {
return a - b;
@@ -284,6 +284,7 @@ exports.returnUser = function(req, res, next) {
var tmpLongest = 1;
var timeKeys = R.keys(timeObject);
+
for (var i = 1; i <= timeKeys.length; i++) {
if (moment(timeKeys[i - 1]).add(1, 'd').toString()
=== moment(timeKeys[i]).toString()) {
@@ -305,13 +306,17 @@ exports.returnUser = function(req, res, next) {
var data = {};
var progressTimestamps = user.progressTimestamps;
- for (var i = 0; i < progressTimestamps.length; i++) {
- data[(progressTimestamps[i] / 1000).toString()] = 1;
- }
+ progressTimestamps.forEach(function(timeStamp) {
+ data[(timeStamp / 1000)] = 1;
+ });
+
+ //for (var i = 0; i < progressTimestamps.length; i++) {
+ // data[(progressTimestamps[i] / 1000).toString()] = 1;
+ //}
user.currentStreak = user.currentStreak || 1;
user.longestStreak = user.longestStreak || 1;
- challenges = user.completedCoursewares.filter(function ( obj ) {
+ var challenges = user.completedCoursewares.filter(function ( obj ) {
return !!obj.solution;
});
res.render('account/show', {
diff --git a/models/User.js b/models/User.js
index 0ad81d2556..5874bb2bdb 100644
--- a/models/User.js
+++ b/models/User.js
@@ -140,6 +140,7 @@ var userSchema = new mongoose.Schema({
default: 0
},
needsMigration: { type: Boolean, default: true },
+ finishedWaypoints: { type: Boolean, default: false },
challengesHash: {}
});
diff --git a/public/css/main.less b/public/css/main.less
index d6e905c8ea..cf93cebef7 100644
--- a/public/css/main.less
+++ b/public/css/main.less
@@ -273,11 +273,6 @@ ul {
height: 30px;
margin-top: -5px;
}
- @media (min-width: 767px) and (max-width: 890px) {
- height: 30px;
- margin-top: -5px;
- }
-
}
.navbar-right {
@@ -790,10 +785,52 @@ iframe.iphone {
transition: background .2s ease-in-out, border .2s ease-in-out;
}
+@media (max-width: 991px) {
+ .navbar-header {
+ float: none;
+ }
+
+ .navbar-toggle {
+ display: block;
+ }
+
+ .navbar-collapse.collapse {
+ display: none !important;
+ }
+
+ .navbar-nav {
+ float: none !important;
+ margin: 7.5px -15px;
+ }
+
+ .navbar-nav > li {
+ float: none;
+ }
+
+ .navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+
+ .navbar-text {
+ float: none;
+ margin: 15px 0;
+ }
+
+ /* since 3.1.0 */
+ .navbar-collapse.collapse.in {
+ display: block !important;
+ }
+
+ .collapsing {
+ overflow: hidden !important;
+ }
+}
+
.hamburger {
width: 80px;
padding-left: 0px;
- padding-right: 0px;
+ padding-right: 8px;
margin-left: 0px;
margin-right: 2px;
text-align:left;
@@ -804,7 +841,7 @@ iframe.iphone {
line-height: 0.75em;
margin-top: 10px;
font-size: 16px;
- margin-left: -5px;
+ margin-left: -8px;
}
.tight-h3 {
diff --git a/public/js/main.js b/public/js/main.js
index 59905c18e3..5ce2c1d9bb 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -4,13 +4,6 @@ $(document).ready(function() {
ga('send', 'event', 'Challenge', 'load', challengeName);
}
- // When introducing a new announcement, change the localStorage attribute
- // and the HTML located in the footer
- if (!localStorage || !localStorage.nodeSchoolAnnouncement) {
- $('#announcementModal').modal('show');
- localStorage.fccShowAnnouncement = "true";
- }
-
var CSRF_HEADER = 'X-CSRF-Token';
var setCSRFToken = function(securityToken) {
@@ -107,7 +100,6 @@ $(document).ready(function() {
});
$('.next-field-guide-button').on('click', function() {
- console.log('click');
var fieldGuideId = $('#fieldGuideId').text();
completedFieldGuide(fieldGuideId);
});
@@ -133,7 +125,6 @@ $(document).ready(function() {
});
$('#next-courseware-button').on('click', function() {
- console.log(passedCoursewareHash);
if ($('.signup-btn-nav').length < 1) {
switch (challengeType) {
case 0:
@@ -204,10 +195,6 @@ $(document).ready(function() {
}
});
- $('.all-challenges').on('click', function() {
- $('#show-all-dialog').modal('show');
- });
-
$('#showAllButton').on('click', function() {
$('#show-all-dialog').modal('show');
});
@@ -217,7 +204,7 @@ $(document).ready(function() {
window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1);
});
-// Bonfire instructions functions
+ // Bonfire instructions functions
$('#more-info').on('click', function() {
ga('send', 'event', 'Challenge', 'more-info', challengeName);
$('#brief-instructions').hide();
@@ -290,7 +277,7 @@ $(document).ready(function() {
$('#story-submit').on('click', storySubmitButtonHandler);
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
- $('comment-button').unbind('click');
+ $('#comment-button').unbind('click');
var data = $('#comment-box').val();
$('#comment-button').attr('disabled', 'disabled');
@@ -298,11 +285,14 @@ $(document).ready(function() {
{
data: {
associatedPost: storyId,
+ originalStoryLink: originalStoryLink,
+ originalStoryAuthorEmail: originalStoryAuthorEmail,
body: data
}
})
.fail(function (xhr, textStatus, errorThrown) {
$('#comment-button').attr('disabled', false);
+ $('#comment-button').bind('click', commentSubmitButtonHandler);
})
.done(function (data, textStatus, xhr) {
window.location.reload();
diff --git a/seed_data/bonfireMDNlinks.js b/seed_data/bonfireMDNlinks.js
index 7038fb414d..65ca1e0d02 100644
--- a/seed_data/bonfireMDNlinks.js
+++ b/seed_data/bonfireMDNlinks.js
@@ -1,6 +1,6 @@
// MDN Links
-/* These links are for Bonfires. Each key/value pair is used to render a Bonfire with approrpiate links.
+/* These links are for Bonfires. Each key/value pair is used to render a Bonfire with appropriate links.
The text of the key is what the link text will be, e.g. Global Array Object
@@ -67,7 +67,7 @@ var links =
"Array.sort()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort",
"Array.splice()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice",
"Array.toString()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString",
-
+
// ======== MATH METHODS
"Math.max()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max",
"Math.min()" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min",
diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json
index d3207d38f9..24d2813064 100644
--- a/seed_data/bonfires.json
+++ b/seed_data/bonfires.json
@@ -644,7 +644,8 @@
"difficulty": "3.12",
"description": [
"Fill in the object constructor with the methods specified in the tests.",
- "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(), setLastName(), and setFullName().",
+ "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(first), setLastName(last), and setFullName(firstAndLast).",
+ "All functions that take an argument have an arity of 1, and the argument will be a string.",
"These methods must be the only available means for interacting with the object."
],
"challengeSeed": "var Person = function(firstAndLast) {\n return firstAndLast;\r\n};\n\nvar bob = new Person('Bob Ross');\nbob.getFullName();",
diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json
index 738c5d8b7e..1f71a32e70 100644
--- a/seed_data/coursewares.json
+++ b/seed_data/coursewares.json
@@ -532,7 +532,7 @@
"_id": "bd7153d8c441eddfaeb5bdff",
"name": "Start a Node.js Server",
"difficulty": 0.39,
- "challengeSeed": "",
+ "challengeSeed": "114685061",
"description": [
"We'll build this Waypoint on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.",
"If you don't already have Cloud 9 account, create one now at http://c9.io.",
@@ -556,7 +556,7 @@
"_id": "bd7153d8c441eddfaeb5bd0f",
"name": "Manage Packages with NPM",
"difficulty": 0.40,
- "challengeSeed": "",
+ "challengeSeed": "114685061",
"description": [
"We'll build this Waypoint on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.",
"If you don't already have Cloud 9 account, create one now at http://c9.io.",
@@ -580,7 +580,7 @@
"_id": "bd7153d8c441eddfaeb5bd1f",
"name": "Build Web Apps with Express.js",
"difficulty": 0.41,
- "challengeSeed": "",
+ "challengeSeed": "114685061",
"description": [
"We'll build this Waypoint on Cloud 9, a powerful online code editor with a full Ubuntu Linux workspace, all running in the cloud.",
"If you don't already have Cloud 9 account, create one now at http://c9.io.",
@@ -634,10 +634,8 @@
"Go to http://freecodecamp.com/bonfires and start working through our Bonfire challenges.",
"Once you you finish pair programming, end the session in Screen Hero session.",
"Congratulations! You have completed your first pair programming session.",
- "Pair program as much as possible with different campers until you've completed all the Bonfire, Zipline and Basejump challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!",
- "Mark this challenge as complete and move on to the Bonfires.",
- "Keep in mind, the Bonfires are a significant challenge in and of themselves. You are not expected to complete them in one sitting, or to complete them before moving on to our next challenges. Mix them in as you keep learning and have fun!",
- "In order to participate in our non-profit projects you will need to have successfully completed all \"3 flame\" bonfires and below. Lastly, completing bonfires is not meant to be a sisyphean task. You won't be required to complete new bonfires that are of lower difficuly than you've already completed!"
+ "Pair program as much as possible with different campers until you've completed all the Bonfire challenges. This is a big time investment, but the JavaScript practice you get will be well worth it!",
+ "Mark this challenge as complete and move on to the Bonfires."
],
"challengeType": 2,
"tests": []
@@ -681,7 +679,7 @@
"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\",\"notmichaelmcdonald\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"joe_at_underflow\",\"noobs2ninjas\",\"mdwasp\",\"beohoff\",\"xenocomagain\"]
",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -700,7 +698,7 @@
"User Story: As a user, I can click a button to show me a new random quote.",
"Bonus User Story: As a user, I can press a button to tweet out a quote.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -723,7 +721,7 @@
"Hint: Get a zipcode's weather (in Kelvin) at http://api.openweathermap.org/data/2.5/weather?q=99705
.",
"Hint: Get your current user's zipcode (based on their IP address) with this line of jQuery: $.get(\"http://ipinfo.io\", function(response) {}, \"jsonp\");
",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -745,7 +743,7 @@
"Bonus User Story: As a user, I can see how many upvotes each story has.",
"Hint: Here's the Camper News Hot Stories API endpoint: http://www.freecodecamp.com/stories/hotStories
.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -766,7 +764,7 @@
"Bonus User Story:As a user, when I type in the search box, I can see a dropdown with autocomplete options for matching wikipedia entries.",
"Hint: Here's an entry on using Wikipedia's API: http://www.mediawiki.org/wiki/API:Main_page
.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -786,7 +784,7 @@
"Bonus User Story: As a user, I can reset the clock for my next pomodoro.",
"Bonus User Story: As a user, I can customize the length of each pomodoro.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -806,7 +804,7 @@
"Bonus User Story: I can clear the input field with a clear button.",
"Bonus User Story: I can keep chaining mathematical operations together until I hit the clear button and it will tell me the correct output.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -827,7 +825,7 @@
"Bonus User Story: As a user, I can choose whether I want to play as X or O.",
"Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp
.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 3,
"tests": []
@@ -909,7 +907,7 @@
"Bonus User Story: As an unauthenticated or authenticated user, I can see the in chart form. (This could be implemented using Chart.js or Google Charts.)",
"Bonus User Story: As an authenticated user, if I don't like the options on a poll I can create a new option.",
"Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 4,
"tests": []
@@ -930,7 +928,7 @@
"User Story: As an authenticated user, I can remove myself from a bar if I no longer want to go there.",
"Bonus User Story: As an unauthenticated user, when I login I should not have to search again.",
"Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 4,
"tests": []
@@ -951,7 +949,7 @@
"User Story: As a user, I can remove stocks.",
"Bonus User Story: As a user, I can see changes in real-time when any other user adds or removes a stock.",
"Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 4,
"tests": []
@@ -972,7 +970,7 @@
"User Story: As an authenticated user, I can update my settings to store my full name, city, and state.",
"Bonus User Story: As an authenticated user, I should be able to propose a trade and wait for the other user to accept the trade.",
"Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 4,
"tests": []
@@ -998,7 +996,7 @@
"Bonus User Story: As an authenticated user, if I don't like the options on a poll I can create a new option.",
"Hint: Masonry.js is a library that allows for Pintrest-style image grids.",
"Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your Github repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.",
- "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text."
+ "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text"
],
"challengeType": 4,
"tests": []
diff --git a/seed_data/field-guides.json b/seed_data/field-guides.json
index 0d72f30919..5b40a05a0c 100644
--- a/seed_data/field-guides.json
+++ b/seed_data/field-guides.json
@@ -330,44 +330,26 @@
" ",
"
It's notoriously difficult to estimate how long building software projects will take, so feel free to ask camp counselors for help.
", - "You'll continue to meet with your stakeholder at least twice a month in your project's Gitter channel.
", - "You should also ask questions in your project's Gitter channel as they come up throughout the week, and your stakeholder can answer them asynchronously.
", + "You'll continue to meet with your stakeholder at least twice a month in your project's Slack channel.
", + "You should also ask questions in your project's Slack channel as they come up throughout the week, and your stakeholder can answer them asynchronously.
", "Getting \"blocked\" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter.
", "Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!
", "You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time.
", "Here are our recommended ways of collaborating:", "
We've created a custom virtual machine image with Ubuntu Linux, Git, Team Viewer, the MEAN Stack and all its dependencies. You can run this virtual image on any computer with at least 2 gigabytes of RAM and 16 gigabytes of hard drive space.
", - "The benefits of using this virtual machine are as follows:
", - "Install a bit torrent client, then download our virtual machine image.
", - "Please note that even though Bit Torrent is often used to download content illegally, all the content on our image is open source and perfectly legal to redistribute.
", - "Once you've downloaded the file, download VirtualBox and follow this tutorial to open the image in VirtualBox. You'll want to assign the virtual machine at least two gigabytes of ram.
", - "Now you have your own Linux development environment. You can shut it down when you're not using it and it will save its state. Please continue to seed the file in bit torrent so that other campers can download it as well. Enjoy!
", "Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away.
", "If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them.
", "Once you complete a nonprofit project, your obligation to its stakeholder is finished. You goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical \"super user\").
", "While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers.
", - "Quincy Larson and/or Michael Johnson will be in the Gitter Nonprofit Project Channel every Monday and Thursday from 9 - 10 p.m. EST.
", - "Our goal is to make the discussion as public as possible so all our campers can benefit from each camper’s questions.
", - "If necessary, we can also hop on Screen Hero with you to help you with issues more specific to your project.
", "Your nonprofit stakeholder, your pair, and the volunteer camp counselor team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one.
", "To confirm that you understand the seriousness of this commitment, we require that all campers sign this pledge before starting on their nonprofit projects.
", @@ -485,6 +467,41 @@ "" ] }, + { + "_id": "bd7158d9c451eddfaeb5bded", + "name": "Bonfire Style Guide", + "description": [ + "open seed_data/bonfires.json
to become familiar with the format of our bonfires.node seed_data/seed.js
. Run gulp
. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.The name of your challenge. It's OK for this to be humorous but it must be brief and relevant to the task.
", + "Attempt to rate difficulty compared against existing bonfire challenges. A good proxy for the difficulty of a bonfire is how long it takes you to solve it. For every 15 minutes it takes, increase the difficulty. For example, a one-hour bonfire should probably be a 4.
", + "Separate paragraphs with a line break. Only the first paragraph is visible prior to a user before they click the the 'More information' button.
", + "All necessary information must be included in the first paragraph. Write this first paragraph as succinctly as possible. Subsequent paragraphs should offer hints or details if needed.
", + "If your subject matter warrants deeper understanding, you may link to Wikipedia.
", + "This is where you set up what will be in the editor when the camper starts the bonfire.
", + "These tests are what bring your challenge to life. Without them, we cannot confirm the accuracy of a user's submitted answer. Choose your tests wisely.
", + "Bonfire tests are written using the Chai.js assertion library. Please use the should and expect syntax for end user readability. As an example of what not do to, many of the original Bonfire challenges are written with assert syntax and many of the test cases are difficult to read.
", + "If your bonfire question has a lot of edge cases, you will need to write many tests for full coverage. If you find yourself writing more tests than you desire, you may consider simplifying the requirements of your bonfire challenge. For difficulty level 1 through 3, you will generally only need 2 to 4 tests.
", + "Take a look at seed_data/bonfireMDNlinks.js
. If any of these concepts are relevant to your bonfire, be sure to include them. If you know of an MDN article that isn't linked here, you can add it to the bonfireMDNlinks.js file before adding it to your bonfire.