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-slack": "0.0.7",
"node-uuid": "^1.4.3", "node-uuid": "^1.4.3",
"nodemailer": "~1.3.0", "nodemailer": "~1.3.0",
"object.assign": "^3.0.0",
"passport-facebook": "^2.0.0", "passport-facebook": "^2.0.0",
"passport-google-oauth2": "^0.1.6", "passport-google-oauth2": "^0.1.6",
"passport-linkedin-oauth2": "^1.2.1", "passport-linkedin-oauth2": "^1.2.1",

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ var links =
"Currying": "https://leanpub.com/javascript-allonge/read#pabc", "Currying": "https://leanpub.com/javascript-allonge/read#pabc",
"Smallest Common Multiple": "https://www.mathsisfun.com/least-common-multiple.html", "Smallest Common Multiple": "https://www.mathsisfun.com/least-common-multiple.html",
"Permutations": "https://www.mathsisfun.com/combinatorics/combinations-permutations.html", "Permutations": "https://www.mathsisfun.com/combinatorics/combinations-permutations.html",
"HTML Entities": "http://dev.w3.org/html5/html-author/charref",
// ========= GLOBAL OBJECTS // ========= GLOBAL OBJECTS
"Global Array Object" : "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array", "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", "dashedName": "bonfire-convert-html-entities",
"difficulty": "2.07", "difficulty": "2.07",
"description": [ "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." "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": [ "challengeSeed": [
@@ -1114,10 +1114,12 @@
"assert.strictEqual(convert('Sixty > twelve'), 'Sixty &gt; twelve', 'should escape characters');", "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('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(\"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');" "assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
], ],
"MDNlinks": [ "MDNlinks": [
"RegExp" "RegExp",
"HTML Entities"
], ],
"challengeType": 5, "challengeType": 5,
"nameCn": "", "nameCn": "",

View File

@@ -1098,7 +1098,8 @@
"In addition to pixels, you can also specify a <code>border-radius</code> using a percentage." "In addition to pixels, you can also specify a <code>border-radius</code> using a percentage."
], ],
"tests": [ "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": [ "challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>", "<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>",
@@ -1756,7 +1757,7 @@
], ],
"tests": [ "tests": [
"assert($('input[placeholder]').length > 0, 'Add a <code>placeholder</code> attribute text <code>input</code> element.')", "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": [ "challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>", "<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>." "For example: <code>&#60;form action=\"/url-where-you-want-to-submit-form-data\"&#62;&#60;/form&#62;</code>."
], ],
"tests": [ "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($('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(/<\\/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>.')" "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": [ "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\\']').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($('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.')" "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": [ "challengeSeed": [

View File

@@ -56,18 +56,20 @@
"name": "Waypoint: Join Our Chat Room", "name": "Waypoint: Join Our Chat Room",
"dashedName": "waypoint-join-our-chat-room", "dashedName": "waypoint-join-our-chat-room",
"difficulty": 0.002, "difficulty": 0.002,
"challengeSeed": ["124555254"], "challengeSeed": ["131321596"],
"description": [ "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.", "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>.", "Create an account with GitHub here: <a href='https://github.com/join' target='_blank'>https://github.com/join</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>.", "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 check your email and click the link in the email from Slack.", "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>.",
"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.", "Once you're in our Welcome chat room, introduce yourself by saying : \"Hello world!\".",
"Now enter the General chat room and introduce yourself to our chat room by typing: \"Hello world!\".",
"Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", "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.", "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.", "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, "challengeType": 2,
"tests": [], "tests": [],
@@ -246,7 +248,7 @@
"Click \"News\" in the upper right hand corner.", "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'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 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 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.", "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.", "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'), var R = require('ramda'),
Rx = require('rx'),
assign = require('object.assign'),
debug = require('debug')('freecc:challenges'),
utils = require('../utils'), 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 challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.getChallengeMapWithIds(); var challengeMapWithIds = utils.getChallengeMapWithIds();
@@ -40,6 +49,28 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
var getMDNLinks = utils.getMDNLinks; 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) { module.exports = function(app) {
var router = app.loopback.Router(); var router = app.loopback.Router();
var Challenge = app.models.Challenge; var Challenge = app.models.Challenge;
@@ -51,23 +82,30 @@ module.exports = function(app) {
// the follow routes are covered by userMigration // the follow routes are covered by userMigration
router.use(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('/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); app.use(router);
function returnNextChallenge(req, res, next) { 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) { var completed = req.user.completedChallenges.map(function (elem) {
return elem.id; return elem.id;
}); });
req.user.uncompletedChallenges = utils.allChallengeIds() req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) { .filter(function(elem) {
if (completed.indexOf(elem) === -1) { if (completed.indexOf(elem) === -1) {
return elem; return elem;
} }
@@ -100,18 +138,17 @@ module.exports = function(app) {
nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges); nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges);
} }
req.user.save(function(err) { saveUser(req.user)
if (err) { .subscribe(
return next(err); function() {},
} next,
return res.redirect('../challenges/' + nextChallengeName); function() {
}); res.redirect('/challenges/' + nextChallengeName);
}
);
} }
function returnCurrentChallenge(req, res, next) { 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) { var completed = req.user.completedChallenges.map(function (elem) {
return elem.id; return elem.id;
}); });
@@ -122,6 +159,7 @@ module.exports = function(app) {
return elem; return elem;
} }
}); });
if (!req.user.currentChallenge) { if (!req.user.currentChallenge) {
req.user.currentChallenge = {}; req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0]; req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
@@ -133,42 +171,60 @@ module.exports = function(app) {
var nameString = req.user.currentChallenge.dashedName; var nameString = req.user.currentChallenge.dashedName;
req.user.save(function(err) { saveUser(req.user)
if (err) { .subscribe(
return next(err); function() {},
} next,
return res.redirect('../challenges/' + nameString); function() {
}); res.redirect('/challenges/' + nameString);
}
);
} }
function returnIndividualChallenge(req, res, next) { 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( Challenge.findOne(
{ where: { dashedName: dashedName }}, { where: { name: { like: challengeName, options: 'i' } } },
function(err, challenge) { function(err, challenge) {
if (err) { return next(err); } if (err) { return next(err); }
// Handle not found // Handle not found
if (!challenge) { if (!challenge) {
debug('did not find challenge for ' + origChallengeName);
req.flash('errors', { req.flash('errors', {
msg: '404: We couldn\'t find a challenge with that name. ' + msg:
'Please double check the name.' '404: We couldn\'t find a challenge with the name `' +
origChallengeName +
'` Please double check the name.'
}); });
return res.redirect('/challenges'); return res.redirect('/challenges');
} }
// Redirect to full name if the user only entered a partial // 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) { if (req.user) {
req.user.currentChallenge = { req.user.currentChallenge = {
challengeId: challenge.id, challengeId: challenge.id,
challengeName: challenge.name, challengeName: challenge.name,
dashedName: challenge.dashedName, dashedName: challenge.dashedName,
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds). challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds)
map(function (key) { .map(function (key) {
return challengeMapWithIds[key] return challengeMapWithIds[key]
.filter(function (elem) { .filter(function (elem) {
return String(elem) === String(challenge.id); return elem === ('' + challenge.id);
}).map(function () { })
.map(function () {
return key; return key;
}); });
}) })
@@ -176,261 +232,169 @@ module.exports = function(app) {
}; };
} }
var challengeType = { var commonLocals = {
0: function() { title: challenge.name,
res.render('coursewares/showHTML', { dashedName: origChallengeName,
title: challenge.name, name: challenge.name,
dashedName: dashedName, details: challenge.description.slice(1),
name: challenge.name, tests: challenge.tests,
brief: challenge.description[0], challengeSeed: challenge.challengeSeed,
details: challenge.description.slice(1), verb: utils.randomVerb(),
tests: challenge.tests, phrase: utils.randomPhrase(),
challengeSeed: challenge.challengeSeed, compliment: utils.randomCompliment(),
verb: utils.randomVerb(), challengeId: challenge.id,
phrase: utils.randomPhrase(), challengeType: challenge.challengeType,
compliment: utils.randomCompliment(), // video challenges
challengeId: challenge.id, video: challenge.challengeSeed[0],
environment: utils.whichEnvironment(), // bonfires specific
challengeType: challenge.challengeType difficulty: Math.floor(+challenge.difficulty),
}); brief: challenge.description.shift(),
}, bonfires: challenge,
MDNkeys: challenge.MDNlinks,
1: function() { MDNlinks: getMDNLinks(challenge.MDNlinks),
res.render('coursewares/showJS', { // htmls specific
title: challenge.name, environment: utils.whichEnvironment()
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
});
}
}; };
if (req.user) {
req.user.save(function (err) { var challengeView = {
if (err) { 0: 'coursewares/showHTML',
return next(err); 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) { function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || ''; debug('compltedBonfire');
var isCompletedDate = Math.round(+new Date()); var completedWith = req.body.challengeInfo.completedWith || false;
var challengeId = req.body.challengeInfo.challengeId; var challengeId = req.body.challengeInfo.challengeId;
var isSolution = req.body.challengeInfo.solution;
var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) { var challengeData = {
User.find({ id: challengeId,
where: { 'profile.username': isCompletedWith.toLowerCase() }, name: req.body.challengeInfo.challengeName,
limit: 1 completedDate: Math.round(+new Date()),
}, function (err, pairedWith) { solution: req.body.challengeInfo.solution,
if (err) { return next(err); } challengeType: 5
};
var index = req.user.uncompletedChallenges.indexOf(challengeId); observableQueryFromModel(
if (index > -1) { User,
req.user.progressTimestamps.push(Date.now() || 0); 'findOne',
req.user.uncompletedChallenges.splice(index, 1); { where: { username: ('' + completedWith).toLowerCase() } }
} )
pairedWith = pairedWith.pop(); .doOnNext(function(pairedWith) {
debug('paired with ', pairedWith);
if (pairedWith) { if (pairedWith) {
updateUserProgress(
index = pairedWith.uncompletedChallenges.indexOf(challengeId); pairedWith,
if (index > -1) { challengeId,
pairedWith.progressTimestamps.push(Date.now() || 0); assign({ completedWith: req.user.id }, challengeData)
pairedWith.uncompletedChallenges.splice(index, 1); );
}
pairedWith.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: req.user.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: pairedWith.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
} }
// User said they paired, but pair wasn't found })
req.user.completedChallenges.push({ .withLatestFrom(
id: challengeId, Rx.Observable.just(req.user),
name: challengeName, function(pairedWith, user) {
completedWith: null, debug('yo');
completedDate: isCompletedDate, return {
solution: isSolution, user: user,
challengeType: 5 pairedWith: pairedWith
}); };
}
req.user.save(function (err, user) { )
if (err) { return next(err); } // side effects should always be done in do's and taps
.doOnNext(function(dats) {
if (pairedWith) { updateUserProgress(
pairedWith.save(function (err, paired) { dats.user,
if (err) { challengeId,
return next(err); dats.pairedWith ?
} // paired programmer found and adding to data
if (user && paired) { assign({ completedWith: dats.pairedWith.id }, challengeData) :
return res.send(true); // user said they paired, but pair wasn't found
} challengeData
}); );
} else if (user) { })
res.send(true); // 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);
} }
}); },
}); next,
} else { function() {
req.user.completedChallenges.push({ debug('completed');
id: challengeId, return res.status(200).send(true);
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);
});
}
} }
function completedChallenge(req, res, next) { function completedChallenge(req, res, next) {
var isCompletedDate = Math.round(+new Date()); var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId; var challengeId = req.body.challengeInfo.challengeId;
req.user.completedChallenges.push({ updateUserProgress(
id: challengeId, req.user,
completedDate: isCompletedDate, challengeId,
name: req.body.challengeInfo.challengeName, {
solution: null, id: challengeId,
githubLink: null, completedDate: completedDate,
verified: true name: req.body.challengeInfo.challengeName,
}); solution: null,
var index = req.user.uncompletedChallenges.indexOf(challengeId); githubLink: null,
verified: true
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err, user) {
if (err) {
return next(err);
} }
if (user) { );
res.sendStatus(200);
} saveUser(req.user)
}); .subscribe(
function() { },
next,
function() {
res.sendStatus(200);
}
);
} }
function completedZiplineOrBasejump(req, res, next) { function completedZiplineOrBasejump(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || false; var completedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date()); var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId; var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL; 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' ? var challengeType = req.body.challengeInfo.challengeType === '4' ?
4 : 3; 4 :
3;
if (!solutionLink || !githubLink) { if (!solutionLink || !githubLink) {
req.flash('errors', { req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
@@ -439,93 +403,64 @@ module.exports = function(app) {
return res.sendStatus(403); return res.sendStatus(403);
} }
if (isCompletedWith) { var challengeData = {
User.find({ id: challengeId,
where: { 'profile.username': isCompletedWith.toLowerCase() }, name: req.body.challengeInfo.challengeName,
limit: 1 completedDate: completedDate,
}, function (err, pairedWithFromMongo) { solution: solutionLink,
if (err) { return next(err); } githubLink: githubLink,
var index = req.user.uncompletedChallenges.indexOf(challengeId); challengeType: challengeType,
if (index > -1) { verified: false
req.user.progressTimestamps.push(Date.now() || 0); };
req.user.uncompletedChallenges.splice(index, 1);
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(); })
.withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) {
req.user.completedChallenges.push({ return {
id: challengeId, user: user,
name: req.body.challengeInfo.challengeName, pairedWith: pairedWith
completedWith: pairedWith.id, };
completedDate: isCompletedDate, })
solution: solutionLink, .doOnNext(function(dats) {
githubLink: githubLink, updateUserProgress(
challengeType: challengeType, dats.user,
verified: false challengeId,
}); dats.pairedWith ?
assign({ completedWith: dats.pairedWith.id }, challengeData) :
req.user.save(function (err, user) { challengeData
if (err) { return next(err); } );
})
if (req.user.id.toString() === pairedWith.id.toString()) { .flatMap(function(dats) {
return res.sendStatus(200); 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) { next,
pairedWith.progressTimestamps.push(Date.now() || 0); function() {
pairedWith.uncompletedChallenges.splice(index, 1); return res.status(200).send(true);
}
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);
} }
// NOTE(berks): under certain conditions this will not close );
// the response.
if (user) {
return res.sendStatus(200);
}
});
}
} }
function challengeMap(req, res, next) { function challengeMap(req, res, next) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,3 +33,13 @@ exports.userMigration = function userMigration(req, res, next) {
); );
return 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 .background-svg.img-center
.points-on-top .points-on-top
= "[ " + (progressTimestamps.length) + " ]" = "[ " + (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 .spacer
.hidden-xs.hidden-sm.col-md-12 .hidden-xs.hidden-sm.col-md-12
#cal-heatmap.d3-centered #cal-heatmap.d3-centered
script. script.
$(document).ready(function () { $(document).ready(function () {
setTimeout(function () { var cal = new CalHeatMap();
var cal = new CalHeatMap(); var calendar = !{JSON.stringify(calender)};
var calendar = !{JSON.stringify(calender)}; cal.init({
cal.init({ itemSelector: "#cal-heatmap",
itemSelector: "#cal-heatmap", domain: "month",
domain: "month", subDomain: "x_day",
subDomain: "x_day", domainGutter: 10,
domainGutter: 10, data: calendar,
data: calendar, cellSize: 15,
cellSize: 15, align: 'center',
align: 'center', cellRadius: 3,
cellRadius: 3, cellPadding: 2,
cellPadding: 2, tooltip: true,
tooltip: true, range: 6,
range: 6, start: new Date().setDate(new Date().getDate() - 150),
start: new Date().setDate(new Date().getDate() - 150), legendColors: ["#cccccc", "#215f1e"],
legendColors: ["#cccccc", "#215f1e"], legend: [1, 2, 3],
legend: [1, 2, 3], label: {
label: { position: "top"
position: "top" }
} });
});
}, 300);
}); });
.row .row
.hidden-xs.col-sm-12.text-center .hidden-xs.col-sm-12.text-center

View File

@@ -84,7 +84,7 @@ block content
label.negative-10.btn.btn-primary.btn-block#submitButton label.negative-10.btn.btn-primary.btn-block#submitButton
i.fa.fa-play i.fa.fa-play
| &nbsp; Run code (ctrl + enter) | &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) if (user && user.sentSlackInvite)
.button-spacer .button-spacer
.btn-group.input-group.btn-group-justified .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 a(href='/challenges') Learn
li li
a(href='/map') Map a(href='/map') Map
if (user && user.sentSlackInvite) li
li a(href='//gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat
a(href='/chat', target='_blank') Chat
else
li
a(href='/challenges/waypoint-join-our-chat-room') Chat
li li
a(href='/stories') News a(href='/stories') News
li li

View File

@@ -3,6 +3,7 @@ block content
script(src="../../../js/calculator.js") script(src="../../../js/calculator.js")
.row .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 .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: h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be:
#city-buttons #city-buttons
.spacer .spacer
@@ -102,3 +103,13 @@ block content
a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here 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. 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