feat(guide): Import guide in to the client app

This commit is contained in:
Bouncey
2018-10-04 14:47:55 +01:00
committed by Stuart Taylor
parent 2723a943c9
commit 6e728ce16d
4338 changed files with 148283 additions and 4200 deletions

View File

@@ -1,4 +1,4 @@
const { getChallenges } = require('@freecodecamp/curriculum');
const { getChallengesForLang } = require('@freecodecamp/curriculum');
const { from, of } = require('rxjs');
const { map } = require('rxjs/operators');
const _ = require('lodash');
@@ -11,97 +11,64 @@ const nameify = utils.nameify;
const arrToString = arr =>
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
exports.buildChallenges$ = function buildChallenges$() {
return from(getChallenges()).pipe(
map(function(challengeSpec) {
const order = challengeSpec.order;
const blockName = challengeSpec.name;
const superBlock = challengeSpec.superBlock;
const superOrder = challengeSpec.superOrder;
const isBeta = !!challengeSpec.isBeta;
const isComingSoon = !!challengeSpec.isComingSoon;
const fileName = challengeSpec.fileName;
const time = challengeSpec.time;
const isLocked = !!challengeSpec.isLocked;
const message = challengeSpec.message;
const required = challengeSpec.required || [];
const template = challengeSpec.template;
const isPrivate = !!challengeSpec.isPrivate;
exports.buildChallenges = async function buildChallenges() {
const curriculum = await getChallengesForLang('english');
const superBlocks = Object.keys(curriculum);
const blocks = superBlocks
.map(superBlock => curriculum[superBlock].blocks)
.reduce((blocks, superBlock) => {
const currentBlocks = Object.keys(superBlock).map(key => superBlock[key]);
return blocks.concat(_.flatten(currentBlocks));
}, []);
// challenge file has no challenges...
if (challengeSpec.challenges.length === 0) {
return of([{ block: 'empty ' + blockName }]);
const builtChallenges = blocks.filter(block => !block.isPrivate).map(({ meta, challenges }) => {
const {
order,
time,
template,
required,
superBlock,
superOrder,
isPrivate,
dashedName: blockDashedName,
fileName
} = meta;
return challenges.map(challenge => {
challenge.name = nameify(challenge.title);
challenge.dashedName = dasherize(challenge.name);
if (challenge.files) {
challenge.files = _.reduce(
challenge.files,
(map, file) => {
map[file.key] = {
...file,
head: arrToString(file.head),
contents: arrToString(file.contents),
tail: arrToString(file.tail)
};
return map;
},
{}
);
}
const block = {
title: blockName,
name: nameify(blockName),
dashedName: dasherize(blockName),
superOrder,
superBlock,
superBlockMessage: message,
order,
time,
isLocked,
isPrivate
};
return challengeSpec.challenges.map(function(challenge, index) {
challenge.name = nameify(challenge.title);
challenge.dashedName = dasherize(challenge.name);
if (challenge.files) {
challenge.files = _.reduce(
challenge.files,
(map, file) => {
map[file.key] = {
...file,
head: arrToString(file.head),
contents: arrToString(file.contents),
tail: arrToString(file.tail)
};
return map;
},
{}
);
}
challenge.fileName = fileName;
challenge.order = order;
challenge.suborder = index + 1;
challenge.block = dasherize(blockName);
challenge.blockId = block.id;
challenge.isBeta = challenge.isBeta || isBeta;
challenge.isComingSoon = challenge.isComingSoon || isComingSoon;
challenge.isLocked = challenge.isLocked || isLocked;
challenge.isPrivate = challenge.isPrivate || isPrivate;
challenge.isRequired = !!challenge.isRequired;
challenge.time = challengeSpec.time;
challenge.superOrder = superOrder;
challenge.superBlock = superBlock
.split('-')
.map(function(word) {
return _.capitalize(word);
})
.join(' ');
challenge.required = (challenge.required || []).concat(required);
challenge.template = challenge.template || template;
return _.omit(challenge, [
'betaSolutions',
'betaTests',
'hints',
'MDNlinks',
'null',
'rawSolutions',
'react',
'reactRedux',
'redux',
'releasedOn',
'translations',
'type'
]);
});
})
);
challenge.fileName = fileName;
challenge.order = order;
challenge.block = blockDashedName;
challenge.isPrivate = challenge.isPrivate || isPrivate;
challenge.isRequired = !!challenge.isRequired;
challenge.time = time;
challenge.superOrder = superOrder;
challenge.superBlock = superBlock
.split('-')
.map(word => _.capitalize(word))
.join(' ');
challenge.required = required;
challenge.template = template;
return challenge;
});
}).reduce((accu, current) => accu.concat(current), [])
return builtChallenges;
};

177
client/utils/formatting.js Normal file
View File

@@ -0,0 +1,177 @@
/* eslint-disable camelcase */
const preFormatted = {
acid: 'ACID',
arraybuffer: 'ArrayBuffer',
aws: 'AWS',
bytelength: 'byteLength',
charat: 'charAt',
charcodeat: 'charCodeAt',
codepointat: 'codePointAt',
columnnumber: 'columnNumber',
compareexchange: 'compareExchange',
copywithin: 'copyWithin',
cplusplus: 'C++',
css: 'CSS',
css3: 'CSS3',
defineproperties: 'defineProperties',
defineproperty: 'defineProperty',
deleteproperty: 'deleteProperty',
displayname: 'displayName',
dynamodb: 'DynamoDB',
e: 'E',
endswith: 'endsWith',
epsilon: 'EPSILON',
filename: 'fileName',
findindex: 'findIndex',
foreach: 'forEach',
fromcharcode: 'fromCharCode',
frompointcode: 'fromPointCode',
getdate: 'getDate',
getday: 'getDay',
getfullyear: 'getFullYear',
gethours: 'getHours',
getmilliseconds: 'getMilliseconds',
getminutes: 'getMinutes',
getmonth: 'getMonth',
getownpropertydescriptor: 'getOwnPropertyDescriptor',
getownpropertydescriptors: 'getOwnPropertyDescriptors',
getownpropertynames: 'getOwnPropertyNames',
getownpropertysymbols: 'getOwnPropertySymbols',
getprototypeof: 'getPrototypeOf',
getseconds: 'getSeconds',
gettime: 'getTime',
getomezoneoffset: 'getTimezoneOffset',
getutcdate: 'getUTCDate',
getutcday: 'getUTCDay',
getutcfullyear: 'getUTCFullYear',
getutchours: 'getUTCHours',
getutcmilliseconds: 'getUTCMilliseconds',
getutcminutes: 'getUTCMinutes',
getutcmonth: 'getUTCMonth',
getutcseconds: 'getUTCSeconds',
getyear: 'getYear',
hasownproperty: 'hasOwnProperty',
html: 'HTML',
html5: 'HTML5',
ide: 'IDE',
indexof: 'indexOf',
ignorecase: 'ignoreCase',
io: 'IO',
isarray: 'isArray',
isealed: 'isSealed',
isextensible: 'isExtensible',
isfinite: 'isFinite',
isfrozen: 'isFrozen',
isgenerator: 'isGenerator',
isinteger: 'isInteger',
islockfree: 'isLockFree',
isnan: 'isNaN',
issafeinteger: 'isSafeInteger',
isview: 'isView',
javascript: 'JavaScript',
jquery: 'jQuery',
json: 'JSON',
lastindexof: 'lastIndexOf',
linenumber: 'lineNumber',
ln2: 'LN2',
ln10: 'LN10',
localcompare: 'localCompare',
log2e: 'LOG2E',
log10e: 'LOG10E',
max_safe_integer: 'MAX_SAFE_INTEGER',
max_value: 'MAX_VALUE',
min_safe_integer: 'MIN_SAFE_INTEGER',
min_value: 'MIN_VALUE',
mongodb: 'MongoDB',
nan: 'NaN',
negative_infinity: 'NEGATIVE_INFINITY',
padend: 'padEnd',
padstart: 'padStart',
parsefloat: 'parseFloat',
parseint: 'parseInt',
pi: 'PI',
positive_infinity: 'POSITIVE_INFINITY',
preventextentions: 'preventExtensions',
propertyisenumerable: 'propertyIsEnumerable',
reduceright: 'reduceRight',
regexp: 'RegExp',
setdate: 'setDate',
setfullyear: 'setFullYear',
sethours: 'setHours',
setmilliseconds: 'setMilliseconds',
setminutes: 'setMinutes',
setmonth: 'setMonth',
setprototypeof: 'setPrototypeOf',
setseconds: 'setSeconds',
settime: 'setTime',
setutcdate: 'setUTCDate',
setutcfullyear: 'setUTCFullYear',
setutchours: 'setUTCHours',
setutcmilliseconds: 'setUTCMilliseconds',
setutcminutes: 'setUTCMinutes',
setutcmonth: 'setUTCMonth',
setutcseconds: 'setUTCSeconds',
setyear: 'setYear',
sql: 'SQL',
sqrt1_2: 'SQRT1_2',
sqrt2: 'SQRT2',
startswith: 'startsWith',
todatestring: 'toDateString',
toexponential: 'toExponential',
tofixed: 'toFixed',
toisostring: 'toISOString',
tojson: 'toJSON',
tolocaledatestring: 'toLocaleDateString',
tolocalelowercase: 'toLocaleLowerCase',
tolocalestring: 'toLocaleString',
tolocaletimestring: 'toLocaleTimeString',
tolocaleuppercase: 'toLocaleUpperCase',
tolowercase: 'toLowerCase',
toprecision: 'toPrecision',
tosource: 'toSource',
tostring: 'toString',
totimestring: 'toTimeString',
touppercase: 'toUpperCase',
toutcstring: 'toUTCString',
trimleft: 'trimLeft',
trimRight: 'trimRight',
valueof: 'valueOf',
__definegetter__: '__defineGetter__',
__definesetter__: '__defineSetter__',
__lookupgetter__: '__lookupGetter__',
__lookupsetter__: '__lookupSetter__'
};
const stopWords = [
'a',
'am',
'an',
'and',
'as',
'at',
'but',
'by',
'for',
'from',
'if',
'in',
'into',
'it',
'it\'s',
'its',
'no',
'nor',
'not',
'of',
'off',
'on',
'or',
'the',
'to',
'with'
];
module.exports = {
preFormatted,
stopWords
};

View File

@@ -1,6 +1,9 @@
const { dasherize } = require('..');
const path = require('path');
const select = require('unist-util-select');
const { head } = require('lodash');
const { dasherize } = require('..');
const { isAStubRE } = require('../regEx');
const { viewTypes } = require('../challengeTypes');
@@ -25,6 +28,11 @@ const superBlockIntro = path.resolve(
'../../src/templates/Introduction/SuperBlockIntro.js'
);
const guideArticle = path.resolve(
__dirname,
'../../src/templates/Guide/GuideArticle.js'
);
const views = {
backend,
classic,
@@ -41,7 +49,7 @@ const getTemplateComponent = challengeType => views[viewTypes[challengeType]];
const getIntroIfRequired = (node, index, nodeArray) => {
const next = nodeArray[index + 1];
const isEndOfBlock = next && next.node.suborder === 1;
const isEndOfBlock = next && next.node.challengeOrder === 1;
let nextSuperBlock = '';
let nextBlock = '';
if (next) {
@@ -55,7 +63,13 @@ const getIntroIfRequired = (node, index, nodeArray) => {
};
exports.createChallengePages = createPage => ({ node }, index, thisArray) => {
const { fields: { slug }, required = [], template, challengeType, id } = node;
const {
fields: { slug },
required = [],
template,
challengeType,
id
} = node;
if (challengeType === 7) {
return;
}
@@ -76,28 +90,65 @@ exports.createChallengePages = createPage => ({ node }, index, thisArray) => {
});
};
exports.createIntroPages = createPage => edge => {
const { fields: { slug }, frontmatter: { superBlock, block } } = edge.node;
exports.createBlockIntroPages = createPage => edge => {
const {
fields: { slug },
frontmatter: { block }
} = edge.node;
// If there is no block specified in the markdown we assume the markdown is
// for a superblock introduction. Otherwise create a block intro page.
if (!block) {
createPage({
path: slug,
component: superBlockIntro,
context: {
superBlock: dasherize(superBlock),
slug
}
});
} else {
createPage({
path: slug,
component: intro,
context: {
block: dasherize(block),
slug
}
});
}
createPage({
path: slug,
component: intro,
context: {
block: dasherize(block),
slug
}
});
};
exports.createSuperBlockIntroPages = createPage => edge => {
const {
fields: { slug },
frontmatter: { superBlock }
} = edge.node;
createPage({
path: slug,
component: superBlockIntro,
context: {
superBlock: dasherize(superBlock),
slug
}
});
};
exports.createGuideArticlePages = createPage => ({
node: {
htmlAst,
excerpt,
fields: { slug },
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' +
'/reecodecamp-square-logo-large.jpg';
const description = head(select(htmlAst, 'element[tagName=p]'));
meta.description = description ? description.children[0].value : '';
}
createPage({
path: slug,
component: guideArticle,
context: {
id,
meta
}
});
};

5
client/utils/infoLog.js Normal file
View File

@@ -0,0 +1,5 @@
const chalk = require('chalk');
module.exports = function info(str, colour = 'red') {
console.info(chalk[colour](str));
};

10
client/utils/readDir.js Normal file
View File

@@ -0,0 +1,10 @@
const fse = require('fs-extra');
const { isAFileRE, shouldBeIgnoredRE } = require('./regEx.js');
module.exports = function readDir(dir) {
return fse
.readdirSync(dir)
.filter(item => !isAFileRE.test(item))
.filter(file => !shouldBeIgnoredRE.test(file));
};

6
client/utils/regEx.js Normal file
View File

@@ -0,0 +1,6 @@
exports.httpsRE = /https?\:\/\//;
exports.isAFileRE = /(\.md|\.jsx?|\.html?)$/;
exports.isAStubRE = /This\sis\sa\sstub.+?Help\sour\scommunity\sexpand\sit/;
exports.markdownLinkRE = /\!?\[.*?\]\(.+?\)/g;
exports.metaTitleRE = /^---\r?\ntitle:([^\n]*)\n---$/m;
exports.shouldBeIgnoredRE = /^(\_|\.)/;

72
client/utils/titleify.js Normal file
View File

@@ -0,0 +1,72 @@
const { preFormatted, stopWords } = require('./formatting');
const prototypeRE = /prototype/i;
const prototypesRE = /prototypes/i;
const removeProto = x => x !== 'prototype';
function titleCase(word) {
return word[0].toUpperCase() + word.slice(1);
}
function prototyper(str) {
const formatted = str
.replace(/javascript/i, '')
.split('-')
.map(str => {
if (prototypeRE.test(str)) {
if (str.length > 9) {
return prototyper(
str.trim().split('prototype').join('-prototype-')
);
}
return str;
}
return titleify(str);
})
.join(' ')
.split(' ');
const noProto = formatted
.filter(removeProto)
.filter(x => !!x);
if (noProto.length === 2) {
const [ first, second ] = noProto;
const secondLC = second.toLowerCase();
const finalSecond = preFormatted[secondLC] ?
preFormatted[secondLC] :
secondLC;
return `${titleify(first)}.prototype.${finalSecond}`;
}
if (noProto.length === 1) {
return prototyper(
noProto[0]
.toLowerCase()
.split('.')
.join('-')
);
}
return titleify(str, true);
}
function titleify(str, triedPrototyper) {
if (str.match(prototypeRE) && !triedPrototyper && !prototypesRE.test(str)) {
return prototyper(str);
}
return str
.toLowerCase()
.split('-')
.map((word, i) => {
if (!word) {
return '';
}
if (stopWords.some(x => x === word) && i !== 0) {
return word;
}
return preFormatted[word] ?
preFormatted[word] :
titleCase(word);
})
.join(' ');
}
module.exports = titleify;