diff --git a/packages/learn/gatsby-node.js b/packages/learn/gatsby-node.js
index 72cb020fae..2562900b7d 100644
--- a/packages/learn/gatsby-node.js
+++ b/packages/learn/gatsby-node.js
@@ -2,30 +2,8 @@ const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { dasherize } = require('./utils');
-const { viewTypes } = require('./utils/challengeTypes');
const { blockNameify } = require('./utils/blockNameify');
-
-const backend = path.resolve(
- __dirname,
- './src/templates/Challenges/backend/Show.js'
-);
-const classic = path.resolve(
- __dirname,
- './src/templates/Challenges/classic/Show.js'
-);
-const intro = path.resolve(__dirname, './src/templates/Introduction/Intro.js');
-const project = path.resolve(
- __dirname,
- './src/templates/Challenges/project/Show.js'
-);
-
-const views = {
- backend,
- classic,
- modern: classic,
- project
- // quiz: Quiz
-};
+const { createChallengePages, createIntroPages } = require('./utils/gatsby');
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
const { createNodeField } = boundActionCreators;
@@ -60,17 +38,22 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
edges {
node {
+ block
challengeType
+ fields {
+ slug
+ }
id
+ order
required {
link
raw
src
}
+ suborder
+ superBlock
+ superOrder
template
- fields {
- slug
- }
}
}
}
@@ -97,46 +80,14 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
}
// Create challenge pages.
- result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => {
- const {
- fields: { slug },
- required = [],
- template,
- challengeType,
- id
- } = edge.node;
- if (challengeType === 7) {
- return;
- }
- const next = thisArray[index + 1];
- const nextChallengePath = next ? next.node.fields.slug : '/';
- createPage({
- path: slug,
- component: views[viewTypes[challengeType]],
- context: {
- challengeMeta: {
- challengeType,
- template,
- required,
- nextChallengePath,
- id
- },
- slug
- }
- });
- });
+ result.data.allChallengeNode.edges.forEach(
+ createChallengePages(createPage)
+ );
// Create intro pages
- result.data.allMarkdownRemark.edges.forEach(edge => {
- const { fields: { slug } } = edge.node;
- createPage({
- path: slug,
- component: intro,
- context: {
- slug
- }
- });
- });
+ result.data.allMarkdownRemark.edges.forEach(
+ createIntroPages(createPage)
+ );
return;
})
diff --git a/packages/learn/package.json b/packages/learn/package.json
index 2dec7ac292..4ea2df9093 100644
--- a/packages/learn/package.json
+++ b/packages/learn/package.json
@@ -55,12 +55,14 @@
"build:frame-runner": "webpack --config ./webpack-frame-runner.js",
"build:loop-protect": "webpack --config ./webpack-loop-protect.js",
"develop": "yarn build:frame-runner && gatsby develop",
- "format": "yarn format:gatsby && yarn format:src && yarn lint",
+ "format": "yarn format:gatsby && yarn format:src && yarn format:utils && yarn lint",
"format:gatsby": "prettier --write './gatsby*.js'",
"format:src": "prettier --write './src/**/*.js'",
- "lint": "yarn lint:gatsby && yarn lint:src",
+ "format:utils": "prettier --write './utils/**/*.js'",
+ "lint": "yarn lint:gatsby && yarn lint:src && yarn lint:utils",
"lint:gatsby": "eslint ./gatsby*.js --fix",
"lint:src": "eslint ./src . --fix",
+ "lint:utils": "eslint ./utils . --fix",
"pretty": "yarn format && yarn lint",
"test": "yarn format && jest src",
"test:watch": "jest --watch src"
diff --git a/packages/learn/src/components/util/ButtonSpacer.js b/packages/learn/src/components/util/ButtonSpacer.js
index 3e46a8654a..f74fe66853 100644
--- a/packages/learn/src/components/util/ButtonSpacer.js
+++ b/packages/learn/src/components/util/ButtonSpacer.js
@@ -1,5 +1,7 @@
import React from 'react';
+import './button-spacer.css';
+
function ButtonSpacer() {
return
;
}
diff --git a/packages/learn/src/components/util/FullWidthRow.js b/packages/learn/src/components/util/FullWidthRow.js
new file mode 100644
index 0000000000..d6161e63df
--- /dev/null
+++ b/packages/learn/src/components/util/FullWidthRow.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Row, Col } from 'react-bootstrap';
+
+function FullWidthRow({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+FullWidthRow.displayName = 'FullWidthRow';
+FullWidthRow.propTypes = {
+ children: PropTypes.any
+};
+
+export default FullWidthRow;
diff --git a/packages/learn/src/components/util/FullWidthRow.test.js b/packages/learn/src/components/util/FullWidthRow.test.js
new file mode 100644
index 0000000000..08f2f0e5ee
--- /dev/null
+++ b/packages/learn/src/components/util/FullWidthRow.test.js
@@ -0,0 +1,16 @@
+/* global expect */
+
+import React from 'react';
+import renderer from 'react-test-renderer';
+import Enzyme from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+
+import FullWidthRow from './FullWidthRow';
+
+Enzyme.configure({ adapter: new Adapter() });
+
+test(' snapshot', () => {
+ const component = renderer.create();
+ let tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
diff --git a/packages/learn/src/components/util/__snapshots__/FullWidthRow.test.js.snap b/packages/learn/src/components/util/__snapshots__/FullWidthRow.test.js.snap
new file mode 100644
index 0000000000..40bb29aa68
--- /dev/null
+++ b/packages/learn/src/components/util/__snapshots__/FullWidthRow.test.js.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` snapshot 1`] = `
+
+`;
diff --git a/packages/learn/src/components/util/button-spacer.css b/packages/learn/src/components/util/button-spacer.css
new file mode 100644
index 0000000000..321488d597
--- /dev/null
+++ b/packages/learn/src/components/util/button-spacer.css
@@ -0,0 +1,3 @@
+.button-spacer {
+ margin: 15px 0;
+}
\ No newline at end of file
diff --git a/packages/learn/src/templates/Challenges/redux/completion-epic.js b/packages/learn/src/templates/Challenges/redux/completion-epic.js
index 7b5ea916d3..8c64abcebb 100644
--- a/packages/learn/src/templates/Challenges/redux/completion-epic.js
+++ b/packages/learn/src/templates/Challenges/redux/completion-epic.js
@@ -162,13 +162,18 @@ export default function completionEpic(action$, { getState }) {
return action$.pipe(
ofType(types.submitChallenge),
switchMap(({ type }) => {
- const { nextChallengePath } = challengeMetaSelector(getState());
+ const { nextChallengePath, introPath } = challengeMetaSelector(
+ getState()
+ );
// const state = getState();
// const { submitType } = challengeMetaSelector(state);
// const submitter = submitters[submitType] || (() => Observable.empty());
return type === types.submitChallenge
- ? of(closeModal('completion'), push(nextChallengePath))
+ ? of(
+ closeModal('completion'),
+ push(introPath ? introPath : nextChallengePath)
+ )
: of({ type: 'PONG' });
})
);
diff --git a/packages/learn/src/templates/Introduction/Intro.js b/packages/learn/src/templates/Introduction/Intro.js
index 6508484dd1..83184d4cbe 100644
--- a/packages/learn/src/templates/Introduction/Intro.js
+++ b/packages/learn/src/templates/Introduction/Intro.js
@@ -1,18 +1,62 @@
/* global graphql */
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
-import { MarkdownRemark } from '../../redux/propTypes';
+import Link from 'gatsby-link';
+import Helmet from 'react-helmet';
+import { Button, ListGroup, ListGroupItem } from 'react-bootstrap';
+
+import FullWidthRow from '../../components/util/FullWidthRow';
+import ButtonSpacer from '../../components/util/ButtonSpacer';
+import { MarkdownRemark, AllChallengeNode } from '../../redux/propTypes';
+
+import './intro.css';
const propTypes = {
data: PropTypes.shape({
- markdownRemark: MarkdownRemark
+ markdownRemark: MarkdownRemark,
+ allChallengeNode: AllChallengeNode
})
};
-function IntroductionPage({ data: { markdownRemark } }) {
- const { html } = markdownRemark;
+function renderMenuItems({ edges }) {
+ return edges.map(({ node }) => node).map(({ title, fields: { slug } }) => (
+
+ {title}
+
+ ));
+}
+
+function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
+ const { html, frontmatter: { block } } = markdownRemark;
+ const firstLesson = allChallengeNode.edges[0].node;
+ const firstLessonPath = firstLesson.fields.slug;
return (
-
+
+
+ {block} | freeCodeCamp
+
+
+
+
+
+
+
+
+
+
+
+
+ Upcoming Lessons
+
+ {renderMenuItems(allChallengeNode)}
+
+
+
);
}
@@ -22,9 +66,25 @@ IntroductionPage.propTypes = propTypes;
export default IntroductionPage;
export const query = graphql`
- query IntroPageBySlug($slug: String!) {
+ query IntroPageBySlug($slug: String!, $block: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
+ frontmatter {
+ block
+ }
html
}
+ allChallengeNode(
+ filter: { block: { eq: $block } }
+ sort: { fields: [superOrder, order, suborder] }
+ ) {
+ edges {
+ node {
+ fields {
+ slug
+ }
+ title
+ }
+ }
+ }
}
`;
diff --git a/packages/learn/src/templates/Introduction/intro.css b/packages/learn/src/templates/Introduction/intro.css
new file mode 100644
index 0000000000..f7b2d16133
--- /dev/null
+++ b/packages/learn/src/templates/Introduction/intro.css
@@ -0,0 +1,9 @@
+.intro-toc .list-group-item:hover {
+ background-color: #eee;
+}
+
+.intro-toc a:hover {
+ text-decoration: none;
+ text-decoration-style: none;
+ color: #006400;
+}
\ No newline at end of file
diff --git a/packages/learn/utils/blockNameify.js b/packages/learn/utils/blockNameify.js
index c9103f77a4..07774a36db 100644
--- a/packages/learn/utils/blockNameify.js
+++ b/packages/learn/utils/blockNameify.js
@@ -13,22 +13,16 @@ const preFormattedBlockNames = {
'the-dom': 'The DOM'
};
-const noFormatting = [
- 'and',
- 'for',
- 'of',
- 'the',
- 'up',
- 'with'
-];
+const noFormatting = ['and', 'for', 'of', 'the', 'up', 'with'];
exports.blockNameify = function blockNameify(phrase) {
const preFormatted = preFormattedBlockNames[phrase] || '';
if (preFormatted) {
return preFormatted;
}
- return phrase.split('-')
- .map((word) => {
+ return phrase
+ .split('-')
+ .map(word => {
if (noFormatting.indexOf(word) !== -1) {
return word;
}
diff --git a/packages/learn/utils/gatsby/index.js b/packages/learn/utils/gatsby/index.js
new file mode 100644
index 0000000000..3f39992056
--- /dev/null
+++ b/packages/learn/utils/gatsby/index.js
@@ -0,0 +1,85 @@
+import { dasherize } from '..';
+
+const path = require('path');
+
+const { viewTypes } = require('../challengeTypes');
+
+const backend = path.resolve(
+ __dirname,
+ '../../src/templates/Challenges/backend/Show.js'
+);
+const classic = path.resolve(
+ __dirname,
+ '../../src/templates/Challenges/classic/Show.js'
+);
+const project = path.resolve(
+ __dirname,
+ '../../src/templates/Challenges/project/Show.js'
+);
+const intro = path.resolve(
+ __dirname,
+ '../../src/templates/Introduction/Intro.js'
+);
+
+const views = {
+ backend,
+ classic,
+ modern: classic,
+ project
+ // quiz: Quiz
+};
+
+const getNextChallengePath = (node, index, nodeArray) => {
+ const next = nodeArray[index + 1];
+ return next ? next.node.fields.slug : '/';
+};
+const getTemplateComponent = challengeType => views[viewTypes[challengeType]];
+
+const getIntroIfRequired = (node, index, nodeArray) => {
+ const next = nodeArray[index + 1];
+ const isEndOfBlock = next && next.node.suborder === 1;
+ let nextSuperBlock = '';
+ let nextBlock = '';
+ if (next) {
+ const { superBlock, block } = next.node;
+ nextSuperBlock = superBlock;
+ nextBlock = block;
+ }
+ return isEndOfBlock
+ ? `/${dasherize(nextSuperBlock)}/${dasherize(nextBlock)}`
+ : '';
+};
+
+exports.createChallengePages = createPage => ({ node }, index, thisArray) => {
+ const { fields: { slug }, required = [], template, challengeType, id } = node;
+ if (challengeType === 7) {
+ return;
+ }
+
+ createPage({
+ path: slug,
+ component: getTemplateComponent(challengeType),
+ context: {
+ challengeMeta: {
+ introPath: getIntroIfRequired(node, index, thisArray),
+ template,
+ required,
+ nextChallengePath: getNextChallengePath(node, index, thisArray),
+ id
+ },
+ slug
+ }
+ });
+};
+
+exports.createIntroPages = createPage => edge => {
+ const { fields: { slug }, frontmatter: { block } } = edge.node;
+ createPage({
+ path: slug,
+ component: intro,
+ context: {
+ block: dasherize(block),
+ slug
+ }
+ });
+};