Merge pull request #8927 from FreeCodeCamp/staging

Release staging
This commit is contained in:
Berkeley Martinez
2016-06-02 23:25:19 -07:00
24 changed files with 232 additions and 100 deletions

View File

@ -190,11 +190,10 @@ Be sure to post in the PR conversation that you have made the requested changes.
## Other resources
- [Searching for Your Issue on GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Searching-for-Your-Issue-on-GitHub)
- [Searching for Your Issue on GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Search-Existing-Issues)
- [Creating a New GitHub Issue](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Creating-a-New-GitHub-Issue)
- [Select Issues for Contributing Using Labels](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Select-Issues-for-Contributing-Using-Labels)
- [How to clone the FreeCodeCamp website on a Windows pc](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/How-to-clone-the-FreeCodeCamp-website-on-a-Windows-pc)
- [How to log in to your local FCC site - using GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/How-To-Log-In-To-Your-Local-FCC-Site)
- [Contributions Guide - With a demo on fixing a typo](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Contributions-Guide---with-Typo-Demo)
- [Writing great git commit message](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Writing-great-git-commit-message)
- [Select Issues for Contributing Using Labels](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Issue-Labels)
- [How to clone the FreeCodeCamp website on a Windows pc](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Fork-Windows)
- [How to log in to your local FCC site - using GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Log-In-To-Local-Instance)
- [Writing great git commit message](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Git-Commit-Message)
- [Contributor Chat Support - For the FCC Repositories, and running a local instance] (https://gitter.im/FreeCodeCamp/HelpContributors)

View File

@ -150,6 +150,11 @@ window.common = (function(global) {
window.ga('send', 'event', 'Challenge', 'load', common.gaName);
}
$('.modal').on('show.bs.modal', function() {
$('.gitter-chat-embed, .wiki-aside, .map-aside')
.addClass('is-collapsed');
});
$('#complete-courseware-dialog').on('hidden.bs.modal', function() {
if (common.editor.focus) {
common.editor.focus();
@ -176,6 +181,15 @@ window.common = (function(global) {
$('#complete-courseware-dialog').modal('show');
});
$('#show-solution').on('click', function() {
$('#complete-courseware-dialog').modal('hide');
$('#nav-wiki-btn').click();
});
$('#challenge-help-btn').on('click', function() {
$('.wiki-aside, .map-aside, #chat-embed-main').addClass('is-collapsed');
});
$('#help-ive-found-a-bug-wiki-article').on('click', function() {
window.open(
'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' +

View File

@ -39,6 +39,9 @@ window.common = (function(global) {
codeOutput.setSize('100%', '100%');
common.updateOutputDisplay = function updateOutputDisplay(str = '') {
if (typeof str === 'function') {
str = str.toString();
}
if (typeof str !== 'string') {
str = JSON.stringify(str);
}

View File

@ -1155,6 +1155,12 @@ code {
}
}
// make btn-default hover more visible
.btn-default:active,
.btn-default:hover {
background-color: @brand-primary;
}
@import "chat.less";
@import "jobs.less";
@import "challenge.less";

View File

@ -90,10 +90,8 @@
}
.wiki-fixed-header {
position: fixed;
background: white;
padding-top: 5px;
width: 100%;
background: linear-gradient(180deg,#fff 0,#fff 70%,hsla(0,0%,100%,0));
padding: 5px 0 18px 0;
z-index: 1;
left: 0;
top: 0;

View File

@ -76,7 +76,6 @@ export class Lecture extends React.Component {
<Row>
<Youtube
id='player_1'
onEnd={ toggleQuestionView }
onError={ this.handleError }
videoId={ id } />
</Row>

View File

@ -4,12 +4,14 @@ import moment from 'moment';
import dedent from 'dedent';
import debugFactory from 'debug';
import { isEmail } from 'validator';
import path from 'path';
import { saveUser, observeMethod } from '../../server/utils/rx';
import { blacklistedUsernames } from '../../server/utils/constants';
const debug = debugFactory('fcc:user:remote');
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
const isDev = process.env.NODE_ENV !== 'production';
function getAboutProfile({
username,
@ -89,7 +91,7 @@ module.exports = function(User) {
'You\'re email has been confirmed!'
]
});
ctx.res.redirect('/email-signin');
ctx.res.redirect('/');
});
User.beforeRemote('create', function({ req, res }, _, next) {
@ -323,24 +325,90 @@ module.exports = function(User) {
);
User.prototype.updateEmail = function updateEmail(email) {
const fiveMinutesAgo = moment().subtract(5, 'minutes');
const lastEmailSentAt = moment(new Date(this.emailVerifyTTL || null));
const ownEmail = email === this.email;
const isWaitPeriodOver = this.emailVerifyTTL ?
lastEmailSentAt.isBefore(fiveMinutesAgo) :
true;
if (!isEmail(email)) {
return Promise.reject(
new Error('The submitted email not valid')
new Error('The submitted email not valid.')
);
}
if (this.email && this.email === email) {
// email is already associated and verified with this account
if (ownEmail && this.emailVerified) {
return Promise.reject(new Error(
`${email} is already associated with this account.`
));
}
if (ownEmail && !isWaitPeriodOver) {
const minutesLeft = 5 -
(moment().minutes() - lastEmailSentAt.minutes());
const timeToWait = minutesLeft ?
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
'a few seconds';
return Promise.reject(new Error(
`Please wait ${timeToWait} to resend email verification.`
));
}
return User.doesExist(null, email)
.then(exists => {
if (exists) {
// not associated with this account, but is associated with another
if (!ownEmail && exists) {
return Promise.reject(
new Error(`${email} is already associated with another account.`)
);
}
return this.update$({ email }).toPromise();
const emailVerified = false;
return this.update$({
email,
emailVerified,
emailVerifyTTL: new Date()
})
.do(() => {
this.email = email;
this.emailVerified = emailVerified;
this.emailVerifyTTL = new Date();
})
.flatMap(() => {
var mailOptions = {
type: 'email',
to: email,
from: 'Team@freecodecamp.com',
subject: 'Welcome to Free Code Camp!',
protocol: isDev ? null : 'https',
host: isDev ? 'localhost' : 'freecodecamp.com',
port: isDev ? null : 443,
template: path.join(
__dirname,
'..',
'..',
'server',
'views',
'emails',
'user-email-verify.ejs'
)
};
return this.verify(mailOptions);
})
.map(() => dedent`
Please check your email.
We sent you a link that you can click to verify your email address.
`)
.catch(error => {
debug(error);
return Observable.throw(
'Oops, something went wrong, please try again later.'
);
})
.toPromise();
});
};
@ -358,8 +426,8 @@ module.exports = function(User) {
],
returns: [
{
arg: 'status',
type: 'object'
arg: 'message',
type: 'string'
}
],
http: {
@ -486,52 +554,11 @@ module.exports = function(User) {
}
);
User.prototype.updateEmail = function updateEmail(email) {
if (this.email && this.email === email) {
return Promise.reject(new Error(
`${email} is already associated with this account.`
));
}
return User.doesExist(null, email)
.then(exists => {
if (exists) {
return Promise.reject(
new Error(`${email} is already associated with another account.`)
);
}
return this.update$({ email }).toPromise();
});
};
User.remoteMethod(
'updateEmail',
{
isStatic: false,
description: 'updates the email of the user object',
accepts: [
{
arg: 'email',
type: 'string',
required: true
}
],
returns: [
{
arg: 'status',
type: 'object'
}
],
http: {
path: '/update-email',
verb: 'POST'
}
}
);
User.themes = {
night: true,
default: true
};
User.prototype.updateTheme = function updateTheme(theme) {
if (!this.constructor.themes[theme]) {
const err = new Error(

View File

@ -16,6 +16,9 @@
}
}
},
"emailVerifyTTL": {
"type": "date"
},
"password": {
"type": "string"
},

View File

@ -130,6 +130,7 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; }
var ignore = {};
var pushonly = {};
var labelPostion = null;
var labelIndex = -1;
function insertReset(lineNum, line, matchPosition) {
// recompile the line with the reset **just** before the actual loop
@ -179,6 +180,7 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; }
if (directlyBeforeLoop(index, lineNum, lines)) {
DEBUG && debug('- found a label: "' + labelMatch[0] + '"'); // jshint ignore:line
labelPostion = lineNum;
labelIndex = index;
} else {
DEBUG && debug('- ignored "label", false positive'); // jshint ignore:line
}
@ -309,10 +311,11 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; }
DEBUG && debug('- reset inserted above matched label on line ' + labelPostion); // jshint ignore:line
if (recompiled[labelPostion] === undefined) {
labelPostion--;
matchPosition = 0;
labelIndex = 0;
}
recompiled[labelPostion] = insertReset(printLineNumber, recompiled[labelPostion], matchPosition);
recompiled[labelPostion] = insertReset(printLineNumber, recompiled[labelPostion], labelIndex);
labelPostion = null;
labelIndex = -1;
}
}

View File

@ -269,7 +269,7 @@
"Convert a date range consisting of two dates formatted as <code>YYYY-MM-DD</code> into a more readable format.",
"The friendly display should use month names instead of numbers and ordinal dates instead of cardinal (<code>1st</code> instead of <code>1</code>).",
"Do not display information that is redundant or that can be inferred by the user: if the date range ends in <em>less than a year</em> from when it begins, do not display the <em>ending year</em>.",
"Additionally, if the date range begins in the <em>current year</em> and ends within one year, the year should not be displayed at the <em>beginning</em> of the friendly range.",
"Additionally, if the date range begins in the <em>current year</em> (i.e. it is currently the year 2016) and ends within one year, the year should not be displayed at the <em>beginning</em> of the friendly range.",
"If the range ends in the <em>same month</em> that it begins, do not display the <em>ending year or month</em>.",
"Examples:",
"<code>makeFriendlyDates([\"2016-07-01\", \"2016-07-04\"])</code> should return <code>[\"July 1st\",\"4th\"]</code>",

View File

@ -362,7 +362,7 @@
"id": "afcc8d540bea9ea2669306b6",
"title": "Repeat a string repeat a string",
"description": [
"Repeat a given string (first argument) <code>num</code> times (second argument). Return an empty string if <code>num</code> is a negative number.",
"Repeat a given string (first argument) <code>num</code> times (second argument). Return an empty string if <code>num</code> is not a positive number.",
"Remember to use <a href=\"//github.com/FreeCodeCamp/freecodecamp/wiki/FreeCodeCamp-Get-Help\" target=\"_blank\">Read-Search-Ask</a> if you get stuck. Write your own code."
],
"challengeSeed": [

View File

@ -643,7 +643,7 @@
],
"tests": [
"assert(remainder === 2, 'message: The value of <code>remainder</code> should be <code>2</code>');",
"assert(/\\d+\\s*%\\s*\\d+/.test(code), 'message: You should use the <code>%</code> operator');"
"assert(/var\\s*?remainder\\s*?=\\s*?.*%.*;/.test(code), 'message: You should use the <code>%</code> operator');"
],
"type": "waypoint",
"challengeType": 1,
@ -951,7 +951,7 @@
"<code>Alan said, \"Peter is learning JavaScript\".</code>",
"<h4>Instructions</h4>",
"Use <dfn>backslashes</dfn> to assign a string to the <code>myStr</code> variable so that <em>if</em> you were to print it to the console, you would see:",
"<code>I am a \"double quoted\" string inside \"double quotes\"</code>"
"<code>I am a \"double quoted\" string inside \"double quotes\".</code>"
],
"releasedOn": "January 1, 2016",
"challengeSeed": [
@ -969,11 +969,11 @@
"})();"
],
"solutions": [
"var myStr = \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\"\";"
"var myStr = \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\".\";"
],
"tests": [
"assert(code.match(/\\\\\"/g).length === 4 && code.match(/[^\\\\]\"/g).length === 2, 'message: You should use two double quotes (<code>&quot;</code>) and four escaped double quotes (<code>&#92;&quot;</code>) ');",
"assert(myStr === \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\"\", 'message: Variable myStr should equal to (<code>I am a \"double quoted\" string inside \"double quotes\"</code>).');"
"assert(code.match(/\\\\\"/g).length === 4 && code.match(/[^\\\\]\"/g).length === 2, 'message: You should use two double quotes (<code>&quot;</code>) and four escaped double quotes (<code>&#92;&quot;</code>).');",
"assert(myStr === \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\".\", 'message: Variable myStr should equal to (<code>I am a \"double quoted\" string inside \"double quotes\".</code>).');"
],
"type": "waypoint",
"challengeType": 1,
@ -986,7 +986,7 @@
"<code>Alan dijo, \"Pedro está aprendiendo JavaScript\".<code>",
"<h4>Instructiones</h4>",
"Usa <dfn>barras invertidas</dfn>para asigar una cadena a la variable <code>myStr</code> de modo que <em>si</em> tu fueras a imprimirla en la consola, tu verías:",
"<code>I am a \"double quoted\" string inside \"double quotes\"</code>"
"<code>I am a \"double quoted\" string inside \"double quotes\".</code>"
]
},
{
@ -1756,9 +1756,9 @@
"id": "56592a60ddddeae28f7aa8e1",
"title": "Access Multi-Dimensional Arrays With Indexes",
"description": [
"One way to think of a <dfn>multi-dimensional</dfn> array, is as an <em>array of arrays</em>. When you use brackets to access your array, the first set of bracket refers to the entries in the outer-most array, and each subsequent level of brackets refers to the next level of entries inside.",
"One way to think of a <dfn>multi-dimensional</dfn> array, is as an <em>array of arrays</em>. When you use brackets to access your array, the first set of bracket refers to the entries in the outer-most (the first level) array, and each additional pair of brackets refers to the next level of entries inside.",
"<strong>Example</strong>",
"<blockquote>var arr = [<br> [1,2,3],<br> [4,5,6],<br> [7,8,9],<br> [[10,11,12], 13, 14]<br>];<br>arr[0]; // equals [1,2,3]<br>arr[1][2]; // equals 6<br>arr[3][0][1]; // equals 11</blockquote>",
"<blockquote>var arr = [<br> [1,2,3],<br> [4,5,6],<br> [7,8,9],<br> [[10,11,12], 13, 14]<br>];<br>arr[3]; // equals [[10,11,12], 13, 14]<br>arr[3][0]; // equals [10,11,12]<br>arr[3][0][1]; // equals 11</blockquote>",
"<h4>Instructions</h4>",
"Using bracket notation select an element from <code>myArray</code> such that <code>myData</code> is equal to <code>8</code>."
],
@ -4002,7 +4002,8 @@
"assert(typeof playerNumber === 'number', 'message: <code>playerNumber</code> should be a number');",
"assert(typeof player === 'string', 'message: The variable <code>player</code> should be a string');",
"assert(player === 'Montana', 'message: The value of <code>player</code> should be \"Montana\"');",
"assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code),'message: You should use bracket notation to access <code>testObj</code>');"
"assert(/testObj\\s*?\\[.*?\\]/.test(code),'message: You should use bracket notation to access <code>testObj</code>');",
"assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code),'message: You should be using the variable <code>playerNumber</code> in your bracket notation');"
],
"type": "waypoint",
"challengeType": 1,
@ -4550,10 +4551,11 @@
"(function(x) { return \"collection = \\n\" + JSON.stringify(x, '\\n', 2); })(collection);"
],
"solutions": [
"var collection = {\n 2548: {\n album: \"Slippery When Wet\",\n artist: \"Bon Jovi\",\n tracks: [ \n \"Let It Rock\", \n \"You Give Love a Bad Name\" \n ]\n },\n 2468: {\n album: \"1999\",\n artist: \"Prince\",\n tracks: [ \n \"1999\", \n \"Little Red Corvette\" \n ]\n },\n 1245: {\n artist: \"Robert Palmer\",\n tracks: [ ]\n },\n 5439: {\n album: \"ABBA Gold\"\n }\n};\n// Keep a copy of the collection for tests\nvar collectionCopy = JSON.parse(JSON.stringify(collection));\n\n// Only change code below this line\nfunction updateRecords(id, prop, value) {\n if(value !== \"\") {\n if(prop === \"tracks\") {\n collection[id][prop].push(value);\n } else {\n collection[id][prop] = value;\n }\n } else {\n delete collection[id][prop];\n }\n\n return collection;\n}"
"var collection = {\n 2548: {\n album: \"Slippery When Wet\",\n artist: \"Bon Jovi\",\n tracks: [ \n \"Let It Rock\", \n \"You Give Love a Bad Name\" \n ]\n },\n 2468: {\n album: \"1999\",\n artist: \"Prince\",\n tracks: [ \n \"1999\", \n \"Little Red Corvette\" \n ]\n },\n 1245: {\n artist: \"Robert Palmer\",\n tracks: [ ]\n },\n 5439: {\n album: \"ABBA Gold\"\n }\n};\n// Keep a copy of the collection for tests\nvar collectionCopy = JSON.parse(JSON.stringify(collection));\n\n// Only change code below this line\nfunction updateRecords(id, prop, value) {\n if(value !== \"\") {\n if(prop === \"tracks\") {\n collection[id][prop]= collection[id][prop] || [];\n collection[id][prop].push(value);\n } else {\n collection[id][prop] = value;\n }\n } else {\n delete collection[id][prop];\n }\n\n return collection;\n}"
],
"tests": [
"collection = collectionCopy; assert(updateRecords(5439, \"artist\", \"ABBA\")[5439][\"artist\"] === \"ABBA\", 'message: After <code>updateRecords(5439, \"artist\", \"ABBA\")</code>, <code>artist</code> should be <code>\"ABBA\"</code>');",
"assert(updateRecords(5439, \"tracks\", \"Take a Chance on Me\")[5439][\"tracks\"].pop() === \"Take a Chance on Me\", 'message: After <code>updateRecords(5439, \"tracks\", \"Take a Chance on Me\")</code>, <code>tracks</code> should have <code>\"Take a Chance on Me\"</code> as the last element.');",
"updateRecords(2548, \"artist\", \"\"); assert(!collection[2548].hasOwnProperty(\"artist\"), 'message: After <code>updateRecords(2548, \"artist\", \"\")</code>, <code>artist</code> should not be set');",
"assert(updateRecords(1245, \"tracks\", \"Addicted to Love\")[1245][\"tracks\"].pop() === \"Addicted to Love\", 'message: After <code>updateRecords(1245, \"tracks\", \"Addicted to Love\")</code>, <code>tracks</code> should have <code>\"Addicted to Love\"</code> as the last element.');",
"updateRecords(2548, \"tracks\", \"\"); assert(!collection[2548].hasOwnProperty(\"tracks\"), 'message: After <code>updateRecords(2548, \"tracks\", \"\")</code>, <code>tracks</code> should not be set');"

View File

@ -192,7 +192,7 @@
"<strong>User Story:</strong> I can see thumbnail images of different projects the portfolio creator has built (if you haven't built any websites before, use placeholders.)",
"<strong>User Story:</strong> I navigate to different sections of the webpage by clicking buttons in the navigation.",
"Don't worry if you don't have anything to showcase on your portfolio yet - you will build several apps on the next few CodePen challenges, and can come back and update your portfolio later.",
"There are many great portfolio templates out there, but for this challenge, you'll need to build a portfolio page yourself. Using Bootstrap will make this much easier for you.",
"There are many great portfolio templates out there already. However, you should consider building your portfolio page as much as you can from the ground up. Using Bootstrap can help make this process much easier for you.",
"Note that CodePen.io overrides the Window.open() function, so if you want to open windows using jQuery, you will need to target invisible anchor elements like this one: <code>&lt;a target='_blank'&gt;</a></code>.",
"Remember to use <a href='//github.com/FreeCodeCamp/freecodecamp/wiki/FreeCodeCamp-Get-Help' target='_blank'>Read-Search-Ask</a> if you get stuck.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. ",

View File

@ -78,7 +78,7 @@
"title": "JavaScript Lingo: Variables & camelCase",
"description": [
"We are going to cover what constitutes a variable, and the reasoning behind camelCase.",
"A variable, also referred to as 'var', is the name or placeholder for a boolean, string, number, or other piece of static information.",
"A variable also referred to as 'var', is the name or placeholder for a boolean, string, number, or another piece of static information.",
"You can use Google Dev Tools to inspect the Free Code Camp home page and look for some variables.",
"You 'declare' variables the first time with 'var' in front of it, but those can be referenced later in your script.",
"camelCase is the way that JavaScript pushes words together and still keeps them legible. The first letter of the first word is lowercase, along with the remainder of the word, but the first letter of every consecutive word is capitalized. There are no spaces. Examples: brianaLovesHerPets, bestFoodIsCheese, and codeIsWorthLearning.",
@ -152,7 +152,7 @@
"id": "56b15f15632298c12f315189",
"title": "JavaScript Lingo: Finding and Indexing Data in Arrays",
"description": [
"There are many reasons you might need to access a certain piece of data from within a larger set, and you do that by referencing it's index.",
"There are many reasons you might need to access a certain piece of data from within a larger set, and you do that by referencing its index.",
"We won't get into syntax now, but you should know that the first thing in an array is actually index 0.",
"This goes for strings and objects, too. All of these indices start at 0, so if you're looking asking the code to find indexArr[2], you're really going to get the third piece of information in that array."
],
@ -296,7 +296,7 @@
"description": [
"RegExp is not formatted like anything else in JS, and can have a steep learning curve.",
"RegExp can also be an incredibly useful and efficient tool.",
"Using RegExp, you can match, replace, search, and split a string, one of the more difficult types of values to manipulate.",
"Using RegExp, you can match, replace, search, and split a string, one of the most difficult types of values to manipulate.",
"Like with all of the other videos, we won't get into the nitty gritty, but I want to show you a few examples of where RegExp is useful.",
"If you wanted to create a registration page that verified passwords contained at least a number and a capital letter, you could use RegExp.",
"If you wanted to ensure that dates entered in a page were all valid dates in the future, you could use RegExp.",

View File

@ -1,8 +1,10 @@
import { Observable } from 'rx';
import debugFactory from 'debug';
import { isEmail } from 'validator';
import path from 'path';
const debug = debugFactory('fcc:user:remote');
const isDev = process.env.NODE_ENV !== 'production';
function destroyAllRelated(id, Model) {
return Observable.fromNodeCallback(
@ -15,7 +17,6 @@ module.exports = function(app) {
var User = app.models.User;
var UserIdentity = app.models.UserIdentity;
var UserCredential = app.models.UserCredential;
var Email = app.models.Email;
User.observe('before delete', function(ctx, next) {
debug('removing user', ctx.where);
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
@ -70,21 +71,21 @@ module.exports = function(app) {
to: user.email,
from: 'Team@freecodecamp.com',
subject: 'Welcome to Free Code Camp!',
redirect: '/',
text: [
'Greetings from San Francisco!\n\n',
'Thank you for joining our community.\n',
'Feel free to email us at this address if you have ',
'any questions about Free Code Camp.\n',
'And if you have a moment, check out our blog: ',
'medium.freecodecamp.com.\n\n',
'Good luck with the challenges!\n\n',
'- the Free Code Camp Team'
].join('')
protocol: isDev ? null : 'https',
host: isDev ? 'localhost' : 'freecodecamp.com',
port: isDev ? null : 443,
template: path.join(
__dirname,
'..',
'views',
'emails',
'a-extend-user-welcome.ejs'
),
redirect: '/'
};
debug('sending welcome email');
return Email.send(mailOptions, function(err) {
return user.verify(mailOptions, function(err) {
if (err) { return next(err); }
return req.logIn(user, function(err) {
if (err) { return next(err); }

View File

@ -18,7 +18,8 @@ import {
import { observeMethod } from '../utils/rx';
import {
ifNoUserSend
ifNoUserSend,
flashIfNotVerified
} from '../utils/middleware';
import getFromDisk$ from '../utils/getFromDisk$';
@ -419,7 +420,10 @@ module.exports = function(app) {
redirectToNextChallenge
);
router.get('/challenges/:challengeName', showChallenge);
router.get('/challenges/:challengeName',
flashIfNotVerified,
showChallenge
);
app.use(router);

View File

@ -19,6 +19,8 @@ import {
calcLongestStreak
} from '../utils/user-stats';
import { flashIfNotVerified } from '../utils/middleware';
const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
const certIds = {
@ -183,6 +185,7 @@ module.exports = function(app) {
router.get(
'/settings',
sendNonUserToMap,
flashIfNotVerified,
getSettings
);
router.get('/vote1', vote1);

View File

@ -28,3 +28,19 @@ export function ifNoUser401(req, res, next) {
}
return res.status(401).end();
}
export function flashIfNotVerified(req, res, next) {
const user = req.user;
if (!user) {
return next();
}
const email = req.user.email;
const emailVerified = req.user.emailVerified;
if (!email || !emailVerified) {
req.flash('info', {
msg: 'Please verify your email address ' +
'<a href="/update-email">here.</a>'
});
}
return next();
}

View File

@ -15,8 +15,7 @@ block content
.form-group
input.input-lg.form-control(type='email', name='email', id='email', value=user.email || '', placeholder=user.email || 'Enter your new email', autofocus, required, autocomplete="off")
.form-group
button.btn.btn-lg.btn-primary.btn-block(type='submit')
| Update my Email
button.btn.btn-lg.btn-primary.btn-block(type='submit')= !user.email || user.emailVerified ? 'Update my Email' : 'Verify Email'
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings')
| Go back to Settings
@ -46,10 +45,10 @@ block content
}
})
.done(data =>{
if(data.status && data.status.count){
$('#flash-content').html("Your email has been updated successfully!");
if(data && data.message){
$('#flash-content').html(data.message);
$('#flash-board')
.removeClass('alert-danger')
.removeClass('alert-info')
.addClass('alert-success')
.fadeIn();
}

View File

@ -71,6 +71,7 @@ block content
if pledge
form.row(name='stop-pledge' action='/commit/stop-commitment' method='post')
input(type='hidden', name='_csrf', value=_csrf)
.col-xs-12.col-sm-6.col-sm-offset-3.text-center
.button-spacer
button.btn.btn-block.btn-lg.btn-default(name='submit' type='submit') Stop my current pledge

View File

@ -0,0 +1,23 @@
Greetings from San Francisco!
<br>
<br>
Thank you for joining our community.
<br>
<br>
Please verify your email by following the link below:
<br>
<br>
<a href="<%= verifyHref %>"><%= verifyHref %></a>
<br>
<br>
Feel free to email us at this address if you have any questions about Free Code Camp.
<br>
<br>
And if you have a moment, check out our blog: https://medium.freecodecamp.com.
<br>
<br>
Good luck with the challenges!
<br>
<br>
<br>
- the Free Code Camp Team.

View File

@ -0,0 +1,14 @@
Thank you, for updating you contact details.
<br>
<br>
Please verify your email by following the link below:
<br>
<br>
<a href="<%= verifyHref %>"><%= verifyHref %></a>
<br>
<br>
Feel free to email us at this address if you have any questions about Free Code Camp.
<br>
<br>
<br>
- the Free Code Camp Team.

View File

@ -5,6 +5,8 @@ aside.map-aside.is-collapsed
a.map-aside-action-item.map-aside-action-pop-out(href='/map' target='_blank' aria-label='open map in new tab')
button.map-aside-action-item.map-aside-action-collapse(aria-label='close map aside')
aside.wiki-aside.is-collapsed
.wiki-aside-action-bar
.wiki-aside-action-bar.wiki-fixed-header
.chat-embed-main-title
span Free Code Camp's Wiki
a.wiki-aside-action-item.wiki-aside-action-pop-out(href='/wiki' target='_blank' aria-label='open wiki in new tab')
button.wiki-aside-action-item.wiki-aside-action-collapse(aria-label='close wiki aside')

View File

@ -0,0 +1,15 @@
'use strict';
let loopProtect = require('../../../public/js/lib/loop-protect/loop-protect');
let test = require('tape');
test('LoopProtect injection', function(t) {
t.plan(1);
// Label indented 2 spaces - loop indented three spaces
t.true(
loopProtect(' loop1:\n while(true) {\n\n}').indexOf('loop1') > 0,
'Should keep loop label intact if not lined up with loop.'
);
});