diff --git a/client/index.js b/client/index.js
index 79c5c541c1..44bdb0a82c 100644
--- a/client/index.js
+++ b/client/index.js
@@ -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
);
})
diff --git a/client/main.js b/client/main.js
index 6e6ce8c696..c1578c1d6f 100644
--- a/client/main.js
+++ b/client/main.js
@@ -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){
diff --git a/common/app/app-stream.jsx b/common/app/app-stream.jsx
index 25ae2a6300..82d5568c09 100644
--- a/common/app/app-stream.jsx
+++ b/common/app/app-stream.jsx
@@ -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 };
});
}
diff --git a/common/app/routes/Jobs/components/CreateJobModal.jsx b/common/app/routes/Jobs/components/CreateJobModal.jsx
new file mode 100644
index 0000000000..446ed957d6
--- /dev/null
+++ b/common/app/routes/Jobs/components/CreateJobModal.jsx
@@ -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 (
+ We post jobs specifically target to our junior developers.Welcome to Free Code Camp's board
+
{ description }
-{ description }
+[1, 2, 3], 2, [3]
should return [3]
.');",
- "assert.deepEqual(slasher([1, 2, 3], 0), [1, 2, 3], '[1, 2, 3], 0
should return [1, 2, 3]
.');",
- "assert.deepEqual(slasher([1, 2, 3], 9), [], '[1, 2, 3], 9
should return []
.');"
+ "assert.deepEqual(slasher([1, 2, 3], 2), [3], 'slasher([1, 2, 3], 2)
should return [3]
.');",
+ "assert.deepEqual(slasher([1, 2, 3], 0), [1, 2, 3], 'slasher([1, 2, 3], 0)
should return [1, 2, 3]
.');",
+ "assert.deepEqual(slasher([1, 2, 3], 9), [], 'slasher([1, 2, 3], 9)
should return []
.');"
],
"MDNlinks": [
"Array.slice()",
diff --git a/seed/challenges/basic-javascript.json b/seed/challenges/basic-javascript.json
index e0b38cc841..13c28c2021 100644
--- a/seed/challenges/basic-javascript.json
+++ b/seed/challenges/basic-javascript.json
@@ -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();",
diff --git a/seed/challenges/html5-and-css.json b/seed/challenges/html5-and-css.json
index 051b6764c7..d865ce2d56 100644
--- a/seed/challenges/html5-and-css.json
+++ b/seed/challenges/html5-and-css.json
@@ -1142,7 +1142,8 @@
"assert($(\"a\").text().match(/cat\\sphotos/gi), 'Your a
element should have the anchor text of \"cat photos\"')",
"assert($(\"p\") && $(\"p\").length > 2, 'Create a new p
element around your a
element.')",
"assert($(\"a[href=\\\"http://www.freecatphotoapp.com\\\"]\").parent().is(\"p\"), 'Your a
element should be nested within your new p
element.')",
- "assert($(\"p\").text().match(/View\\smore/gi), 'Your p
element should have the text \"View more\".')",
+ "assert($(\"p\").text().match(/^View\\smore\\s/gi), 'Your p
element should have the text \"View more \" (with a space after it).')",
+ "assert(!$(\"a\").text().match(/View\\smore/gi), 'Your a
element should not have the text \"View more\".')",
"assert(editor.match(/<\\/p>/g) && editor.match(//g).length === editor.match(/
p elements has a closing tag.')",
"assert(editor.match(/<\\/a>/g) && editor.match(//g).length === editor.match(/a elements has a closing tag.')"
],
diff --git a/seed/challenges/intermediate-bonfires.json b/seed/challenges/intermediate-bonfires.json
index 93b21abba7..f9080c7214 100644
--- a/seed/challenges/intermediate-bonfires.json
+++ b/seed/challenges/intermediate-bonfires.json
@@ -153,7 +153,7 @@
"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 }, { 'a': 5 }, { 'a': 5, 'b': 10 }], { 'a': 5, 'b': 10 }), [{ 'a': 5, 'b': 10 }], 'should return a single object 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');"
],
"MDNlinks": [
"Global Object",
diff --git a/seed/challenges/jquery.json b/seed/challenges/jquery.json
index 3a7b988816..95214991e4 100644
--- a/seed/challenges/jquery.json
+++ b/seed/challenges/jquery.json
@@ -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 target
and give them classes: $(\".target:odd\").addClass(\"animated shake\");
",
+ "Note that jQuery is zero-indexed, meaning that, counter-intuitively, :odd
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 animated
and shake
."
],
"tests": [
diff --git a/server/boot/a-react.js b/server/boot/a-react.js
index 6c0cd04819..9b1f4926c9 100644
--- a/server/boot/a-react.js
+++ b/server/boot/a-react.js
@@ -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
diff --git a/server/boot/challenge.js b/server/boot/challenge.js
index 15736e3a51..239d21d4d0 100644
--- a/server/boot/challenge.js
+++ b/server/boot/challenge.js
@@ -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"
});
},
diff --git a/server/views/challengeMap/show.jade b/server/views/challengeMap/show.jade
index 11d14bceee..42fa90422b 100644
--- a/server/views/challengeMap/show.jade
+++ b/server/views/challengeMap/show.jade
@@ -131,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
diff --git a/server/views/coursewares/showJS.jade b/server/views/coursewares/showJS.jade
index 03d6b823e1..f4621b5b28 100644
--- a/server/views/coursewares/showJS.jade
+++ b/server/views/coursewares/showJS.jade
@@ -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
- = 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
diff --git a/server/views/coursewares/showVideo.jade b/server/views/coursewares/showVideo.jade
index 30ad2d5f1a..bd7f42e5ed 100644
--- a/server/views/coursewares/showVideo.jade
+++ b/server/views/coursewares/showVideo.jade
@@ -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
- = 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.