fix: removed all guide bits from gatsby
This commit is contained in:
parent
7bc6367d68
commit
0124e7556d
@ -6,16 +6,8 @@ const {
|
||||
localeChallengesRootDir
|
||||
} = require('./utils/buildChallenges');
|
||||
|
||||
const {
|
||||
NODE_ENV: env,
|
||||
LOCALE: locale = 'english',
|
||||
API_PROXY: proxyUrl = 'http://localhost:3000'
|
||||
} = process.env;
|
||||
const { API_PROXY: proxyUrl = 'http://localhost:3000' } = process.env;
|
||||
|
||||
const selectedGuideDir = `../${
|
||||
env === 'production' ? 'guide' : 'mock-guide'
|
||||
}/${locale}`;
|
||||
const guideRoot = path.resolve(__dirname, selectedGuideDir);
|
||||
const curriculumIntroRoot = path.resolve(__dirname, './src/pages');
|
||||
|
||||
module.exports = {
|
||||
@ -50,13 +42,6 @@ module.exports = {
|
||||
curriculumPath: localeChallengesRootDir
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
name: 'guides',
|
||||
path: guideRoot
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
@ -97,19 +82,6 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-remark-node-identity',
|
||||
options: {
|
||||
identity: 'guideMarkdown',
|
||||
predicate: ({ frontmatter }) => {
|
||||
if (!frontmatter) {
|
||||
return false;
|
||||
}
|
||||
const { title, block, superBlock } = frontmatter;
|
||||
return title && !block && !superBlock;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-remark-node-identity',
|
||||
options: {
|
||||
@ -136,7 +108,6 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{ resolve: 'fcc-create-nav-data' },
|
||||
{
|
||||
resolve: 'gatsby-plugin-manifest',
|
||||
options: {
|
||||
|
@ -4,16 +4,13 @@ const { createFilePath } = require('gatsby-source-filesystem');
|
||||
|
||||
const { dasherize } = require('./utils');
|
||||
const { blockNameify } = require('./utils/blockNameify');
|
||||
const { getGithubPath } = require('./utils/getGithubPath');
|
||||
const {
|
||||
createChallengePages,
|
||||
createBlockIntroPages,
|
||||
createSuperBlockIntroPages,
|
||||
createGuideArticlePages
|
||||
createSuperBlockIntroPages
|
||||
} = require('./utils/gatsby');
|
||||
|
||||
const createByIdentityMap = {
|
||||
guideMarkdown: createGuideArticlePages,
|
||||
blockIntroMarkdown: createBlockIntroPages,
|
||||
superBlockIntroMarkdown: createSuperBlockIntroPages
|
||||
};
|
||||
@ -34,16 +31,10 @@ exports.onCreateNode = function onCreateNode({ node, actions, getNode }) {
|
||||
const slug = createFilePath({ node, getNode });
|
||||
if (!slug.includes('LICENSE')) {
|
||||
const {
|
||||
fileAbsolutePath,
|
||||
frontmatter: { component = '' }
|
||||
} = node;
|
||||
createNodeField({ node, name: 'slug', value: slug });
|
||||
createNodeField({ node, name: 'component', value: component });
|
||||
createNodeField({
|
||||
node,
|
||||
name: 'githubPath',
|
||||
value: getGithubPath(fileAbsolutePath)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -86,7 +77,6 @@ exports.createPages = function createPages({ graphql, actions }) {
|
||||
slug
|
||||
nodeIdentity
|
||||
component
|
||||
githubPath
|
||||
}
|
||||
frontmatter {
|
||||
block
|
||||
|
@ -1,66 +0,0 @@
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
|
||||
const commonREs = require('../../utils/regEx');
|
||||
const readDir = require('../../utils/readDir');
|
||||
|
||||
const { isAStubRE } = commonREs;
|
||||
// default locale to english for testing
|
||||
const { NODE_ENV: env, LOCALE: locale = 'english' } = process.env;
|
||||
|
||||
const guideDir = `../../../${
|
||||
env === 'production' ? 'guide' : 'mock-guide'
|
||||
}/${locale}`;
|
||||
|
||||
const pagesDir = path.resolve(__dirname, guideDir);
|
||||
const indexMdRe = new RegExp(`\\${path.sep}index.md$`);
|
||||
|
||||
function withGuidePrefix(str) {
|
||||
return `/guide${str}`;
|
||||
}
|
||||
|
||||
exports.createNavigationNode = function createNavigationNode(node) {
|
||||
const {
|
||||
fileAbsolutePath,
|
||||
frontmatter: { title },
|
||||
internal: { content },
|
||||
parent
|
||||
} = node;
|
||||
|
||||
const nodeDir = path.resolve(fileAbsolutePath).replace(indexMdRe, '');
|
||||
const dashedName = nodeDir.split(path.sep).slice(-1)[0];
|
||||
const nodePath = nodeDir
|
||||
.split(pagesDir)[1]
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const parentPath = nodePath
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/');
|
||||
|
||||
const categoryChildren = readDir(nodeDir);
|
||||
const navNode = {
|
||||
categoryChildren,
|
||||
hasChildren: !!categoryChildren.length,
|
||||
dashedName,
|
||||
isStubbed: isAStubRE.test(content),
|
||||
path: withGuidePrefix(nodePath),
|
||||
parentPath: withGuidePrefix(parentPath),
|
||||
title
|
||||
};
|
||||
|
||||
const gatsbyRequired = {
|
||||
id: fileAbsolutePath + ' >>> NavigationNode',
|
||||
parent,
|
||||
children: [],
|
||||
internal: {
|
||||
type: 'NavigationNode',
|
||||
contentDigest: crypto
|
||||
.createHash('md5')
|
||||
.update(JSON.stringify(navNode))
|
||||
.digest('hex')
|
||||
}
|
||||
};
|
||||
|
||||
return { ...navNode, ...gatsbyRequired };
|
||||
};
|
@ -1,85 +0,0 @@
|
||||
const path = require('path');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { createNavigationNode } = require('./create-navigation-node');
|
||||
|
||||
describe('fcc-create-nav-data', () => {
|
||||
describe('create-vanigation-node', () => {
|
||||
const mockNode = {
|
||||
internal: {
|
||||
content:
|
||||
'---\ntitle: File Writing\n---\n## File Writing\n\nThis is a stub.' +
|
||||
" <a href='https://github.com/freeCodeCamp/guides/tree/master/src/" +
|
||||
"pages/php/functions/files/writing/index.md' target='_blank' " +
|
||||
"rel='nofollow'>Help our community expand it</a>.\n\n<a href=" +
|
||||
"'https://github.com/freeCodeCamp/freeCodeCamp/blob/master/docs/style-guide-for-guide-articles.md' " +
|
||||
"target='_blank' rel='nofollow'>This quick style guide will help " +
|
||||
'ensure your pull request gets accepted</a>.\n\n<!-- The article ' +
|
||||
'goes here, in GitHub-flavored Markdown. Feel free to add YouTube ' +
|
||||
'videos, images, and CodePen/JSBin embeds -->\n\n#### More ' +
|
||||
'Information:\n<!-- Please add any articles you think might be ' +
|
||||
'helpful to read before writing the article -->\n'
|
||||
},
|
||||
frontmatter: {
|
||||
title: 'File Writing'
|
||||
},
|
||||
fileAbsolutePath: path.resolve(
|
||||
__dirname,
|
||||
'../../../mock-guide/english/php/functions/files/file-writing/index.md'
|
||||
)
|
||||
};
|
||||
|
||||
const result = createNavigationNode(mockNode);
|
||||
|
||||
it('should return an object', () => {
|
||||
expect(result).to.be.an('object');
|
||||
});
|
||||
|
||||
it('node.children should be an array', () => {
|
||||
expect(result.categoryChildren).to.be.an('array');
|
||||
});
|
||||
|
||||
it('node.dashedName should equal the containing folder name', () => {
|
||||
expect(result.dashedName).equal('file-writing');
|
||||
});
|
||||
|
||||
it(
|
||||
'node.path should equal the file path from pagesDir, ' +
|
||||
'prefixed with `/guide`',
|
||||
() => {
|
||||
expect(result.path).to.equal('/guide/php/functions/files/file-writing');
|
||||
}
|
||||
);
|
||||
|
||||
it(
|
||||
'node.parentPath should equal the path of the parent page, ' +
|
||||
'prefixed with `/guide`',
|
||||
() => {
|
||||
expect(result.parentPath).to.equal('/guide/php/functions/files');
|
||||
}
|
||||
);
|
||||
|
||||
it('node.title should equal srcNode.frontmatter.title', () => {
|
||||
expect(result.title).to.equal(mockNode.frontmatter.title);
|
||||
});
|
||||
|
||||
it('node.isStubbed should be a boolean', () => {
|
||||
expect(result.isStubbed).to.be.a('boolean');
|
||||
});
|
||||
|
||||
it('node.isStubbed should be true for a stubbed article', () => {
|
||||
expect(result.isStubbed).to.equal(true);
|
||||
});
|
||||
|
||||
it('node.isStubbed should be false for a non-stubbed article', () => {
|
||||
const notAStub = {
|
||||
...mockNode,
|
||||
internal: {
|
||||
content: 'this is not a stub article. Infact, it is very informative'
|
||||
}
|
||||
};
|
||||
const result = createNavigationNode(notAStub);
|
||||
expect(result.isStubbed).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
const { createNavigationNode } = require('./create-navigation-node');
|
||||
|
||||
exports.onCreateNode = function createNavDataOnCreateNode({ actions, node }) {
|
||||
const {
|
||||
internal: { type },
|
||||
fields
|
||||
} = node;
|
||||
if (
|
||||
type === 'MarkdownRemark' &&
|
||||
fields &&
|
||||
fields.nodeIdentity === 'guideMarkdown'
|
||||
) {
|
||||
if (node.fileAbsolutePath.includes('LICENSE.md')) {
|
||||
return null;
|
||||
}
|
||||
const { createNode } = actions;
|
||||
const navNode = createNavigationNode(node);
|
||||
return createNode(navNode);
|
||||
}
|
||||
return null;
|
||||
};
|
218
client/plugins/fcc-create-nav-data/package-lock.json
generated
218
client/plugins/fcc-create-nav-data/package-lock.json
generated
@ -1,218 +0,0 @@
|
||||
{
|
||||
"name": "fcc-create-nav-data",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
|
||||
},
|
||||
"chai": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||
"requires": {
|
||||
"assertion-error": "^1.1.0",
|
||||
"check-error": "^1.0.2",
|
||||
"deep-eql": "^3.0.1",
|
||||
"get-func-name": "^2.0.0",
|
||||
"pathval": "^1.1.0",
|
||||
"type-detect": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"requires": {
|
||||
"type-detect": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"growl": {
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"he": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
|
||||
"integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
|
||||
"requires": {
|
||||
"browser-stdout": "1.3.1",
|
||||
"commander": "2.15.1",
|
||||
"debug": "3.1.0",
|
||||
"diff": "3.5.0",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"glob": "7.1.2",
|
||||
"growl": "1.10.5",
|
||||
"he": "1.1.1",
|
||||
"minimatch": "3.0.4",
|
||||
"mkdirp": "0.5.1",
|
||||
"supports-color": "5.4.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"pathval": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
||||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
||||
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "fcc-create-nav-data",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"test": "mocha -R spec \"./{,!(node_modules)/**/}*.test.js\"",
|
||||
"test:watch": "mocha -R spec \"./{,!(node_modules)/**/}*.test.js\" --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"mocha": "^5.0.5"
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StaticQuery, graphql } from 'gatsby';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Grid, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import SideNav from './components/guide/SideNav';
|
||||
import Spacer from '../helpers/Spacer';
|
||||
|
||||
import 'prismjs/themes/prism.css';
|
||||
import './guide.css';
|
||||
|
||||
import {
|
||||
toggleExpandedState,
|
||||
toggleDisplayMenu,
|
||||
displayMenuSelector,
|
||||
expandedStateSelector
|
||||
} from './components/guide/redux';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.any,
|
||||
data: PropTypes.shape({
|
||||
allNavigationNode: PropTypes.shape({
|
||||
edges: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
node: PropTypes.shape({
|
||||
dashedName: PropTypes.string,
|
||||
isStubbed: PropTypes.bool,
|
||||
path: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
}),
|
||||
displayMenu: PropTypes.bool,
|
||||
expandedState: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
toggleDisplayMenu: PropTypes.func,
|
||||
toggleExpandedState: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
displayMenuSelector,
|
||||
expandedStateSelector,
|
||||
(displayMenu, expandedState) => ({
|
||||
displayMenu,
|
||||
expandedState
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ toggleExpandedState, toggleDisplayMenu }, dispatch);
|
||||
|
||||
class GuideLayout extends React.Component {
|
||||
getContentRef = ref => (this.contentRef = ref);
|
||||
|
||||
handleNavigation = () => {};
|
||||
|
||||
hideSideNav = () => {
|
||||
if (this.props.displayMenu) {
|
||||
this.props.toggleDisplayMenu();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { expandedState, toggleExpandedState, displayMenu } = this.props;
|
||||
return (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query GuideLayoutQuery {
|
||||
allNavigationNode {
|
||||
edges {
|
||||
node {
|
||||
dashedName
|
||||
hasChildren
|
||||
isStubbed
|
||||
parentPath
|
||||
path
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const { edges } = data.allNavigationNode;
|
||||
const pages = edges.map(edge => edge.node);
|
||||
return (
|
||||
<Fragment>
|
||||
<Spacer size={1} />
|
||||
<Grid className='guide-container'>
|
||||
<Row>
|
||||
<Col md={4} smHidden={!displayMenu} xsHidden={!displayMenu}>
|
||||
<SideNav
|
||||
expandedState={expandedState}
|
||||
onNavigate={this.handleNavigation}
|
||||
pages={pages}
|
||||
toggleDisplaySideNav={this.hideSideNav}
|
||||
toggleExpandedState={toggleExpandedState}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={8} smHidden={displayMenu} xsHidden={displayMenu}>
|
||||
<main
|
||||
className='content'
|
||||
id='main'
|
||||
ref={this.getContentRef}
|
||||
tabIndex='-1'
|
||||
>
|
||||
{this.props.children}
|
||||
</main>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GuideLayout.displayName = 'GuideLayout';
|
||||
GuideLayout.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(GuideLayout);
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Link from 'gatsby-link';
|
||||
|
||||
const propTypes = {
|
||||
isStubbed: PropTypes.bool,
|
||||
onNavigate: PropTypes.func.isRequired,
|
||||
path: PropTypes.string,
|
||||
router: PropTypes.object,
|
||||
title: PropTypes.string,
|
||||
toggleDisplaySideNav: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class NavItem extends React.Component {
|
||||
handleClick = () => {
|
||||
this.props.toggleDisplaySideNav();
|
||||
this.props.onNavigate();
|
||||
};
|
||||
render() {
|
||||
const { isStubbed, path, title } = this.props;
|
||||
return (
|
||||
<li>
|
||||
<Link onClick={this.handleClick} to={path}>
|
||||
<span className={'navItemTitle' + (isStubbed ? ' stubbed' : '')}>
|
||||
{title}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavItem.displayName = 'NavItem';
|
||||
NavItem.propTypes = propTypes;
|
||||
|
||||
export default NavItem;
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import MenuButton from '../../../../components/Header/components/MenuButton';
|
||||
import MenuLinks from '../../../../components/Header/components/MenuLinks';
|
||||
|
||||
import { toggleDisplayMenu, displayMenuSelector } from './redux';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
displayMenuSelector,
|
||||
displayMenu => ({
|
||||
displayMenu
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ toggleDisplayMenu }, dispatch);
|
||||
|
||||
function GuideNavigationMenu(props) {
|
||||
const { displayMenu, toggleDisplayMenu } = props;
|
||||
return (
|
||||
<>
|
||||
<MenuButton
|
||||
className={'guide-menu-button'}
|
||||
displayMenu={displayMenu}
|
||||
onClick={toggleDisplayMenu}
|
||||
/>
|
||||
<MenuLinks className={'guide-top-nav'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
GuideNavigationMenu.displayName = 'GuideNavigationMenu';
|
||||
GuideNavigationMenu.propTypes = {
|
||||
displayMenu: PropTypes.bool.isRequired,
|
||||
toggleDisplayMenu: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(GuideNavigationMenu);
|
@ -1,106 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Panel } from '@freecodecamp/react-bootstrap';
|
||||
import { Link } from 'gatsby';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.any,
|
||||
dashedName: PropTypes.string,
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
hasChildren: PropTypes.bool,
|
||||
isExpanded: PropTypes.bool,
|
||||
onNavigate: PropTypes.func.isRequired,
|
||||
path: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
toggleDisplaySideNav: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function NoArticles() {
|
||||
return (
|
||||
<li>
|
||||
<span>
|
||||
No articles yet.
|
||||
<br />
|
||||
Could you
|
||||
<a
|
||||
href={
|
||||
'https://github.com/freeCodeCamp/freeCodeCamp/blob/master/docs/' +
|
||||
'how-to-work-on-guide-articles.md#how-to-work-on-guide-articles'
|
||||
}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
write one?
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
class NavPanel extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.renderHeader = this.renderHeader.bind(this);
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
this.handleTitleClick = this.handleTitleClick.bind(this);
|
||||
this.renderBody = this.renderBody.bind(this);
|
||||
}
|
||||
|
||||
handleHeaderClick() {
|
||||
const { path, handleClick } = this.props;
|
||||
handleClick(path);
|
||||
}
|
||||
|
||||
handleTitleClick() {
|
||||
const { toggleDisplaySideNav, onNavigate } = this.props;
|
||||
toggleDisplaySideNav();
|
||||
onNavigate();
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
const { isExpanded, path, title } = this.props;
|
||||
return (
|
||||
<div className='title' onClick={this.handleHeaderClick}>
|
||||
<span
|
||||
className={
|
||||
'caret ' + (isExpanded ? 'caretStyle expanded' : 'caretStyle')
|
||||
}
|
||||
/>
|
||||
<Link onClick={this.handleTitleClick} to={path}>
|
||||
{title}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
const { hasChildren, children, isExpanded } = this.props;
|
||||
return (
|
||||
<div className={isExpanded ? 'body' : ''}>
|
||||
<ul className='navPanelUl'>
|
||||
{hasChildren ? children : <NoArticles />}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isExpanded, dashedName } = this.props;
|
||||
return (
|
||||
<Panel
|
||||
bsClass='panelStyle panel'
|
||||
id={`${dashedName}-panel`}
|
||||
role='listitem'
|
||||
>
|
||||
<Panel.Heading>{this.renderHeader()}</Panel.Heading>
|
||||
{isExpanded ? <Panel.Body>{this.renderBody()}</Panel.Body> : null}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavPanel.displayName = 'NavPanel';
|
||||
NavPanel.propTypes = propTypes;
|
||||
|
||||
export default NavPanel;
|
@ -1,108 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PanelGroup from '@freecodecamp/react-bootstrap/lib/PanelGroup';
|
||||
|
||||
import NavPanel from './NavPanel';
|
||||
import NavItem from './NavItem';
|
||||
|
||||
const propTypes = {
|
||||
expandedState: PropTypes.object,
|
||||
onNavigate: PropTypes.func.isRequired,
|
||||
pages: PropTypes.arrayOf(PropTypes.object),
|
||||
parents: PropTypes.arrayOf(PropTypes.object),
|
||||
toggleDisplaySideNav: PropTypes.func.isRequired,
|
||||
toggleExpandedState: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function parentFilter(node) {
|
||||
return node.path.split('/').filter(Boolean).length === 2;
|
||||
}
|
||||
|
||||
class SideNav extends Component {
|
||||
constructor(...props) {
|
||||
super(...props);
|
||||
|
||||
this.renderChildren = this.renderChildren.bind(this);
|
||||
this.renderPanels = this.renderPanels.bind(this);
|
||||
this.renderParent = this.renderParent.bind(this);
|
||||
}
|
||||
|
||||
renderPanels(parents, pages) {
|
||||
if (!parents) {
|
||||
return 'No Parents Here';
|
||||
}
|
||||
return parents.map(parent => this.renderParent(parent, pages));
|
||||
}
|
||||
|
||||
renderParent(parent, pages) {
|
||||
const childrenForParent = pages.filter(
|
||||
page => page.parentPath === parent.path
|
||||
);
|
||||
const { path } = parent;
|
||||
const isExpanded = !!this.props.expandedState[path];
|
||||
const [category] = pages.filter(page => page.path === path);
|
||||
const { title, hasChildren, dashedName } = category;
|
||||
|
||||
const children = isExpanded
|
||||
? this.renderChildren(childrenForParent, pages)
|
||||
: null;
|
||||
return (
|
||||
<NavPanel
|
||||
dashedName={dashedName}
|
||||
handleClick={this.props.toggleExpandedState}
|
||||
hasChildren={hasChildren}
|
||||
isExpanded={isExpanded}
|
||||
key={parent.path}
|
||||
onNavigate={this.props.onNavigate}
|
||||
path={parent.path}
|
||||
title={title}
|
||||
toggleDisplaySideNav={this.props.toggleDisplaySideNav}
|
||||
>
|
||||
{children}
|
||||
</NavPanel>
|
||||
);
|
||||
}
|
||||
|
||||
renderChildren(children, pages) {
|
||||
return children.map(child => {
|
||||
if (child.hasChildren) {
|
||||
return this.renderParent(child, pages);
|
||||
}
|
||||
return (
|
||||
<NavItem
|
||||
isStubbed={child.isStubbed}
|
||||
key={child.path}
|
||||
onNavigate={this.props.onNavigate}
|
||||
path={child.path}
|
||||
title={child.title}
|
||||
toggleDisplaySideNav={this.props.toggleDisplaySideNav}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { pages, expandedState } = this.props;
|
||||
const parents = pages.filter(parentFilter);
|
||||
const panels = this.renderPanels(parents, pages);
|
||||
return (
|
||||
<nav className='sideNav' id='side-nav'>
|
||||
<PanelGroup id='guide-navigation-side-panel' role='list'>
|
||||
{!parents || !expandedState ? (
|
||||
<NavPanel
|
||||
title='No Parents Here'
|
||||
toggleDisplaySideNav={this.props.toggleDisplaySideNav}
|
||||
/>
|
||||
) : (
|
||||
panels
|
||||
)}
|
||||
</PanelGroup>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SideNav.displayName = 'SideNav';
|
||||
SideNav.propTypes = propTypes;
|
||||
|
||||
export default SideNav;
|
@ -1,38 +0,0 @@
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
|
||||
import { createTypes } from '../../../../../utils/createTypes';
|
||||
import { createSideNavigationSaga } from './side-navigation-saga';
|
||||
|
||||
export const ns = 'guideNav';
|
||||
|
||||
const initialState = {
|
||||
displayMenu: false,
|
||||
expandedState: {}
|
||||
};
|
||||
|
||||
const types = createTypes(['toggleExpandedState', 'toggleDisplayMenu'], ns);
|
||||
|
||||
export const sagas = [...createSideNavigationSaga(types)];
|
||||
|
||||
export const toggleExpandedState = createAction(types.toggleExpandedState);
|
||||
export const toggleDisplayMenu = createAction(types.toggleDisplayMenu);
|
||||
|
||||
export const displayMenuSelector = state => state[ns].displayMenu;
|
||||
export const expandedStateSelector = state => state[ns].expandedState;
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[types.toggleExpandedState]: (state, { payload }) => ({
|
||||
...state,
|
||||
expandedState: {
|
||||
...state.expandedState,
|
||||
[payload]: !state.expandedState[payload]
|
||||
}
|
||||
}),
|
||||
[types.toggleDisplayMenu]: state => ({
|
||||
...state,
|
||||
displayMenu: !state.displayMenu
|
||||
})
|
||||
},
|
||||
initialState
|
||||
);
|
@ -1,31 +0,0 @@
|
||||
import { takeEvery, put, all } from 'redux-saga/effects';
|
||||
|
||||
import { toggleExpandedState } from './';
|
||||
import { types as appTypes } from '../../../../../redux';
|
||||
|
||||
function* expandSideNavOnMountSaga() {
|
||||
let guideRegex = /^\/guide\//;
|
||||
let onGuide = guideRegex.test(window.location.pathname);
|
||||
if (onGuide && typeof window !== 'undefined') {
|
||||
const pathMap = window.location.pathname
|
||||
.slice(1)
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.reduce((map, current, i, pathArray) => {
|
||||
const path =
|
||||
i !== 0 ? map[pathArray[i - 1]] + `/${current}` : `/${current}`;
|
||||
return {
|
||||
...map,
|
||||
[current]: path
|
||||
};
|
||||
}, {});
|
||||
|
||||
let routes = Object.keys(pathMap).map(key => pathMap[key]);
|
||||
|
||||
yield all(routes.map(route => put(toggleExpandedState(route))));
|
||||
}
|
||||
}
|
||||
|
||||
export function createSideNavigationSaga() {
|
||||
return [takeEvery(appTypes.appMount, expandSideNavOnMountSaga)];
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
.guide-container {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.breadcrumb {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.breadcrumb:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.breadcrumb > li a {
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.breadcrumb > li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.breadcrumb > li + li:before {
|
||||
content: '\203A';
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.breadcrumb > .active {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.content,
|
||||
.sideNav {
|
||||
/* 100vh - (navbar height) - (spacer height) */
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content img.forum-image {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* SideNav */
|
||||
|
||||
.caretStyle {
|
||||
border-top: 8px dashed;
|
||||
border-right: 6px solid transparent;
|
||||
border-left: 6px solid transparent;
|
||||
color: darkgray;
|
||||
margin-right: 5px;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 150ms ease;
|
||||
}
|
||||
|
||||
.caretStyle.expanded {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.navPanelUl {
|
||||
list-style: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.navPanelUl > li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.navItemTitle {
|
||||
margin-left: 19px;
|
||||
}
|
||||
|
||||
.panelStyle {
|
||||
border: none !important;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sideNav {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stubbed {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.stubbed::after {
|
||||
content: 'stub';
|
||||
background-color: #aaa;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 0 4px;
|
||||
margin-left: 5px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.night .panelStyle,
|
||||
.night .title {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.content {
|
||||
height: auto;
|
||||
}
|
||||
#top-right-nav.guide-top-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 993px) {
|
||||
#top-nav .guide-menu-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content,
|
||||
.article {
|
||||
outline: 0;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
export { default as CertificationLayout } from './Certification';
|
||||
export { default as DefaultLayout } from './Default';
|
||||
export { default as GuideLayout } from './Guide';
|
||||
export { default as LearnLayout } from './Learn';
|
||||
|
@ -1,53 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Link } from '../components/helpers';
|
||||
|
||||
function Index() {
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>Guide | freeCodeCamp.org</title>
|
||||
<meta
|
||||
content='Short, concise guides and how-tos for the busy developer.'
|
||||
name='description'
|
||||
/>
|
||||
</Helmet>
|
||||
<h2>freeCodeCamp Guide</h2>
|
||||
<p>
|
||||
This website is full of articles about all things related to
|
||||
programming. You can use the search bar above to find something would
|
||||
like to learn about, or use the navigation to explore the content.
|
||||
</p>
|
||||
<p>There are articles on:</p>
|
||||
<ul>
|
||||
<li>SQL</li>
|
||||
<li>Mathematics</li>
|
||||
<li>JavaScript</li>
|
||||
<li>Bootstrap</li>
|
||||
<li>Git</li>
|
||||
<li>and a whole lot more</li>
|
||||
</ul>
|
||||
<h3>Not sure where to start?</h3>
|
||||
<p>
|
||||
If you want to learn programming but you're not sure where to start,
|
||||
check out <Link to='https://freecodecamp.org'>freeCodeCamp.org</Link>.
|
||||
It has a curriculum that starts from zero and helps you learn to code.
|
||||
</p>
|
||||
<h3>Contribute to the Guide</h3>
|
||||
<p>
|
||||
This site and the articles on it are{' '}
|
||||
<Link to='https://github.com/freeCodeCamp/freeCodeCamp'>
|
||||
{' '}
|
||||
open source{' '}
|
||||
</Link>{' '}
|
||||
. Your help in making it better is greatly appreciated!
|
||||
</p>
|
||||
<hr />
|
||||
<p>Happy coding!</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Index.displayName = 'IndexPage';
|
||||
|
||||
export default Index;
|
@ -6,10 +6,6 @@ import {
|
||||
reducer as flash,
|
||||
ns as flashNameSpace
|
||||
} from '../components/Flash/redux';
|
||||
import {
|
||||
reducer as guideNav,
|
||||
ns as guideNavNameSpace
|
||||
} from '../components/layouts/components/guide/redux';
|
||||
import { reducer as settings, ns as settingsNameSpace } from './settings';
|
||||
import {
|
||||
reducer as curriculumMap,
|
||||
@ -38,7 +34,6 @@ export default combineReducers({
|
||||
[challengeNameSpace]: challenge,
|
||||
[curriculumMapNameSpace]: curriculumMap,
|
||||
[flashNameSpace]: flash,
|
||||
[guideNavNameSpace]: guideNav,
|
||||
form: formReducer,
|
||||
[searchNameSpace]: search,
|
||||
[settingsNameSpace]: settings
|
||||
|
@ -5,15 +5,6 @@ import { sagas as appSagas } from './';
|
||||
import { sagas as challengeSagas } from '../templates/Challenges/redux';
|
||||
import { sagas as settingsSagas } from './settings';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
import { sagas as sideNavigationSaga } from '../components/layouts/components/guide/redux';
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([
|
||||
...errorSagas,
|
||||
...appSagas,
|
||||
...challengeSagas,
|
||||
...settingsSagas,
|
||||
...sideNavigationSaga
|
||||
]);
|
||||
yield all([...errorSagas, ...appSagas, ...challengeSagas, ...settingsSagas]);
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { graphql } from 'gatsby';
|
||||
|
||||
import ArticleLayout from './components/ArticleLayout';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
const GuideArticle = props => {
|
||||
const {
|
||||
data: {
|
||||
markdownRemark: { html }
|
||||
}
|
||||
} = props;
|
||||
return (
|
||||
<ArticleLayout {...props}>
|
||||
<article
|
||||
className='article'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
id='article'
|
||||
tabIndex='-1'
|
||||
/>
|
||||
</ArticleLayout>
|
||||
);
|
||||
};
|
||||
|
||||
GuideArticle.displayName = 'GuideArticle';
|
||||
GuideArticle.propTypes = propTypes;
|
||||
|
||||
export default GuideArticle;
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query ArticleById($id: String!) {
|
||||
markdownRemark(id: { eq: $id }) {
|
||||
html
|
||||
...ArticleLayout
|
||||
}
|
||||
}
|
||||
`;
|
@ -1,72 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { graphql } from 'gatsby';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import Breadcrumbs from './Breadcrumbs';
|
||||
import GuideFooter from './GuideFooter';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.any,
|
||||
data: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
pageContext: PropTypes.shape({
|
||||
meta: PropTypes.objectOf(PropTypes.string)
|
||||
})
|
||||
};
|
||||
|
||||
const ArticleLayout = props => {
|
||||
const {
|
||||
children,
|
||||
location: { pathname },
|
||||
data: {
|
||||
markdownRemark: {
|
||||
fields: { slug, githubPath },
|
||||
frontmatter: { title }
|
||||
}
|
||||
},
|
||||
pageContext: { meta }
|
||||
} = props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>{`${title} | freeCodeCamp.org`}</title>
|
||||
<link href={`https://www.freecodecamp.org${slug}`} rel='canonical' />
|
||||
<meta
|
||||
content={`https://www.freecodecamp.org${slug}`}
|
||||
property='og:url'
|
||||
/>
|
||||
<meta content={title} property='og:title' />
|
||||
<meta
|
||||
content={meta.description ? meta.description : ''}
|
||||
property='og:description'
|
||||
/>
|
||||
<meta
|
||||
content={meta.description ? meta.description : ''}
|
||||
name='description'
|
||||
/>
|
||||
<meta content={meta.featureImage} property='og:image' />
|
||||
</Helmet>
|
||||
<Breadcrumbs path={pathname} />
|
||||
{children}
|
||||
<GuideFooter githubPath={githubPath} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleLayout.displayName = 'ArticleLayout';
|
||||
ArticleLayout.propTypes = propTypes;
|
||||
|
||||
export default ArticleLayout;
|
||||
|
||||
export const fragmentQuery = graphql`
|
||||
fragment ArticleLayout on MarkdownRemark {
|
||||
fields {
|
||||
slug
|
||||
githubPath
|
||||
}
|
||||
frontmatter {
|
||||
title
|
||||
}
|
||||
}
|
||||
`;
|
@ -1,55 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Link from 'gatsby-link';
|
||||
import titleify from '../../../../utils/titleify';
|
||||
|
||||
const propTypes = {
|
||||
path: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
function Breadcrumbs(props) {
|
||||
const { path } = props;
|
||||
if (path === '/') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pathMap = path
|
||||
// remove leading and trailing slash
|
||||
.replace(/^\/([a-z0-9/-]+[^/])\/?$/i, '$1')
|
||||
.split('/')
|
||||
.reduce((accu, current, i, pathArray) => {
|
||||
const path =
|
||||
i !== 0 ? accu[pathArray[i - 1]].path + `/${current}` : `/${current}`;
|
||||
return {
|
||||
...accu,
|
||||
[current]: {
|
||||
path,
|
||||
title: titleify(current)
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
|
||||
const crumbs = Object.keys(pathMap)
|
||||
.map(key => pathMap[key])
|
||||
.map((page, i, thisArray) => {
|
||||
if (i === thisArray.length - 1) {
|
||||
return (
|
||||
<li className='active' key={i}>
|
||||
{page.title}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li key={i}>
|
||||
<Link to={page.path}>{page.title}</Link>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return <ol className='breadcrumb'>{crumbs}</ol>;
|
||||
}
|
||||
|
||||
Breadcrumbs.displayName = 'Breadcrumbs';
|
||||
Breadcrumbs.propTypes = propTypes;
|
||||
|
||||
export default Breadcrumbs;
|
@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, Spacer } from '../../../components/helpers';
|
||||
|
||||
const propTypes = {
|
||||
githubPath: PropTypes.string
|
||||
};
|
||||
|
||||
const GuideFooter = ({ githubPath }) => (
|
||||
<>
|
||||
<Spacer />
|
||||
<hr />
|
||||
<h4>Contributing to the Guide</h4>
|
||||
<p>
|
||||
This open source guide is curated by thousands of contributors. You can
|
||||
help by researching, writing and updating these articles. It is an easy
|
||||
and fun way to get started with contributing to open source.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<Link to='https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md#research-write-and-update-our-guide-articles'>
|
||||
Follow our contributing guidelines for working on guide articles
|
||||
</Link>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
<Link to={githubPath}>Edit this article on GitHub</Link>.
|
||||
</li>
|
||||
</ul>
|
||||
<Spacer />
|
||||
</>
|
||||
);
|
||||
|
||||
GuideFooter.displayName = 'GuideFooter';
|
||||
GuideFooter.propTypes = propTypes;
|
||||
|
||||
export default GuideFooter;
|
@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { graphql } from 'gatsby';
|
||||
|
||||
import ArticleLayout from '../../ArticleLayout';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
class CustomClickCounter extends React.Component {
|
||||
state = {
|
||||
init: false,
|
||||
span: null,
|
||||
div: null,
|
||||
counter: 0
|
||||
};
|
||||
|
||||
initComponent() {
|
||||
const span = document.getElementById('example-component_span');
|
||||
const div = document.getElementById('example-component_div');
|
||||
this.setState({ init: true, span, div });
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState(state => ({ counter: state.counter + 1 }));
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.initComponent();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.init) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{ReactDOM.createPortal(this.state.counter, this.state.span)}
|
||||
{ReactDOM.createPortal(
|
||||
<button onClick={this.handleClick}>Click me!</button>,
|
||||
this.state.div
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ExampleComponent = props => {
|
||||
const {
|
||||
data: {
|
||||
markdownRemark: { html }
|
||||
}
|
||||
} = props;
|
||||
return (
|
||||
<ArticleLayout {...props}>
|
||||
<article
|
||||
className='article'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
id='article'
|
||||
tabIndex='-1'
|
||||
/>
|
||||
<CustomClickCounter />
|
||||
</ArticleLayout>
|
||||
);
|
||||
};
|
||||
|
||||
ExampleComponent.displayName = 'ExampleComponent';
|
||||
ExampleComponent.propTypes = propTypes;
|
||||
export default ExampleComponent;
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query ExampleComponent($id: String!) {
|
||||
markdownRemark(id: { eq: $id }) {
|
||||
html
|
||||
...ArticleLayout
|
||||
}
|
||||
}
|
||||
`;
|
@ -1,47 +0,0 @@
|
||||
const path = require('path');
|
||||
const select = require('unist-util-select');
|
||||
const { head } = require('lodash');
|
||||
|
||||
const { isAStubRE } = require('../regEx');
|
||||
|
||||
const guideArticle = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Guide/GuideArticle.js'
|
||||
);
|
||||
|
||||
exports.createGuideArticlePages = createPage => ({
|
||||
node: {
|
||||
htmlAst,
|
||||
excerpt,
|
||||
fields: { slug, component },
|
||||
id
|
||||
}
|
||||
}) => {
|
||||
let meta = {};
|
||||
|
||||
if (!isAStubRE.test(excerpt)) {
|
||||
const featureImage = head(select(htmlAst, 'element[tagName=img]'));
|
||||
meta.featureImage = featureImage
|
||||
? featureImage.properties.src
|
||||
: 'https://s3.amazonaws.com/freecodecamp' +
|
||||
'/freecodecamp-square-logo-large.jpg';
|
||||
|
||||
const description = head(select(htmlAst, 'element[tagName=p]'));
|
||||
meta.description = description ? description.children[0].value : '';
|
||||
}
|
||||
|
||||
return createPage({
|
||||
path: `/guide${slug}`,
|
||||
component: !component
|
||||
? guideArticle
|
||||
: path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Guide/components/',
|
||||
component
|
||||
),
|
||||
context: {
|
||||
id,
|
||||
meta
|
||||
}
|
||||
});
|
||||
};
|
@ -1,7 +1,5 @@
|
||||
const challengePageCreators = require('./challengePageCreator');
|
||||
const guidePageCreators = require('./guidePageCreator');
|
||||
|
||||
module.exports = {
|
||||
...challengePageCreators,
|
||||
...guidePageCreators
|
||||
...challengePageCreators
|
||||
};
|
||||
|
@ -3,11 +3,8 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
CertificationLayout,
|
||||
DefaultLayout,
|
||||
GuideLayout
|
||||
DefaultLayout
|
||||
} from '../../src/components/layouts';
|
||||
// eslint-disable-next-line max-len
|
||||
import GuideNavMenu from '../../src/components/layouts/components/guide/NavMenu';
|
||||
import FourOhFourPage from '../../src/pages/404';
|
||||
|
||||
export default function layoutSelector({ element, props }) {
|
||||
@ -28,11 +25,7 @@ export default function layoutSelector({ element, props }) {
|
||||
return <CertificationLayout>{element}</CertificationLayout>;
|
||||
}
|
||||
if (/^\/guide(\/.*)*/.test(pathname)) {
|
||||
return (
|
||||
<DefaultLayout navigationMenu={<GuideNavMenu />} pathname={pathname}>
|
||||
<GuideLayout>{element}</GuideLayout>
|
||||
</DefaultLayout>
|
||||
);
|
||||
console.log('Hitting guide for some reason. Need a redirect.');
|
||||
}
|
||||
if (/^\/learn(\/.*)*/.test(pathname)) {
|
||||
return (
|
||||
|
Loading…
x
Reference in New Issue
Block a user