@ -125,7 +125,7 @@ window.common = (function({ $, common = { init: [] }}) {
|
|||||||
// assume api returns string when fails
|
// assume api returns string when fails
|
||||||
return $el.parent()
|
return $el.parent()
|
||||||
.find('.disabled')
|
.find('.disabled')
|
||||||
.replaceWith('<p>' + data + '</p>');
|
.replaceWith('<p class="col-sm-4 col-xs-12">' + data + '</p>');
|
||||||
})
|
})
|
||||||
.fail(function() {
|
.fail(function() {
|
||||||
console.log('failed');
|
console.log('failed');
|
||||||
|
@ -64,7 +64,6 @@ body.top-and-bottom-margins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.no-top-and-bottom-margins {
|
body.no-top-and-bottom-margins {
|
||||||
display: none;
|
|
||||||
margin: 75px 20px 0px 20px;
|
margin: 75px 20px 0px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +165,7 @@ main.setMapShare = function setMapShare(id) {
|
|||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
const { Observable } = window.Rx;
|
||||||
var CSRF_HEADER = 'X-CSRF-Token';
|
var CSRF_HEADER = 'X-CSRF-Token';
|
||||||
|
|
||||||
var setCSRFToken = function(securityToken) {
|
var setCSRFToken = function(securityToken) {
|
||||||
@ -548,51 +549,68 @@ $(document).ready(function() {
|
|||||||
// keyboard shortcuts: open map
|
// keyboard shortcuts: open map
|
||||||
window.Mousetrap.bind('g m', toggleMap);
|
window.Mousetrap.bind('g m', toggleMap);
|
||||||
|
|
||||||
// Night Mode
|
function addAlert(message = '', type = 'alert-info') {
|
||||||
function changeMode() {
|
return $('.flashMessage').append($(`
|
||||||
var newValue = false;
|
<div class='alert ${type}'>
|
||||||
try {
|
<button class='close' type='button', data-dismiss='alert'>
|
||||||
newValue = !JSON.parse(localStorage.getItem('nightMode'));
|
<span class='ion-close-circled' />
|
||||||
} catch (e) {
|
</Button>
|
||||||
console.error('Error parsing value form local storage:', 'nightMode', e);
|
<div>${message}</div>
|
||||||
}
|
</div>
|
||||||
localStorage.setItem('nightMode', String(newValue));
|
`));
|
||||||
toggleNightMode(newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleNightMode(nightModeEnabled) {
|
function toggleNightMode() {
|
||||||
var iframe = document.getElementById('map-aside-frame');
|
if (!main.userId) {
|
||||||
if (iframe) {
|
return addAlert('You must be logged in to use our themes.');
|
||||||
iframe.src = iframe.src;
|
|
||||||
}
|
}
|
||||||
var body = $('body');
|
const iframe$ = document.getElementById('map-aside-frame');
|
||||||
body.hide();
|
const body$ = $('body');
|
||||||
if (nightModeEnabled) {
|
if (iframe$) {
|
||||||
body.addClass('night');
|
iframe$.src = iframe$.src;
|
||||||
|
}
|
||||||
|
body$.hide();
|
||||||
|
let updateThemeTo;
|
||||||
|
if (body$.hasClass('night')) {
|
||||||
|
body$.removeClass('night');
|
||||||
|
updateThemeTo = 'default';
|
||||||
} else {
|
} else {
|
||||||
body.removeClass('night');
|
body$.addClass('night');
|
||||||
|
updateThemeTo = 'night';
|
||||||
}
|
}
|
||||||
body.fadeIn('100');
|
body$.fadeIn('100');
|
||||||
|
const options = {
|
||||||
|
url: `/api/users/${main.userId}/update-theme`,
|
||||||
|
type: 'POST',
|
||||||
|
data: { theme: updateThemeTo },
|
||||||
|
dataType: 'json'
|
||||||
|
};
|
||||||
|
return $.ajax(options)
|
||||||
|
.success(() => console.log('theme updated successfully'))
|
||||||
|
.fail(err => {
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(err.responseText).error.message;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!message) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return addAlert(message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof localStorage.getItem('nightMode') !== 'undefined') {
|
Observable.merge(
|
||||||
var oldVal = false;
|
Observable.fromEvent($('#night-mode'), 'click'),
|
||||||
try {
|
Observable.create(observer => {
|
||||||
oldVal = JSON.parse(localStorage.getItem('nightMode'));
|
window.Mousetrap.bind('g t n', () => observer.onNext());
|
||||||
} catch (e) {
|
})
|
||||||
console.error('Error parsing value form local storage:', 'nightMode', e);
|
)
|
||||||
}
|
.debounce(500)
|
||||||
toggleNightMode(oldVal);
|
.subscribe(toggleNightMode, err => console.error(err));
|
||||||
$('.nightMode-btn').on('click', function() {
|
|
||||||
changeMode();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('nightMode', 'false');
|
|
||||||
toggleNightMode('false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hot Keys
|
// Hot Keys
|
||||||
window.Mousetrap.bind('g t n', changeMode);
|
|
||||||
window.Mousetrap.bind('g n n', () => {
|
window.Mousetrap.bind('g n n', () => {
|
||||||
// Next Challenge
|
// Next Challenge
|
||||||
window.location = '/challenges/next-challenge';
|
window.location = '/challenges/next-challenge';
|
||||||
|
@ -3,6 +3,7 @@ import uuid from 'node-uuid';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
|
import { isEmail } from 'validator';
|
||||||
|
|
||||||
import { saveUser, observeMethod } from '../../server/utils/rx';
|
import { saveUser, observeMethod } from '../../server/utils/rx';
|
||||||
import { blacklistedUsernames } from '../../server/utils/constants';
|
import { blacklistedUsernames } from '../../server/utils/constants';
|
||||||
@ -62,6 +63,9 @@ module.exports = function(User) {
|
|||||||
|
|
||||||
User.observe('before save', function({ instance: user }, next) {
|
User.observe('before save', function({ instance: user }, next) {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
if (user.email && !isEmail(user.email)) {
|
||||||
|
return next(new Error('Email format is not valid'));
|
||||||
|
}
|
||||||
user.username = user.username.trim().toLowerCase();
|
user.username = user.username.trim().toLowerCase();
|
||||||
user.email = typeof user.email === 'string' ?
|
user.email = typeof user.email === 'string' ?
|
||||||
user.email.trim().toLowerCase() :
|
user.email.trim().toLowerCase() :
|
||||||
@ -75,7 +79,7 @@ module.exports = function(User) {
|
|||||||
user.progressTimestamps.push({ timestamp: Date.now() });
|
user.progressTimestamps.push({ timestamp: Date.now() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('setting up user hooks');
|
debug('setting up user hooks');
|
||||||
@ -93,6 +97,9 @@ module.exports = function(User) {
|
|||||||
if (!req.body.email) {
|
if (!req.body.email) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
if (!isEmail(req.body.email)) {
|
||||||
|
return next(new Error('Email format is not valid'));
|
||||||
|
}
|
||||||
return User.doesExist(null, req.body.email)
|
return User.doesExist(null, req.body.email)
|
||||||
.then(exists => {
|
.then(exists => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
@ -118,6 +125,10 @@ module.exports = function(User) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
User.on('resetPasswordRequest', function(info) {
|
User.on('resetPasswordRequest', function(info) {
|
||||||
|
if (!isEmail(info.email)) {
|
||||||
|
console.error(new Error('Email format is not valid'));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let url;
|
let url;
|
||||||
const host = User.app.get('host');
|
const host = User.app.get('host');
|
||||||
const { id: token } = info.accessToken;
|
const { id: token } = info.accessToken;
|
||||||
@ -150,7 +161,7 @@ module.exports = function(User) {
|
|||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
User.app.models.Email.send(mailOptions, function(err) {
|
return User.app.models.Email.send(mailOptions, function(err) {
|
||||||
if (err) { console.error(err); }
|
if (err) { console.error(err); }
|
||||||
debug('email reset sent');
|
debug('email reset sent');
|
||||||
});
|
});
|
||||||
@ -159,9 +170,12 @@ module.exports = function(User) {
|
|||||||
User.beforeRemote('login', function(ctx, notUsed, next) {
|
User.beforeRemote('login', function(ctx, notUsed, next) {
|
||||||
const { body } = ctx.req;
|
const { body } = ctx.req;
|
||||||
if (body && typeof body.email === 'string') {
|
if (body && typeof body.email === 'string') {
|
||||||
|
if (!isEmail(body.email)) {
|
||||||
|
return next(new Error('Email format is not valid'));
|
||||||
|
}
|
||||||
body.email = body.email.toLowerCase();
|
body.email = body.email.toLowerCase();
|
||||||
}
|
}
|
||||||
next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
User.afterRemote('login', function(ctx, accessToken, next) {
|
User.afterRemote('login', function(ctx, accessToken, next) {
|
||||||
@ -216,7 +230,7 @@ module.exports = function(User) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
User.doesExist = function doesExist(username, email) {
|
User.doesExist = function doesExist(username, email) {
|
||||||
if (!username && !email) {
|
if (!username && (!email || !isEmail(email))) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
debug('checking existence');
|
debug('checking existence');
|
||||||
@ -309,6 +323,11 @@ module.exports = function(User) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
User.prototype.updateEmail = function updateEmail(email) {
|
User.prototype.updateEmail = function updateEmail(email) {
|
||||||
|
if (!isEmail(email)) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error('The submitted email not valid')
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.email && this.email === email) {
|
if (this.email && this.email === email) {
|
||||||
return Promise.reject(new Error(
|
return Promise.reject(new Error(
|
||||||
`${email} is already associated with this account.`
|
`${email} is already associated with this account.`
|
||||||
@ -467,6 +486,91 @@ 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(
|
||||||
|
'Theme is not valid.'
|
||||||
|
);
|
||||||
|
err.messageType = 'info';
|
||||||
|
err.userMessage = err.message;
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
return this.update$({ theme })
|
||||||
|
.map({ updatedTo: theme })
|
||||||
|
.toPromise();
|
||||||
|
};
|
||||||
|
|
||||||
|
User.remoteMethod(
|
||||||
|
'updateTheme',
|
||||||
|
{
|
||||||
|
isStatic: false,
|
||||||
|
description: 'updates the users chosen theme',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'theme',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
arg: 'status',
|
||||||
|
type: 'object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: '/update-theme',
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// user.updateTo$(updateData: Object) => Observable[Number]
|
// user.updateTo$(updateData: Object) => Observable[Number]
|
||||||
User.prototype.update$ = function update$(updateData) {
|
User.prototype.update$ = function update$(updateData) {
|
||||||
const id = this.getId();
|
const id = this.getId();
|
||||||
|
@ -188,6 +188,10 @@
|
|||||||
},
|
},
|
||||||
"timezone": {
|
"timezone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "default"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"validations": [],
|
"validations": [],
|
||||||
@ -242,6 +246,13 @@
|
|||||||
"principalId": "$owner",
|
"principalId": "$owner",
|
||||||
"permission": "ALLOW",
|
"permission": "ALLOW",
|
||||||
"property": "updateEmail"
|
"property": "updateEmail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"property": "updateTheme"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"methods": []
|
"methods": []
|
||||||
|
@ -453,7 +453,7 @@
|
|||||||
"tests": [
|
"tests": [
|
||||||
"assert(myVar === 88, 'message: <code>myVar</code> should equal <code>88</code>');",
|
"assert(myVar === 88, 'message: <code>myVar</code> should equal <code>88</code>');",
|
||||||
"assert(/myVar\\s*\\=.*myVar/.test(code) === false, 'message: <code>myVar = myVar</code> should be changed');",
|
"assert(/myVar\\s*\\=.*myVar/.test(code) === false, 'message: <code>myVar = myVar</code> should be changed');",
|
||||||
"assert(/myVar\\s*[+]{2}/.test(code), 'message: Use the <code>++</code> operator');",
|
"assert(/[+]{2}\\s*myVar|myVar\\s*[+]{2}/.test(code), 'message: Use the <code>++</code> operator');",
|
||||||
"assert(/var myVar = 87;/.test(code), 'message: Do not change code above the line');"
|
"assert(/var myVar = 87;/.test(code), 'message: Do not change code above the line');"
|
||||||
],
|
],
|
||||||
"type": "waypoint",
|
"type": "waypoint",
|
||||||
@ -497,7 +497,7 @@
|
|||||||
],
|
],
|
||||||
"tests": [
|
"tests": [
|
||||||
"assert(myVar === 10, 'message: <code>myVar</code> should equal <code>10</code>');",
|
"assert(myVar === 10, 'message: <code>myVar</code> should equal <code>10</code>');",
|
||||||
"assert(/myVar\\s*[-]{2}/.test(code), 'message: Use the <code>--</code> operator on <code>myVar</code>');",
|
"assert(/[-]{2}\\s*myVar|myVar\\s*[-]{2}/.test(code), 'message: Use the <code>--</code> operator on <code>myVar</code>');",
|
||||||
"assert(/var myVar = 11;/.test(code), 'message: Do not change code above the line');"
|
"assert(/var myVar = 11;/.test(code), 'message: Do not change code above the line');"
|
||||||
],
|
],
|
||||||
"type": "waypoint",
|
"type": "waypoint",
|
||||||
|
@ -365,9 +365,14 @@
|
|||||||
"id": "bad87fee1348cd8acef08812",
|
"id": "bad87fee1348cd8acef08812",
|
||||||
"title": "Create a Block Element Bootstrap Button",
|
"title": "Create a Block Element Bootstrap Button",
|
||||||
"description": [
|
"description": [
|
||||||
"Normally, your <code>button</code> 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.",
|
"Normally, your <code>button</code> elements with a class of <code>btn</code> are only as wide as the text that they contain. For example:",
|
||||||
"This image illustrates the difference between <code>inline</code> elements and <code>block-level</code> elements:",
|
"<code><button class=\"btn\">Submit</button></code>",
|
||||||
"<a href=\"//i.imgur.com/O32cDWE.png\" data-lightbox=\"img-enlarge\"><img class=\"img-responsive\" src=\"//i.imgur.com/O32cDWE.png\" title=\"Click to enlarge\" alt=\"An \"inline\" button is as small as the text it contains. In this image, it's centered. Below it is a \"block-level\" button, which stretches to fill the entire horizontal space.'></a>",
|
"This button would only be as wide as the word \"Submit\".",
|
||||||
|
"<button class='btn' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Submit</button>",
|
||||||
|
"By making them block elements with the additional class of <code>btn-block</code>, 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.",
|
||||||
|
"<code><button class=\"btn btn-block\">Submit</button></code>",
|
||||||
|
"This button would take up 100% of the available width.",
|
||||||
|
"<button class='btn btn-block' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Submit</button>",
|
||||||
"Note that these buttons still need the <code>btn</code> class.",
|
"Note that these buttons still need the <code>btn</code> class.",
|
||||||
"Add Bootstrap's <code>btn-block</code> class to your Bootstrap button."
|
"Add Bootstrap's <code>btn-block</code> class to your Bootstrap button."
|
||||||
],
|
],
|
||||||
@ -440,17 +445,27 @@
|
|||||||
"challengeType": 0,
|
"challengeType": 0,
|
||||||
"titleEs": "Crea un elemento botón de bloque con Bootstrap",
|
"titleEs": "Crea un elemento botón de bloque con Bootstrap",
|
||||||
"descriptionEs": [
|
"descriptionEs": [
|
||||||
"Normalmente, tus elementos <code>button</code> 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.",
|
"Normalmente , los elementos de <code>button</code> con una clase de <code>btn</code> sólo son tan ancha como el texto que contienen. Por ejemplo:",
|
||||||
"Esta imagen es un ejemplo de la diferencia entre elementos en línea (<code>inline</code>) y elementos a nivel de bloque (<code>block-level</code>):",
|
"<code><button class=\"btn\">Enviar</button></code>",
|
||||||
"<a href=\"//i.imgur.com/O32cDWE.png\" data-lightbox=\"img-enlarge\"><img class=\"img-responsive\" src=\"//i.imgur.com/O32cDWE.png\" title=\"Pulsa para agrandar\" alt=\"Un botón \"en línea\" es tan pequeño como el texto que contiene. En esta imagen, está centrado. Debajo de este hay un botón \"a nivel de bloque\", que se estira hasta llenar todo el espacio horizontal.'></a>",
|
"Este botón sólo sería tan amplia como la palabra \"Enviar\"",
|
||||||
|
"<button class='btn' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Enviar</button>",
|
||||||
|
"Haciéndolos bloquean elementos con la clase adicional de <code>btn-block</code>, 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 .",
|
||||||
|
"<code><button class=\"btn btn-block\">Enviar</button></code>",
|
||||||
|
"Este botón llevaría hasta el 100% de la anchura disponible.",
|
||||||
|
"<button class='btn btn-block' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Enviar</button>",
|
||||||
"Ten en cuenta que estos botones todavía necesitan la clase <code>btn</code>.",
|
"Ten en cuenta que estos botones todavía necesitan la clase <code>btn</code>.",
|
||||||
"Agrega la clase de Bootstrap <code>btn-block</code> a tu botón Bootstrap."
|
"Agrega la clase de Bootstrap <code>btn-block</code> a tu botón Bootstrap."
|
||||||
],
|
],
|
||||||
"nameFr": "Créer un bouton bloc Bootstrap",
|
"nameFr": "Créer un bouton bloc Bootstrap",
|
||||||
"descriptionFr": [
|
"descriptionFr": [
|
||||||
"Normalement, vos éléments <code>button</code> 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.",
|
"Normalement , vos éléments de <code>button</code> avec une classe de <code>btn</code> ne sont aussi larges que le texte qu'ils contiennent . Par exemple:",
|
||||||
"Cette image illustre la différence entre éléments <code>inline</code> (sans briser la ligne) et éléments <code>block-level</code> (en blocs)",
|
"<code><button class=\"btn\">Soumettre</button></code>",
|
||||||
"<a href=\"https://i.imgur.com/O32cDWE.png\" data-lightbox=\"img-enlarge\"><img class=\"img-responsive\" src=\"https://i.imgur.com/O32cDWE.png\" title=\"Cliquez pour agrandir\" alt=\"Un bouton \"inline\" est aussi petit que le texte qu'il contient. Dans cette image, il est centré. En dessous de celui-ci il y a un bouton \"block-level\" , qui s'étire et remplit l'espace horizontal.'></a>",
|
"Ce bouton ne serait plus large que le mot \"Soumettre\" .",
|
||||||
|
"<button class='btn' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Soumettre</button>",
|
||||||
|
"En leur faisant bloquer les éléments avec la classe supplémentaire de <code>btn-block</code>, 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 .",
|
||||||
|
"<code><button class=\"btn btn-block\">Soumettre</button></code>",
|
||||||
|
"Ce bouton prendrait 100% de la largeur disponible .",
|
||||||
|
"<button class='btn btn-block' style='background-color: rgb(0, 100, 0); color: rgb(255, 255, 255);'>Soumettre</button>",
|
||||||
"Notez que ces boutons ont toujours besoin de la classe <code>btn</code>",
|
"Notez que ces boutons ont toujours besoin de la classe <code>btn</code>",
|
||||||
"Ajoutez la classe Bootstrap <code>btn-block</code> à votre bouton Bootstrap."
|
"Ajoutez la classe Bootstrap <code>btn-block</code> à votre bouton Bootstrap."
|
||||||
]
|
]
|
||||||
|
@ -1953,8 +1953,8 @@
|
|||||||
"<p>Top 3 things cats hate:</p>"
|
"<p>Top 3 things cats hate:</p>"
|
||||||
],
|
],
|
||||||
"tests": [
|
"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($(\"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\").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 three <code>li</code> elements within your <code>ul</code> element.');",
|
"assert.equal($(\"ul li\").length, 3, 'message: You should have three <code>li</code> elements within your <code>ul</code> element.');",
|
||||||
"assert.equal($(\"ol li\").length, 3, 'message: You should have three <code>li</code> elements within your <code>ol</code> element.');",
|
"assert.equal($(\"ol li\").length, 3, 'message: You should have three <code>li</code> elements within your <code>ol</code> element.');",
|
||||||
"assert(code.match(/<\\/ul>/g) && code.match(/<\\/ul>/g).length === code.match(/<ul>/g).length, 'message: Make sure your <code>ul</code> element has a closing tag.');",
|
"assert(code.match(/<\\/ul>/g) && code.match(/<\\/ul>/g).length === code.match(/<ul>/g).length, 'message: Make sure your <code>ul</code> element has a closing tag.');",
|
||||||
|
@ -481,14 +481,14 @@
|
|||||||
"Remember to use <a href='//github.com/FreeCodeCamp/freecodecamp/wiki/How-to-get-help-when-you-get-stuck' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
|
"Remember to use <a href='//github.com/FreeCodeCamp/freecodecamp/wiki/How-to-get-help-when-you-get-stuck' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
|
||||||
],
|
],
|
||||||
"challengeSeed": [
|
"challengeSeed": [
|
||||||
"function uniteUnique(arr1, arr2, arr3) {",
|
"function uniteUnique(arr) {",
|
||||||
" return arr1;",
|
" return arr;",
|
||||||
"}",
|
"}",
|
||||||
"",
|
"",
|
||||||
"uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]);"
|
"uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]);"
|
||||||
],
|
],
|
||||||
"solutions": [
|
"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": [
|
"tests": [
|
||||||
"assert.deepEqual(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]), [1, 3, 2, 5, 4], 'message: <code>uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1])</code> should return <code>[1, 3, 2, 5, 4]</code>.');",
|
"assert.deepEqual(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]), [1, 3, 2, 5, 4], 'message: <code>uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1])</code> should return <code>[1, 3, 2, 5, 4]</code>.');",
|
||||||
|
@ -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.",
|
"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",
|
"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:",
|
"Here's some code that does this:",
|
||||||
"<blockquote>if (navigator.geolocation) {</br> navigator.geolocation.getCurrentPosition(function(position) {</br> $(\"#data\").html(\"latitude: \" + position.coords.latitude + \"<br>longitude: \" + position.coords.longitude);</br> });</br>}</blockquote>"
|
"<blockquote>if (navigator.geolocation) {<br> navigator.geolocation.getCurrentPosition(function(position) {<br> $(\"#data\").html(\"latitude: \" + position.coords.latitude + \"<br>longitude: \" + position.coords.longitude);<br> });<br>}</blockquote>"
|
||||||
],
|
],
|
||||||
"challengeSeed": [
|
"challengeSeed": [
|
||||||
"fccss",
|
"fccss",
|
||||||
@ -463,7 +463,10 @@
|
|||||||
"</div>"
|
"</div>"
|
||||||
],
|
],
|
||||||
"tests": [
|
"tests": [
|
||||||
"assert(code.match(/navigator\\.geolocation\\.getCurrentPosition/gi), 'message: You should make use of the <code>navigator.geolocation</code> to access the users current location.');"
|
"assert(code.match(/navigator\\.geolocation\\.getCurrentPosition/gi), 'message: You should make use of <code>navigator.geolocation</code> to access the users current location.');",
|
||||||
|
"assert(code.match(/position\\.coords\\.latitude/gi), 'message: You should make use of <code>position.coords.latitude</code> to display the users' latitudinal location.');",
|
||||||
|
"assert(code.match(/position\\.coords\\.longitude/gi), 'message: You should make use of <code>position.coords.longitude</code> 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 <code>data</code> div element');"
|
||||||
],
|
],
|
||||||
"type": "waypoint",
|
"type": "waypoint",
|
||||||
"challengeType": 0,
|
"challengeType": 0,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
|
import { isEmail } from 'validator';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:user:remote');
|
const debug = debugFactory('fcc:user:remote');
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ module.exports = function(app) {
|
|||||||
// send welcome email to new camper
|
// send welcome email to new camper
|
||||||
User.afterRemote('create', function({ req, res }, user, next) {
|
User.afterRemote('create', function({ req, res }, user, next) {
|
||||||
debug('user created, sending email');
|
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 ?
|
const redirect = req.session && req.session.returnTo ?
|
||||||
req.session.returnTo :
|
req.session.returnTo :
|
||||||
'/';
|
'/';
|
||||||
|
@ -550,6 +550,7 @@ module.exports = function(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function postForgot(req, res) {
|
function postForgot(req, res) {
|
||||||
|
req.validate('email', 'Email format is not valid').isEmail();
|
||||||
const errors = req.validationErrors();
|
const errors = req.validationErrors();
|
||||||
const email = req.body.email.toLowerCase();
|
const email = req.body.email.toLowerCase();
|
||||||
|
|
||||||
|
@ -6,6 +6,10 @@ export default function globalLocals() {
|
|||||||
if (req.csrfToken) {
|
if (req.csrfToken) {
|
||||||
res.expose({ token: res.locals._csrf }, 'csrf');
|
res.expose({ token: res.locals._csrf }, 'csrf');
|
||||||
}
|
}
|
||||||
|
res.locals.theme = req.user && req.user.theme ||
|
||||||
|
req.cookies.theme ||
|
||||||
|
'default';
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ export function ifNoUserRedirectTo(url, message, type = 'errors') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.flash(type, {
|
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);
|
return res.redirect(url);
|
||||||
|
@ -5,7 +5,7 @@ block content
|
|||||||
h2.text-center Actions
|
h2.text-center Actions
|
||||||
.row
|
.row
|
||||||
.col-xs-12
|
.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
|
.row
|
||||||
.col-xs-12
|
.col-xs-12
|
||||||
if (!user.isGithubCool)
|
if (!user.isGithubCool)
|
||||||
@ -133,4 +133,4 @@ block content
|
|||||||
form(action='/account/delete', method='POST')
|
form(action='/account/delete', method='POST')
|
||||||
input(type='hidden', name='_csrf', value=_csrf)
|
input(type='hidden', name='_csrf', value=_csrf)
|
||||||
button.btn.btn-danger.btn-block(type='submit')
|
button.btn.btn-danger.btn-block(type='submit')
|
||||||
| I am 100% sure I want to delete my account and all of my progress
|
| I am 100% sure I want to delete my account and all of my progress
|
||||||
|
@ -19,12 +19,12 @@ block content
|
|||||||
if index === 0
|
if index === 0
|
||||||
.col-sm-4.hidden-xs
|
.col-sm-4.hidden-xs
|
||||||
else
|
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})
|
.challenge-step-counter.large-p.col-sm-4.col-xs-12.text-center (#{index + 1} / #{description.length})
|
||||||
if 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
|
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
|
.clearfix
|
||||||
.spacer
|
.spacer
|
||||||
#challenge-step-modal.modal(tabindex='-1')
|
#challenge-step-modal.modal(tabindex='-1')
|
||||||
|
@ -8,9 +8,9 @@ html(lang='en')
|
|||||||
include partials/scripts
|
include partials/scripts
|
||||||
block content
|
block content
|
||||||
else
|
else
|
||||||
body.no-top-and-bottom-margins
|
body.no-top-and-bottom-margins(class=theme !== 'default' ? theme : '')
|
||||||
include partials/scripts
|
include partials/scripts
|
||||||
include partials/navbar
|
include partials/navbar
|
||||||
include partials/flash
|
include partials/flash
|
||||||
block content
|
block content
|
||||||
include partials/footer
|
include partials/footer
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
extends ../layout-wide
|
extends ../layout-wide
|
||||||
block content
|
block content
|
||||||
.mapWrapper
|
.mapWrapper
|
||||||
.text-center.map-fixed-header
|
.text-center.map-fixed-header
|
||||||
p Challenges required for certifications are marked with a *
|
p Challenges required for certifications are marked with a *
|
||||||
.row.map-buttons
|
.row.map-buttons
|
||||||
@ -9,7 +9,7 @@ block content
|
|||||||
.input-group
|
.input-group
|
||||||
input#map-filter.form-control(type="text" placeholder="Type a challenge name" autocomplete="off" value="")
|
input#map-filter.form-control(type="text" placeholder="Type a challenge name" autocomplete="off" value="")
|
||||||
span.input-group-addon
|
span.input-group-addon
|
||||||
i.fa.fa-search
|
i.fa.fa-search
|
||||||
hr
|
hr
|
||||||
include ../partials/flash
|
include ../partials/flash
|
||||||
#accordion.map-accordion
|
#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 #2") Mock Interview #2
|
||||||
p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #3") Mock Interview #3
|
p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #3") Mock Interview #3
|
||||||
.spacer
|
.spacer
|
||||||
|
include ../partials/flash
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.container
|
.container
|
||||||
.row.flashMessage.negative-30
|
.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)
|
if (messages.errors || messages.error)
|
||||||
.alert.alert-danger.fade.in
|
.alert.alert-danger.fade.in
|
||||||
button.close(type='button', data-dismiss='alert')
|
button.close(type='button', data-dismiss='alert')
|
||||||
|
@ -7,5 +7,11 @@ script.
|
|||||||
ga('require', 'displayfeatures');
|
ga('require', 'displayfeatures');
|
||||||
ga('send', 'pageview');
|
ga('send', 'pageview');
|
||||||
// Leave the below lines alone!
|
// 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', 'vendor-main.js'))
|
||||||
script(src=rev('/js', 'main.js'))
|
script(src=rev('/js', 'main.js'))
|
||||||
|
Reference in New Issue
Block a user