Merge pull request #3 from FreeCodeCamp/staging

Pull up to date
This commit is contained in:
Rex Schrader
2015-06-22 21:43:52 -07:00
39 changed files with 813 additions and 1671 deletions

View File

@ -17,7 +17,7 @@ Our campers (students) start by working through our free, self-paced, browser-ba
80% of our campers are over 25, and nearly a fifth of our campers are women.
This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Slack](http://freecodecamp.slack.com), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp).
This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp).
[Join our community](http://www.freecodecamp.com/signin)!
@ -27,7 +27,7 @@ Contributing
We welcome pull requests from Free Code Camp campers (our students) and seasoned JavaScript developers alike! Follow these steps to contribute:
1. Check our [public Waffle Board](https://waffle.io/freecodecamp/freecodecamp).
2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Slack](http://freecodecamp.slack.com).
2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp)
3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy.
4. Create a branch specific to the issue or feature you are working on. Push your work to that branch. ([Need help with branching?](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches))
5. Name the branch something like `user-xxx` where user is your username and xxx is the issue number you are addressing.

View File

@ -134,7 +134,8 @@
"type": "string"
},
"uncompletedBonfires": {
"type": "array"
"type": "array",
"default": []
},
"completedBonfires": {
"type": [

View File

@ -13,10 +13,6 @@ module.exports = {
key: process.env.BLOGGER_KEY
},
slack: {
key: process.env.SLACK_KEY
},
mandrill: {
user: process.env.MANDRILL_USER,
password: process.env.MANDRILL_PASSWORD

View File

@ -47,7 +47,6 @@
"express-session": "~1.9.2",
"express-validator": "~2.8.0",
"font-awesome": "~4.3.0",
"forcedomain": "~0.4.0",
"forever": "~0.14.1",
"frameguard": "^0.2.2",
"github-api": "~0.7.0",
@ -71,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",

12
pm2Start.js Normal file
View File

@ -0,0 +1,12 @@
var pm2 = require('pm2');
pm2.connect(function() {
pm2.start({
name: 'server',
script: 'server/server.js',
exec_mode: 'cluster',
instances: '2',
max_memory_restart: '900M'
}, function(err, apps) {
pm2.disconnect();
});
});

View File

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

View File

@ -1,3 +1,7 @@
$(document).ready(function() {
$('#reset-button').on('click', resetEditor);
});
var widgets = [];
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
lineNumbers: true,
@ -18,7 +22,7 @@ editor.setSize("100%", "auto");
// Hijack tab key to enter two spaces intead
editor.setOption("extraKeys", {
Tab: function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("add");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -26,7 +30,7 @@ editor.setOption("extraKeys", {
}
},
"Shift-Tab": function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("subtract");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -40,12 +44,92 @@ editor.setOption("extraKeys", {
});
/*
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
*/
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
};
// 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 );
};
codeStorage.onSave = function(func){
codeStorage.eventArray.push(func);
};
codeStorage.setSaveKey = function(key){
codeStorage.keyValue = key + 'Val';
};
codeStorage.getEditorValue = function(){
return ('' + localStorage.getItem(codeStorage.keyValue));
};
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(codeStorage.keyVersion, codeStorage.version);//just write current version
}else{
if( savedVersion !== codeStorage.version ){
//Update version
}
}
})();
///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 resetEditor() {
editor.setValue(allSeeds);
codeStorage.updateStorage();
};
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
lineNumbers: false,
@ -61,7 +145,10 @@ codeOutput.setValue('/**\n' +
' */');
codeOutput.setSize("100%", "100%");
var info = editor.getScrollInfo();
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
var after = editor.charCoords({
line: editor.getCursor().line + 1,
ch: 0
}, "local").top;
if (info.top + info.clientHeight < after)
editor.scrollTo(null, after - info.clientHeight + 3);
@ -71,20 +158,20 @@ var editorValue;
var challengeSeed = challengeSeed || null;
var tests = tests || [];
var allSeeds = '';
(function() {
challengeSeed.forEach(function(elem) {
allSeeds += elem + '\n';
allSeeds += elem + '\n';
});
})();
editorValue = allSeeds;
editorValue = (codeStorage.isAlive())? codeStorage.getEditorValue() : allSeeds;
myCodeMirror.setValue(editorValue);
function doLinting () {
editor.operation(function () {
function doLinting() {
editor.operation(function() {
for (var i = 0; i < widgets.length; ++i)
editor.removeLineWidget(widgets[i]);
widgets.length = 0;
@ -106,14 +193,14 @@ function doLinting () {
});
};
$('#submitButton').on('click', function () {
$('#submitButton').on('click', function() {
bonfireExecute();
});
function bonfireExecute() {
attempts++;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests= null;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests = null;
$('#codeOutput').empty();
var userJavaScript = myCodeMirror.getValue();
userJavaScript = removeComments(userJavaScript);
@ -145,16 +232,23 @@ var scrapeTests = function(userJavaScript) {
}
var counter = 0;
var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/);
var regex = new RegExp(
/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/
);
var match = regex.exec(userJavaScript);
while (match != null) {
var replacement = '//' + counter + testSalt;
userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length);
userJavaScript = userJavaScript.substring(0, match.index) + replacement +
userJavaScript.substring(match.index + match[0].length);
if (!userTests) {
userTests= [];
userTests = [];
}
userTests.push({"text": match[0], "line": counter, "err": null});
userTests.push({
"text": match[0],
"line": counter,
"err": null
});
counter++;
match = regex.exec(userJavaScript);
}
@ -176,17 +270,22 @@ var createTestDisplay = function() {
if (pushed) {
userTests.pop();
}
for (var i = 0; i < userTests.length;i++) {
for (var i = 0; i < userTests.length; i++) {
var test = userTests[i];
var testDoc = document.createElement("div");
if (test.err != null) {
console.log('Should be displaying bad tests');
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" + test.text + "</div><div class='col-xs-10 test-output wrappable'>" + test.err + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" +
test.text + "</div><div class='col-xs-10 test-output wrappable'>" +
test.err + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
} else {
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" + test.text + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" +
test.text + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
}
};
@ -208,18 +307,21 @@ var runTests = function(err, data) {
pushed = false;
$('#testSuite').children().remove();
if (err && userTests.length > 0) {
userTests= [{text:"Program Execution Failure", err: "No user tests were run."}];
userTests = [{
text: "Program Execution Failure",
err: "No user tests were run."
}];
createTestDisplay();
} else if (userTests) {
userTests.push(false);
pushed = true;
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray, __testArray){
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray,
__testArray) {
try {
if (chaiTestFromJSON) {
var output = eval(reassembleTest(chaiTestFromJSON, data));
debugger;
}
} catch(error) {
} catch (error) {
allTestsPassed = false;
__testArray[indexOfTestArray].err = error.message;
} finally {
@ -239,12 +341,12 @@ var runTests = function(err, data) {
function showCompletion() {
var time = Math.floor(Date.now()) - started;
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +', Attempts: ' + attempts);
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +
', Attempts: ' + attempts);
var bonfireSolution = myCodeMirror.getValue();
var didCompleteWith = $('#completed-with').val() || null;
$.post(
'/completed-bonfire/',
{
'/completed-bonfire/', {
challengeInfo: {
challengeId: challenge_Id,
challengeName: challenge_Name,
@ -252,10 +354,11 @@ function showCompletion() {
challengeType: challengeType,
solution: bonfireSolution
}
}, function(res) {
},
function(res) {
if (res) {
$('#complete-courseware-dialog').modal('show');
$('#complete-courseware-dialog').keydown(function (e) {
$('#complete-courseware-dialog').keydown(function(e) {
if (e.ctrlKey && e.keyCode == 13) {
$('#next-courseware-button').click();
}

View File

@ -16,67 +16,11 @@ $(document).ready(function() {
setCSRFToken($('meta[name="csrf-token"]').attr('content'));
$('#i-want-help').on('click', function() {
$('#help-modal').modal('hide');
var editorValue = editor.getValue();
var currentLocation = window.location.href;
$.post(
'/get-help',
{
payload: {
code: editorValue,
challenge: currentLocation
}
},
function(res) {
if (res) {
window.open('https://freecodecamp.slack.com/messages/help/', '_blank')
}
}
);
});
$('#i-want-help-editorless').on('click', function() {
$('#help-editorless-modal').modal('hide');
var currentLocation = window.location.href;
$.post(
'/get-help',
{
payload: {
challenge: currentLocation
}
},
function(res) {
if (res) {
window.open('https://freecodecamp.slack.com/messages/help/', '_blank')
}
}
);
});
$('#report-issue').on('click', function() {
$('#issue-modal').modal('hide');
window.open('https://github.com/freecodecamp/freecodecamp/issues/new?&body=Challenge '+ window.location.href +' has an issue. Please describe how to reproduce it, and include links to screen shots if possible.', '_blank')
});
$('#i-want-to-pair').on('click', function() {
$('#pair-modal').modal('hide');
var currentLocation = window.location.href;
$.post(
'/get-pair',
{
payload: {
challenge: currentLocation
}
},
function(res) {
if (res) {
window.open('https://freecodecamp.slack.com/messages/letspair/', '_blank')
}
}
);
});
$('.checklist-element').each(function() {
var checklistElementId = $(this).attr('id');
if(!!localStorage[checklistElementId]) {
@ -150,10 +94,6 @@ $(document).ready(function() {
$('#help-modal').modal('show');
});
$('#trigger-help-editorless-modal').on('click', function() {
$('#help-editorless-modal').modal('show');
});
$('#trigger-issue-modal').on('click', function() {
$('#issue-modal').modal('show');
});
@ -331,6 +271,7 @@ $(document).ready(function() {
$('#story-submit').on('click', storySubmitButtonHandler);
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
$('#comment-button').unbind('click');
var data = $('#comment-box').val();

View File

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

View File

@ -81,7 +81,7 @@
"sym([1, 2, 3], [5, 2, 1, 4]);"
],
"tests": [
"expect(sym([1, 2, 3], [5, 2, 1, 4])).to.eqls([3, 5, 4])",
"expect(sym([1, 2, 3], [5, 2, 1, 4])).to.equal([3, 5, 4]);",
"assert.deepEqual(sym([1, 2, 5], [2, 3, 5], [3, 4, 5]), [1, 4, 5], 'should return the symmetric difference of the given arrays');",
"assert.deepEqual(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]), [1, 4, 5], 'should return an array of unique values');",
"assert.deepEqual(sym([1, 1]), [1], 'should return an array of unique values');"
@ -230,7 +230,7 @@
"permAlone('aab');"
],
"tests": [
"expect(permAlone('aab')).to.be.a.number;",
"expect(permAlone('aab')).to.be.a('number');",
"expect(permAlone('aab')).to.equal(2);",
"expect(permAlone('aaa')).to.equal(0);",
"expect(permAlone('aabb')).to.equal(8);",

View File

@ -14,7 +14,7 @@
"Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.",
"Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.",
"The most popular tool for pair programming is Screen Hero. You can download Screen Hero for <a href='http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjowLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLnppcD9zb3VyY2U9d2ViIn0=' target='_blank'>Mac</a> or <a href='http://links.screenhero.com/e/c/eyJlbWFpbF9pZCI6Ik1qQTNNem9XQkNJQ1pBQUNjd0FYQVZrVEdnRkxNamtfX0JWZEdGVEpSZkVCWlRwbFpXRTBNamM0WVMxaE56SmlMVEV4WlRRdE9HUXpZUzFpWXpVNE1HRTJNalkxTldNNk1UUTJNVEEyQUE9PSIsInBvc2l0aW9uIjoxLCJocmVmIjoiaHR0cDovL2RsLnNjcmVlbmhlcm8uY29tL3NtYXJ0ZG93bmxvYWQvZklYQU1UUUJBTEtQQkhQTC9TY3JlZW5oZXJvLXNldHVwLmV4ZSJ9' target='_blank'>Windows</a>. Create your new user account from within the app.",
"We have a special chat room for people ready to pair program. Go to our <a href='http://freecodecamp.slack.com/messages/letspair'>http://freecodecamp.slack.com/messages/letspair</a> and type \"Hello Pair Programmers!\"",
"We have a special chat room for people ready to pair program. Go to our <a href='//gitter.im/FreeCodeCamp/LetsPair' target='_blank'>LetsPair chatroom on gitter</a> and type \"Hello Pair Programmers!\"",
"If someone is available, they will be your \"pair\" - the person you pair programming with.",
"If no one gets back to you in the first few minutes, don't worry. There will be lots of opportunities to pair program in the future.",
"If someone does get back to you, private message them and ask for the email address they used to register Screen Hero.",
@ -180,7 +180,8 @@
"assert.deepEqual(palindrome(\"not a palindrome\"), false);",
"assert.deepEqual(palindrome(\"A man, a plan, a canal. Panama\"), true);",
"assert.deepEqual(palindrome(\"never odd or even\"), true);",
"assert.deepEqual(palindrome(\"nope\"), false);"
"assert.deepEqual(palindrome(\"nope\"), false);",
"assert.deepEqual(palindrome(\"almostomla\"), false);"
],
"challengeSeed": [
"function palindrome(str) {",
@ -422,6 +423,7 @@
],
"tests": [
"expect(truncate('A-tisket a-tasket A green and yellow basket', 11)).to.eqls('A-tisket...');",
"expect(truncate('Peter Piper picked a peck of pickled peppers', 14)).to.eqls('Peter Piper...');",
"assert(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length) === 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is = length');",
"assert.strictEqual(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length + 2), 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is < length');"
],
@ -656,7 +658,10 @@
],
"tests": [
"assert.deepEqual(destroyer([1, 2, 3, 1, 2, 3], 2, 3), [1, 1], 'should remove correct values from an array');",
"assert.deepEqual(destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3), [1, 5, 1], 'should remove correct values from an array');"
"assert.deepEqual(destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3), [1, 5, 1], 'should remove correct values from an array');",
"assert.deepEqual(destroyer([3, 5, 1, 2, 2], 2, 3, 5), [1], 'should accept more than two additional arguments');",
"assert.deepEqual(destroyer([2, 3, 2, 3], 2, 3), [], 'should remove correct values from an array');",
"assert.deepEqual(destroyer(['tree', 'hamburger', 53], 'tree', 53), ['hamburger'], 'should handle NaN-elements');"
],
"MDNlinks": [
"Arguments object",
@ -754,8 +759,8 @@
},
{
"id": "a5de63ebea8dbee56860f4f2",
"name": "bonfire-diff-two-arrays",
"dashedName": "Bonfire: Diff Two Arrays",
"name": "Bonfire: Diff Two Arrays",
"dashedName": "bonfire-diff-two-arrays",
"difficulty": "2.01",
"description": [
"Compare two arrays and return a new array with any items not found in both of the original arrays.",
@ -853,7 +858,7 @@
"difficulty": "2.03",
"description": [
"Perform a search and replace on the sentence using the arguments provided and return the new sentence.",
"First argument is the sentence the perform the search and replace on.",
"First argument is the sentence to perform the search and replace on.",
"Second argument is the word that you will be replacing (before).",
"Third argument is what you will be replacing the second argument with (after).",
"NOTE: Preserve the case of the original word when you are replacing it. For example if you mean to replace the word 'Book' with the word 'dog', it should be replaced as 'Dog'",
@ -990,7 +995,8 @@
"expect(fearNotLetter('yz')).to.be.undefined;"
],
"MDNlinks": [
"String.charCodeAt()"
"String.charCodeAt()",
"String.fromCharCode()"
],
"challengeType": 5,
"nameCn": "",
@ -1091,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": [
@ -1104,10 +1110,16 @@
],
"tests": [
"assert.strictEqual(convert('Dolce & Gabbana'), 'Dolce &amp; Gabbana', 'should escape characters');",
"assert.strictEqual(convert('Hamburgers < Pizza < Tacos'), 'Hamburgers &lt; Pizza &lt; Tacos', '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(\"Shindler's List\"), 'Shindler&apos;s List', 'should escape characters');",
"assert.strictEqual(convert('<>'), '&lt;&gt;', 'should escape characters');",
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
],
"MDNlinks": [
"RegExp"
"RegExp",
"HTML Entities"
],
"challengeType": 5,
"nameCn": "",

View File

@ -943,7 +943,8 @@
"In addition to pixels, you can also specify a <code>border-radius</code> using a percentage."
],
"tests": [
"assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent&#44; making it perfectly circular.')"
"assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent&#44; making it perfectly circular.')",
"assert(editor.match(/50%/g), 'Be sure to use a percentage instead of a pixel value.')"
],
"challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>",
@ -1005,7 +1006,7 @@
],
"tests": [
"assert((/cat photos/gi).test($('a').text()), 'Your <code>a</code> element should have the anchor text of \"cat photos\"')",
"assert($('a').filter(function(index) { return /com/gi.test($('a').attr('href')); }).length > 0, 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(/http:\\/\\/catphotoapp\\.com/gi.test($('a').attr('href')), 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(editor.match(/<\\/a>/g) && editor.match(/<\\/a>/g).length === editor.match(/<a/g).length, 'Make sure your <code>a</code> element has a closing tag.')"
],
"challengeSeed": [
@ -1540,7 +1541,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'>",
@ -1613,7 +1614,7 @@
"For example: <code>&#60;form action=\"/url-where-you-want-to-submit-form-data\"&#62;&#60;/form&#62;</code>."
],
"tests": [
"assert($('form').length > 0, 'Wrap your text input element within a <code>form</code> element.')",
"assert($('form') && $('form').children('input') && $('form').children('input').length > 0, 'Wrap your text input element within a <code>form</code> element.')",
"assert($('form').attr('action'), 'Your <code>form</code> element should have an <code>action</code> attribute.')",
"assert(editor.match(/<\\/form>/g) && editor.match(/<form/g) && editor.match(/<\\/form>/g).length === editor.match(/<form/g).length, 'Make sure your <code>form</code> element has a closing tag.')",
"assert(editor.match(/\\/submit-cat-photo/ig), 'Make sure your <code>form</code> action is set to <code>/submit-cat-photo</code>.')"

View File

@ -626,7 +626,7 @@
"The \"row\" class is applied to a <code>div</code>, and the buttons themselves can be wrapped within it."
],
"tests": [
"assert($('div.row:has(button)'), 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.row:has(button)').length > 0, 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.col-xs-4:has(button)').length > 2, 'Each of your Bootstrap buttons should be wrapped within its own a <code>div</code> element with the class \"col-xs-4\".')",
"assert(editor.match(/<\\/button>/g) && editor.match(/<button/g) && editor.match(/<\\/button>/g).length === editor.match(/<button/g).length, 'Make sure each of your <code>button</code> elements has a closing tag.')",
"assert(editor.match(/<\\/div>/g) && editor.match(/<div/g) && editor.match(/<\\/div>/g).length === editor.match(/<div/g).length, 'Make sure each of your <code>div</code> elements has a closing tag.')"
@ -1264,7 +1264,7 @@
"tests": [
"assert($('button[type=\\'submit\\']').hasClass('btn btn-primary'), 'Give the submit button in your form the classes \"btn btn-primary\".')",
"assert($('button[type=\\'submit\\']:has(i.fa.fa-paper-plane)').length > 0, 'Add a <code>&#60;i class=\"fa fa-paper-plane\"&#62;&#60;/i&#62;</code> within your submit <code>button</code> element.')",
"assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text <code>input<code> in your form the class \"form-control\".')",
"assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text <code>input</code> in your form the class \"form-control\".')",
"assert(editor.match(/<\\/i>/g) && editor.match(/<\\/i/g).length > 3, 'Make sure each of your <code>i</code> elements has a closing tag.')"
],
"challengeSeed": [

View File

@ -45,18 +45,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": [],
@ -195,7 +197,7 @@
"Click \"News\" in the upper right hand corner.",
"You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.",
"You can upvote links. This will push the link up the rankings of hot links.",
"You an also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
"You can also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
"You can also submit links. You can modify the link's headline and also leave an initial comment about the link.",
"You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.",
"When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link.",

View File

@ -56,7 +56,7 @@
"<span class='text-info'>Hint:</span> Here's an example call to Twitch.tv's JSON API: <code>https://api.twitch.tv/kraken/streams/freecodecamp</code>.",
"<span class='text-info'>Hint:</span> The relevant documentation about this API call is here: <a href='https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel' target='_blank'>https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel</a>.",
"<span class='text-info'>Hint:</span> Here's an array of the Twitch.tv usernames of people who regularly stream coding: <code>[\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]</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 using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"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 using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -87,7 +87,7 @@
"Here are the <a href='http://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a> you must enable, and optional bonus user stories:",
"<span class='text-info'>User Story:</span> As a user, I can click a button to show me a new random quote.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can press a button to tweet out a quote.",
"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 using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"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 using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -120,7 +120,7 @@
"<span class='text-info'>Bonus User Story:</span> As a user, I can see an icon depending on the temperature..",
"<span class='text-info'>Bonus User Story:</span> As a user, I see a different background image depending on the temperature (e.g. snowy mountain, hot desert).",
"<span class='text-info'>Bonus User Story:</span> As a user, I can push a button to toggle between Fahrenheit and Celsius.",
"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 using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"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 using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -154,7 +154,7 @@
"<span class='text-info'>User Story:</span> As a user, I can click a link to go directly to the post's discussion page.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can see how many upvotes each story has.",
"<span class='text-info'>Hint:</span> Here's the Camper News Hot Stories API endpoint: <code>http://www.freecodecamp.com/stories/hotStories</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 using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"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 using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -187,7 +187,7 @@
"<span class='text-info'>Bonus User Story:</span>As a user, I can click a button to see a random Wikipedia entry.",
"<span class='text-info'>Bonus User Story:</span>As a user, when I type in the search box, I can see a dropdown menu with autocomplete options for matching Wikipedia entries.",
"<span class='text-info'>Hint:</span> Here's an entry on using Wikipedia's API: <code>http://www.mediawiki.org/wiki/API:Main_page</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 using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"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 using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],

View File

@ -863,7 +863,7 @@
"description": [
"<div class=\"col-xs-12 col-sm-10 col-sm-offset-1\">",
" <p class='h2'>Translation is an all-or-nothing proposal.</h2>",
" <p class='large-p'>We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that langauge.</p>",
" <p class='large-p'>We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that language.</p>",
" <p class='large-p'>In addition to translating these initially, we'll also need to maintain the translation as the challenges are gradually updated.</p>",
" <p class='large-p'>If you're able to help us, you can join our <a href='https://trello.com/b/m7zhwXka/fcc-translation' target='_blank'>Trello board</a> by sending @quincylarson your email address in Slack.</p>",
"</div>"

View File

@ -31,63 +31,89 @@
*/
var R = require('ramda'),
Rx = require('rx'),
assign = require('object.assign'),
debug = require('debug')('freecc:challenges'),
utils = require('../utils'),
// this would be so much cleaner with destructering...
saveUser = require('../utils/rx').saveUser,
observableQueryFromModel = require('../utils/rx').observableQueryFromModel,
userMigration = require('../utils/middleware').userMigration,
MDNlinks = require('../../seed/bonfireMDNlinks');
ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo,
ifNoUserSend = require('../utils/middleware').ifNoUserSend;
var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.getChallengeMapWithIds();
var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i;
var dasherize = utils.dasherize;
var unDasherize = utils.unDashedName;
function getMDNlinks(links) {
// takes in an array of links, which are strings
var populatedLinks = [];
var getMDNLinks = utils.getMDNLinks;
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
if (links) {
links.forEach(function (value) {
populatedLinks.push(MDNlinks[value]);
});
function updateUserProgress(user, challengeId, completedChallenge) {
var index = user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
user.progressTimestamps.push(Date.now());
user.uncompletedChallenges.splice(index, 1);
}
return populatedLinks;
user.completedChallenges.push(completedChallenge);
return user;
}
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
var User = app.models.User;
var redirectNonUser =
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works');
var send200toNonUser = ifNoUserSend(true);
router.post(
'/completed-challenge/',
send200toNonUser,
completedChallenge
);
router.post(
'/completed-zipline-or-basejump',
send200toNonUser,
completedZiplineOrBasejump
);
router.post(
'/completed-bonfire',
send200toNonUser,
completedBonfire
);
// the follow routes are covered by userMigration
router.use(userMigration);
router.get('/map', challengeMap);
router.get(
'/challenges/next-challenge',
userMigration,
redirectNonUser,
returnNextChallenge
);
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
router.get('/challenges/:challengeName', returnIndividualChallenge);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
router.get(
'/challenges/',
redirectNonUser,
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;
}
@ -120,18 +146,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;
});
@ -142,6 +167,7 @@ module.exports = function(app) {
return elem;
}
});
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
@ -153,42 +179,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;
});
})
@ -196,261 +240,168 @@ 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) {
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 ' +
@ -459,92 +410,110 @@ 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) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
// backwards compatibility
return (challenge.id || challenge._id);
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
// backwards compatibility hack
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -1,66 +0,0 @@
var R = require('ramda'),
// debug = require('debug')('freecc:cntr:challengeMap'),
utils = require('./../utils'),
middleware = require('../utils/middleware');
module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
return (challenge.id || challenge._id); // backwards compatibility
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
//backwards compatibility hack
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -1,6 +1,13 @@
var debug = require('debug')('freecc:nonprofits');
var observeMethod = require('../utils/rx').observeMethod;
var unDasherize = require('../utils').unDasherize;
var dasherize = require('../utils').dasherize;
module.exports = function(app) {
var router = app.loopback.Router();
var Nonprofit = app.models.Nonprofit;
var findNonprofits = observeMethod(Nonprofit, 'find');
var findOneNonprofit = observeMethod(Nonprofit, 'findOne');
router.get('/nonprofits/directory', nonprofitsDirectory);
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
@ -8,57 +15,55 @@ module.exports = function(app) {
app.use(router);
function nonprofitsDirectory(req, res, next) {
Nonprofit.find(
{ where: { estimatedHours: { $gt: 0 } } },
function(err, nonprofits) {
if (err) { return next(err); }
findNonprofits({ where: { estimatedHours: { gt: 0 } } }).subscribe(
function(nonprofits) {
res.render('nonprofits/directory', {
title: 'Nonprofits we help',
nonprofits: nonprofits
});
}
},
next
);
}
function returnIndividualNonprofit(req, res, next) {
var dashedName = req.params.nonprofitName;
var nonprofitName = dashedName.replace(/\-/g, ' ');
var nonprofitName = unDasherize(dashedName);
var query = { where: { name: {
like: nonprofitName,
options: 'i'
} } };
Nonprofit.find(
{ where: { name: new RegExp(nonprofitName, 'i') } },
function(err, nonprofit) {
if (err) {
return next(err);
}
if (nonprofit.length < 1) {
debug('looking for %s', nonprofitName);
debug('query', query);
findOneNonprofit(query).subscribe(
function(nonprofit) {
if (!nonprofit) {
req.flash('errors', {
msg: "404: We couldn't find a nonprofit with that name. " +
'Please double check the name.'
});
return res.redirect('/nonprofits');
}
nonprofit = nonprofit.pop();
var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-');
var dashedNameFull = dasherize(nonprofit.name);
if (dashedNameFull !== dashedName) {
return res.redirect('../nonprofit/' + dashedNameFull);
}
var buttonActive = false;
if (req.user) {
if (req.user.uncompletedBonfires.length === 0) {
if (req.user.completedCoursewares.length > 63) {
var hasShownInterest =
nonprofit.interestedCampers.filter(function ( obj ) {
return obj.username === req.user.username;
});
if (hasShownInterest.length === 0) {
buttonActive = true;
}
}
var buttonActive = false;
if (
req.user &&
req.user.uncompletedBonfires.length === 0 &&
req.user.completedCoursewares.length > 63
) {
var hasShownInterest =
nonprofit.interestedCampers.filter(function(user) {
return user.username === req.user.username;
});
if (hasShownInterest.length === 0) {
buttonActive = true;
}
}
@ -97,33 +102,8 @@ module.exports = function(app) {
buttonActive: buttonActive,
currentStatus: nonprofit.currentStatus
});
}
},
next
);
}
/*
function interestedInNonprofit(req, res, next) {
if (req.user) {
Nonprofit.findOne(
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
function(err, nonprofit) {
if (err) { return next(err); }
nonprofit.interestedCampers.push({
username: req.user.username,
picture: req.user.picture,
timeOfInterest: Date.now()
});
nonprofit.save(function(err) {
if (err) { return next(err); }
req.flash('success', {
msg: 'Thanks for expressing interest in this nonprofit project! ' +
"We've added you to this project as an interested camper!"
});
res.redirect('back');
});
}
);
}
}
*/
};

View File

@ -2,20 +2,17 @@ var Rx = require('rx'),
Twit = require('twit'),
async = require('async'),
moment = require('moment'),
Slack = require('node-slack'),
request = require('request'),
debug = require('debug')('freecc:cntr:resources'),
constantStrings = require('../utils/constantStrings.json'),
bootcampJson = require('../utils/bootcamps.json'),
secrets = require('../../config/secrets');
var slack = new Slack(secrets.slackHook);
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;
@ -24,8 +21,6 @@ module.exports = function(app) {
router.get('/api/trello', trelloCalls);
router.get('/api/codepen/twitter/:screenName', twitter);
router.get('/sitemap.xml', sitemap);
router.post('/get-help', getHelp);
router.post('/get-pair', getPair);
router.get('/chat', chat);
router.get('/coding-bootcamp-cost-calculator', bootcampCalculator);
router.get('/coding-bootcamp-cost-calculator.json', bootcampCalculatorJson);
@ -39,72 +34,9 @@ module.exports = function(app) {
router.get('/unsubscribe/:email', unsubscribe);
router.get('/unsubscribed', unsubscribed);
router.get('/cats.json', getCats);
router.get('/api/slack', slackInvite);
app.use(router);
function slackInvite(req, res, next) {
if (req.user) {
if (req.user.email) {
var invite = {
'email': req.user.email,
'token': process.env.SLACK_KEY,
'set_active': true
};
var headers = {
'User-Agent': 'Node Browser/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
};
var options = {
url: 'https://freecodecamp.slack.com/api/users.admin.invite',
method: 'POST',
headers: headers,
form: invite
};
request(options, function (error, response) {
if (!error && response.statusCode === 200) {
req.flash('success', {
msg: 'We\'ve successfully requested an invite for you.' +
' Please check your email and follow the ' +
'instructions from Slack.'
});
req.user.sentSlackInvite = true;
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('back');
});
} else {
req.flash('errors', {
msg: 'The invitation email did not go through for some reason.' +
' Please try again or <a href=\'mailto:team@' +
'freecodecamp.com?subject=' +
'slack%20invite%20failed%20to%20send\'>' +
'email us</a>.'
});
return res.redirect('back');
}
});
} else {
req.flash('notice', {
msg: 'Before we can send your Slack invite, we need your email ' +
'address. Please update your profile information here.'
});
return res.redirect('/account');
}
} else {
req.flash('notice', {
msg: 'You need to sign in to Free Code Camp before ' +
'we can send you a Slack invite.'
});
return res.redirect('/account');
}
}
function twitter(req, res, next) {
// sends out random tweets about javascript
var T = new Twit({
@ -134,56 +66,6 @@ module.exports = function(app) {
);
}
function getHelp(req, res) {
var userName = req.user.username;
var code = req.body.payload.code ? '\n```\n' +
req.body.payload.code + '\n```\n'
: '';
var challenge = req.body.payload.challenge;
slack.send({
text: '*@' + userName + '* wants help with ' + challenge + '. ' +
code + 'Hey, *@' + userName + '*, if no one helps you right ' +
'away, try typing out your problem in detail to me. Like this: ' +
'http://en.wikipedia.org/wiki/Rubber_duck_debugging',
channel: '#help',
username: 'Debuggy the Rubber Duck',
'icon_url': 'https://pbs.twimg.com/profile_images/' +
'3609875545/569237541c920fa78d78902069615caf.jpeg'
});
return res.sendStatus(200);
}
function getPair(req, res) {
var userName = req.user.username;
var challenge = req.body.payload.challenge;
slack.send({
text: [
'Anyone want to pair with *@',
userName,
'* on ',
challenge,
'?\nMake sure you install Screen Hero here: ',
'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n',
'Then start your pair program session with *@',
userName,
'* by typing \"/hero @',
userName,
'\" into Slack.\n And *@',
userName,
'*, be sure to launch Screen Hero, then keep coding. ',
'Another camper may pair with you soon.'
].join(''),
channel: '#letspair',
username: 'Companion Cube',
'icon_url':
'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' +
'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg'
});
return res.sendStatus(200);
}
function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD');
@ -193,15 +75,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 +106,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 +126,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 +147,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 +167,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 +188,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 +199,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) {
@ -338,6 +219,10 @@ module.exports = function(app) {
res.send(bootcampJson);
}
function chat(req, res) {
res.redirect('https://gitter.im/FreeCodeCamp/FreeCodeCamp');
}
function jobsForm(req, res) {
res.render('resources/jobs-form', {
title: 'Employer Partnership Form for Job Postings,' +
@ -383,7 +268,7 @@ module.exports = function(app) {
}
function unsubscribe(req, res, next) {
User.findOne({ email: req.params.email }, function(err, user) {
User.findOne({ where: { email: req.params.email } }, function(err, user) {
if (user) {
if (err) {
return next(err);

View File

@ -18,5 +18,13 @@ module.exports = function(app) {
);
});
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
};

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ module.exports = {
},
'google-login': {
provider: 'google',
authScheme: 'oauth2',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
@ -55,7 +55,7 @@ module.exports = {
},
'google-link': {
provider: 'google',
authScheme: 'oauth2',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,

View File

@ -1,6 +1,6 @@
require('dotenv').load();
require('pmx').init();
// handle uncaught exceptions. Forever will restart process on shutdown
var pmx = require('pmx');
pmx.init();
var R = require('ramda'),
assign = require('lodash').assign,
@ -20,7 +20,6 @@ var R = require('ramda'),
path = require('path'),
expressValidator = require('express-validator'),
lessMiddleware = require('less-middleware'),
pmx = require('pmx'),
passportProviders = require('./passport-providers'),
/**
@ -42,12 +41,6 @@ app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//if (process.env.NODE_ENV === 'production') {
// app.use(forceDomain({
// hostname: 'www.freecodecamp.com'
// }));
//}
app.use(compress());
app.use(lessMiddleware(path.join(__dirname, '/public')));
app.use(logger('dev'));
@ -98,6 +91,8 @@ var trusted = [
'https://freecodecamp.com',
'https://freecodecamp.org',
'*.freecodecamp.org',
// NOTE(berks): add the following as the blob above was not covering www
'http://www.freecodecamp.org',
'ws://freecodecamp.com/',
'ws://www.freecodecamp.com/',
'*.gstatic.com',
@ -143,7 +138,8 @@ app.use(helmet.csp({
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js'
'http://cdn.inspectlet.com/inspectlet.js',
'http://www.freecodecamp.org'
].concat(trusted),
'connect-src': [].concat(trusted),
styleSrc: [
@ -191,6 +187,8 @@ app.use(
})
);
// track when connecting to db starts
var startTime = Date.now();
boot(app, {
appRootDir: __dirname,
dev: process.env.NODE_ENV
@ -259,6 +257,8 @@ R.keys(passportProviders).map(function(strategy) {
// if (process.env.NODE_ENV === 'development') {
if (true) { // eslint-disable-line
// NOTE(berks): adding pmx here for Beta test. Remove for production
app.use(pmx.expressErrorHandler());
app.use(errorHandler({
log: true
}));
@ -302,20 +302,46 @@ if (true) { // eslint-disable-line
});
}
/**
* Start Express server.
*/
module.exports = app;
app.listen(app.get('port'), function() {
console.log(
'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'),
app.get('env')
);
});
app.start = function () {
app.listen(app.get('port'), function() {
console.log(
'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'),
app.get('env')
);
});
};
// start the server if `$ node server.js`
if (require.main === module) {
if (process.env.NODE_ENV === 'production') {
var timeoutHandler;
console.log('waiting for db to connect');
var onConnect = function() {
console.log('db connected in %s ms', Date.now() - startTime);
if (timeoutHandler) {
clearTimeout(timeoutHandler);
}
app.start();
};
module.exports = app;
var timeoutHandler = setTimeout(function() {
var message =
'db did not after ' +
(Date.now() - startTime) +
' ms connect crashing hard';
console.log(message);
// purposely shutdown server
// pm2 should restart this in production
throw new Error(message);
}, 5000);
app.dataSources.db.on('connected', onConnect);
} else {
app.start();
}
}

View File

@ -7,6 +7,7 @@ var path = require('path'),
fs = require('fs'),
MDNlinks = require('../../seed/bonfireMDNlinks'),
resources = require('./resources.json'),
nonprofits = require('../../seed/nonprofits.json'),
fieldGuides = require('../../seed/field-guides.json');
@ -16,7 +17,7 @@ var path = require('path'),
*/
var allFieldGuideIds, allFieldGuideNames, allNonprofitNames,
challengeMap, challengeMapForDisplay, challengeMapWithIds,
challengeMapWithNames, allChallengeIds, allChallenges,
challengeMapWithNames, allChallengeIds,
challengeMapWithDashedNames;
/**
@ -59,6 +60,17 @@ Array.zip = function(left, right, combinerFunction) {
module.exports = {
dasherize: function dasherize(name) {
return ('' + name)
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\.]/gi, '');
},
unDasherize: function unDasherize(name) {
return ('' + name).replace(/\-/g, ' ');
},
getChallengeMapForDisplay: function () {
if (!challengeMapForDisplay) {
challengeMapForDisplay = {};
@ -216,5 +228,18 @@ module.exports = {
}
});
})();
},
getMDNLinks: function(links) {
if (!links) {
return [];
}
// takes in an array of links, which are strings
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
return links.map(function(value) {
return MDNlinks[value];
});
}
};

View File

@ -33,3 +33,21 @@ 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);
};
};
exports.ifNoUserSend = function ifNoUserSend(sendThis) {
return function(req, res, next) {
if (req.user) {
return next();
}
return res.status(200).send(sendThis);
};
};

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

@ -0,0 +1,30 @@
var Rx = require('rx');
var debug = require('debug')('freecc:rxUtils');
var slice = Array.prototype.slice;
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);
};
exports.observeMethod = function observeMethod(Model, method) {
return Rx.Observable.fromNodeCallback(Model[method], Model);
};

View File

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

View File

@ -84,19 +84,20 @@ block content
label.negative-10.btn.btn-primary.btn-block#submitButton
i.fa.fa-play
| &nbsp; Run code (ctrl + enter)
if (user && user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
label.btn.btn-success#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
label.btn.btn-success#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-reset-modal
i.fa.fa-refresh
| &nbsp; Reset
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
label.btn.btn-success#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
label.btn.btn-success#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.button-spacer
form.code
.form-group.codeMirrorView
@ -119,7 +120,7 @@ block content
form.code
.form-group.codeMirrorView
textarea#codeEditor(autofocus=true, style='display: none;')
script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.5.js')
script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.6.js')
#complete-courseware-dialog.modal(tabindex='-1')
.modal-dialog.animated.zoomIn.fast-animation
@ -140,6 +141,15 @@ block content
= phrase
else
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
#reset-modal.modal(tabindex='-1')
.modal-dialog.animated.fadeInUp.fast-animation
.modal-content
.modal-header.challenge-list-header Clear your code?
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body
h3 This will restore your code editor to its original state.
a.btn.btn-lg.btn-info.btn-block#reset-button(href='#', data-dismiss='modal', aria-hidden='true') Clear my code
a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
include ../partials/challenge-modals
script.
var MDNlinks = !{JSON.stringify(MDNlinks)};

View File

@ -33,8 +33,6 @@ block content
label.btn.btn-primary.btn-block.negative-10#next-courseware-button
.ion-checkmark-circled
| &nbsp; Go to my next challenge (ctrl + enter)
if (user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-help-modal

View File

@ -35,18 +35,17 @@ block content
span.ion-arrow-up-b
| Less information
#submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter)
if (user && user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
label.btn.btn-success#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
label.btn.btn-success#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
label.btn.btn-success#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
label.btn.btn-success#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.spacer
form.code
.form-group.codeMirrorView

View File

@ -21,14 +21,13 @@ block content
script.
var userLoggedIn = true;
.button-spacer
if (user.sentSlackInvite)
.btn-group.input-group.btn-group-justified
.btn.btn-success.btn-big#trigger-help-editorless-modal
i.fa.fa-medkit
| &nbsp; Get help
.btn.btn-success.btn-big#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Report a bug
.btn-group.input-group.btn-group-justified
.btn.btn-success.btn-big#trigger-help-modal-modal
i.fa.fa-medkit
| &nbsp; Get help
.btn.btn-success.btn-big#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Report a bug
.button-spacer
else
a.btn.btn-big.signup-btn.btn-block(href='/login') Sign in so you can save your progress

View File

@ -18,18 +18,17 @@ block content
br
if (user)
a.btn.btn-primary.btn-big.btn-block#completed-zipline-or-basejump I've completed this challenge (ctrl + enter)
if (user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified
.btn.btn-success.btn-big#trigger-help-editorless-modal
i.fa.fa-medkit
| &nbsp; Help
.btn.btn-success.btn-big#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
.btn.btn-success.btn-big#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.button-spacer
.btn-group.input-group.btn-group-justified
.btn.btn-success.btn-big#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
.btn.btn-success.btn-big#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
.btn.btn-success.btn-big#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug
.button-spacer
script.
var userLoggedIn = true;

View File

@ -4,12 +4,12 @@
.modal-header.challenge-list-header Ready to pair program?
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body.text-center
h3 This will create a pair programming request.
h3 This will take you to our pair programming room where you can request a pair.
h3 You'll need &thinsp;
a(href='/field-guide/how-do-i-install-screenhero' target='_blank') Screen Hero
| .
h3 Other campers may then message you about pair programming.
a.btn.btn-lg.btn-primary.btn-block#i-want-to-pair(name='_csrf', value=_csrf) Create my pair request
a.btn.btn-lg.btn-primary.btn-block(href='https://gitter.im/FreeCodeCamp/LetsPair', data-dismiss='modal', aria-hidden='true' target='_blank') Take me to the pair programming room
a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
#issue-modal.modal(tabindex='-1')
@ -33,20 +33,6 @@
a(href='/field-guide/how-do-i-get-help-when-i-get-stuck' target='_blank') RSAP
| .
h3 If you've already read the errors and searched Google, you should ask for help.
h3 This will open a help request in our Help chat room.
a.btn.btn-lg.btn-primary.btn-block#i-want-help(name='_csrf', value=_csrf) Ask for help
a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
#help-editorless-modal.modal(tabindex='-1')
.modal-dialog.animated.zoomIn.fast-animation
.modal-content
.modal-header.challenge-list-header Need some help?
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body.text-center
h3 Remember to use &thinsp;
a(href='/field-guide/how-do-i-get-help-when-i-get-stuck' target='_blank') RSAP
| .
h3 If you've already read the errors and searched Google, you should ask for help.
h3 This will open a help request in our Help chat room.
a.btn.btn-lg.btn-primary.btn-block#i-want-help-editorless(name='_csrf', value=_csrf) Ask for help
h3 This will take you to our help room.
a.btn.btn-lg.btn-primary.btn-block(href='https://gitter.im/FreeCodeCamp/LetsPair', data-dismiss='modal', aria-hidden='true' target='_blank') Take me to the help room
a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel

View File

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

View File

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

749
setup.js
View File

@ -1,749 +0,0 @@
var fs = require('fs');
var os = require('os');
var blessed = require('blessed');
var multiline = require('multiline');
if (os.platform() === 'win32') {
console.log('**************************************************************');
console.log('Hackathon Starter Generator has been disabled on Windows until');
console.log('https://github.com/chjj/blessed is fixed or until I find a');
console.log('better CLI module.');
console.log('**************************************************************');
process.exit();
}
var screen = blessed.screen({
autoPadding: true
});
screen.key('q', function() {
process.exit(0);
});
var home = blessed.list({
parent: screen,
padding: { top: 2 },
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
selectedFg: 'blue',
selectedBg: 'white',
items: [
'» REMOVE AUTHENTICATION PROVIDER',
'» CHANGE EMAIL SERVICE',
'» ADD NODE.JS CLUSTER SUPPORT',
'» EXIT'
]
});
var homeTitle = blessed.text({
parent: screen,
align: 'center',
fg: 'blue',
bg: 'white',
content: 'Hackathon Starter (c) 2014'
});
var footer = blessed.text({
parent: screen,
bottom: 0,
fg: 'white',
bg: 'blue',
tags: true,
content: ' {cyan-fg}<Up/Down>{/cyan-fg} moves |' +
' {cyan-fg}<Enter>{/cyan-fg} selects | {cyan-fg}<q>{/cyan-fg} exits'
});
var inner = blessed.form({
top: 'center',
left: 'center',
mouse: true,
keys: true,
width: 33,
height: 10,
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
fg: 'white',
bg: 'red'
});
var success = blessed.box({
top: 'center',
left: 'center',
mouse: true,
keys: true,
tags: true,
width: '50%',
height: '40%',
border: {
type: 'line',
fg: 'white',
bg: 'green'
},
fg: 'white',
bg: 'green',
padding: 1
});
success.on('keypress', function() {
home.focus();
home.remove(success);
});
var clusterText = blessed.text({
top: 'top',
bg: 'red',
fg: 'white',
tags: true,
content: 'Take advantage of multi-core systems' +
' using built-in {underline}cluster{/underline} module.'
});
var enable = blessed.button({
parent: inner,
bottom: 0,
mouse: true,
shrink: true,
name: 'enable',
content: ' ENABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var disable = blessed.button({
parent: inner,
bottom: 0,
left: 10,
mouse: true,
shrink: true,
name: 'disable',
content: ' DISABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var cancel = blessed.button({
parent: inner,
bottom: 0,
left: 21,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
cancel.on('press', function() {
home.focus();
home.remove(inner);
screen.render();
});
var authForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
authForm.on('submit', function() {
var passportConfig = fs.readFileSync('config/passport.js')
.toString().split(os.EOL);
var loginTemplate = fs.readFileSync('views/account/login.jade')
.toString().split(os.EOL);
var profileTemplate = fs.readFileSync('views/account/profile.jade')
.toString().split(os.EOL);
var userModel = fs.readFileSync('models/User.js').toString().split(os.EOL);
var app = fs.readFileSync('app.js').toString().split(os.EOL);
var secrets = fs.readFileSync('config/secrets.js').toString().split(os.EOL);
var index = passportConfig
.indexOf('var FacebookStrategy = require("passport-facebook").Strategy;');
if (facebookCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Facebook.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-facebook.btn-social(href="/auth/facebook")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.facebook');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' facebook: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf([
'app.get("/auth/facebook"',
'passport.authenticate("facebook",',
' { scope: ["email", "user_location"] }));'
].join(' '));
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
"var GitHubStrategy = require('passport-github').Strategy;"
);
if (githubCheckbox.checked && index !== -1) {
console.log(index);
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with GitHub.');
passportConfig.splice(index, 48);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-github.btn-social(href='/auth/github')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.github');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' github: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/github", passport.authenticate("github"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var GoogleStrategy = require("passport-google-oauth").OAuth2Strategy;'
);
if (googleCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Google.');
passportConfig.splice(index, 46);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.google');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' google: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/google",' +
' passport.authenticate("google", { scope: "profile email" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var TwitterStrategy = require("passport-twitter").Strategy;'
);
if (twitterCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Twitter.');
passportConfig.splice(index, 43);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-twitter.btn-social(href="/auth/twitter")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.twitter');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade', profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' twitter: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
"app.get('/auth/twitter', passport.authenticate('twitter'));"
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var LinkedInStrategy = require("passport-linkedin-oauth2").Strategy;'
);
if (linkedinCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with LinkedIn.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-linkedin.btn-social(href="/auth/linkedin")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.linkedin');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' linkedin: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/linkedin",',
' passport.authenticate("linkedin", { state: "SOME STATE" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var InstagramStrategy = require("passport-instagram").Strategy;'
);
if (instagramCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Instagram.');
passportConfig.splice(index, 43);
fs.writeFileSync(
'config/passport.js',
passportConfig.join(os.EOL)
);
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-instagram.btn-social(href="/auth/instagram")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync(
'views/account/login.jade',
loginTemplate.join(os.EOL)
);
index = profileTemplate.indexOf(' if user.instagram');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = app.indexOf(
'app.get("/auth/instagram", passport.authenticate("instagram"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
}
home.remove(authForm);
home.append(success);
success.setContent(
'Selected authentication providers have been removed' +
'from passportConfig.js, User.js, server.js, login.jade and profile.jade!'
);
success.focus();
screen.render();
});
var authText = blessed.text({
parent: authForm,
content: 'Selecting a checkbox removes an authentication provider.' +
' If authentication provider is already removed, no action will be taken.',
padding: 1,
bg: 'magenta',
fg: 'white'
});
var facebookCheckbox = blessed.checkbox({
parent: authForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Facebook'
});
var githubCheckbox = blessed.checkbox({
parent: authForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'GitHub'
});
var googleCheckbox = blessed.checkbox({
parent: authForm,
top: 8,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Google'
});
var twitterCheckbox = blessed.checkbox({
parent: authForm,
top: 9,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Twitter'
});
var linkedinCheckbox = blessed.checkbox({
parent: authForm,
top: 10,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'LinkedIn'
});
var instagramCheckbox = blessed.checkbox({
parent: authForm,
top: 11,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Instagram'
});
var authSubmit = blessed.button({
parent: authForm,
top: 13,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authSubmit.on('press', function() {
authForm.submit();
});
var authCancel = blessed.button({
parent: authForm,
top: 13,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authCancel.on('press', function() {
home.focus();
home.remove(authForm);
screen.render();
});
var emailForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
emailForm.on('submit', function() {
var contactCtrl = fs.readFileSync('controllers/contact.js')
.toString().split(os.EOL);
var userCtrl = fs.readFileSync('controllers/user.js')
.toString().split(os.EOL);
var choice = null;
if (sendgridRadio.checked) {
choice = 'SendGrid';
} else if (mailgunRadio.checked) {
choice = 'Mailgun';
} else if (mandrillRadio.checked) {
choice = 'Mandrill';
}
var index = contactCtrl.indexOf(
'var transporter = nodemailer.createTransport({'
);
contactCtrl.splice(index + 1, 1, " service: '" + choice + "',");
contactCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
contactCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/contact.js', contactCtrl.join(os.EOL));
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({'
);
userCtrl.splice(index + 1, 1, " service: '" + choice + "',");
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({',
(index + 1)
);
userCtrl.splice(
index + 1,
1,
' service: "' + choice + '",'
);
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/user.js', userCtrl.join(os.EOL));
home.remove(emailForm);
home.append(success);
success.setContent('Email Service has been switched to ' + choice);
success.focus();
screen.render();
});
var emailText = blessed.text({
parent: emailForm,
content: 'Select one of the following email service providers ' +
'for {underline}contact form{/underline}' +
' and {underline}password reset{/underline}.',
padding: 1,
bg: 'red',
fg: 'white',
tags: true
});
var sendgridRadio = blessed.radiobutton({
parent: emailForm,
top: 5,
checked: true,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'SendGrid'
});
var mailgunRadio = blessed.radiobutton({
parent: emailForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mailgun'
});
var mandrillRadio = blessed.radiobutton({
parent: emailForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mandrill'
});
var emailSubmit = blessed.button({
parent: emailForm,
top: 9,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailSubmit.on('press', function() {
emailForm.submit();
});
var emailCancel = blessed.button({
parent: emailForm,
top: 9,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailCancel.on('press', function() {
home.focus();
home.remove(emailForm);
screen.render();
});
home.on('select', function(child, index) {
switch (index) {
case 0:
home.append(authForm);
authForm.focus();
screen.render();
break;
case 1:
home.append(emailForm);
emailForm.focus();
break;
case 2:
addClusterSupport();
home.append(success);
success.setContent([
'New file {underline}cluster_app.js{/underline} has been created.',
'Your app is now able to use more than 1 CPU by running',
'{underline}node cluster_app.js{/underline}, which in turn',
'spawns multiple instances of {underline}server.js{/underline}'
].join(' '));
success.focus();
screen.render();
break;
default:
process.exit(0);
}
});
screen.render();
function addClusterSupport() {
var fileContents = multiline(function() {
/*
var os = require('os');
var cluster = require('cluster');
cluster.setupMaster({
exec: 'server.js'
});
cluster.on('exit', function(worker) {
console.log('worker ' + worker.id + ' died');
cluster.fork();
});
for (var i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
*/
});
fs.writeFileSync('cluster_app.js', fileContents);
}