Merge pull request #7998 from FreeCodeCamp/staging

Release staging
This commit is contained in:
Berkeley Martinez
2016-04-08 15:26:26 -07:00
13 changed files with 283 additions and 152 deletions

View File

@ -29,7 +29,7 @@
white-space: pre;
padding: 5px 10px;
margin-bottom: 10px;
margin-top: -10px;
margin-top: -5px;
overflow: auto;
}
dfn {
@ -111,3 +111,28 @@ iframe.iphone {
right: -207px;
}
}
.night {
.challenge-instructions blockquote {
background-color: #242424;
border-color: #515151;
color: #ABABAB
}
div.CodeMirror {
background-color:#242424;
color:#ABABAB;
&-gutters {
background-color:#242424;
color:#ABABAB;
}
.cm-bracket, .cm-tag {
color:#5CAFD6;
}
.cm-property, .cm-string {
color:#B5753A;
}
.cm-keyword, .cm-attribute {
color:#9BBBDC;
}
}
}

View File

@ -54,6 +54,10 @@ html {
border-color: @brand-primary;
}
body {
display: none;
}
body.full-screen-body-background {
background-color: @body-bg;
}
@ -678,7 +682,7 @@ thead {
/**
* Bonfire styling
* Challenge styling
*/
form.code span {
@ -986,7 +990,7 @@ hr {
}
code {
padding: 0;
padding: 1px 5px;
}
@media only screen and (min-width: 993px) {
@ -1133,6 +1137,49 @@ code {
}
}
.night {
background-color: #333;
color: #999;
.btn-group,
.text-success,
.challenge-list-header,
.fcc-footer {
background-color: #333;
}
.navbar-default .navbar-nav > li > a {
color: #CCC;
&:hover {
background-color: #666;
}
}
a, .input-group-addon {
color: #CCC;
}
.black-text {
color:#111;
}
code {
background-color: #2A2A2A;
color: #006400;
}
.fcc-footer {
a {
&hover {
background-color: #333;
}
}
}
.btn-primary {
background-color: #006400;
color:#CCC;
transition: color 0.2s, background-color 0.2s;
&:hover {
background-color: #ABABAB!important;
color: #333!important;
}
}
}
@import "chat.less";
@import "jobs.less";
@import "challenge.less";

View File

@ -2,6 +2,7 @@
* based off of https://github.com/gitterHQ/sidecar
* license: MIT
*/
.map-aside {
width:500px;
@ -314,3 +315,33 @@
.map-aside-action-collapse {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNzEuNDI5IDE3MS40MjkiIGZpbGw9IiMzYTMxMzMiPjxwYXRoIGQ9Ik0xMjIuNDMzLDEwNi4xMzhsLTE2LjI5NSwxNi4yOTVjLTAuNzQ0LDAuNzQ0LTEuNiwxLjExNi0yLjU2NiwxLjExNmMtMC45NjgsMC0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTUuMjktMTUuMjkgbC0xNS4yOSwxNS4yOWMtMC43NDQsMC43NDQtMS42LDEuMTE2LTIuNTY3LDEuMTE2cy0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTYuMjk0LTE2LjI5NWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY2IGMwLTAuOTY4LDAuMzcyLTEuODIzLDEuMTE2LTIuNTY3bDE1LjI5LTE1LjI5bC0xNS4yOS0xNS4yOWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY3czAuMzcyLTEuODIzLDEuMTE2LTIuNTY3IEw2NS4yOSw0OC45OTZjMC43NDQtMC43NDQsMS42LTEuMTE2LDIuNTY3LTEuMTE2czEuODIzLDAuMzcyLDIuNTY3LDEuMTE2bDE1LjI5LDE1LjI5bDE1LjI5LTE1LjI5IGMwLjc0NC0wLjc0NCwxLjYtMS4xMTYsMi41NjctMS4xMTZjMC45NjcsMCwxLjgyMiwwLjM3MiwyLjU2NiwxLjExNmwxNi4yOTUsMTYuMjk0YzAuNzQ0LDAuNzQ0LDEuMTE2LDEuNiwxLjExNiwyLjU2NyBzLTAuMzcyLDEuODIzLTEuMTE2LDIuNTY3bC0xNS4yOSwxNS4yOWwxNS4yOSwxNS4yOWMwLjc0NCwwLjc0NCwxLjExNiwxLjYsMS4xMTYsMi41NjcgQzEyMy41NDksMTA0LjUzOSwxMjMuMTc3LDEwNS4zOTQsMTIyLjQzMywxMDYuMTM4eiBNMTQ2LjQyOSw4NS43MTRjMC0xMS4wMTItMi43MTctMjEuMTY4LTguMTQ4LTMwLjQ2OSBzLTEyLjc5Ny0xNi42NjctMjIuMDk4LTIyLjA5OFM5Ni43MjYsMjUsODUuNzE0LDI1cy0yMS4xNjgsMi43MTYtMzAuNDY5LDguMTQ3UzM4LjU3OSw0NS45NDUsMzMuMTQ3LDU1LjI0NlMyNSw3NC43MDMsMjUsODUuNzE0IHMyLjcxNiwyMS4xNjgsOC4xNDcsMzAuNDY5czEyLjc5NywxNi42NjYsMjIuMDk4LDIyLjA5OHMxOS40NTcsOC4xNDgsMzAuNDY5LDguMTQ4czIxLjE2OC0yLjcxNywzMC40NjktOC4xNDggczE2LjY2Ni0xMi43OTcsMjIuMDk4LTIyLjA5OFMxNDYuNDI5LDk2LjcyNiwxNDYuNDI5LDg1LjcxNHogTTE3MS40MjksODUuNzE0YzAsMTUuNTUxLTMuODMyLDI5Ljg5My0xMS40OTYsNDMuMDI0IGMtNy42NjQsMTMuMTMzLTE4LjA2MiwyMy41My0zMS4xOTQsMzEuMTk0Yy0xMy4xMzIsNy42NjQtMjcuNDc0LDExLjQ5Ni00My4wMjQsMTEuNDk2cy0yOS44OTItMy44MzItNDMuMDI0LTExLjQ5NiBjLTEzLjEzMy03LjY2NC0yMy41MzEtMTguMDYyLTMxLjE5NC0zMS4xOTRDMy44MzIsMTE1LjYwNywwLDEwMS4yNjUsMCw4NS43MTRTMy44MzIsNTUuODIyLDExLjQ5Niw0Mi42OSBjNy42NjQtMTMuMTMzLDE4LjA2Mi0yMy41MzEsMzEuMTk0LTMxLjE5NEM1NS44MjIsMy44MzIsNzAuMTY0LDAsODUuNzE0LDBzMjkuODkzLDMuODMyLDQzLjAyNCwxMS40OTYgYzEzLjEzMyw3LjY2NCwyMy41MywxOC4wNjIsMzEuMTk0LDMxLjE5NEMxNjcuNTk3LDU1LjgyMiwxNzEuNDI5LDcwLjE2NCwxNzEuNDI5LDg1LjcxNHoiLz48L3N2Zz4=)
}
.night {
.map-fixed-header {
background-color: #333;
}
.map-aside {
border-left-color:#222;
}
#map-filter, .input-group-addon {
border-color: #222;
background-color: #666;
color:#ABABAB;
}
.map-accordion h2 > a {
background:#666;
}
.map-accordion a:focus, #noneFound {
color: #ABABAB;
}
.input-group-addon {
&.filled{
background: #555;
border-color: #000d00;
color: white;
}
}
.challenge-title {
color: #999;
}
}

