Merge branch 'staging' into converge-bonfires

Conflicts:
	seed/challenges/basic-bonfires.json
	seed/challenges/intermediate-bonfires.json
This commit is contained in:
Quincy Larson
2015-09-30 19:06:28 -07:00
38 changed files with 854 additions and 181 deletions

View File

@ -4,7 +4,7 @@ import React from 'react';
import Fetchr from 'fetchr';
import debugFactory from 'debug';
import { Router } from 'react-router';
import { history } from 'react-router/lib/BrowserHistory';
import { createLocation, createHistory } from 'history';
import { hydrate } from 'thundercats';
import { Render } from 'thundercats-react';
@ -18,21 +18,29 @@ const services = new Fetchr({
});
Rx.config.longStackSupport = !!debug.enabled;
const history = createHistory();
const appLocation = createLocation(
location.pathname + location.search
);
// returns an observable
app$(history)
app$({ history, location: appLocation })
.flatMap(
({ AppCat }) => {
// instantiate the cat with service
const appCat = AppCat(null, services);
// hydrate the stores
return hydrate(appCat, catState)
.map(() => appCat);
},
({ initialState }, appCat) => ({ initialState, appCat })
// not using nextLocation at the moment but will be used for
// redirects in the future
({ nextLocation, props }, appCat) => ({ nextLocation, props, appCat })
)
.flatMap(({ initialState, appCat }) => {
.flatMap(({ props, appCat }) => {
props.history = history;
return Render(
appCat,
React.createElement(Router, initialState),
React.createElement(Router, props),
DOMContianer
);
})

View File

@ -1,3 +1,32 @@
var mapShareKey = 'map-shares';
var lastCompleted = typeof lastCompleted !== 'undefined' ?
lastCompleted :
'';
function getMapShares() {
var alreadyShared = JSON.parse(localStorage.getItem(mapShareKey) || '[]');
if (!alreadyShared || !Array.isArray(alreadyShared)) {
localStorage.setItem(mapShareKey, JSON.stringify([]));
alreadyShared = [];
}
return alreadyShared;
}
function setMapShare(id) {
var alreadyShared = getMapShares();
var found = false;
alreadyShared.forEach(function(_id) {
if (_id === id) {
found = true;
}
});
if (!found) {
alreadyShared.push(id);
}
localStorage.setItem(mapShareKey, JSON.stringify(alreadyShared));
return alreadyShared;
}
$(document).ready(function() {
var challengeName = typeof challengeName !== 'undefined' ?
@ -383,6 +412,40 @@ $(document).ready(function() {
}
}, false);
}
// map sharing
var alreadyShared = getMapShares();
if (lastCompleted && alreadyShared.indexOf(lastCompleted) === -1) {
$('div[id="' + lastCompleted + '"]')
.parent()
.parent()
.removeClass('hidden');
}
// on map view
$('.map-challenge-block-share').on('click', function(e) {
e.preventDefault();
var challengeBlockName = $(this).children().attr('id');
var challengeBlockEscapedName = challengeBlockName.replace(/\s/, '%20');
var username = typeof window.username !== 'undefined' ?
window.username :
'';
var link = 'https://www.facebook.com/dialog/feed?' +
'app_id=1644598365767721' +
'&display=page&' +
'caption=I%20just%20completed%20the%20' +
challengeBlockEscapedName +
'%20section%20on%20Free%20Code%20Camp%2E' +
'&link=http%3A%2F%2Ffreecodecamp%2Ecom%2F' +
username +
'&redirect_uri=http%3A%2F%2Ffreecodecamp%2Ecom%2Fmap';
setMapShare(challengeBlockName);
window.location.href = link;
});
});
function defCheck(a){

View File

@ -1,17 +1,17 @@
import Rx from 'rx';
import { Router } from 'react-router';
import { match } 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 route$ = Rx.Observable.fromNodeCallback(match);
const routes = Object.assign({ components: App }, childRoutes);
export default function app$(location) {
return router$(routes, location)
.map(([initialState, transistion]) => {
return { initialState, transistion, AppCat };
export default function app$({ location, history }) {
return route$({ routes, location, history })
.map(([nextLocation, props]) => {
return { nextLocation, props, AppCat };
});
}

View File

@ -0,0 +1,43 @@
import React, { PropTypes } from 'react';
import { History } from 'react-router';
import { Button, Modal } from 'react-bootstrap';
export default React.createClass({
displayName: 'CreateJobsModal',
propTypes: {
onHide: PropTypes.func,
showModal: PropTypes.bool
},
mixins: [History],
goToNewJob(onHide) {
onHide();
this.history.pushState(null, '/jobs/new');
},
render() {
const {
showModal,
onHide
} = this.props;
return (
<Modal
onHide={ onHide }
show={ showModal }>
<Modal.Body>
<h4>Welcome to Free Code Camp's board</h4>
<p>We post jobs specifically target to our junior developers.</p>
<Button
block={ true }
className='signup-btn'
onClick={ () => this.goToNewJob(onHide) }>
Post a Job
</Button>
</Modal.Body>
</Modal>
);
}
});

View File

@ -1,7 +1,9 @@
import React, { cloneElement, PropTypes } from 'react';
import { contain } from 'thundercats-react';
import { Navigation } from 'react-router';
import { History } from 'react-router';
import { Button, Jumbotron, Row } from 'react-bootstrap';
import CreateJobModal from './CreateJobModal.jsx';
import ListJobs from './List.jsx';
export default contain(
@ -13,12 +15,14 @@ export default contain(
React.createClass({
displayName: 'Jobs',
mixins: [History],
propTypes: {
children: PropTypes.element,
jobActions: PropTypes.object,
jobs: PropTypes.array
jobs: PropTypes.array,
showModal: PropTypes.bool
},
mixins: [Navigation],
handleJobClick(id) {
const { jobActions } = this.props;
@ -26,7 +30,7 @@ export default contain(
return null;
}
jobActions.findJob(id);
this.transitionTo(`/jobs/${id}`);
this.history.pushState(null, `/jobs/${id}`);
},
renderList(handleJobClick, jobs) {
@ -48,7 +52,12 @@ export default contain(
},
render() {
const { children, jobs } = this.props;
const {
children,
jobs,
showModal,
jobActions
} = this.props;
return (
<div>
@ -62,7 +71,8 @@ export default contain(
</p>
<Button
bsSize='large'
className='signup-btn'>
className='signup-btn'
onClick={ jobActions.openModal }>
Try the first month 20% off!
</Button>
</Jumbotron>
@ -71,6 +81,9 @@ export default contain(
{ this.renderChild(children, jobs) ||
this.renderList(this.handleJobClick, jobs) }
</Row>
<CreateJobModal
onHide={ jobActions.closeModal }
showModal={ showModal } />
</div>
);
}

View File

@ -22,6 +22,7 @@ export default React.createClass({
id,
company,
position,
isHighlighted,
description,
logo,
city,
@ -44,6 +45,7 @@ export default React.createClass({
);
return (
<Panel
bsStyle={ isHighlighted ? 'warning' : 'default' }
collapsible={ true }
eventKey={ index }
header={ header }

View File

@ -0,0 +1,319 @@
import React, { PropTypes } from 'react';
import { History } from 'react-router';
import { contain } from 'thundercats-react';
import debugFactory from 'debug';
import { getDefaults } from '../utils';
import {
inHTMLData,
uriInSingleQuotedAttr
} from 'xss-filters';
import {
Button,
Col,
Input,
Row,
Well
} from 'react-bootstrap';
import {
isAscii,
isEmail,
isMobilePhone,
isURL
} from 'validator';
const debug = debugFactory('freecc:jobs:newForm');
const checkValidity = [
'position',
'locale',
'description',
'email',
'phone',
'url',
'logo',
'name',
'highlight'
];
function formatValue(value, validator, type = 'string') {
const formated = getDefaults(type);
if (validator && type === 'string') {
formated.valid = validator(value);
}
if (value) {
formated.value = value;
formated.bsStyle = formated.valid ? 'success' : 'error';
}
return formated;
}
function isValidURL(data) {
return isURL(data, { 'require_protocol': true });
}
function isValidPhone(data) {
return isMobilePhone(data, 'en-US');
}
export default contain({
actions: 'jobActions',
store: 'jobsStore',
map({ form = {} }) {
const {
position,
locale,
description,
email,
phone,
url,
logo,
name,
highlight
} = form;
return {
position: formatValue(position, isAscii),
locale: formatValue(locale, isAscii),
description: formatValue(description, isAscii),
email: formatValue(email, isEmail),
phone: formatValue(phone, isValidPhone),
url: formatValue(url, isValidURL),
logo: formatValue(logo, isValidURL),
name: formatValue(name, isAscii),
highlight: formatValue(highlight, null, 'bool')
};
},
subscribeOnWillMount() {
return typeof window !== 'undefined';
}
},
React.createClass({
displayName: 'NewJob',
propTypes: {
jobActions: PropTypes.object,
position: PropTypes.object,
locale: PropTypes.object,
description: PropTypes.object,
email: PropTypes.object,
phone: PropTypes.object,
url: PropTypes.object,
logo: PropTypes.object,
name: PropTypes.object,
highlight: PropTypes.object
},
mixins: [History],
handleSubmit(e) {
e.preventDefault();
const props = this.props;
let valid = true;
checkValidity.forEach((prop) => {
// if value exist, check if it is valid
if (props[prop].value && props[prop].type !== 'boolean') {
valid = valid && !!props[prop].valid;
}
});
if (!valid) {
debug('form not valid');
return;
}
const {
position,
locale,
description,
email,
phone,
url,
logo,
name,
highlight,
jobActions
} = this.props;
// sanitize user output
const jobValues = {
position: inHTMLData(position.value),
location: inHTMLData(locale.value),
description: inHTMLData(description.value),
email: inHTMLData(email.value),
phone: inHTMLData(phone.value),
url: uriInSingleQuotedAttr(url.value),
logo: uriInSingleQuotedAttr(logo.value),
name: inHTMLData(name.value),
highlight: !!highlight.value
};
const job = Object.keys(jobValues).reduce((accu, prop) => {
if (jobValues[prop]) {
accu[prop] = jobValues[prop];
}
return accu;
}, {});
job.postedOn = new Date();
debug('job sanitized', job);
jobActions.saveForm(job);
this.history.pushState(null, '/jobs/new/preview');
},
componentDidMount() {
const { jobActions } = this.props;
jobActions.getSavedForm();
},
handleChange(name, { target: { value } }) {
const { jobActions: { handleForm } } = this.props;
handleForm({ [name]: value });
},
render() {
const {
position,
locale,
description,
email,
phone,
url,
logo,
name,
highlight,
jobActions: { handleForm }
} = this.props;
const { handleChange } = this;
const labelClass = 'col-sm-offset-1 col-sm-2';
const inputClass = 'col-sm-6';
return (
<div>
<Row>
<Col>
<Well className='text-center'>
<h1>Create Your Job Post</h1>
<form
className='form-horizontal'
onSubmit={ this.handleSubmit }>
<div className='spacer'>
<h2>Job Information</h2>
</div>
<Input
bsStyle={ position.bsStyle }
label='Position'
labelClassName={ labelClass }
onChange={ (e) => handleChange('position', e) }
placeholder='Position'
type='text'
value={ position.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ locale.bsStyle }
label='Location'
labelClassName={ labelClass }
onChange={ (e) => handleChange('locale', e) }
placeholder='Location'
type='text'
value={ locale.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ description.bsStyle }
label='Description'
labelClassName={ labelClass }
onChange={ (e) => handleChange('description', e) }
placeholder='Description'
rows='10'
type='textarea'
value={ description.value }
wrapperClassName={ inputClass } />
<div className='divider'>
<h2>Company Information</h2>
</div>
<Input
bsStyle={ name.bsStyle }
label='Company Name'
labelClassName={ labelClass }
onChange={ (e) => handleChange('name', e) }
placeholder='Foo, INC'
type='text'
value={ name.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ email.bsStyle }
label='Email'
labelClassName={ labelClass }
onChange={ (e) => handleChange('email', e) }
placeholder='Email'
type='email'
value={ email.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ phone.bsStyle }
label='Phone'
labelClassName={ labelClass }
onChange={ (e) => handleChange('phone', e) }
placeholder='555-123-1234'
type='tel'
value={ phone.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ url.bsStyle }
label='URL'
labelClassName={ labelClass }
onChange={ (e) => handleChange('url', e) }
placeholder='http://freecatphotoapp.com'
type='url'
value={ url.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ logo.bsStyle }
label='Logo'
labelClassName={ labelClass }
onChange={ (e) => handleChange('logo', e) }
placeholder='http://freecatphotoapp.com/logo.png'
type='url'
value={ logo.value }
wrapperClassName={ inputClass } />
<div className='divider'>
<h2>Make it stand out</h2>
</div>
<Input
checked={ highlight.value }
label='Highlight your ad'
labelClassName={ 'col-sm-offset-1 col-sm-6'}
onChange={
({ target: { checked } }) => handleForm({
highlight: !!checked
})
}
type='checkbox' />
<div className='spacer' />
<Row>
<Col
lg={ 6 }
lgOffset={ 3 }>
<Button
block={ true }
bsSize='large'
bsStyle='primary'
type='submit'>
Preview My Ad
</Button>
</Col>
</Row>
</form>
</Well>
</Col>
</Row>
</div>
);
}
})
);

View File

@ -0,0 +1,14 @@
// import React, { PropTypes } from 'react';
import { contain } from 'thundercats-react';
import ShowJob from './ShowJob.jsx';
export default contain(
{
store: 'JobsStore',
actions: 'JobActions',
map({ form: job = {} }) {
return { job };
}
},
ShowJob
);

View File

@ -1,13 +1,5 @@
import React, { PropTypes } from 'react';
import { contain } from 'thundercats-react';
import { Row, Thumbnail, Panel, Well } from 'react-bootstrap';
import moment from 'moment';
const thumbnailStyle = {
backgroundColor: 'white',
maxHeight: '100px',
maxWidth: '100px'
};
import ShowJob from './ShowJob.jsx';
export default contain(
{
@ -28,61 +20,5 @@ export default contain(
return job.id !== id;
}
},
React.createClass({
displayName: 'ShowJob',
propTypes: {
job: PropTypes.object,
params: PropTypes.object
},
renderHeader({ company, position }) {
return (
<div>
<h4 style={{ display: 'inline-block' }}>{ company }</h4>
<h5
className='pull-right hidden-xs hidden-md'
style={{ display: 'inline-block' }}>
{ position }
</h5>
</div>
);
},
render() {
const { job = {} } = this.props;
const {
logo,
position,
city,
company,
state,
email,
phone,
postedOn,
description
} = job;
return (
<div>
<Row>
<Well>
<Thumbnail
alt={ company + 'company logo' }
src={ logo }
style={ thumbnailStyle } />
<Panel>
Position: { position }
Location: { city }, { state }
<br />
Contact: { email || phone || 'N/A' }
<br />
Posted On: { moment(postedOn).format('MMMM Do, YYYY') }
</Panel>
<p>{ description }</p>
</Well>
</Row>
</div>
);
}
})
ShowJob
);

View File

@ -0,0 +1,67 @@
import React, { PropTypes } from 'react';
import { Row, Thumbnail, Panel, Well } from 'react-bootstrap';
import moment from 'moment';
const thumbnailStyle = {
backgroundColor: 'white',
maxHeight: '100px',
maxWidth: '100px'
};
export default React.createClass({
displayName: 'ShowJob',
propTypes: {
job: PropTypes.object,
params: PropTypes.object
},
renderHeader({ company, position }) {
return (
<div>
<h4 style={{ display: 'inline-block' }}>{ company }</h4>
<h5
className='pull-right hidden-xs hidden-md'
style={{ display: 'inline-block' }}>
{ position }
</h5>
</div>
);
},
render() {
const { job = {} } = this.props;
const {
logo,
position,
city,
company,
state,
email,
phone,
postedOn,
description
} = job;
return (
<div>
<Row>
<Well>
<Thumbnail
alt={ company + 'company logo' }
src={ logo }
style={ thumbnailStyle } />
<Panel>
Position: { position }
Location: { city }, { state }
<br />
Contact: { email || phone || 'N/A' }
<br />
Posted On: { moment(postedOn).format('MMMM Do, YYYY') }
</Panel>
<p>{ description }</p>
</Well>
</Row>
</div>
);
}
});

View File

@ -1,7 +1,9 @@
import { Actions } from 'thundercats';
import store from 'store';
import debugFactory from 'debug';
const debug = debugFactory('freecc:jobs:actions');
const assign = Object.assign;
export default Actions({
setJobs: null,
@ -23,7 +25,7 @@ export default Actions({
// if no job found this will be null which is a op noop
return foundJob ?
Object.assign({}, oldState, { currentJob: foundJob }) :
assign({}, oldState, { currentJob: foundJob }) :
null;
};
},
@ -31,6 +33,31 @@ export default Actions({
getJob: null,
getJobs(params) {
return { params };
},
openModal() {
return { showModal: true };
},
closeModal() {
return { showModal: false };
},
handleForm(value) {
return {
transform(oldState) {
const { form } = oldState;
const newState = assign({}, oldState);
newState.form = assign(
{},
form,
value
);
return newState;
}
};
},
saveForm: null,
getSavedForm: null,
setForm(form) {
return { form };
}
})
.refs({ displayName: 'JobActions' })
@ -56,8 +83,22 @@ export default Actions({
debug('job services experienced an issue', err);
return jobActions.setError({ err });
}
if (job) {
jobActions.setJobs({ currentJob: job });
}
jobActions.setJobs({});
});
});
jobActions.saveForm.subscribe((form) => {
store.set('newJob', form);
});
jobActions.getSavedForm.subscribe(() => {
const job = store.get('newJob');
if (job && !Array.isArray(job) && typeof job === 'object') {
jobActions.setForm(job);
}
});
return jobActions;
});

View File

@ -6,12 +6,25 @@ const {
transformer
} = Store;
export default Store()
export default Store({ showModal: false })
.refs({ displayName: 'JobsStore' })
.init(({ instance: jobsStore, args: [cat] }) => {
const { setJobs, findJob, setError } = cat.getActions('JobActions');
const {
setJobs,
findJob,
setError,
openModal,
closeModal,
handleForm,
setForm
} = cat.getActions('JobActions');
const register = createRegistrar(jobsStore);
register(setter(setJobs));
register(transformer(findJob));
register(setter(setError));
register(setter(openModal));
register(setter(closeModal));
register(setter(setForm));
register(transformer(findJob));
register(handleForm);
});

View File

@ -1,5 +1,7 @@
import Jobs from './components/Jobs.jsx';
import NewJob from './components/NewJob.jsx';
import Show from './components/Show.jsx';
import Preview from './components/Preview.jsx';
/*
* index: /jobs list jobs
@ -11,6 +13,12 @@ export default {
childRoutes: [{
path: '/jobs',
component: Jobs
}, {
path: 'jobs/new',
component: NewJob
}, {
path: 'jobs/new/preview',
component: Preview
}, {
path: 'jobs/:id',
component: Show

View File

@ -0,0 +1,22 @@
const defaults = {
'string': {
value: '',
valid: false,
pristine: true,
type: 'string'
},
bool: {
value: false,
type: 'boolean'
}
};
export function getDefaults(type, value) {
if (!type) {
return defaults['string'];
}
if (value) {
return Object.assign({}, defaults[type], { value });
}
return Object.assign({}, defaults[type]);
}

View File

@ -3,12 +3,8 @@ import Hikes from './Hikes';
export default {
path: '/',
getChildRoutes(locationState, cb) {
setTimeout(() => {
cb(null, [
childRoutes: [
Jobs,
Hikes
]);
}, 0);
}
]
};

View File

@ -1,6 +1,7 @@
{
"name": "job",
"base": "PersistedModel",
"strict": true,
"idInjection": true,
"trackChanges": false,
"properties": {
@ -29,6 +30,9 @@
"state": {
"type": "string"
},
"url": {
"type": "string"
},
"country": {
"type": "string"
},
@ -38,7 +42,7 @@
"description": {
"type": "string"
},
"isApproverd": {
"isApproved": {
"type": "boolean"
},
"isHighlighted": {

View File

@ -34,10 +34,6 @@
"description": {
"type": "string"
},
"originalStoryAuthorEmail": {
"type": "string",
"default": ""
},
"rank": {
"type": "number",
"default": 0

View File

@ -157,6 +157,9 @@
"rand": {
"type": "number",
"index": true
},
"tshirtVote": {
"type": "number"
}
},
"validations": [],

View File

@ -49,6 +49,7 @@ var paths = {
'!public/js/bundle*',
'node_modules/',
'client/',
'seed',
'server/manifests/*.json',
'server/rev-manifest.json'
],

View File

@ -58,10 +58,10 @@
"gulp-webpack": "^1.5.0",
"helmet": "~0.9.0",
"helmet-csp": "^0.2.3",
"history": "^1.9.0",
"jade": "~1.8.0",
"json-loader": "^0.5.2",
"less": "~1.7.5",
"less-middleware": "~2.0.1",
"less": "~2.5.1",
"lodash": "^3.9.3",
"loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password",
"loopback-boot": "2.8.2",
@ -89,7 +89,7 @@
"react": "^0.13.3",
"react-bootstrap": "~0.23.7",
"react-motion": "~0.1.0",
"react-router": "https://github.com/BerkeleyTrue/react-router#freecodecamp",
"react-router": "^1.0.0-rc1",
"react-vimeo": "^0.0.3",
"request": "~2.53.0",
"rev-del": "^1.0.5",
@ -97,12 +97,14 @@
"sanitize-html": "~1.6.1",
"sort-keys": "^1.1.1",
"source-map-support": "^0.3.2",
"store": "https://github.com/berkeleytrue/store.js.git#feature/noop-server",
"thundercats": "^2.1.0",
"thundercats-react": "^0.1.0",
"twit": "~1.1.20",
"uglify-js": "~2.4.15",
"validator": "~3.22.1",
"validator": "^3.22.1",
"webpack": "^1.9.12",
"xss-filters": "^1.2.6",
"yui": "~3.18.1"
},
"devDependencies": {

View File

@ -520,7 +520,7 @@
"challengeSeed":[
"var ourArray = [1,2,3];",
"ourArray[1] = 3;",
"// ourArray[1] now equals [1,3,3].",
"// ourArray now equals [1,3,3].",
"var myArray = [1,2,3];",
"// Only change code below this line.",
"",
@ -643,8 +643,9 @@
"challengeSeed": [
"var ourArray = [\"Stimpson\", \"J\", [\"cat\"]];",
"ourArray.shift();",
"// ourArray now equals [\"happy\", \"J\", [\"cat\"]]",
"// ourArray now equals [\"J\", [\"cat\"]]",
"ourArray.unshift(\"happy\");",
"// ourArray now equals [\"happy\", \"J\", [\"cat\"]]",
"",
"var myArray = [\"John\", 23, [\"dog\", 3]];",
"myArray.shift();",

View File

@ -1142,7 +1142,8 @@
"assert($(\"a\").text().match(/cat\\sphotos/gi), 'Your <code>a</code> element should have the anchor text of \"cat photos\"')",
"assert($(\"p\") && $(\"p\").length > 2, 'Create a new <code>p</code> element around your <code>a</code> element.')",
"assert($(\"a[href=\\\"http://www.freecatphotoapp.com\\\"]\").parent().is(\"p\"), 'Your <code>a</code> element should be nested within your new <code>p</code> element.')",
"assert($(\"p\").text().match(/View\\smore/gi), 'Your <code>p</code> element should have the text \"View more\".')",
"assert($(\"p\").text().match(/^View\\smore\\s/gi), 'Your <code>p</code> element should have the text \"View more \" (with a space after it).')",
"assert(!$(\"a\").text().match(/View\\smore/gi), 'Your <code>a</code> element should <em>not</em> have the text \"View more\".')",
"assert(editor.match(/<\\/p>/g) && editor.match(/<p/g) && editor.match(/<\\/p>/g).length === editor.match(/<p/g).length, 'Make sure each of your <code>p</code> elements has a closing tag.')",
"assert(editor.match(/<\\/a>/g) && editor.match(/<a/g) && editor.match(/<\\/a>/g).length === editor.match(/<a/g).length, 'Make sure each of your <code>a</code> elements has a closing tag.')"
],

View File

@ -61,6 +61,7 @@
"diff([1, 2, 3, 5], [1, 2, 3, 4, 5]);"
],
"tests": [
<<<<<<< HEAD
"assert(typeof(diff([1, 2, 3, 5], [1, 2, 3, 4, 5])) === \"object\", 'message: <code>diff()</code> should return an array.');",
"assert.deepEqual(diff([\"diorite\", \"andesite\", \"grass\", \"dirt\", \"pink wool\", \"dead shrub\"], [\"diorite\", \"andesite\", \"grass\", \"dirt\", \"dead shrub\"]), [\"pink wool\"], 'message: <code>[\"diorite\", \"andesite\", \"grass\", \"dirt\", \"pink wool\", \"dead shrub\"], [\"diorite\", \"andesite\", \"grass\", \"dirt\", \"dead shrub\"]</code> should return <code>[\"pink wool\"]</code>.');",
"assert.includeMembers(diff([\"andesite\", \"grass\", \"dirt\", \"pink wool\", \"dead shrub\"], [\"diorite\", \"andesite\", \"grass\", \"dirt\", \"dead shrub\"]), [\"diorite\", \"pink wool\"], 'message: <code>[\"andesite\", \"grass\", \"dirt\", \"pink wool\", \"dead shrub\"], [\"diorite\", \"andesite\", \"grass\", \"dirt\", \"dead shrub\"]</code> should return <code>[\"diorite\", \"pink wool\"]</code>.');",
@ -68,6 +69,15 @@
"assert.deepEqual(diff([1, 2, 3, 5], [1, 2, 3, 4, 5]), [4], 'message: <code>[1, 2, 3, 5], [1, 2, 3, 4, 5]</code> should return <code>[4]</code>.');",
"assert.includeMembers(diff([1, \"calf\", 3, \"piglet\"], [1, \"calf\", 3, 4]), [\"piglet\", 4], 'message: <code>[1, \"calf\", 3, \"piglet\"], [1, \"calf\", 3, 4]</code> should return <code>[\"piglet\", 4]</code>.');",
"assert.deepEqual(diff([], [\"snuffleupagus\", \"cookie monster\", \"elmo\"]), [\"snuffleupagus\", \"cookie monster\", \"elmo\"], 'message: <code>[], [\"snuffleupagus\", \"cookie monster\", \"elmo\"]</code> should return <code>[\"snuffleupagus\", \"cookie monster\", \"elmo\"]</code>.');"
=======
"assert(typeof(diff([1, 2, 3, 5], [1, 2, 3, 4, 5])) === \"object\", 'The result should be an array.');",
"assert.deepEqual(diff(['diorite', 'andesite', 'grass', 'dirt', 'pink wool', 'dead shrub'], ['diorite', 'andesite', 'grass', 'dirt', 'dead shrub']), ['pink wool'], 'arrays with only one difference');",
"assert.includeMembers(diff(['andesite', 'grass', 'dirt', 'pink wool', 'dead shrub'], ['diorite', 'andesite', 'grass', 'dirt', 'dead shrub']), ['diorite', 'pink wool'], 'arrays with more than one difference');",
"assert.deepEqual(diff(['andesite', 'grass', 'dirt', 'dead shrub'], ['andesite', 'grass', 'dirt', 'dead shrub']), [], 'arrays with no difference');",
"assert.deepEqual(diff([1, 2, 3, 5], [1, 2, 3, 4, 5]), [4], 'arrays with numbers');",
"assert.includeMembers(diff([1, 'calf', 3, 'piglet'], [1, 'calf', 3, 4]), ['piglet', 4], 'arrays with numbers and strings');",
"assert.deepEqual(diff([], ['snuffleupagus', 'cookie monster', 'elmo']), ['snuffleupagus', 'cookie monster', 'elmo'], 'empty array');"
>>>>>>> staging
],
"MDNlinks": [
"Comparison Operators",
@ -93,11 +103,19 @@
"id": "a7f4d8f2483413a6ce226cac",
"title": "Roman Numeral Converter",
"tests": [
<<<<<<< HEAD
"assert.deepEqual(convert(12), \"XII\", 'message: <code>convert(12)</code> should return \"XII\".');",
"assert.deepEqual(convert(5), \"V\", 'message: <code>convert(5)</code> should return \"V\".');",
"assert.deepEqual(convert(9), \"IX\", 'message: <code>convert(9)</code> should return \"IX\".');",
"assert.deepEqual(convert(29), \"XXIX\", 'message: <code>convert(29)</code> should return \"XXIX\".');",
"assert.deepEqual(convert(16), \"XVI\", 'message: <code>convert(16)</code> should return \"XVI\".');"
=======
"assert.deepEqual(convert(12), \"XII\", '<code>convert(12)</code> should return \"XII\"');",
"assert.deepEqual(convert(5), \"V\", '<code>convert(5)</code> should return \"V\"');",
"assert.deepEqual(convert(9), \"IX\", '<code>convert(9)</code> should return \"IX\"');",
"assert.deepEqual(convert(29), \"XXIX\", '<code>convert(29)</code> should return \"XXIX\"');",
"assert.deepEqual(convert(16), \"XVI\", '<code>convert(16)</code> should return \"XVI\"');"
>>>>>>> staging
],
"difficulty": "2.02",
"description": [
@ -150,9 +168,16 @@
"where([{ first: \"Romeo\", last: \"Montague\" }, { first: \"Mercutio\", last: null }, { first: \"Tybalt\", last: \"Capulet\" }], { last: \"Capulet\" });"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(where([{ first: \"Romeo\", last: \"Montague\" }, { first: \"Mercutio\", last: null }, { first: \"Tybalt\", last: \"Capulet\" }], { last: \"Capulet\" }), [{ first: \"Tybalt\", last: \"Capulet\" }], 'message: <code>where()</code> should return an array of objects.');",
"assert.deepEqual(where([{ \"a\": 1 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2 }], { \"a\": 1 }), [{ \"a\": 1 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2 }], 'message: <code>where([{ \"a\": 1 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2 }], { \"a\": 1 })</code> should return <code>[{ \"a\": 1 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2 }]</code>.');",
"assert.deepEqual(where([{ \"a\": 1, \"b\": 2 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2, \"c\": 2 }], { \"a\": 1, \"b\": 2 }), [{ \"a\": 1, \"b\": 2 }, { \"a\": 1, \"b\": 2, \"c\": 2 }], 'message: <code>where([{ \"a\": 1, \"b\": 2 }, { \"a\": 1 }, { \"a\": 1, \"b\": 2, \"c\": 2 }], { \"a\": 1, \"b\": 2 })</code> should return <code>[{ \"a\": 1, \"b\": 2 }, { \"a\": 1, \"b\": 2, \"c\": 2 }]</code>.');"
=======
"assert.deepEqual(where([{ first: 'Romeo', last: 'Montague' }, { first: 'Mercutio', last: null }, { first: 'Tybalt', last: 'Capulet' }], { last: 'Capulet' }), [{ first: 'Tybalt', last: 'Capulet' }], 'should return an array of objects');",
"assert.deepEqual(where([{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }], 'should return with multiples');",
"assert.deepEqual(where([{ 'a': 1, 'b': 2 }, { 'a': 1 }, { 'a': 1, 'b': 2, 'c': 2 }], { 'a': 1, 'b': 2 }), [{ 'a': 1, 'b': 2 }, { 'a': 1, 'b': 2, 'c': 2 }], 'should return two objects in array');",
"assert.deepEqual(where([{ 'a': 5 }, { 'b': 10 }, { 'a': 5, 'b': 10 }], { 'a': 5, 'b': 10 }), [{ 'a': 5, 'b': 10 }], 'should return a single object in array');"
>>>>>>> staging
],
"MDNlinks": [
"Global Object",
@ -176,11 +201,19 @@
"id": "a0b5010f579e69b815e7c5d6",
"title": "Search and Replace",
"tests": [
<<<<<<< HEAD
"assert.deepEqual(myReplace(\"Let us go to the store\", \"store\", \"mall\"), \"Let us go to the mall\", 'message: <code>myReplace(\"Let us go to the store\", \"store\", \"mall\")</code> should return \"Let us go to the mall\".');",
"assert.deepEqual(myReplace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\"), \"He is Sitting on the couch\", 'message: <code>myReplace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\")</code> should return \"He is Sitting on the couch\".');",
"assert.deepEqual(myReplace(\"This has a spellngi error\", \"spellngi\", \"spelling\"), \"This has a spelling error\", 'message: <code>myReplace(\"This has a spellngi error\", \"spellingi\", \"spelling\")</code> should return \"This has a spelling error\".');",
"assert.deepEqual(myReplace(\"His name is Tom\", \"Tom\", \"john\"), \"His name is John\", 'message: <code>myReplace(\"His name is Tom\", \"Tom\", \"john\")</code> should return \"His name is John\".');",
"assert.deepEqual(myReplace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\"), \"Let us get back to more Bonfires\", 'message: <code>myReplace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\")</code> should return \"Let us get back to more Bonfires\".');"
=======
"assert.deepEqual(replace(\"Let us go to the store\", \"store\", \"mall\"), \"Let us go to the mall\", '<code>replace(\"Let us go to the store\", \"store\", \"mall\")</code> should return \"Let us go to the mall\"');",
"assert.deepEqual(replace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\"), \"He is Sitting on the couch\", '<code>replace(\"He is Sleeping on the couch\", \"Sleeping\", \"sitting\")</code> should return \"He is Sitting on the couch\"');",
"assert.deepEqual(replace(\"This has a spellngi error\", \"spellngi\", \"spelling\"), \"This has a spelling error\", '<code>replace(\"This has a spellngi error\", \"spellingi\", \"spelling\")</code> should return \"This has a spelling error\"');",
"assert.deepEqual(replace(\"His name is Tom\", \"Tom\", \"john\"), \"His name is John\", '<code>replace(\"His name is Tom\", \"Tom\", \"john\")</code> should return \"His name is John\"');",
"assert.deepEqual(replace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\"), \"Let us get back to more Bonfires\", '<code>replace(\"Let us get back to more Coding\", \"Coding\", \"bonfires\")</code> should return \"Let us get back to more Bonfires\"');"
>>>>>>> staging
],
"difficulty": "2.035",
"description": [
@ -220,11 +253,19 @@
"id": "aa7697ea2477d1316795783b",
"title": "Pig Latin",
"tests": [
<<<<<<< HEAD
"assert.deepEqual(translate(\"california\"), \"aliforniacay\", 'message: <code>translate(\"california\")</code> should return \"aliforniacay\".');",
"assert.deepEqual(translate(\"paragraphs\"), \"aragraphspay\", 'message: <code>translate(\"paragraphs\")</code> should return \"aragraphspay\".');",
"assert.deepEqual(translate(\"glove\"), \"oveglay\", 'message: <code>translate(\"glove\")</code> should return \"oveglay\".');",
"assert.deepEqual(translate(\"algorithm\"), \"algorithmway\", 'message: <code>translate(\"algorithm\")</code> should return \"algorithmway\".');",
"assert.deepEqual(translate(\"eight\"), \"eightway\", 'message: <code>translate(\"eight\")</code> should return \"eightway\".');"
=======
"assert.deepEqual(translate(\"california\"), \"aliforniacay\", '<code>translate(\"california\")</code> should return \"aliforniacay\"');",
"assert.deepEqual(translate(\"paragraphs\"), \"aragraphspay\", '<code>translate(\"paragraphs\")</code> should return \"aragraphspay\"');",
"assert.deepEqual(translate(\"glove\"), \"oveglay\", '<code>translate(\"glove\")</code> should return \"oveglay\"');",
"assert.deepEqual(translate(\"algorithm\"), \"algorithmway\", '<code>translate(\"algorithm\")</code> should return \"algorithmway\"');",
"assert.deepEqual(translate(\"eight\"), \"eightway\", '<code>translate(\"eight\")</code> should return \"eightway\"');"
>>>>>>> staging
],
"difficulty": "2.04",
"description": [
@ -318,10 +359,17 @@
"fearNotLetter(\"abce\");"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(fearNotLetter(\"abce\"), \"d\", 'message: <code>fearNotLetter(\"abce\")</code> should return \"d\".');",
"assert.deepEqual(fearNotLetter(\"abcdefghjklmno\"), \"i\", 'message: <code>fearNotLetter(\"abcdefghjklmno\")</code> should return \"i\".');",
"assert.isUndefined(fearNotLetter(\"bcd\"), 'message: <code>fearNotLetter(\"bcd\")</code> should return undefined.');",
"assert.isUndefined(fearNotLetter(\"yz\"), 'message: <code>fearNotLetter(\"yz\")</code> should return undefined.');"
=======
"assert.deepEqual(fearNotLetter(\"abce\"), \"d\", '<code>fearNotLetter(\"abce\")</code> should return d');",
"assert.deepEqual(fearNotLetter(\"abcdefghjklmno\"), \"i\", '<code>fearNotLetter(\"abcdefghjklmno\")</code> should return i');",
"assert.isUndefined(fearNotLetter(\"bcd\"), '<code>fearNotLetter(\"bcd\")</code> should return undefined');",
"assert.isUndefined(fearNotLetter(\"yz\"), '<code>fearNotLetter(\"yz\")</code> should return undefined');"
>>>>>>> staging
],
"MDNlinks": [
"String.charCodeAt()",
@ -524,12 +572,21 @@
"sumFibs(4);"
],
"tests": [
<<<<<<< HEAD
"assert(typeof(sumFibs(1)) === \"number\", 'message: <code>sumFibs()</code> should return a number.');",
"assert.deepEqual(sumFibs(1000), 1785, 'message: <code>sumFibs(1000)</code> should return 1785.');",
"assert.deepEqual(sumFibs(4000000), 4613732, 'message: <code>sumFibs(4000000)</code> should return 4613732.');",
"assert.deepEqual(sumFibs(4), 5, 'message: <code>sumFibs(4)</code> should return 5.');",
"assert.deepEqual(sumFibs(75024), 60696, 'message: <code>sumFibs(75024)</code> should return 60696.');",
"assert.deepEqual(sumFibs(75025), 135721, 'message: <code>sumFibs(75025)</code> should return 135721.');"
=======
"assert.deepEqual(typeof(sumFibs(1)), \"number\", \"The result should be a number\");",
"assert.deepEqual(sumFibs(1000), 1785, '<code>sumFibs(1000)</code> should return 1785');",
"assert.deepEqual(sumFibs(4000000), 4613732, '<code>sumFibs(4000000)</code> should return 4613732');",
"assert.deepEqual(sumFibs(4), 5, '<code>sumFibs(4)</code> should return 5');",
"assert.deepEqual(sumFibs(75024), 60696, '<code>sumFibs(75024)</code> should return 60696');",
"assert.deepEqual(sumFibs(75025), 135721, '<code>sumFibs(75025)</code> should return 135721');"
>>>>>>> staging
],
"MDNlinks": [
"Remainder"
@ -565,9 +622,15 @@
"sumPrimes(10);"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(typeof(sumPrimes(10)), \"number\", 'message: <code>sumPrimes()</code> should return a number.');",
"assert.deepEqual(sumPrimes(10), 17, 'message: <code>sumPrimes(10)</code> should return 17.');",
"assert.deepEqual(sumPrimes(977), 73156, 'message: <code>sumPrimes(977)</code> should return 73156.');"
=======
"assert.deepEqual(typeof(sumPrimes(10)), \"number\", \"The result should be a number\");",
"assert.deepEqual(sumPrimes(10), 17, '<code>sumPrimes(10)</code> should return 17');",
"assert.deepEqual(sumPrimes(977), 73156, '<code>sumPrimes(977)</code> should return 73156');"
>>>>>>> staging
],
"MDNlinks": [
"For Loops",
@ -605,10 +668,17 @@
"smallestCommons([1,5]);"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(typeof(smallestCommons([1, 5])), \"number\", 'message: <code>smallestCommons()</code> should return a number.');",
"assert.deepEqual(smallestCommons([1, 5]), 60, 'message: <code>smallestCommons([1, 5])</code> should return 60.');",
"assert.deepEqual(smallestCommons([5, 1]), 60, 'message: <code>smallestCommons([5, 1])</code> should return 60.');",
"assert.deepEqual(smallestCommons([1, 13]), 360360, 'message: <code>smallestCommons([1, 13])</code> should return 360360.');"
=======
"assert.deepEqual(typeof(smallestCommons([1, 5])), \"number\", \"The result should be a number\");",
"assert.deepEqual(smallestCommons([1, 5]), 60, '<code>smallestCommons([1, 5])</code> should return 60');",
"assert.deepEqual(smallestCommons([5, 1]), 60, '<code>smallestCommons([5, 1])</code> should return 60');",
"assert.deepEqual(smallestCommons([1, 13]), 360360, '<code>smallestCommons([1, 13])</code> should return 360360');"
>>>>>>> staging
],
"MDNlinks": [
"Smallest Common Multiple"
@ -679,10 +749,17 @@
"drop([1, 2, 3], function(n) {return n < 3; });"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n>= 3;}), [3, 4], 'message: <code>drop([1, 2, 3, 4], function(n) {return n>= 3;})</code> should return <code>[3, 4]</code>.');",
"assert.deepEqual(drop([1, 2, 3], function(n) {return n > 0; }), [1, 2, 3], 'message: <code>drop([1, 2, 3], function(n) {return n > 0; })</code> should return <code>[1, 2, 3]</code>.');",
"assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n > 5;}), [], 'message: <code>drop([1, 2, 3, 4], function(n) {return n > 5;})</code> should return <code>[]</code>.');",
"assert.deepEqual(drop([1, 2, 3, 7, 4], function(n) {return n > 3}), [7, 4], 'message: <code>drop([1, 2, 3, 7, 4], function(n) {return n>= 3})</code> should return <code>[7, 4]</code>.');"
=======
"assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n>= 3;}), [3, 4], '<code>drop([1, 2, 3, 4], function(n) {return n>= 3;})</code> should return [3, 4]');",
"assert.deepEqual(drop([1, 2, 3], function(n) {return n > 0; }), [1, 2, 3], '<code>drop([1, 2, 3], function(n) {return n > 0; })</code> should return [1, 2, 3]');",
"assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n > 5;}), [], '<code>drop([1, 2, 3, 4], function(n) {return n > 5;})</code> should return []');",
"assert.deepEqual(drop([1, 2, 3, 7, 4], function(n) {return n > 3}), [7, 4], '<code>drop([1, 2, 3, 7, 4], function(n) {return n>= 3})</code> should return [7, 4]');"
>>>>>>> staging
],
"MDNlinks": [
"Arguments object",
@ -756,8 +833,13 @@
"binaryAgent(\"01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111\");"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(binaryAgent('01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111'), \"Aren't bonfires fun!?\", 'message: <code>binaryAgent(\"01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111\")</code> should return \"Aren&#39;t bonfires fun!?\"');",
"assert.deepEqual(binaryAgent(\"01001001 00100000 01101100 01101111 01110110 01100101 00100000 01000110 01110010 01100101 01100101 01000011 01101111 01100100 01100101 01000011 01100001 01101101 01110000 00100001\"), \"I love FreeCodeCamp!\", 'message: <code>binaryAgent(\"01001001 00100000 01101100 01101111 01110110 01100101 00100000 01000110 01110010 01100101 01100101 01000011 01101111 01100100 01100101 01000011 01100001 01101101 01110000 00100001\"</code> should return \"I love FreeCodeCamp!\"');"
=======
"assert.deepEqual(binaryAgent('01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111'), \"Aren't bonfires fun!?\", \"<code>binaryAgent()</code> should return Aren't bonfires fun!?\");",
"assert.deepEqual(binaryAgent('01001001 00100000 01101100 01101111 01110110 01100101 00100000 01000110 01110010 01100101 01100101 01000011 01101111 01100100 01100101 01000011 01100001 01101101 01110000 00100001'), \"I love FreeCodeCamp!\", '<code>binaryAgent()</code> should return \"I love FreeCodeCamp!\"');"
>>>>>>> staging
],
"MDNlinks": [
"String.charCodeAt()",
@ -837,11 +919,19 @@
"add(2,3);"
],
"tests": [
<<<<<<< HEAD
"assert.deepEqual(add(2, 3), 5, 'message: <code>add(2, 3)</code> should return 5.');",
"assert.deepEqual(add(2)(3), 5, 'message: <code>add(2)(3)</code> should return 5.');",
"assert.isUndefined(add(\"http://bit.ly/IqT6zt\"), 'message: <code>add(\"http://bit.ly/IqT6zt\")</code> should return undefined.');",
"assert.isUndefined(add(2, \"3\"), 'message: <code>add(2, \"3\")</code> should return undefined.');",
"assert.isUndefined(add(2)([3]), 'message: <code>add(2)([3])</code> should return undefined.');"
=======
"assert.deepEqual(add(2, 3), 5, '<code>add(2, 3)</code> should return 5');",
"assert.deepEqual(add(2)(3), 5, '<code>add(2)(3)</code> should return 5');",
"assert.isUndefined(add(\"http://bit.ly/IqT6zt\"), '<code>add(\"http://bit.ly/IqT6zt\")</code> should return undefined');",
"assert.isUndefined(add(2, \"3\"), '<code>add(2, \"3\")</code> should return undefined');",
"assert.isUndefined(add(2)([3]), '<code>add(2)([3])</code> should return undefined');"
>>>>>>> staging
],
"MDNlinks": [
"Global Function Object",

View File

@ -37,7 +37,7 @@
},
{
"id": "bd7158d8c442eddfaeb5bd19",
"title": "Wikipedia Viewer",
"title": "Build a Wikipedia Viewer",
"difficulty": 1.03,
"challengeSeed": ["126415131"],
"description": [

View File

@ -791,6 +791,7 @@
"description": [
"You can also target all the even-numbered elements.",
"Here's how you would target all the odd-numbered elements with class <code>target</code> and give them classes: <code>$(\".target:odd\").addClass(\"animated shake\");</code>",
"Note that jQuery is zero-indexed, meaning that, counter-intuitively, <code>:odd</code> selects the second element, fourth element, and so on.",
"Try selecting all the even-numbered elements - that is, what your browser will consider even-numbered elements - and giving them the classes of <code>animated</code> and <code>shake</code>."
],
"tests": [

View File

@ -1,7 +1,7 @@
import React from 'react';
import Router from 'react-router';
import { RoutingContext } from 'react-router';
import Fetchr from 'fetchr';
import Location from 'react-router/lib/Location';
import { createLocation } from 'history';
import debugFactory from 'debug';
import { app$ } from '../../common/app';
import { RenderToString } from 'thundercats-react';
@ -30,25 +30,25 @@ export default function reactSubRouter(app) {
function serveReactApp(req, res, next) {
const services = new Fetchr({ req });
const location = new Location(req.path, req.query);
const location = createLocation(req.path);
// returns a router wrapped app
app$(location)
app$({ location })
// if react-router does not find a route send down the chain
.filter(function({ initialState }) {
if (!initialState) {
.filter(function({ props}) {
if (!props) {
debug('react tried to find %s but got 404', location.pathname);
return next();
}
return !!initialState;
return !!props;
})
.flatMap(function({ initialState, AppCat }) {
.flatMap(function({ props, AppCat }) {
// call thundercats renderToString
// prefetches data and sets up it up for current state
debug('rendering to string');
return RenderToString(
AppCat(null, services),
React.createElement(Router, initialState)
React.createElement(RoutingContext, props)
);
})
// makes sure we only get one onNext and closes subscription

View File

@ -471,6 +471,7 @@ module.exports = function(app) {
}
function challengeMap({ user = {} }, res, next) {
let lastCompleted;
const daysRunning = moment().diff(new Date('10/15/2014'), 'days');
// if user
@ -513,7 +514,13 @@ module.exports = function(app) {
})
.filter(({ name }) => name !== 'Hikes')
// turn stream of blocks into a stream of an array
.toArray();
.toArray()
.doOnNext((blocks) => {
const lastCompletedBlock = _.findLast(blocks, (block) => {
return block.completed === 100;
});
lastCompleted = lastCompletedBlock.name;
});
Observable.combineLatest(
camperCount$,
@ -526,6 +533,7 @@ module.exports = function(app) {
blocks,
daysRunning,
camperCount,
lastCompleted,
title: "A map of all Free Code Camp's Challenges"
});
},

View File

@ -170,7 +170,6 @@ module.exports = function(app) {
title: story.headline,
link: story.link,
originalStoryLink: dashedName,
originalStoryAuthorEmail: story.author.email || '',
author: story.author,
rank: story.upVotes.length,
upVotes: story.upVotes,
@ -373,13 +372,11 @@ module.exports = function(app) {
author: {
picture: req.user.picture,
userId: req.user.id,
username: req.user.username,
email: req.user.email
username: req.user.username
},
image: data.image,
storyLink: storyLink,
metaDescription: data.storyMetaDescription,
originalStoryAuthorEmail: req.user.email
metaDescription: data.storyMetaDescription
});
return saveInstance(newStory);
});

View File

@ -76,6 +76,8 @@ module.exports = function(app) {
);
router.get('/account/unlink/:provider', getOauthUnlink);
router.get('/account', getAccount);
router.get('/vote1', vote1);
router.get('/vote2', vote2);
// Ensure this is the last route!
router.get('/:username', returnUser);
@ -332,4 +334,36 @@ module.exports = function(app) {
});
});
}
function vote1(req, res) {
if (req.user) {
req.user.tshirtVote = 1;
req.user.save(function (err) {
if (err) {
return next(err);
}
req.flash('success', {msg: 'Thanks for voting!'});
res.redirect('/map');
});
} else {
req.flash('error', {msg: 'You must be signed in to vote.'});
res.redirect('/map');
}
}
function vote2(req, res) {
if (req.user) {
req.user.tshirtVote = 2;
req.user.save(function (err) {
if (err) {
return next(err);
}
req.flash('success', {msg: 'Thanks for voting!'});
res.redirect('/map');
});
} else {
req.flash('error', {msg: 'You must be signed in to vote.'});
res.redirect('/map');
}
}
};

View File

@ -26,13 +26,12 @@ export default function addReturnToUrl() {
return function(req, res, next) {
// Remember original destination before login.
var path = req.path.split('/')[1];
var subPath = req.path.split('/')[2];
if (
req.method !== 'GET' ||
pathsOfNoReturnRegex.test(path) ||
!whiteListRegex.test(path) ||
(/news/i).test(path) && (/hot/i).test(subPath)
(/news/i).test(path) && (/hot/i).test(req.path)
) {
return next();
}

View File

@ -17,7 +17,36 @@ block content
| since we launched &thinsp;
span.text-primary #{daysRunning} &thinsp;
| days ago.
a.btn.btn-lg.signup-btn.btn-block(href="https://www.facebook.com/sharer/sharer.php?u=http://freecodecamp.com" target='_blank') Share our open source community on Facebook and help us grow.
.spacer
if (user && !user.tshirtVote && user.progressTimestamps.length > 5)
h3.text-center Vote for the T-shirt design you like the most.
h4.text-center We'll announce the winning design during our Summit on Saturday at Noon EST on&thinsp;
a(href='https://twitch.tv/freecodecamp' target='_blank') Twitch.tv
| &thinsp;and it will become our community's first official t-shirt (in women's and men's sizes).
.row
.col-xs-6
a(href="http://i.imgur.com/LlXGa5y.png" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/LlXGa5y.png' alt="t-shirt option 1 women's")
.col-xs-6
a(href="http://i.imgur.com/aefwnnv.png" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/aefwnnv.png' alt="t-shirt option 2 women's")
.button-spacer
.row
.col-xs-6
a(href="http://i.imgur.com/aYH0aqf.png" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/aYH0aqf.png' alt="t-shirt option 1 men's")
.col-xs-6
a(href="http://i.imgur.com/v9KlV4g.png" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/v9KlV4g.png' alt="t-shirt option 2 men's")
.button-spacer
.row
.col-xs-6
h3.text-center "Minified JavaScript Logo"
a.button.btn.btn-block.btn-primary(href='/vote1') Vote for this Design
.col-xs-6
h3.text-center "Function Call Logo"
a.button.btn.btn-block.btn-primary(href='/vote2') Vote for this design
.spacer
.row
.col-xs-12.col-sm-8.col-sm-offset-2
h3 800 Hours of Practice:
@ -102,7 +131,16 @@ block content
span= challenge.title
span.sr-only= " Incomplete"
//#announcementModal.modal(tabindex='-1')
if (challengeBlock.completed === 100)
.button-spacer
.row
.col-xs-12.col-sm-8.col-md-6.col-sm-offset-3.col-md-offset-2.hidden
a.btn.btn-lg.btn-block.signup-btn.map-challenge-block-share Section complete. Share your Portfolio with your friends.
.hidden(id="#{challengeBlock.name}")
script.
var username = !{JSON.stringify(user && user.username || '')};
var lastCompleted = !{JSON.stringify(lastCompleted || false)}
// #announcementModal.modal(tabindex='-1')
// .modal-dialog.animated.fadeInUp.fast-animation
// .modal-content
// .modal-header.challenge-list-header Add us to your LinkedIn profile

View File

@ -55,9 +55,6 @@ block content
label.btn.btn-success#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
label.btn.btn-success#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
label.btn.btn-success#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug

View File

@ -94,10 +94,6 @@ block content
.row
if (user)
#submit-challenge.animated.fadeIn.btn.btn-lg.btn-primary.btn-block Submit and go to my next challenge (ctrl + enter)
if (user.progressTimestamps.length > 2)
a.btn.btn-lg.btn-block.btn-twitter(target="_blank", href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{dashedName}&hashtags=LearnToCode, JavaScript")
i.fa.fa-twitter &thinsp;
= phrase
else
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge
include ../partials/challenge-modals

View File

@ -69,12 +69,6 @@ block content
a.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) I've completed this challenge (ctrl + enter)
script.
$('#complete-courseware-editorless-dialog').bind('keypress', modalControlEnterHandler);
if (user.progressTimestamps.length > 2)
.button-spacer
a.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank")
i.fa.fa-twitter &thinsp;
= phrase
else
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) I've completed this challenge (ctrl + enter)
script.

View File

@ -27,9 +27,6 @@ block content
.btn.btn-success.btn-big#trigger-help-modal
i.fa.fa-medkit
| &nbsp; Help
.btn.btn-success.btn-big#trigger-pair-modal
i.fa.fa-user-plus
| &nbsp; Pair
.btn.btn-success.btn-big#trigger-issue-modal
i.fa.fa-bug
| &nbsp; Bug

View File

@ -1,17 +1,3 @@
#pair-modal.modal(tabindex='-1')
.modal-dialog.animated.fadeIn.fast-animation
.modal-content
.modal-header.challenge-list-header Ready to pair program?
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body.text-center
h3 This will take you to our pair programming room where you can request a pair.
h3 You'll need &thinsp;
a(href='//github.com/FreeCodeCamp/freecodecamp/wiki/How-to-install-Screenhero' target='_blank') Screenhero
| .
h3 Other campers may then message you about pair programming.
a.btn.btn-lg.btn-primary.btn-block.close-modal(href='https://gitter.im/FreeCodeCamp/LetsPair', target='_blank') Take me to the pair programming room
a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
#issue-modal.modal(tabindex='-1')
.modal-dialog.animated.fadeIn.fast-animation
.modal-content

View File

@ -65,7 +65,7 @@ block content
.big-spacer
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/EZHzKCV.gif' alt='A gif showing you how to click the link below to go to our chat room and click the \"sign in with GitHub\" button. Then you can click into the text input field and type a message to your fellow campers.')
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/Uuc2Ked.gif' alt='A gif showing you how to click the link below to go to our chat room and click the \"sign in with GitHub\" button. Then you can click into the text input field and type a message to your fellow campers.')
.caption
p.large-p Try this:&thinsp;
| Now that you have a GitHub account, you can&thinsp;
@ -99,12 +99,6 @@ block content
a(href='https://gitter.im/apps' target='_blank') download the chat room app
| &thinsp;to your computer or phone.
.big-spacer
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/DoOqkNW.gif' alt='A gif showing how you can click the "Wiki" button in your upper-right corner to access the wiki.')
.caption
p.large-p Try this: Click the "Wiki" button in your upper right hand corner. Our community has contributed lots of useful information to this searchable wiki.
.big-spacer
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/FkEzbto.gif' alt='A gif showing how you can click your profile image in your upper right hand corner to access the account page and connect GitHub.')
@ -128,25 +122,15 @@ block content
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/Elb3dfj.jpg' alt='A picture of some of our campers meeting in a local cafe. 3 men and 3 women are sitting around a table with laptops out, and are smiling and coding.')
.caption
p.large-p Our Campsites help you code with campers in your city. You can coordinate study groups or attend local coding events together.
p.large-p Our Campsites help you code with campers in your city. You can discuss coding and attend local Coffee-n-Code events.
.big-spacer
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/EZHzKCV.gif' alt="A gif showing how you can click the link below, find your city on the list of Campsites, then click on the Facebook link for your city and join your city's Facebook group.")
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/tYf8jrI.gif' alt="A gif showing how you can click the link below, find your city on the list of Campsites, then click on the Facebook link for your city and join your city's Facebook group.")
.caption
p.large-p Try this:&thinsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/List-of-Free-Code-Camp-city-based-Campsites' target='_blank') Find your city on this list
| . Click the "Join group" button to join your city's Facebook group. If your city isn't on this list,&thinsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/How-to-create-a-Campsite-for-your-city' target='_blank') follow these directions to create a Facebook group for your city
| .
.big-spacer
.thumbnail
img.gif-block.img-center.img-responsive(src='http://i.imgur.com/3AgvJQg.gif' alt="A gif showing how click the link below, find your city, and click the \"Gitter\" button to join your city's Gitter chat room")
.caption
p.large-p Try this:&thinsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/List-of-Free-Code-Camp-city-based-Campsites' target='_blank') Go back to our list of Campsites&thinsp;
| and click "Gitter" to join your city's chat room.
| , then click it. Click the "Join group" button to join your city's Campsite.
.big-spacer
.thumbnail

View File

@ -1,7 +1,6 @@
script.
var storyId = !{JSON.stringify(id)};
var originalStoryLink = !{JSON.stringify(originalStoryLink)};
var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)};
var upVotes = !{JSON.stringify(upVotes)};
var image = !{JSON.stringify(image)};
var hasUserVoted = !{JSON.stringify(hasUserVoted)};