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 + } + }); +};