View File

@ -356,7 +356,7 @@ $(document).ready(function() {
function showMap() {
if (!main.isMapAsideLoad) {
var mapAside = $('<iframe>');
var mapAside = $('<iframe id = "map-aside-frame" >');
mapAside.attr({
src: '/map-aside',
frameBorder: '0'
@ -534,4 +534,82 @@ $(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 toggleNightMode(nightModeEnabled) {
var iframe = document.getElementById('map-aside-frame');
if (iframe) {
iframe.src = iframe.src;
}
var body = $('body');
body.hide();
if (nightModeEnabled) {
body.addClass('night');
} else {
body.removeClass('night');
}
body.fadeIn('100');
}
if (typeof localStorage.getItem('nightMode') !== 'undefined') {
var oldVal = false;
try {
oldVal = JSON.parse(localStorage.getItem('nightMode'));
} catch (e) {
console.error('Error parsing value form local storage:', 'nightMode', e);
}
toggleNightMode(oldVal);
$('.nightMode-btn').on('click', function() {
changeMode();
});
} else {
localStorage.setItem('nightMode', 'false');
toggleNightMode('false');
}
// Hot Keys
window.Mousetrap.bind('g t n', changeMode);
window.Mousetrap.bind('g n n', () => {
// Next Challenge
window.location = '/challenges/next-challenge';
});
window.Mousetrap.bind('g n a', () => {
// Account
window.location = '/account';
});
window.Mousetrap.bind('g n m', () => {
// Map
window.location = '/map';
});
window.Mousetrap.bind('g n w', () => {
// Wiki
window.location = '/wiki';
});
window.Mousetrap.bind('g n a', () => {
// About
window.location = '/about';
});
window.Mousetrap.bind('g n s', () => {
// Shop
window.location = '/shop';
});
window.Mousetrap.bind('g n o', () => {
// Settings
window.location = '/settings';
});
window.Mousetrap.bind('g n r', () => {
// Repo
window.location = 'https://github.com/freecodecamp/freecodecamp/';
});
});

View File

@ -4953,7 +4953,7 @@
"description": [
"Random numbers are useful for creating random behavior.",
"JavaScript has a <code>Math.random()</code> function that generates a random decimal number between <code>0</code> (inclusive) and not quite up to <code>1</code> (exclusive). Thus <code>Math.random()</code> can return a <code>0</code> but never quite return a <code>1</code>",
"<strong>Note</strong><br>Like <a href='waypoint-storing-values-with-the-equal-operator' target='_blank'>Storing Values with the Equal Operator</a>, all function calls will be resolved before the <code>return</code> executes, so we can simply <code>return</code> the value of the <code>Math.random()</code> function.",
"<strong>Note</strong><br>Like <a href='storing-values-with-the-assignment-operator' target='_blank'>Storing Values with the Equal Operator</a>, all function calls will be resolved before the <code>return</code> executes, so we can simply <code>return</code> the value of the <code>Math.random()</code> function.",
"<h4>Instructions</h4>",
"Change <code>randomFraction</code> to return a random number instead of returning <code>0</code>."
],

View File

@ -17,10 +17,11 @@
"Click the \"Create workspace\" button.",
"Once C9 builds and loads your workspace, you should see a terminal window in the lower right hand corner. In this window use the following commands. You don't need to know what these mean at this point.",
"Install <code>git-it</code> with this command: <code>npm install -g git-it</code>",
"Before you start, read <a href='https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/' target='_blank'>Adding a new SSH key to your GitHub account</a> if you have two-factor authentication set up on your GitHub account.",
"Now start the tutorial by running <code>git-it</code>.",
"Note that you can resize the c9.io's windows by dragging their borders.",
"Make sure that you are always in your project's \"workspace\" directory. You can always navigate back to this directory by running this command: <code>cd ~/workspace</code>.",
"You can view this Node School module's source code on GitHub at <a href='https://github.com/jlord/git-it'>https://github.com/jlord/git-it</a>.",
"You can view this Node School module's source code on GitHub at <a href='https://github.com/jlord/git-it' target='_blank'>https://github.com/jlord/git-it</a>.",
"Complete \"Get Git\"",
"Complete \"Repository\"",
"Complete \"Commit to it\"",
@ -54,7 +55,7 @@
"Ahora inicia el tutorial ejecutando <code>git-it</code>",
"Puedes modificar el tamaño de las ventanas en c9.io arrastrándolas por el borde.",
"Asegúrate de que siempre te encuentres en el directorio \"workspace\" de tu proyecto. Puedes regresar a este directorio cuando quieras usando el comando: <code>cd ~/workspace</code>.",
"Puedes ver el código fuente de este módulo de Node School en GitHub visitando <a href='https://github.com/jlord/git-it'>https://github.com/jlord/git-it</a>.",
"Puedes ver el código fuente de este módulo de Node School en GitHub visitando <a href='https://github.com/jlord/git-it' target='_blank'>https://github.com/jlord/git-it</a>.",
"Completa \"Get Git\"",
"Completa \"Repository\"",
"Completa \"Commit to it\"",

View File

@ -84,7 +84,7 @@ export default function certificate(app) {
function verifyCert(certType, req, res, next) {
const { user } = req;
return user.getChallengeMap()
return user.getChallengeMap$()
.flatMap(() => certTypeIds[certType])
.flatMap(challenge => {
const {

View File

@ -1,24 +1,15 @@
var Rx = require('rx'),
async = require('async'),
moment = require('moment'),
request = require('request'),
debug = require('debug')('fcc:cntr:resources'),
constantStrings = require('../utils/constantStrings.json'),
labs = require('../resources/labs.json'),
testimonials = require('../resources/testimonials.json'),
secrets = require('../../config/secrets');
import request from 'request';
import constantStrings from '../utils/constantStrings.json';
import labs from '../resources/labs.json';
import testimonials from '../resources/testimonials.json';
import secrets from '../../config/secrets';
module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var Challenge = app.models.Challenge;
var Story = app.models.Story;
var Nonprofit = app.models.Nonprofit;
const router = app.loopback.Router();
const User = app.models.User;
router.get('/api/github', githubCalls);
router.get('/api/blogger', bloggerCalls);
router.get('/api/trello', trelloCalls);
router.get('/sitemap.xml', sitemap);
router.get('/chat', chat);
router.get('/coding-bootcamp-cost-calculator', bootcampCalculator);
router.get('/twitch', twitch);
@ -43,7 +34,6 @@ module.exports = function(app) {
router.get('/how-nonprofit-projects-work', howNonprofitProjectsWork);
router.get('/code-of-conduct', codeOfConduct);
router.get('/academic-honesty', academicHonesty);
router.get(
'/the-fastest-web-page-on-the-internet',
theFastestWebPageOnTheInternet
@ -51,118 +41,6 @@ module.exports = function(app) {
app.use(router);
function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD');
// TODO(berks): refactor async to rx
async.parallel({
users: function(callback) {
User.find(
{
where: { username: { nlike: '' } },
fields: { username: true }
},
function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(users, null, null, Rx.Scheduler.default)
.map(function(user) {
return user.username;
})
.toArray()
.subscribe(
function(usernames) {
callback(null, usernames);
},
callback
);
}
});
},
challenges: function(callback) {
Challenge.find(
{ fields: { name: true } },
function(err, challenges) {
if (err) {
debug('Challenge err: ', err);
callback(err);
} else {
Rx.Observable.from(challenges, null, null, Rx.Scheduler.default)
.map(function(challenge) {
return challenge.name;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
});
},
stories: function(callback) {
Story.find(
{ field: { link: true } },
function(err, stories) {
if (err) {
debug('Story err: ', err);
callback(err);
} else {
Rx.Observable.from(stories, null, null, Rx.Scheduler.default)
.map(function(story) {
return story.link;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
}
);
},
nonprofits: function(callback) {
Nonprofit.find(
{ field: { name: true } },
function(err, nonprofits) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(nonprofits, null, null, Rx.Scheduler.default)
.map(function(nonprofit) {
return nonprofit.name;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
});
}
}, function(err, results) {
if (err) {
return next(err);
}
return process.nextTick(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
now: now,
users: results.users,
challenges: results.challenges,
stories: results.stories,
nonprofits: results.nonprofits
});
});
}
);
}
function chat(req, res) {
res.redirect('https://gitter.im/FreeCodeCamp/FreeCodeCamp');
}

64
server/boot/sitemap.js Normal file
View File

@ -0,0 +1,64 @@
import moment from 'moment';
import { Scheduler, Observable } from 'rx';
import { timeCache, observeQuery } from '../utils/rx';
import { dasherize } from '../utils';
const cacheTimeout = [ 24, 'hours' ];
const appUrl = 'https://www.freecodecamp.com';
// getCachedObservable(
// app: ExpressApp,
// modelName: String,
// nameProp: String,
// map: (nameProp: String) => String
// ) => Observable[models]
function getCachedObservable(app, modelName, nameProp, map) {
return observeQuery(
app.models[modelName],
'find',
{ fields: { [nameProp]: true } }
)
.flatMap(models => {
return Observable.from(models, null, null, Scheduler.default);
})
.filter(model => !!model[nameProp])
.map(model => model[nameProp])
.map(map ? map : (x) => x)
.toArray()
::timeCache(cacheTimeout[0], cacheTimeout[1]);
}
export default function sitemapRouter(app) {
const router = app.loopback.Router();
const challenges$ = getCachedObservable(app, 'Challenge', 'dashedName');
const stories$ = getCachedObservable(app, 'Story', 'storyLink', dasherize);
const jobs$ = getCachedObservable(app, 'Job', 'id');
function sitemap(req, res, next) {
const now = moment(new Date()).format('YYYY-MM-DD');
return Observable.combineLatest(
challenges$,
stories$,
jobs$,
(
challenges,
stories,
jobs
) => ({ challenges, stories, jobs })
)
.subscribe(
({ challenges, stories, jobs }) => {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl,
now,
challenges,
stories,
jobs
});
},
next
);
}
router.get('/sitemap.xml', sitemap);
app.use(router);
}

View File

@ -25,15 +25,20 @@ export function saveInstance(instance) {
// alias saveInstance
export const saveUser = saveInstance;
export function observeQuery(Model, method, query) {
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
// observeQuery(Model: Object, methodName: String, query: Any) => Observable
export function observeQuery(Model, methodName, query) {
return Rx.Observable.fromNodeCallback(Model[methodName], Model)(query);
}
// observeMethod(
// context: Object, methodName: String
// ) => (query: Any) => Observable
export function observeMethod(context, methodName) {
return Rx.Observable.fromNodeCallback(context[methodName], context);
}
// timeChache(amount: Number, unit: String) => Observable
// must be bound to an observable instance
// timeCache(amount: Number, unit: String) => Observable
export function timeCache(time, unit) {
const source = this;
let cache;

View File

@ -3,6 +3,9 @@ block content
h1.text-center Settings for your Account
hr
h2.text-center Actions
.row
.col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(class = "nightMode-btn") Night Mode
.row
.col-xs-12
if (!user.isGithubCool)

View File

@ -8,7 +8,7 @@ html(lang='en')
include partials/scripts
block content
else
body.no-top-and-bottom-margins.full-screen-body-background
body.no-top-and-bottom-margins
include partials/scripts
include partials/navbar
include partials/flash

View File

@ -49,24 +49,23 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
lastmod= now
priority= 0.9
//- User
each user in users
url
loc #{appUrl}/#{user}
lastmod= now
changefreq weekly
priority= 0.5
each challenge in challenges
url
loc #{appUrl}/challenges/#{challenge.replace(/\s/g, '-')}
loc #{appUrl}/challenges/#{challenge}
lastmod= now
changefreq weekly
priority= 0.9
each story in stories
url
loc #{appUrl}/news/#{story.replace(/\s/g, '-')}
loc #{appUrl}/news/#{story}
lastmod= now
changefreq weekly
changefreq monthly
priority= 0.5
each job in jobs
url
loc #{appUrl}/jobs/#{job}
lastmod= now
changefreq monthly
priority= 0.5