Merge branch 'staging' of https://github.com/FreeCodeCamp/freecodecamp into greasan-translateDE
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -154,6 +154,10 @@ $(document).ready(function () {
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
d3.selectAll("#chart").on("click", function () {
|
||||
change();
|
||||
});
|
||||
|
||||
function change() {
|
||||
if ($("body").data("state") === "stacked") {
|
||||
transitionGrouped();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 > twelve', 'should escape characters');",
|
||||
"assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in "quotation marks"', 'should escape characters');",
|
||||
"assert.strictEqual(convert(\"Shindler's List\"), 'Shindler's List', 'should escape characters');",
|
||||
"assert.strictEqual(convert('<>'), '<>', 'should escape characters');",
|
||||
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
|
||||
],
|
||||
"MDNlinks": [
|
||||
"RegExp"
|
||||
"RegExp",
|
||||
"HTML Entities"
|
||||
],
|
||||
"challengeType": 5,
|
||||
"nameCn": "",
|
||||
|
||||
@@ -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, making it perfectly circular.')"
|
||||
"assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent, 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><form action=\"/url-where-you-want-to-submit-form-data\"></form></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>.')"
|
||||
|
||||
@@ -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><i class=\"fa fa-paper-plane\"></i></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": [
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@ module.exports = {
|
||||
clientID: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
25
server/utils/rx.js
Normal 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);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -84,7 +84,7 @@ block content
|
||||
label.negative-10.btn.btn-primary.btn-block#submitButton
|
||||
i.fa.fa-play
|
||||
| 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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  
|
||||
a(href='https://twitter.com/intent/user?screen_name=SteelCityCoach' target='_blank') follow her on Twitter
|
||||
| .
|
||||
.spacer
|
||||
|
||||
Reference in New Issue
Block a user