Merge branch 'staging' of https://github.com/FreeCodeCamp/freecodecamp into greasan-translateDE

This commit is contained in:
greasan
2015-06-21 12:25:45 +02:00
20 changed files with 467 additions and 516 deletions

View File

@@ -70,6 +70,7 @@
"node-slack": "0.0.7",
"node-uuid": "^1.4.3",
"nodemailer": "~1.3.0",
"object.assign": "^3.0.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth2": "^0.1.6",
"passport-linkedin-oauth2": "^1.2.1",

View File

@@ -154,6 +154,10 @@ $(document).ready(function () {
}, 1000);
});
d3.selectAll("#chart").on("click", function () {
change();
});
function change() {
if ($("body").data("state") === "stacked") {
transitionGrouped();

View File

@@ -1,3 +1,7 @@
$(document).ready(function() {
$('#reset-button').on('click', resetEditor);
});
var widgets = [];
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
lineNumbers: true,
@@ -41,73 +45,90 @@ editor.setOption("extraKeys", {
/*
Local Storage Update System By Andrew Cay(Resto)
localBonfire: singleton object that contains properties and methods related to
dealing with the localStorage system.
The keys work off of the variable challenge_name to make unique identifiers per bonfire
Local Storage Update System By Andrew Cay(Resto)
codeStorage: singleton object that contains properties and methods related to
dealing with the localStorage system.
The keys work off of the variable challenge_name to make unique identifiers per bonfire
Two extra functionalities:
Added anonymous version checking system incase of future updates to the system
Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage
Two extra functionalities:
Added anonymous version checking system incase of future updates to the system
Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage
*/
var localBonfire = {
version: 0.01,
keyVersion:"saveVersion",
keyStamp: challenge_Name + 'Stamp',
keyValue: challenge_Name + 'Val',
stampExpireTime: (1000 *60) *60 *24,
updateWait: 1500,// 1.5 seconds
updateTimeoutId: null
var codeStorage = {
version: 0.01,
keyVersion:"saveVersion",
keyValue: null,//where the value of the editor is saved
updateWait: 2000,// 2 seconds
updateTimeoutId: null,
eventArray: []//for firing saves
};
localBonfire.getEditorValue = function(){
return localStorage.getItem(localBonfire.keyValue);
// Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere")
codeStorage.hasSaved = function(){
return ( updateTimeoutId === null );
};
localBonfire.getStampTime = function(){
//localstorage always saves as strings.
return Number.parseInt( localStorage.getItem(localBonfire.keyStamp) );
codeStorage.onSave = function(func){
codeStorage.eventArray.push(func);
};
localBonfire.isAlive = function(){// returns true if IDE was edited within expire time
return ( Date.now() - localBonfire.getStampTime() < localBonfire.stampExpireTime );
codeStorage.setSaveKey = function(key){
codeStorage.keyValue = key + 'Val';
};
localBonfire.updateStorage = function(){
if(typeof(Storage) !== undefined) {
var stamp = Date.now(),
value = editor.getValue();
localStorage.setItem(localBonfire.keyValue, value);
localStorage.setItem(localBonfire.keyStamp, stamp);
} else {
if( debugging ){
console.log('no web storage');
}
}
localBonfire.updateTimeoutId = null;
codeStorage.getEditorValue = function(){
return ('' + localStorage.getItem(codeStorage.keyValue));
};
// ANONYMOUS 1 TIME UPDATE VERSION
codeStorage.isAlive = function() {
var val = this.getEditorValue()
return val !== 'null' &&
val !== 'undefined' &&
(val && val.length > 0);
}
codeStorage.updateStorage = function(){
if(typeof(Storage) !== undefined) {
var value = editor.getValue();
localStorage.setItem(codeStorage.keyValue, value);
} else {
var debugging = false;
if( debugging ){
console.log('no web storage');
}
}
codeStorage.updateTimeoutId = null;
codeStorage.eventArray.forEach(function(func){
func();
});
};
//Update Version
(function(){
var savedVersion = localStorage.getItem('saveVersion');
if( savedVersion === null ){
localStorage.setItem(localBonfire.keyVersion, localBonfire.version);//just write current version
}else{
//do checking if not current version
if( savedVersion !== localBonfire.version ){
//update version
}
}
var savedVersion = localStorage.getItem('saveVersion');
if( savedVersion === null ){
localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version
}else{
if( savedVersion !== codeStorage.version ){
//Update version
}
}
})();
editor.on('keyup', function(codMir, event){
window.clearTimeout(localBonfire.updateTimeoutId);
localBonfire.updateTimeoutId = window.setTimeout(localBonfire.updateStorage, localBonfire.updateWait);
///Set everything up one page
/// Update local save when editor has changed
codeStorage.setSaveKey(challenge_Name);
editor.on('keyup', function(){
window.clearTimeout(codeStorage.updateTimeoutId);
codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait);
});
var attempts = 0;
if (attempts) {
attempts = 0;
}
var resetEditor = function() {
var resetEditor = function resetEditor() {
editor.setValue(allSeeds);
localBonfire.updateStorage();
codeStorage.updateStorage();
};
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
@@ -141,11 +162,11 @@ var tests = tests || [];
var allSeeds = '';
(function() {
challengeSeed.forEach(function(elem) {
allSeeds += elem + '\n';
allSeeds += elem + '\n';
});
})();
editorValue = (localBonfire.isAlive())? localBonfire.getEditorValue() : allSeeds;
editorValue = (codeStorage.isAlive())? codeStorage.getEditorValue() : allSeeds;
myCodeMirror.setValue(editorValue);

View File

@@ -331,7 +331,6 @@ $(document).ready(function() {
$('#story-submit').on('click', storySubmitButtonHandler);
$('#reset-button').on('click', resetEditor);
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
$('#comment-button').unbind('click');

View File

@@ -12,6 +12,7 @@ var links =
"Currying": "https://leanpub.com/javascript-allonge/read#pabc",
"Smallest Common Multiple": "https://www.mathsisfun.com/least-common-multiple.html",
"Permutations": "https://www.mathsisfun.com/combinatorics/combinations-permutations.html",
"HTML Entities": "http://dev.w3.org/html5/html-author/charref",
// ========= GLOBAL OBJECTS
"Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array",

View File

@@ -1097,7 +1097,7 @@
"dashedName": "bonfire-convert-html-entities",
"difficulty": "2.07",
"description": [
"Convert the characters \"&\", \"<\", \">\", '\"', and \"'\", in a string to their corresponding HTML entities.",
"Convert the characters \"&\", \"<\", \">\", '\"' (double quote), and \"'\" (apostrophe), in a string to their corresponding HTML entities.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
@@ -1114,10 +1114,12 @@
"assert.strictEqual(convert('Sixty > twelve'), 'Sixty &gt; twelve', 'should escape characters');",
"assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in &quot;quotation marks&quot;', 'should escape characters');",
"assert.strictEqual(convert(\"Shindler's List\"), 'Shindler&apos;s List', 'should escape characters');",
"assert.strictEqual(convert('<>'), '&lt;&gt;', 'should escape characters');",
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
],
"MDNlinks": [
"RegExp"
"RegExp",
"HTML Entities"
],
"challengeType": 5,
"nameCn": "",

View File

@@ -1098,7 +1098,8 @@
"In addition to pixels, you can also specify a <code>border-radius</code> using a percentage."
],
"tests": [
"assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent&#44; making it perfectly circular.')"
"assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent&#44; making it perfectly circular.')",
"assert(editor.match(/50%/g), 'Be sure to use a percentage instead of a pixel value.')"
],
"challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>",
@@ -1756,7 +1757,7 @@
],
"tests": [
"assert($('input[placeholder]').length > 0, 'Add a <code>placeholder</code> attribute text <code>input</code> element.')",
"assert($('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')"
"assert($('input') && $('input').attr('placeholder') && $('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')"
],
"challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>",
@@ -1835,7 +1836,7 @@
"For example: <code>&#60;form action=\"/url-where-you-want-to-submit-form-data\"&#62;&#60;/form&#62;</code>."
],
"tests": [
"assert($('form').length > 0, 'Wrap your text input element within a <code>form</code> element.')",
"assert($('form') && $('form').children('input') && $('form').children('input').length > 0, 'Wrap your text input element within a <code>form</code> element.')",
"assert($('form').attr('action'), 'Your <code>form</code> element should have an <code>action</code> attribute.')",
"assert(editor.match(/<\\/form>/g) && editor.match(/<form/g) && editor.match(/<\\/form>/g).length === editor.match(/<form/g).length, 'Make sure your <code>form</code> element has a closing tag.')",
"assert(editor.match(/\\/submit-cat-photo/ig), 'Make sure your <code>form</code> action is set to <code>/submit-cat-photo</code>.')"

View File

@@ -1264,7 +1264,7 @@
"tests": [
"assert($('button[type=\\'submit\\']').hasClass('btn btn-primary'), 'Give the submit button in your form the classes \"btn btn-primary\".')",
"assert($('button[type=\\'submit\\']:has(i.fa.fa-paper-plane)').length > 0, 'Add a <code>&#60;i class=\"fa fa-paper-plane\"&#62;&#60;/i&#62;</code> within your submit <code>button</code> element.')",
"assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text <code>input<code> in your form the class \"form-control\".')",
"assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text <code>input</code> in your form the class \"form-control\".')",
"assert(editor.match(/<\\/i>/g) && editor.match(/<\\/i/g).length > 3, 'Make sure each of your <code>i</code> elements has a closing tag.')"
],
"challengeSeed": [

View File

@@ -56,18 +56,20 @@
"name": "Waypoint: Join Our Chat Room",
"dashedName": "waypoint-join-our-chat-room",
"difficulty": 0.002,
"challengeSeed": ["124555254"],
"challengeSeed": ["131321596"],
"description": [
"Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.",
"Make sure your Free Code Camp account includes your email address. Please note that the email address you use will be invisible to the public, but Slack will make it visible to other campers in our slack chat rooms. You can do this here: <a href='/account' target='_blank'>http://freecodecamp.com/account</a>.",
"Click this link, which will email you can invite to Free Code Camp's Slack chat rooms: <a href='/api/slack' target='_blank'>http://freecodecamp.com/api/slack</a>.",
"Now check your email and click the link in the email from Slack.",
"Complete the sign up process, then update your biographical information and upload an image. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward.",
"Now enter the General chat room and introduce yourself to our chat room by typing: \"Hello world!\".",
"Create an account with GitHub here: <a href='https://github.com/join' target='_blank'>https://github.com/join</a>.",
"Click the pixel art in the upper right hand corner of GitHub, then choose settings. Upload a picture of yourself. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward. You can add your city and your personal website if you have one.",
"Now follow this link to enter our Welcome chat room: <a href='https://gitter.im/FreeCodeCamp/welcome' target='_blank'>https://gitter.im/FreeCodeCamp/welcome</a>.",
"Once you're in our Welcome chat room, introduce yourself by saying : \"Hello world!\".",
"Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.",
"This is the best room for new campers, but feel free to join other chat rooms as well. Our main chat room: <a href='https://gitter.im/FreeCodeCamp/FreeCodeCamp' target='_blank'>https://gitter.im/FreeCodeCamp/FreeCodeCamp</a>.",
"Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.",
"You can also download a desktop or mobile chat application here: <a href='https://gitter.im/apps' target='_blank'>https://gitter.im/apps</a>",
"You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner.",
"In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: <a href='/field-guide/what-is-the-free-code-camp-code-of-conduct?' target='_blank'>http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?</a>"
"In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: <a href='/field-guide/what-is-the-free-code-camp-code-of-conduct?' target='_blank'>http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?</a>",
"Now you're ready to move on. Click the \"I've completed this challenge\" button to move on to your next challenge."
],
"challengeType": 2,
"tests": [],
@@ -246,7 +248,7 @@
"Click \"News\" in the upper right hand corner.",
"You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.",
"You can upvote links. This will push the link up the rankings of hot links.",
"You an also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
"You can also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
"You can also submit links. You can modify the link's headline and also leave an initial comment about the link.",
"You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.",
"When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link.",

View File

@@ -31,8 +31,17 @@
*/
var R = require('ramda'),
Rx = require('rx'),
assign = require('object.assign'),
debug = require('debug')('freecc:challenges'),
utils = require('../utils'),
userMigration = require('../utils/middleware').userMigration;
// this would be so much cleaner with destructering...
saveUser = require('../utils/rx').saveUser,
observableQueryFromModel = require('../utils/rx').observableQueryFromModel,
userMigration = require('../utils/middleware').userMigration,
ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo;
var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.getChallengeMapWithIds();
@@ -40,6 +49,28 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
var getMDNLinks = utils.getMDNLinks;
var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i;
function dasherize(name) {
return ('' + name)
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\.]/gi, '');
}
function unDasherize(name) {
return ('' + name).replace(/\-/g, ' ');
}
function updateUserProgress(user, challengeId, completedChallenge) {
var index = user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
user.progressTimestamps.push(Date.now());
user.uncompletedChallenges.splice(index, 1);
}
user.completedChallenges.push(completedChallenge);
return user;
}
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
@@ -51,23 +82,30 @@ module.exports = function(app) {
// the follow routes are covered by userMigration
router.use(userMigration);
router.get('/challenges/next-challenge', returnNextChallenge);
router.get('/challenges/:challengeName', returnIndividualChallenge);
router.get('/challenges/', returnCurrentChallenge);
router.get('/map', challengeMap);
router.get(
'/challenges/next-challenge',
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'),
returnNextChallenge
);
router.get('/challenges/:challengeName', returnIndividualChallenge);
router.get(
'/challenges/',
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'),
returnCurrentChallenge
);
app.use(router);
function returnNextChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem.id;
});
req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) {
.filter(function(elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
@@ -100,18 +138,17 @@ module.exports = function(app) {
nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges);
}
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('../challenges/' + nextChallengeName);
});
saveUser(req.user)
.subscribe(
function() {},
next,
function() {
res.redirect('/challenges/' + nextChallengeName);
}
);
}
function returnCurrentChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem.id;
});
@@ -122,6 +159,7 @@ module.exports = function(app) {
return elem;
}
});
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
@@ -133,42 +171,60 @@ module.exports = function(app) {
var nameString = req.user.currentChallenge.dashedName;
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('../challenges/' + nameString);
});
saveUser(req.user)
.subscribe(
function() {},
next,
function() {
res.redirect('/challenges/' + nameString);
}
);
}
function returnIndividualChallenge(req, res, next) {
var dashedName = req.params.challengeName;
var origChallengeName = req.params.challengeName;
var unDashedName = unDasherize(origChallengeName);
var challengeName = challangesRegex.test(unDashedName) ?
// remove first word if matches
unDashedName.split(' ').slice(1).join(' ') :
unDashedName;
debug('looking for ', challengeName);
Challenge.findOne(
{ where: { dashedName: dashedName }},
{ where: { name: { like: challengeName, options: 'i' } } },
function(err, challenge) {
if (err) { return next(err); }
// Handle not found
if (!challenge) {
debug('did not find challenge for ' + origChallengeName);
req.flash('errors', {
msg: '404: We couldn\'t find a challenge with that name. ' +
'Please double check the name.'
msg:
'404: We couldn\'t find a challenge with the name `' +
origChallengeName +
'` Please double check the name.'
});
return res.redirect('/challenges');
}
// Redirect to full name if the user only entered a partial
if (dasherize(challenge.name) !== origChallengeName) {
debug('redirecting to fullname');
return res.redirect('/challenges/' + dasherize(challenge.name));
}
if (req.user) {
req.user.currentChallenge = {
challengeId: challenge.id,
challengeName: challenge.name,
dashedName: challenge.dashedName,
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds).
map(function (key) {
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds)
.map(function (key) {
return challengeMapWithIds[key]
.filter(function (elem) {
return String(elem) === String(challenge.id);
}).map(function () {
return elem === ('' + challenge.id);
})
.map(function () {
return key;
});
})
@@ -176,261 +232,169 @@ module.exports = function(app) {
};
}
var challengeType = {
0: function() {
res.render('coursewares/showHTML', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
environment: utils.whichEnvironment(),
challengeType: challenge.challengeType
});
},
1: function() {
res.render('coursewares/showJS', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
challengeType: challenge.challengeType
});
},
2: function() {
res.render('coursewares/showVideo', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
tests: challenge.tests,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
challengeType: challenge.challengeType
});
},
3: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
challengeType: challenge.challengeType
});
},
4: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
challengeType: challenge.challengeType
});
},
5: function() {
res.render('coursewares/showBonfire', {
completedWith: null,
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
difficulty: Math.floor(+challenge.difficulty),
brief: challenge.description.shift(),
details: challenge.description,
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
bonfires: challenge,
challengeId: challenge.id,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNLinks(challenge.MDNlinks),
challengeType: challenge.challengeType
});
}
var commonLocals = {
title: challenge.name,
dashedName: origChallengeName,
name: challenge.name,
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge.id,
challengeType: challenge.challengeType,
// video challenges
video: challenge.challengeSeed[0],
// bonfires specific
difficulty: Math.floor(+challenge.difficulty),
brief: challenge.description.shift(),
bonfires: challenge,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNLinks(challenge.MDNlinks),
// htmls specific
environment: utils.whichEnvironment()
};
if (req.user) {
req.user.save(function (err) {
if (err) {
return next(err);
var challengeView = {
0: 'coursewares/showHTML',
1: 'coursewares/showJS',
2: 'coursewares/showVideo',
3: 'coursewares/showZiplineOrBasejump',
4: 'coursewares/showZiplineOrBasejump',
5: 'coursewares/showBonfire'
};
saveUser(req.user)
.subscribe(
function() {},
next,
function() {
var view = challengeView[challenge.challengeType];
res.render(view, commonLocals);
}
return challengeType[challenge.challengeType]();
});
} else {
return challengeType[challenge.challengeType]();
}
);
});
}
function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || '';
var isCompletedDate = Math.round(+new Date());
debug('compltedBonfire');
var completedWith = req.body.challengeInfo.completedWith || false;
var challengeId = req.body.challengeInfo.challengeId;
var isSolution = req.body.challengeInfo.solution;
var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWith) {
if (err) { return next(err); }
var challengeData = {
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedDate: Math.round(+new Date()),
solution: req.body.challengeInfo.solution,
challengeType: 5
};
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
pairedWith = pairedWith.pop();
observableQueryFromModel(
User,
'findOne',
{ where: { username: ('' + completedWith).toLowerCase() } }
)
.doOnNext(function(pairedWith) {
debug('paired with ', pairedWith);
if (pairedWith) {
index = pairedWith.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedChallenges.splice(index, 1);
}
pairedWith.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: req.user.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: pairedWith.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
updateUserProgress(
pairedWith,
challengeId,
assign({ completedWith: req.user.id }, challengeData)
);
}
// User said they paired, but pair wasn't found
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.save(function (err, user) {
if (err) { return next(err); }
if (pairedWith) {
pairedWith.save(function (err, paired) {
if (err) {
return next(err);
}
if (user && paired) {
return res.send(true);
}
});
} else if (user) {
res.send(true);
})
.withLatestFrom(
Rx.Observable.just(req.user),
function(pairedWith, user) {
debug('yo');
return {
user: user,
pairedWith: pairedWith
};
}
)
// side effects should always be done in do's and taps
.doOnNext(function(dats) {
updateUserProgress(
dats.user,
challengeId,
dats.pairedWith ?
// paired programmer found and adding to data
assign({ completedWith: dats.pairedWith.id }, challengeData) :
// user said they paired, but pair wasn't found
challengeData
);
})
// not iterate users
.flatMap(function(dats) {
debug('flatmap');
return Rx.Observable.from([dats.user, dats.pairedWith]);
})
// save user
.flatMap(function(user) {
// save user will do nothing if user is falsey
return saveUser(user);
})
.subscribe(
function(user) {
debug('onNext');
if (user) {
debug('user %s saved', user.username);
}
});
});
} else {
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err) {
if (err) { return next(err); }
res.send(true);
});
}
},
next,
function() {
debug('completed');
return res.status(200).send(true);
}
);
}
function completedChallenge(req, res, next) {
var isCompletedDate = Math.round(+new Date());
var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
req.user.completedChallenges.push({
id: challengeId,
completedDate: isCompletedDate,
name: req.body.challengeInfo.challengeName,
solution: null,
githubLink: null,
verified: true
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err, user) {
if (err) {
return next(err);
updateUserProgress(
req.user,
challengeId,
{
id: challengeId,
completedDate: completedDate,
name: req.body.challengeInfo.challengeName,
solution: null,
githubLink: null,
verified: true
}
if (user) {
res.sendStatus(200);
}
});
);
saveUser(req.user)
.subscribe(
function() { },
next,
function() {
res.sendStatus(200);
}
);
}
function completedZiplineOrBasejump(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date());
var completedWith = req.body.challengeInfo.completedWith || false;
var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL;
var githubLink = req.body.challengeInfo.challengeType === '4'
? req.body.challengeInfo.githubURL : true;
var githubLink = req.body.challengeInfo.challengeType === '4' ?
req.body.challengeInfo.githubURL :
true;
var challengeType = req.body.challengeInfo.challengeType === '4' ?
4 : 3;
4 :
3;
if (!solutionLink || !githubLink) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
@@ -439,93 +403,64 @@ module.exports = function(app) {
return res.sendStatus(403);
}
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWithFromMongo) {
if (err) { return next(err); }
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
var challengeData = {
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedDate: completedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
};
observableQueryFromModel(
User,
'findOne',
{ where: { username: completedWith.toLowerCase() } }
)
.doOnNext(function(pairedWith) {
if (pairedWith) {
updateUserProgress(
pairedWith,
challengeId,
assign({ completedWith: req.user.id }, challengeData)
);
}
var pairedWith = pairedWithFromMongo.pop();
req.user.completedChallenges.push({
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: pairedWith.id,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
req.user.save(function (err, user) {
if (err) { return next(err); }
if (req.user.id.toString() === pairedWith.id.toString()) {
return res.sendStatus(200);
})
.withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) {
return {
user: user,
pairedWith: pairedWith
};
})
.doOnNext(function(dats) {
updateUserProgress(
dats.user,
challengeId,
dats.pairedWith ?
assign({ completedWith: dats.pairedWith.id }, challengeData) :
challengeData
);
})
.flatMap(function(dats) {
return Rx.Observable.from([dats.user, dats.pairedWith]);
})
// save users
.flatMap(function(user) {
// save user will do nothing if user is falsey
return saveUser(user);
})
.subscribe(
function(user) {
if (user) {
debug('user %s saved', user.username);
}
index = pairedWith.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedChallenges.splice(index, 1);
}
pairedWith.completedChallenges.push({
id: challengeId,
name: req.body.challengeInfo.coursewareName,
completedWith: req.user.id,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
pairedWith.save(function (err, paired) {
if (err) {
return next(err);
}
if (user && paired) {
return res.sendStatus(200);
}
});
});
});
} else {
req.user.completedChallenges.push({
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err, user) {
if (err) {
return next(err);
},
next,
function() {
return res.status(200).send(true);
}
// NOTE(berks): under certain conditions this will not close
// the response.
if (user) {
return res.sendStatus(200);
}
});
}
);
}
function challengeMap(req, res, next) {

View File

@@ -15,7 +15,7 @@ module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var Challenge = app.models.Challenge;
var Story = app.models.Store;
var Story = app.models.Story;
var FieldGuide = app.models.FieldGuide;
var Nonprofit = app.models.Nonprofit;
@@ -193,15 +193,15 @@ module.exports = function(app) {
users: function(callback) {
User.find(
{
where: { 'profile.username': { nlike: '' } },
fields: { 'profile.username': true }
where: { username: { nlike: '' } },
fields: { username: true }
},
function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(users)
Rx.Observable.from(users, null, null, Rx.Scheduler.default)
.map(function(user) {
return user.username;
})
@@ -224,7 +224,7 @@ module.exports = function(app) {
debug('Challenge err: ', err);
callback(err);
} else {
Rx.Observable.from(challenges)
Rx.Observable.from(challenges, null, null, Rx.Scheduler.default)
.map(function(challenge) {
return challenge.name;
})
@@ -244,7 +244,7 @@ module.exports = function(app) {
debug('Story err: ', err);
callback(err);
} else {
Rx.Observable.from(stories)
Rx.Observable.from(stories, null, null, Rx.Scheduler.default)
.map(function(story) {
return story.link;
})
@@ -265,7 +265,7 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(nonprofits)
Rx.Observable.from(nonprofits, null, null, Rx.Scheduler.default)
.map(function(nonprofit) {
return nonprofit.name;
})
@@ -285,7 +285,12 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(fieldGuides)
Rx.Observable.from(
fieldGuides,
null,
null,
Rx.Scheduler.default
)
.map(function(fieldGuide) {
return fieldGuide.name;
})
@@ -301,7 +306,7 @@ module.exports = function(app) {
if (err) {
return next(err);
}
setTimeout(function() {
process.nextTick(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
@@ -312,19 +317,13 @@ module.exports = function(app) {
nonprofits: results.nonprofits,
fieldGuides: results.fieldGuides
});
}, 0);
});
}
);
}
function chat(req, res) {
if (req.user && req.user.progressTimestamps.length > 5) {
res.redirect('http://freecodecamp.slack.com');
} else {
res.render('resources/chat', {
title: 'Watch us code live on Twitch.tv'
});
}
res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp');
}
function bootcampCalculator(req, res) {
@@ -383,7 +382,7 @@ module.exports = function(app) {
}
function unsubscribe(req, res, next) {
User.findOne({ email: req.params.email }, function(err, user) {
User.findOne({ where: { email: req.params.email } }, function(err, user) {
if (user) {
if (err) {
return next(err);

View File

@@ -4,7 +4,7 @@ var _ = require('lodash'),
crypto = require('crypto'),
nodemailer = require('nodemailer'),
moment = require('moment'),
//debug = require('debug')('freecc:cntr:userController'),
// debug = require('debug')('freecc:cntr:userController'),
secrets = require('../../config/secrets');

View File

@@ -17,4 +17,4 @@ module.exports = {
clientID: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}
};
};

View File

@@ -3,8 +3,8 @@ var secrets = require('../config/secrets');
module.exports = {
db: {
connector: 'mongodb',
connectionTimeout: 15000,
url: process.env.MONGOHQ_URL
connectionTimeout: 5000,
url: secrets.db
},
mail: {
connector: 'mail',

View File

@@ -33,3 +33,13 @@ exports.userMigration = function userMigration(req, res, next) {
);
return next();
};
exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) {
return function(req, res, next) {
if (req.user) {
return next();
}
return res.redirect(url);
};
};

25
server/utils/rx.js Normal file
View File

@@ -0,0 +1,25 @@
var Rx = require('rx');
var debug = require('debug')('freecc:rxUtils');
exports.saveUser = function saveUser(user) {
return new Rx.Observable.create(function(observer) {
if (!user || typeof user.save !== 'function') {
debug('no user or save method');
observer.onNext();
return observer.onCompleted();
}
user.save(function(err, savedUser) {
if (err) {
return observer.onError(err);
}
debug('user saved');
observer.onNext(savedUser);
observer.onCompleted();
});
});
};
exports.observableQueryFromModel =
function observableQueryFromModel(Model, method, query) {
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
};

View File

@@ -47,88 +47,32 @@ block content
.background-svg.img-center
.points-on-top
= "[ " + (progressTimestamps.length) + " ]"
.row
.col-xs-12
if (website1Title && website1Link && website1Image)
.row
.col-xs-12.col-md-5
img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website1Title
a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website1Title && website1Link && !website1Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website1Title
a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website2Title && website2Link && website2Image)
.row
.col-xs-12.col-md-5
img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website2Title
a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website2Title && website2Link && !website2Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website2Title
a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website3Title && website3Link && website3Image)
.row
.col-xs-12.col-md-5
img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website3Title
a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
i.fa.icon-beaker
| Try it out
if (website3Title && website3Link && !website3Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website3Title
a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
i.fa.icon-beaker
| Try it out
.spacer
.hidden-xs.hidden-sm.col-md-12
#cal-heatmap.d3-centered
script.
$(document).ready(function () {
setTimeout(function () {
var cal = new CalHeatMap();
var calendar = !{JSON.stringify(calender)};
cal.init({
itemSelector: "#cal-heatmap",
domain: "month",
subDomain: "x_day",
domainGutter: 10,
data: calendar,
cellSize: 15,
align: 'center',
cellRadius: 3,
cellPadding: 2,
tooltip: true,
range: 6,
start: new Date().setDate(new Date().getDate() - 150),
legendColors: ["#cccccc", "#215f1e"],
legend: [1, 2, 3],
label: {
position: "top"
}
});
}, 300);
var cal = new CalHeatMap();
var calendar = !{JSON.stringify(calender)};
cal.init({
itemSelector: "#cal-heatmap",
domain: "month",
subDomain: "x_day",
domainGutter: 10,
data: calendar,
cellSize: 15,
align: 'center',
cellRadius: 3,
cellPadding: 2,
tooltip: true,
range: 6,
start: new Date().setDate(new Date().getDate() - 150),
legendColors: ["#cccccc", "#215f1e"],
legend: [1, 2, 3],
label: {
position: "top"
}
});
});
.row
.hidden-xs.col-sm-12.text-center

View File

@@ -84,7 +84,7 @@ block content
label.negative-10.btn.btn-primary.btn-block#submitButton
i.fa.fa-play
| &nbsp; Run code (ctrl + enter)
#resetButton.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code
#trigger-reset-modal.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code
if (user && user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified

View File

@@ -13,12 +13,8 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
a(href='/challenges') Learn
li
a(href='/map') Map
if (user && user.sentSlackInvite)
li
a(href='/chat', target='_blank') Chat
else
li
a(href='/challenges/waypoint-join-our-chat-room') Chat
li
a(href='//gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat
li
a(href='/stories') News
li

View File

@@ -3,6 +3,7 @@ block content
script(src="../../../js/calculator.js")
.row
.col-xs-12.col-sm-10.col-md-8.col-lg-6.col-sm-offset-1.col-md-offset-2.col-lg-offset-3
h1.text-center Coding Bootcamp Cost Calculator
h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be:
#city-buttons
.spacer
@@ -102,3 +103,13 @@ block content
a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here
| .
li.large-li Free Code Camp. We don't charge tuition or garnish wages. We're fully online so you don't have to move. We're self-paced so you don't have to quit your job. Thus, your true cost of attending Free Code Camp will be $0.
.spacer
.row
.col-xs-12.col-sm-4.col-md-3
img.img-responsive.testimonial-image(src='https://www.evernote.com/l/AHRIBndcq-5GwZVnSy1_D7lskpH4OcJcUKUB/image.png')
.col-xs-12.col-sm-8.col-md-9
h3 Built by Suzanne Atkinson
p.large-p Suzanne is an emergency medicine physician, triathlon coach and web developer from Pittsburgh. You should &thinsp;
a(href='https://twitter.com/intent/user?screen_name=SteelCityCoach' target='_blank') follow her on Twitter
| .
.spacer