diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..12606a3a96 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "stage": 0 +} diff --git a/.eslintrc b/.eslintrc index 5c50e37af5..f06a712e01 100644 --- a/.eslintrc +++ b/.eslintrc @@ -185,10 +185,12 @@ "sort-vars": 0, "space-after-keywords": [ 2, - "always", - { "checkFunctionKeyword": false } + "always" + ], + "space-before-function-paren": [ + 2, + "never" ], - "space-after-function-names": "never", "space-before-blocks": [ 2, "always" diff --git a/.gitignore b/.gitignore index 6b84cb7df8..86cbd7b0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ main.css bundle.js coverage .remote-sync.json + +server/*.bundle.js +public/js/*.bundle.js +*.map diff --git a/client/config.json b/client/config.json new file mode 100644 index 0000000000..3f5ecb7649 --- /dev/null +++ b/client/config.json @@ -0,0 +1,8 @@ +{ + "restApiRoot": "/api", + "remoting": { + "context": { + "enableHttpContext": false + } + } +} diff --git a/client/config.local.js b/client/config.local.js new file mode 100644 index 0000000000..e5058ced89 --- /dev/null +++ b/client/config.local.js @@ -0,0 +1,5 @@ +var globalConfig = require('../common/config.global'); + +module.exports = { + restApiRoot: globalConfig.restApi +}; diff --git a/client/datasources.json b/client/datasources.json new file mode 100644 index 0000000000..d6d2206bd6 --- /dev/null +++ b/client/datasources.json @@ -0,0 +1,10 @@ +{ + "remote": { + "name": "remote", + "connector": "remote" + }, + "local": { + "name": "local", + "connector": "memory" + } +} diff --git a/client/datasources.local.js b/client/datasources.local.js new file mode 100644 index 0000000000..4f07ba41f3 --- /dev/null +++ b/client/datasources.local.js @@ -0,0 +1,7 @@ +var globalConfig = require('../common/config.global'); + +module.exports = { + remote: { + url: globalConfig.restApiUrl + } +}; diff --git a/client/index.js b/client/index.js index 68baa7ec0a..e95bd0f908 100644 --- a/client/index.js +++ b/client/index.js @@ -1,20 +1,44 @@ -import BrowserHistory from 'react-router/lib/BrowserHistory'; +import Rx from 'rx'; +import React from 'react'; +import { Router } from 'react-router'; +import { history } from 'react-router/lib/BrowserHistory'; import debugFactory from 'debug'; -import { Cat } from 'thundercats'; +import { hydrate } from 'thundercats'; +import { Render } from 'thundercats-react'; -import AppFactory from '../common/app/appFactory'; +import { app$ } from '../common/app'; const debug = debugFactory('fcc:client'); -const DOMContianer = document.getElemenetById('#fCC'); -const fcc = new Cat(); +const DOMContianer = document.getElementById('fcc'); +const catState = window.__fcc__.data || {}; + +Rx.longStackSupport = !!debug.enabled; // returns an observable -fcc.render(AppFactory(BrowserHistory), DOMContianer) +app$(history) + .flatMap( + ({ AppCat }) => { + const appCat = AppCat(); + return hydrate(appCat, catState) + .map(() => appCat); + }, + ({ initialState }, appCat) => ({ initialState, appCat }) + ) + .flatMap(({ initialState, appCat }) => { + return Render( + appCat, + React.createElement(Router, initialState), + DOMContianer + ); + }) .subscribe( - function() { + () => { debug('react rendered'); }, - function(err) { + err => { debug('an error has occured', err.stack); + }, + () => { + debug('react closed subscription'); } ); diff --git a/client/loopbackClient.js b/client/loopbackClient.js new file mode 100644 index 0000000000..9f860ae72b --- /dev/null +++ b/client/loopbackClient.js @@ -0,0 +1,8 @@ +var loopback = require('loopback'), + boot = require('loopback-boot'); + +var app = loopback(); + +boot(app, __dirname); + +module.exports = app; diff --git a/client/model-config.json b/client/model-config.json new file mode 100644 index 0000000000..61e1e58d3d --- /dev/null +++ b/client/model-config.json @@ -0,0 +1,32 @@ +{ + "_meta": { + "sources": [ + "../common/models", + "./models" + ] + }, + "user": { + "dataSource": "remote" + }, + "bonfire": { + "dataSource": "remote" + }, + "challenge": { + "dataSource": "remote" + }, + "comment": { + "dataSource": "remote" + }, + "fieldGuide": { + "dataSource": "remote" + }, + "job": { + "dataSource": "remote" + }, + "nonprofit": { + "dataSource": "remote" + }, + "story": { + "dataSource": "remote" + } +} diff --git a/common/app/App.jsx b/common/app/App.jsx index f3a77cae51..e97ac4d83f 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import { Row } from 'react-bootstrap'; -import Nav from './components/Nav'; -import Footer from './components/Footer'; +import { Nav } from './components/Nav'; +import { Footer } from './components/Footer'; export default class extends React.Component { constructor(props) { @@ -17,7 +18,9 @@ export default class extends React.Component { return (
); diff --git a/common/app/Cat.js b/common/app/Cat.js new file mode 100644 index 0000000000..d1686b78fc --- /dev/null +++ b/common/app/Cat.js @@ -0,0 +1,9 @@ +import { Cat } from 'thundercats'; +import { HikesActions, HikesStore } from './routes/Hikes/flux'; + + +export default Cat() + .init(({ instance }) => { + instance.register(HikesActions); + instance.register(HikesStore, null, instance); + }); diff --git a/common/app/app-stream.jsx b/common/app/app-stream.jsx new file mode 100644 index 0000000000..dcba9b7ca0 --- /dev/null +++ b/common/app/app-stream.jsx @@ -0,0 +1,18 @@ +import Rx from 'rx'; +import assign from 'object.assign'; +import { Router } from 'react-router'; +import App from './App.jsx'; +import AppCat from './Cat'; + +import childRoutes from './routes'; + +const router$ = Rx.Observable.fromNodeCallback(Router.run, Router); + +const routes = assign({ components: App }, childRoutes); + +export default function app$(location) { + return router$(routes, location) + .map(([initialState, transistion]) => { + return { initialState, transistion, AppCat }; + }); +} diff --git a/common/app/appFactory.jsx b/common/app/appFactory.jsx deleted file mode 100644 index b8732a89c6..0000000000 --- a/common/app/appFactory.jsx +++ /dev/null @@ -1,18 +0,0 @@ -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 ( - - - - - - ); -}; diff --git a/common/app/components/Footer/Footer.jsx b/common/app/components/Footer/Footer.jsx index f95e0e3adc..b26a75099e 100644 --- a/common/app/components/Footer/Footer.jsx +++ b/common/app/components/Footer/Footer.jsx @@ -11,6 +11,7 @@ export default class extends React.Component { { this.renderContent(mobile, link.content) } diff --git a/common/app/components/Footer/links.json b/common/app/components/Footer/links.json index 54071917b4..92199b127f 100644 --- a/common/app/components/Footer/links.json +++ b/common/app/components/Footer/links.json @@ -1,43 +1,43 @@ [ { "className": "ion-speakerphone", - "content": " Blog  ", + "content": " Blog ", "href": "http://blog.freecodecamp.com", "target": "_blank" }, { "className": "ion-social-twitch-outline", - "content": " Twitch  ", + "content": " Twitch ", "href": "http://www.twitch.tv/freecodecamp", "target": "_blank" }, { "className": "ion-social-github", - "content": " Github  ", + "content": " Github ", "href": "http://github.com/freecodecamp", "target": "_blank" }, { "className": "ion-social-twitter", - "content": " Twitter  ", + "content": " Twitter ", "href": "http://twitter.com/freecodecamp", "target": "_blank" }, { "className": "ion-social-facebook", - "content": " Facebook  ", + "content": " Facebook ", "href": "http://facebook.com/freecodecamp", "target": "_blank" }, { "className": "ion-information-circled", - "content": " About  ", + "content": " About ", "href": "/learn-to-code", "target": "_self" }, { "className": "ion-locked", - "content": " Privacy  ", + "content": " Privacy ", "href": "/privacy'", "target": "_self" } diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index f5c339c2c1..eb10a307a0 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -1,6 +1,29 @@ import React from 'react'; import { Nav, Navbar, NavItem } from 'react-bootstrap'; -import NavItemFCC from './NavItem.jsx'; + +import navLinks from './links.json'; +import FCCNavItem from './NavItem.jsx'; + +const fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg'; +const navElements = navLinks.map((navItem, index) => { + return ( + + { navItem.content } + + ); +}); + +const logoElement = ( + + learn to code javascript at Free Code Camp logo + +); export default class extends React.Component { constructor(props) { @@ -13,15 +36,6 @@ export default class extends React.Component { } renderBrand() { - var fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg'; - return ( - - learn to code javascript at Free Code Camp logo - - ); } renderSignin() { @@ -34,12 +48,12 @@ export default class extends React.Component { ); } else { return ( - Sign In - + ); } } @@ -47,7 +61,7 @@ export default class extends React.Component { render() { return ( @@ -55,22 +69,8 @@ export default class extends React.Component { className='hamburger-dropdown' eventKey={ 0 } right={ true }> - - Challenges - - - Chat - - - Bonfires - - { this._renderSignin() } + { navElements } + { this.renderSignin() } ); diff --git a/common/app/components/Nav/NavItem.jsx b/common/app/components/Nav/NavItem.jsx index 88c4f363a9..eec245e7d8 100644 --- a/common/app/components/Nav/NavItem.jsx +++ b/common/app/components/Nav/NavItem.jsx @@ -1,58 +1,31 @@ -var React = require('react/addons'); -var joinClasses = require('react-bootstrap/lib/utils/joinClasses'); -var classSet = React.addons.classSet; -var BootstrapMixin = require('react-bootstrap').BootstrapMixin; +import React from 'react'; +import classNames from 'classnames'; +import BootstrapMixin from 'react-bootstrap/lib/BootstrapMixin'; -var NavItem = React.createClass({ +export default React.createClass({ + displayName: 'FCCNavItem', mixins: [BootstrapMixin], propTypes: { - onSelect: React.PropTypes.func, active: React.PropTypes.bool, + 'aria-controls': React.PropTypes.string, disabled: React.PropTypes.bool, - href: React.PropTypes.string, - title: React.PropTypes.string, eventKey: React.PropTypes.any, - target: React.PropTypes.string + href: React.PropTypes.string, + linkId: React.PropTypes.string, + onSelect: React.PropTypes.func, + role: React.PropTypes.string, + target: React.PropTypes.string, + title: React.PropTypes.node }, - getDefaultProps: function () { + getDefaultProps() { return { href: '#' }; }, - render: function () { - var { - disabled, - active, - href, - title, - target, - children, - } = this.props, - props = this.props, - classes = { - 'active': active, - 'disabled': disabled - }; - - return ( -
  • - - { children } - -
  • - ); - }, - - handleClick: function (e) { + handleClick(e) { if (this.props.onSelect) { e.preventDefault(); @@ -60,7 +33,54 @@ var NavItem = React.createClass({ this.props.onSelect(this.props.eventKey, this.props.href, this.props.target); } } + }, + + render() { + let { + role, + linkId, + disabled, + active, + href, + title, + target, + children, + 'aria-controls': ariaControls, // eslint-disable-line react/prop-types + className, + ...props + } = this.props; + + let classes = { + active, + disabled + }; + + let linkProps = { + role, + href, + title, + target, + id: linkId, + onClick: this.handleClick, + ref: 'anchor' + }; + + if (!role && href === '#') { + linkProps.role = 'button'; + } + + return ( +
  • + + { children } + +
  • + ); } }); - -module.exports = NavItem; diff --git a/common/app/components/Nav/links.json b/common/app/components/Nav/links.json new file mode 100644 index 0000000000..269a0aad4e --- /dev/null +++ b/common/app/components/Nav/links.json @@ -0,0 +1,16 @@ +[{ + "content": "Map", + "link": "/map" +}, { + "content": "Chat", + "link": "//gitter.im/FreeCodeCamp/FreeCodeCamp" +},{ + "content": "News", + "link": "/news" +},{ + "content": "Guide", + "link": "/field-guide" +},{ + "content": "Jobs", + "link": "/jobs" +}] diff --git a/common/app/components/NotFound/index.js b/common/app/components/NotFound/index.js new file mode 100644 index 0000000000..8206b46115 --- /dev/null +++ b/common/app/components/NotFound/index.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default class extends React.Component { + constructor(props) { + super(props); + } + static displayName = 'NotFound' + static propTypes = {} + componentDidMount() { + } + render() { + return null; + } +} diff --git a/common/app/index.js b/common/app/index.js new file mode 100644 index 0000000000..c55039fe05 --- /dev/null +++ b/common/app/index.js @@ -0,0 +1 @@ +export { default as app$ } from './app-stream.jsx'; diff --git a/common/app/routes/Hikes/components/Hikes.jsx b/common/app/routes/Hikes/components/Hikes.jsx new file mode 100644 index 0000000000..f56e57f298 --- /dev/null +++ b/common/app/routes/Hikes/components/Hikes.jsx @@ -0,0 +1,57 @@ +import React, { PropTypes } from 'react'; +import { Row } from 'react-bootstrap'; +import { contain } from 'thundercats-react'; +// import debugFactory from 'debug'; + +import HikesMap from './Map.jsx'; + +// const debug = debugFactory('freecc:hikes'); + +export default contain( + { + store: 'hikesStore', + fetchAction: 'hikesActions.fetchHikes', + getPayload: ({ hikes, params }) => ({ + isPrimed: (hikes && !!hikes.length), + dashedName: params.dashedName + }), + shouldContainerFetch(props, nextProps) { + return props.params.dashedName !== nextProps.params.dashedName; + } + }, + React.createClass({ + displayName: 'Hikes', + + propTypes: { + children: PropTypes.element, + currentHike: PropTypes.object, + hikes: PropTypes.array + }, + + renderMap(hikes) { + return ( + + ); + }, + + renderChild(children, hikes, currentHike) { + if (!children) { + return null; + } + return React.cloneElement(children, { hikes, currentHike }); + }, + + render() { + const { hikes, children, currentHike } = this.props; + const preventOverflow = { overflow: 'hidden' }; + return ( +
    + + { this.renderChild(children, hikes, currentHike) || + this.renderMap(hikes) } + +
    + ); + } + }) +); diff --git a/common/app/routes/Hikes/components/Lecture.jsx b/common/app/routes/Hikes/components/Lecture.jsx new file mode 100644 index 0000000000..ea3c85191d --- /dev/null +++ b/common/app/routes/Hikes/components/Lecture.jsx @@ -0,0 +1,71 @@ +import React, { PropTypes } from 'react'; +import { Button, Col, Row, Panel } from 'react-bootstrap'; +import { Navigation } from 'react-router'; +import Vimeo from 'react-vimeo'; +import debugFactory from 'debug'; + +const debug = debugFactory('freecc:hikes'); + +export default React.createClass({ + displayName: 'Lecture', + mixins: [Navigation], + + propTypes: { + currentHike: PropTypes.object, + params: PropTypes.object + }, + + handleError: debug, + + handleFinish() { + debug('loading questions'); + const { dashedName } = this.props.params; + this.transitionTo(`/hikes/${dashedName}/questions/1`); + }, + + renderTranscript(transcript, dashedName) { + return transcript.map((line, index) => ( +

    { line }

    + )); + }, + + render() { + const { + title, + challengeSeed = ['1'], + description = [] + } = this.props.currentHike; + const { dashedName } = this.props.params; + + const [ id ] = challengeSeed; + + const videoTitle =

    { title }

    ; + return ( + + + + + + + + + + { this.renderTranscript(description, dashedName) } + + + + + + + + ); + } +}); diff --git a/common/app/routes/Hikes/components/Map.jsx b/common/app/routes/Hikes/components/Map.jsx new file mode 100644 index 0000000000..830f74a7ef --- /dev/null +++ b/common/app/routes/Hikes/components/Map.jsx @@ -0,0 +1,38 @@ +import React, { PropTypes } from 'react'; +import { Link } from 'react-router'; +import { ListGroup, ListGroupItem, Panel } from 'react-bootstrap'; + +export default React.createClass({ + displayName: 'HikesMap', + + propTypes: { + hikes: PropTypes.array + }, + + render() { + const { + hikes + } = this.props; + + const vidElements = hikes.map(({ title, dashedName}) => { + return ( + + +

    { title }

    + +
    + ); + }); + + return ( +
    + +

    Welcome To Hikes!

    +
    + + { vidElements } + +
    + ); + } +}); diff --git a/common/app/routes/Hikes/components/Question.jsx b/common/app/routes/Hikes/components/Question.jsx new file mode 100644 index 0000000000..e7fe7662d9 --- /dev/null +++ b/common/app/routes/Hikes/components/Question.jsx @@ -0,0 +1,276 @@ +import React, { PropTypes } from 'react'; +import { Spring } from 'react-motion'; +import { Navigation, TransitionHook } from 'react-router'; +import debugFactory from 'debug'; +import { + Button, + Col, + Modal, + Panel, + Row +} from 'react-bootstrap'; + +import { postJSON$ } from '../../../../utils/ajax-stream.js'; + +const debug = debugFactory('freecc:hikes'); +const ANSWER_THRESHOLD = 200; + +export default React.createClass({ + displayName: 'Question', + + mixins: [ + Navigation, + TransitionHook + ], + + propTypes: { + currentHike: PropTypes.object, + dashedName: PropTypes.string, + hikes: PropTypes.array, + params: PropTypes.object + }, + + getInitialState: () => ({ + mouse: [0, 0], + correct: false, + delta: [0, 0], + isPressed: false, + showInfo: false, + shake: false + }), + + getTweenValues() { + const { mouse: [x, y] } = this.state; + return { + val: { x, y }, + config: [120, 10] + }; + }, + + handleMouseDown({ pageX, pageY, touches }) { + if (touches) { + ({ pageX, pageY } = touches[0]); + } + const { mouse: [pressX, pressY] } = this.state; + const dx = pageX - pressX; + const dy = pageY - pressY; + this.setState({ + isPressed: true, + delta: [dx, dy], + mouse: [pageX - dx, pageY - dy] + }); + }, + + handleMouseUp() { + const { correct } = this.state; + if (correct) { + return this.setState({ + isPressed: false, + delta: [0, 0] + }); + } + this.setState({ + isPressed: false, + mouse: [0, 0], + delta: [0, 0] + }); + }, + + handleMouseMove(answer) { + return (e) => { + let { pageX, pageY, touches } = e; + + if (touches) { + e.preventDefault(); + // these reassins the values of pageX, pageY from touches + ({ pageX, pageY } = touches[0]); + } + + const { isPressed, delta: [dx, dy] } = this.state; + if (isPressed) { + const mouse = [pageX - dx, pageY - dy]; + if (mouse[0] >= ANSWER_THRESHOLD) { + this.handleMouseUp(); + return this.onAnswer(answer, true)(); + } + if (mouse[0] <= -ANSWER_THRESHOLD) { + this.handleMouseUp(); + return this.onAnswer(answer, false)(); + } + this.setState({ mouse }); + } + }; + }, + + hideInfo() { + this.setState({ showInfo: false }); + }, + + onAnswer(answer, userAnswer) { + return (e) => { + if (e && e.preventDefault) { + e.preventDefault(); + } + + if (this.disposeTimeout) { + clearTimeout(this.disposeTimeout); + this.disposeTimeout = null; + } + + if (answer === userAnswer) { + debug('correct answer!'); + this.setState({ + correct: true, + mouse: [ userAnswer ? 1000 : -1000, 0] + }); + this.disposeTimeout = setTimeout(() => { + this.onCorrectAnswer(); + }, 1000); + return; + } + + debug('incorrect'); + this.setState({ + showInfo: true, + shake: true + }); + + this.disposeTimeout = setTimeout( + () => this.setState({ shake: false }), + 500 + ); + }; + }, + + onCorrectAnswer() { + const { hikes, currentHike } = this.props; + const { dashedName, number } = this.props.params; + const { id, name, difficulty, tests } = currentHike; + const nextQuestionIndex = +number; + + postJSON$('/completed-challenge', { id, name }).subscribeOnCompleted(() => { + if (tests[nextQuestionIndex]) { + return this.transitionTo( + `/hikes/${ dashedName }/questions/${ nextQuestionIndex + 1 }` + ); + } + // next questions does not exist; + debug('finding next hike'); + const nextHike = [].slice.call(hikes) + // hikes is in oder of difficulty, lets get reverse order + .reverse() + // now lets find the hike with the difficulty right above this one + .reduce((lowerHike, hike) => { + if (hike.difficulty > difficulty) { + return hike; + } + return lowerHike; + }, null); + + if (nextHike) { + return this.transitionTo(`/hikes/${ nextHike.dashedName }`); + } + debug( + 'next Hike was not found, currentHike %s', + currentHike.dashedName + ); + this.transitionTo('/hikes'); + }); + }, + + routerWillLeave(nextState, router, cb) { + // TODO(berks): do animated transitions here stuff here + this.setState({ + showInfo: false, + correct: false, + mouse: [0, 0] + }, cb); + }, + + renderInfo(showInfo, info) { + if (!info) { + return null; + } + return ( + + +

    + { info } +

    +
    + + + +
    + ); + }, + + renderQuestion(number, question, answer, shake) { + return ({ val: { x } }) => { + const style = { + WebkitTransform: `translate3d(${ x }px, 0, 0)`, + transform: `translate3d(${ x }px, 0, 0)` + }; + const title =

    Question { number }

    ; + return ( + +

    { question }

    +
    + ); + }; + }, + + render() { + const { showInfo, shake } = this.state; + const { currentHike: { tests = [] } } = this.props; + const { number = '1' } = this.props.params; + + const [question, answer, info] = tests[number - 1] || []; + + return ( + + + + { this.renderQuestion(number, question, answer, shake) } + + { this.renderInfo(showInfo, info) } + + + + + + + ); + } +}); diff --git a/common/app/routes/Hikes/flux/Actions.js b/common/app/routes/Hikes/flux/Actions.js new file mode 100644 index 0000000000..b3532f15f0 --- /dev/null +++ b/common/app/routes/Hikes/flux/Actions.js @@ -0,0 +1,69 @@ +import { Actions } from 'thundercats'; +import assign from 'object.assign'; +import debugFactory from 'debug'; +import Fetchr from 'fetchr'; + +const debug = debugFactory('freecc:hikes:actions'); +const service = new Fetchr({ + xhrPath: '/services' +}); + +function getCurrentHike(hikes =[{}], dashedName, currentHike) { + if (!dashedName) { + debug('no dashedName'); + return hikes[0]; + } + + const filterRegex = new RegExp(dashedName, 'i'); + if (currentHike && filterRegex.test(currentHike.dashedName)) { + return currentHike; + } + + debug('setting new hike'); + return hikes + .filter(({ dashedName }) => { + return filterRegex.test(dashedName); + }) + .reduce((throwAway, hike) => { + return hike; + }, currentHike || {}); +} + +export default Actions({ + // start fetching hikes + fetchHikes: null, + // set hikes on store + setHikes: null +}) + .refs({ displayName: 'HikesActions' }) + .init(({ instance }) => { + // set up hikes fetching + instance.fetchHikes.subscribe( + ({ isPrimed, dashedName }) => { + if (isPrimed) { + return instance.setHikes({ + transform: (oldState) => { + const { hikes } = oldState; + const currentHike = getCurrentHike( + hikes, + dashedName, + oldState.currentHike + ); + return assign({}, oldState, { currentHike }); + } + }); + } + service.read('hikes', null, null, (err, hikes) => { + if (err) { + debug('an error occurred fetching hikes', err); + } + instance.setHikes({ + set: { + hikes: hikes, + currentHike: getCurrentHike(hikes, dashedName) + } + }); + }); + } + ); + }); diff --git a/common/app/routes/Hikes/flux/Store.js b/common/app/routes/Hikes/flux/Store.js new file mode 100644 index 0000000000..4893148326 --- /dev/null +++ b/common/app/routes/Hikes/flux/Store.js @@ -0,0 +1,17 @@ +import { Store } from 'thundercats'; + +const initialValue = { + hikes: [], + currentHike: {} +}; + +export default Store(initialValue) + .refs({ displayName: 'HikesStore'}) + .init(({ instance, args }) => { + const [cat] = args; + + let { setHikes } = cat.getActions('hikesActions'); + instance.register(setHikes); + + return instance; + }); diff --git a/common/app/routes/Hikes/flux/index.js b/common/app/routes/Hikes/flux/index.js new file mode 100644 index 0000000000..05980cb3ff --- /dev/null +++ b/common/app/routes/Hikes/flux/index.js @@ -0,0 +1,2 @@ +export { default as HikesActions } from './Actions'; +export { default as HikesStore } from './Store'; diff --git a/common/app/routes/Hikes/index.js b/common/app/routes/Hikes/index.js new file mode 100644 index 0000000000..203380153e --- /dev/null +++ b/common/app/routes/Hikes/index.js @@ -0,0 +1,20 @@ +import Hikes from './components/Hikes.jsx'; +import Lecture from './components/Lecture.jsx'; +import Question from './components/Question.jsx'; + +/* + * show video /hikes/someVideo + * show question /hikes/someVideo/question1 + */ + +export default { + path: 'hikes', + component: Hikes, + childRoutes: [{ + path: ':dashedName', + component: Lecture + }, { + path: ':dashedName/questions/:number', + component: Question + }] +}; diff --git a/common/app/routes/Jobs/index.js b/common/app/routes/Jobs/index.js index 44813f34f6..88bab9f6eb 100644 --- a/common/app/routes/Jobs/index.js +++ b/common/app/routes/Jobs/index.js @@ -1,3 +1,5 @@ +import Jobs from './components/Jobs.jsx'; + /* * show: /jobs * showOne: /jobs/:id @@ -8,12 +10,9 @@ export default { path: '/jobs/(:jobId)', - getComponents(cb) { - require.ensure([], require => { - cb(null, [ - require('./components/Jobs') - ]); - }); + setTimeout(() => { + cb(null, Jobs); + }, 0); } }; diff --git a/common/app/routes/index.js b/common/app/routes/index.js index 6e67b86742..a8e3e6a014 100644 --- a/common/app/routes/index.js +++ b/common/app/routes/index.js @@ -1,11 +1,14 @@ +// import Jobs from './Jobs'; +import Hikes from './Hikes'; + export default { path: '/', - getRoutes(cb) { - require.ensure([], require => { + getChildRoutes(locationState, cb) { + setTimeout(() => { cb(null, [ - // require('./Bonfires'), - require('./Jobs') + // Jobs, + Hikes ]); - }); + }, 0); } }; diff --git a/common/models/challenge.json b/common/models/challenge.json index f270e7f70b..3258d641ee 100644 --- a/common/models/challenge.json +++ b/common/models/challenge.json @@ -8,6 +8,9 @@ "type": "string", "unique": true }, + "title": { + "type": "string" + }, "dashedName": { "type": "string" }, diff --git a/common/models/field-guide.json b/common/models/field-guide.json index be3ae99a25..ce0993efea 100644 --- a/common/models/field-guide.json +++ b/common/models/field-guide.json @@ -15,6 +15,9 @@ "description": { "type": "array", "unique": false + }, + "category": { + "type": "string" } }, "validations": [], diff --git a/common/models/user.json b/common/models/user.json index 0254ff8407..b49b9cd085 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -133,10 +133,6 @@ "resetPasswordExpires": { "type": "string" }, - "uncompletedBonfires": { - "type": "array", - "default": [] - }, "completedBonfires": { "type": [ { diff --git a/common/utils/ajax-stream.js b/common/utils/ajax-stream.js new file mode 100644 index 0000000000..63b55e76ce --- /dev/null +++ b/common/utils/ajax-stream.js @@ -0,0 +1,286 @@ +/* + * Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. + * Microsoft Open Technologies would like to thank its contributors, a list + * of whom are at http://rx.codeplex.com/wikipage?title=Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import debugFactory from 'debug'; +import { AnonymousObservable, helpers } from 'rx'; + +const debug = debugFactory('freecc:ajax$'); +const root = typeof window !== 'undefined' ? window : {}; + +// Gets the proper XMLHttpRequest for support for older IE +function getXMLHttpRequest() { + if (root.XMLHttpRequest) { + return new root.XMLHttpRequest(); + } else { + var progId; + try { + var progIds = [ + 'Msxml2.XMLHTTP', + 'Microsoft.XMLHTTP', + 'Msxml2.XMLHTTP.4.0' + ]; + for (var i = 0; i < 3; i++) { + try { + progId = progIds[i]; + if (new root.ActiveXObject(progId)) { + break; + } + } catch (e) { + // purposely do nothing + helpers.noop(e); + } + } + return new root.ActiveXObject(progId); + } catch (e) { + throw new Error('XMLHttpRequest is not supported by your browser'); + } + } +} + +// Get CORS support even for older IE +function getCORSRequest() { + var xhr = new root.XMLHttpRequest(); + if ('withCredentials' in xhr) { + return xhr; + } else if (root.XDomainRequest) { + return new XDomainRequest(); + } else { + throw new Error('CORS is not supported by your browser'); + } +} + +function normalizeAjaxSuccessEvent(e, xhr, settings) { + var response = ('response' in xhr) ? xhr.response : xhr.responseText; + response = settings.responseType === 'json' ? JSON.parse(response) : response; + return { + response: response, + status: xhr.status, + responseType: xhr.responseType, + xhr: xhr, + originalEvent: e + }; +} + +function normalizeAjaxErrorEvent(e, xhr, type) { + return { + type: type, + status: xhr.status, + xhr: xhr, + originalEvent: e + }; +} + +/* + * + * Creates an observable for an Ajax request with either a settings object + * with url, headers, etc or a string for a URL. + * + * @example + * source = Rx.DOM.ajax('/products'); + * source = Rx.DOM.ajax( url: 'products', method: 'GET' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the Ajax call. + * An object with the following properties + * - url: URL of the request + * - body: The body of the request + * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE + * - async: Whether the request is async + * - headers: Optional headers + * - crossDomain: true if a cross domain request, else false + * + * @returns {Observable} An observable sequence containing the XMLHttpRequest. +*/ + +export function ajax$(options) { + var settings = { + method: 'GET', + crossDomain: false, + async: true, + headers: {}, + responseType: 'text', + createXHR: function() { + return this.crossDomain ? getCORSRequest() : getXMLHttpRequest(); + }, + normalizeError: normalizeAjaxErrorEvent, + normalizeSuccess: normalizeAjaxSuccessEvent + }; + + if (typeof options === 'string') { + settings.url = options; + } else { + for (var prop in options) { + if (hasOwnProperty.call(options, prop)) { + settings[prop] = options[prop]; + } + } + } + + var normalizeError = settings.normalizeError; + var normalizeSuccess = settings.normalizeSuccess; + + if (!settings.crossDomain && !settings.headers['X-Requested-With']) { + settings.headers['X-Requested-With'] = 'XMLHttpRequest'; + } + settings.hasContent = typeof settings.body !== 'undefined'; + + return new AnonymousObservable(function(observer) { + var isDone = false; + var xhr; + + var processResponse = function(xhr, e) { + var status = xhr.status === 1223 ? 204 : xhr.status; + if ((status >= 200 && status <= 300) || status === 0 || status === '') { + observer.onNext(normalizeSuccess(e, xhr, settings)); + observer.onCompleted(); + } else { + observer.onError(normalizeError(e, xhr, 'error')); + } + isDone = true; + }; + + try { + xhr = settings.createXHR(); + } catch (err) { + observer.onError(err); + } + + try { + if (settings.user) { + xhr.open( + settings.method, + settings.url, + settings.async, + settings.user, + settings.password + ); + } else { + xhr.open(settings.method, settings.url, settings.async); + } + + var headers = settings.headers; + for (var header in headers) { + if (hasOwnProperty.call(headers, header)) { + xhr.setRequestHeader(header, headers[header]); + } + } + + if ( + !xhr.upload || + (!('withCredentials' in xhr) && root.XDomainRequest) + ) { + xhr.onload = function(e) { + if (settings.progressObserver) { + settings.progressObserver.onNext(e); + settings.progressObserver.onCompleted(); + } + processResponse(xhr, e); + }; + + if (settings.progressObserver) { + xhr.onprogress = function(e) { + settings.progressObserver.onNext(e); + }; + } + + xhr.onerror = function(e) { + if (settings.progressObserver) { + settings.progressObserver.onError(e); + } + observer.onError(normalizeError(e, xhr, 'error')); + isDone = true; + }; + + xhr.onabort = function(e) { + if (settings.progressObserver) { + settings.progressObserver.onError(e); + } + observer.onError(normalizeError(e, xhr, 'abort')); + isDone = true; + }; + } else { + + xhr.onreadystatechange = function(e) { + if (xhr.readyState === 4) { + processResponse(xhr, e); + } + }; + } + + debug( + 'ajax$ sending content', + settings.hasContent && settings.body + ); + xhr.send(settings.hasContent && settings.body || null); + } catch (e) { + observer.onError(e); + } + + return function() { + if (!isDone && xhr.readyState !== 4) { xhr.abort(); } + }; + }); +} + +/** + * Creates an observable sequence from an Ajax POST Request with the body. + * + * @param {String} url The URL to POST + * @param {Object} body The body to POST + * @returns {Observable} The observable sequence which contains the response + * from the Ajax POST. + */ +export function post$(url, body) { + return ajax$({ url, body, method: 'POST' }); +} + +export function postJSON$(url, body) { + return ajax$({ + url, + body: JSON.stringify(body), + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); +} + +/** + * Creates an observable sequence from an Ajax GET Request with the body. + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which + * contains the response from the Ajax GET. + */ +export function get$(url) { + return ajax$({ url: url }); +} + +/** + * Creates an observable sequence from JSON from an Ajax request + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which contains the parsed JSON + */ +export function getJSON$(url) { + if (!root.JSON && typeof root.JSON.parse !== 'function') { + throw new TypeError('JSON is not supported in your runtime.'); + } + return ajax$({url: url, responseType: 'json'}).map(function(x) { + return x.response; + }); +} diff --git a/gulpfile.js b/gulpfile.js index e5cd405528..28cef3e44a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,18 +1,70 @@ -var gulp = require('gulp'), +require('babel-core/register'); +var Rx = require('rx'), + gulp = require('gulp'), + path = require('path'), + + // utils + notify = require('gulp-notify'), debug = require('debug')('freecc:gulp'), bower = require('bower-main-files'), + + // loopback client + browserify = require('browserify'), + boot = require('loopback-boot'), + envify = require('envify/custom'), + toVinylWithName = require('vinyl-source-stream'), + + // react app + webpack = require('gulp-webpack'), + webpackConfig = require('./webpack.config.js'), + webpackConfigNode = require('./webpack.config.node.js'), + + // server process nodemon = require('gulp-nodemon'), sync = require('browser-sync'), - reload = sync.reload, + inject = require('gulp-inject'), - reloadDelay = 1000, + // css less = require('gulp-less'), - path = require('path'), + + // lint eslint = require('gulp-eslint'); + +Rx.longStackSupport = true; +var reloadDelay = 1000; +var reload = sync.reload; var paths = { server: './server/server.js', - serverIgnore: [] + serverIgnore: [ + 'gulpfile.js', + 'public/', + '!public/js/bundle.js', + 'node_modules/', + 'client/' + ], + + publicJs: './public/js', + + loopback: { + client: './client/loopbackClient', + root: path.join(__dirname, 'client/'), + clientName: 'lbApp' + }, + + client: { + src: './client', + dest: 'public/js' + }, + + node: { + src: './client', + dest: 'common/app' + }, + + syncWatch: [ + 'public/**/*.*' + ] }; gulp.task('inject', function() { @@ -23,12 +75,62 @@ gulp.task('inject', function() { .pipe(gulp.dest('views')); }); +// NOTE(berks): not using this for now as loopback client is just too large +gulp.task('loopback', function() { + var config = { + basedir: __dirname, + debug: true, + cache: {}, + packageCache: {}, + fullPaths: true, + standalone: paths.loopback.clientName + }; + + var b = browserify(config); + + // compile loopback for the client + b.require(paths.loopback.client); + + boot.compileToBrowserify(paths.loopback.root, b); + + // sub process.env for proper strings + b.transform(envify({ + NODE_ENV: 'development' + })); + + return b.bundle() + .on('error', errorNotifier) + .pipe(toVinylWithName(paths.loopback.clientName + '.js')) + .pipe(gulp.dest(paths.publicJs)); +}); + +gulp.task('pack-client', function() { + return gulp.src(webpackConfig.entry) + .pipe(webpack(webpackConfig)) + .pipe(gulp.dest(webpackConfig.output.path)); +}); + +gulp.task('pack-watch', function() { + return gulp.src(webpackConfig.entry) + .pipe(webpack(Object.assign(webpackConfig, { watch: true }))) + .pipe(gulp.dest(webpackConfig.output.path)); +}); + +gulp.task('pack-node', function() { + return gulp.src(webpackConfigNode.entry) + .pipe(webpack(webpackConfigNode)) + .pipe(gulp.dest(webpackConfigNode.output.path)); +}); + +gulp.task('pack', ['pack-client', 'pack-node']); + gulp.task('serve', function(cb) { var called = false; nodemon({ script: paths.server, - ext: '.js', + ext: '.js .json', ignore: paths.serverIgnore, + exec: './node_modules/.bin/babel-node', env: { 'NODE_ENV': 'development', 'DEBUG': process.env.DEBUG || 'freecc:*' @@ -57,7 +159,7 @@ gulp.task('sync', ['serve'], function() { sync.init(null, { proxy: 'http://localhost:3000', logLeval: 'debug', - files: ['public/js/lib/*/*.{js, jsx}'], + files: paths.syncWatch, port: 3001, open: false, reloadDelay: reloadDelay @@ -84,4 +186,17 @@ gulp.task('watch', ['less', 'serve', 'sync'], function() { gulp.watch('./public/css/*.less', ['less']); }); -gulp.task('default', ['less', 'serve', 'sync', 'watch']); +gulp.task('default', ['less', 'serve', 'sync', 'watch', 'pack-watch']); + +function errorNotifier() { + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: 'Compile Error', + message: '<%= error %>' + }).apply(this, args); + + // Keep gulp from hanging on this task + this.emit('end'); +} diff --git a/package.json b/package.json index b7f3b12659..6b349a7595 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "npm": "2.3.0" }, "scripts": { - "start": "node server/server.js", + "start": "babel-node server/server.js", "lint": "eslint --ext=.js,.jsx .", "test": "mocha", "postinstall": "bower cache clean && bower install && gulp build" @@ -29,10 +29,14 @@ "dependencies": { "accepts": "~1.2.5", "async": "~0.9.0", + "babel": "5.6.14", + "babel-core": "5.6.15", + "babel-loader": "5.2.2", "bcrypt-nodejs": "~0.0.3", - "body-parser": "~1.9.3", + "body-parser": "^1.13.2", "chai-jquery": "~2.0.0", "cheerio": "~0.18.0", + "classnames": "^2.1.2", "clockwork": "~0.1.1", "compression": "~1.2.1", "connect-mongo": "~0.7.0", @@ -45,16 +49,20 @@ "express": "~4.10.4", "express-flash": "~0.0.2", "express-session": "~1.9.2", + "express-state": "^1.2.0", "express-validator": "~2.8.0", + "fetchr": "^0.5.12", "font-awesome": "~4.3.0", "forever": "~0.14.1", "frameguard": "^0.2.2", "github-api": "~0.7.0", "gulp-less": "^3.0.3", "gulp-minify-css": "~0.5.1", + "gulp-webpack": "^1.5.0", "helmet": "~0.9.0", "helmet-csp": "^0.2.3", "jade": "~1.8.0", + "json-loader": "^0.5.2", "less": "~1.7.5", "less-middleware": "~2.0.1", "lodash": "^3.9.3", @@ -67,6 +75,7 @@ "moment": "~2.10.2", "mongodb": "^2.0.33", "morgan": "~1.5.0", + "node-libs-browser": "^0.5.2", "node-slack": "0.0.7", "node-uuid": "^1.4.3", "nodemailer": "~1.3.0", @@ -80,15 +89,20 @@ "pmx": "^0.3.16", "ramda": "~0.10.0", "react": "^0.13.3", - "react-bootstrap": "^0.23.4", - "react-router": "^1.0.0-beta1", + "react-bootstrap": "^0.23.7", + "react-motion": "~0.1.0", + "react-router": "https://github.com/BerkeleyTrue/react-router#freecodecamp", + "react-vimeo": "^0.0.3", "request": "~2.53.0", "rx": "^2.5.3", "sanitize-html": "~1.6.1", - "thundercats": "^2.0.0-rc6", + "source-map-support": "^0.3.2", + "thundercats": "^2.0.0", + "thundercats-react": "0.0.6", "twit": "~1.1.20", "uglify-js": "~2.4.15", "validator": "~3.22.1", + "webpack": "^1.9.12", "yui": "~3.18.1" }, "devDependencies": { @@ -96,18 +110,22 @@ "blessed": "~0.0.37", "bower-main-files": "~0.0.4", "browser-sync": "~1.8.1", + "browserify": "^10.2.4", "chai": "~1.10.0", + "envify": "^3.4.0", "eslint": "^0.21.2", "eslint-plugin-react": "^2.3.0", "gulp": "~3.8.8", "gulp-eslint": "~0.9.0", "gulp-inject": "~1.0.2", "gulp-nodemon": "^2.0.3", + "gulp-notify": "^2.2.0", "istanbul": "^0.3.15", "loopback-explorer": "^1.7.2", "loopback-testing": "^1.1.0", "mocha": "~2.0.1", "multiline": "~1.0.1", - "supertest": "~0.15.0" + "supertest": "~0.15.0", + "vinyl-source-stream": "^1.1.0" } } diff --git a/pm2Start.js b/pm2Start.js index 052f3bdbfa..5742d6c232 100644 --- a/pm2Start.js +++ b/pm2Start.js @@ -1,12 +1,13 @@ +require('babel/register'); var pm2 = require('pm2'); pm2.connect(function() { pm2.start({ name: 'server', script: 'server/server.js', - exec_mode: 'cluster', + 'exec_mode': 'cluster', instances: '2', - max_memory_restart: '900M' - }, function(err, apps) { + 'max_memory_restart': '900M' + }, function() { pm2.disconnect(); }); }); diff --git a/public/css/lib/Vimeo.css b/public/css/lib/Vimeo.css new file mode 100644 index 0000000000..1bdb4b99bd --- /dev/null +++ b/public/css/lib/Vimeo.css @@ -0,0 +1,97 @@ +.vimeo-image, +.vimeo-image:after, +.vimeo-embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.vimeo { + position: relative; + padding-bottom: 56.25%; + background: #e2e2e2; +} +.vimeo iframe { + border: 0; +} +.vimeo-image { + background-position: center center; + background-size: 100% auto; +} +.vimeo-image:after { + z-index: 1; + display: block; + content: ''; + background: rgba(0,0,0,0.3); +} +.vimeo-play-button, +.vimeo-loading { + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); +} +.vimeo-play-button { + z-index: 2; + position: absolute; + padding: 0; + width: 70px; + border: 0; + background: none; +} +.vimeo-play-button:focus { + outline: none; +} +.vimeo-play-button svg { + fill: #fff; + -webkit-filter: drop-shadow(0 1px 1px rgba(0,0,0,0.8)); + filter: drop-shadow(0 1px 1px rgba(0,0,0,0.8)); +} +.vimeo-loading { + z-index: 4; + position: absolute; + width: 32px; + height: 32px; +} +.vimeo-loading svg { + fill: #000; + transform-origin: 50% 50%; + -webkit-animation: spinner 0.8s infinite linear; + animation: spinner 0.8s infinite linear; +} +.vimeo-embed iframe { + width: 100%; + height: 100%; +} +@-moz-keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +@-webkit-keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +@-o-keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +@keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/public/js/main_0.0.3.js b/public/js/main_0.0.3.js index 7d27601eb0..602bc0985f 100644 --- a/public/js/main_0.0.3.js +++ b/public/js/main_0.0.3.js @@ -384,11 +384,14 @@ $(document).ready(function() { } }); } - var initOff = ($('.scroll-locker').offset().top - $(window).scrollTop()); - lockTop(initOff); - $(window).on('resize', function(){ - lockTop(initOff); - }); + var $scrollLocker = $('.scroll-locker'); + if ($scrollLocker.offset()) { + var initOff = $scrollLocker.offset().top - $(window).scrollTop(); + lockTop(initOff); + $(window).on('resize', function(){ + lockTop(initOff); + }); + } } $('#comment-button').on('click', commentSubmitButtonHandler); diff --git a/public/json/bootcamps.json b/public/json/bootcamps.json index c9895932f6..18f2a5f046 100644 --- a/public/json/bootcamps.json +++ b/public/json/bootcamps.json @@ -96,6 +96,16 @@ "san-francisco", "chicago" ] +}, { + "name": "DevMountain", + "cost": "8900", + "housing": "0", + "finance": true, + "weeks": "12", + "cities": [ + "provo", + "salt-lake-city" + ] }, { "name": "Epicodus", "cost": "4500", diff --git a/seed/challenges/angularjs.json b/seed/challenges/angularjs.json new file mode 100644 index 0000000000..48207fb41f --- /dev/null +++ b/seed/challenges/angularjs.json @@ -0,0 +1,125 @@ +{ + "name": "AngularJS", + "order": 0.015, + "challenges": [ + { + "id": "bd7154d8c441eddfaeb5bdef", + "name": "Waypoint: Get Started with Angular.js", + "dashedName": "waypoint-get-started-with-angularjs", + "difficulty": 0.34, + "challengeSeed": ["114684726"], + "description": [ + "Code School has a short, free Angular.js course. This will give us a quick tour of Angular.js's mechanics and features.", + "In this course, we'll build a virtual shop entirely in Angular.js.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/1/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bd7155d8c441eddfaeb5bdef", + "name": "Waypoint: Apply Angular.js Directives", + "dashedName": "waypoint-apply-angularjs-directives", + "difficulty": 0.35, + "challengeSeed": ["114684727"], + "description": [ + "Directives serve as markers in your HTML. When Angular.js compiles your HTML, it will can alter the behavior of DOM elements based on the directives you've used.", + "Let's learn how these powerful directives work, and how to use them to make your web apps more dynamic", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bd7156d8c441eddfaeb5bdef", + "name": "Waypoint: Power Forms with Angular.js", + "dashedName": "waypoint-power-forms-with-angularjs", + "difficulty": 0.36, + "challengeSeed": ["114684729"], + "description": [ + "One area where Angular.js really shines is its powerful web forms.", + "Learn how to create reactive Angular.js forms, including real-time form validation.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/3/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bd7157d8c441eddfaeb5bdef", + "name": "Waypoint: Customize Angular.js Directives", + "dashedName": "waypoint-customize-angularjs-directives", + "difficulty": 0.37, + "challengeSeed": ["114685062"], + "description": [ + "Now we'll learn how to modify existing Angular.js directives, and even build directives of your own.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/4/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bd7158d8c441eddfaeb5bdef", + "name": "Waypoint: Create Angular.js Services", + "dashedName": "waypoint-create-angularjs-services", + "difficulty": 0.38, + "challengeSeed": ["114685060"], + "description": [ + "Services are functions that you can use and reuse throughout your Angular.js app to get things done.", + "We'll learn how to use services in this final Code School Angular.js challenge.", + "Go to http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/5/section/1/video/1 and complete the section." + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + } + ] +} diff --git a/seed/challenges/basejumps.json b/seed/challenges/basejumps.json index 70e07e0788..717a99a47d 100644 --- a/seed/challenges/basejumps.json +++ b/seed/challenges/basejumps.json @@ -1,6 +1,6 @@ { "name": "Full Stack JavaScript Projects", - "order": 0.014, + "order": 0.017, "challenges": [ { "id": "bd7158d8c443eddfaeb5bcef", @@ -17,9 +17,7 @@ "Click on Create New Workspace at the top right of the c9.io page, then click on the \"Create a new workspace\" popup that appears below it the button after you click on it.", "Give your workspace a name.", "Choose Node.js in the selection area below the name field.", - "Click the Create button.", - "Wait for the workspace to finish processing and select it on the left sidebar, below the Create New Workspace button.", - "Click the \"Start Editing\" button.", + "Click the Create button. Then click into your new workspace.", "In the lower right hand corner you should see a terminal window. In this window use the following commands. You don't need to know what these mean at this point.", "Never run this command on your local machine. But in your Cloud 9 terminal window, run: rm -rf * && echo \"export NODE_PATH=$NODE_PATH:/home/ubuntu/.nvm/v0.10.35/lib/node_modules\" >> ~/.bashrc && source ~/.bashrc && npm install -g yo grunt grunt-cli generator-angular-fullstack && yo angular-fullstack", "Yeoman will prompt you to answer some questions. Answer them like this:", @@ -59,6 +57,7 @@ "Set the config flag for your Heroku environment and add MongoLab for your MongoDB instance by running the following command: cd ~/workspace/dist && heroku config:set NODE_ENV=production && heroku addons:create mongolab.", "As you build your app, you should frequently commit changes to your codebase. Make sure you're in the ~/workspace directory by running cd ~/workspace. Then you can this code to stage the changes to your changes and commit them: git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a short summary of the changes you made to your code, such as \"added a records controller and corresponding routes\".", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Now you're ready to move on to your first Basejump. Click the \"I've completed this challenge\" and move on." ], "challengeType": 2, @@ -69,9 +68,9 @@ "name": "Basejump: Build a Voting App", "dashedName": "basejump-build-a-voting-app", "difficulty": 2.01, - "challengeSeed": ["128451852"], + "challengeSeed": ["133315786"], "description": [ - "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://voteplex.herokuapp.com/ and deploy it to Heroku.", + "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://votingapp.herokuapp.com/ and deploy it to Heroku.", "Note that for each Basejump, you should create a new GitHub repository and a new Heroku project. If you can't remember how to do this, revisit http://freecodecamp.com/challenges/get-set-for-basejumps.", "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", @@ -84,6 +83,7 @@ "Bonus User Story: As an unauthenticated user, I can see everyone's polls, but I can't vote on anything.", "Bonus User Story: As an unauthenticated or authenticated user, I can see the results of polls in chart form. (This could be implemented using Chart.js or Google Charts.)", "Bonus User Story: As an authenticated user, if I don't like the options on a poll, I can create a new option.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your GitHub repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.", "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.

    Click here then add your link to your tweet's text" ], @@ -105,9 +105,9 @@ "name": "Basejump: Build a Nightlife Coordination App", "dashedName": "basejump-build-a-nightlife-coordination-app", "difficulty": 2.02, - "challengeSeed": ["128451852"], + "challengeSeed": ["133315781"], "description": [ - "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://sociallife.herokuapp.com/ and deploy it to Heroku.", + "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://whatsgoinontonight.herokuapp.com/ and deploy it to Heroku.", "Note that for each Basejump, you should create a new GitHub repository and a new Heroku project. If you can't remember how to do this, revisit http://freecodecamp.com/challenges/get-set-for-basejumps.", "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", @@ -116,6 +116,8 @@ "User Story: As an authenticated user, I can add myself to a bar to indicate I am going there tonight.", "User Story: As an authenticated user, I can remove myself from a bar if I no longer want to go there.", "Bonus User Story: As an unauthenticated user, when I login I should not have to search again.", + "Hint: Try using the Yelp API to find venues in the cities your users search for.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your GitHub repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.", "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.

    Click here then add your link to your tweet's text" ], @@ -137,9 +139,9 @@ "name": "Basejump: Chart the Stock Market", "dashedName": "basejump-chart-the-stock-market", "difficulty": 2.03, - "challengeSeed": ["128451852"], + "challengeSeed": ["133315787"], "description": [ - "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://stockjump.herokuapp.com/ and deploy it to Heroku.", + "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://stockstream.herokuapp.com/ and deploy it to Heroku.", "Note that for each Basejump, you should create a new GitHub repository and a new Heroku project. If you can't remember how to do this, revisit http://freecodecamp.com/challenges/get-set-for-basejumps.", "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", @@ -148,6 +150,7 @@ "User Story: As a user, I can add new stocks by their symbol name.", "User Story: As a user, I can remove stocks.", "Bonus User Story: As a user, I can see changes in real-time when any other user adds or removes a stock.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your GitHub repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.", "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.

    Click here then add your link to your tweet's text" ], @@ -169,9 +172,9 @@ "name": "Basejump: Manage a Book Trading Club", "dashedName": "basejump-manage-a-book-trading-club", "difficulty": 2.04, - "challengeSeed": ["128451852"], + "challengeSeed": ["133316032"], "description": [ - "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://bookoutpost.herokuapp.com/ and deploy it to Heroku.", + "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://bookjump.herokuapp.com/ and deploy it to Heroku.", "Note that for each Basejump, you should create a new GitHub repository and a new Heroku project. If you can't remember how to do this, revisit http://freecodecamp.com/challenges/get-set-for-basejumps.", "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", @@ -180,6 +183,7 @@ "User Story: As an authenticated user, I can add a new book.", "User Story: As an authenticated user, I can update my settings to store my full name, city, and state.", "Bonus User Story: As an authenticated user, I can propose a trade and wait for the other user to accept the trade.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your GitHub repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.", "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.

    Click here then add your link to your tweet's text" ], @@ -201,9 +205,9 @@ "name": "Basejump: Build a Pinterest Clone", "dashedName": "basejump-build-a-pinterest-clone", "difficulty": 2.05, - "challengeSeed": ["128451852"], + "challengeSeed": ["133315784"], "description": [ - "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://linkterest.herokuapp.com/ and deploy it to Heroku.", + "Objective: Build a full stack JavaScript app that successfully reverse-engineers this: http://stark-lowlands-3680.herokuapp.com/ and deploy it to Heroku.", "Note that for each Basejump, you should create a new GitHub repository and a new Heroku project. If you can't remember how to do this, revisit http://freecodecamp.com/challenges/get-set-for-basejumps.", "As you build your app, you should frequently commit changes to your codebase. You can do this by running git commit -am \"your commit message\". Note that you should replace \"your commit message\" with a brief summary of the changes you made to your code.", "You can push these new commits to GitHub by running git push origin master, and to Heroku by running grunt --force && grunt buildcontrol:heroku.", @@ -215,6 +219,7 @@ "User Story: As an unauthenticated user, I can browse other users' walls of images.", "Bonus User Story: As an authenticated user, if I upload an image that is broken, it will be replaced by a placeholder image. (can use jQuery broken image detection)", "Hint: Masonry.js is a library that allows for Pinterest-style image grids.", + "If you need further guidance on using Yeoman Angular-Fullstack Generator, check out: https://github.com/clnhll/guidetobasejumps.", "Once you've finished implementing these user stories, click the \"I've completed this challenge\" button and enter the URLs for both your GitHub repository and your live app running on Heroku. If you pair programmed with a friend, enter his or her Free Code Camp username as well so that you both get credit for completing it.", "If you'd like immediate feedback on your project, click this button and paste in a link to your Heroku project. Otherwise, we'll review it before you start your nonprofit projects.

    Click here then add your link to your tweet's text" ], diff --git a/seed/challenges/basic-javascript.json b/seed/challenges/basic-javascript.json index 9d1345c659..b47d31002e 100644 --- a/seed/challenges/basic-javascript.json +++ b/seed/challenges/basic-javascript.json @@ -3,7 +3,7 @@ "order": 0.006, "challenges": [ { - "_id":"bd7123c9c441eddfaeb4bdef", + "id":"bd7123c9c441eddfaeb4bdef", "name":"Welcome To Comments", "dashedName":"waypoint-welcome-to-comments", "difficulty":"9.98", @@ -29,7 +29,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c441eddfaeb5bdef", + "id": "bd7123c9c441eddfaeb5bdef", "name": "Unconditionally Loving Booleans", "dashedName": "waypoint-unconditionally-loving-booleans", "difficulty": "9.98001", @@ -55,7 +55,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c443eddfaeb5bdef", + "id": "bd7123c9c443eddfaeb5bdef", "name": "Start Using Variables", "dashedName": "waypoint-start-using-variables", "difficulty": "9.9801", @@ -83,7 +83,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c444eddfaeb5bdef", + "id": "bd7123c9c444eddfaeb5bdef", "name": "Define Your First and Last Name", "dashedName": "waypoint-define-your-first-and-last-name", "difficulty": "9.9802", @@ -111,7 +111,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c448eddfaeb5bdef", + "id": "bd7123c9c448eddfaeb5bdef", "name": "Check the Length Property of a String Variable", "dashedName": "waypoint-check-the-length-property-of-a-string-variable", "difficulty": "9.9809", @@ -142,7 +142,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c549eddfaeb5bdef", + "id": "bd7123c9c549eddfaeb5bdef", "name": "Use Bracket Notation to Find the First Character in a String", "dashedName": "waypoint-use-bracket-notation-to-find-the-first-character-in-a-string", "difficulty": "9.9810", @@ -174,7 +174,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c450eddfaeb5bdef", + "id": "bd7123c9c450eddfaeb5bdef", "name": "Use Bracket Notation to Find the Nth Character in a String", "dashedName": "waypoint-use-bracket-notation-to-find-the-nth-character-in-a-string", "difficulty": "9.9811", @@ -205,7 +205,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c451eddfaeb5bdef", + "id": "bd7123c9c451eddfaeb5bdef", "name": "Use Bracket Notation to Find the Last Character in a String", "dashedName": "waypoint-use-bracket-notation-to-find-the-last-character-in-a-string", "difficulty": "9.9812", @@ -235,7 +235,7 @@ "challengeType": 1 }, { - "_id": "bd7123c9c452eddfaeb5bdef", + "id": "bd7123c9c452eddfaeb5bdef", "name": "Use Bracket Notation to Find the Nth to Last Character in a String", "dashedName": "waypoint-use-bracket-notation-to-find-the-nth-to-last-character-in-a-string", "difficulty": "9.9813", @@ -265,7 +265,7 @@ "challengeType": 1 }, { - "_id": "cf1111c1c11feddfaeb3bdef", + "id": "cf1111c1c11feddfaeb3bdef", "name": "Magical Maths Addition", "dashedName": "waypoint-magical-maths-addition", "difficulty": "9.98141", @@ -289,7 +289,7 @@ "challengeType": 1 }, { - "_id": "cf1111c1c11feddfaeb4bdef", + "id": "cf1111c1c11feddfaeb4bdef", "name": "Magical Maths Subtraction", "dashedName": "waypoint-magical-maths-subtraction", "difficulty": "9.98142", @@ -313,7 +313,7 @@ "challengeType": 1 }, { - "_id": "cf1111c1c11feddfaeb5bdef", + "id": "cf1111c1c11feddfaeb5bdef", "name": "Magical Maths Multiplication", "dashedName": "waypoint-magical-maths-multiplication", "difficulty": "9.98143", @@ -337,7 +337,7 @@ "challengeType": 1 }, { - "_id": "cf1111c1c11feddfaeb6bdef", + "id": "cf1111c1c11feddfaeb6bdef", "name": "Magical Maths Division", "dashedName": "waypoint-magical-maths-division", "difficulty": "9.9814", @@ -361,7 +361,7 @@ "challengeType": 1 }, { - "_id": "cf1111c1c11feddfaeb4bdef", + "id": "cf1111c1c11feddfaeb4bdef", "name": "Creating Decimals", "dashedName": "waypoint-creating-decimals", "difficulty": "9.9815", @@ -386,7 +386,7 @@ "challengeType": 1 }, { - "_id": "bd7993c9c69feddfaeb7bdef", + "id": "bd7993c9c69feddfaeb7bdef", "name": "Working With Decimals", "dashedName": "waypoint-working-with-decimals", "difficulty": "9.98151", @@ -411,7 +411,7 @@ "challengeType": 1 }, { - "_id": "bd7993c9c69feddfaeb8bdef", + "id": "bd7993c9c69feddfaeb8bdef", "name": "An Array Of new Information", "dashedName": "waypoint-an-array-of-new-information", "difficulty": "9.9816", @@ -439,7 +439,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb7bdef", + "id":"cf1111c1c11feddfaeb7bdef", "name":"Nesting Arrays", "dashedName":"waypoint-nesting-arrays", "difficulty":"9.98161", @@ -460,7 +460,7 @@ "challengeType": 1 }, { - "_id":"bg9997c9c79feddfaeb9bdef", + "id":"bg9997c9c79feddfaeb9bdef", "name":"Accessing data with Indexes", "dashedName":"waypoint-accessing-data-with-indexes", "difficulty":"9.9817", @@ -492,7 +492,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb8bdef", + "id":"cf1111c1c11feddfaeb8bdef", "name":"Modifying Data With Indexes", "dashedName":"waypoint-modifying-data-with-indexes", "difficulty":"9.98171", @@ -525,7 +525,7 @@ "challengeType": 1 }, { - "_id": "bg9994c9c69feddfaeb9bdef", + "id": "bg9994c9c69feddfaeb9bdef", "name": "Manipulating Arrays With pop()", "dashedName": "waypoint-manipulating-arrays-with-pop", "difficulty": "9.9818", @@ -557,7 +557,7 @@ "challengeType": 1 }, { - "_id": "bg9995c9c69feddfaeb9bdef", + "id": "bg9995c9c69feddfaeb9bdef", "name": "Manipulating Arrays With push()", "dashedName": "waypoint-manipulating-arrays-with-push", "difficulty": "9.9818", @@ -580,7 +580,7 @@ "challengeType": 1 }, { - "_id": "bg9996c9c69feddfaeb9bdef", + "id": "bg9996c9c69feddfaeb9bdef", "name": "Manipulating Arrays With shift()", "dashedName": "waypoint-manipulating-arrays-with-shift", "difficulty": "9.9817", @@ -604,7 +604,7 @@ "challengeType": 1 }, { - "_id": "bg9997c9c69feddfaeb9bdef", + "id": "bg9997c9c69feddfaeb9bdef", "name": "Manipulating Arrays With unshift()", "dashedName": "waypoint-manipulating-arrays-with-unshift", "difficulty": "9.9818", @@ -627,7 +627,7 @@ "challengeType": 1 }, { - "_id":"bg9997c9c89feddfaeb9bdef", + "id":"bg9997c9c89feddfaeb9bdef", "name":"Make it functional", "dashedName":"waypoint-make-it-functional", "difficulty":"9.9819", @@ -665,7 +665,7 @@ "challengeType": 1 }, { - "_id":"bg9998c9c99feddfaeb9bdef", + "id":"bg9998c9c99feddfaeb9bdef", "name":"I Object!", "dashedName":"waypoint-i-object", "difficulty":"9.9822", @@ -712,7 +712,7 @@ "challengeType": 1 }, { - "_id":"bg9999c9c99feddfaeb9bdef", + "id":"bg9999c9c99feddfaeb9bdef", "name":"Manipulating Objects", "dashedName":"waypoint-manipulating-objects", "difficulty":"9.9823", @@ -758,7 +758,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb5bdef", + "id":"cf1111c1c11feddfaeb5bdef", "name":"Looping with for", "dashedName":"waypoint-looping-with-for", "difficulty":"9.9824", @@ -788,7 +788,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb1bdef", + "id":"cf1111c1c11feddfaeb1bdef", "name":"Looping with while", "dashedName":"waypoint-looping-with-while", "difficulty":"9.9825", @@ -819,7 +819,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb2bdef", + "id":"cf1111c1c11feddfaeb2bdef", "name":"Looping with do while", "dashedName":"waypoint-looping-with-do-while", "difficulty":"9.9826", @@ -850,7 +850,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c11feddfaeb9bdef", + "id":"cf1111c1c11feddfaeb9bdef", "name":"Random Numbers", "dashedName":"waypoint-random-numbers", "difficulty":"9.9827", @@ -877,7 +877,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb1bdef", + "id":"cf1111c1c12feddfaeb1bdef", "name":"Random Whole Numbers", "dashedName":"waypoint-random-whole-numbers", "difficulty":"9.9828", @@ -907,7 +907,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb2bdef", + "id":"cf1111c1c12feddfaeb2bdef", "name":"Random Whole Numbers In a Range", "dashedName":"waypoint-random-whole-numbers-in-a-range", "difficulty":"9.9829", @@ -936,7 +936,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb3bdef", + "id":"cf1111c1c12feddfaeb3bdef", "name":"If Else Statements", "dashedName":"waypoint-if-else-statements", "difficulty":"9.983", @@ -971,7 +971,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb6bdef", + "id":"cf1111c1c12feddfaeb6bdef", "name":"An Intro To RegEx", "dashedName":"waypoint-an-intro-to-regex", "difficulty":"9.984", @@ -1008,7 +1008,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb7bdef", + "id":"cf1111c1c12feddfaeb7bdef", "name":"Finding Numbers", "dashedName":"waypoint-finding-numbers", "difficulty":"9.985", @@ -1040,7 +1040,7 @@ "challengeType": 1 }, { - "_id":"cf1111c1c12feddfaeb8bdef", + "id":"cf1111c1c12feddfaeb8bdef", "name":"Finding WhiteSpace", "dashedName":"waypoint-finding-whitespace", "difficulty":"9.987", diff --git a/seed/challenges/ziplines.json b/seed/challenges/basic-ziplines.json similarity index 58% rename from seed/challenges/ziplines.json rename to seed/challenges/basic-ziplines.json index 02571408c2..16053946ec 100644 --- a/seed/challenges/ziplines.json +++ b/seed/challenges/basic-ziplines.json @@ -1,6 +1,6 @@ { - "name": "Front End Development Projects", - "order": 0.012, + "name": "Basic Front End Development Projects", + "order": 0.006, "challenges": [ { "id": "bd7158d8c442eddfbeb5bd1f", @@ -37,21 +37,58 @@ "namePt": "", "descriptionPt": [] }, + { + "id": "bd7158d8c242eddfaeb5bd13", + "name": "Zipline: Build a Personal Portfolio Webpage", + "dashedName": "zipline-build-a-personal-portfolio-webpage", + "difficulty": 1.01, + "challengeSeed": ["133315782"], + "description": [ + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/ThiagoFerreir4/full/eNMxEp.", + "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", + "Rule #2: You may use whichever libraries or APIs you need.", + "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", + "Here are the user stories you must enable, and optional bonus user stories:", + "User Story: As a user, I can access all of the portfolio webpage's content just by scrolling.", + "User Story: As a user, I can click different buttons that will take me to the portfolio creator's different social media pages.", + "User Story: As a user, I can see thumbnail images of different projects the portfolio creator has built (if you don't haven't built any websites before, use placeholders.)", + "Bonus User Story: As a user, I navigate to different sections of the webpage by clicking buttons in the navigation.", + "Don't worry if you don't have anything to showcase on your portfolio yet - you will build several several apps on the next few CodePen challenges, and can come back and update your portfolio later.", + "There are many great portfolio templates out there, but for this challenge, you'll need to build a portfolio page yourself. Using Bootstrap will make this much easier for you.", + "Note that CodePen.io overrides the Window.open() function, so if you want to open windows using jquery, you will need to target invisible anchor elements like this one: <a target='_blank'&rt;.", + "Remember to use RSAP if you get stuck.", + "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.

    Click here then add your link to your tweet's text" + ], + "challengeType": 3, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, { "id": "bd7158d8c442eddfaeb5bd13", "name": "Zipline: Build a Random Quote Machine", "dashedName": "zipline-build-a-random-quote-machine", - "difficulty": 1.01, + "difficulty": 1.02, "challengeSeed": ["126415122"], "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/AdventureBear/full/vEoVMw.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/AdventureBear/full/vEoVMw.", + "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", "Rule #2: You may use whichever libraries or APIs you need.", "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", "Here are the user stories you must enable, and optional bonus user stories:", "User Story: As a user, I can click a button to show me a new random quote.", "Bonus User Story: As a user, I can press a button to tweet out a quote.", - "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.", + "Note that you can either put your quotes into an array and show them at random, or use an API to get quotes, such as http://forismatic.com/en/api/.", + "Remember to use RSAP if you get stuck.", "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.

    Click here then add your link to your tweet's text" ], @@ -72,11 +109,11 @@ "id": "bd7158d8c442eddfaeb5bd10", "name": "Zipline: Show the Local Weather", "dashedName": "zipline-show-the-local-weather", - "difficulty": 1.02, + "difficulty": 1.03, "challengeSeed": ["126415127"], "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/AdventureBear/full/yNBJRj.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/AdventureBear/full/yNBJRj.", + "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", "Rule #2: You may use whichever libraries or APIs you need.", "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", "Here are the user stories you must enable, and optional bonus user stories:", @@ -84,110 +121,7 @@ "Bonus User Story: As a user, I can see an icon depending on the temperature..", "Bonus User Story: As a user, I see a different background image depending on the temperature (e.g. snowy mountain, hot desert).", "Bonus User Story: As a user, I can push a button to toggle between Fahrenheit and Celsius.", - "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.

    Click here then add your link to your tweet's text" - ], - "challengeType": 3, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "", - "descriptionEs": [], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd7158d8c442eddfaeb5bd1f", - "name": "Zipline: Use the Twitch.tv JSON API", - "dashedName": "zipline-use-the-twitchtv-json-api", - "difficulty": 1.03, - "challengeSeed": ["126411564"], - "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/GJKRxZ.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", - "Rule #2: You may use whichever libraries or APIs you need.", - "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", - "Here are the user stories you must enable, and optional bonus user stories:", - "User Story: As a user, I can see whether Free Code Camp is currently streaming on Twitch.tv.", - "User Story: As a user, I can click the status output and be sent directly to the Free Code Camp's Twitch.tv channel.", - "User Story: As a user, if Free Code Camp is streaming, I can see additional details about what they are streaming.", - "Bonus User Story: As a user, I can search through the streams listed.", - "Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp.", - "Hint: The relevant documentation about this API call is here: https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel.", - "Hint: Here's an array of the Twitch.tv usernames of people who regularly stream coding: [\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]", - "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.

    Click here then add your link to your tweet's text" - ], - "challengeType": 3, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "", - "descriptionEs": [], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd7158d8c442eddfaeb5bd18", - "name": "Zipline: Stylize Stories on Camper News", - "dashedName": "zipline-stylize-stories-on-camper-news", - "difficulty": 1.04, - "challengeSeed": ["126415129"], - "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/Wveezv.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", - "Rule #2: You may use whichever libraries or APIs you need.", - "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", - "Here are the user stories you must enable, and optional bonus user stories:", - "User Story: As a user, I can browse recent posts from Camper News.", - "User Story: As a user, I can click on a post to be taken to the story's original URL.", - "User Story: As a user, I can click a link to go directly to the post's discussion page.", - "Bonus User Story: As a user, I can see how many upvotes each story has.", - "Hint: Here's the Camper News Hot Stories API endpoint: http://www.freecodecamp.com/stories/hotStories.", - "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.

    Click here then add your link to your tweet's text" - ], - "challengeType": 3, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "", - "descriptionEs": [], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd7158d8c442eddfaeb5bd19", - "name": "Zipline: Wikipedia Viewer", - "dashedName": "zipline-wikipedia-viewer", - "difficulty": 1.05, - "challengeSeed": ["126415131"], - "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/MwgQea.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", - "Rule #2: You may use whichever libraries or APIs you need.", - "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", - "Here are the user stories you must enable, and optional bonus user stories:", - "User Story: As a user, I can search Wikipedia entries in a search box and see the resulting Wikipedia entries.", - "Bonus User Story:As a user, I can click a button to see a random Wikipedia entry.", - "Bonus User Story:As a user, when I type in the search box, I can see a dropdown menu with autocomplete options for matching Wikipedia entries.", - "Hint: Here's an entry on using Wikipedia's API: http://www.mediawiki.org/wiki/API:Main_page.", - "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.", + "Remember to use RSAP if you get stuck.", "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.

    Click here then add your link to your tweet's text" ], @@ -208,11 +142,11 @@ "id": "bd7158d8c442eddfaeb5bd0f", "name": "Zipline: Build a Pomodoro Clock", "dashedName": "zipline-build-a-pomodoro-clock", - "difficulty": 1.06, + "difficulty": 1.04, "challengeSeed": ["126411567"], "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/RPbGxZ/.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/RPbGxZ/.", + "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", "Rule #2: You may use whichever libraries or APIs you need.", "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", "Here are the user stories you must enable, and optional bonus user stories:", @@ -237,53 +171,24 @@ "descriptionPt": [] }, { - "id": "bd7158d8c442eddfaeb5bd17", - "name": "Zipline: Build a JavaScript Calculator", - "dashedName": "zipline-build-a-javascript-calculator", - "difficulty": 1.07, - "challengeSeed": ["126411565"], + "id": "bd7158d8c442eddfaeb5bd1f", + "name": "Zipline: Use the Twitch.tv JSON API", + "dashedName": "zipline-use-the-twitchtv-json-api", + "difficulty": 1.05, + "challengeSeed": ["126411564"], "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/zxgaqw.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", + "Objective: Build a CodePen.io app that successfully reverse-engineers this: http://codepen.io/GeoffStorbeck/full/GJKRxZ.", + "Rule #1: Don't look at the example project's code on CodePen. Figure it out for yourself.", "Rule #2: You may use whichever libraries or APIs you need.", "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", "Here are the user stories you must enable, and optional bonus user stories:", - "User Story: As a user, I can add, subtract, multiply and divide two numbers.", - "Bonus User Story: I can clear the input field with a clear button.", - "Bonus User Story: I can keep chaining mathematical operations together until I hit the clear button, and the calculator will tell me the correct output.", - "Remember to use RSAP if you get stuck.", - "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.

    Click here then add your link to your tweet's text" - ], - "challengeType": 3, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "", - "descriptionEs": [], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd7158d8c442eddfaeb5bd1c", - "name": "Zipline: Build a Tic Tac Toe Game", - "dashedName": "zipline-build-a-tic-tac-toe-game", - "difficulty": 1.08, - "challengeSeed": ["126415123"], - "description": [ - "Objective: Build a CodePen.io that successfully reverse-engineers this: http://codepen.io/alex-dixon/full/JogOpQ/.", - "Rule #1: Don't look at the example project's code. Figure it out for yourself.", - "Rule #2: You may use whichever libraries or APIs you need.", - "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", - "Here are the user stories you must enable, and optional bonus user stories:", - "User Story: As a user, I can play a game of Tic Tac Toe with the computer.", - "Bonus User Story: As a user, I can never actually win against the computer - at best I can tie.", - "Bonus User Story: As a user, my game will reset as soon as it's over so I can play again.", - "Bonus User Story: As a user, I can choose whether I want to play as X or O.", + "User Story: As a user, I can see whether Free Code Camp is currently streaming on Twitch.tv.", + "User Story: As a user, I can click the status output and be sent directly to the Free Code Camp's Twitch.tv channel.", + "User Story: As a user, if Free Code Camp is streaming, I can see additional details about what they are streaming.", + "Bonus User Story: As a user, I can search through the streams listed.", + "Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp.", + "Hint: The relevant documentation about this API call is here: https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel.", + "Hint: Here's an array of the Twitch.tv usernames of people who regularly stream coding: [\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]", "Remember to use RSAP if you get stuck.", "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.

    Click here then add your link to your tweet's text" diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index a52373ce99..a8b417283b 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -6,7 +6,7 @@ "id": "bad87fee1348bd9acde08812", "name": "Waypoint: Mobile Responsive Images", "dashedName": "waypoint-mobile-responsive-images", - "difficulty": 0.047, + "difficulty": 1, "description": [ "Now let's go back to our Cat Photo App. This time, we'll style it using the popular Bootstrap responsive CSS framework. First, add a new image with the src attribute of \"http://bit.ly/fcc-kittens2\", and add the \"img-responsive\" Bootstrap class to that image.", "It would be great if the image could be exactly the width of our phone's screen.", @@ -92,7 +92,7 @@ "id": "bad87fee1348bd8acde08812", "name": "Waypoint: Center Text with Bootstrap", "dashedName": "waypoint-center-text-with-bootstrap", - "difficulty": 0.048, + "difficulty": 2, "description": [ "Add Bootstrap's \"text-center\" class to your h2 element.", "Now that we're using Bootstrap, we can center our heading elements to make them look better. All we need to do is add the class text-center to our h1 and h2 elements.", @@ -174,7 +174,7 @@ "id": "bad87fee1348cd8acdf08812", "name": "Waypoint: Create a Bootstrap Button", "dashedName": "waypoint-create-a-bootstrap-button", - "difficulty": 0.049, + "difficulty": 3, "description": [ "Create a new button element below your large kitten photo. Give it the class \"btn\" and the text of \"like this photo\".", "Bootstrap has its own styles for button elements, which look much better than the plain HTML ones." @@ -257,7 +257,7 @@ "id": "bad87fee1348cd8acef08812", "name": "Waypoint: Create a Block Element Bootstrap Button", "dashedName": "waypoint-create-a-block-element-bootstrap-button", - "difficulty": 0.050, + "difficulty": 4, "description": [ "Add Bootstrap's \"btn-block\" class to your Bootstrap button.", "Normally, your button elements are only as wide as the text that they contain. By making them block elements, your button will stretch to fill your page's entire horizontal space.", @@ -344,7 +344,7 @@ "id": "bad87fee1348cd8acef08811", "name": "Waypoint: Taste the Bootstrap Button Color Rainbow", "dashedName": "waypoint-taste-the-bootstrap-button-color-rainbow", - "difficulty": 0.051, + "difficulty": 5, "description": [ "Add Bootstrap's \"btn-primary\" class to your button.", "The \"btn-primary\" class is the main color you'll use in your app. It is useful for highlighting actions you want your user to take.", @@ -429,7 +429,7 @@ "id": "bad87fee1348cd8acef08813", "name": "Waypoint: Call out Optional Actions with Button Info", "dashedName": "waypoint-call-out-optional-actions-with-button-info", - "difficulty": 0.052, + "difficulty": 6, "description": [ "Create a new block-level Bootstrap button below your \"Like\" button with the text \"Info\", and add Bootstrap's \"btn-info\" and \"btn-block\" classes to it.", "Bootstrap comes with several pre-defined colors for buttons. The \"btn-info\" class is used to call attention to optional actions that the user can take.", @@ -515,7 +515,7 @@ "id": "bad87fee1348ce8acef08814", "name": "Waypoint: Warn your Users of a Dangerous Action", "dashedName": "waypoint-warn-your-users-of-a-dangerous-action", - "difficulty": 0.053, + "difficulty": 7, "description": [ "Create a button with the text \"Delete\" and give it the class \"btn-danger\".", "Bootstrap comes with several pre-defined colors for buttons. The \"btn-danger\" class is the button color you'll use to notify users that the button performs a destructive action, such as deleting a cat photo.", @@ -602,7 +602,7 @@ "id": "bad88fee1348ce8acef08815", "name": "Waypoint: Use the Bootstrap Grid to Put Elements Side By Side", "dashedName": "waypoint-use-the-bootstrap-grid-to-put-elements-side-by-side", - "difficulty": 0.054, + "difficulty": 8, "description": [ "Put the \"Like\", \"Info\" and \"Delete\" buttons side-by-side by wrapping all three of them within one <div class=\"row\"> element, then each of them within a <div class=\"col-xs-4\"> element.", "Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", @@ -694,7 +694,7 @@ "id": "bad87fee1348bd9aedf08845", "name": "Waypoint: Ditch Custom CSS for Bootstrap", "dashedName": "waypoint-ditch-custom-css-for-bootstrap", - "difficulty": 0.055, + "difficulty": 9, "description": [ "Delete the \".red-text\", \"p\", and \".smaller-image\" CSS declarations from your style element so that the only declarations left in your style element are \"h2\" and \"thick-green-border\". Then Delete the p element that contains a dead link. Then remove the \"red-text\" class from your h2 element and replace it with the \"text-primary\" Bootstrap class. Finally, remove the \"smaller-image\" class from your first img element and replace it with the img-responsive class.", "We can clean up our code and make our Cat Photo App look more conventional by using Bootstrap's built-in styles instead of the custom styles we created earlier.", @@ -792,7 +792,7 @@ "id": "bad87fee1348bd9aede08845", "name": "Waypoint: Create a Custom Heading", "dashedName": "waypoint-create-a-custom-heading", - "difficulty": 0.056, + "difficulty": 11, "description": [ "Wrap your first image and your h2 element within a single <div class='row'> element. Wrap your h2 text within a <div class='col-xs-8'> and your image in a <div class='col-xs-4'> so that they are on the same line.", "We will make a simple heading for our Cat Photo App by putting them in the same row.", @@ -879,7 +879,7 @@ "id": "bad87fee1348bd9aedd08845", "name": "Waypoint: Add Font Awesome Icons to our Buttons", "dashedName": "waypoint-add-font-awesome-icons-to-our-buttons", - "difficulty": 0.057, + "difficulty": 12, "description": [ "Use Font Awesome to add a \"thumbs-up\" icon to your like button by giving it a i element with the classes \"fa\" and \"fa-thumbs-up\".", "Font Awesome is a convenient library of icons. These icons are vector graphics, stored in the \".svg\" file format. These icons are treated just like fonts. You can specify their size using pixels, and they will assume the font size of their parent HTML elements.", @@ -963,7 +963,7 @@ "id": "bad87fee1348bd9aedc08845", "name": "Waypoint: Add Font Awesome Icons to all of our Buttons", "dashedName": "waypoint-add-font-awesome-icons-to-all-of-our-Buttons", - "difficulty": 0.058, + "difficulty": 13, "description": [ "Use Font Awesome to add a \"info-circle\" icon to your info button and a \"trash\" icon to your delete button.", "Font Awesome is a convenient library of icons. These icons are vector graphics, stored in the \".svg\" file format. These icons are treated just like fonts. You can specify their size using pixels, and they will assume the font size of their parent HTML elements.", @@ -1047,7 +1047,7 @@ "id": "bad87fee1348bd9aedb08845", "name": "Waypoint: Responsively Style Radio Buttons", "dashedName": "waypoint-responsively-style-radio-buttons", - "difficulty": 0.059, + "difficulty": 14, "description": [ "Wrap all of your radio buttons within a <div class='row'> element. Then wrap each of them within a <div class='col-xs-6'> element.", "You can use Bootstrap's \"col-xs-*\" classes on form elements, too! This way, our radio buttons will be evenly spread out across the page, regardless of how wide the screen resolution is." @@ -1130,7 +1130,7 @@ "id": "bad87fee1348bd9aeda08845", "name": "Waypoint: Responsively Style Checkboxes", "dashedName": "waypoint-responsively-style-checkboxes", - "difficulty": 0.060, + "difficulty": 15, "description": [ "Wrap all your checkboxes in a <div class='row'> element. Then wrap each of them in a <div class='col-xs-4'> element.", "You can use Bootstrap's \"col-xs-*\" classes on form elements, too! This way, our checkboxes will be evenly spread out across the page, regardless of how wide the screen resolution is." @@ -1220,7 +1220,7 @@ "id": "bad87fee1348bd9aed908845", "name": "Waypoint: Style Text Inputs as Form Controls", "dashedName": "waypoint-style-text-inputs-as-form-controls", - "difficulty": 0.061, + "difficulty": 16, "description": [ "Give your form's text input field a class of \"form-control\". Give your form's submit button the classes \"btn btn-primary\". Also give this button the Font Awesome icon of \"fa-paper-plane\".", "You can add the \"fa-paper-plane\" Font Awesome icon by adding <i class=\"fa fa-paper-plane\"></i> within your submit button element." @@ -1319,7 +1319,7 @@ "id": "bad87fee1348bd9aec908845", "name": "Waypoint: Line up Form Elements Responsively with Bootstrap", "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", - "difficulty": 0.062, + "difficulty": 17, "description": [ "Now let's get your form input and your submission button on the same line. We'll do this the same way we have previously: by using a div element with the class \"row\", and other div elements within it using the \"col-xs-*\" class.", "Wrap both your form's text input and submit button within a div with the class \"row\". Wrap your form's text input within a div with the class of \"col-xs-7\". Wrap your form's submit button in a div with the class \"col-xs-5\".", @@ -1414,6 +1414,458 @@ "descriptionEs": [], "namePt": "", "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908846", + "name": "Waypoint: Create a Bootstrap Headline", + "dashedName": "waypoint-create-a-bootstrap-headline", + "difficulty": 18, + "description": [ + + ], + "tests": [ + "assert($('h3') && $('h3').length > 0, 'Add a h3 element to your page.')", + "assert(editor.match(/<\\/h3>/g) && editor.match(/

    /g).length === editor.match(/

    h3 element has a closing tag.')", + "assert($('h3').hasClass('text-primary'), 'Your h3 element should be colored by applying the class \"text-primary\"')", + "assert($('h3').hasClass('text-center'), 'Your h3 element should be centered by applying the class \"text-center\"')", + "assert.isTrue((/jquery(\\s)+playground/gi).test($('h3').text()), 'Your h3 element should have the text \"jQuery Playground\".')" + ], + "challengeSeed": [ + "", + "", + "" + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9bec908846", + "name": "Waypoint: Create a Bootstrap Row", + "dashedName": "waypoint-create-a-bootstrap-row", + "difficulty": 18.5, + "description": [ + ], + "tests": [ + "assert($('div').length > 0, 'Add a div element to your page.')", + "assert($('div').hasClass('row'), 'Your div element should have the class \"row\"')", + "assert(editor.match(/<\\/div>/g) && editor.match(/
    /g).length === editor.match(/
    div element has a closing tag.')" + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "", + "" + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908847", + "name": "Waypoint: Split your Bootstrap Row", + "dashedName": "waypoint-split-your-bootstrap-row", + "difficulty": 19, + "description": [ + ], + "tests": [ + "assert($('div.row').children('div.col-xs-6').length > 1, 'Wrap two div class=\"col-xs-6\" elements within your div class=\"row\" element.')", + "assert(editor.match(/<\\/div>/g) && editor.match(/
    /g).length === editor.match(/
    div elements have closing tags.')" + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908848", + "name": "Waypoint: Create Bootstrap Wells", + "dashedName": "waypoint-create-bootstrap-wells", + "difficulty": 20, + "description": [ + ], + "tests": [ + "assert($('div').length > 4, 'Add two div elements inside your div class=\"row\"> element both with the class \"col-xs-6\"')", + "assert($('div.row').children('div.col-xs-6').length > 1, 'Wrap both of your div class=\"col-xs-6\" elements within your div class=\"row\" element.')", + "assert($('div.col-xs-6').children('div.well').length > 1, 'Wrap both of your div class=\"col-xs-6\" elements within your div class=\"row\" element.')", + "assert(editor.match(/<\\/div>/g) && editor.match(/
    /g).length === editor.match(/
    div elements have closing tags.')" + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908849", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 21, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908850", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 22, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908852", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 23, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908853", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 24, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908854", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 25, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908855", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 26, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "

    #left-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "

    #right-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908856", + "name": "Waypoint: Build a jQuery Playground in Bootstrap", + "dashedName": "waypoint-line-up-form-elements-responsively-with-bootstrap", + "difficulty": 27, + "description": [ + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "

    #left-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "

    #right-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, + { + "id": "bad87fee1348bd9aec908857", + "name": "Waypoint: Use Comments to Clarify Code", + "dashedName": "waypoint-use-comments-to-clarify-code", + "difficulty": 28, + "description": [ + "add " + ], + "tests": [ + + ], + "challengeSeed": [ + "

    jQuery Playground

    ", + "
    ", + "
    ", + "

    #left-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    ", + "

    #right-well

    ", + "
    ", + " ", + " ", + " ", + "
    ", + "
    ", + "
    " + ], + "challengeType": 0, + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] } ] } diff --git a/seed/challenges/computer-science.json b/seed/challenges/computer-science.json deleted file mode 100644 index 7f2a11b91d..0000000000 --- a/seed/challenges/computer-science.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "name": "Computer Science", - "order": 0.005, - "challenges": [ - { - "id": "bd7123d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Basic Computer Science", - "dashedName": "waypoint-learn-basic-computer-science", - "difficulty": 0.90, - "challengeSeed": ["114628241"], - "description": [ - "Stanford has an excellent free online Computer Science curriculum. This interactive course uses a modified version of JavaScript. It will cover a lot of concepts quickly.", - "Note that Harvard also has an excellent introduction to computer science course called CS50, but it takes more than 100 hours to complete, and doesn't use JavaScript.", - "Despite being completely self-paced, Stanford's CS101 course is broken up into weeks. Each of the following challenges will address one of those weeks.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ and complete the first week's course work." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende Informática Básica", - "descriptionEs": [ - "Stanford tiene en internet un excelente currículo gratuito sobre Informática. Este curso interactivo utiliza una versión modificada de JavaScript. En él se cubrirán varios conceptos rápidamente.", - "Vale recalcar que Harvard también tiene un excelente curso de introducción a la informática llamado CS50, pero éste toma más de 100 horas para completar y no utiliza JavaScript.", - "A pesar de que puedes llevarlo a tu propio ritmo, el curso de Stanford CS101 está separado en semanas. Cada uno de los desafíos apuntará a cada una de esas semanas.", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z54/z1/ y completa la primera semana del curso." - ], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd8124d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Loops", - "dashedName": "waypoint-learn-loops", - "difficulty": 0.19, - "challengeSeed": ["114597348"], - "description": [ - "Now let's tackle week 2 of Stanford's Intro to Computer Science course.", - "This will introduce us to loops, a fundamental feature of every programming language.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ and complete Week 2." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende Loops", - "descriptionEs": [ - "Ahora apuntaremos a completar la semana 2 del curso de Introducción a Informática de Stanford.", - "Esto nos introducirá a los loops (bucles), una característica fundamental de todos los lenguajes de programación", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z100/a7a70ce6e4724c58862ee6007284face/ y completa la segunda semana del curso." - ], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd8125d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Computer Hardware", - "dashedName": "waypoint-learn-computer-hardware", - "difficulty": 0.20, - "challengeSeed": ["114597347"], - "description": [ - "Week 3 of Stanford's Intro to Computer Science covers computer hardware and explains Moore's law of exponential growth in the price-performance of processors.", - "This challenge will also give you an understanding of how bits and bytes work.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ and complete Week 3." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende sobre el Hardware de las Computadoras", - "descriptionEs": [ - "La semana 3 del curso de Introducción a Informática de Stanford cubrirá el hardware de las computadoras y explicará la ley de Moore sobre el crecimiento exponencial del precio-desempeño de los procesadores.", - "Este desafío también te dará un entendimiento más amplio sobre como funcionan los bits y bytes.", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z143/z101/ y completa la tercera semana del curso." - ], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd8126d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Computer Networking", - "dashedName": "waypoint-learn-computer-networking", - "difficulty": 0.21, - "challengeSeed": ["114604811"], - "description": [ - "Now that you've learned about computer hardware, it's time to learn about the software that runs on top of it.", - "Particularly important, you will learn about networks and TCP/IP - the protocol that powers the internet.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ and complete Week 4." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende sobre Redes de Computadoras", - "descriptionEs": [ - "Ahora que ya hemos aprendido sobre hardware, es hora de aprender sobre el software que corre encima de él.", - "Es importante recalcar que aprenderás sobre como funcionan las redes y TCP/IP - el protocolo que potencia el internet.", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z187/z144/ y completa la cuarta semana del curso." - ], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd8127d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Boolean Logic", - "dashedName": "waypoint-learn-boolean-logic", - "difficulty": 0.22, - "challengeSeed": ["114604812"], - "description": [ - "Now we'll do some more table exercises and learn boolean logic.", - "We'll also learn the difference between digital data and analog data.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ and complete Week 5." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende Lógica Booleana", - "descriptionEs": [ - "Ahora trabajaremos más ejercicios de tablas y aprenderemos lógica booleana.", - "También aprenderemos la diferencia entre datos digitales y datos análogos.", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z208/z188/ y completa la quinta semana del curso." - ], - "namePt": "", - "descriptionPt": [] - }, - { - "id": "bd8128d8c441eddfaeb5bdef", - "name": "Waypoint: Learn Computer Security", - "dashedName": "waypoint-learn-computer-security", - "difficulty": 0.23, - "challengeSeed": ["114604813"], - "description": [ - "We're almost done with Stanford's Introduction to Computer Science course!", - "We'll learn about one of the most important inventions of the 20th century - spreadsheets.", - "We'll also learn about Computer Security and some of the more common vulnerabilities software systems have.", - "Go to https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z229/z213/ and complete Week 6, the final week of the course." - ], - "challengeType": 2, - "tests": [], - "nameCn": "", - "descriptionCn": [], - "nameFr": "", - "descriptionFr": [], - "nameRu": "", - "descriptionRu": [], - "nameEs": "Waypoint: Aprende sobre Seguridad Informática", - "descriptionEs": [ - "¡Ya casi completamos el curso de Introducción a la Informática!", - "Aprenderemos sobre una de la más importante invención del siglo 20 - la hoja de cálculo.", - "También aprenderemos sobre la Seguridad Informática y sobre algunas de las vulnerabilidades más comúnes en los sistemas de software.", - "Ingresa a https://class.stanford.edu/courses/Engineering/CS101/Summer2014/courseware/z229/z213/ y completa la sexta y última semana del curso." - ], - "namePt": "", - "descriptionPt": [] - } - ] -} diff --git a/seed/challenges/functional-programming.json b/seed/challenges/functional-programming.json index a7a60efdf9..467be534e9 100644 --- a/seed/challenges/functional-programming.json +++ b/seed/challenges/functional-programming.json @@ -10,9 +10,10 @@ "challengeSeed": ["129169463"], "description": [ "Functional programming holds the key to unlocking JavaScript's powerful asynchronous features.", - "Jafar Husain's 42-step interactive Functional Programming course will familiarize you with the various ways you can recombine these functions.", + "Jafar Husain's interactive Functional Programming course will familiarize you with the various ways you can recombine these functions.", "Functional programming in JavaScript involves using five key functions: \"map\", \"reduce\", \"filter\", \"concatAll\", and \"zip\".", "Click here to go to the challenge: http://jhusain.github.io/learnrx/.", + "You only need to complete the first 27 steps of this tutorial.", "This challenge will take several hours, but don't worry. Jafar's website will save your progress (using your browser's local storage) so you don't need to finish it in one sitting.", "If you've spent several minutes on one of these challenges, and still can't figure out its correct answer, you can click \"show answer\", then click \"run\" to advance to the next challenge. Be sure to read the correct answer and make sure you understand it before moving on." ], diff --git a/seed/challenges/future-jquery-ajax-json.json b/seed/challenges/future-jquery-ajax-json.json deleted file mode 100644 index 435381f0b8..0000000000 --- a/seed/challenges/future-jquery-ajax-json.json +++ /dev/null @@ -1,598 +0,0 @@ -{ - "name": "jQuery, Ajax and JSON", - "dashedName": "jQuery, Ajax and JSON", - "order": 0.004, - "challenges": [{ - "id": "bad87fee1348bd9acdd08826", - "name": "Waypoint: Learn how Script Tags and Document Ready Work", - "dashedName": "Waypoint: Learn how Script Tags and Document Ready Work", - "difficulty": 0.072, - "description": [ - "We've simplified our Cat Photo App and removed our style element. Add a script element to your page and create a $(document).ready function within it.", - "Add $(document).ready(function() { to your script element, and then close it on the following line with });." - ], - "tests": [ - "assert(editor.match(/", + "
    ", + "
    ", + "

    Coming from _______, and making $_______, your true costs will be:

    ", + "
    ", + "
    ", + "

    Where do you live?

    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "

    How much money did you make last year (in USD)?

    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + "
    View Data Source JSON  •  Recalculate", + "
    ", + "

    Notes:

    ", + "
      ", + "
    1. For cash-up-front bootcamps, we assumed an APR of 6% and a term of 3 years.
    2. ", + "
    3. For wage-garnishing bootcamps, we assume 18% of first year wages at their advertised starting annual salary of around $100,000.
    4. ", + "
    5. We assume a cost of living of $500 for cities like San Francisco and New York City, and $400 per week for everywhere else.
    6. ", + "
    7. 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 here.
    8. ", + "
    9. 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.
    10. ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "

    Built by Suzanne Atkinson

    ", + "

    Suzanne is an emergency medicine physician, triathlon coach and web developer from Pittsburgh. You should  follow her on Twitter.

    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    " + ] + }, + { + "id": "bd7158d9c442eddfaeb5bdef", + "name": "How do I best use the Global Control Shortcuts for Mac?", + "dashedName": "how-do-i-best-use-the-global-control-shortcuts-for-mac", + "category": "FYI", + "description": [ + "
    ", + "

    These Global Control Shortcuts for Mac will save you hours by speeding up your typing.


    ", + "
    ", + "

    These global shortcuts work everywhere on a Mac:

    ", + "

    ", + "

      ", + "
    • Control + F = Forward
    • ", + "
    • Control + B = Backward
    • ", + "
    • Control + N = Next Line
    • ", + "
    • Control + P = Previous Line
    • ", + "
    • Control + H = Backspace
    • ", + "
    • Control + D = Delete
    • ", + "
    • Control + A = Beginning of Line
    • ", + "
    • Control + E = End of Line
    • ", + "
    • Control + K = Kill line
    • ", + "
    ", + "

    ", + "
    " + ] + }, + { + "id": "bd7158d9c445eddfaeb5bdef", + "name": "How can I get to Inbox Zero with Gmail shortcuts?", + "dashedName": "how-can-i-get-to-inbox-zero-with-gmail-shortcuts", + "category": "FYI", + "description": [ + "
    ", + "

    These Gmail Shortcuts will save you hours and help you get to Inbox Zero.


    ", + "
    ", + " ", + "
    ", + "

    The shortcuts:

    ", + "

    ", + "

      ", + "
    • j - move down
    • ", + "
    • k - move up
    • ", + "
    • o - open
    • ", + "
    • r - reply
    • ", + "
    • a - reply all
    • ", + "
    • f - forward
    • ", + "
    • c - compose
    • ", + "
    • x - select
    • ", + "
    • e - archive
    • ", + "
    • ! - mark spam
    • ", + "
    • z - undo
    • ", + "
    ", + "

    ", + "
    " + ] + }, + { + "id": "bd7158d9c445eddfaeb5bdff", + "name": "How can I deploy a website without writing any code at all?", + "dashedName": "how-can-i-deploy-a-website-without-writing-any-code-at-all", + "category": "FYI", + "description": [ + "
    ", + "

    It's possible to build dynamic, mobile-responsive websites without writing any code at all, in just a few minutes.


    ", + "
    ", + " ", + "
    ", + "

    Here are the technologies we used here:

    ", + "

    ", + "

      ", + "
    • atom.io - a free code editor
    • ", + "
    • startbootstrap.com - a collection of free responsive (Bootstrap) templates
    • ", + "
    • powr.io - a collection of free JavaScript plugins
    • ", + "
    • bitballoon.com - a tool for drag and drop website deployment
    • ", + "
    ", + "

    ", + "

    You will quickly reach the limits of what you can do without actually coding, but it's nice to be able to rapidly build working prototype websites like this.

    ", + "
    " + ] + }, + { + "id": "bd7158d9c447eddfaeb5bdef", + "name": "How do I install Screenhero?", + "dashedName": "how-do-i-install-screenhero", + "category": "FYI", + "description": [ + "
    ", + "

    Download for Mac

    ", + "

    Download for Windows

    ", + "

    You'll use Screenhero to pair program starting with http://freecodecamp.com/challenges/pair-program-on-bonfires

    ", + "
    " + ] + }, + { + "id": "bd7158d9c436eddfaeb5dd3b", + "name": "What other resources does Free Code Camp recommend to nonprofits?", + "dashedName": "what-other-resources-does-free-code-camp-recommend-to-nonprofits", + "category": "FYI", + "description": [ + "
    ", + "

    Here are some excellent resources for nonprofits.

    ", + "

    Please note that Free Code Camp is not partnered with, nor do we receive a referral fee from, any of the following providers. We simply want to help guide you towards a solution for your organization.

    ", + "

    Skills-based Volunteer Organizations:

    ", + "

    http://www.volunteermatch.com

    ", + "

    http://www.catchafire.org

    ", + "

    Building a website:

    ", + "

    http://www.wix.com/

    ", + "

    https://wordpress.com/

    ", + "

    Build it yourself for free with no code

    ", + "

    Donor and Volunteer Management Systems

    ", + "

    https://www.thedatabank.com/

    ", + "

    http://www.donorsnap.com/

    ", + "

    http://www.donorperfect.com/

    ", + "

    https://www.blackbaud.com/fundraising-crm/etapestry-donor-management

    ", + "

    http://www.z2systems.com

    ", + "

    http://www.regpacks.com/volunteer-management

    ", + "

    http://sumac.com

    ", + "

    http://www.volgistics.com

    ", + "

    Inventory Management Systems

    ", + "

    https://www.ezofficeinventory.com/industries/non-profits

    ", + "

    https://www.ordoro.com

    ", + "

    http://www.unleashedsoftware.com

    ", + "

    E-Learning platforms

    ", + "

    http://www.dokeos.com

    ", + "

    http://www.efrontlearning.net/

    ", + "

    https://moodle.org/

    ", + "

    https://sakaiproject.org/

    ", + "

    Community Management

    ", + "

    https://civicrm.org/

    ", + "

    http://tcmgr.com/

    ", + "

    Electronic Forms

    ", + "

    http://www.google.com/forms

    ", + "

    http://www.typeform.com

    ", + "
    " + ] + } +] diff --git a/seed/field-guides/contact.json b/seed/field-guides/contact.json new file mode 100644 index 0000000000..16bbd732c6 --- /dev/null +++ b/seed/field-guides/contact.json @@ -0,0 +1,22 @@ +[ + { + "id": "bd7158d9c436eddfaeb5bd3c", + "name": "How can I reach the Free Code Camp team to interview them for my publication?", + "dashedName": "how-can-i-reach-the-free-code-camp-team-to-interview-them-for-my-publication", + "category": "contact", + "description": [ + "
    ", + "

    We're happy to do a quick interview for your publication or show. Here's whom you should contact about what, and how to best reach them:

    ", + "

    ", + "

      ", + "
    1. Want to talk to about Free Code Camp's curriculum or long-term vision? Reach out to Quincy Larson. He's @ossia on Twitter and @quincylarson on Gitter.
    2. ", + "
    3. Want to talk about Free Code Camp's open source codebase, infrastructure, or JavaScript in general? Talk to Nathan Leniz. He's @terakilobyte on Twitter and @terakilobyte on Gitter.
    4. ", + "
    5. Want to explore our efforts to empower nonprofits with code? Michael D. Johnson eats, sleeps and breathes that. He's @figitalboy on Twitter and @codenonprofit on Gitter.
    6. ", + "
    7. Want to get a camper's perspective on our community? Talk with Bianca Mihai (@biancamihai on Gitter and @bubuslubu on Twitter) or Suzanne Atkinson (@adventurebear on Gitter and @steelcitycoach on Twitter).", + "
    ", + "

    ", + "

    We strive to be helpful and transparent in everything we do. We'll do what we can to help you share our community with your audience.

    ", + "
    " + ] + } +] diff --git a/seed/field-guides.json b/seed/field-guides/orientation.json similarity index 72% rename from seed/field-guides.json rename to seed/field-guides/orientation.json index 17d3748856..29f6fadfe1 100644 --- a/seed/field-guides.json +++ b/seed/field-guides/orientation.json @@ -3,6 +3,7 @@ "id": "bd7158d9c441eddfaeb5bdef", "name": "How do I use this guide?", "dashedName": "how-do-i-use-this-guide", + "category": "orientation", "description": [ "
    ", "

    This guide strives to provide clear answers to common questions about Free Code Camp, learning to code, and getting a coding job.

    ", @@ -15,6 +16,7 @@ "id": "bd7158d9c441eddfaeb5bdff", "name": "What exactly is Free Code Camp?", "dashedName": "what-exactly-is-free-code-camp", + "category": "orientation", "description": [ "
    ", "

    We're a community of busy people who learn to code by building projects for nonprofits.

    ", @@ -33,6 +35,7 @@ "id": "bd7158d9c441eddfaeb5bd1f", "name": "Why do I need Free Code Camp?", "dashedName": "why-do-i-need-free-code-camp", + "category": "orientation", "description": [ "
    ", "

    Learning to code is hard.

    ", @@ -51,6 +54,7 @@ "id": "bd7158d9c441eddfaeb5bd2f", "name": "What are the main advantages of Free Code Camp?", "dashedName": "what-are-the-main-advantages-of-free-code-camp", + "category": "orientation", "description": [ "
    ", "

    Our main advantage is that we're accessible to busy adults who want to change careers. Specifically, we're:

    ", @@ -68,6 +72,7 @@ "id": "bd7158d9c441eddfaeb5bd3f", "name": "How does Free Code Camp work?", "dashedName": "how-does-free-code-camp-work", + "category": "orientation", "description": [ "
    ", "

    Our free, self-paced, browser-based program takes about 1,600 hours to complete.", @@ -90,6 +95,7 @@ "id": "bd7158d9c441eddfaeb5bd4f", "name": "Will I really be able to get software engineering job after Free Code Camp?", "dashedName": "will-i-really-be-able-to-get-a-software-engineering-job-after-free-code-camp", + "category": "orientation", "description": [ "
    ", "

    If you complete this program, you will be able to get a coding job. Many of our campers have already gotten coding jobs.

    ", @@ -110,6 +116,7 @@ "id": "bd7158d9c440eddfaeb5bdef", "name": "What will I learn, and in what sequence?", "dashedName": "what-will-i-learn-and-in-what-sequence", + "category": "orientation", "description": [ "
    ", "

    First, you'll learn basic web design tools like:

    ", @@ -147,6 +154,7 @@ "id": "bd7158d9c434eddfaeb5bdef", "name": "How long does Free Code Camp take?", "dashedName": "how-long-does-free-code-camp-take", + "category": "orientation", "description": [ "
    ", "

    It takes about 1,600 hours of coding to develop the skills you'll need to get an entry level software engineering job.

    ", @@ -185,6 +193,7 @@ "id": "bd7158d9c438eddfaeb5bdef", "name": "Why does Free Code Camp use JavaScript instead of Ruby or Python?", "dashedName": "why-does-free-code-camp-use-javascript-instead-of-ruby-or-python", + "category": "orientation", "description": [ "
    ", "

    Like JavaScript, Ruby and Python are high-level scripting languages that can be used for full stack web development.

    ", @@ -199,6 +208,7 @@ "id": "bd7158d9c437eddfaeb5bdef", "name": "What is pair programming, and why is it special?", "dashedName": "what-is-pair-programming-and-why-is-it-special", + "category": "orientation", "description": [ "
    ", "

    Pair programming is where two people code together on one computer.

    ", @@ -212,6 +222,7 @@ "id": "bd7158d9c436eddfaeb5bd2f", "name": "How do I get help when I get stuck?", "dashedName": "how-do-i-get-help-when-i-get-stuck", + "category": "orientation", "description": [ "
    ", "

    When you get stuck, remember: RSAP.

    ", @@ -235,6 +246,7 @@ "id": "bd7158d9c435eddfaeb5bdcf", "name": "What are the Official Free Code Camp Chat Rooms?", "dashedName": "what-are-the-official-free-code-camp-chat-rooms", + "category": "orientation", "description": [ "
    ", "

    We have 4 official chat rooms:

    ", @@ -260,22 +272,11 @@ "
    " ] }, - { - "id": "bd7158d9c436eddfaeb5bd3f", - "name": "Can I jump around in this guide?", - "dashedName": "can-i-jump-around-in-this-guide", - "description": [ - "
    ", - "

    This guide was designed as a reference for you. You shouldn't try to read it all today.

    ", - "

    Feel free to come back any time and jump around, reading any articles that seem interesting to you at the time.

    ", - "

    If you're currently doing our \"Browse our Field Guide\" Waypoint, go ahead and mark that challenge complete and move on to your next Waypoint.

    ", - "
    " - ] - }, { "id": "bd7158d9c436eddfaeb5bdef", "name": "If Free Code Camp is free, how does it make money?", "dashedName": "if-free-code-camp-is-free-how-does-it-make-money", + "category": "orientation", "description": [ "
    ", "

    We are completely free for both students and for nonprofits.

    ", @@ -285,168 +286,11 @@ "
    " ] }, - { - "id": "bd7159d9c436eddfaeb5bdef", - "name": "Can I Calculate the True Cost of a Bootcamp with a Coding Bootcamp Cost Calculator?", - "dashedName": "can-i-calculate-the-true-cost-of-a-bootcamp-with-a-coding-bootcamp-cost-calculator", - "description": [ - "", - "
    ", - "
    ", - "

    Coding Bootcamp Cost Calculator

    ", - "

    Coming from _______, and making $_______, your true costs will be:

    ", - "
    ", - "
    ", - "

    Where do you live?

    ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "

    How much money did you make last year (in USD)?

    ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - " ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - " ", - "
    View Data Source JSON  •  Recalculate", - "
    ", - "

    Notes:

    ", - "
      ", - "
    1. For cash-up-front bootcamps, we assumed an APR of 6% and a term of 3 years.
    2. ", - "
    3. For wage-garnishing bootcamps, we assume 18% of first year wages at their advertised starting annual salary of around $100,000.
    4. ", - "
    5. We assume a cost of living of $500 for cities like San Francisco and New York City, and $400 per week for everywhere else.
    6. ", - "
    7. 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 here.
    8. ", - "
    9. 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.
    10. ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "

    Built by Suzanne Atkinson

    ", - "

    Suzanne is an emergency medicine physician, triathlon coach and web developer from Pittsburgh. You should  follow her on Twitter.

    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    " - ] - }, { "id": "bd7158d9c435eddfaeb5bdef", "name": "Does Free Code Camp have an application process?", "dashedName": "does-free-code-camp-have-an-application-process", + "category": "orientation", "description": [ "
    ", "

    Unlike coding bootcamps, anyone can study at Free Code Camp.

    ", @@ -455,31 +299,11 @@ "
    " ] }, - { - "id": "bd7158d9c436eddfaeb5bd3b", - "name": "How can I stream my live coding sessions on Free Code Camp's Twitch.tv channel?", - "dashedName": "how-can-i-stream-my-live-coding-sessions-on-free-code-camps-twitch-tv-channel", - "description": [ - "
    ", - "

    If you're interested in coding JavaScript live in front of dozens of people on our popular twitch.tv channel, we'd love to have you.

    ", - "

    Please follow these steps to get started:

    ", - "

    ", - "

      ", - "
    1. Follow this tutorial to set up your computer for streaming.
    2. ", - "
    3. Contact Jason Ruekert - he's @jsonify in Gitter. He's in charge of our Twitch.tv channel. Tell him what you'd like to stream, and when you're available to stream.
    4. ", - "
    5. Jason will pair with you using Screenhero to verify your computer is configured properly to stream.
    6. ", - "
    ", - "

    ", - "

    Be respectful of your audience. Everything you stream should be related to coding JavaScript, and should be acceptable for children. (Yes, children do sometimes watch our Twitch stream to learn to code).

    ", - "

    While you're streaming, keep the chat room open so you can respond to questions from your viewers. If someone follows Free Code Camp on Twitch, try to thank them.

    ", - "

    If you do a good job, we'll invite you back to stream some more. Who knows, you might become one of our regular streamers!

    ", - "
    " - ] - }, { "id": "bd7158d9c436eddfaeb5bd3d", "name": "How can I find other Free Code Camp campers in my city?", "dashedName": "how-can-i-find-other-free-code-camp-campers-in-my-city", + "category": "orientation", "description": [ "
    ", "

    Find your city below and join their Facebook group and Gitter Chat. This is a great way to hang out with other coders, share insights, and pair program.

    ", @@ -742,174 +566,11 @@ "
    " ] }, - { - "id": "bd7158d9c442eddfaeb5bdef", - "name": "How do I best use the Global Control Shortcuts for Mac?", - "dashedName": "how-do-i-best-use-the-global-control-shortcuts-for-mac", - "description": [ - "
    ", - "

    These Global Control Shortcuts for Mac will save you hours by speeding up your typing.


    ", - "
    ", - "

    These global shortcuts work everywhere on a Mac:

    ", - "

    ", - "

      ", - "
    • Control + F = Forward
    • ", - "
    • Control + B = Backward
    • ", - "
    • Control + N = Next Line
    • ", - "
    • Control + P = Previous Line
    • ", - "
    • Control + H = Backspace
    • ", - "
    • Control + D = Delete
    • ", - "
    • Control + A = Beginning of Line
    • ", - "
    • Control + E = End of Line
    • ", - "
    • Control + K = Kill line
    • ", - "
    ", - "

    ", - "
    " - ] - }, - { - "id": "bd7158d9c445eddfaeb5bdef", - "name": "How can I get to Inbox Zero with Gmail shortcuts?", - "dashedName": "how-can-i-get-to-inbox-zero-with-gmail-shortcuts", - "description": [ - "
    ", - "

    These Gmail Shortcuts will save you hours and help you get to Inbox Zero.


    ", - "
    ", - " ", - "
    ", - "

    The shortcuts:

    ", - "

    ", - "

      ", - "
    • j - move down
    • ", - "
    • k - move up
    • ", - "
    • o - open
    • ", - "
    • r - reply
    • ", - "
    • a - reply all
    • ", - "
    • f - forward
    • ", - "
    • c - compose
    • ", - "
    • x - select
    • ", - "
    • e - archive
    • ", - "
    • ! - mark spam
    • ", - "
    • z - undo
    • ", - "
    ", - "

    ", - "
    " - ] - }, - { - "id": "bd7158d9c445eddfaeb5bdff", - "name": "How can I deploy a website without writing any code at all?", - "dashedName": "how-can-i-deploy-a-website-without-writing-any-code-at-all", - "description": [ - "
    ", - "

    It's possible to build dynamic, mobile-responsive websites without writing any code at all, in just a few minutes.


    ", - "
    ", - " ", - "
    ", - "

    Here are the technologies we used here:

    ", - "

    ", - "

      ", - "
    • atom.io - a free code editor
    • ", - "
    • startbootstrap.com - a collection of free responsive (Bootstrap) templates
    • ", - "
    • powr.io - a collection of free JavaScript plugins
    • ", - "
    • bitballoon.com - a tool for drag and drop website deployment
    • ", - "
    ", - "

    ", - "

    You will quickly reach the limits of what you can do without actually coding, but it's nice to be able to rapidly build working prototype websites like this.

    ", - "
    " - ] - }, - { - "id": "bd7158d9c446eddfaeb5bdef", - "name": "How do Free Code Camp's Nonprofit Projects work?", - "dashedName": "how-do-free-code-camps-nonprofit-projects-work", - "description": [ - "
    ", - "

    Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp Waypoints, Bonfires, Ziplines and Basejumps, you'll begin this process.

    ", - "

    Starting with the end in mind

    ", - "

    Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction).

    ", - "

    You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects.

    ", - "

    Choosing your first Nonprofit Project

    ", - "

    We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates.

    ", - "

    Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week. If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks.

    ", - "

    Our Nonprofit Project team will match you and your pair based on:

    ", - "

    ", - "

      ", - "
    1. Your estimated time commitment (10, 20 or 40 hours per week)
    2. ", - "
    3. Your time zone
    4. ", - "
    5. The nonprofit projects you've chosen
    6. ", - "
    7. Prior coding experience (we'd like both campers to be able to contribute equally)
    8. ", - "
    ", - "

    ", - "

    We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace.

    ", - "

    You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project you've expressed interest in. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one.

    ", - "

    Finalizing the Project

    ", - "

    Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process:

    ", - "

    ", - "

      ", - "
    1. We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above.
    2. ", - "
    3. We'll call the stakeholder to confirm once again that he or she agrees with our  terms  and has signed our  Nonprofit Project Stakeholder Pledge.
    4. ", - "
    5. We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder.
    6. ", - "
    7. If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project.
    8. ", - "
    ", - "

    ", - "

    This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time.

    ", - "

    Nonprofit Stakeholders

    ", - "

    Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a \"stakeholder\" - an authorative person who understands the organization and its needs for this particular project.

    ", - "

    Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs.

    ", - "

    When you and your pair first speak with your nonprofit stakeholder, you'll:

    ", - "

    ", - "

      ", - "
    • talk at length to better understand their needs.
    • ", - "
    • create a new Trello board and use it to prioritize what needs to be built.
    • ", - "
    • and establish deadlines based on your weekly time commitment, and how long you think each task will take.
    • ", - "
    ", - "

    ", - "

    It's notoriously difficult to estimate how long building software projects will take, so feel free to ask our volunteer team for help.

    ", - "

    You'll continue to meet with your stakeholder at least twice a month in your project's Gitter or Slack channel.

    ", - "

    You should also ask questions in your project's Gitter or Slack channel as they come up throughout the week, and your stakeholder can answer them asynchronously.

    ", - "

    Getting \"blocked\" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter.

    ", - "

    Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!

    ", - "

    Working with your Pair

    ", - "

    You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time.

    ", - "

    Here are our recommended ways of collaborating:

    ", - "

    ", - "

      ", - "
    • • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
    • ", - "
    • • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them.
    • ", - "
    • • Screenhero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming.
    • ", - "
    • • Write clear and readable code, commit messages, branch names, and pull request messages.
    • ", - "
    ", - "

    ", - "

    Hosting Apps

    ", - "

    Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away.

    ", - "

    If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them.

    ", - "

    Maintaining Apps

    ", - "

    Once you complete a nonprofit project, your obligation to its stakeholder is finished. Your goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical \"super user\").

    ", - "

    While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers.

    ", - "

    Pledging to finish the project

    ", - "

    Your nonprofit stakeholder, your pair, and our volunteer team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one.

    ", - "

    To confirm that you understand the seriousness of this commitment, we require that all campers  sign this pledge  before starting on their nonprofit projects.

    ", - "

    There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, \"Real artists ship.\" And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career.

    ", - "
    " - ] - }, - { - "id": "bd7158d9c447eddfaeb5bdef", - "name": "How do I install Screenhero?", - "dashedName": "how-do-i-install-screenhero", - "description": [ - "
    ", - "

    Download for Mac

    ", - "

    Download for Windows

    ", - "

    You'll use Screenhero to pair program starting with http://freecodecamp.com/challenges/pair-program-on-bonfires

    ", - "
    " - ] - }, { "id": "bd7158d9c451eddfaeb5bded", "name": "What is the style guide for Bonfires?", "dashedName": "what-is-the-style-guide-for-bonfires", + "category": "orientation", "description": [ "
    ", "

    Writing Bonfire challenges is a great way to exercise your own problem solving and testing abilities. Follow this process closely to maximize the chances of us accepting your bonfire.

    ", @@ -946,6 +607,7 @@ "id": "bd7158d9c451eddfaeb5bdee", "name": "What is the Free Code Camp Code of Conduct?", "dashedName": "what-is-the-free-code-camp-code-of-conduct", + "category": "orientation", "description": [ "
    ", "

    Free Code Camp is friendly place to learn to code. We're committed to keeping it that way.

    ", @@ -971,6 +633,7 @@ "id": "bd7158d9c451eddfaeb5bdef", "name": "What is the Free Code Camp Privacy Policy?", "dashedName": "what-is-the-free-code-camp-privacy-policy", + "category": "orientation", "description": [ "
    ", "

    Free Code Camp is committed to respecting the privacy of visitors to our websites and web applications. The guidelines below explain how we protect the privacy of visitors to FreeCodeCamp.com and its features.

    ", @@ -1009,111 +672,11 @@ "
    " ] }, - { - "id": "bd7158d9c436eddfaeb5bd3c", - "name": "How can I reach the Free Code Camp team to interview them for my publication?", - "dashedName": "how-can-i-reach-the-free-code-camp-team-to-interview-them-for-my-publication", - "description": [ - "
    ", - "

    We're happy to do a quick interview for your publication or show. Here's whom you should contact about what, and how to best reach them:

    ", - "

    ", - "

      ", - "
    1. Want to talk to about Free Code Camp's curriculum or long-term vision? Reach out to Quincy Larson. He's @ossia on Twitter and @quincylarson on Gitter.
    2. ", - "
    3. Want to talk about Free Code Camp's open source codebase, infrastructure, or JavaScript in general? Talk to Nathan Leniz. He's @terakilobyte on Twitter and @terakilobyte on Gitter.
    4. ", - "
    5. Want to explore our efforts to empower nonprofits with code? Michael D. Johnson eats, sleeps and breathes that. He's @figitalboy on Twitter and @codenonprofit on Gitter.
    6. ", - "
    7. Want to get a camper's perspective on our community? Talk with Bianca Mihai (@biancamihai on Gitter and @bubuslubu on Twitter) or Suzanne Atkinson (@adventurebear on Gitter and @steelcitycoach on Twitter).", - "
    ", - "

    ", - "

    We strive to be helpful and transparent in everything we do. We'll do what we can to help you share our community with your audience.

    ", - "
    " - ] - }, - { - "id": "bd7158d9c436eddfaeb5dd3b", - "name": "What other resources does Free Code Camp recommend to nonprofits?", - "dashedName": "what-other-resources-does-free-code-camp-recommend-to-nonprofits", - "description": [ - "
    ", - "

    Here are some excellent resources for nonprofits.

    ", - "

    Please note that Free Code Camp is not partnered with, nor do we receive a referral fee from, any of the following providers. We simply want to help guide you towards a solution for your organization.

    ", - "

    Skills-based Volunteer Organizations:

    ", - "

    http://www.volunteermatch.com

    ", - "

    http://www.catchafire.org

    ", - "

    Building a website:

    ", - "

    http://www.wix.com/

    ", - "

    https://wordpress.com/

    ", - "

    Build it yourself for free with no code

    ", - "

    Donor and Volunteer Management Systems

    ", - "

    https://www.thedatabank.com/

    ", - "

    http://www.donorsnap.com/

    ", - "

    http://www.donorperfect.com/

    ", - "

    https://www.blackbaud.com/fundraising-crm/etapestry-donor-management

    ", - "

    http://www.z2systems.com

    ", - "

    http://www.regpacks.com/volunteer-management

    ", - "

    http://sumac.com

    ", - "

    http://www.volgistics.com

    ", - "

    Inventory Management Systems

    ", - "

    https://www.ezofficeinventory.com/industries/non-profits

    ", - "

    https://www.ordoro.com

    ", - "

    http://www.unleashedsoftware.com

    ", - "

    E-Learning platforms

    ", - "

    http://www.dokeos.com

    ", - "

    http://www.efrontlearning.net/

    ", - "

    https://moodle.org/

    ", - "

    https://sakaiproject.org/

    ", - "

    Community Management

    ", - "

    https://civicrm.org/

    ", - "

    http://tcmgr.com/

    ", - "

    Electronic Forms

    ", - "

    http://www.google.com/forms

    ", - "

    http://www.typeform.com

    ", - "
    " - ] - }, - { - "id": "bd7158d9c436eddfadb5bd3e", - "name": "How can I contribute to this guide?", - "dashedName": "how-can-i-contribute-to-this-guide", - "description": [ - "
    ", - "

    Contributing to our field guide is a great way to establish your history on GitHub, add to your portfolio, and help other campers. If you have a question about JavaScript or programming in general that you'd like us to add to the field guide, here are two ways to get it into the guide:

    ", - "

    ", - "

      ", - "
    1. You can message @danraley on Gitter with your question.
    2. ", - "
    3. You can also contribute to this field guide directly via GitHub pull request, by cloning Free Code Camp's main repository and modifying field-guides.json.
    4. ", - "
    ", - "

    ", - "
    " - ] - }, - { - "id": "bd7158d9c436eddfadb5bd32", - "name": "How can I help the Free Code Camp translation effort?", - "dashedName": "how-can-i-help-the-free-code-camp-translation-effort", - "description": [ - "
    ", - "

    Our translation effort is driven by bilingual campers like you.

    ", - "

    If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

    ", - "
    " - ] - }, - { - "id": "bd7158d9c436eddfadb5bd31", - "name": "What if I speak a language that Free Code Camp does not yet support?", - "dashedName": "what-if-i-speak-a-language-that-free-code-camp-does-not-yet-support", - "description": [ - "
    ", - "

    Translation is an all-or-nothing proposal.

    ", - "

    We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that language.

    ", - "

    In addition to translating these initially, we'll also need to maintain the translation as the challenges are gradually updated.

    ", - "

    If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

    ", - "" - ] - }, { "id": "bd7158d9c436eddfadb5bd30", "name": "Can I do Free Code Camp completely in my native language?", "dashedName": "can-i-do-free-code-camp-completely-in-my-native-language", + "category": "orientation", "description": [ "
    ", "

    The last 800 hours of free code camp involve building projects for nonprofits. These nonprofit projects will involve lots of meetings, correspondence, and pair programming, all of which will be conducted in English.

    ", @@ -1127,6 +690,7 @@ "id": "bd7158d9c436eddfadb5bd3c", "name": "What is the new Free Code Camp Mobile Experience?", "dashedName": "what-is-the-new-free-code-camp-mobile-experience", + "category": "orientation", "description": [ "
    ", "

    We're building an on-the-go version of Free Code Camp.

    ", @@ -1144,6 +708,7 @@ "id": "bd7158d9c436eddfadb5bd3b", "name": "What is the Free Code Camp Front End Development Certificate?", "dashedName": "what-is-the-free-code-camp-front-end-development-certificate", + "category": "orientation", "description": [ "
    ", "

    We're creating a free Front End Development Certificate.

    ", @@ -1162,5 +727,88 @@ "

    All campers who have already completed these challenges are retroactively eligible for the certificate!

    ", "
    " ] + }, + { + "id": "bd7156d9c436eddfadb5bd3b", + "name": "How can I watch Live Coding on the Free Code Camp Twitch.tv Channel?", + "dashedName": "how-can-i-watch-live-coding-on-the-free-code-camp-twitch-tv-channel", + "description": [ + "
    ", + "
    ", + "

    Watch the live stream below or on our  Twitch.tv channel.

    ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "

    Check out our scheduled shows. You can add them to your calendar.

    ", + "
    ", + " ", + "
    ", + "
    ", + "

    ", + "
    ", + "
    ", + "

    Here are some of our previous shows (you can full-screen them):

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/Z_43xApGB9Y

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/PvWHzcebjjQ

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/yHL6mEr-LGY

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/dolG-yRMcPs

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/YMz_vrK_KlQ

    ", + "
    ", + "
    ", + "
    ", + " ", + "
    ", + "

    link:  http://www.youtube.com/watch/bbFVxaza8Ik

    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    " + ] } ] diff --git a/seed/field-guides/outreach.json b/seed/field-guides/outreach.json new file mode 100644 index 0000000000..154c93d44c --- /dev/null +++ b/seed/field-guides/outreach.json @@ -0,0 +1,143 @@ +[ + { + "id": "bd7158d9c436eddfaeb5bd3b", + "name": "How can I stream my live coding sessions on Free Code Camp's Twitch.tv channel?", + "dashedName": "how-can-i-stream-my-live-coding-sessions-on-free-code-camps-twitch-tv-channel", + "category": "outreach", + "description": [ + "
    ", + "

    If you're interested in coding JavaScript live in front of dozens of people on our popular twitch.tv channel, we'd love to have you.

    ", + "

    Please follow these steps to get started:

    ", + "

    ", + "

      ", + "
    1. Follow this tutorial to set up your computer for streaming.
    2. ", + "
    3. Contact Jason Ruekert - he's @jsonify in Gitter. He's in charge of our Twitch.tv channel. Tell him what you'd like to stream, and when you're available to stream.
    4. ", + "
    5. Jason will pair with you using Screenhero to verify your computer is configured properly to stream.
    6. ", + "
    ", + "

    ", + "

    Be respectful of your audience. Everything you stream should be related to coding JavaScript, and should be acceptable for children. (Yes, children do sometimes watch our Twitch stream to learn to code).

    ", + "

    While you're streaming, keep the chat room open so you can respond to questions from your viewers. If someone follows Free Code Camp on Twitch, try to thank them.

    ", + "

    If you do a good job, we'll invite you back to stream some more. Who knows, you might become one of our regular streamers!

    ", + "
    " + ] + }, + { + "id": "bd7158d9c446eddfaeb5bdef", + "name": "How do Free Code Camp's Nonprofit Projects work?", + "dashedName": "how-do-free-code-camps-nonprofit-projects-work", + "category": "outreach", + "description": [ + "
    ", + "

    Building nonprofit projects is the main way that our campers learn full stack JavaScript and agile software development. Once you complete the Free Code Camp Waypoints, Bonfires, Ziplines and Basejumps, you'll begin this process.

    ", + "

    Starting with the end in mind

    ", + "

    Our goal at Free Code Camp is to help you land a job as a junior software developer (or, if you prefer, a 'pivot job' that leads your current career in a more technical direction).

    ", + "

    You'll continue to work on nonprofit projects until you've built a sufficiently impressive portfolio and references to start your job search. Your portfolio will ultimately have three to five nonprofit projects. We estimate that the 900 hours of nonprofit projects you're going to complete, in addition to the 100 hours of challenges you've already completed, will be enough to qualify you for your first coding job. This will produce a much broader portfolio than a traditional coding bootcamp, which generally only has one or two capstone projects.

    ", + "

    Choosing your first Nonprofit Project

    ", + "

    We've categorized all the nonprofit projects by estimated time investment per camper: 100 hours, 200 hours, and 300 hours. These are only rough estimates.

    ", + "

    Example: if you and the camper you're paired up with (your pair) each stated you could work 20 hours per week. If the project is a 100 hour per camper project, you should be able to complete it in about 5 weeks.

    ", + "

    Our Nonprofit Project team will match you and your pair based on:

    ", + "

    ", + "

      ", + "
    1. Your estimated time commitment (10, 20 or 40 hours per week)
    2. ", + "
    3. Your time zone
    4. ", + "
    5. The nonprofit projects you've chosen
    6. ", + "
    7. Prior coding experience (we'd like both campers to be able to contribute equally)
    8. ", + "
    ", + "

    ", + "

    We won't take age or gender into account. This will provide you with valuable experience in meshing with diverse teams, which is a reality of the contemporary workplace.

    ", + "

    You'll only work on one project at a time. Once you start a nonprofit project, we'll remove you from all other nonprofit project you've expressed interest in. There's a good chance those projects will no longer be available when you finish your current project, anyway. Don't worry, though - we get new nonprofit project requests every day, so there will be plenty more projects for you to consider after you finish your current one.

    ", + "

    Finalizing the Project

    ", + "

    Before you can start working on the project, our team of Nonprofit Project Coordinators will go through the following process:

    ", + "

    ", + "

      ", + "
    1. We'll wait until there are two campers who have chosen the same project and look like they're a good match for one another based on the factors mentioned above.
    2. ", + "
    3. We'll call the stakeholder to confirm once again that he or she agrees with our  terms  and has signed our  Nonprofit Project Stakeholder Pledge.
    4. ", + "
    5. We'll set an initial meeting with representatives from Free Code Camp, the two campers, and the stakeholder.
    6. ", + "
    7. If the stakeholder and both campers shows up promptly, and seem enthusiastic and professional, we'll start the project.
    8. ", + "
    ", + "

    ", + "

    This lengthy process serves an important purpose: it reduces the likelihood that any of our campers or stakeholders will waste their precious time.

    ", + "

    Nonprofit Stakeholders

    ", + "

    Each nonprofit project was submitted by a nonprofit. A representative from this nonprofit has agreed to serve as a \"stakeholder\" - an authorative person who understands the organization and its needs for this particular project.

    ", + "

    Stakeholders have a deep understanding of their organizations' needs. Campers will work with them to figure out the best solutions to these needs.

    ", + "

    When you and your pair first speak with your nonprofit stakeholder, you'll:

    ", + "

    ", + "

      ", + "
    • talk at length to better understand their needs.
    • ", + "
    • create a new Trello board and use it to prioritize what needs to be built.
    • ", + "
    • and establish deadlines based on your weekly time commitment, and how long you think each task will take.
    • ", + "
    ", + "

    ", + "

    It's notoriously difficult to estimate how long building software projects will take, so feel free to ask our volunteer team for help.

    ", + "

    You'll continue to meet with your stakeholder at least twice a month in your project's Gitter or Slack channel.

    ", + "

    You should also ask questions in your project's Gitter or Slack channel as they come up throughout the week, and your stakeholder can answer them asynchronously.

    ", + "

    Getting \"blocked\" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter.

    ", + "

    Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!

    ", + "

    Working with your Pair

    ", + "

    You and your pair will pair program (code together on the same computer virtually) about half of the time, and work independently the other half of the time.

    ", + "

    Here are our recommended ways of collaborating:

    ", + "

    ", + "

      ", + "
    • • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
    • ", + "
    • • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them.
    • ", + "
    • • Screenhero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming.
    • ", + "
    • • Write clear and readable code, commit messages, branch names, and pull request messages.
    • ", + "
    ", + "

    ", + "

    Hosting Apps

    ", + "

    Unless your stakeholder has an existing modern host (AWS, Digital Ocean), you'll need to transition them over to a new platform. We believe Heroku is the best choice for a vast majority of web projects. It's free, easy to use, and has both browser and command line interfaces. It's owned by Salesforce and used by a ton of companies, so it's accountable and unlikely to go away.

    ", + "

    If you need help convincing your stakeholder that Heroku is the ideal platform, we'll be happy to talk with them.

    ", + "

    Maintaining Apps

    ", + "

    Once you complete a nonprofit project, your obligation to its stakeholder is finished. Your goal is to leave behind a well documented solution that can be easily maintained by a contract JavaScript developer (or even a less-technical \"super user\").

    ", + "

    While you will no longer need to help with feature development, we encourage you to consider helping your stakeholder with occasional patches down the road. After all, this project will be an important piece of your portfolio, and you'll want it to remain in good shape for curious future employers.

    ", + "

    Pledging to finish the project

    ", + "

    Your nonprofit stakeholder, your pair, and our volunteer team are all counting on you to finish your nonprofit project. If you walk away from an unfinished nonprofit project, you'll become ineligible to ever be assigned another one.

    ", + "

    To confirm that you understand the seriousness of this commitment, we require that all campers  sign this pledge  before starting on their nonprofit projects.

    ", + "

    There will likely be times of confusion or frustration. This is normal in software development. The most important thing is that you do not give up and instead persevere through these setbacks. As Steve Jobs famously said, \"Real artists ship.\" And you are going to ship one successful nonprofit project after another until you feel ready to take the next step in your promising career.

    ", + "
    " + ] + }, + { + "id": "bd7158d9c436eddfadb5bd3e", + "name": "How can I contribute to this guide?", + "dashedName": "how-can-i-contribute-to-this-guide", + "category": "outreach", + "description": [ + "
    ", + "

    Contributing to our field guide is a great way to establish your history on GitHub, add to your portfolio, and help other campers. If you have a question about JavaScript or programming in general that you'd like us to add to the field guide, here are two ways to get it into the guide:

    ", + "

    ", + "

      ", + "
    1. You can message @danraley on Gitter with your question.
    2. ", + "
    3. You can also contribute to this field guide directly via GitHub pull request, by cloning Free Code Camp's main repository and modifying field-guides.json.
    4. ", + "
    ", + "

    ", + "
    " + ] + }, + { + "id": "bd7158d9c436eddfadb5bd32", + "name": "How can I help the Free Code Camp translation effort?", + "dashedName": "how-can-i-help-the-free-code-camp-translation-effort", + "category": "outreach", + "description": [ + "
    ", + "

    Our translation effort is driven by bilingual campers like you.", + "

    If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

    ", + "
    " + ] + }, + { + "id": "bd7158d9c436eddfadb5bd31", + "name": "What if I speak a language that Free Code Camp does not yet support?", + "dashedName": "what-if-i-speak-a-language-that-free-code-camp-does-not-yet-support", + "category": "outreach", + "description": [ + "
    ", + "

    Translation is an all-or-nothing proposal.", + "

    We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that language.

    ", + "

    In addition to translating these initially, we'll also need to maintain the translation as the challenges are gradually updated.

    ", + "

    If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

    ", + "
    " + ] + } +] diff --git a/seed/index.js b/seed/index.js index 7eff9f3f2e..84c47644ea 100644 --- a/seed/index.js +++ b/seed/index.js @@ -1,18 +1,25 @@ /* eslint-disable no-process-exit */ +require('babel/register'); require('dotenv').load(); var fs = require('fs'), path = require('path'), app = require('../server/server'), - fieldGuides = require('./field-guides.json'), nonprofits = require('./nonprofits.json'), jobs = require('./jobs.json'); +var challangesRegex = /^(bonfire:|waypoint:|zipline:|basejump:|hike:)/i; + +function getFilesFor(dir) { + return fs.readdirSync(path.join(__dirname, '/' + dir)); +} + var Challenge = app.models.Challenge; var FieldGuide = app.models.FieldGuide; var Nonprofit = app.models.Nonprofit; var Job = app.models.Job; var counter = 0; -var challenges = fs.readdirSync(path.join(__dirname, '/challenges')); +var challenges = getFilesFor('challenges'); +var fieldGuides = getFilesFor('field-guides'); var offerings = 3 + challenges.length; var CompletionMonitor = function() { @@ -32,10 +39,17 @@ Challenge.destroyAll(function(err, info) { } else { console.log('Deleted ', info); } - challenges.forEach(function (file) { + challenges.forEach(function(file) { + var challenges = require('./challenges/' + file).challenges + .map(function(challenge) { + // NOTE(berks): add title for displaying in views + challenge.title = challenge.name.replace(challangesRegex, '').trim(); + return challenge; + }); + Challenge.create( - require('./challenges/' + file).challenges, - function (err) { + challenges, + function(err) { if (err) { console.log(err); } else { @@ -53,14 +67,16 @@ FieldGuide.destroyAll(function(err, info) { } else { console.log('Deleted ', info); } - FieldGuide.create(fieldGuides, function(err, data) { - if (err) { - console.log(err); - } else { - console.log('Saved ', data); - } - CompletionMonitor(); - console.log('field guides'); + fieldGuides.forEach(function(file) { + FieldGuide.create(require('./field-guides/' + file), function(err, data) { + if (err) { + console.log(err); + } else { + console.log('Saved ', data); + } + CompletionMonitor(); + console.log('field guides'); + }); }); }); diff --git a/server/boot/a-react.js b/server/boot/a-react.js new file mode 100644 index 0000000000..cf4bb86d43 --- /dev/null +++ b/server/boot/a-react.js @@ -0,0 +1,64 @@ +import React from 'react'; +import Router from 'react-router'; +import Location from 'react-router/lib/Location'; +import debugFactory from 'debug'; +import { app$ } from '../../common/app'; +import { RenderToString } from 'thundercats-react'; + +const debug = debugFactory('freecc:servereact'); + +// add routes here as they slowly get reactified +// remove their individual controllers +const routes = [ + '/hikes', + '/hikes/*', + '/jobs' +]; + +export default function reactSubRouter(app) { + var router = app.loopback.Router(); + + routes.forEach(function(route) { + router.get(route, serveReactApp); + }); + + app.use(router); + + function serveReactApp(req, res, next) { + const location = new Location(req.path, req.query); + + // returns a router wrapped app + app$(location) + // if react-router does not find a route send down the chain + .filter(function({ initialState }) { + if (!initialState) { + debug('react tried to find %s but got 404', location.pathname); + return next(); + } + return !!initialState; + }) + .flatMap(function({ initialState, AppCat }) { + // call thundercats renderToString + // prefetches data and sets up it up for current state + debug('rendering to string'); + return RenderToString( + AppCat(), + React.createElement(Router, initialState) + ); + }) + // makes sure we only get one onNext and closes subscription + .flatMap(function({ data, markup }) { + debug('react rendered'); + res.expose(data, 'data'); + // now render jade file with markup injected from react + return res.render$('layout-react', { markup: markup }); + }) + .subscribe( + function(markup) { + debug('jade rendered'); + res.send(markup); + }, + next + ); + } +} diff --git a/server/boot/a-services.js b/server/boot/a-services.js new file mode 100644 index 0000000000..3cb288a99d --- /dev/null +++ b/server/boot/a-services.js @@ -0,0 +1,8 @@ +import Fetchr from 'fetchr'; +import getHikesService from '../services/hikes'; + +export default function bootServices(app) { + const hikesService = getHikesService(app); + Fetchr.registerFetcher(hikesService); + app.use('/services', Fetchr.middleware()); +} diff --git a/server/boot/challenge.js b/server/boot/challenge.js index c0ec900804..cf89399296 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -55,10 +55,12 @@ var unDasherize = utils.unDasherize; var getMDNLinks = utils.getMDNLinks; function updateUserProgress(user, challengeId, completedChallenge) { - var index = user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { + var alreadyCompleted = user.completedChallenges.some(({ id }) => { + return id === challengeId; + }); + + if (alreadyCompleted) { user.progressTimestamps.push(Date.now()); - user.uncompletedChallenges.splice(index, 1); } user.completedChallenges.push(completedChallenge); return user; @@ -108,16 +110,6 @@ module.exports = function(app) { app.use(router); function returnNextChallenge(req, res, next) { - var completed = req.user.completedChallenges.map(function (elem) { - return elem.id; - }); - - req.user.uncompletedChallenges = utils.allChallengeIds() - .filter(function(elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); // find the user's current challenge and block // look in that block and find the index of their current challenge @@ -157,17 +149,6 @@ module.exports = function(app) { } function returnCurrentChallenge(req, res, next) { - var completed = req.user.completedChallenges.map(function (elem) { - return elem.id; - }); - - req.user.uncompletedChallenges = utils.allChallengeIds() - .filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - if (!req.user.currentChallenge) { req.user.currentChallenge = {}; req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0]; @@ -227,12 +208,12 @@ module.exports = function(app) { challengeName: challenge.name, dashedName: challenge.dashedName, challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds) - .map(function (key) { + .map(function(key) { return challengeMapWithIds[key] - .filter(function (elem) { + .filter(function(elem) { return elem === ('' + challenge.id); }) - .map(function () { + .map(function() { return key; }); }) @@ -244,7 +225,7 @@ module.exports = function(app) { title: challenge.name, dashedName: origChallengeName, name: challenge.name, - details: challenge.description.slice(1), + details: challenge.description, tests: challenge.tests, challengeSeed: challenge.challengeSeed, verb: utils.randomVerb(), @@ -256,7 +237,6 @@ module.exports = function(app) { video: challenge.challengeSeed[0], // bonfires specific difficulty: Math.floor(+challenge.difficulty), - brief: challenge.description.shift(), bonfires: challenge, MDNkeys: challenge.MDNlinks, MDNlinks: getMDNLinks(challenge.MDNlinks), @@ -264,6 +244,7 @@ module.exports = function(app) { environment: utils.whichEnvironment() }; + // TODO Berkeley var challengeView = { 0: 'coursewares/showHTML', 1: 'coursewares/showJS', @@ -361,16 +342,17 @@ module.exports = function(app) { function completedChallenge(req, res, next) { - var completedDate = Math.round(+new Date()); - var challengeId = req.body.challengeInfo.challengeId; + const completedDate = Math.round(+new Date()); + const { id, name } = req.body; + const { challengeId, challengeName } = req.body.challengeInfo || {}; updateUserProgress( req.user, - challengeId, + id || challengeId, { - id: challengeId, + id: id || challengeId, completedDate: completedDate, - name: req.body.challengeInfo.challengeName, + name: name || challengeName, solution: null, githubLink: null, verified: true @@ -379,7 +361,7 @@ module.exports = function(app) { saveUser(req.user) .subscribe( - function() { }, + function(user) { debug('user save', user && user.progressTimestamps); }, next, function() { res.sendStatus(200); diff --git a/server/boot/fieldGuide.js b/server/boot/fieldGuide.js index 87fcdd0e69..ef723a6b39 100644 --- a/server/boot/fieldGuide.js +++ b/server/boot/fieldGuide.js @@ -6,6 +6,14 @@ var utils = require('../utils'); var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds(); +// order here determine order on all-articles page +const categories = [ + 'orientation', + 'FYI', + 'outreach', + 'contact' +]; + module.exports = function(app) { var router = app.loopback.Router(); var FieldGuide = app.models.FieldGuide; @@ -23,6 +31,7 @@ module.exports = function(app) { var dashedName = req.params.fieldGuideName; var userSave = Rx.Observable.just(req.user) .filter(function(user) { + debug('filtering user', !!user); return !!user; }) .map(function(user) { @@ -38,8 +47,11 @@ module.exports = function(app) { return user; }) .flatMap(function(user) { + debug('saving user'); return saveUser(user); - }); + }) + // always call onNext + .defaultIfEmpty(null); var query = { where: { dashedName: { like: dashedName, options: 'i' } } }; @@ -53,6 +65,7 @@ module.exports = function(app) { .subscribe( // don't care about return from userSave function(fieldGuide) { + debug('onNext', fieldGuide); if (!fieldGuide) { req.flash('errors', { msg: '404: We couldn\'t find a field guide entry with ' + @@ -70,7 +83,10 @@ module.exports = function(app) { description: fieldGuide.description.join('') }); }, - next + next, + function() { + debug('onCompleted called'); + } ); } @@ -80,9 +96,25 @@ module.exports = function(app) { completedFieldGuides = req.user.completedFieldGuides; } + // produces an array of arrays of field guides ordered by the above + // i.e. [[...orientFieldGuides][...FYIfieldGuides]...] + const orderFieldGuides = categories + .reduce((ordered, category) => { + + const fieldGuidesForCategory = allFieldGuideNamesAndIds + .filter(fieldGuide => { + return category === fieldGuide.category; + }); + + return ordered.concat([fieldGuidesForCategory]); + }, []); + res.render('field-guide/all-articles', { + // leaving this property as legacy. allFieldGuideNamesAndIds: allFieldGuideNamesAndIds, - completedFieldGuides: completedFieldGuides + completedFieldGuides: completedFieldGuides, + categories: categories, + fieldGuides: orderFieldGuides }); } diff --git a/server/boot/user.js b/server/boot/user.js index d2d9a0936b..2a6c37d882 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -45,7 +45,7 @@ module.exports = function(app) { * Siginin page. */ - function getSignin (req, res) { + function getSignin(req, res) { if (req.user) { return res.redirect('/'); } @@ -59,7 +59,7 @@ module.exports = function(app) { * Log out. */ - function signout (req, res) { + function signout(req, res) { req.logout(); res.redirect('/'); } @@ -69,7 +69,7 @@ module.exports = function(app) { * Signup page. */ - function getEmailSignin (req, res) { + function getEmailSignin(req, res) { if (req.user) { return res.redirect('/'); } @@ -83,7 +83,7 @@ module.exports = function(app) { * Signup page. */ - function getEmailSignup (req, res) { + function getEmailSignup(req, res) { if (req.user) { return res.redirect('/'); } @@ -97,7 +97,7 @@ module.exports = function(app) { * Profile page. */ - function getAccount (req, res) { + function getAccount(req, res) { if (!req.user) { return res.redirect('/'); } @@ -110,7 +110,7 @@ module.exports = function(app) { * Angular API Call */ - function getAccountAngular (req, res) { + function getAccountAngular(req, res) { res.json({ user: req.user || {} }); @@ -122,7 +122,7 @@ module.exports = function(app) { * Public Profile page. */ - function returnUser (req, res, next) { + function returnUser(req, res, next) { User.findOne( { where: { 'username': req.params.username.toLowerCase() } }, function(err, user) { @@ -162,11 +162,13 @@ module.exports = function(app) { user.currentStreak = 1; var today = moment(Date.now()).format('YYYY-MM-DD'); + const yesterday = moment(today).subtract(1, 'd').toString(); + const yesteryesterday = moment(today).subtract(2, 'd').toString(); + if ( moment(today).toString() === moment(timeKeys[0]).toString() || - moment(today).subtract(1, 'd').toString() === - moment(timeKeys[0]).toString() || moment(today).subtract(2, 'd').toString() === - moment(timeKeys[0]).toString() + yesterday === moment(timeKeys[0]).toString() || + yesteryesterday === moment(timeKeys[0]).toString() ) { for (var _i = 1; _i <= timeKeys.length; _i++) { @@ -196,7 +198,7 @@ module.exports = function(app) { user.currentStreak = user.currentStreak || 1; user.longestStreak = user.longestStreak || 1; - var challenges = user.completedChallenges.filter(function ( obj ) { + var challenges = user.completedChallenges.filter(function( obj ) { return obj.challengeType === 3 || obj.challengeType === 4; }); @@ -249,7 +251,7 @@ module.exports = function(app) { * Update profile information. */ - function postUpdateProfile (req, res, next) { + function postUpdateProfile(req, res, next) { User.findById(req.user.id, function(err) { if (err) { return next(err); } @@ -318,7 +320,7 @@ module.exports = function(app) { user.website3Image = body.website3Image.trim() || ''; - user.save(function (err) { + user.save(function(err) { if (err) { return next(err); } @@ -346,7 +348,7 @@ module.exports = function(app) { * Update current password. */ - function postUpdatePassword (req, res, next) { + function postUpdatePassword(req, res, next) { req.assert('password', 'Password must be at least 4 characters long') .len(4); @@ -379,7 +381,7 @@ module.exports = function(app) { * Delete user account. */ - function postDeleteAccount (req, res, next) { + function postDeleteAccount(req, res, next) { User.destroyById(req.user.id, function(err) { if (err) { return next(err); } req.logout(); @@ -393,7 +395,7 @@ module.exports = function(app) { * Unlink OAuth provider. */ - function getOauthUnlink (req, res, next) { + function getOauthUnlink(req, res, next) { var provider = req.params.provider; User.findById(req.user.id, function(err, user) { if (err) { return next(err); } @@ -417,7 +419,7 @@ module.exports = function(app) { * Reset Password page. */ - function getReset (req, res, next) { + function getReset(req, res, next) { if (req.isAuthenticated()) { return res.redirect('/'); } @@ -448,7 +450,7 @@ module.exports = function(app) { * Process the reset password request. */ - function postReset (req, res, next) { + function postReset(req, res, next) { var errors = req.validationErrors(); if (errors) { @@ -526,7 +528,7 @@ module.exports = function(app) { * Forgot Password page. */ - function getForgot (req, res) { + function getForgot(req, res) { if (req.isAuthenticated()) { return res.redirect('/'); } @@ -540,7 +542,7 @@ module.exports = function(app) { * Create a random token, then the send user an email with a reset link. */ - function postForgot (req, res, next) { + function postForgot(req, res, next) { var errors = req.validationErrors(); if (errors) { @@ -626,7 +628,7 @@ module.exports = function(app) { foundStories, foundComments; - Story.find({ 'author.userId': userId }, function (err, stories) { + Story.find({ 'author.userId': userId }, function(err, stories) { if (err) { return cb(err); } @@ -635,7 +637,7 @@ module.exports = function(app) { saveStoriesAndComments(); }); - Comment.find({ 'author.userId': userId }, function (err, comments) { + Comment.find({ 'author.userId': userId }, function(err, comments) { if (err) { return cb(err); } @@ -649,22 +651,22 @@ module.exports = function(app) { return; } var tasks = []; - R.forEach(function (comment) { + R.forEach(function(comment) { comment.author.picture = picture; comment.author.username = username; - tasks.push(function (cb) { + tasks.push(function(cb) { comment.save(cb); }); }, foundComments); - R.forEach(function (story) { + R.forEach(function(story) { story.author.picture = picture; story.author.username = username; - tasks.push(function (cb) { + tasks.push(function(cb) { story.save(cb); }); }, foundStories); - async.parallel(tasks, function (err) { + async.parallel(tasks, function(err) { if (err) { return cb(err); } diff --git a/server/debug-entry.js b/server/debug-entry.js new file mode 100644 index 0000000000..f0a88e9337 --- /dev/null +++ b/server/debug-entry.js @@ -0,0 +1,4 @@ +require('babel/register'); +var app = require('./server'); + +app.start(); diff --git a/server/server.js b/server/server.js index 51f0047aeb..34c686ec9f 100755 --- a/server/server.js +++ b/server/server.js @@ -10,6 +10,7 @@ var R = require('ramda'), cookieParser = require('cookie-parser'), compress = require('compression'), session = require('express-session'), + expressState = require('express-state'), logger = require('morgan'), errorHandler = require('errorhandler'), methodOverride = require('method-override'), @@ -22,6 +23,7 @@ var R = require('ramda'), lessMiddleware = require('less-middleware'), passportProviders = require('./passport-providers'), + rxMiddleware = require('./utils/rx').rxMiddleware, /** * API keys and Passport configuration. */ @@ -33,6 +35,10 @@ var generateKey = * Create Express server. */ var app = loopback(); + +expressState.extend(app); +app.set('state namespace', '__fcc__'); + var PassportConfigurator = require('loopback-component-passport').PassportConfigurator; var passportConfigurator = new PassportConfigurator(app); @@ -141,7 +147,9 @@ app.use(helmet.csp({ 'http://cdn.inspectlet.com/inspectlet.js', 'http://www.freecodecamp.org' ].concat(trusted), - 'connect-src': [].concat(trusted), + 'connect-src': [ + 'vimeo.com' + ].concat(trusted), styleSrc: [ '*.googleapis.com', '*.gstatic.com' @@ -175,19 +183,22 @@ app.use(helmet.csp({ passportConfigurator.init(); +// add rx methods to express +app.use(rxMiddleware()); + 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 }) ); -// track when connecting to db starts var startTime = Date.now(); boot(app, { appRootDir: __dirname, @@ -301,7 +312,7 @@ if (process.env.NODE_ENV === 'development') { module.exports = app; -app.start = function () { +app.start = function() { app.listen(app.get('port'), function() { app.emit('started'); console.log( diff --git a/server/services/hikes.js b/server/services/hikes.js new file mode 100644 index 0000000000..10b5221729 --- /dev/null +++ b/server/services/hikes.js @@ -0,0 +1,32 @@ +import debugFactory from 'debug'; +import assign from 'object.assign'; + +const debug = debugFactory('freecc:services:hikes'); + +export default function hikesService(app) { + const Challenge = app.models.Challenge; + + return { + name: 'hikes', + read: (req, resource, params, config, cb) => { + const query = { + where: { challengeType: '6' }, + order: 'difficulty ASC' + }; + + debug('params', params); + if (params) { + assign(query.where, { + dashedName: { like: params.dashedName, options: 'i' } + }); + } + debug('query', query); + Challenge.find(query, (err, hikes) => { + if (err) { + return cb(err); + } + cb(null, hikes); + }); + } + }; +} diff --git a/server/utils/index.js b/server/utils/index.js index ec64435a5f..b7c7be34dc 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -9,8 +9,17 @@ var path = require('path'), MDNlinks = require('../../seed/bonfireMDNlinks'), resources = require('./resources.json'), - nonprofits = require('../../seed/nonprofits.json'), - fieldGuides = require('../../seed/field-guides.json'); + nonprofits = require('../../seed/nonprofits.json'); + +var fieldGuides = fs.readdirSync( + path.join(__dirname, '../../seed/field-guides') + ) + .map(function(file) { + return require('../../seed/field-guides/' + file); + }) + .reduce(function(allGuides, categoryGuides) { + return allGuides.concat(categoryGuides); + }, []); /** * Cached values @@ -38,15 +47,15 @@ Array.zip = function(left, right, combinerFunction) { path.join(__dirname, '../../seed/challenges') ); var keyCounter = 0; - files = files.map(function (file) { + files = files.map(function(file) { return require( path.join(__dirname, '../../seed/challenges/' + file) ); }); - files = files.sort(function (a, b) { + files = files.sort(function(a, b) { return a.order - b.order; }); - files.forEach(function (file) { + files.forEach(function(file) { localChallengeMap[keyCounter++] = file; }); challengeMap = _.cloneDeep(localChallengeMap); @@ -66,10 +75,10 @@ module.exports = { return ('' + name).replace(/\-/g, ' ').trim(); }, - getChallengeMapForDisplay: function () { + getChallengeMapForDisplay: function() { if (!challengeMapForDisplay) { challengeMapForDisplay = {}; - Object.keys(challengeMap).forEach(function (key) { + Object.keys(challengeMap).forEach(function(key) { challengeMapForDisplay[key] = { name: challengeMap[key].name, dashedName: challengeMap[key].name.replace(/\s/g, '-'), @@ -81,11 +90,11 @@ module.exports = { return challengeMapForDisplay; }, - getChallengeMapWithIds: function () { + getChallengeMapWithIds: function() { if (!challengeMapWithIds) { challengeMapWithIds = {}; - Object.keys(challengeMap).forEach(function (key) { - var onlyIds = challengeMap[key].challenges.map(function (elem) { + Object.keys(challengeMap).forEach(function(key) { + var onlyIds = challengeMap[key].challenges.map(function(elem) { return elem.id; }); challengeMapWithIds[key] = onlyIds; @@ -94,11 +103,11 @@ module.exports = { return challengeMapWithIds; }, - allChallengeIds: function () { + allChallengeIds: function() { if (!allChallengeIds) { allChallengeIds = []; - Object.keys(this.getChallengeMapWithIds()).forEach(function (key) { + Object.keys(this.getChallengeMapWithIds()).forEach(function(key) { allChallengeIds.push(challengeMapWithIds[key]); }); allChallengeIds = R.flatten(allChallengeIds); @@ -106,12 +115,12 @@ module.exports = { return allChallengeIds; }, - getChallengeMapWithNames: function () { + getChallengeMapWithNames: function() { if (!challengeMapWithNames) { challengeMapWithNames = {}; Object.keys(challengeMap). - forEach(function (key) { - var onlyNames = challengeMap[key].challenges.map(function (elem) { + forEach(function(key) { + var onlyNames = challengeMap[key].challenges.map(function(elem) { return elem.name; }); challengeMapWithNames[key] = onlyNames; @@ -124,8 +133,8 @@ module.exports = { if (!challengeMapWithDashedNames) { challengeMapWithDashedNames = {}; Object.keys(challengeMap). - forEach(function (key) { - var onlyNames = challengeMap[key].challenges.map(function (elem) { + forEach(function(key) { + var onlyNames = challengeMap[key].challenges.map(function(elem) { return elem.dashedName; }); challengeMapWithDashedNames[key] = onlyNames; @@ -135,62 +144,63 @@ module.exports = { }, - randomPhrase: function () { + randomPhrase: function() { return resources.phrases[ Math.floor(Math.random() * resources.phrases.length) ]; }, - randomVerb: function () { + randomVerb: function() { return resources.verbs[ Math.floor(Math.random() * resources.verbs.length) ]; }, - randomCompliment: function () { + randomCompliment: function() { return resources.compliments[ Math.floor(Math.random() * resources.compliments.length) ]; }, - allFieldGuideIds: function () { + allFieldGuideIds: function() { if (allFieldGuideIds) { return allFieldGuideIds; } else { - allFieldGuideIds = fieldGuides.map(function (elem) { + allFieldGuideIds = fieldGuides.map(function(elem) { return elem.id; }); return allFieldGuideIds; } }, - allFieldGuideNamesAndIds: function () { + allFieldGuideNamesAndIds: function() { if (allFieldGuideNames) { return allFieldGuideNames; } else { - allFieldGuideNames = fieldGuides.map(function (elem) { + allFieldGuideNames = fieldGuides.map(function(elem) { return { name: elem.name, dashedName: elem.dashedName, - id: elem.id + id: elem.id, + category: elem.category }; }); return allFieldGuideNames; } }, - allNonprofitNames: function () { + allNonprofitNames: function() { if (allNonprofitNames) { return allNonprofitNames; } else { - allNonprofitNames = nonprofits.map(function (elem) { + allNonprofitNames = nonprofits.map(function(elem) { return {name: elem.name}; }); return allNonprofitNames; } }, - whichEnvironment: function () { + whichEnvironment: function() { return process.env.NODE_ENV; }, diff --git a/server/utils/rx.js b/server/utils/rx.js index 8088e163e0..ed7e0ebf73 100644 --- a/server/utils/rx.js +++ b/server/utils/rx.js @@ -27,6 +27,15 @@ exports.observableQueryFromModel = return Rx.Observable.fromNodeCallback(Model[method], Model)(query); }; -exports.observeMethod = function observeMethod(Model, method) { - return Rx.Observable.fromNodeCallback(Model[method], Model); +exports.observeMethod = function observeMethod(context, methodName) { + return Rx.Observable.fromNodeCallback(context[methodName], context); +}; + +// add rx methods to express +exports.rxMiddleware = function rxMiddleware() { + return function rxMiddleware(req, res, next) { + // render to observable + res.render$ = Rx.Observable.fromNodeCallback(res.render, res); + next(); + }; }; diff --git a/server/views/challengeMap/show.jade b/server/views/challengeMap/show.jade index 392e4275f7..1d3f91dfe7 100644 --- a/server/views/challengeMap/show.jade +++ b/server/views/challengeMap/show.jade @@ -41,7 +41,7 @@ block content li.large-p.negative-10 a(href='#' + challengeBlock.dashedName)= challengeBlock.name else - .hidden-xs.col-sm-3 + .hidden-xs.col-sm-3.col-md-2 span.negative-10 .col-xs-12.col-sm-9.col-md-10 li.large-p.negative-10 diff --git a/server/views/field-guide/all-articles.jade b/server/views/field-guide/all-articles.jade index 882bccd44a..309bde7be6 100644 --- a/server/views/field-guide/all-articles.jade +++ b/server/views/field-guide/all-articles.jade @@ -9,9 +9,11 @@ block content .panel-body .col-xs-12.col-md-10.col-md-offset-1 .col-xs-12.no-right-padding + each category, index in categories + h2.text-center= category.charAt(0).toUpperCase() + category.slice(1) h3 ol - each fieldGuide in allFieldGuideNamesAndIds + each fieldGuide in fieldGuides[index] if completedFieldGuides.indexOf(fieldGuide.id) > -1 .row .hidden-xs.col-sm-3.col-md-2.text-primary.ion-checkmark-circled.padded-ionic-icon.text-center diff --git a/server/views/home.jade b/server/views/home.jade index 3740c64e3b..7b25a30777 100644 --- a/server/views/home.jade +++ b/server/views/home.jade @@ -30,7 +30,12 @@ block content a.btn.btn-cta.signup-btn.btn-block(href="/login") Start learning to code (it's free) .button-spacer a.btn.btn-cta.btn-success.btn-block(href="/nonprofits") My nonprofit needs coding help - .big-break + h2 As seen in: + img.img-center.img-responsive(src='https://s3.amazonaws.com/freecodecamp/as-seen-on.png') + .spacer + h2 We're a proven way to start your software engineering career: + img.img-center.img-responsive(src='https://s3.amazonaws.com/freecodecamp/linkedin-alumni.png') + .spacer h2 Campers you'll hang out with: .row .col-xs-12.col-sm-12.col-md-4 @@ -61,8 +66,8 @@ block content .landing-skill-icon.fa.fa-database.font-awesome-padding h2.black-text Databases .col-xs-12.col-sm-12.col-md-3 - .landing-skill-icon.ion-social-chrome - h2.black-text DevTools + .landing-skill-icon.ion-social-github + h2.black-text Git .col-xs-12.col-sm-12.col-md-3 .landing-skill-icon.ion-social-nodejs h2.black-text Node.js diff --git a/server/views/layout-react.jade b/server/views/layout-react.jade new file mode 100644 index 0000000000..f77aa0ec75 --- /dev/null +++ b/server/views/layout-react.jade @@ -0,0 +1,10 @@ +doctype html +html(ng-app='profileValidation', lang='en') + head + include partials/small-head + body.top-and-bottom-margins(style='overflow: hidden') + .container + include partials/flash + #fcc!= markup + script!= state + script(src='/js/bundle.js') diff --git a/server/views/partials/small-head.jade b/server/views/partials/small-head.jade index 36b7c88c86..552b9d815b 100644 --- a/server/views/partials/small-head.jade +++ b/server/views/partials/small-head.jade @@ -3,6 +3,7 @@ script(src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstra link(rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Lato:400|Inconsolata") link(rel='stylesheet', href='/bower_components/font-awesome/css/font-awesome.min.css') link(rel='stylesheet', href='/css/main.css') +link(rel='stylesheet', href='/css/lib/Vimeo.css') // End **REQUIRED** includes include meta diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000..68091d74e5 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,32 @@ +var path = require('path'); + +module.exports = { + devtool: 'inline-source-map', + entry: './client', + output: { + filename: 'bundle.js', + path: path.join(__dirname, '/public/js'), + publicPath: 'public/' + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + include: [ + path.join(__dirname, 'client/'), + path.join(__dirname, 'common/') + ], + loaders: [ + 'babel-loader' + ] + }, + { + test: /\.json$/, + loaders: [ + 'json-loader' + ] + } + ] + }, + plugins: [] +}; diff --git a/webpack.config.node.js b/webpack.config.node.js new file mode 100644 index 0000000000..f422032d85 --- /dev/null +++ b/webpack.config.node.js @@ -0,0 +1,54 @@ +var fs = require('fs'); +var path = require('path'); +var webpack = require('webpack'); + +var nodeModules = fs.readdirSync('node_modules') + .filter(function(x) { + return ['.bin'].indexOf(x) === -1; + }) + .reduce(function(nodeModules, module) { + nodeModules[module] = 'commonjs ' + module; + return nodeModules; + }, {}); + +module.exports = { + devtool: 'sourcemap', + target: 'node', + entry: './common/app', + // keeps webpack from bundling modules + externals: nodeModules, + output: { + filename: 'app-stream.bundle.js', + path: path.join(__dirname, '/server'), + publicPath: 'public/' + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + include: [ + path.join(__dirname, 'client/'), + path.join(__dirname, 'common/') + ], + loaders: [ + 'babel-loader' + ] + }, + { + test: /\.json$/, + loaders: [ + 'json-loader' + ] + } + ] + }, + plugins: [ + new webpack.BannerPlugin( + 'require("source-map-support").install();', + { + raw: true, + entryOnly: false + } + ) + ] +};