Merge branch 'staging' of https://github.com/FreeCodeCamp/freecodecamp into greasan-translateDE

This commit is contained in:
greasan
2015-06-20 10:46:50 +02:00
70 changed files with 992 additions and 1561 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ bower_components
main.css
bundle.js
coverage
.remote-sync.json

View File

@ -7,9 +7,9 @@ Welcome to Free Code Camp's open source codebase!
=======================
#Note
We're currently very close to moving from express to loopback. As such, please keep in mind that the instructions here for setting up and running the project do not directly translate to the staging branch. Additionally, the file structure is quite a bit different. As always, the staging branch is the appropriate place to branch off of to fix/add something!
We're currently very close to moving from Express to Loopback. As such, please keep in mind that the instructions here for setting up and running the project do not directly translate to the staging branch. Additionally, the file structure is quite a bit different. As always, the staging branch is the appropriate place to branch off of to fix/add something!
Free Code Camp is an open-source community of busy people who learn to code, then build projects for nonprofits.
Free Code Camp is an open-source community of busy people who learn to code, then build projects for nonprofits.
Our campers (students) start by working through our free, self-paced, browser-based curriculum. Next, they build several practice projects. Finally, we pair two campers together with a stakeholder from a nonprofit organization, and help them build the solution the nonprofit has requested.
@ -17,7 +17,7 @@ Our campers (students) start by working through our free, self-paced, browser-ba
80% of our campers are over 25, and nearly a fifth of our campers are women.
This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Slack](http://freecodecamp.slack.com), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp).
This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Slack](http://freecodecamp.slack.com), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp).
[Join our community](http://www.freecodecamp.com/signin)!
@ -28,7 +28,7 @@ We welcome pull requests from Free Code Camp campers (our students) and seasoned
1. Check our [public Waffle Board](https://waffle.io/freecodecamp/freecodecamp).
2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Slack](http://freecodecamp.slack.com).
3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy.
3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy.
4. Create a branch specific to the issue or feature you are working on. Push your work to that branch. ([Need help with branching?](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches))
5. Name the branch something like `user-xxx` where user is your username and xxx is the issue number you are addressing.
6. You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [AirBnB's JavaScript Style Guide](https://github.com/airbnb/javascript). Please do not ignore any linting errors, as they are meant to **help** you. Make sure none of your JavaScript is longer than 80 characters per line.

3
client/README.md Normal file
View File

@ -0,0 +1,3 @@
This is the entry point for the client
Code that should only run on the client should be put here.
NOTE(berks): For react specific stuff this should be the entry point

20
client/index.js Normal file
View File

@ -0,0 +1,20 @@
import BrowserHistory from 'react-router/lib/BrowserHistory';
import debugFactory from 'debug';
import { Cat } from 'thundercats';
import AppFactory from '../common/app/appFactory';
const debug = debugFactory('fcc:client');
const DOMContianer = document.getElemenetById('#fCC');
const fcc = new Cat();
// returns an observable
fcc.render(AppFactory(BrowserHistory), DOMContianer)
.subscribe(
function() {
debug('react rendered');
},
function(err) {
debug('an error has occured', err.stack);
}
);

25
common/app/App.jsx Normal file
View File

@ -0,0 +1,25 @@
import React, { PropTypes } from 'react';
import Nav from './components/Nav';
import Footer from './components/Footer';
export default class extends React.Component {
constructor(props) {
super(props);
}
static displayName = 'FreeCodeCamp'
static propTypes = {
children: PropTypes.node
}
render() {
return (
<div>
<Nav />
{ this.props.children }
<Footer />
</div>
);
}
}

18
common/app/appFactory.jsx Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
import { Router, Route } from 'react-router';
// components
import App from './App.jsx';
import Jobs from './screens/App/screens/Jobs';
module.exports = function appFactory(history) {
return (
<Router history={ history }>
<Route handler={ App }>
<Route
component={ Jobs }
path='/jobs' />
</Route>
</Router>
);
};

View File

@ -0,0 +1,50 @@
import React from 'react';
import { Col, Row, Grid } from 'react-bootstrap';
import links from './links.json';
export default class extends React.Component {
static displayName = 'Footer'
renderLinks(mobile) {
return links.map(link => {
return (
<a
className={ link.className}
href={ link.href }
target={ link.target }>
{ this.renderContent(mobile, link.content) }
</a>
);
});
}
renderContent(mobile, content) {
if (mobile) {
return (
<span className='sr-only'>
content;
</span>
);
}
return content;
}
render() {
return (
<Grid className='fcc-footer'>
<Row>
<Col
className='hidden-xs hidden-sm'
xs={ 12 }>
{ this.renderLinks() }
</Col>
<Col
className='visible-xs visible-sm'
xs={ 12 }>
{ this.renderLinks(true) }
</Col>
</Row>
</Grid>
);
}
}

View File

@ -0,0 +1 @@
export { default as Footer } from './Footer.jsx';

View File

@ -0,0 +1,44 @@
[
{
"className": "ion-speakerphone",
"content": "&nbsp;Blog&nbsp;&nbsp;",
"href": "http://blog.freecodecamp.com",
"target": "_blank"
},
{
"className": "ion-social-twitch-outline",
"content": "&nbsp;Twitch&nbsp;&nbsp;",
"href": "http://www.twitch.tv/freecodecamp",
"target": "_blank"
},
{
"className": "ion-social-github",
"content": "&nbsp;Github&nbsp;&nbsp;",
"href": "http://github.com/freecodecamp",
"target": "_blank"
},
{
"className": "ion-social-twitter",
"content": "&nbsp;Twitter&nbsp;&nbsp;",
"href": "http://twitter.com/freecodecamp",
"target": "_blank"
},
{
"className": "ion-social-facebook",
"content": "&nbsp;Facebook&nbsp;&nbsp;",
"href": "http://facebook.com/freecodecamp",
"target": "_blank"
},
{
"className": "ion-information-circled",
"content": "&nbsp;About&nbsp;&nbsp;",
"href": "/learn-to-code",
"target": "_self"
},
{
"className": "ion-locked",
"content": "&nbsp;Privacy&nbsp;&nbsp;",
"href": "/privacy'",
"target": "_self"
}
]

View File

@ -1,31 +1,30 @@
var React = require('react'),
bootStrap = require('react-bootstrap'),
Navbar = bootStrap.Navbar,
Nav = bootStrap.Nav,
NavItem = bootStrap.NavItem,
NavItemFCC = require('./NavItem.jsx');
import React from 'react';
import { Nav, Navbar, NavItem } from 'react-bootstrap';
import NavItemFCC from './NavItem.jsx';
var NavBarComp = React.createClass({
export default class extends React.Component {
constructor(props) {
super(props);
}
propTypes: { signedIn: React.PropTypes.bool },
static displayName = 'Nav'
static propTypes = {
signedIn: React.PropTypes.bool
}
getDefaultProps: function() {
return { signedIn: false };
},
_renderBrand: function() {
renderBrand() {
var fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg';
return (
<a href='/'>
<img
src={ fCClogo }
alt='learn to code javascript at Free Code Camp logo'
className='img-responsive nav-logo' />
className='img-responsive nav-logo'
src={ fCClogo } />
</a>
);
},
}
_renderSignin: function() {
renderSignin() {
if (this.props.signedIn) {
return (
<NavItem
@ -36,27 +35,26 @@ var NavBarComp = React.createClass({
} else {
return (
<NavItemFCC
aClassName='btn signup-btn signup-btn-nav'
eventKey={ 2 }
href='/login'
aClassName='btn signup-btn signup-btn-nav'>
Sign In
href='/login'>
Sign In
</NavItemFCC>
);
}
},
render: function() {
}
render() {
return (
<Navbar
brand={ this._renderBrand() }
className='nav-height'
fixedTop={ true }
toggleNavKey={ 0 }
className='nav-height'>
toggleNavKey={ 0 }>
<Nav
right={ true }
className='hamburger-dropdown'
eventKey={ 0 }
className='hamburger-dropdown'>
right={ true }>
<NavItem
eventKey={ 1 }
href='/Challenges'>
@ -77,5 +75,4 @@ var NavBarComp = React.createClass({
</Navbar>
);
}
});
module.exports = NavBarComp;
}

View File

@ -0,0 +1 @@
export { default as Nav } from './Nav.jsx';

View File

@ -0,0 +1 @@
things like NavBar and Footer go here

View File

@ -0,0 +1 @@
in case we ever want an admin panel

View File

@ -0,0 +1 @@
This folder contains things relative to the bonfires screens

View File

@ -0,0 +1,6 @@
export default {
path: 'bonfires/(:bonfireName)',
getComponents(cb) {
// TODO(berks): add bonfire component
}
};

View File

@ -0,0 +1 @@
future home of FAVS app

View File

@ -0,0 +1 @@
This folder contains everything relative to Jobs board

View File

@ -0,0 +1,15 @@
import { Actions } from 'thundercats';
export default class JobsActions extends Actions {
constructor() {
super();
}
static displayName = 'JobsActions'
getJob(id) {
return { id };
}
getJobs(params) {
return { params };
}
}

View File

@ -0,0 +1,28 @@
import React, { PropTypes } from 'react';
import { createContainer } from 'thundercats';
import { Grid, Row } from 'react-bootstrap';
@createContainer({
store: 'JobsStore'
})
export default class extends React.Component {
constructor(props) {
super(props);
}
static displayName = 'Jobs'
static propTypes = {
jobs: PropTypes.array
}
render() {
return (
<Grid>
<Row>
foo
</Row>
</Grid>
);
}
}

View File

@ -0,0 +1,13 @@
import React, { PropTypes } from 'react';
export default class extends React.Component {
constructor(props) {
super(props);
}
static displayName = 'JobsList'
static propTypes = {}
render() {
return null;
}
}

View File

@ -0,0 +1,9 @@
import { Store } from 'thundercats';
export default class JobsStore extends Store {
constructor(cat) {
super();
let JobsActions = cat.getActions('JobsActions');
}
static displayName = 'JobsStore'
}

View File

@ -0,0 +1,19 @@
/*
* show: /jobs
* showOne: /jobs/:id
* edit /jobs/:id
* delete /jobs/:id
* createOne /jobs/new
*/
export default {
path: '/jobs/(:jobId)',
getComponents(cb) {
require.ensure([], require => {
cb(null, [
require('./components/Jobs')
]);
});
}
};

View File

@ -0,0 +1,11 @@
export default {
path: '/',
getRoutes(cb) {
require.ensure([], require => {
cb(null, [
// require('./Bonfires'),
require('./Jobs')
]);
});
}
};

View File

@ -0,0 +1 @@
Here is where all components that are shared between multiple views

View File

@ -1,19 +0,0 @@
var React = require('react'),
RouteHandler = require('react-router').RouteHandler,
// ## components
Nav = require('./nav'),
Footer = require('./footer');
var App = React.createClass({
render: function() {
return (
<div>
<Nav />
<RouteHandler />
<Footer />
</div>
);
}
});
module.exports = App;

View File

@ -1,34 +0,0 @@
var React = require('react'),
// react router
Router = require('react-router'),
Route = Router.Route,
// NotFound = Router.NotFoundRoute,
DefaultRoute = Router.DefaultRoute,
// # Components
App = require('./App.jsx'),
Bonfires = require('./bonfires');
var routes = (
<Route
name='app'
path='/'
handler={ App }>
<Route
name='bonfires'
path='/bonfires/?:bonfireName?'
handler={ Bonfires } />
<DefaultRoute
handler={ Bonfires } />
</Route>
);
module.exports = function(Location) {
return Router.create({
routes: routes,
location: Location
});
};

View File

@ -1 +0,0 @@
module.exports = require('./Bonfires.jsx');

View File

@ -1,34 +0,0 @@
var debug = require('debug')('freecc:context'),
BonfireActions = require('../bonfires/Actions'),
BonfireStore = require('../bonfires/Store');
var {
Action,
waitFor
} = require('thundercats');
var actions = Action.createActions([
'setContext',
'renderToUser'
]);
actions
.setContext
.filter(function(ctx) {
return ctx.state.path.indexOf('/bonfire') !== -1;
})
.subscribe(function(ctx) {
debug('set ctx');
BonfireActions.getBonfire(ctx.state.params);
waitFor(BonfireStore)
.firstOrDefault()
.catch(function(err) {
// handle timeout error
debug('err', err);
})
.subscribe(function() {
actions.renderToUser(ctx);
});
});
module.exports = actions;

View File

@ -1,18 +0,0 @@
var Store = require('thundercats').Store,
ContextActions = require('./Actions');
var ContextStore = Store.create({
getInitialValue: function() {
return {};
},
getOperations: function() {
return ContextActions
.renderToUser
.map(function(ctx) {
return { value: ctx };
});
}
});
module.exports = ContextStore;

View File

@ -1,106 +0,0 @@
var React = require('react');
var Footer = React.createClass({
render: function() {
return (
<div className='fcc-footer'>
<div className='col-xs-12 hidden-xs hidden-sm'>
<a
href='http://blog.freecodecamp.com'
target='_blank' className='ion-speakerphone'>
&nbsp;Blog&nbsp;&nbsp;
</a>
<a
ref='http://www.twitch.tv/freecodecamp'
target='_blank' className='ion-social-twitch-outline'>
&nbsp;Twitch&nbsp;&nbsp;
</a>
<a
href='http://github.com/freecodecamp'
target='_blank'
className='ion-social-github'>
&nbsp;Github&nbsp;&nbsp;
</a>
<a
href='http://twitter.com/freecodecamp'
target='_blank' className='ion-social-twitter'>
&nbsp;Twitter&nbsp;&nbsp;
</a>
<a
href='http://facebook.com/freecodecamp'
target='_blank'
className='ion-social-facebook'>
&nbsp;Facebook&nbsp;&nbsp;
</a>
<a
ref='/learn-to-code'
className='ion-information-circled'>
&nbsp;About&nbsp;&nbsp;
</a>
<a
href='/privacy'
className='ion-locked'>
&nbsp;Privacy&nbsp;&nbsp;
</a>
</div>
<div className='col-xs-12 visible-xs visible-sm'>
<a
href='http://blog.freecodecamp.com'
target='_blank' className='ion-speakerphone'>
<span className='sr-only'>
Free Code Camp\'s Blog
</span>
</a>
<a
href='http://www.twitch.tv/freecodecamp'
target='_blank'
className='ion-social-twitch-outline'>
<span className='sr-only'>
Free Code Camp Live Pair Programming on Twitch.tv
</span>
</a>
<a
href='http://github.com/freecodecamp'
target='_blank'
className='ion-social-github'>
<span className='sr-only'>
Free Code Camp on GitHub
</span>
</a>
<a
href='http://twitter.com/freecodecamp'
target='_blank'
className='ion-social-twitter'>
<span className='sr-only'>
Free Code Camp on Twitter
</span>
</a>
<a
href='http://facebook.com/freecodecamp'
target='_blank'
className='ion-social-facebook'>
<span className='sr-only'>
Free Code Camp on Facebook
</span>
</a>
<a
href='/learn-to-code'
className='ion-information-circled'>
<span className='sr-only'>
About Free Code Camp
</span>
</a>
<a
href='/privacy'
className='ion-locked'>
<span className='sr-only'>
Free Code Camp's Privacy Policy
</span>
</a>
</div>
</div>
);
}
});
module.exports = Footer;

View File

@ -1 +0,0 @@
module.exports = require('./Footer.jsx');

View File

@ -1 +0,0 @@
module.exports = require('./Nav.jsx');

View File

@ -70,7 +70,7 @@ gulp.task('lint', function() {
.pipe(eslint.format());
});
gulp.task('build', function() {
gulp.task('less', function() {
return gulp.src('./public/css/*.less')
.pipe(less({
paths: [ path.join(__dirname, 'less', 'includes') ]
@ -78,4 +78,10 @@ gulp.task('build', function() {
.pipe(gulp.dest('./public/css/'));
});
gulp.task('default', ['build', 'serve', 'sync']);
gulp.task('build', ['less']);
gulp.task('watch', ['less', 'serve', 'sync'], function() {
gulp.watch('./public/css/*.less', ['less']);
});
gulp.task('default', ['less', 'serve', 'sync', 'watch']);

View File

@ -47,7 +47,6 @@
"express-session": "~1.9.2",
"express-validator": "~2.8.0",
"font-awesome": "~4.3.0",
"forcedomain": "~0.4.0",
"forever": "~0.14.1",
"frameguard": "^0.2.2",
"github-api": "~0.7.0",
@ -59,7 +58,7 @@
"less": "~1.7.5",
"less-middleware": "~2.0.1",
"lodash": "^3.9.3",
"loopback": "^2.18.0",
"loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password",
"loopback-boot": "^2.8.0",
"loopback-component-passport": "git://github.com/FreeCodeCamp/loopback-component-passport.git#feature/emailOptional",
"loopback-connector-mongodb": "^1.10.0",
@ -72,7 +71,6 @@
"node-uuid": "^1.4.3",
"nodemailer": "~1.3.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth": "^0.2.0",
"passport-google-oauth2": "^0.1.6",
"passport-linkedin-oauth2": "^1.2.1",
"passport-local": "^1.0.0",
@ -80,9 +78,13 @@
"passport-twitter": "^1.0.3",
"pmx": "^0.3.16",
"ramda": "~0.10.0",
"react": "^0.13.3",
"react-bootstrap": "^0.23.4",
"react-router": "^1.0.0-beta1",
"request": "~2.53.0",
"rx": "^2.5.3",
"sanitize-html": "~1.6.1",
"thundercats": "^2.0.0-rc6",
"twit": "~1.1.20",
"uglify-js": "~2.4.15",
"validator": "~3.22.1",

12
pm2Start.js Normal file
View File

@ -0,0 +1,12 @@
var pm2 = require('pm2');
pm2.connect(function() {
pm2.start({
name: 'server',
script: 'server/server.js',
exec_mode: 'cluster',
instances: '2',
max_memory_restart: '900M'
}, function(err, apps) {
pm2.disconnect();
});
});

View File

@ -1123,13 +1123,8 @@ hr {
// Calculator styles
.hidden-initially {
visibility: hidden;
}
#four p{
font-size: .6em;
color: black;
.initially-hidden {
display: none;
}
.chart rect {
@ -1137,8 +1132,7 @@ hr {
}
.chart text {
fill: #121401;
font: 13px sans-serif;
font-size: 14px;
text-anchor: end;
}

268
public/js/calculator.js Normal file
View File

@ -0,0 +1,268 @@
$(document).ready(function () {
var bootcamps = ''
$.getJSON('/coding-bootcamp-cost-calculator.json', function(data) {
bootcamps = data;
});
var city = "";
$("body").data("state", "stacked");
$('#city-buttons').on("click", "button", function () {
$(this).addClass('animated pulse');
city = $(this).attr("id");
$('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}) + ', and making $_______, your true costs will be:');
setTimeout(function () {
$('#city-buttons').hide();
$('#income').addClass('animated fadeIn').show();
}, 1000);
});
$('#income').on("click", "button", function () {
$(this).addClass('animated pulse');
setTimeout(function () {
$('#income').hide();
$('#chart').addClass('animated fadeIn').show();
$('#chart-controls').addClass('animated fadeIn').show();
$('#explanation').addClass('animated fadeIn').show();
}, 1000);
var lastYearsIncome = parseInt($(this).attr("id"));
$('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}) + ', and making $' + lastYearsIncome.toString().replace(/0000$/, '0,000') + ', your true costs will be:');
var categoryNames = ['Lost Wages', 'Financing Cost', 'Housing Cost', 'Tuition / Wage Garnishing'];
bootcamps.forEach(function (camp) {
var x0 = 0;
if (camp.cities.indexOf(city) > -1) {
weeklyHousing = 0;
} else {
weeklyHousing = +camp.housing;
}
camp.mapping = [{
name: camp.name,
label: 'Tuition / Wage Garnishing',
value: +camp.cost,
x0: x0,
x1: x0 += +camp.cost
}, {
name: camp.name,
label: 'Financing Cost',
value: +Math.floor(camp.cost * .09519),
x0: +camp.cost,
x1: camp.finance ? x0 += +Math.floor(camp.cost * .09519) : 0
}, {
name: camp.name,
label: 'Housing Cost',
value: +weeklyHousing * camp.weeks,
x0: camp.finance ? +Math.floor(camp.cost * 1.09519) : camp.cost,
x1: x0 += weeklyHousing * camp.weeks
}, {
name: camp.name,
label: 'Lost Wages',
value: +(Math.floor(camp.weeks * lastYearsIncome / 50)),
x0: camp.finance ? +(Math.floor(camp.cost * 1.09519) + weeklyHousing * camp.weeks) : +camp.cost + weeklyHousing * camp.weeks,
x1: x0 += +(Math.floor(camp.weeks * lastYearsIncome / 50))
}];
camp.total = camp.mapping[camp.mapping.length - 1].x1;
});
bootcamps.sort(function (a, b) {
return a.total - b.total;
});
maxValue = 0;
bootcamps.forEach(function (camp) {
camp.mapping.forEach(function (elem) {
if (elem.value > maxValue) {
maxValue = elem.value;
}
});
});
var xStackMax = d3.max(bootcamps, function (d) {
return d.total;
}), //Scale for Stacked
xGroupMax = bootcamps.map(function (camp) {
return camp.mapping.reduce(function (a, b) {
return a.value > b.value ? a.value : b.value;
});
}).reduce(function (a, b) {
return a > b ? a : b;
});
var margin = {
top: 30,
right: 60,
bottom: 50,
left: 140
},
width = 800 - margin.left - margin.right,
height = 1200 - margin.top - margin.bottom;
var barHeight = 20;
var xScale = d3.scale.linear()
.domain([0, xStackMax])
.rangeRound([0, width]);
var y0Scale = d3.scale.ordinal()
.domain(bootcamps.map(function (d) {
return d.name;
}))
.rangeRoundBands([0, height], .1);
var y1Scale = d3.scale.ordinal()
.domain(categoryNames).rangeRoundBands([0, y0Scale.rangeBand()]);
var color = d3.scale.ordinal()
.range(["#215f1e", "#5f5c1e", "#1e215f", "#5c1e5f"])
.domain(categoryNames);
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var selection = svg.selectAll(".series")
.data(bootcamps)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) {
return "translate(0," + y0Scale(d.name) + ")";
});
var rect = selection.selectAll("rect")
.data(function (d) {
return d.mapping;
})
.enter().append("rect")
.attr("x", 0)
.attr("width", 0)
.attr("height", y0Scale.rangeBand())
.style("fill", function (d) {
return color(d.label);
})
.style("stroke", "white")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
});
rect.transition()
.delay(function (d, i) {
return i * 10;
})
.attr("x", function (d) {
return xScale(d.x0);
})
.attr("width", function (d) {
return xScale((d.x1) - (d.x0));
});
d3.selectAll("#transform").on("click", function () {
$('#transform').addClass('animated pulse');
change();
setTimeout(function () {
$('#transform').removeClass('animated pulse');
}, 1000);
});
function change() {
if ($("body").data("state") === "stacked") {
transitionGrouped();
$("body").data("state", "grouped");
} else {
transitionStacked();
$("body").data("state", "stacked");
}
}
function transitionGrouped() {
xScale.domain = ([0, xGroupMax]);
rect.transition()
.duration(500)
.delay(function (d, i) {
return i * 10;
})
.attr("width", function (d) {
return xScale((d.x1) - (d.x0));
})
.transition()
.attr("y", function (d) {
return y1Scale(d.label);
})
.attr("x", 0)
.attr("height", y1Scale.rangeBand())
}
function transitionStacked() {
xScale.domain = ([0, xStackMax]);
rect.transition()
.duration(500)
.delay(function (d, i) {
return i * 10;
})
.attr("x", function (d) {
return xScale(d.x0);
})
.transition()
.attr("y", function (d) {
return y0Scale(d.label);
})
.attr("height", y0Scale.rangeBand())
}
//axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y0Scale)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", 300)
.attr("y", 35)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Cost in $USD");
//tooltips
function removePopovers() {
$('.popover').each(function () {
$(this).remove();
});
}
function showPopover(d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html: true,
content: function () {
return d.label +
"<br/>$" +
d3.format(",")(d.value ? d.value : d.x1 - d.x0);
}
});
$(this).popover('show')
}
//legends
var legend = svg.selectAll(".legend")
.data(categoryNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) {
return "translate(30," + i * y0Scale.rangeBand() * 1.1 + ")";
});
legend.append("rect")
.attr("x", width - y0Scale.rangeBand())
.attr("width", y0Scale.rangeBand())
.attr("height", y0Scale.rangeBand())
.style("fill", color)
.style("stroke", "white");
legend.append("text")
.attr("x", width - y0Scale.rangeBand() * 1.2)
.attr("y", 12)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) {
return d;
});
});
});

View File

@ -18,7 +18,7 @@ editor.setSize("100%", "auto");
// Hijack tab key to enter two spaces intead
editor.setOption("extraKeys", {
Tab: function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("add");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -26,7 +26,7 @@ editor.setOption("extraKeys", {
}
},
"Shift-Tab": function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("subtract");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -40,12 +40,75 @@ editor.setOption("extraKeys", {
});
/*
Local Storage Update System By Andrew Cay(Resto)
localBonfire: 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
Two extra functionalities:
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 = {
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
};
localBonfire.getEditorValue = function(){
return localStorage.getItem(localBonfire.keyValue);
};
localBonfire.getStampTime = function(){
//localstorage always saves as strings.
return Number.parseInt( localStorage.getItem(localBonfire.keyStamp) );
};
localBonfire.isAlive = function(){// returns true if IDE was edited within expire time
return ( Date.now() - localBonfire.getStampTime() < localBonfire.stampExpireTime );
};
localBonfire.updateStorage = function(){
if(typeof(Storage) !== undefined) {
var stamp = Date.now(),
value = editor.getValue();
localStorage.setItem(localBonfire.keyValue, value);
localStorage.setItem(localBonfire.keyStamp, stamp);
} else {
if( debugging ){
console.log('no web storage');
}
}
localBonfire.updateTimeoutId = null;
};
// ANONYMOUS 1 TIME UPDATE VERSION
(function(){
var savedVersion = localStorage.getItem('saveVersion');
if( savedVersion === null ){
localStorage.setItem(localBonfire.keyVersion, localBonfire.version);//just write current version
}else{
//do checking if not current version
if( savedVersion !== localBonfire.version ){
//update version
}
}
})();
editor.on('keyup', function(codMir, event){
window.clearTimeout(localBonfire.updateTimeoutId);
localBonfire.updateTimeoutId = window.setTimeout(localBonfire.updateStorage, localBonfire.updateWait);
});
var attempts = 0;
if (attempts) {
attempts = 0;
}
var resetEditor = function() {
editor.setValue(allSeeds);
localBonfire.updateStorage();
};
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
lineNumbers: false,
@ -61,7 +124,10 @@ codeOutput.setValue('/**\n' +
' */');
codeOutput.setSize("100%", "100%");
var info = editor.getScrollInfo();
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
var after = editor.charCoords({
line: editor.getCursor().line + 1,
ch: 0
}, "local").top;
if (info.top + info.clientHeight < after)
editor.scrollTo(null, after - info.clientHeight + 3);
@ -71,20 +137,20 @@ var editorValue;
var challengeSeed = challengeSeed || null;
var tests = tests || [];
var allSeeds = '';
(function() {
challengeSeed.forEach(function(elem) {
allSeeds += elem + '\n';
allSeeds += elem + '\n';
});
})();
editorValue = allSeeds;
editorValue = (localBonfire.isAlive())? localBonfire.getEditorValue() : allSeeds;
myCodeMirror.setValue(editorValue);
function doLinting () {
editor.operation(function () {
function doLinting() {
editor.operation(function() {
for (var i = 0; i < widgets.length; ++i)
editor.removeLineWidget(widgets[i]);
widgets.length = 0;
@ -106,14 +172,14 @@ function doLinting () {
});
};
$('#submitButton').on('click', function () {
$('#submitButton').on('click', function() {
bonfireExecute();
});
function bonfireExecute() {
attempts++;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests= null;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests = null;
$('#codeOutput').empty();
var userJavaScript = myCodeMirror.getValue();
userJavaScript = removeComments(userJavaScript);
@ -145,16 +211,23 @@ var scrapeTests = function(userJavaScript) {
}
var counter = 0;
var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/);
var regex = new RegExp(
/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/
);
var match = regex.exec(userJavaScript);
while (match != null) {
var replacement = '//' + counter + testSalt;
userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length);
userJavaScript = userJavaScript.substring(0, match.index) + replacement +
userJavaScript.substring(match.index + match[0].length);
if (!userTests) {
userTests= [];
userTests = [];
}
userTests.push({"text": match[0], "line": counter, "err": null});
userTests.push({
"text": match[0],
"line": counter,
"err": null
});
counter++;
match = regex.exec(userJavaScript);
}
@ -176,17 +249,22 @@ var createTestDisplay = function() {
if (pushed) {
userTests.pop();
}
for (var i = 0; i < userTests.length;i++) {
for (var i = 0; i < userTests.length; i++) {
var test = userTests[i];
var testDoc = document.createElement("div");
if (test.err != null) {
console.log('Should be displaying bad tests');
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" + test.text + "</div><div class='col-xs-10 test-output wrappable'>" + test.err + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" +
test.text + "</div><div class='col-xs-10 test-output wrappable'>" +
test.err + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
} else {
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" + test.text + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" +
test.text + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
}
};
@ -208,18 +286,21 @@ var runTests = function(err, data) {
pushed = false;
$('#testSuite').children().remove();
if (err && userTests.length > 0) {
userTests= [{text:"Program Execution Failure", err: "No user tests were run."}];
userTests = [{
text: "Program Execution Failure",
err: "No user tests were run."
}];
createTestDisplay();
} else if (userTests) {
userTests.push(false);
pushed = true;
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray, __testArray){
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray,
__testArray) {
try {
if (chaiTestFromJSON) {
var output = eval(reassembleTest(chaiTestFromJSON, data));
debugger;
}
} catch(error) {
} catch (error) {
allTestsPassed = false;
__testArray[indexOfTestArray].err = error.message;
} finally {
@ -239,12 +320,12 @@ var runTests = function(err, data) {
function showCompletion() {
var time = Math.floor(Date.now()) - started;
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +', Attempts: ' + attempts);
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +
', Attempts: ' + attempts);
var bonfireSolution = myCodeMirror.getValue();
var didCompleteWith = $('#completed-with').val() || null;
$.post(
'/completed-bonfire/',
{
'/completed-bonfire/', {
challengeInfo: {
challengeId: challenge_Id,
challengeName: challenge_Name,
@ -252,10 +333,11 @@ function showCompletion() {
challengeType: challengeType,
solution: bonfireSolution
}
}, function(res) {
},
function(res) {
if (res) {
$('#complete-courseware-dialog').modal('show');
$('#complete-courseware-dialog').keydown(function (e) {
$('#complete-courseware-dialog').keydown(function(e) {
if (e.ctrlKey && e.keyCode == 13) {
$('#next-courseware-button').click();
}

View File

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

View File

@ -230,7 +230,7 @@
"permAlone('aab');"
],
"tests": [
"expect(permAlone('aab')).to.be.a.number;",
"expect(permAlone('aab')).to.be.a('number');",
"expect(permAlone('aab')).to.equal(2);",
"expect(permAlone('aaa')).to.equal(0);",
"expect(permAlone('aabb')).to.equal(8);",

View File

@ -180,7 +180,8 @@
"assert.deepEqual(palindrome(\"not a palindrome\"), false);",
"assert.deepEqual(palindrome(\"A man, a plan, a canal. Panama\"), true);",
"assert.deepEqual(palindrome(\"never odd or even\"), true);",
"assert.deepEqual(palindrome(\"nope\"), false);"
"assert.deepEqual(palindrome(\"nope\"), false);",
"assert.deepEqual(palindrome(\"almostomla\"), false);"
],
"challengeSeed": [
"function palindrome(str) {",
@ -422,6 +423,7 @@
],
"tests": [
"expect(truncate('A-tisket a-tasket A green and yellow basket', 11)).to.eqls('A-tisket...');",
"expect(truncate('Peter Piper picked a peck of pickled peppers', 14)).to.eqls('Peter Piper...');",
"assert(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length) === 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is = length');",
"assert.strictEqual(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length + 2), 'A-tisket a-tasket A green and yellow basket', 'should not truncate if string is < length');"
],
@ -656,7 +658,10 @@
],
"tests": [
"assert.deepEqual(destroyer([1, 2, 3, 1, 2, 3], 2, 3), [1, 1], 'should remove correct values from an array');",
"assert.deepEqual(destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3), [1, 5, 1], 'should remove correct values from an array');"
"assert.deepEqual(destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3), [1, 5, 1], 'should remove correct values from an array');",
"assert.deepEqual(destroyer([3, 5, 1, 2, 2], 2, 3, 5), [1], 'should accept more than two additional arguments');",
"assert.deepEqual(destroyer([2, 3, 2, 3], 2, 3), [], 'should remove correct values from an array');",
"assert.deepEqual(destroyer(['tree', 'hamburger', 53], 'tree', 53), ['hamburger'], 'should handle NaN-elements');"
],
"MDNlinks": [
"Arguments object",
@ -754,8 +759,8 @@
},
{
"id": "a5de63ebea8dbee56860f4f2",
"name": "bonfire-diff-two-arrays",
"dashedName": "Bonfire: Diff Two Arrays",
"name": "Bonfire: Diff Two Arrays",
"dashedName": "bonfire-diff-two-arrays",
"difficulty": "2.01",
"description": [
"Compare two arrays and return a new array with any items not found in both of the original arrays.",
@ -853,7 +858,7 @@
"difficulty": "2.03",
"description": [
"Perform a search and replace on the sentence using the arguments provided and return the new sentence.",
"First argument is the sentence the perform the search and replace on.",
"First argument is the sentence to perform the search and replace on.",
"Second argument is the word that you will be replacing (before).",
"Third argument is what you will be replacing the second argument with (after).",
"NOTE: Preserve the case of the original word when you are replacing it. For example if you mean to replace the word 'Book' with the word 'dog', it should be replaced as 'Dog'",
@ -990,7 +995,8 @@
"expect(fearNotLetter('yz')).to.be.undefined;"
],
"MDNlinks": [
"String.charCodeAt()"
"String.charCodeAt()",
"String.fromCharCode()"
],
"challengeType": 5,
"nameCn": "",
@ -1104,6 +1110,10 @@
],
"tests": [
"assert.strictEqual(convert('Dolce & Gabbana'), 'Dolce &amp; Gabbana', 'should escape characters');",
"assert.strictEqual(convert('Hamburgers < Pizza < Tacos'), 'Hamburgers &lt; Pizza &lt; Tacos', 'should escape characters');",
"assert.strictEqual(convert('Sixty > twelve'), 'Sixty &gt; twelve', 'should escape characters');",
"assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in &quot;quotation marks&quot;', 'should escape characters');",
"assert.strictEqual(convert(\"Shindler's List\"), 'Shindler&apos;s List', 'should escape characters');",
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
],
"MDNlinks": [

View File

@ -974,7 +974,7 @@
"assert($('img').hasClass('thick-green-border'), 'Your <code>img</code> element should have the class \"thick-green-border\".')",
"assert($('img').hasClass('thick-green-border') && parseInt($('img').css('border-top-width')), 'Give your image a border width of 10px.')",
"assert(new RegExp('solid', 'gi').test(editor), 'Give your image a border style of \"solid\".')",
"assert($('img').css('border-left-color') === 'rgb(0, 128, 0)', 'Your <code>img</code> element should be green.')"
"assert($('img').css('border-left-color') === 'rgb(0, 128, 0)', 'The border around your <code>img</code> element should be green.')"
],
"challengeSeed": [
"<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>",
@ -1165,7 +1165,7 @@
],
"tests": [
"assert((/cat photos/gi).test($('a').text()), 'Your <code>a</code> element should have the anchor text of \"cat photos\"')",
"assert($('a').filter(function(index) { return /com/gi.test($('a').attr('href')); }).length > 0, 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(/http:\\/\\/catphotoapp\\.com/gi.test($('a').attr('href')), 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(editor.match(/<\\/a>/g) && editor.match(/<\\/a>/g).length === editor.match(/<a/g).length, 'Make sure your <code>a</code> element has a closing tag.')"
],
"challengeSeed": [

View File

@ -626,7 +626,7 @@
"The \"row\" class is applied to a <code>div</code>, and the buttons themselves can be wrapped within it."
],
"tests": [
"assert($('div.row:has(button)'), 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.row:has(button)').length > 0, 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.col-xs-4:has(button)').length > 2, 'Each of your Bootstrap buttons should be wrapped within its own a <code>div</code> element with the class \"col-xs-4\".')",
"assert(editor.match(/<\\/button>/g) && editor.match(/<button/g) && editor.match(/<\\/button>/g).length === editor.match(/<button/g).length, 'Make sure each of your <code>button</code> elements has a closing tag.')",
"assert(editor.match(/<\\/div>/g) && editor.match(/<div/g) && editor.match(/<\\/div>/g).length === editor.match(/<div/g).length, 'Make sure each of your <code>div</code> elements has a closing tag.')"

View File

@ -56,7 +56,7 @@
"<span class='text-info'>Hint:</span> Here's an example call to Twitch.tv's JSON API: <code>https://api.twitch.tv/kraken/streams/freecodecamp</code>.",
"<span class='text-info'>Hint:</span> The relevant documentation about this API call is here: <a href='https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel' target='_blank'>https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel</a>.",
"<span class='text-info'>Hint:</span> Here's an array of the Twitch.tv usernames of people who regularly stream coding: <code>[\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]</code>",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -87,7 +87,7 @@
"Here are the <a href='http://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a> you must enable, and optional bonus user stories:",
"<span class='text-info'>User Story:</span> As a user, I can click a button to show me a new random quote.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can press a button to tweet out a quote.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -120,7 +120,7 @@
"<span class='text-info'>Bonus User Story:</span> As a user, I can see an icon depending on the temperature..",
"<span class='text-info'>Bonus User Story:</span> As a user, I see a different background image depending on the temperature (e.g. snowy mountain, hot desert).",
"<span class='text-info'>Bonus User Story:</span> As a user, I can push a button to toggle between Fahrenheit and Celsius.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -154,7 +154,7 @@
"<span class='text-info'>User Story:</span> As a user, I can click a link to go directly to the post's discussion page.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can see how many upvotes each story has.",
"<span class='text-info'>Hint:</span> Here's the Camper News Hot Stories API endpoint: <code>http://www.freecodecamp.com/stories/hotStories</code>.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -187,7 +187,7 @@
"<span class='text-info'>Bonus User Story:</span>As a user, I can click a button to see a random Wikipedia entry.",
"<span class='text-info'>Bonus User Story:</span>As a user, when I type in the search box, I can see a dropdown menu with autocomplete options for matching Wikipedia entries.",
"<span class='text-info'>Hint:</span> Here's an entry on using Wikipedia's API: <code>http://www.mediawiki.org/wiki/API:Main_page</code>.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],

View File

@ -863,7 +863,7 @@
"description": [
"<div class=\"col-xs-12 col-sm-10 col-sm-offset-1\">",
" <p class='h2'>Translation is an all-or-nothing proposal.</h2>",
" <p class='large-p'>We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that langauge.</p>",
" <p class='large-p'>We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that language.</p>",
" <p class='large-p'>In addition to translating these initially, we'll also need to maintain the translation as the challenges are gradually updated.</p>",
" <p class='large-p'>If you're able to help us, you can join our <a href='https://trello.com/b/m7zhwXka/fcc-translation' target='_blank'>Trello board</a> by sending @quincylarson your email address in Slack.</p>",
"</div>"

View File

@ -32,50 +32,30 @@
var R = require('ramda'),
utils = require('../utils'),
userMigration = require('../utils/middleware').userMigration,
MDNlinks = require('../../seed/bonfireMDNlinks');
userMigration = require('../utils/middleware').userMigration;
var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.getChallengeMapWithIds();
var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
function getMDNlinks(links) {
// takes in an array of links, which are strings
var populatedLinks = [];
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
if (links) {
links.forEach(function (value) {
populatedLinks.push(MDNlinks[value]);
});
}
return populatedLinks;
}
var getMDNLinks = utils.getMDNLinks;
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
var User = app.models.User;
router.get(
'/challenges/next-challenge',
userMigration,
returnNextChallenge
);
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
// the follow routes are covered by userMigration
router.use(userMigration);
router.get('/challenges/next-challenge', returnNextChallenge);
router.get('/challenges/:challengeName', returnIndividualChallenge);
router.get('/challenges/', returnCurrentChallenge);
router.get('/map', challengeMap);
app.use(router);
function returnNextChallenge(req, res, next) {
@ -295,7 +275,7 @@ module.exports = function(app) {
bonfires: challenge,
challengeId: challenge.id,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNlinks(challenge.MDNlinks),
MDNlinks: getMDNLinks(challenge.MDNlinks),
challengeType: challenge.challengeType
});
}
@ -547,4 +527,51 @@ module.exports = function(app) {
});
}
}
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
// backwards compatibility
return (challenge.id || challenge._id);
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
// backwards compatibility hack
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -1,66 +0,0 @@
var R = require('ramda'),
// debug = require('debug')('freecc:cntr:challengeMap'),
utils = require('./../utils'),
middleware = require('../utils/middleware');
module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
return (challenge.id || challenge._id); // backwards compatibility
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
//backwards compatibility hack
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -9,7 +9,7 @@ module.exports = function(app) {
function nonprofitsDirectory(req, res, next) {
Nonprofit.find(
{ where: { estimatedHours: { $gt: 0 } } },
{ where: { estimatedHours: { gt: 0 } } },
function(err, nonprofits) {
if (err) { return next(err); }

View File

@ -27,8 +27,8 @@ module.exports = function(app) {
router.post('/get-help', getHelp);
router.post('/get-pair', getPair);
router.get('/chat', chat);
router.get('/bootcamp-calculator', bootcampCalculator);
router.get('/bootcamp-calculator.json', bootcampCalculatorJson);
router.get('/coding-bootcamp-cost-calculator', bootcampCalculator);
router.get('/coding-bootcamp-cost-calculator.json', bootcampCalculatorJson);
router.get('/twitch', twitch);
router.get('/pmi-acp-agile-project-managers', agileProjectManagers);
router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm);

View File

@ -18,5 +18,13 @@ module.exports = function(app) {
);
});
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
};

View File

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

View File

@ -41,6 +41,7 @@ module.exports = {
},
'google-login': {
provider: 'google',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
@ -54,6 +55,7 @@ module.exports = {
},
'google-link': {
provider: 'google',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
@ -94,7 +96,7 @@ module.exports = {
},
'linkedin-login': {
provider: 'linkedin',
authScheme: 'oauth',
authScheme: 'oauth2',
module: 'passport-linkedin-oauth2',
authPath: '/auth/linkedin',
callbackURL: '/auth/linkedin/callback',
@ -103,12 +105,15 @@ module.exports = {
failureRedirect: failureRedirect,
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
scope: ['r_fullprofile', 'r_emailaddress'],
scope: ['r_basicprofile', 'r_emailaddress'],
authOptions: {
state: process.env.LINKEDIN_STATE
},
failureFlash: true
},
'linkedin-link': {
provider: 'linkedin',
authScheme: 'oauth',
authScheme: 'oauth2',
module: 'passport-linkedin-oauth2',
authPath: '/link/linkedin',
callbackURL: '/link/linkedin/callback',
@ -117,7 +122,10 @@ module.exports = {
failureRedirect: failureRedirect,
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
scope: ['r_fullprofile', 'r_emailaddress'],
scope: ['r_basicprofile', 'r_emailaddress'],
authOptions: {
state: process.env.LINKEDIN_STATE
},
failureFlash: true
}
};

View File

@ -1,10 +1,9 @@
require('dotenv').load();
require('pmx').init();
var pmx = require('pmx');
pmx.init();
// handle uncaught exceptions. Forever will restart process on shutdown
var https = require('https'),
sslConfig = require('./ssl-config'),
R = require('ramda'),
var R = require('ramda'),
assign = require('lodash').assign,
loopback = require('loopback'),
boot = require('loopback-boot'),
@ -21,9 +20,7 @@ var https = require('https'),
flash = require('express-flash'),
path = require('path'),
expressValidator = require('express-validator'),
forceDomain = require('forcedomain'),
lessMiddleware = require('less-middleware'),
pmx = require('pmx'),
passportProviders = require('./passport-providers'),
/**
@ -45,20 +42,16 @@ app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//if (process.env.NODE_ENV === 'production') {
// app.use(forceDomain({
// hostname: 'www.freecodecamp.com'
// }));
//}
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(bodyParser.urlencoded({
extended: true
}));
app.use(expressValidator({
customValidators: {
matchRegex: function (param, regex) {
matchRegex: function(param, regex) {
return regex.test(param);
}
}
@ -99,6 +92,8 @@ var trusted = [
'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',
@ -133,7 +128,8 @@ var trusted = [
'wss://inspectletws.herokuapp.com/',
'http://hn.inspectlet.com/',
'*.googleapis.com',
'*.gstatic.com'
'*.gstatic.com',
'https://hn.inspectlet.com/'
];
app.use(helmet.csp({
@ -143,10 +139,10 @@ app.use(helmet.csp({
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js'
].concat(trusted),
'connect-src': [
'http://cdn.inspectlet.com/inspectlet.js',
'http://www.freecodecamp.org'
].concat(trusted),
'connect-src': [].concat(trusted),
styleSrc: [
'*.googleapis.com',
'*.gstatic.com'
@ -180,14 +176,16 @@ app.use(helmet.csp({
passportConfigurator.init();
app.use(function (req, res, next) {
app.use(function(req, res, next) {
// Make user object available in templates.
res.locals.user = req.user;
next();
});
app.use(
loopback.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
loopback.static(path.join(__dirname, '../public'), {
maxAge: 86400000
})
);
boot(app, {
@ -195,7 +193,7 @@ boot(app, {
dev: process.env.NODE_ENV
});
app.use(function (req, res, next) {
app.use(function(req, res, next) {
// Remember original destination before login.
var path = req.path.split('/')[1];
if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) {
@ -224,7 +222,8 @@ var passportOptions = {
null;
var username = (profile.username || profile.id);
username = typeof username === 'string' ? username.toLowerCase() : username;
username = typeof username === 'string' ? username.toLowerCase() :
username;
var password = generateKey('password');
var userObj = {
username: username,
@ -255,8 +254,13 @@ R.keys(passportProviders).map(function(strategy) {
* 500 Error Handler.
*/
if (process.env.NODE_ENV === 'development') {
app.use(errorHandler({ log: true }));
// if (process.env.NODE_ENV === 'development') {
if (true) { // eslint-disable-line
// NOTE(berks): adding pmx here for Beta test. Remove for production
app.use(pmx.expressErrorHandler());
app.use(errorHandler({
log: true
}));
} else {
app.use(pmx.expressErrorHandler());
// error handling in production disabling eslint due to express parity rules
@ -279,12 +283,16 @@ if (process.env.NODE_ENV === 'development') {
var message = 'opps! Something went wrong. Please try again later';
if (type === 'html') {
req.flash('errors', { msg: message });
req.flash('errors', {
msg: message
});
return res.redirect('/');
// json
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
return res.send({ message: message });
return res.send({
message: message
});
// plain text
} else {
res.setHeader('Content-Type', 'text/plain');
@ -297,37 +305,16 @@ if (process.env.NODE_ENV === 'development') {
* Start Express server.
*/
var options = {
key: sslConfig.privateKey,
cert: sslConfig.certificate
};
if (process.env.NODE_ENV === 'production') {
app.start = function() {
var server = https.createServer(options, app);
server.listen('https://' + process.env.HOST + ':' + app.get('port'), function () {
console.log(
'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'),
app.get('env')
);
});
};
} else {
app.start = function () {
app.listen(app.get('port'), function () {
console.log(
'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'),
app.get('env')
);
});
};
}
app.listen(app.get('port'), function() {
console.log(
'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'),
app.get('env')
);
});
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
module.exports = app;

View File

@ -8,9 +8,9 @@ var fs = require('fs');
if (process.env.NODE_ENV === 'production') {
exports.privateKey =
fs.readFileSync(path.join(__dirname,
'../../private/privatekey.pem')).toString();
'../../private/privatekey.pem'));
exports.certificate =
fs.readFileSync(path.join(__dirname,
'../../private/certificate.pem')).toString();
'../../private/certificate.pem'));
}

View File

@ -7,6 +7,7 @@ var path = require('path'),
fs = require('fs'),
MDNlinks = require('../../seed/bonfireMDNlinks'),
resources = require('./resources.json'),
nonprofits = require('../../seed/nonprofits.json'),
fieldGuides = require('../../seed/field-guides.json');
@ -16,7 +17,7 @@ var path = require('path'),
*/
var allFieldGuideIds, allFieldGuideNames, allNonprofitNames,
challengeMap, challengeMapForDisplay, challengeMapWithIds,
challengeMapWithNames, allChallengeIds, allChallenges,
challengeMapWithNames, allChallengeIds,
challengeMapWithDashedNames;
/**
@ -216,5 +217,18 @@ module.exports = {
}
});
})();
},
getMDNLinks: function(links) {
if (!links) {
return [];
}
// takes in an array of links, which are strings
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
return links.map(function(value) {
return MDNlinks[value];
});
}
};

View File

@ -84,10 +84,16 @@ block content
label.negative-10.btn.btn-primary.btn-block#submitButton
i.fa.fa-play
| &nbsp; Run code (ctrl + enter)
#resetButton.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code
if (user && user.sentSlackInvite)
.button-spacer
.btn-group.input-group.btn-group-justified
label.btn.btn-success#trigger-help-modal
i.fa.fa-refresh
| &nbsp; Reset
label.btn.btn-success#trigger-help-modal
i.fa.fa-refresh
| &nbsp; Reset
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
@ -119,7 +125,7 @@ block content
form.code
.form-group.codeMirrorView
textarea#codeEditor(autofocus=true, style='display: none;')
script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.5.js')
script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.6.js')
#complete-courseware-dialog.modal(tabindex='-1')
.modal-dialog.animated.zoomIn.fast-animation
@ -140,6 +146,15 @@ block content
= phrase
else
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
#reset-modal.modal(tabindex='-1')
.modal-dialog.animated.fadeInUp.fast-animation
.modal-content
.modal-header.challenge-list-header Clear your code?
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body
h3 This will restore your code editor to its original state.
a.btn.btn-lg.btn-info.btn-block#reset-button(href='#', data-dismiss='modal', aria-hidden='true') Clear my code
a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
include ../partials/challenge-modals
script.
var MDNlinks = !{JSON.stringify(MDNlinks)};

View File

@ -7,7 +7,6 @@
a.ion-social-facebook(href="/field-guide/how-can-i-find-other-free-code-camp-campers-in-my-city") &nbsp;Facebook&nbsp;&nbsp;
a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank') &nbsp;Twitter&nbsp;&nbsp;
a.ion-locked(href="/privacy") &nbsp;Privacy&nbsp;&nbsp;
a.ion-android-mail(href="mailto:team@freecodecamp.com") &nbsp;Contact&nbsp;&nbsp;
.col-xs-12.visible-xs.visible-sm
a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank')
span.sr-only Free Code Camp's Blog
@ -23,5 +22,3 @@
span.sr-only Free Code Camp on Twitter
a.ion-locked(href="/privacy")
span.sr-only Free Code Camp's Privacy Policy
a.ion-android-mail(href="mailto:team@freecodecamp.com")
span.sr-only Contact Free Code Camp by email

View File

@ -1,344 +1,104 @@
extends ../layout
extends ../layout-wide
block content
.panel.panel-info
.panel-heading.text-center Coding Bootcamp Cost Calculator
.panel-body
.row
script(src="../../../js/calculator.js")
.row
.col-xs-12.col-sm-10.col-md-8.col-lg-6.col-sm-offset-1.col-md-offset-2.col-lg-offset-3
h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be:
#city-buttons
.spacer
h2.text-center Where do you live?
.spacer
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#atlanta.btn.btn-primary.btn-block.btn-lg Atlanta
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#austin.btn.btn-primary.btn-block.btn-lg Austin
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#brisbane.btn.btn-primary.btn-block.btn-lg Brisbane
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#boulder.btn.btn-primary.btn-block.btn-lg Boulder
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#chicago.btn.btn-primary.btn-block.btn-lg Chicago
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#denver.btn.btn-primary.btn-block.btn-lg Denver
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#hong-kong.btn.btn-primary.btn-block.btn-lg Hong Kong
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#london.btn.btn-primary.btn-block.btn-lg London
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#los-angeles.btn.btn-primary.btn-block.btn-lg Los Angeles
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#manchester.btn.btn-primary.btn-block.btn-lg Manchester
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#melbourne.btn.btn-primary.btn-block.btn-lg Melbourne
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#new-york-city.btn.btn-primary.btn-block.btn-lg New York City
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#portland.btn.btn-primary.btn-block.btn-lg Portland
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#raleigh-durham.btn.btn-primary.btn-block.btn-lg Raleigh-Durham
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#san-francisco.btn.btn-primary.btn-block.btn-lg San Fransisco
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#seattle.btn.btn-primary.btn-block.btn-lg Seattle
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#singapore.btn.btn-primary.btn-block.btn-lg Singapore
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#toronto.btn.btn-primary.btn-block.btn-lg Toronto
.col-xs-12.btn-nav
button#other.btn.btn-primary.btn-block.btn-lg Other
.spacer
#income.initially-hidden
.spacer
h2.text-center How much money did you make last year (in USD)?
.spacer
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#0.btn.btn-primary.btn-block.btn-lg(href='#') $0
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#20000.btn.btn-primary.btn-block.btn-lg(href='#') $20,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#30000.btn.btn-primary.btn-block.btn-lg(href='#') $30,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#40000.btn.btn-primary.btn-block.btn-lg(href='#') $40,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#50000.btn.btn-primary.btn-block.btn-lg(href='#') $50,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#60000.btn.btn-primary.btn-block.btn-lg(href='#') $60,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#70000.btn.btn-primary.btn-block.btn-lg(href='#') $70,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#80000.btn.btn-primary.btn-block.btn-lg(href='#') $80,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#90000.btn.btn-primary.btn-block.btn-lg(href='#') $90,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#100000.btn.btn-primary.btn-block.btn-lg(href='#') $100,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#120000.btn.btn-primary.btn-block.btn-lg(href='#') $120,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#140000.btn.btn-primary.btn-block.btn-lg(href='#') $140,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#160000.btn.btn-primary.btn-block.btn-lg(href='#') $160,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#180000.btn.btn-primary.btn-block.btn-lg(href='#') $180,000
.col-xs-12.col-sm-12.col-md-4.btn-nav
button#200000.btn.btn-primary.btn-block.btn-lg(href='#') $200,000
.spacer
#chart.initially-hidden
.d3-centered
svg.chart
#explanation.initially-hidden
.col-xs-12.col-sm-10.col-sm-offset-1
h2.text-primary#chosen
#chart-controls.initially-hidden
form
label
input(type='radio', name='mode', value='grouped')
| &thinsp; Grouped &nbsp;
label
input(type='radio', name='mode', value='stacked')
| &thinsp; Stacked
br
a(href='/bootcamp-calculator.json') View Data Source JSON
#city-buttons
h2 Where do you live?
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#atlanta.btn.btn-primary.btn-block.btn-lg Atlanta
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#austin.btn.btn-primary.btn-block.btn-lg Austin
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#brisbane.btn.btn-primary.btn-block.btn-lg Brisbane
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#boulder.btn.btn-primary.btn-block.btn-lg Boulder
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#chicago.btn.btn-primary.btn-block.btn-lg Chicago
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#denver.btn.btn-primary.btn-block.btn-lg Denver
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#hong-kong.btn.btn-primary.btn-block.btn-lg Hong Kong
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#london.btn.btn-primary.btn-block.btn-lg London
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#los-angeles.btn.btn-primary.btn-block.btn-lg Los Angeles
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#manchester.btn.btn-primary.btn-block.btn-lg Manchester
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#melbourne.btn.btn-primary.btn-block.btn-lg Melbourne
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#new-york-city.btn.btn-primary.btn-block.btn-lg New York City
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#portland.btn.btn-primary.btn-block.btn-lg Portland
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#raleigh-durham.btn.btn-primary.btn-block.btn-lg Raleigh-Durham
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#san-francisco.btn.btn-primary.btn-block.btn-lg San Fransisco
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#seattle.btn.btn-primary.btn-block.btn-lg Seattle
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#singapore.btn.btn-primary.btn-block.btn-lg Singapore
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#toronto.btn.btn-primary.btn-block.btn-lg Toronto
.col-xs-12.btn-nav
button#other.btn.btn-primary.btn-block.btn-lg Other
#income.hidden-by-default
h2 How much money did you make last year (in USD)?
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#0.btn.btn-primary.btn-block.btn-lg(href='#') $0
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#20000.btn.btn-primary.btn-block.btn-lg(href='#') $20,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#30000.btn.btn-primary.btn-block.btn-lg(href='#') $30,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#40000.btn.btn-primary.btn-block.btn-lg(href='#') $40,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#50000.btn.btn-primary.btn-block.btn-lg(href='#') $50,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#60000.btn.btn-primary.btn-block.btn-lg(href='#') $60,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#70000.btn.btn-primary.btn-block.btn-lg(href='#') $70,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#80000.btn.btn-primary.btn-block.btn-lg(href='#') $80,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#90000.btn.btn-primary.btn-block.btn-lg(href='#') $90,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#100000.btn.btn-primary.btn-block.btn-lg(href='#') $100,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#120000.btn.btn-primary.btn-block.btn-lg(href='#') $120,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#140000.btn.btn-primary.btn-block.btn-lg(href='#') $140,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#160000.btn.btn-primary.btn-block.btn-lg(href='#') $160,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#180000.btn.btn-primary.btn-block.btn-lg(href='#') $180,000
.col-xs-12.col-sm-6.col-md-4.btn-nav
button#200000.btn.btn-primary.btn-block.btn-lg(href='#') $200,000
#chart.hidden-by-default
svg.chart
script.
$(document).ready(function () {
var bootcamps = !{JSON.stringify(bootcampJson)};
var city = "";
var cityArray = ["san-fransisco", "los-angeles", "chicago", "austin", "new-york-city", "melbourne", "hong-kong", "seattle", "singapore", "london", "toronto", "portland", "brisbane", "atlanta", "raleigh-durham"];
$('#city-buttons').on("click", "button", function () {
city = $(this).attr("id");
$('#city-buttons').hide();
$('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}));
$('#income').css({visibility: 'visible'});
});
$('#income').on("click", "button", function() {
$('#income').hide();
$('#chart').css({visibility: 'visible'});
var lastYearsIncome = parseInt($(this).attr("id"));
$('#chosen').append(' making $' + lastYearsIncome.toString().replace(/0000/, '0,000') + ', your true costs are:');
var categoryNames = ['Opportunity Cost at Current Wage', 'Financing Cost', 'Housing Cost', 'Tuition / Wage Garnishing'];
bootcamps.forEach(function (camp) {
var x0 = 0;
if (camp.cities.indexOf(city) > -1) {
weeklyHousing = 0;
} else {
weeklyHousing = +camp.housing;
}
camp.mapping = [{
name: camp.name,
label: 'Opportunity Cost at Current Wage',
value: +camp.cost,
x0: x0,
x1: x0 += +camp.cost
}, {
name: camp.name,
label: 'Financing Cost',
value: +Math.floor(camp.cost * .09519),
x0: +camp.cost,
x1: camp.finance ? x0 += +Math.floor(camp.cost * .09519) : 0
}, {
name: camp.name,
label: 'Housing Cost',
value: +weeklyHousing * camp.weeks,
x0: camp.finance ? +Math.floor(camp.cost * 1.09519) : camp.cost,
x1: x0 += weeklyHousing * camp.weeks
}, {
name: camp.name,
label: 'Tuition / Wage Garnishing',
value: +(Math.floor(camp.weeks * lastYearsIncome / 50)),
x0: camp.finance ? +(Math.floor(camp.cost * 1.09519) + weeklyHousing * camp.weeks) : +camp.cost + weeklyHousing * camp.weeks,
x1: x0 += +(Math.floor(camp.weeks * lastYearsIncome / 50))
}];
camp.total = camp.mapping[camp.mapping.length - 1].x1;
});
bootcamps.sort(function(a, b) { return a.total - b.total; });
maxValue = 0;
bootcamps.forEach(function (camp) {
camp.mapping.forEach(function (thing) {
//console.log(thing.value );
if (thing.value > maxValue) {
maxValue = thing.value;
console.log(maxValue);
}
});
});
var xStackMax = d3.max(bootcamps, function (d) {
return d.total;
}), //Scale for Stacked
xGroupMax = bootcamps.map(function (camp) {
return camp.mapping.reduce(function (a, b) {
return a.value > b.value ? a.value : b.value;
});
}).reduce(function (a, b) {
return a > b ? a : b;
});
var margin = {
top: 30,
right: 60,
bottom: 50,
left: 140
},
width = 800 - margin.left - margin.right,
height = 1200 - margin.top - margin.bottom;
var barHeight = 20;
var xScale = d3.scale.linear()
.domain([0, xStackMax])
.rangeRound([0, width]);
var y0Scale = d3.scale.ordinal()
.domain(bootcamps.map(function (d) {
return d.name;
}))
.rangeRoundBands([0, height], .1);
var y1Scale = d3.scale.ordinal()
.domain(categoryNames).rangeRoundBands([0, y0Scale.rangeBand()]);
var color = d3.scale.ordinal()
.range(["#215f1e", "#5f5c1e", "#1e215f", "#5c1e5f"])
.domain(categoryNames);
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var selection = svg.selectAll(".series")
.data(bootcamps)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) {
return "translate(0," + y0Scale(d.name) + ")";
});
var rect = selection.selectAll("rect")
.data(function (d) {
return d.mapping;
})
.enter().append("rect")
.attr("x", 0)
.attr("width", 0)
.attr("height", y0Scale.rangeBand())
.style("fill", function (d) {
return color(d.label);
})
.style("stroke", "white")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
});
rect.transition()
.delay(function (d, i) {
return i * 10;
})
.attr("x", function (d) {
return xScale(d.x0);
})
.attr("width", function (d) {
return xScale((d.x1) - (d.x0));
});
d3.selectAll("input").on("change", change);
var timeout= setTimeout(function () {
d3.select("input[value=\"stacked\"]").property("checked",true).each(change);
// d3.select("input[value=\"stacked\"]").property("checked",true).each(change);
}, 4000);
var timeout= setTimeout(function () {
d3.select("input[value=\"grouped\"]").property("checked",true).each(change);
}, 1500);
function change() {
clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();
else transitionStacked();
}
function transitionGrouped() {
xScale.domain = ([0, xGroupMax]);
rect.transition()
.duration(500)
.delay(function (d, i) {
return i * 10;
})
.attr("width", function (d) {
return xScale((d.x1) - (d.x0));
})
.transition()
.attr("y", function (d) {
return y1Scale(d.label);
})
.attr("x", 0)
.attr("height", y1Scale.rangeBand())
}
function transitionStacked() {
xScale.domain = ([0, xStackMax]);
rect.transition()
.duration(500)
.delay(function (d, i) {
return i * 10;
})
.attr("x", function (d) {
return xScale(d.x0);
})
.transition()
.attr("y", function (d) {
return y0Scale(d.label);
})
.attr("height", y0Scale.rangeBand())
}
//axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y0Scale)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", 300)
.attr("y", 35)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Cost in $USD");
//tooltips
function removePopovers() {
$('.popover').each(function () {
$(this).remove();
});
}
function showPopover(d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html: true,
content: function () {
return d.label +
"<br/>$" +
d3.format(",")(d.value ? d.value : d.x1 - d.x0);
}
});
$(this).popover('show')
}
//legends
var legend = svg.selectAll(".legend")
.data(categoryNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) {
return "translate(30," + i * y0Scale.rangeBand() * 1.1 + ")";
});
legend.append("rect")
.attr("x", width - y0Scale.rangeBand())
.attr("width", y0Scale.rangeBand())
.attr("height", y0Scale.rangeBand())
.style("fill", color)
.style("stroke", "white");
legend.append("text")
.attr("x", width - y0Scale.rangeBand() * 1.2)
.attr("y", 12)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) {
return d;
});
});
});
.text-center
button#transform.btn.btn-primary.btn-lg Transform
.button-spacer
a(href='/coding-bootcamp-cost-calculator.json') View Data Source JSON
span &nbsp; &bullet; &nbsp;
a(href='/coding-bootcamp-cost-calculator') Recalculate
h3 Notes:
ol
li.large-li For cash-up-front bootcamps, we assumed an APR of 6% and a term of 3 years.
li.large-li For wage-garnishing bootcamps, we assume 18% of first year wages at their advertised starting annual salary of around $100,000.
li.large-li We assume a cost of living of $500 for cities like San Francisco and New York City, and $400 per week for everywhere else.
li.large-li The most substantial cost for most people is lost wages. A 40-hour-per-week job at the US Federal minimum wage would pay at least $15,000 per year. You can read more about economic cost
a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here
| .
li.large-li Free Code Camp. We don't charge tuition or garnish wages. We're fully online so you don't have to move. We're self-paced so you don't have to quit your job. Thus, your true cost of attending Free Code Camp will be $0.

749
setup.js
View File

@ -1,749 +0,0 @@
var fs = require('fs');
var os = require('os');
var blessed = require('blessed');
var multiline = require('multiline');
if (os.platform() === 'win32') {
console.log('**************************************************************');
console.log('Hackathon Starter Generator has been disabled on Windows until');
console.log('https://github.com/chjj/blessed is fixed or until I find a');
console.log('better CLI module.');
console.log('**************************************************************');
process.exit();
}
var screen = blessed.screen({
autoPadding: true
});
screen.key('q', function() {
process.exit(0);
});
var home = blessed.list({
parent: screen,
padding: { top: 2 },
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
selectedFg: 'blue',
selectedBg: 'white',
items: [
'» REMOVE AUTHENTICATION PROVIDER',
'» CHANGE EMAIL SERVICE',
'» ADD NODE.JS CLUSTER SUPPORT',
'» EXIT'
]
});
var homeTitle = blessed.text({
parent: screen,
align: 'center',
fg: 'blue',
bg: 'white',
content: 'Hackathon Starter (c) 2014'
});
var footer = blessed.text({
parent: screen,
bottom: 0,
fg: 'white',
bg: 'blue',
tags: true,
content: ' {cyan-fg}<Up/Down>{/cyan-fg} moves |' +
' {cyan-fg}<Enter>{/cyan-fg} selects | {cyan-fg}<q>{/cyan-fg} exits'
});
var inner = blessed.form({
top: 'center',
left: 'center',
mouse: true,
keys: true,
width: 33,
height: 10,
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
fg: 'white',
bg: 'red'
});
var success = blessed.box({
top: 'center',
left: 'center',
mouse: true,
keys: true,
tags: true,
width: '50%',
height: '40%',
border: {
type: 'line',
fg: 'white',
bg: 'green'
},
fg: 'white',
bg: 'green',
padding: 1
});
success.on('keypress', function() {
home.focus();
home.remove(success);
});
var clusterText = blessed.text({
top: 'top',
bg: 'red',
fg: 'white',
tags: true,
content: 'Take advantage of multi-core systems' +
' using built-in {underline}cluster{/underline} module.'
});
var enable = blessed.button({
parent: inner,
bottom: 0,
mouse: true,
shrink: true,
name: 'enable',
content: ' ENABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var disable = blessed.button({
parent: inner,
bottom: 0,
left: 10,
mouse: true,
shrink: true,
name: 'disable',
content: ' DISABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var cancel = blessed.button({
parent: inner,
bottom: 0,
left: 21,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
cancel.on('press', function() {
home.focus();
home.remove(inner);
screen.render();
});
var authForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
authForm.on('submit', function() {
var passportConfig = fs.readFileSync('config/passport.js')
.toString().split(os.EOL);
var loginTemplate = fs.readFileSync('views/account/login.jade')
.toString().split(os.EOL);
var profileTemplate = fs.readFileSync('views/account/profile.jade')
.toString().split(os.EOL);
var userModel = fs.readFileSync('models/User.js').toString().split(os.EOL);
var app = fs.readFileSync('app.js').toString().split(os.EOL);
var secrets = fs.readFileSync('config/secrets.js').toString().split(os.EOL);
var index = passportConfig
.indexOf('var FacebookStrategy = require("passport-facebook").Strategy;');
if (facebookCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Facebook.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-facebook.btn-social(href="/auth/facebook")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.facebook');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' facebook: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf([
'app.get("/auth/facebook"',
'passport.authenticate("facebook",',
' { scope: ["email", "user_location"] }));'
].join(' '));
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
"var GitHubStrategy = require('passport-github').Strategy;"
);
if (githubCheckbox.checked && index !== -1) {
console.log(index);
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with GitHub.');
passportConfig.splice(index, 48);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-github.btn-social(href='/auth/github')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.github');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' github: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/github", passport.authenticate("github"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var GoogleStrategy = require("passport-google-oauth").OAuth2Strategy;'
);
if (googleCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Google.');
passportConfig.splice(index, 46);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.google');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' google: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/google",' +
' passport.authenticate("google", { scope: "profile email" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var TwitterStrategy = require("passport-twitter").Strategy;'
);
if (twitterCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Twitter.');
passportConfig.splice(index, 43);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-twitter.btn-social(href="/auth/twitter")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.twitter');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade', profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' twitter: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
"app.get('/auth/twitter', passport.authenticate('twitter'));"
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var LinkedInStrategy = require("passport-linkedin-oauth2").Strategy;'
);
if (linkedinCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with LinkedIn.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-linkedin.btn-social(href="/auth/linkedin")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.linkedin');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' linkedin: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/linkedin",',
' passport.authenticate("linkedin", { state: "SOME STATE" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var InstagramStrategy = require("passport-instagram").Strategy;'
);
if (instagramCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Instagram.');
passportConfig.splice(index, 43);
fs.writeFileSync(
'config/passport.js',
passportConfig.join(os.EOL)
);
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-instagram.btn-social(href="/auth/instagram")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync(
'views/account/login.jade',
loginTemplate.join(os.EOL)
);
index = profileTemplate.indexOf(' if user.instagram');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = app.indexOf(
'app.get("/auth/instagram", passport.authenticate("instagram"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
}
home.remove(authForm);
home.append(success);
success.setContent(
'Selected authentication providers have been removed' +
'from passportConfig.js, User.js, server.js, login.jade and profile.jade!'
);
success.focus();
screen.render();
});
var authText = blessed.text({
parent: authForm,
content: 'Selecting a checkbox removes an authentication provider.' +
' If authentication provider is already removed, no action will be taken.',
padding: 1,
bg: 'magenta',
fg: 'white'
});
var facebookCheckbox = blessed.checkbox({
parent: authForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Facebook'
});
var githubCheckbox = blessed.checkbox({
parent: authForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'GitHub'
});
var googleCheckbox = blessed.checkbox({
parent: authForm,
top: 8,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Google'
});
var twitterCheckbox = blessed.checkbox({
parent: authForm,
top: 9,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Twitter'
});
var linkedinCheckbox = blessed.checkbox({
parent: authForm,
top: 10,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'LinkedIn'
});
var instagramCheckbox = blessed.checkbox({
parent: authForm,
top: 11,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Instagram'
});
var authSubmit = blessed.button({
parent: authForm,
top: 13,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authSubmit.on('press', function() {
authForm.submit();
});
var authCancel = blessed.button({
parent: authForm,
top: 13,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authCancel.on('press', function() {
home.focus();
home.remove(authForm);
screen.render();
});
var emailForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
emailForm.on('submit', function() {
var contactCtrl = fs.readFileSync('controllers/contact.js')
.toString().split(os.EOL);
var userCtrl = fs.readFileSync('controllers/user.js')
.toString().split(os.EOL);
var choice = null;
if (sendgridRadio.checked) {
choice = 'SendGrid';
} else if (mailgunRadio.checked) {
choice = 'Mailgun';
} else if (mandrillRadio.checked) {
choice = 'Mandrill';
}
var index = contactCtrl.indexOf(
'var transporter = nodemailer.createTransport({'
);
contactCtrl.splice(index + 1, 1, " service: '" + choice + "',");
contactCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
contactCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/contact.js', contactCtrl.join(os.EOL));
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({'
);
userCtrl.splice(index + 1, 1, " service: '" + choice + "',");
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({',
(index + 1)
);
userCtrl.splice(
index + 1,
1,
' service: "' + choice + '",'
);
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/user.js', userCtrl.join(os.EOL));
home.remove(emailForm);
home.append(success);
success.setContent('Email Service has been switched to ' + choice);
success.focus();
screen.render();
});
var emailText = blessed.text({
parent: emailForm,
content: 'Select one of the following email service providers ' +
'for {underline}contact form{/underline}' +
' and {underline}password reset{/underline}.',
padding: 1,
bg: 'red',
fg: 'white',
tags: true
});
var sendgridRadio = blessed.radiobutton({
parent: emailForm,
top: 5,
checked: true,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'SendGrid'
});
var mailgunRadio = blessed.radiobutton({
parent: emailForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mailgun'
});
var mandrillRadio = blessed.radiobutton({
parent: emailForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mandrill'
});
var emailSubmit = blessed.button({
parent: emailForm,
top: 9,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailSubmit.on('press', function() {
emailForm.submit();
});
var emailCancel = blessed.button({
parent: emailForm,
top: 9,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailCancel.on('press', function() {
home.focus();
home.remove(emailForm);
screen.render();
});
home.on('select', function(child, index) {
switch (index) {
case 0:
home.append(authForm);
authForm.focus();
screen.render();
break;
case 1:
home.append(emailForm);
emailForm.focus();
break;
case 2:
addClusterSupport();
home.append(success);
success.setContent([
'New file {underline}cluster_app.js{/underline} has been created.',
'Your app is now able to use more than 1 CPU by running',
'{underline}node cluster_app.js{/underline}, which in turn',
'spawns multiple instances of {underline}server.js{/underline}'
].join(' '));
success.focus();
screen.render();
break;
default:
process.exit(0);
}
});
screen.render();
function addClusterSupport() {
var fileContents = multiline(function() {
/*
var os = require('os');
var cluster = require('cluster');
cluster.setupMaster({
exec: 'server.js'
});
cluster.on('exit', function(worker) {
console.log('worker ' + worker.id + ' died');
cluster.fork();
});
for (var i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
*/
});
fs.writeFileSync('cluster_app.js', fileContents);
}