Merge remote-tracking branch 'origin/staging' into staging

This commit is contained in:
benmcmahon100
2015-08-05 15:39:18 +01:00
13 changed files with 86 additions and 527 deletions

View File

@ -1,14 +1,16 @@
var debug = require('debug')('freecc:models:userIdent');
import debugFactory from 'debug';
var defaultProfileImage =
require('../utils/constantStrings.json').defaultProfileImage;
const debug = debugFactory('freecc:models:userIdent');
const { defaultProfileImage } = require('../utils/constantStrings.json');
function getFirstImageFromProfile(profile) {
return profile && profile.photos && profile.photos[0] ?
profile.photos[0].value :
null;
}
module.exports = function(UserIdent) {
export default function(UserIdent) {
UserIdent.observe('before save', function(ctx, next) {
var userIdent = ctx.currentInstance || ctx.instance;
if (!userIdent) {
@ -16,13 +18,14 @@ module.exports = function(UserIdent) {
return next();
}
userIdent.user(function(err, user) {
let userChanged = false;
if (err) { return next(err); }
if (!user) {
debug('no user attached to identity!');
return next();
}
var picture = getFirstImageFromProfile(userIdent.profile);
const picture = getFirstImageFromProfile(userIdent.profile);
debug('picture', picture, user.picture);
// check if picture was found
@ -34,15 +37,35 @@ module.exports = function(UserIdent) {
(!user.picture || user.picture === defaultProfileImage)
) {
debug('setting user picture');
user.picture = userIdent.profile.photos[0].value;
user.picture = picture;
userChanged = true;
}
// if user signed in with github
// and user is not github cool
// or username is different from github username
// then make them github cool
// and set their username from their github profile.
if (
userIdent.provider === 'github-login' &&
(!user.isGithubCool ||
user.username !== userIdent.provider.username.toLowerCase())
) {
debug("user isn't github cool or username from github is different");
user.isGithubCool = true;
user.username = userIdent.profile.username.toLowerCase();
userChanged = true;
}
if (userChanged) {
return user.save(function(err) {
if (err) { return next(err); }
next();
});
}
debug('exiting after user ident');
debug('exiting after user identity before save');
next();
});
});
};
}

View File

@ -161,9 +161,9 @@ module.exports = function(User) {
if (!username) {
// Zalgo!!
return nextTick(() => {
cb(
new TypeError('FCC: username should be a string but got %s', username)
);
cb(new TypeError(
`username should be a string but got ${ username }`
));
});
}
User.findOne({ where: { username } }, (err, user) => {
@ -171,7 +171,7 @@ module.exports = function(User) {
return cb(err);
}
if (!user || user.username !== username) {
return cb(new Error('FCC: no user found for %s', username));
return cb(new Error(`no user found for ${ username }`));
}
const aboutUser = getAboutProfile(user);
return cb(null, aboutUser);

View File

@ -19,28 +19,14 @@
"password": {
"type": "string"
},
"facebook": {
"type": "string"
},
"twitter": {
"type": "string"
},
"google": {
"type": "string"
},
"github": {
"type": "string"
},
"linkedin": {
"type": "string"
},
"tokens": {
"type": "array"
},
"progressTimestamps": {
"type": "array",
"default": []
},
"isGithubCool": {
"type": "boolean",
"default": false
},
"username": {
"type": "string",
"lowercase": true,
@ -87,48 +73,6 @@
"type": "string",
"default": ""
},
"website1Link": {
"type": "string",
"default": ""
},
"website1Title": {
"type": "string",
"default": ""
},
"website1Image": {
"type": "string",
"default": ""
},
"website2Link": {
"type": "string",
"default": ""
},
"website2Title": {
"type": "string",
"default": ""
},
"website2Image": {
"type": "string",
"default": ""
},
"website3Link": {
"type": "string",
"default": ""
},
"website3Title": {
"type": "string",
"default": ""
},
"website3Image": {
"type": "string",
"default": ""
},
"resetPasswordToken": {
"type": "string"
},
"resetPasswordExpires": {
"type": "string"
},
"completedBonfires": {
"type": [
{
@ -170,21 +114,10 @@
"type": "number",
"default": 0
},
"needsSomeDataModeled": {
"type": "boolean",
"default": false
},
"needsMigration": {
"type": "boolean",
"default": true
},
"sendMonthlyEmail": {
"type": "boolean",
"default": true
},
"challengesHash": {
"type": {}
},
"currentChallenge": {
"type": {}
},
@ -205,10 +138,6 @@
}
],
"default": []
},
"uncompletedChallenges": {
"type": "array",
"default": []
}
},
"validations": [],

View File

@ -577,6 +577,7 @@ thead {
margin: 0 auto;
position: relative;
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
}
@ -612,6 +613,11 @@ thead {
color: #009900
}
.default-border-radius {
border-radius: 5px;
}
.testimonial-copy {
font-size: 20px;
text-align: center;

View File

@ -12,7 +12,7 @@
"Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name <code>Responsive Design</code>.",
"With responsive design, there is no need to design a mobile version of your website. It will look good on devices with screens of any width.",
"You can add Bootstrap to any app just by including it with <code>&#60;link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/&#62;</code> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.",
"To get started, we should nest all of our HTML in a <code>div</code> element with the class \"fluid-container\"."
"To get started, we should nest all of our HTML in a <code>div</code> element with the class \"container-fluid\"."
],
"tests": [
"assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"row\"')",
@ -1566,7 +1566,7 @@
"difficulty": 2.18,
"description": [
"Now let's make sure all the content on your page is mobile-responsive.",
"Let's nest your <code>h3</code> element within a <code>div<code> element with the class \"containter-fluid\"."
"Let's nest your <code>h3</code> element within a <code>div<code> element with the class \"container-fluid\"."
],
"tests": [
"assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"container-fluid\"')",
@ -1743,7 +1743,7 @@
],
"tests": [
"assert($('.btn').length > 5, 'Apply the \"btn\" class to each of your <code>button</code> elements.')",
"assert($('.btn').length > 5, 'Apply the \"btn-default\" class to each of your <code>button</code> elements.')"
"assert($('.btn-default').length > 5, 'Apply the \"btn-default\" class to each of your <code>button</code> elements.')"
],
"challengeSeed": [
"<div class='container-fluid'>",

View File

@ -4,7 +4,7 @@ module.exports = function(app) {
router.get('/nonprofit-project-instructions', function(req, res) {
res.redirect(
301,
"https://github.com/FreeCodeCamp/freecodecamp/wiki/How-Free-Code-Camp's-Nonprofit-Projects-work"
"//github.com/FreeCodeCamp/freecodecamp/wiki/How-Free-Code-Camp's-Nonprofit-Projects-work"
);
});
@ -14,7 +14,7 @@ module.exports = function(app) {
router.get('/privacy', function(req, res) {
res.redirect(
301, "https://github.com/FreeCodeCamp/freecodecamp/wiki/Free-Code-Camp's-Privacy-Policy"
301, "//github.com/FreeCodeCamp/freecodecamp/wiki/Free-Code-Camp's-Privacy-Policy"
);
});
@ -22,6 +22,10 @@ module.exports = function(app) {
res.redirect(301, '/map');
});
router.get('/field-guide/*', function(req, res) {
res.redirect(302, '//github.com/freecodecamp/freecodecamp/wiki')
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});

View File

@ -214,19 +214,7 @@ module.exports = function(app) {
bio: user.bio,
picture: user.picture,
progressTimestamps: user.progressTimestamps,
website1Link: user.website1Link,
website1Title: user.website1Title,
website1Image: user.website1Image,
website2Link: user.website2Link,
website2Title: user.website2Title,
website2Image: user.website2Image,
website3Link: user.website3Link,
website3Title: user.website3Title,
website3Image: user.website3Image,
challenges: challenges,
bonfires: user.completedChallenges.filter(function(challenge) {
return challenge.challengeType === 5;
}),
calender: data,
moment: moment,
longestStreak: user.longestStreak +
@ -290,35 +278,12 @@ module.exports = function(app) {
return res.redirect('/account');
}
var body = req.body || {};
user.email = body.email.trim() || '';
user.name = body.name.trim() || '';
user.username = body.username.trim() || '';
user.location = body.location.trim() || '';
user.githubProfile = body.githubProfile.trim() || '';
user.facebookProfile = body.facebookProfile.trim() || '';
user.linkedinProfile = body.linkedinProfile.trim() || '';
user.codepenProfile = body.codepenProfile.trim() || '';
user.twitterHandle = body.twitterHandle.trim() || '';
user.bio = body.bio.trim() || '';
user.picture = body.picture.trim() ||
'https://s3.amazonaws.com/freecodecamp/' +
'camper-image-placeholder.png';
user.website1Title = body.website1Title.trim() || '';
user.website1Link = body.website1Link.trim() || '';
user.website1Image = body.website1Image.trim() || '';
user.website2Title = body.website2Title.trim() || '';
user.website2Link = body.website2Link.trim() || '';
user.website2Image = body.website2Image.trim() || '';
user.website3Title = body.website3Title.trim() || '';
user.website3Link = body.website3Link.trim() || '';
user.website3Image = body.website3Image.trim() || '';
user.save(function(err) {
if (err) {
return next(err);

View File

@ -3,30 +3,11 @@ var pmx = require('pmx');
pmx.init();
var assign = require('lodash').assign,
loopback = require('loopback'),
boot = require('loopback-boot'),
accepts = require('accepts'),
cookieParser = require('cookie-parser'),
compress = require('compression'),
session = require('express-session'),
expressState = require('express-state'),
logger = require('morgan'),
errorHandler = require('errorhandler'),
methodOverride = require('method-override'),
bodyParser = require('body-parser'),
helmet = require('helmet'),
MongoStore = require('connect-mongo')(session),
flash = require('express-flash'),
path = require('path'),
expressValidator = require('express-validator'),
lessMiddleware = require('less-middleware'),
passportProviders = require('./passport-providers'),
rxMiddleware = require('./utils/rx').rxMiddleware,
/**
* API keys and Passport configuration.
*/
secrets = require('./../config/secrets');
loopback = require('loopback'),
boot = require('loopback-boot'),
expressState = require('express-state'),
path = require('path'),
passportProviders = require('./passport-providers');
var generateKey =
require('loopback-component-passport/lib/models/utils').generateKey;
@ -45,147 +26,9 @@ var passportConfigurator = new PassportConfigurator(app);
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(compress());
app.use(lessMiddleware(path.join(__dirname, '/public')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(expressValidator({
customValidators: {
matchRegex: function(param, regex) {
return regex.test(param);
}
}
}));
app.use(methodOverride());
app.use(cookieParser(secrets.cookieSecret));
app.use(session({
resave: true,
saveUninitialized: true,
secret: secrets.sessionSecret,
store: new MongoStore({
url: secrets.db,
'autoReconnect': true
})
}));
app.use(flash());
app.disable('x-powered-by');
// adds passport initialization after session middleware phase is complete
app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.frameguard());
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
var trusted = [
"'self'",
'blob:',
'104.236.218.15',
'*.freecodecamp.com',
'http://www.freecodecamp.com',
'https://www.freecodecamp.com',
'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',
'*.google-analytics.com',
'*.googleapis.com',
'*.google.com',
'*.gstatic.com',
'*.doubleclick.net',
'*.twitter.com',
'*.twitch.tv',
'*.twimg.com',
"'unsafe-eval'",
"'unsafe-inline'",
'*.bootstrapcdn.com',
'*.cloudflare.com',
'https://*.cloudflare.com',
'localhost:3001',
'ws://localhost:3001/',
'http://localhost:3001',
'localhost:3000',
'ws://localhost:3000/',
'http://localhost:3000',
'127.0.0.1',
'127.0.0.1:3000',
'ws://127.0.0.1:3000/',
'http://127.0.0.1:3000',
'*.ionicframework.com',
'https://syndication.twitter.com',
'*.youtube.com',
'*.jsdelivr.net',
'https://*.jsdelivr.net',
'*.ytimg.com',
'*.bitly.com',
'http://cdn.inspectlet.com/',
'https://cdn.inspeclet.com/',
'wss://inspectletws.herokuapp.com/',
'http://hn.inspectlet.com/',
'*.googleapis.com',
'*.gstatic.com',
'https://hn.inspectlet.com/',
'http://*.github.com'
];
app.use(helmet.csp({
defaultSrc: trusted,
scriptSrc: [
'*.optimizely.com',
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js',
'http://beta.freecodecamp.com'
].concat(trusted),
'connect-src': [
'vimeo.com'
].concat(trusted),
styleSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
imgSrc: [
/* allow all input since we have user submitted images for public profile*/
'*'
].concat(trusted),
fontSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
mediaSrc: [
'*.amazonaws.com',
'*.twitter.com'
].concat(trusted),
frameSrc: [
'*.gitter.im',
'*.gitter.im https:',
'*.vimeo.com',
'*.twitter.com',
'*.ghbtns.com'
].concat(trusted),
// set to true if you only want to report errors
reportOnly: false,
// set to true if you want to set all headers
setAllHeaders: false,
// set to true if you want to force buggy CSP in Safari 5
safari5: false
}));
passportConfigurator.init();
boot(app, {
@ -222,6 +65,9 @@ var passportOptions = {
if (email) {
userObj.email = email;
}
if (provider === 'github-login') {
userObj.isGithubCool = true;
}
return userObj;
}
};

View File

@ -2,108 +2,27 @@ extends ../layout
block content
script.
var challengeName = 'Account View'
.panel.panel-info.min-height-1000(ng-controller="profileValidationController")
.panel.panel-info(ng-controller="profileValidationController")
.panel-heading.text-center Update your portfolio here:
.panel-body
.container.text-center
if (!user.github)
.col-xs-12
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
i.fa.fa-github
| Link GitHub with my account
.col-xs-12
form.form-horizontal(action='/account/profile', method='POST', novalidate='novalidate', name='profileForm' ng-show="asyncComplete")
input(type='hidden', name='_csrf', value=_csrf)
.col-sm-4.col-sm-offset-5
h2 Bio
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='name') Name *
.col-sm-4
input.form-control(type='text', placeholder='Name', name='name', autocomplete="off", ng-model='user.name', ng-minlength='3', ng-maxlength='50', required='required', id='name')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.name.$invalid && profileForm.name.$error.required")
alert(type='danger')
span.ion-close-circled(id='#name-error')
| Your name is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.name.$error.minlength && !profileForm.name.$pristine')
alert(type='danger')
span.ion-close-circled
| Your name must be at least 3 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.name.$error.maxlength && !profileForm.name.$pristine')
alert(type='danger')
span.ion-close-circled
| Your name must be fewer than 50 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) *
.col-sm-4
input.form-control(type='text', placeholder='username' name='username', autocomplete="off", id='username', ng-model='user.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='', ng-pattern="/^[A-z0-9_]+$/")
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.pattern")
alert(type='danger')
span.ion-close-circled
| Your username should only contain letters, numbers and underscores (az10_).
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.required")
alert(type='danger')
span.ion-close-circled
| Your username is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.minlength && !profileForm.username.$pristine")
alert(type='danger')
span.ion-close-circled
| Your username must be at least 5 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.maxlength && !profileForm.username.$pristine")
alert(type='danger')
span.ion-close-circled
| Your username must be fewer than 15 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine && $scope.storedUsername !== user.username")
alert(type='danger')
span.ion-close-circled
| That username is already in use.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Email *
.col-sm-4
input.form-control(type='email', name='email', id='email', autocomplete="off", ng-model='user.email', required='required', ng-keypress='', unique-email='')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.email.$error.required")
alert(type='danger')
span.ion-close-circled
| Your email address is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.$error.email && !profileForm.email.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid email format.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.email.$error.unique && !profileForm.email.$pristine")
alert(type='danger')
span.ion-close-circled
| That email is already in use.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='location') Location
.col-sm-4
input.form-control(type='text', name='location', autocomplete="off", id='location', ng-model='user.location')
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Link to Profile Photo (1:1 ratio)
.col-sm-4
input.form-control(type='url', name='picture', id='picture', ng-model='user.picture', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.picture.$error.url && !profileForm.picture.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='bio') Bio (140 characters)
label.col-sm-3.col-sm-offset-1.control-label(for='bio') Bio (140 characters)
.col-sm-4
input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.bio', ng-maxlength='140', id='bio')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine')
alert(type='danger')
span.ion-close-circled
| Your bio must be fewer than 140 characters.
.form-group
.col-sm-offset-5.col-sm-4
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my Bio
.col-sm-4.col-sm-offset-5
h2 Social Profiles
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Twitter
label.col-sm-3.col-sm-offset-1.control-label(for='email') Twitter
.col-sm-4
.input-group.twitter-input
span.input-group-addon @
@ -116,17 +35,9 @@ block content
alert(type='danger')
span.ion-close-circled
| Your name must be fewer than 15 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') GitHub
.col-sm-4
input.form-control(type='url', name='githubProfile', id='githubProfile', autocomplete="off", ng-model='user.githubProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.githubProfile.$error.url && !profileForm.githubProfile.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') CodePen
label.col-sm-3.col-sm-offset-1.control-label(for='email') CodePen
.col-sm-4
input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.codepenProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine")
@ -135,7 +46,7 @@ block content
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') LinkedIn
label.col-sm-3.col-sm-offset-1.control-label(for='email') LinkedIn
.col-sm-4
input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.linkedinProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine")
@ -144,7 +55,7 @@ block content
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Facebook
label.col-sm-3.col-sm-offset-1.control-label(for='email') Facebook
.col-sm-4
input.form-control(type='url', name='facebookProfile', id='facebookProfile', autocomplete="off", ng-model='user.facebookProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.facebookProfile.$error.url && !profileForm.facebookProfile.$pristine")
@ -152,120 +63,14 @@ block content
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
.col-sm-offset-5.col-sm-4
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my Social Links
.col-sm-4.col-sm-offset-5.negative-bottom
h2 Portfolio
.col-sm-4.col-sm-offset-5.flat-top
h3 First Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Title') Title
.col-sm-4
input.form-control(type='text', name='website1Title', id='website1Title', autocomplete="off", ng-model='user.website1Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website1Title.$error.maxlength && !profileForm.website1Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Link') Link
.col-sm-4
input.form-control(type='url', name='website1Link', id='website1Link', autocomplete="off", ng-model='user.website1Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website1Link.$error.url && !profileForm.website1Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.website1Image', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.url && !profileForm.website1Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.col-sm-4.col-sm-offset-5.flat-top
h3 Second Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Title') Title
.col-sm-4
input.form-control(type='text', name='website2Title', id='website2Title', autocomplete="off", ng-model='user.website2Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website2Title.$error.maxlength && !profileForm.website2Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Link') Link
.col-sm-4
input.form-control(type='url', name='website2Link', id='website2Link', autocomplete="off", ng-model='user.website2Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website2Link.$error.url && !profileForm.website2Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.website2Image', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.url && !profileForm.website2Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.col-sm-4.col-sm-offset-5.flat-top
h3 Third Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Title') Title
.col-sm-4
input.form-control(type='text', name='website3Title', id='website3Title', autocomplete="off", ng-model='user.website3Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website3Title.$error.maxlength && !profileForm.website3Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Link') Link
.col-sm-4
input.form-control(type='url', name='website3Link', id='website3Link', autocomplete="off", ng-model='user.website3Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website3Link.$error.url && !profileForm.website3Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website3Image', id='website3Image', autocomplete="off", ng-model='user.website3Image', placeholder='http://www.example.com/image.jpg', ng-pattern='/[\.](jpg|png|jpeg|gif)(\s+)?$/')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Image.$error.url && !profileForm.website3Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.form-group
.col-sm-offset-5.col-sm-4
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my Portfolio
br
button.btn.btn-lg.btn-block.btn-primary.btn-link-social(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my info
.panel.panel-info
.panel-heading.text-center Manage your account here:
.panel-body
if (!user.google || !user.facebook || !user.linkedin || !user.twitter)
if (!user.github)
.col-xs-12
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
i.fa.fa-github
| Link GitHub with my account
if (!user.twitter)
.col-xs-12
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social.disabled(href='#')

View File

@ -96,21 +96,3 @@ block content
td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY")
td.col-xs-6
a(href=challenge.solution, target='_blank') View my solution
br
if (bonfires.length > 0)
.col-sm-12
table.table.table-striped
thead
tr
th.col-xs-4 Bonfire
th.col-xs-2 Completed
th.col-xs-6 Solution
for bonfire in bonfires
tr
td.col-xs-4
a(href='/challenges/' + bonfire.name, target='_blank')= bonfire.name
td.col-xs-2= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY")
td.col-xs-6
pre.wrappable= bonfire.solution
br

View File

@ -3,6 +3,9 @@ block content
script.
var completedChallenges = !{JSON.stringify(completedChallengeList)};
var challengeList = !{JSON.stringify(challengeList)};
.bg-danger.default-border-radius
p &nbsp;&nbsp;&nbsp;&nbsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta' target='_blank') You're using our experimental beta site. None of your progress here will be saved. Please click this to learn more.
.panel.panel-info
.panel-heading.text-center
h1 Challenge Map

View File

@ -1,8 +1,8 @@
extends layout
block content
.bg-danger
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta before continuing')
h3 Warning! You are on beta! Please read this link before continuing! https://github.com/freecodecamp/freecodecamp/wiki/beta
.bg-danger.default-border-radius
p &nbsp;&nbsp;&nbsp;&nbsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta' target='_blank') You're using our experimental beta site. None of your progress here will be saved. Please click this to learn more.
.jumbotron
.text-center
h1.hug-top Code with Us

View File

@ -7,10 +7,6 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg.gz', alt='learn to code javascript at Free Code Camp logo')
.collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right.hamburger-dropdown
if user
li
a(href='/challenges') Learn
li
a(href='/map') Map
li
@ -18,7 +14,7 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
li
a(href='/news') News
li
a(href='//github.com/FreeCodeCamp/freecodecamp/wiki/Home') Wiki
a(href='//github.com/FreeCodeCamp/freecodecamp/wiki/Home', target='_blank') Wiki
if !user
li &thinsp; &thinsp; &thinsp;
li