diff --git a/client/commonFramework/step-challenge.js b/client/commonFramework/step-challenge.js index f70bc469bb..97a19754d3 100644 --- a/client/commonFramework/step-challenge.js +++ b/client/commonFramework/step-challenge.js @@ -125,7 +125,7 @@ window.common = (function({ $, common = { init: [] }}) { // assume api returns string when fails return $el.parent() .find('.disabled') - .replaceWith('
' + data + '
'); + .replaceWith('' + data + '
'); }) .fail(function() { console.log('failed'); diff --git a/client/less/main.less b/client/less/main.less index 60320c5d6a..0bd7729021 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -64,7 +64,6 @@ body.top-and-bottom-margins { } body.no-top-and-bottom-margins { - display: none; margin: 75px 20px 0px 20px; } diff --git a/client/main.js b/client/main.js index 05fd4dc69f..1efdb3e0d6 100644 --- a/client/main.js +++ b/client/main.js @@ -165,6 +165,7 @@ main.setMapShare = function setMapShare(id) { $(document).ready(function() { + const { Observable } = window.Rx; var CSRF_HEADER = 'X-CSRF-Token'; var setCSRFToken = function(securityToken) { @@ -548,51 +549,68 @@ $(document).ready(function() { // keyboard shortcuts: open map window.Mousetrap.bind('g m', toggleMap); - // Night Mode - function changeMode() { - var newValue = false; - try { - newValue = !JSON.parse(localStorage.getItem('nightMode')); - } catch (e) { - console.error('Error parsing value form local storage:', 'nightMode', e); - } - localStorage.setItem('nightMode', String(newValue)); - toggleNightMode(newValue); + function addAlert(message = '', type = 'alert-info') { + return $('.flashMessage').append($(` +myVar
should equal 88
');",
"assert(/myVar\\s*\\=.*myVar/.test(code) === false, 'message: myVar = myVar
should be changed');",
- "assert(/myVar\\s*[+]{2}/.test(code), 'message: Use the ++
operator');",
+ "assert(/[+]{2}\\s*myVar|myVar\\s*[+]{2}/.test(code), 'message: Use the ++
operator');",
"assert(/var myVar = 87;/.test(code), 'message: Do not change code above the line');"
],
"type": "waypoint",
@@ -497,7 +497,7 @@
],
"tests": [
"assert(myVar === 10, 'message: myVar
should equal 10
');",
- "assert(/myVar\\s*[-]{2}/.test(code), 'message: Use the --
operator on myVar
');",
+ "assert(/[-]{2}\\s*myVar|myVar\\s*[-]{2}/.test(code), 'message: Use the --
operator on myVar
');",
"assert(/var myVar = 11;/.test(code), 'message: Do not change code above the line');"
],
"type": "waypoint",
diff --git a/seed/challenges/01-front-end-development-certification/bootstrap.json b/seed/challenges/01-front-end-development-certification/bootstrap.json
index 323a88e233..c8ea98fb5e 100644
--- a/seed/challenges/01-front-end-development-certification/bootstrap.json
+++ b/seed/challenges/01-front-end-development-certification/bootstrap.json
@@ -365,9 +365,14 @@
"id": "bad87fee1348cd8acef08812",
"title": "Create a Block Element Bootstrap Button",
"description": [
- "Normally, your button
elements are only as wide as the text that they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space and any elements following it will flow onto a \"new line\" below the block.",
- "This image illustrates the difference between inline
elements and block-level
elements:",
- "button
elements with a class of btn
are only as wide as the text that they contain. For example:",
+ "<button class=\"btn\">Submit</button>
",
+ "This button would only be as wide as the word \"Submit\".",
+ "",
+ "By making them block elements with the additional class of btn-block
, your button will stretch to fill your page's entire horizontal space and any elements following it will flow onto a \"new line\" below the block.",
+ "<button class=\"btn btn-block\">Submit</button>
",
+ "This button would take up 100% of the available width.",
+ "",
"Note that these buttons still need the btn
class.",
"Add Bootstrap's btn-block
class to your Bootstrap button."
],
@@ -440,17 +445,27 @@
"challengeType": 0,
"titleEs": "Crea un elemento botón de bloque con Bootstrap",
"descriptionEs": [
- "Normalmente, tus elementos button
son sólo tan anchos como el texto que contienen. Al convertir un botón en elemento a nivel de bloque, este se estirará para llenar todo el espacio horizontal y cualquier elemento que lo siga se desplazará a una \"nueva línea\" debajo del bloque.",
- "Esta imagen es un ejemplo de la diferencia entre elementos en línea (inline
) y elementos a nivel de bloque (block-level
):",
- "button
con una clase de btn
sólo son tan ancha como el texto que contienen. Por ejemplo:",
+ "<button class=\"btn\">Enviar</button>
",
+ "Este botón sólo sería tan amplia como la palabra \"Enviar\"",
+ "",
+ "Haciéndolos bloquean elementos con la clase adicional de btn-block
, el botón se amplía para llenar toda espacio horizontal de la página y los elementos siguientes fluirá sobre una \"nueva línea\" debajo del bloque .",
+ "<button class=\"btn btn-block\">Enviar</button>
",
+ "Este botón llevaría hasta el 100% de la anchura disponible.",
+ "",
"Ten en cuenta que estos botones todavía necesitan la clase btn
.",
"Agrega la clase de Bootstrap btn-block
a tu botón Bootstrap."
],
"nameFr": "Créer un bouton bloc Bootstrap",
"descriptionFr": [
- "Normalement, vos éléments button
sont aussi large que le texte qu'ils contiennent. En les transformants en éléments blocs, vos boutons vont s'ajuster pour remplir l'intégralité de l'espace horizontal de la page et tous les éléments qui le suivront se placeront sur une \"nouvelle ligne\" en dessous du bloc.",
- "Cette image illustre la différence entre éléments inline
(sans briser la ligne) et éléments block-level
(en blocs)",
- "button
avec une classe de btn
ne sont aussi larges que le texte qu'ils contiennent . Par exemple:",
+ "<button class=\"btn\">Soumettre</button>
",
+ "Ce bouton ne serait plus large que le mot \"Soumettre\" .",
+ "",
+ "En leur faisant bloquer les éléments avec la classe supplémentaire de btn-block
, votre bouton étirer pour remplir tout l'espace horizontal de votre page et tous les éléments suivants, il coulera sur une \"nouvelle ligne\" en dessous du bloc .",
+ "<button class=\"btn btn-block\">Soumettre</button>
",
+ "Ce bouton prendrait 100% de la largeur disponible .",
+ "",
"Notez que ces boutons ont toujours besoin de la classe btn
",
"Ajoutez la classe Bootstrap btn-block
à votre bouton Bootstrap."
]
diff --git a/seed/challenges/01-front-end-development-certification/html5-and-css.json b/seed/challenges/01-front-end-development-certification/html5-and-css.json
index 886b2bf8d5..5719312ee4 100644
--- a/seed/challenges/01-front-end-development-certification/html5-and-css.json
+++ b/seed/challenges/01-front-end-development-certification/html5-and-css.json
@@ -1953,8 +1953,8 @@
"Top 3 things cats hate:
" ], "tests": [ - "assert.equal($(\"ol\").prev().text(), 'Top 3 things cats hate:', 'message: Your should have an ordered list for \"Top 3 things cats hate\"');", - "assert.equal($(\"ul\").prev().text(), \"Things cats love:\", 'message: You should have an unordered list for \"Things Cats Love\"');", + "assert.equal($(\"ol\").prev().text(), 'Top 3 things cats hate:', 'message: You should have an ordered list for \"Top 3 things cats hate:\"');", + "assert.equal($(\"ul\").prev().text(), \"Things cats love:\", 'message: You should have an unordered list for \"Things cats love:\"');", "assert.equal($(\"ul li\").length, 3, 'message: You should have threeli
elements within your ul
element.');",
"assert.equal($(\"ol li\").length, 3, 'message: You should have three li
elements within your ol
element.');",
"assert(code.match(/<\\/ul>/g) && code.match(/<\\/ul>/g).length === code.match(/ul
element has a closing tag.');",
diff --git a/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json b/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json
index 839a72e3f1..48e6dae67b 100644
--- a/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json
+++ b/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json
@@ -481,14 +481,14 @@
"Remember to use Read-Search-Ask if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
- "function uniteUnique(arr1, arr2, arr3) {",
- " return arr1;",
+ "function uniteUnique(arr) {",
+ " return arr;",
"}",
"",
"uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]);"
],
"solutions": [
- "function uniteUnique(arr1, arr2, arr3) {\n return [].slice.call(arguments).reduce(function(a, b) {\n return [].concat(a, b.filter(function(e) {return a.indexOf(e) === -1;}));\n }, []);\n}"
+ "function uniteUnique(arr) {\n return [].slice.call(arguments).reduce(function(a, b) {\n return [].concat(a, b.filter(function(e) {return a.indexOf(e) === -1;}));\n }, []);\n}"
],
"tests": [
"assert.deepEqual(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]), [1, 3, 2, 5, 4], 'message: uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1])
should return [1, 3, 2, 5, 4]
.');",
diff --git a/seed/challenges/01-front-end-development-certification/json-apis-and-ajax.json b/seed/challenges/01-front-end-development-certification/json-apis-and-ajax.json
index 493bad9cb3..ed4c4eba3a 100644
--- a/seed/challenges/01-front-end-development-certification/json-apis-and-ajax.json
+++ b/seed/challenges/01-front-end-development-certification/json-apis-and-ajax.json
@@ -447,7 +447,7 @@
"You will see a prompt to allow or block this site from knowing your current location. The challenge can be completed either way, as long as the code is correct.",
"By selecting allow you will see the text on the output phone change to your latitude and longitude",
"Here's some code that does this:",
- "if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { $(\"#data\").html(\"latitude: \" + position.coords.latitude + \"<br>longitude: \" + position.coords.longitude); });}" + "
if (navigator.geolocation) {" ], "challengeSeed": [ "fccss", @@ -463,7 +463,10 @@ "" ], "tests": [ - "assert(code.match(/navigator\\.geolocation\\.getCurrentPosition/gi), 'message: You should make use of the
navigator.geolocation.getCurrentPosition(function(position) {
$(\"#data\").html(\"latitude: \" + position.coords.latitude + \"<br>longitude: \" + position.coords.longitude);
});
}
navigator.geolocation
to access the users current location.');"
+ "assert(code.match(/navigator\\.geolocation\\.getCurrentPosition/gi), 'message: You should make use of navigator.geolocation
to access the users current location.');",
+ "assert(code.match(/position\\.coords\\.latitude/gi), 'message: You should make use of position.coords.latitude
to display the users' latitudinal location.');",
+ "assert(code.match(/position\\.coords\\.longitude/gi), 'message: You should make use of position.coords.longitude
to display the users' longitudinal location.');",
+ "assert(code.match(/\\$\\(\\s*\"#data\"\\s*\\)\\s*\\.html\\([.\\w\\W]*?\\);/gi), 'message: You should display the users' position within the data
div element');"
],
"type": "waypoint",
"challengeType": 0,
diff --git a/server/boot/a-extendUser.js b/server/boot/a-extendUser.js
index e43cdeff2b..932bd7dcdb 100644
--- a/server/boot/a-extendUser.js
+++ b/server/boot/a-extendUser.js
@@ -1,5 +1,6 @@
import { Observable } from 'rx';
import debugFactory from 'debug';
+import { isEmail } from 'validator';
const debug = debugFactory('fcc:user:remote');
@@ -59,7 +60,7 @@ module.exports = function(app) {
// send welcome email to new camper
User.afterRemote('create', function({ req, res }, user, next) {
debug('user created, sending email');
- if (!user.email) { return next(); }
+ if (!user.email || !isEmail(user.email)) { return next(); }
const redirect = req.session && req.session.returnTo ?
req.session.returnTo :
'/';
diff --git a/server/boot/user.js b/server/boot/user.js
index 989a37edd6..685a2aed5c 100644
--- a/server/boot/user.js
+++ b/server/boot/user.js
@@ -550,6 +550,7 @@ module.exports = function(app) {
}
function postForgot(req, res) {
+ req.validate('email', 'Email format is not valid').isEmail();
const errors = req.validationErrors();
const email = req.body.email.toLowerCase();
diff --git a/server/middlewares/global-locals.js b/server/middlewares/global-locals.js
index 1871eb95ff..aa2d7e32d0 100644
--- a/server/middlewares/global-locals.js
+++ b/server/middlewares/global-locals.js
@@ -6,6 +6,10 @@ export default function globalLocals() {
if (req.csrfToken) {
res.expose({ token: res.locals._csrf }, 'csrf');
}
+ res.locals.theme = req.user && req.user.theme ||
+ req.cookies.theme ||
+ 'default';
+
next();
};
}
diff --git a/server/utils/middleware.js b/server/utils/middleware.js
index 1e5243f5da..458168bac3 100644
--- a/server/utils/middleware.js
+++ b/server/utils/middleware.js
@@ -6,7 +6,7 @@ export function ifNoUserRedirectTo(url, message, type = 'errors') {
}
req.flash(type, {
- msg: message || `You must be signed to go to ${path}`
+ msg: message || `You must be signed in to access ${path}`
});
return res.redirect(url);
diff --git a/server/views/account/settings.jade b/server/views/account/settings.jade
index c992ece48e..e904dbc413 100644
--- a/server/views/account/settings.jade
+++ b/server/views/account/settings.jade
@@ -5,7 +5,7 @@ block content
h2.text-center Actions
.row
.col-xs-12
- a.btn.btn-lg.btn-block.btn-primary.btn-link-social(class = "nightMode-btn") Night Mode
+ a#night-mode.btn.btn-lg.btn-block.btn-primary.btn-link-social Night Mode
.row
.col-xs-12
if (!user.isGithubCool)
@@ -133,4 +133,4 @@ block content
form(action='/account/delete', method='POST')
input(type='hidden', name='_csrf', value=_csrf)
button.btn.btn-danger.btn-block(type='submit')
- | I am 100% sure I want to delete my account and all of my progress
\ No newline at end of file
+ | I am 100% sure I want to delete my account and all of my progress
diff --git a/server/views/challenges/showStep.jade b/server/views/challenges/showStep.jade
index d276c836c8..732c0fb988 100644
--- a/server/views/challenges/showStep.jade
+++ b/server/views/challenges/showStep.jade
@@ -19,12 +19,12 @@ block content
if index === 0
.col-sm-4.hidden-xs
else
- .btn.btn-primary.btn-primary-ghost.col-sm-4.col-xs-12.challenge-step-btn-prev.btn-lg(id='#{index - 1}') Go to my previous step
+ button.btn.btn-primary.btn-primary-ghost.col-sm-4.col-xs-12.challenge-step-btn-prev.btn-lg(id='#{index - 1}') Go to my previous step
.challenge-step-counter.large-p.col-sm-4.col-xs-12.text-center (#{index + 1} / #{description.length})
if index + 1 === description.length
- .btn.btn-primary.col-sm-4.col-xs-12.challenge-step-btn-finish.btn-lg(id='last' class=step[3] && !isCompleted ? 'disabled' : '') Finish challenge
+ button.btn.btn-primary.col-sm-4.col-xs-12.challenge-step-btn-finish.btn-lg(id='last' class=step[3] && !isCompleted ? 'disabled' : '') Finish challenge
else
- .btn.btn-primary.col-sm-4.col-xs-12.challenge-step-btn-next.btn-lg(id='#{index}' class=step[3] && !isCompleted ? 'disabled' : '') Go to my next step
+ button.btn.btn-primary.col-sm-4.col-xs-12.challenge-step-btn-next.btn-lg(id='#{index}' class=step[3] && !isCompleted ? 'disabled' : '') Go to my next step
.clearfix
.spacer
#challenge-step-modal.modal(tabindex='-1')
diff --git a/server/views/layout-wide.jade b/server/views/layout-wide.jade
index 4ffc168436..cf548c3edc 100644
--- a/server/views/layout-wide.jade
+++ b/server/views/layout-wide.jade
@@ -8,9 +8,9 @@ html(lang='en')
include partials/scripts
block content
else
- body.no-top-and-bottom-margins
- include partials/scripts
- include partials/navbar
- include partials/flash
- block content
- include partials/footer
+ body.no-top-and-bottom-margins(class=theme !== 'default' ? theme : '')
+ include partials/scripts
+ include partials/navbar
+ include partials/flash
+ block content
+ include partials/footer
diff --git a/server/views/map/show.jade b/server/views/map/show.jade
index 896032cbad..cf42fab570 100644
--- a/server/views/map/show.jade
+++ b/server/views/map/show.jade
@@ -1,6 +1,6 @@
extends ../layout-wide
block content
- .mapWrapper
+ .mapWrapper
.text-center.map-fixed-header
p Challenges required for certifications are marked with a *
.row.map-buttons
@@ -9,7 +9,7 @@ block content
.input-group
input#map-filter.form-control(type="text" placeholder="Type a challenge name" autocomplete="off" value="")
span.input-group-addon
- i.fa.fa-search
+ i.fa.fa-search
hr
include ../partials/flash
#accordion.map-accordion
@@ -122,3 +122,4 @@ block content
p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #2") Mock Interview #2
p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #3") Mock Interview #3
.spacer
+ include ../partials/flash
diff --git a/server/views/partials/flash.jade b/server/views/partials/flash.jade
index 8668a4174d..8b9fa3e431 100644
--- a/server/views/partials/flash.jade
+++ b/server/views/partials/flash.jade
@@ -1,6 +1,6 @@
.container
.row.flashMessage.negative-30
- .col-xs-12
+ .col-xs-12.col-sm-8.col-sm-offset-2.col-md-6.col-md-offset-3
if (messages.errors || messages.error)
.alert.alert-danger.fade.in
button.close(type='button', data-dismiss='alert')
diff --git a/server/views/partials/scripts.jade b/server/views/partials/scripts.jade
index a002f935eb..988b26bd9f 100644
--- a/server/views/partials/scripts.jade
+++ b/server/views/partials/scripts.jade
@@ -7,5 +7,11 @@ script.
ga('require', 'displayfeatures');
ga('send', 'pageview');
// Leave the below lines alone!
+script.
+ (function(global) {
+ global.main = global.main || {};
+ global.main.isLoggedIn = !{JSON.stringify(!!user)};
+ global.main.userId = !{JSON.stringify(user && user.id || false)};
+ }(window))
script(src=rev('/js', 'vendor-main.js'))
script(src=rev('/js', 'main.js'))