Merge branch 'staging' of https://github.com/FreeCodeCamp/freecodecamp into greasan-translateDE
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,3 +27,4 @@ bower_components
|
||||
main.css
|
||||
bundle.js
|
||||
coverage
|
||||
.remote-sync.json
|
||||
|
@ -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
3
client/README.md
Normal 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
20
client/index.js
Normal 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
25
common/app/App.jsx
Normal 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
18
common/app/appFactory.jsx
Normal 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>
|
||||
);
|
||||
};
|
50
common/app/components/Footer/Footer.jsx
Normal file
50
common/app/components/Footer/Footer.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
1
common/app/components/Footer/index.js
Normal file
1
common/app/components/Footer/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as Footer } from './Footer.jsx';
|
44
common/app/components/Footer/links.json
Normal file
44
common/app/components/Footer/links.json
Normal file
@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"className": "ion-speakerphone",
|
||||
"content": " Blog ",
|
||||
"href": "http://blog.freecodecamp.com",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"className": "ion-social-twitch-outline",
|
||||
"content": " Twitch ",
|
||||
"href": "http://www.twitch.tv/freecodecamp",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"className": "ion-social-github",
|
||||
"content": " Github ",
|
||||
"href": "http://github.com/freecodecamp",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"className": "ion-social-twitter",
|
||||
"content": " Twitter ",
|
||||
"href": "http://twitter.com/freecodecamp",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"className": "ion-social-facebook",
|
||||
"content": " Facebook ",
|
||||
"href": "http://facebook.com/freecodecamp",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"className": "ion-information-circled",
|
||||
"content": " About ",
|
||||
"href": "/learn-to-code",
|
||||
"target": "_self"
|
||||
},
|
||||
{
|
||||
"className": "ion-locked",
|
||||
"content": " Privacy ",
|
||||
"href": "/privacy'",
|
||||
"target": "_self"
|
||||
}
|
||||
]
|
@ -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;
|
||||
}
|
1
common/app/components/Nav/index.js
Normal file
1
common/app/components/Nav/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as Nav } from './Nav.jsx';
|
1
common/app/components/README.md
Normal file
1
common/app/components/README.md
Normal file
@ -0,0 +1 @@
|
||||
things like NavBar and Footer go here
|
1
common/app/routes/Admin/README.md
Normal file
1
common/app/routes/Admin/README.md
Normal file
@ -0,0 +1 @@
|
||||
in case we ever want an admin panel
|
1
common/app/routes/Bonfires/README.md
Normal file
1
common/app/routes/Bonfires/README.md
Normal file
@ -0,0 +1 @@
|
||||
This folder contains things relative to the bonfires screens
|
6
common/app/routes/Bonfires/index.js
Normal file
6
common/app/routes/Bonfires/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
path: 'bonfires/(:bonfireName)',
|
||||
getComponents(cb) {
|
||||
// TODO(berks): add bonfire component
|
||||
}
|
||||
};
|
1
common/app/routes/FAVS/README.md
Normal file
1
common/app/routes/FAVS/README.md
Normal file
@ -0,0 +1 @@
|
||||
future home of FAVS app
|
1
common/app/routes/Jobs/README.md
Normal file
1
common/app/routes/Jobs/README.md
Normal file
@ -0,0 +1 @@
|
||||
This folder contains everything relative to Jobs board
|
15
common/app/routes/Jobs/components/Actions.js
Normal file
15
common/app/routes/Jobs/components/Actions.js
Normal 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 };
|
||||
}
|
||||
}
|
28
common/app/routes/Jobs/components/Jobs.jsx
Normal file
28
common/app/routes/Jobs/components/Jobs.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
13
common/app/routes/Jobs/components/List.jsx
Normal file
13
common/app/routes/Jobs/components/List.jsx
Normal 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;
|
||||
}
|
||||
}
|
9
common/app/routes/Jobs/components/Store.js
Normal file
9
common/app/routes/Jobs/components/Store.js
Normal 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'
|
||||
}
|
19
common/app/routes/Jobs/index.js
Normal file
19
common/app/routes/Jobs/index.js
Normal 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')
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
11
common/app/routes/index.js
Normal file
11
common/app/routes/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
export default {
|
||||
path: '/',
|
||||
getRoutes(cb) {
|
||||
require.ensure([], require => {
|
||||
cb(null, [
|
||||
// require('./Bonfires'),
|
||||
require('./Jobs')
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
1
common/app/shared/README.md
Normal file
1
common/app/shared/README.md
Normal file
@ -0,0 +1 @@
|
||||
Here is where all components that are shared between multiple views
|
@ -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;
|
@ -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
|
||||
});
|
||||
};
|
@ -1 +0,0 @@
|
||||
module.exports = require('./Bonfires.jsx');
|
@ -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;
|
@ -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;
|
@ -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'>
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
ref='http://www.twitch.tv/freecodecamp'
|
||||
target='_blank' className='ion-social-twitch-outline'>
|
||||
Twitch
|
||||
</a>
|
||||
<a
|
||||
href='http://github.com/freecodecamp'
|
||||
target='_blank'
|
||||
className='ion-social-github'>
|
||||
Github
|
||||
</a>
|
||||
<a
|
||||
href='http://twitter.com/freecodecamp'
|
||||
target='_blank' className='ion-social-twitter'>
|
||||
Twitter
|
||||
</a>
|
||||
<a
|
||||
href='http://facebook.com/freecodecamp'
|
||||
target='_blank'
|
||||
className='ion-social-facebook'>
|
||||
Facebook
|
||||
</a>
|
||||
<a
|
||||
ref='/learn-to-code'
|
||||
className='ion-information-circled'>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href='/privacy'
|
||||
className='ion-locked'>
|
||||
Privacy
|
||||
</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;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./Footer.jsx');
|
@ -1 +0,0 @@
|
||||
module.exports = require('./Nav.jsx');
|
10
gulpfile.js
10
gulpfile.js
@ -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']);
|
||||
|
@ -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
12
pm2Start.js
Normal 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();
|
||||
});
|
||||
});
|
@ -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
268
public/js/calculator.js
Normal 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;
|
||||
});
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
@ -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();
|
||||
|
@ -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);",
|
||||
|
@ -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 & Gabbana', 'should escape characters');",
|
||||
"assert.strictEqual(convert('Hamburgers < Pizza < Tacos'), 'Hamburgers < Pizza < Tacos', 'should escape characters');",
|
||||
"assert.strictEqual(convert('Sixty > twelve'), 'Sixty > twelve', 'should escape characters');",
|
||||
"assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in "quotation marks"', 'should escape characters');",
|
||||
"assert.strictEqual(convert(\"Shindler's List\"), 'Shindler's List', 'should escape characters');",
|
||||
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
|
||||
],
|
||||
"MDNlinks": [
|
||||
|
@ -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://catphotoapp.com<code>.')",
|
||||
"assert(/http:\\/\\/catphotoapp\\.com/gi.test($('a').attr('href')), 'You need an <code>a</code> element that links to <code>http://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": [
|
||||
|
@ -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.')"
|
||||
|
@ -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>"
|
||||
],
|
||||
|
@ -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>"
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
@ -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); }
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ var secrets = require('../config/secrets');
|
||||
module.exports = {
|
||||
db: {
|
||||
connector: 'mongodb',
|
||||
connectionTimeout: 15000,
|
||||
url: process.env.MONGOHQ_URL
|
||||
},
|
||||
mail: {
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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'));
|
||||
|
||||
}
|
||||
|
@ -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];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -84,10 +84,16 @@ block content
|
||||
label.negative-10.btn.btn-primary.btn-block#submitButton
|
||||
i.fa.fa-play
|
||||
| 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
|
||||
| Reset
|
||||
label.btn.btn-success#trigger-help-modal
|
||||
i.fa.fa-refresh
|
||||
| Reset
|
||||
label.btn.btn-success#trigger-help-modal
|
||||
i.fa.fa-medkit
|
||||
| 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)};
|
||||
|
@ -7,7 +7,6 @@
|
||||
a.ion-social-facebook(href="/field-guide/how-can-i-find-other-free-code-camp-campers-in-my-city") Facebook
|
||||
a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank') Twitter
|
||||
a.ion-locked(href="/privacy") Privacy
|
||||
a.ion-android-mail(href="mailto:team@freecodecamp.com") Contact
|
||||
.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
|
||||
|
@ -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')
|
||||
|   Grouped
|
||||
label
|
||||
input(type='radio', name='mode', value='stacked')
|
||||
|   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 •
|
||||
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
749
setup.js
@ -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);
|
||||
}
|
Reference in New Issue
Block a user