Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging

Conflicts:
	server/boot/randomAPIs.js
	server/views/coursewares/showBonfire.jade
	server/views/partials/navbar.jade
This commit is contained in:
Quincy Larson
2015-06-21 15:53:16 -07:00
12 changed files with 365 additions and 370 deletions

View File

@ -70,6 +70,7 @@
"node-slack": "0.0.7",
"node-uuid": "^1.4.3",
"nodemailer": "~1.3.0",
"object.assign": "^3.0.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth2": "^0.1.6",
"passport-linkedin-oauth2": "^1.2.1",

View File

@ -1,3 +1,7 @@
$(document).ready(function() {
$('#reset-button').on('click', resetEditor);
});
var widgets = [];
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
lineNumbers: true,
@ -42,7 +46,7 @@ editor.setOption("extraKeys", {
/*
Local Storage Update System By Andrew Cay(Resto)
localBonfire: singleton object that contains properties and methods related to
codeStorage: singleton object that contains properties and methods related to
dealing with the localStorage system.
The keys work off of the variable challenge_name to make unique identifiers per bonfire
@ -50,64 +54,81 @@ editor.setOption("extraKeys", {
Added anonymous version checking system incase of future updates to the system
Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage
*/
var localBonfire = {
var codeStorage = {
version: 0.01,
keyVersion:"saveVersion",
keyStamp: challenge_Name + 'Stamp',
keyValue: challenge_Name + 'Val',
stampExpireTime: (1000 *60) *60 *24,
updateWait: 1500,// 1.5 seconds
updateTimeoutId: null
keyValue: null,//where the value of the editor is saved
updateWait: 2000,// 2 seconds
updateTimeoutId: null,
eventArray: []//for firing saves
};
localBonfire.getEditorValue = function(){
return localStorage.getItem(localBonfire.keyValue);
// Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere")
codeStorage.hasSaved = function(){
return ( updateTimeoutId === null );
};
localBonfire.getStampTime = function(){
//localstorage always saves as strings.
return Number.parseInt( localStorage.getItem(localBonfire.keyStamp) );
codeStorage.onSave = function(func){
codeStorage.eventArray.push(func);
};
localBonfire.isAlive = function(){// returns true if IDE was edited within expire time
return ( Date.now() - localBonfire.getStampTime() < localBonfire.stampExpireTime );
codeStorage.setSaveKey = function(key){
codeStorage.keyValue = key + 'Val';
};
localBonfire.updateStorage = function(){
codeStorage.getEditorValue = function(){
return ('' + localStorage.getItem(codeStorage.keyValue));
};
codeStorage.isAlive = function() {
var val = this.getEditorValue()
return val !== 'null' &&
val !== 'undefined' &&
(val && val.length > 0);
}
codeStorage.updateStorage = function(){
if(typeof(Storage) !== undefined) {
var stamp = Date.now(),
value = editor.getValue();
localStorage.setItem(localBonfire.keyValue, value);
localStorage.setItem(localBonfire.keyStamp, stamp);
var value = editor.getValue();
localStorage.setItem(codeStorage.keyValue, value);
} else {
var debugging = false;
if( debugging ){
console.log('no web storage');
}
}
localBonfire.updateTimeoutId = null;
codeStorage.updateTimeoutId = null;
codeStorage.eventArray.forEach(function(func){
func();
});
};
// ANONYMOUS 1 TIME UPDATE VERSION
//Update Version
(function(){
var savedVersion = localStorage.getItem('saveVersion');
if( savedVersion === null ){
localStorage.setItem(localBonfire.keyVersion, localBonfire.version);//just write current version
localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version
}else{
//do checking if not current version
if( savedVersion !== localBonfire.version ){
//update version
if( savedVersion !== codeStorage.version ){
//Update version
}
}
})();
editor.on('keyup', function(codMir, event){
window.clearTimeout(localBonfire.updateTimeoutId);
localBonfire.updateTimeoutId = window.setTimeout(localBonfire.updateStorage, localBonfire.updateWait);
///Set everything up one page
/// Update local save when editor has changed
codeStorage.setSaveKey(challenge_Name);
editor.on('keyup', function(){
window.clearTimeout(codeStorage.updateTimeoutId);
codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait);
});
var attempts = 0;
if (attempts) {
attempts = 0;
}
var resetEditor = function() {
var resetEditor = function resetEditor() {
editor.setValue(allSeeds);
localBonfire.updateStorage();
codeStorage.updateStorage();
};
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
@ -145,7 +166,7 @@ var allSeeds = '';
});
})();
editorValue = (localBonfire.isAlive())? localBonfire.getEditorValue() : allSeeds;
editorValue = (codeStorage.isAlive())? codeStorage.getEditorValue() : allSeeds;
myCodeMirror.setValue(editorValue);

View File

@ -271,6 +271,7 @@ $(document).ready(function() {
$('#story-submit').on('click', storySubmitButtonHandler);
var commentSubmitButtonHandler = function commentSubmitButtonHandler() {
$('#comment-button').unbind('click');
var data = $('#comment-box').val();

View File

@ -31,8 +31,15 @@
*/
var R = require('ramda'),
Rx = require('rx'),
assign = require('object.assign'),
debug = require('debug')('freecc:challenges'),
utils = require('../utils'),
// this would be so much cleaner with destructering...
saveUser = require('../utils/rx').saveUser,
observableQueryFromModel = require('../utils/rx').observableQueryFromModel,
userMigration = require('../utils/middleware').userMigration,
ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo;
@ -42,6 +49,28 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
var getMDNLinks = utils.getMDNLinks;
var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i;
function dasherize(name) {
return ('' + name)
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\.]/gi, '');
}
function unDasherize(name) {
return ('' + name).replace(/\-/g, ' ');
}
function updateUserProgress(user, challengeId, completedChallenge) {
var index = user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
user.progressTimestamps.push(Date.now());
user.uncompletedChallenges.splice(index, 1);
}
user.completedChallenges.push(completedChallenge);
return user;
}
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
@ -56,7 +85,7 @@ module.exports = function(app) {
router.get('/map', challengeMap);
router.get(
'/challenges/next-challenge',
ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'),
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'),
returnNextChallenge
);
@ -64,7 +93,7 @@ module.exports = function(app) {
router.get(
'/challenges/',
ifNoUserRedirectTo('../challenges/learn-how-free-code-camp-works'),
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'),
returnCurrentChallenge
);
@ -114,7 +143,7 @@ module.exports = function(app) {
function() {},
next,
function() {
res.redirect('../challenges/' + nextChallengeName);
res.redirect('/challenges/' + nextChallengeName);
}
);
}
@ -130,6 +159,7 @@ module.exports = function(app) {
return elem;
}
});
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
@ -146,40 +176,53 @@ module.exports = function(app) {
function() {},
next,
function() {
res.redirect('../challenges/' + nameString);
res.redirect('/challenges/' + nameString);
}
);
}
function returnIndividualChallenge(req, res, next) {
var dashedName = req.params.challengeName;
var origChallengeName = req.params.challengeName;
var unDashedName = unDasherize(origChallengeName);
var challengeName = challangesRegex.test(unDashedName) ?
// remove first word if matches
unDashedName.split(' ').slice(1).join(' ') :
unDashedName;
debug('looking for ', challengeName);
Challenge.findOne(
{ where: { dashedName: dashedName }},
{ where: { name: { like: challengeName, options: 'i' } } },
function(err, challenge) {
if (err) { return next(err); }
// Handle not found
if (!challenge) {
debug('did not find challenge for ' + origChallengeName);
req.flash('errors', {
msg:
'404: We couldn\'t find a challenge with the name `' +
dashedName +
origChallengeName +
'` Please double check the name.'
});
return res.redirect('/challenges');
}
// Redirect to full name if the user only entered a partial
if (dasherize(challenge.name) !== origChallengeName) {
debug('redirecting to fullname');
return res.redirect('/challenges/' + dasherize(challenge.name));
}
if (req.user) {
req.user.currentChallenge = {
challengeId: challenge.id,
challengeName: challenge.name,
dashedName: challenge.dashedName,
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds).
map(function (key) {
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds)
.map(function (key) {
return challengeMapWithIds[key]
.filter(function (elem) {
return String(elem) === challenge.id;
return elem === ('' + challenge.id);
})
.map(function () {
return key;
@ -191,7 +234,7 @@ module.exports = function(app) {
var commonLocals = {
title: challenge.name,
dashedName: dashedName,
dashedName: origChallengeName,
name: challenge.name,
details: challenge.description.slice(1),
tests: challenge.tests,
@ -235,143 +278,123 @@ module.exports = function(app) {
}
function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || '';
var isCompletedDate = Math.round(+new Date());
debug('compltedBonfire');
var completedWith = req.body.challengeInfo.completedWith || false;
var challengeId = req.body.challengeInfo.challengeId;
var isSolution = req.body.challengeInfo.solution;
var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWith) {
if (err) { return next(err); }
var challengeData = {
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedDate: Math.round(+new Date()),
solution: req.body.challengeInfo.solution,
challengeType: 5
};
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
pairedWith = pairedWith.pop();
observableQueryFromModel(
User,
'findOne',
{ where: { username: ('' + completedWith).toLowerCase() } }
)
.doOnNext(function(pairedWith) {
debug('paired with ', pairedWith);
if (pairedWith) {
index = pairedWith.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedChallenges.splice(index, 1);
updateUserProgress(
pairedWith,
challengeId,
assign({ completedWith: req.user.id }, challengeData)
);
}
pairedWith.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: req.user.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: pairedWith.id,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
})
.withLatestFrom(
Rx.Observable.just(req.user),
function(pairedWith, user) {
debug('yo');
return {
user: user,
pairedWith: pairedWith
};
}
// User said they paired, but pair wasn't found
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.save(function (err, user) {
if (err) { return next(err); }
if (pairedWith) {
pairedWith.save(function (err, paired) {
if (err) {
return next(err);
)
// side effects should always be done in do's and taps
.doOnNext(function(dats) {
updateUserProgress(
dats.user,
challengeId,
dats.pairedWith ?
// paired programmer found and adding to data
assign({ completedWith: dats.pairedWith.id }, challengeData) :
// user said they paired, but pair wasn't found
challengeData
);
})
// not iterate users
.flatMap(function(dats) {
debug('flatmap');
return Rx.Observable.from([dats.user, dats.pairedWith]);
})
// save user
.flatMap(function(user) {
// save user will do nothing if user is falsey
return saveUser(user);
})
.subscribe(
function(user) {
debug('onNext');
if (user) {
debug('user %s saved', user.username);
}
if (user && paired) {
return res.send(true);
}
});
} else if (user) {
res.send(true);
}
});
});
} else {
req.user.completedChallenges.push({
id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err) {
if (err) { return next(err); }
res.send(true);
});
},
next,
function() {
debug('completed');
return res.status(200).send(true);
}
);
}
function completedChallenge(req, res, next) {
var isCompletedDate = Math.round(+new Date());
var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
req.user.completedChallenges.push({
updateUserProgress(
req.user,
challengeId,
{
id: challengeId,
completedDate: isCompletedDate,
completedDate: completedDate,
name: req.body.challengeInfo.challengeName,
solution: null,
githubLink: null,
verified: true
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
);
req.user.save(function (err, user) {
if (err) {
return next(err);
}
if (user) {
saveUser(req.user)
.subscribe(
function() { },
next,
function() {
res.sendStatus(200);
}
});
);
}
function completedZiplineOrBasejump(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date());
var completedWith = req.body.challengeInfo.completedWith || false;
var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL;
var githubLink = req.body.challengeInfo.challengeType === '4'
? req.body.challengeInfo.githubURL : true;
var githubLink = req.body.challengeInfo.challengeType === '4' ?
req.body.challengeInfo.githubURL :
true;
var challengeType = req.body.challengeInfo.challengeType === '4' ?
4 : 3;
4 :
3;
if (!solutionLink || !githubLink) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
@ -380,93 +403,64 @@ module.exports = function(app) {
return res.sendStatus(403);
}
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWithFromMongo) {
if (err) { return next(err); }
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
var pairedWith = pairedWithFromMongo.pop();
req.user.completedChallenges.push({
var challengeData = {
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: pairedWith.id,
completedDate: isCompletedDate,
completedDate: completedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
};
req.user.save(function (err, user) {
if (err) { return next(err); }
if (req.user.id.toString() === pairedWith.id.toString()) {
return res.sendStatus(200);
observableQueryFromModel(
User,
'findOne',
{ where: { username: completedWith.toLowerCase() } }
)
.doOnNext(function(pairedWith) {
if (pairedWith) {
updateUserProgress(
pairedWith,
challengeId,
assign({ completedWith: req.user.id }, challengeData)
);
}
index = pairedWith.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedChallenges.splice(index, 1);
}
pairedWith.completedChallenges.push({
id: challengeId,
name: req.body.challengeInfo.coursewareName,
completedWith: req.user.id,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
pairedWith.save(function (err, paired) {
if (err) {
return next(err);
}
if (user && paired) {
return res.sendStatus(200);
}
});
});
});
} else {
req.user.completedChallenges.push({
id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err, user) {
if (err) {
return next(err);
}
// NOTE(berks): under certain conditions this will not close
// the response.
})
.withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) {
return {
user: user,
pairedWith: pairedWith
};
})
.doOnNext(function(dats) {
updateUserProgress(
dats.user,
challengeId,
dats.pairedWith ?
assign({ completedWith: dats.pairedWith.id }, challengeData) :
challengeData
);
})
.flatMap(function(dats) {
return Rx.Observable.from([dats.user, dats.pairedWith]);
})
// save users
.flatMap(function(user) {
// save user will do nothing if user is falsey
return saveUser(user);
})
.subscribe(
function(user) {
if (user) {
return res.sendStatus(200);
debug('user %s saved', user.username);
}
});
},
next,
function() {
return res.status(200).send(true);
}
);
}
function challengeMap(req, res, next) {

View File

@ -13,7 +13,7 @@ module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var Challenge = app.models.Challenge;
var Story = app.models.Store;
var Story = app.models.Story;
var FieldGuide = app.models.FieldGuide;
var Nonprofit = app.models.Nonprofit;
@ -76,15 +76,15 @@ module.exports = function(app) {
users: function(callback) {
User.find(
{
where: { 'profile.username': { nlike: '' } },
fields: { 'profile.username': true }
where: { username: { nlike: '' } },
fields: { username: true }
},
function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(users)
Rx.Observable.from(users, null, null, Rx.Scheduler.default)
.map(function(user) {
return user.username;
})
@ -107,7 +107,7 @@ module.exports = function(app) {
debug('Challenge err: ', err);
callback(err);
} else {
Rx.Observable.from(challenges)
Rx.Observable.from(challenges, null, null, Rx.Scheduler.default)
.map(function(challenge) {
return challenge.name;
})
@ -127,7 +127,7 @@ module.exports = function(app) {
debug('Story err: ', err);
callback(err);
} else {
Rx.Observable.from(stories)
Rx.Observable.from(stories, null, null, Rx.Scheduler.default)
.map(function(story) {
return story.link;
})
@ -148,7 +148,7 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(nonprofits)
Rx.Observable.from(nonprofits, null, null, Rx.Scheduler.default)
.map(function(nonprofit) {
return nonprofit.name;
})
@ -168,7 +168,12 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(fieldGuides)
Rx.Observable.from(
fieldGuides,
null,
null,
Rx.Scheduler.default
)
.map(function(fieldGuide) {
return fieldGuide.name;
})
@ -184,7 +189,7 @@ module.exports = function(app) {
if (err) {
return next(err);
}
setTimeout(function() {
process.nextTick(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
@ -195,11 +200,15 @@ module.exports = function(app) {
nonprofits: results.nonprofits,
fieldGuides: results.fieldGuides
});
}, 0);
});
}
);
}
function chat(req, res) {
res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp');
}
function bootcampCalculator(req, res) {
res.render('resources/calculator', {
title: 'Coding Bootcamp Cost Calculator',
@ -260,7 +269,7 @@ module.exports = function(app) {
}
function unsubscribe(req, res, next) {
User.findOne({ email: req.params.email }, function(err, user) {
User.findOne({ where: { email: req.params.email } }, function(err, user) {
if (user) {
if (err) {
return next(err);

View File

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

View File

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

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

@ -0,0 +1,25 @@
var Rx = require('rx');
var debug = require('debug')('freecc:rxUtils');
exports.saveUser = function saveUser(user) {
return new Rx.Observable.create(function(observer) {
if (!user || typeof user.save !== 'function') {
debug('no user or save method');
observer.onNext();
return observer.onCompleted();
}
user.save(function(err, savedUser) {
if (err) {
return observer.onError(err);
}
debug('user saved');
observer.onNext(savedUser);
observer.onCompleted();
});
});
};
exports.observableQueryFromModel =
function observableQueryFromModel(Model, method, query) {
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
};

View File

@ -47,66 +47,11 @@ block content
.background-svg.img-center
.points-on-top
= "[ " + (progressTimestamps.length) + " ]"
.row
.col-xs-12
if (website1Title && website1Link && website1Image)
.row
.col-xs-12.col-md-5
img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website1Title
a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website1Title && website1Link && !website1Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website1Title
a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website2Title && website2Link && website2Image)
.row
.col-xs-12.col-md-5
img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website2Title
a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website2Title && website2Link && !website2Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website2Title
a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
i.fa.icon-beaker
| Try it out
br
if (website3Title && website3Link && website3Image)
.row
.col-xs-12.col-md-5
img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}")
.col-xs-12.col-md-7
h3.text-center.wrappable.flat-top= website3Title
a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
i.fa.icon-beaker
| Try it out
if (website3Title && website3Link && !website3Image)
.col-xs-12.col-md-12
h3.text-center.wrappable.flat-top= website3Title
a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
i.fa.icon-beaker
| Try it out
.spacer
.hidden-xs.hidden-sm.col-md-12
#cal-heatmap.d3-centered
script.
$(document).ready(function () {
setTimeout(function () {
var cal = new CalHeatMap();
var calendar = !{JSON.stringify(calender)};
cal.init({
@ -128,7 +73,6 @@ block content
position: "top"
}
});
}, 300);
});
.row
.hidden-xs.col-sm-12.text-center

View File

@ -86,7 +86,7 @@ block content
| &nbsp; Run code (ctrl + enter)
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#resetButton
label.btn.btn-success#trigger-reset-modal
i.fa.fa-refresh
| &nbsp; Reset
label.btn.btn-success#trigger-help-modal

View File

@ -14,7 +14,7 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
li
a(href='/map') Map
li
a(href='gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat
a(href='//gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat
li
a(href='/stories') News
li