initial commit
This commit is contained in:
parent
1314830576
commit
15aae9a57c
5
packages/learn/.eslintignore
Normal file
5
packages/learn/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
public/
|
||||
.cache/
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
static/
|
3
packages/learn/.eslintrc
Normal file
3
packages/learn/.eslintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "freecodecamp"
|
||||
}
|
5
packages/learn/.gitignore
vendored
5
packages/learn/.gitignore
vendored
@ -9,3 +9,8 @@ yarn-error.log
|
||||
|
||||
# Build directory
|
||||
/public
|
||||
static/js
|
||||
|
||||
seed/tmp/
|
||||
seed/transformed
|
||||
seed/ready
|
@ -1,7 +1,22 @@
|
||||
/**
|
||||
* Implement Gatsby's Browser APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/browser-apis/
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'react-router-redux';
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
import { createStore } from './src/redux/store';
|
||||
|
||||
exports.replaceRouterComponent = ({ history }) => {
|
||||
const store = createStore(history || {});
|
||||
|
||||
const ConnectedRouterWrapper = ({ children }) => (
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>{children}</ConnectedRouter>
|
||||
</Provider>
|
||||
);
|
||||
ConnectedRouterWrapper.displayName = 'ConnectedRouterWrapper';
|
||||
ConnectedRouterWrapper.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
return ConnectedRouterWrapper;
|
||||
};
|
||||
|
@ -1,21 +1,33 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const path = require('path');
|
||||
const { getChallengesByFile } = require('../seed/getChallenges');
|
||||
const { buildChallenges$ } = require('./seed/buildChallenges');
|
||||
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: 'freeCodeCamp | Learn to code and help non-profits',
|
||||
title: 'freeCodeCamp | Learn to code and help non-profits ',
|
||||
siteUrl: 'https://learn.freecodecamp.org'
|
||||
},
|
||||
proxy: {
|
||||
prefix: '/',
|
||||
url: 'http://localhost:3000'
|
||||
},
|
||||
plugins: [
|
||||
'gatsby-plugin-react-next',
|
||||
'gatsby-plugin-react-helmet',
|
||||
{
|
||||
resolve: 'fcc-source-challenges',
|
||||
options: {
|
||||
name: 'challenges',
|
||||
path: path.resolve(__dirname, '../seed/challenges'),
|
||||
source: getChallengesByFile
|
||||
path: path.resolve(__dirname, './seed/challenges'),
|
||||
source: buildChallenges$
|
||||
}
|
||||
},
|
||||
'gatsby-transformer-json'
|
||||
{
|
||||
resolve: 'gatsby-plugin-google-fonts',
|
||||
options: {
|
||||
fonts: ['Lato:400,400i,700']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,7 +1,86 @@
|
||||
/**
|
||||
* Implement Gatsby's Node APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/node-apis/
|
||||
*/
|
||||
const path = require('path');
|
||||
const { dasherize } = require('./utils');
|
||||
const { viewTypes } = require('./utils/challengeTypes');
|
||||
const { blockNameify } = require('./utils/blockNameify');
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
const views = {
|
||||
// backend: BackEnd,
|
||||
classic: path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/views/classic/Show.js'
|
||||
),
|
||||
// modern: Modern,
|
||||
project: path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/views/project/Show.js'
|
||||
),
|
||||
// quiz: Quiz,
|
||||
// simple: Project,
|
||||
step: path.resolve(__dirname, './src/templates/Challenges/views/step/Show.js')
|
||||
// invalid: NotFound
|
||||
};
|
||||
|
||||
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
||||
const { createNodeField } = boundActionCreators;
|
||||
if (node.internal.type === 'ChallengeNode') {
|
||||
const { tests = [], block, title, superBlock } = node;
|
||||
|
||||
const slug = `/${dasherize(superBlock)}/${dasherize(block)}/${dasherize(
|
||||
title
|
||||
)}`;
|
||||
createNodeField({ node, name: 'slug', value: slug });
|
||||
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
|
||||
// TODO: Normalise tests to { test: '', testString: ''}?
|
||||
createNodeField({ node, name: 'tests', value: tests });
|
||||
}
|
||||
};
|
||||
|
||||
exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
const { createPage } = boundActionCreators;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Query for all markdown "nodes" and for the slug we previously created.
|
||||
resolve(
|
||||
graphql(`
|
||||
{
|
||||
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
||||
edges {
|
||||
node {
|
||||
challengeType
|
||||
id
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`).then(result => {
|
||||
if (result.errors) {
|
||||
console.log(result.errors);
|
||||
reject(result.errors);
|
||||
}
|
||||
|
||||
// Create challenge pages.
|
||||
result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => {
|
||||
const { fields: { slug }, challengeType, id } = edge.node;
|
||||
const next = thisArray[index + 1];
|
||||
const nextChallengePath = next ? next.node.fields.slug : '/';
|
||||
createPage({
|
||||
path: slug,
|
||||
component: views[viewTypes[challengeType]],
|
||||
context: {
|
||||
challengeMeta: {
|
||||
nextChallengePath,
|
||||
id
|
||||
},
|
||||
slug
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -1,7 +1,18 @@
|
||||
/**
|
||||
* Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/ssr-apis/
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
import { createStore } from './src/redux/store';
|
||||
|
||||
exports.replaceRenderer = ({
|
||||
history,
|
||||
bodyComponent,
|
||||
replaceBodyHTMLString
|
||||
}) => {
|
||||
const store = createStore(history);
|
||||
|
||||
const ConnectedBody = () => (
|
||||
<Provider store={store}>{bodyComponent}</Provider>
|
||||
);
|
||||
replaceBodyHTMLString(renderToString(<ConnectedBody />));
|
||||
};
|
||||
|
@ -4,28 +4,65 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
|
||||
"dependencies": {
|
||||
"adler32": "^0.1.7",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-standalone": "^6.26.0",
|
||||
"brace": "^0.11.1",
|
||||
"chai": "^4.1.2",
|
||||
"codemirror": "^5.36.0",
|
||||
"debug": "^3.1.0",
|
||||
"dotenv": "^5.0.1",
|
||||
"gatsby": "^1.9.238",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-15": "^1.0.5",
|
||||
"gatsby": "^1.9.243",
|
||||
"gatsby-link": "^1.6.39",
|
||||
"gatsby-plugin-google-fonts": "^0.0.4",
|
||||
"gatsby-plugin-react-helmet": "^2.0.8",
|
||||
"gatsby-plugin-react-next": "^1.0.11",
|
||||
"gatsby-source-filesystem": "^1.5.27",
|
||||
"gatsby-source-mongodb": "^1.5.19",
|
||||
"gatsby-transformer-json": "^1.0.16",
|
||||
"lodash": "^4.17.5",
|
||||
"loop-protect": "^2.1.6",
|
||||
"mongodb": "^3.0.5",
|
||||
"react-bootstrap": "^0.32.1",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
"react-helmet": "^5.2.0",
|
||||
"rxjs": "^5.5.7"
|
||||
"react-redux": "^5.0.7",
|
||||
"react-reflex": "^2.2.1",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"react-test-renderer": "^16.3.1",
|
||||
"redux": "^3.7.2",
|
||||
"redux-actions": "^2.3.0",
|
||||
"redux-observable": "^0.18.0",
|
||||
"reselect": "^3.0.1",
|
||||
"rxjs": "^5.5.7",
|
||||
"uglifyjs-webpack-plugin": "^1.2.4"
|
||||
},
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "gatsby build",
|
||||
"develop": "gatsby develop",
|
||||
"format": "prettier --write '{src,plugins}/**/*.js'",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "yarn build:frame-runner && gatsby build",
|
||||
"build:frame-runner": "webpack --config ./webpack-frame-runner.js",
|
||||
"build:loop-protect": "webpack --config ./webpack-loop-protect.js",
|
||||
"develop": "NODE_ENV=production yarn build:frame-runner && gatsby develop",
|
||||
"format": "yarn format:gatsby && yarn format:src",
|
||||
"format:gatsby": "prettier --write './gatsby*.js'",
|
||||
"format:src": "prettier --write './src/**/*.js'",
|
||||
"lint": "yarn lint:gatsby && yarn lint:src",
|
||||
"lint:gatsby": "eslint ./gatsby*.js --fix",
|
||||
"lint:src": "eslint ./src . --fix",
|
||||
"test": "yarn format && yarn lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^8.2.2",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-freecodecamp": "^1.1.1",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-prefer-object-spread": "^1.2.1",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"prettier": "^1.11.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,38 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
function createChallengeNodes(
|
||||
path,
|
||||
pluginOptions = {}
|
||||
) {
|
||||
const { source } = pluginOptions;
|
||||
return new Promise(resolve => {
|
||||
const challengeNodes = source(path)
|
||||
.reduce((nodes, { challenges, name }) => {
|
||||
const challengeNodes = challenges.map(challenge => {
|
||||
const contentDigest = crypto
|
||||
.createHash('md5')
|
||||
.update(JSON.stringify(challenge))
|
||||
.digest('hex');
|
||||
const internal = {
|
||||
contentDigest,
|
||||
type: 'ChallengeNode'
|
||||
};
|
||||
function createChallengeNodes(challenge, reporter) {
|
||||
if (typeof challenge.description[0] !== 'string') {
|
||||
reporter.panic(`
|
||||
|
||||
return JSON.parse(
|
||||
JSON.stringify({
|
||||
id: challenge.id,
|
||||
children: [],
|
||||
parent: null,
|
||||
internal,
|
||||
sourceInstanceName: pluginOptions.name || '__PROGRAMATTIC__',
|
||||
...challenge
|
||||
})
|
||||
);
|
||||
});
|
||||
return nodes.concat(challengeNodes);
|
||||
}, []);
|
||||
resolve(challengeNodes);
|
||||
});
|
||||
${challenge.title} has a description that will break things!
|
||||
|
||||
`);
|
||||
}
|
||||
const contentDigest = crypto
|
||||
.createHash('md5')
|
||||
.update(JSON.stringify(challenge))
|
||||
.digest('hex');
|
||||
const internal = {
|
||||
contentDigest,
|
||||
type: 'ChallengeNode'
|
||||
};
|
||||
|
||||
exports.createChallengeNodes = createChallengeNodes;
|
||||
/* eslint-disable prefer-object-spread/prefer-object-spread */
|
||||
return JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
id: challenge.id + ' >>>> ChallengeNode',
|
||||
children: [],
|
||||
parent: null,
|
||||
internal,
|
||||
sourceInstanceName: 'challenge'
|
||||
},
|
||||
challenge
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
exports.createChallengeNodes = createChallengeNodes;
|
||||
|
@ -1,45 +0,0 @@
|
||||
const { GraphQLString } = require('graphql');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = ({ type, getNodeAndSavePathDependency, pathPrefix = '' }) => {
|
||||
if (type.name !== 'File') {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
publicURL: {
|
||||
type: GraphQLString,
|
||||
args: {},
|
||||
description: 'Copy file to static directory and return public url to it',
|
||||
resolve: (file, fieldArgs, context) => {
|
||||
const details = getNodeAndSavePathDependency(file.id, context.path);
|
||||
const fileName = `${file.name}-${file.internal.contentDigest}${
|
||||
details.ext
|
||||
}`;
|
||||
|
||||
const publicPath = path.join(
|
||||
process.cwd(),
|
||||
'public',
|
||||
'static',
|
||||
fileName
|
||||
);
|
||||
|
||||
if (!fs.existsSync(publicPath)) {
|
||||
fs.copy(details.absolutePath, publicPath, err => {
|
||||
if (err) {
|
||||
console.error(
|
||||
`error copying file from ${
|
||||
details.absolutePath
|
||||
} to ${publicPath}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return `${pathPrefix}/static/${fileName}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
@ -1,12 +1,9 @@
|
||||
const chokidar = require('chokidar');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const { createId, createChallengeNodes } = require('./create-Challenge-nodes');
|
||||
const { createChallengeNodes } = require('./create-Challenge-nodes');
|
||||
|
||||
exports.sourceNodes = (
|
||||
{ boundActionCreators, getNode, reporter },
|
||||
pluginOptions
|
||||
) => {
|
||||
exports.sourceNodes = ({ boundActionCreators, reporter }, pluginOptions) => {
|
||||
if (!(pluginOptions && pluginOptions.path)) {
|
||||
reporter.panic(`
|
||||
"path" is a required option for gatsby-source-filesystem
|
||||
@ -29,8 +26,8 @@ Please use the path to the seed directory.
|
||||
that delivers challenge files to the plugin
|
||||
`);
|
||||
}
|
||||
|
||||
const { createNode, deleteNode } = boundActionCreators;
|
||||
// TODO: Add live seed updates
|
||||
const { createNode } = boundActionCreators;
|
||||
|
||||
let ready = false;
|
||||
|
||||
@ -45,11 +42,14 @@ that delivers challenge files to the plugin
|
||||
'../**/dist/**'
|
||||
]
|
||||
});
|
||||
const { source } = pluginOptions;
|
||||
const createAndProcessNodes = () =>
|
||||
source()
|
||||
.map(nodes => nodes.map(node => createChallengeNodes(node, reporter)))
|
||||
.map(nodes => nodes.map(node => createNode(node)))
|
||||
.subscribe();
|
||||
|
||||
const createAndProcessNodes = path =>
|
||||
createChallengeNodes(path, pluginOptions).then(nodes => nodes.forEach(node => createNode(node))
|
||||
);
|
||||
|
||||
createAndProcessNodes();
|
||||
// For every path that is reported before the 'ready' event, we throw them
|
||||
// into a queue and then flush the queue when 'ready' event arrives.
|
||||
// After 'ready', we handle the 'add' event without putting it into a queue.
|
||||
@ -60,44 +60,20 @@ that delivers challenge files to the plugin
|
||||
return Promise.all(queue.map(createAndProcessNodes));
|
||||
};
|
||||
|
||||
watcher.on('add', path => {
|
||||
if (ready) {
|
||||
reporter.info(`added file at ${path}`);
|
||||
createAndProcessNodes(path).catch(err => reporter.error(err));
|
||||
} else {
|
||||
pathQueue.push(path);
|
||||
}
|
||||
});
|
||||
// watcher.on('change', path => {
|
||||
// reporter.info(`changed file at ${path}`);
|
||||
// createAndProcessNodes().catch(err => reporter.error(err));
|
||||
// });
|
||||
|
||||
watcher.on('change', path => {
|
||||
reporter.info(`changed file at ${path}`);
|
||||
createAndProcessNodes(path).catch(err => reporter.error(err));
|
||||
});
|
||||
|
||||
watcher.on('unlink', path => {
|
||||
reporter.info(`file deleted at ${path}`);
|
||||
const node = getNode(createId(path));
|
||||
// It's possible the file node was never created as sometimes tools will
|
||||
// write and then immediately delete temporary files to the file system.
|
||||
if (node) {
|
||||
deleteNode(node.id, node);
|
||||
}
|
||||
});
|
||||
|
||||
watcher.on('addDir', path => {
|
||||
if (ready) {
|
||||
reporter.info(`added directory at ${path}`);
|
||||
createAndProcessNodes(path).catch(err => reporter.error(err));
|
||||
} else {
|
||||
pathQueue.push(path);
|
||||
}
|
||||
});
|
||||
|
||||
watcher.on('unlinkDir', path => {
|
||||
reporter.info(`directory deleted at ${path}`);
|
||||
const node = getNode(createId(path));
|
||||
deleteNode(node.id, node);
|
||||
});
|
||||
// watcher.on('unlink', path => {
|
||||
// reporter.info(`file deleted at ${path}`);
|
||||
// const node = getNode(createId(path));
|
||||
// // It's possible the file node was never created as sometimes tools will
|
||||
// // write and then immediately delete temporary files to the file system.
|
||||
// if (node) {
|
||||
// deleteNode(node.id, node);
|
||||
// }
|
||||
// });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
watcher.on('ready', () => {
|
||||
|
126
packages/learn/seed/buildChallenges.js
Normal file
126
packages/learn/seed/buildChallenges.js
Normal file
@ -0,0 +1,126 @@
|
||||
/* eslint-disable no-process-exit */
|
||||
require('babel-register');
|
||||
require('dotenv').load();
|
||||
const adler32 = require('adler32');
|
||||
|
||||
const Rx = require('rxjs');
|
||||
const _ = require('lodash');
|
||||
const utils = require('../utils');
|
||||
const getChallenges = require('./getChallenges');
|
||||
const createDebugger = require('debug');
|
||||
|
||||
const log = createDebugger('fcc:seed');
|
||||
// force logger to always output
|
||||
// this may be brittle
|
||||
log.enabled = true;
|
||||
|
||||
const dasherize = utils.dasherize;
|
||||
const nameify = utils.nameify;
|
||||
const Observable = Rx.Observable;
|
||||
|
||||
const arrToString = arr =>
|
||||
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
|
||||
|
||||
exports.buildChallenges$ = function buildChallenges$() {
|
||||
return Observable.from(getChallenges()).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 helpRoom = challengeSpec.helpRoom || 'Help';
|
||||
const time = challengeSpec.time;
|
||||
const isLocked = !!challengeSpec.isLocked;
|
||||
const message = challengeSpec.message;
|
||||
const required = challengeSpec.required || [];
|
||||
const template = challengeSpec.template;
|
||||
const isPrivate = !!challengeSpec.isPrivate;
|
||||
|
||||
// challenge file has no challenges...
|
||||
if (challengeSpec.challenges.length === 0) {
|
||||
return Rx.Observable.of([{ block: 'empty ' + blockName }]);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
challenge.checksum = adler32.sum(
|
||||
Buffer(
|
||||
challenge.title +
|
||||
JSON.stringify(challenge.description) +
|
||||
JSON.stringify(challenge.challengeSeed) +
|
||||
JSON.stringify(challenge.tests)
|
||||
)
|
||||
);
|
||||
|
||||
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.helpRoom = helpRoom;
|
||||
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'
|
||||
]);
|
||||
});
|
||||
});
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,36 +4,6 @@
|
||||
"time": "150 hours",
|
||||
"helpRoom": "Help",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "587d78af367417b2b2512b02",
|
||||
"title": "Get Set for our Responsive Web Design Projects",
|
||||
"description": [
|
||||
"",
|
||||
"",
|
||||
"Our front end development projects will give you a chance to apply the front end skills you've developed up to this point. We'll use a popular browser-based code editor called CodePen.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"These projects are hard. It takes most campers several days to build each project. You will get frustrated. But don't quit. This gets easier with practice.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"When you get stuck, just use the Read-Search-Ask methodology. Don't worry - you've got this.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"You can build these project challenges on your local computer or on a web-based code editor like <a href='https://www.codepen.io'>CodePen.io</a>. As long as your projects are publicly accessible online, you can submit their web addresses so you can claim your verified Responsive Web Design certificate.",
|
||||
"https://codepen.io/accounts/signup/user/free"
|
||||
],
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "Waypoint",
|
||||
"isRequired": false,
|
||||
"challengeType": 7
|
||||
},
|
||||
{
|
||||
"id": "bd7158d8c442eddfaeb5bd18",
|
||||
"title": "Build a Tribute Page",
|
||||
@ -45,7 +15,6 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 1, 2016",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
@ -60,6 +29,16 @@
|
||||
"ru": {
|
||||
"title": "Создайте страницу посвященную тому что вас вдохновляет"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -73,12 +52,21 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {}
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d78af367417b2b2512b04",
|
||||
@ -91,12 +79,21 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {}
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d78b0367417b2b2512b05",
|
||||
@ -109,12 +106,21 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {}
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bd7158d8c242eddfaeb5bd13",
|
||||
@ -126,7 +132,6 @@
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
@ -141,7 +146,20 @@
|
||||
"ru": {
|
||||
"title": "Создайте сайт-портфолио"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"fileName": "01-responsive-web-design/responsive-web-design-projects.json",
|
||||
"superBlock": "responsive-web-design",
|
||||
"superOrder": 1
|
||||
}
|
@ -4,70 +4,6 @@
|
||||
"time": "1 hour",
|
||||
"helpRoom": "Help",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "587d78b0367417b2b2512b07",
|
||||
"title": "Introduction to the Responsive Web Design Challenges",
|
||||
"description": [
|
||||
"",
|
||||
"",
|
||||
"Today, there are many types of devices that can access the web. They range from large desktop computers to small mobile phones. These devices have different screen sizes, resolutions, and processing power.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"Responsive Web Design is an approach to designing web content that responds to the constraints of different devices. The page structure and CSS rules should be flexible to accommodate these differences.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"In general, design the page's CSS to your target audience. If you expect most of your traffic to be from mobile users, take a 'mobile-first' approach. Then add conditional rules for larger screen sizes. If your visitors are desktop users, then design for larger screens with conditional rules for smaller sizes.",
|
||||
"",
|
||||
"@@STEP@@",
|
||||
"",
|
||||
"",
|
||||
"CSS gives you the tools to write different style rules, then apply them depending on the device displaying the page. This section will cover the basic ways to use CSS for Responsive Web Design.",
|
||||
"",
|
||||
"@@STEP@@"
|
||||
],
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "waypoint",
|
||||
"challengeType": 7,
|
||||
"isRequired": false,
|
||||
"translations": {
|
||||
"pt-br": {
|
||||
"title": "Introdução aos Desafios de Web Design Responsivo",
|
||||
"description": [
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"Hoje em dia há muitos dispositivos que podem acessar a web. Eles vão de computadores grandes de mesa até os pequenos celulares. Estes dispositivos tem diferentes tamanhos de tela, resoluções e capacidade de processamento.",
|
||||
""
|
||||
],
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"O Web Design Responsivo é uma abordagem para o design the conteúdo para a web que responde às limitações dos diferentes dispositivos. A estrutura das páginas e as regras CSS devem ser flexíveis para acomodar estas diferenças.",
|
||||
""
|
||||
],
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"Em geral, foque o design do CSS de sua página em seu público alvo. Se você espera que a maior parte dos seus usuários utilizem seus celulares, use uma abordagem 'mobile-first.' E então adicione regras condicionais para as telas maiores. Se seus usuários utilizam computadores de mesa, foque o design em telas maiores e utilize regras condicionais para tamanhos menores.",
|
||||
""
|
||||
],
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"CSS lhe dá as ferramentas para escrever regras de estilo diversas, e então aplicá-las dependendo do dispositivo que estiver acessando a página. Esta seção cobrirá as maneiras básicas de utilizar CSS para o Web Design Responsivo.",
|
||||
""
|
||||
]
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d78b0367417b2b2512b08",
|
||||
"title": "Create a Media Query",
|
||||
@ -82,21 +18,15 @@
|
||||
"<hr>",
|
||||
"Add a media query, so that the <code>p</code> tag has a <code>font-size</code> of 10px when the device's height is less than or equal to 800px."
|
||||
],
|
||||
"challengeSeed": [
|
||||
"<style>",
|
||||
" p {",
|
||||
" font-size: 20px;",
|
||||
" }",
|
||||
" ",
|
||||
" /* Add media query below */",
|
||||
" ",
|
||||
"</style>",
|
||||
" ",
|
||||
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus quis tempus massa. Aenean erat nisl, gravida vel vestibulum cursus, interdum sit amet lectus. Sed sit amet quam nibh. Suspendisse quis tincidunt nulla. In hac habitasse platea dictumst. Ut sit amet pretium nisl. Vivamus vel mi sem. Aenean sit amet consectetur sem. Suspendisse pretium, purus et gravida consequat, nunc ligula ultricies diam, at aliquet velit libero a dui.</p>"
|
||||
],
|
||||
"tests": [
|
||||
"assert($('p').css('font-size') == '10px', 'message: Your <code>p</code> element should have the <code>font-size</code> of 10px when the device <code>height</code> is less than or equal to 800px.');",
|
||||
"assert(code.match(/@media\\s?\\(max-height:\\s*?800px\\)/g), 'message: Declare a <code>@media</code> query for devices with a <code>height</code> less than or equal to 800px.');"
|
||||
{
|
||||
"text": "Your <code>p</code> element should have the <code>font-size</code> of 10px when the device <code>height</code> is less than or equal to 800px.'",
|
||||
"testString": "assert($('p').css('font-size') == '10px', 'Your <code>p</code> element should have the <code>font-size</code> of 10px when the device <code>height</code> is less than or equal to 800px.')"
|
||||
},
|
||||
{
|
||||
"text": "Declare a <code>@media</code> query for devices with a <code>height</code> less than or equal to 800px.'",
|
||||
"testString": "assert(code.match(/@media\\s?\\(max-height:\\s*?800px\\)/g), 'Declare a <code>@media</code> query for devices with a <code>height</code> less than or equal to 800px.')"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
@ -116,6 +46,27 @@
|
||||
"Adicione uma media query para que o tag <code>p</code> tenha um <code>font-size</code> de 10px quando a altura do dispositivo for menor ou igual a 800px."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style>",
|
||||
" p {",
|
||||
" font-size: 20px;",
|
||||
" }",
|
||||
" ",
|
||||
" /* Add media query below */",
|
||||
" ",
|
||||
"</style>",
|
||||
" ",
|
||||
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus quis tempus massa. Aenean erat nisl, gravida vel vestibulum cursus, interdum sit amet lectus. Sed sit amet quam nibh. Suspendisse quis tincidunt nulla. In hac habitasse platea dictumst. Ut sit amet pretium nisl. Vivamus vel mi sem. Aenean sit amet consectetur sem. Suspendisse pretium, purus et gravida consequat, nunc ligula ultricies diam, at aliquet velit libero a dui.</p>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -130,17 +81,19 @@
|
||||
"<hr>",
|
||||
"Add style rules for the <code>img</code> tag to make it responsive to the size of its container. It should display as a block-level element, it should fit the full width of its container without stretching, and it should keep its original aspect ratio."
|
||||
],
|
||||
"challengeSeed": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<img src=\"https://s3.amazonaws.com/freecodecamp/FCCStickerPack.jpg\" alt=\"freeCodeCamp stickers set\">"
|
||||
],
|
||||
"tests": [
|
||||
"assert(code.match(/max-width:\\s*?100%;/g), 'message: Your <code>img</code> tag should have a <code>max-width</code> set to 100%.');",
|
||||
"assert($('img').css('display') == 'block', 'message: Your <code>img</code> tag should have a <code>display</code> set to block.');",
|
||||
"assert(code.match(/height:\\s*?auto;/g), 'message: Your <code>img</code> tag should have a <code>height</code> set to auto.');"
|
||||
{
|
||||
"text": "Your <code>img</code> tag should have a <code>max-width</code> set to 100%.'",
|
||||
"testString": "assert(code.match(/max-width:\\s*?100%;/g), 'Your <code>img</code> tag should have a <code>max-width</code> set to 100%.')"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>img</code> tag should have a <code>display</code> set to block.'",
|
||||
"testString": "assert($('img').css('display') == 'block', 'Your <code>img</code> tag should have a <code>display</code> set to block.')"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>img</code> tag should have a <code>height</code> set to auto.'",
|
||||
"testString": "assert(code.match(/height:\\s*?auto;/g), 'Your <code>img</code> tag should have a <code>height</code> set to auto.')"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
@ -160,6 +113,22 @@
|
||||
"Adicione regras de estilo para a tag <code>img</code> para torná-la responsiva com relação ao tamanho do seu container. Ela deve ser exibida como um elemento de nível block, e deve preencher toda a largura de seu container sem esticar, mantendo as proporções originais."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<img src=\"https://s3.amazonaws.com/freecodecamp/FCCStickerPack.jpg\" alt=\"freeCodeCamp stickers set\">"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -172,16 +141,15 @@
|
||||
"<hr>",
|
||||
"Set the <code>width</code> and <code>height</code> of the <code>img</code> tag to half of their original values. In this case, both the original <code>height</code> and the original <code>width</code> are 200px."
|
||||
],
|
||||
"challengeSeed": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<img src=\"https://s3.amazonaws.com/freecodecamp/FCCStickers-CamperBot200x200.jpg\" alt=\"freeCodeCamp sticker that says 'Because CamperBot Cares'\">"
|
||||
],
|
||||
"tests": [
|
||||
"assert($('img').css('width') == '100px', 'message: Your <code>img</code> tag should have a <code>width</code> of 100 pixels.');",
|
||||
"assert($('img').css('height') == '100px', 'message: Your <code>img</code> tag should have a <code>height</code> of 100 pixels.');"
|
||||
{
|
||||
"text": "Your <code>img</code> tag should have a <code>width</code> of 100 pixels.'",
|
||||
"testString": "assert($('img').css('width') == '100px', 'Your <code>img</code> tag should have a <code>width</code> of 100 pixels.')"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>img</code> tag should have a <code>height</code> of 100 pixels.'",
|
||||
"testString": "assert($('img').css('height') == '100px', 'Your <code>img</code> tag should have a <code>height</code> of 100 pixels.')"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
@ -199,6 +167,22 @@
|
||||
"Configure os valores de <code>width</code> e <code>height</code> da tag <code>img</code> como metade do seu tamanho original. Nesse caso, o valor original de <code>height</code> e o valor original de <code>width</code> são de 200px."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<img src=\"https://s3.amazonaws.com/freecodecamp/FCCStickers-CamperBot200x200.jpg\" alt=\"freeCodeCamp sticker that says 'Because CamperBot Cares'\">"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -211,17 +195,15 @@
|
||||
"<hr>",
|
||||
"Set the <code>width</code> of the <code>h2</code> tag to 80% of the viewport's width and the <code>width</code> of the paragraph as 75% of the viewport's smaller dimension."
|
||||
],
|
||||
"challengeSeed": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<h2>Importantus Ipsum</h2>",
|
||||
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus quis tempus massa. Aenean erat nisl, gravida vel vestibulum cursus, interdum sit amet lectus. Sed sit amet quam nibh. Suspendisse quis tincidunt nulla. In hac habitasse platea dictumst. Ut sit amet pretium nisl. Vivamus vel mi sem. Aenean sit amet consectetur sem. Suspendisse pretium, purus et gravida consequat, nunc ligula ultricies diam, at aliquet velit libero a dui.</p>"
|
||||
],
|
||||
"tests": [
|
||||
"assert(code.match(/h2\\s*?{\\s*?width:\\s*?80vw;\\s*?}/g), 'message: Your <code>h2</code> tag should have a <code>width</code> of 80vw.');",
|
||||
"assert(code.match(/p\\s*?{\\s*?width:\\s*?75vmin;\\s*?}/g), 'message: Your <code>p</code> tag should have a <code>width</code> of 75vmin.');"
|
||||
{
|
||||
"text": "Your <code>h2</code> tag should have a <code>width</code> of 80vw.'",
|
||||
"testString": "assert(code.match(/h2\\s*?{\\s*?width:\\s*?80vw;\\s*?}/g), 'Your <code>h2</code> tag should have a <code>width</code> of 80vw.')"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>p</code> tag should have a <code>width</code> of 75vmin.'",
|
||||
"testString": "assert(code.match(/p\\s*?{\\s*?width:\\s*?75vmin;\\s*?}/g), 'Your <code>p</code> tag should have a <code>width</code> of 75vmin.')"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
@ -239,7 +221,27 @@
|
||||
"Ajuste o <code>width</code> da tag <code>h2</code> para 80% da largura da janela de exibição e a <code>width</code> do parágrafo para 75% da menor dimensão da janela de exibição."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style>",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<h2>Importantus Ipsum</h2>",
|
||||
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus quis tempus massa. Aenean erat nisl, gravida vel vestibulum cursus, interdum sit amet lectus. Sed sit amet quam nibh. Suspendisse quis tincidunt nulla. In hac habitasse platea dictumst. Ut sit amet pretium nisl. Vivamus vel mi sem. Aenean sit amet consectetur sem. Suspendisse pretium, purus et gravida consequat, nunc ligula ultricies diam, at aliquet velit libero a dui.</p>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fileName": "01-responsive-web-design/responsive-web-design.json",
|
||||
"superBlock": "responsive-web-design",
|
||||
"superOrder": 1
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,599 @@
|
||||
{
|
||||
"name": "Debugging",
|
||||
"order": 4,
|
||||
"time": "1 hour",
|
||||
"helpRoom": "Help",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "587d7b83367417b2b2512b33",
|
||||
"title": "Use the JavaScript Console to Check the Value of a Variable",
|
||||
"description": [
|
||||
"Both Chrome and Firefox have excellent JavaScript consoles, also known as DevTools, for debugging your JavaScript.",
|
||||
"You can find Developer tools in your Chrome's menu or Web Console in FireFox's menu. If you're using a different browser, or a mobile phone, we strongly recommend switching to desktop Firefox or Chrome.",
|
||||
"The <code>console.log()</code> method, which \"prints\" the output of what's within its parentheses to the console, will likely be the most helpful debugging tool. Placing it at strategic points in your code can show you the intermediate values of variables. It's good practice to have an idea of what the output should be before looking at what it is. Having check points to see the status of your calculations throughout your code will help narrow down where the problem is.",
|
||||
"Here's an example to print 'Hello world!' to the console:",
|
||||
"<code>console.log('Hello world!');</code>",
|
||||
"<hr>",
|
||||
"Use the <code>console.log()</code> method to print the value of the variable <code>a</code> where noted in the code."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use <code>console.log()</code> to check the value of the variable <code>a</code>.'",
|
||||
"testString": "assert(code.match(/console\\.log\\(a\\)/g), 'Your code should use <code>console.log()</code> to check the value of the variable <code>a</code>.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let a = 5;",
|
||||
"let b = 1;",
|
||||
"a++;",
|
||||
"// Add your code below this line",
|
||||
"",
|
||||
"",
|
||||
"let sumAB = a + b;",
|
||||
"console.log(sumAB);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b83367417b2b2512b37",
|
||||
"title": "Understanding the Differences between the freeCodeCamp and Browser Console",
|
||||
"description": [
|
||||
"You may have noticed that some freeCodeCamp JavaScript challenges include their own console. This console behaves a little differently than the browser console you used in the last challenge.",
|
||||
"The following challenge is meant to highlight some of the differences between the freeCodeCamp console and the browser console.",
|
||||
"First, the browser console. When you load and run an ordinary JavaScript file in your browser the <code>console.log()</code> statements will print exactly what you tell them to print to the browser console the exact number of times you requested. In your in-browser text editor the process is slightly different and can be confusing at first.",
|
||||
"Values passed to <code>console.log()</code> in the text editor block run each set of tests as well as one more time for any function calls that you have in you code.",
|
||||
"This lends itself to some interesting behavior and might trip you up in the beginning, because a logged value that you expect to see only once may print out many more times depending on the number of tests and the values being passed to those tests.",
|
||||
"If you would like to see only your single output and not have to worry about running through the test cycles, you can use <code>console.clear()</code>.",
|
||||
"<hr>",
|
||||
"Use <code>console.log()</code> to print the variables in the code where indicated.",
|
||||
""
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Use <code>console.log()</code> to print the <code>outputTwice</code> variable. In your Browser Console this should print out the value of the variable two times.'",
|
||||
"testString": "assert(code.match(/console\\.log\\(outputTwo\\)/g), 'Use <code>console.log()</code> to print the <code>outputTwice</code> variable. In your Browser Console this should print out the value of the variable two times.')"
|
||||
},
|
||||
{
|
||||
"text": "Use <code>console.log()</code> to print the <code>outputOne</code> variable.'",
|
||||
"testString": "assert(code.match(/console\\.log\\(outputOne\\)/g), 'Use <code>console.log()</code> to print the <code>outputOne</code> variable.')"
|
||||
},
|
||||
{
|
||||
"text": "Use <code>console.clear()</code> to modify your output so that <code>outputOne</code> variable only outputs once.'",
|
||||
"testString": "assert(code.match(/console\\.clear\\(\\)/g), 'Use <code>console.clear()</code> to modify your output so that <code>outputOne</code> variable only outputs once.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// Open your browser console",
|
||||
"let outputTwo = \"This will print to the browser console 2 times\";",
|
||||
"// Use console.log() to print the outputTwo variable",
|
||||
"",
|
||||
"",
|
||||
"let outputOne = \"Try to get this to log only once to the browser console\";",
|
||||
"// Use console.clear() in the next line to print the outputOne only once",
|
||||
"",
|
||||
"",
|
||||
"// Use console.log() to print the outputOne variable",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b84367417b2b2512b34",
|
||||
"title": "Use typeof to Check the Type of a Variable",
|
||||
"description": [
|
||||
"You can use <code>typeof</code> to check the data structure, or type, of a variable. This is useful in debugging when working with multiple data types. If you think you're adding two numbers, but one is actually a string, the results can be unexpected. Type errors can lurk in calculations or function calls. Especially take care when you're accessing and working with external data in the form of a JavaScript object (JSON).",
|
||||
"Here are some examples using <code>typeof</code>:",
|
||||
"<blockquote>console.log(typeof \"\"); // outputs \"string\"<br>console.log(typeof 0); // outputs \"number\"<br>console.log(typeof []); // outputs \"object\"<br>console.log(typeof {}); // outputs \"object\"</blockquote>",
|
||||
"JavaScript recognizes six primitive (immutable) data types: <code>Boolean</code>, <code>Null</code>, <code>Undefined</code>, <code>Number</code>, <code>String</code>, and <code>Symbol</code> (new with ES6) and one type for mutable items: <code>Object</code>. Note that in JavaScript, arrays are technically a type of object.",
|
||||
"<hr>",
|
||||
"Add two <code>console.log()</code> statements to check the <code>typeof</code> each of the two variables <code>seven</code> and <code>three</code> in the code."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use <code>typeof</code> in two <code>console.log()</code> statements to check the type of the variables.'",
|
||||
"testString": "assert(code.match(/console\\.log\\(typeof[\\( ].*\\)?\\)/g).length == 2, 'Your code should use <code>typeof</code> in two <code>console.log()</code> statements to check the type of the variables.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should use <code>typeof</code> to check the type of the variable <code>seven</code>.'",
|
||||
"testString": "assert(code.match(/typeof[\\( ]seven\\)?/g), 'Your code should use <code>typeof</code> to check the type of the variable <code>seven</code>.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should use <code>typeof</code> to check the type of the variable <code>three</code>.'",
|
||||
"testString": "assert(code.match(/typeof[\\( ]three\\)?/g), 'Your code should use <code>typeof</code> to check the type of the variable <code>three</code>.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let seven = 7;",
|
||||
"let three = \"3\";",
|
||||
"console.log(seven + three);",
|
||||
"// Add your code below this line",
|
||||
""
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b84367417b2b2512b35",
|
||||
"title": "Catch Misspelled Variable and Function Names",
|
||||
"description": [
|
||||
"The <code>console.log()</code> and <code>typeof</code> methods are the two primary ways to check intermediate values and types of program output. Now it's time to get into the common forms that bugs take. One syntax-level issue that fast typers can commiserate with is the humble spelling error.",
|
||||
"Transposed, missing, or mis-capitalized characters in a variable or function name will have the browser looking for an object that doesn't exist - and complain in the form of a reference error. JavaScript variable and function names are case-sensitive.",
|
||||
"<hr>",
|
||||
"Fix the two spelling errors in the code so the <code>netWorkingCapital</code> calculation works."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Check the spelling of the two variables used in the netWorkingCapital calculation, the console output should show that \"Net working capital is: 2\".'",
|
||||
"testString": "assert(netWorkingCapital === 2, 'Check the spelling of the two variables used in the netWorkingCapital calculation, the console output should show that \"Net working capital is: 2\".')"
|
||||
},
|
||||
{
|
||||
"text": "There should be no instances of mis-spelled variables in the code.'",
|
||||
"testString": "assert(!code.match(/recievables/g), 'There should be no instances of mis-spelled variables in the code.')"
|
||||
},
|
||||
{
|
||||
"text": "The <code>receivables</code> variable should be declared and used properly in the code.'",
|
||||
"testString": "assert(code.match(/receivables/g).length == 2, 'The <code>receivables</code> variable should be declared and used properly in the code.')"
|
||||
},
|
||||
{
|
||||
"text": "There should be no instances of mis-spelled variables in the code.'",
|
||||
"testString": "assert(!code.match(/payable;/g), 'There should be no instances of mis-spelled variables in the code.')"
|
||||
},
|
||||
{
|
||||
"text": "The <code>payables</code> variable should be declared and used properly in the code.'",
|
||||
"testString": "assert(code.match(/payables/g).length == 2, 'The <code>payables</code> variable should be declared and used properly in the code.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let receivables = 10;",
|
||||
"let payables = 8;",
|
||||
"let netWorkingCapital = recievables - payable;",
|
||||
"console.log(`Net working capital is: ${netWorkingCapital}`);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b84367417b2b2512b36",
|
||||
"title": "Catch Unclosed Parentheses, Brackets, Braces and Quotes",
|
||||
"description": [
|
||||
"Another syntax error to be aware of is that all opening parentheses, brackets, curly braces, and quotes have a closing pair. Forgetting a piece tends to happen when you're editing existing code and inserting items with one of the pair types. Also, take care when nesting code blocks into others, such as adding a callback function as an argument to a method.",
|
||||
"One way to avoid this mistake is as soon as the opening character is typed, immediately include the closing match, then move the cursor back between them and continue coding. Fortunately, most modern code editors generate the second half of the pair automatically.",
|
||||
"<hr>",
|
||||
"Fix the two pair errors in the code."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should fix the missing piece of the array.'",
|
||||
"testString": "assert(code.match(/myArray\\s*?=\\s*?\\[\\s*?1\\s*?, 'Your code should fix the missing piece of the array.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should fix the missing piece of the <code>.reduce()</code> method. The console output should show that \"Sum of array values is: 6\".'",
|
||||
"testString": "assert(arraySum === 6, 'Your code should fix the missing piece of the <code>.reduce()</code> method. The console output should show that \"Sum of array values is: 6\".')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let myArray = [1, 2, 3;",
|
||||
"let arraySum = myArray.reduce((previous, current => previous + current);",
|
||||
"console.log(`Sum of array values is: ${arraySum}`);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b84367417b2b2512b37",
|
||||
"title": "Catch Mixed Usage of Single and Double Quotes",
|
||||
"description": [
|
||||
"JavaScript allows the use of both single ('') and double (\"\") quotes to declare a string. Deciding which one to use generally comes down to personal preference, with some exceptions.",
|
||||
"Having two choices is great when a string has contractions or another piece of text that's in quotes. Just be careful that you don't close the string too early, which causes a syntax error.",
|
||||
"Here are some examples of mixing quotes:",
|
||||
"<blockquote>// These are correct:<br>const grouchoContraction = \"I've had a perfectly wonderful evening, but this wasn't it.\";<br>const quoteInString = \"Groucho Marx once said 'Quote me as saying I was mis-quoted.'\";<br>// This is incorrect:<br>const uhOhGroucho = 'I've had a perfectly wonderful evening, but this wasn't it.';</blockquote>",
|
||||
"Of course, it is okay to use only one style of quotes. You can escape the quotes inside the string by using the backslash (\\) escape character:",
|
||||
"<blockquote>// Correct use of same quotes:<br>const allSameQuotes = 'I\\'ve had a perfectly wonderful evening, but this wasn\\'t it.';</blockquote>",
|
||||
"<hr>",
|
||||
"Fix the string so it either uses different quotes for the <code>href</code> value, or escape the existing ones. Keep the double quote marks around the entire string."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should fix the quotes around the <code>href</code> value \"#Home\" by either changing or escaping them.'",
|
||||
"testString": "assert(code.match(/<a href=\\s*?('|\\\\\")#Home\\1\\s*?>/g), 'Your code should fix the quotes around the <code>href</code> value \"#Home\" by either changing or escaping them.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should keep the double quotes around the entire string.'",
|
||||
"testString": "assert(code.match(/\"<p>.*?<\\/p>\";/g), 'Your code should keep the double quotes around the entire string.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let innerHtml = \"<p>Click here to <a href=\"#Home\">return home</a></p>\";",
|
||||
"console.log(innerHtml);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b85367417b2b2512b38",
|
||||
"title": "Catch Use of Assignment Operator Instead of Equality Operator",
|
||||
"description": [
|
||||
"Branching programs, i.e. ones that do different things if certain conditions are met, rely on <code>if</code>, <code>else if</code>, and <code>else</code> statements in JavaScript. The condition sometimes takes the form of testing whether a result is equal to a value.",
|
||||
"This logic is spoken (in English, at least) as \"if x equals y, then ...\" which can literally translate into code using the <code>=</code>, or assignment operator. This leads to unexpected control flow in your program.",
|
||||
"As covered in previous challenges, the assignment operator (<code>=</code>) in JavaScript assigns a value to a variable name. And the <code>==</code> and <code>===</code> operators check for equality (the triple <code>===</code> tests for strict equality, meaning both value and type are the same).",
|
||||
"The code below assigns <code>x</code> to be 2, which evaluates as <code>true</code>. Almost every value on its own in JavaScript evaluates to <code>true</code>, except what are known as the \"falsy\" values: <code>false</code>, <code>0</code>, <code>\"\"</code> (an empty string), <code>NaN</code>, <code>undefined</code>, and <code>null</code>.",
|
||||
"<blockquote>let x = 1;<br>let y = 2;<br>if (x = y) {<br> // this code block will run for any value of y (unless y were originally set as a falsy)<br>} else {<br> // this code block is what should run (but won't) in this example<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"Fix the condition so the program runs the right branch, and the appropriate value is assigned to <code>result</code>."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should fix the condition so it checks for equality, instead of using assignment.'",
|
||||
"testString": "assert(result == \"Not equal!\", 'Your code should fix the condition so it checks for equality, instead of using assignment.')"
|
||||
},
|
||||
{
|
||||
"text": "The condition can use either <code>==</code> or <code>===</code> to test for equality.'",
|
||||
"testString": "assert(code.match(/x\\s*?===?\\s*?y/g), 'The condition can use either <code>==</code> or <code>===</code> to test for equality.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"let x = 7;",
|
||||
"let y = 9;",
|
||||
"let result = \"to come\";",
|
||||
"",
|
||||
"if(x = y) {",
|
||||
" result = \"Equal!\";",
|
||||
"} else {",
|
||||
" result = \"Not equal!\";",
|
||||
"}",
|
||||
"",
|
||||
"console.log(result);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b85367417b2b2512b39",
|
||||
"title": "Catch Missing Open and Closing Parenthesis After a Function Call",
|
||||
"description": [
|
||||
"When a function or method doesn't take any arguments, you may forget to include the (empty) opening and closing parentheses when calling it. Often times the result of a function call is saved in a variable for other use in your code. This error can be detected by logging variable values (or their types) to the console and seeing that one is set to a function reference, instead of the expected value the function returns.",
|
||||
"The variables in the following example are different:",
|
||||
"<blockquote>function myFunction() {<br> return \"You rock!\";<br>}<br>let varOne = myFunction; // set to equal a function<br>let varTwo = myFunction(); // set to equal the string \"You rock!\"</blockquote>",
|
||||
"<hr>",
|
||||
"Fix the code so the variable <code>result</code> is set to the value returned from calling the function <code>getNine</code>."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should fix the variable <code>result</code> so it is set to the number that the function <code>getNine</code> returns.'",
|
||||
"testString": "assert(result == 9, 'Your code should fix the variable <code>result</code> so it is set to the number that the function <code>getNine</code> returns.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should call the <code>getNine</code> function.'",
|
||||
"testString": "assert(code.match(/getNine\\(\\)/g).length == 2, 'Your code should call the <code>getNine</code> function.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function getNine() {",
|
||||
" let x = 6;",
|
||||
" let y = 3;",
|
||||
" return x + y;",
|
||||
"}",
|
||||
"",
|
||||
"let result = getNine;",
|
||||
"console.log(result);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b85367417b2b2512b3a",
|
||||
"title": "Catch Arguments Passed in the Wrong Order When Calling a Function",
|
||||
"description": [
|
||||
"Continuing the discussion on calling functions, the next bug to watch out for is when a function's arguments are supplied in the incorrect order. If the arguments are different types, such as a function expecting an array and an integer, this will likely throw a runtime error. If the arguments are the same type (all integers, for example), then the logic of the code won't make sense. Make sure to supply all required arguments, in the proper order to avoid these issues.",
|
||||
"<hr>",
|
||||
"The function <code>raiseToPower</code> raises a base to an exponent. Unfortunately, it's not called properly - fix the code so the value of <code>power</code> is the expected 8."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should fix the variable <code>power</code> so it equals 2 raised to the 3rd power, not 3 raised to the 2nd power.'",
|
||||
"testString": "assert(power == 8, 'Your code should fix the variable <code>power</code> so it equals 2 raised to the 3rd power, not 3 raised to the 2nd power.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should use the correct order of the arguments for the <code>raiseToPower</code> function call.'",
|
||||
"testString": "assert(code.match(/raiseToPower\\(\\s*?base\\s*?, 'Your code should use the correct order of the arguments for the <code>raiseToPower</code> function call.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function raiseToPower(b, e) {",
|
||||
" return Math.pow(b, e);",
|
||||
"}",
|
||||
"",
|
||||
"let base = 2;",
|
||||
"let exp = 3;",
|
||||
"let power = raiseToPower(exp, base);",
|
||||
"console.log(power);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b86367417b2b2512b3b",
|
||||
"title": "Catch Off By One Errors When Using Indexing",
|
||||
"description": [
|
||||
"<code>Off by one errors</code> (sometimes called OBOE) crop up when you're trying to target a specific index of a string or array (to slice or access a segment), or when looping over the indices of them. JavaScript indexing starts at zero, not one, which means the last index is always one less than the length of the item. If you try to access an index equal to the length, the program may throw an \"index out of range\" reference error or print <code>undefined</code>.",
|
||||
"When you use string or array methods that take index ranges as arguments, it helps to read the documentation and understand if they are inclusive (the item at the given index is part of what's returned) or not. Here are some examples of off by one errors:",
|
||||
"<blockquote>let alphabet = \"abcdefghijklmnopqrstuvwxyz\";<br>let len = alphabet.length;<br>for (let i = 0; i <= len; i++) {<br> // loops one too many times at the end<br> console.log(alphabet[i]);<br>}<br>for (let j = 1; j < len; j++) {<br> // loops one too few times and misses the first character at index 0<br> console.log(alphabet[j]);<br>}<br>for (let k = 0; k < len; k++) {<br> // Goldilocks approves - this is just right<br> console.log(alphabet[k]);<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"Fix the two indexing errors in the following function so all the numbers 1 through 5 are printed to the console."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should set the initial condition of the loop so it starts at the first index.'",
|
||||
"testString": "assert(code.match(/i\\s*?=\\s*?0\\s*?;/g).length == 1, 'Your code should set the initial condition of the loop so it starts at the first index.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should fix the initial condition of the loop so that the index starts at 0.'",
|
||||
"testString": "assert(!code.match(/i\\s?=\\s*?1\\s*?;/g), 'Your code should fix the initial condition of the loop so that the index starts at 0.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should set the terminal condition of the loop so it stops at the last index.'",
|
||||
"testString": "assert(code.match(/i\\s*?<\\s*?len\\s*?;/g).length == 1, 'Your code should set the terminal condition of the loop so it stops at the last index.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should fix the terminal condition of the loop so that it stops at 1 before the length.'",
|
||||
"testString": "assert(!code.match(/i\\s*?<=\\s*?len;/g), 'Your code should fix the terminal condition of the loop so that it stops at 1 before the length.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function countToFive() {",
|
||||
" let firstFive = \"12345\";",
|
||||
" let len = firstFive.length;",
|
||||
" // Fix the line below",
|
||||
" for (let i = 1; i <= len; i++) {",
|
||||
" // Do not alter code below this line",
|
||||
" console.log(firstFive[i]);",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"countToFive();"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b86367417b2b2512b3c",
|
||||
"title": "Use Caution When Reinitializing Variables Inside a Loop",
|
||||
"description": [
|
||||
"Sometimes it's necessary to save information, increment counters, or re-set variables within a loop. A potential issue is when variables either should be reinitialized, and aren't, or vice versa. This is particularly dangerous if you accidentally reset the variable being used for the terminal condition, causing an infinite loop.",
|
||||
"Printing variable values with each cycle of your loop by using <code>console.log()</code> can uncover buggy behavior related to resetting, or failing to reset a variable.",
|
||||
"<hr>",
|
||||
"The following function is supposed to create a two-dimensional array with <code>m</code> rows and <code>n</code> columns of zeroes. Unfortunately, it's not producing the expected output because the <code>row</code> variable isn't being reinitialized (set back to an empty array) in the outer loop. Fix the code so it returns a correct 3x2 array of zeroes, which looks like <code>[[0, 0], [0, 0], [0, 0]]</code>."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should set the <code>matrix</code> variable to an array holding 3 rows of 2 columns of zeroes each.'",
|
||||
"testString": "assert(JSON.stringify(matrix) == \"[[0, 'Your code should set the <code>matrix</code> variable to an array holding 3 rows of 2 columns of zeroes each.')"
|
||||
},
|
||||
{
|
||||
"text": "The <code>matrix</code> variable should have 3 rows.'",
|
||||
"testString": "assert(matrix.length == 3, 'The <code>matrix</code> variable should have 3 rows.')"
|
||||
},
|
||||
{
|
||||
"text": "The <code>matrix</code> variable should have 2 columns in each row.'",
|
||||
"testString": "assert(matrix[0].length == 2 && matrix[1].length === 2 && matrix[2].length === 2, 'The <code>matrix</code> variable should have 2 columns in each row.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function zeroArray(m, n) {",
|
||||
" // Creates a 2-D array with m rows and n columns of zeroes",
|
||||
" let newArray = [];",
|
||||
" let row = [];",
|
||||
" for (let i = 0; i < m; i++) {",
|
||||
" // Adds the m-th row into newArray",
|
||||
" ",
|
||||
" for (let j = 0; j < n; j++) {",
|
||||
" // Pushes n zeroes into the current row to create the columns",
|
||||
" row.push(0);",
|
||||
" }",
|
||||
" // Pushes the current row, which now has n zeroes in it, to the array",
|
||||
" newArray.push(row);",
|
||||
" }",
|
||||
" return newArray;",
|
||||
"}",
|
||||
"",
|
||||
"let matrix = zeroArray(3, 2);",
|
||||
"console.log(matrix);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7b86367417b2b2512b3d",
|
||||
"title": "Prevent Infinite Loops with a Valid Terminal Condition",
|
||||
"description": [
|
||||
"The final topic is the dreaded infinite loop. Loops are great tools when you need your program to run a code block a certain number of times or until a condition is met, but they need a terminal condition that ends the looping. Infinite loops are likely to freeze or crash the browser, and cause general program execution mayhem, which no one wants.",
|
||||
"There was an example of an infinite loop in the introduction to this section - it had no terminal condition to break out of the <code>while</code> loop inside <code>loopy()</code>. Do NOT call this function!",
|
||||
"<blockquote>function loopy() {<br> while(true) {<br> console.log(\"Hello, world!\");<br> }<br>}</blockquote>",
|
||||
"It's the programmer's job to ensure that the terminal condition, which tells the program when to break out of the loop code, is eventually reached. One error is incrementing or decrementing a counter variable in the wrong direction from the terminal condition. Another one is accidentally resetting a counter or index variable within the loop code, instead of incrementing or decrementing it.",
|
||||
"<hr>",
|
||||
"The <code>myFunc()</code> function contains an infinite loop because the terminal condition <code>i != 4</code> will never evaluate to <code>false</code> (and break the looping) - <code>i</code> will increment by 2 each pass, and jump right over 4 since <code>i</code> is odd to start. Fix the comparison operator in the terminal condition so the loop only runs for <code>i</code> less than or equal to 4."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should change the comparison operator in the terminal condition (the middle part) of the <code>for</code> loop.'",
|
||||
"testString": "assert(code.match(/i\\s*?<=\\s*?4;/g).length == 1, 'Your code should change the comparison operator in the terminal condition (the middle part) of the <code>for</code> loop.')"
|
||||
},
|
||||
{
|
||||
"text": "Your code should fix the comparison operator in the terminal condition of the loop.'",
|
||||
"testString": "assert(!code.match(/i\\s*?!=\\s*?4;/g), 'Your code should fix the comparison operator in the terminal condition of the loop.')"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 1,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function myFunc() {",
|
||||
" for (let i = 1; i != 4; i += 2) {",
|
||||
" console.log(\"Still going!\");",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fileName": "02-javascript-algorithms-and-data-structures/debugging.json",
|
||||
"superBlock": "javascript-algorithms-and-data-structures",
|
||||
"superOrder": 2
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,662 @@
|
||||
{
|
||||
"name": "JavaScript Algorithms and Data Structures Projects",
|
||||
"order": 10,
|
||||
"time": "50 hours",
|
||||
"helpRoom": "HelpJavaScript",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "aaa48de84e1ecc7c742e1124",
|
||||
"title": "Palindrome Checker",
|
||||
"description": [
|
||||
"Return <code>true</code> if the given string is a palindrome. Otherwise, return <code>false</code>.",
|
||||
"A <dfn>palindrome</dfn> is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.",
|
||||
"<strong>Note</strong><br>You'll need to remove <strong>all non-alphanumeric characters</strong> (punctuation, spaces and symbols) and turn everything into the same case (lower or upper case) in order to check for palindromes.",
|
||||
"We'll pass strings with varying formats, such as <code>\"racecar\"</code>, <code>\"RaceCar\"</code>, and <code>\"race CAR\"</code> among others.",
|
||||
"We'll also pass strings with special symbols, such as <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, and <code>\"2_A3*3#A2\"</code>.",
|
||||
"Remember to use <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Read-Search-Ask</a> if you get stuck. Write your own code."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>palindrome(\"eye\")</code> should return a boolean.'",
|
||||
"testString": "assert(typeof palindrome(\"eye\") === \"boolean\", '<code>palindrome(\"eye\")</code> should return a boolean.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"eye\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"eye\") === true, '<code>palindrome(\"eye\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"_eye\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"_eye\") === true, '<code>palindrome(\"_eye\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"race car\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"race car\") === true, '<code>palindrome(\"race car\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"not a palindrome\")</code> should return false.'",
|
||||
"testString": "assert(palindrome(\"not a palindrome\") === false, '<code>palindrome(\"not a palindrome\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"A man, a plan, a canal. Panama\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"A man, a plan, a canal. Panama\") === true, '<code>palindrome(\"A man, a plan, a canal. Panama\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"never odd or even\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"never odd or even\") === true, '<code>palindrome(\"never odd or even\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"nope\")</code> should return false.'",
|
||||
"testString": "assert(palindrome(\"nope\") === false, '<code>palindrome(\"nope\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"almostomla\")</code> should return false.'",
|
||||
"testString": "assert(palindrome(\"almostomla\") === false, '<code>palindrome(\"almostomla\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"My age is 0, 0 si ega ym.\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"My age is 0, 0 si ega ym.\") === true, '<code>palindrome(\"My age is 0, 0 si ega ym.\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"1 eye for of 1 eye.\")</code> should return false.'",
|
||||
"testString": "assert(palindrome(\"1 eye for of 1 eye.\") === false, '<code>palindrome(\"1 eye for of 1 eye.\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"0_0 (: /-\\ :) 0-0\")</code> should return true.'",
|
||||
"testString": "assert(palindrome(\"0_0 (: /-\\ :) 0-0\") === true, '<code>palindrome(\"0_0 (: /-\\ :) 0-0\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>palindrome(\"five|\\_/|four\")</code> should return false.'",
|
||||
"testString": "assert(palindrome(\"five|\\_/|four\") === false, '<code>palindrome(\"five|\\_/|four\")</code> should return false.')"
|
||||
}
|
||||
],
|
||||
"type": "bonfire",
|
||||
"isRequired": true,
|
||||
"solutions": [
|
||||
"function palindrome(str) {\n var string = str.toLowerCase().split(/[^A-Za-z0-9]/gi).join('');\n var aux = string.split('');\n if (aux.join('') === aux.reverse().join('')){\n return true;\n }\n\n return false;\n}"
|
||||
],
|
||||
"MDNlinks": [
|
||||
"String.prototype.replace()",
|
||||
"String.prototype.toLowerCase()"
|
||||
],
|
||||
"challengeType": 5,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Verifica si es palíndromo",
|
||||
"description": [
|
||||
"Crea una función que devuelva <code>true</code> si una cadena de texto dada es un palíndromo, y que devuelva <code>false</code> en caso contrario",
|
||||
"Un palíndromo es una palabra u oración que se escribe de la misma forma en ambos sentidos, sin tomar en cuenta signos de puntuación, espacios y sin distinguir entre mayúsculas y minúsculas.",
|
||||
"Tendrás que quitar los caracteres no alfanuméricos (signos de puntuación, espacioes y símbolos) y transformar las letras a minúsculas para poder verificar si el texto es palíndromo.",
|
||||
"Te proveeremos textos en varios formatos, como \"racecar\", \"RaceCar\", and \"race CAR\" entre otros.",
|
||||
"También vamos a pasar cadenas con símbolos especiales, tales como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, y <code>\"2_A3*3#A2\"</code>.",
|
||||
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
|
||||
]
|
||||
},
|
||||
"pt-br": {
|
||||
"title": "Procure por Palíndromos",
|
||||
"description": [
|
||||
"Retorne <code>true</code> se o texto fornecida é um palíndromo. Caso contrário, retorne <code>false</code>.",
|
||||
"Um <dfn>palíndromo</dfn> é uma palavra ou sentença que é soletrada da mesma maneira tanto para a frente quanto para trás, ignorando pontuação, maiúsculas e minúsculas, e espaçamento.",
|
||||
"<strong>Nota</strong><br>Você precisará remover <strong>todos caracteres não alfanuméricos</strong> (pontuação, espaços e símbolos) e transformar todas as letras em maiúsculas ou minúsculas para procurar por palíndromos.",
|
||||
"Nós vamos passar textos de vários formatos, tais como <code>\"racecar\"</code>, <code>\"RaceCar\"</code> e <code>\"race CAR\"</code> entre outras.",
|
||||
"Nós também vamos passar textos com símbolos especiais, tais como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code> e <code>\"2_A3*3#A2\"</code>.",
|
||||
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function palindrome(str) {",
|
||||
" // Good luck!",
|
||||
" return true;",
|
||||
"}",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"palindrome(\"eye\");"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "a7f4d8f2483413a6ce226cac",
|
||||
"title": "Roman Numeral Converter",
|
||||
"description": [
|
||||
"Convert the given number into a roman numeral.",
|
||||
"All <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">roman numerals</a> answers should be provided in upper-case.",
|
||||
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
|
||||
],
|
||||
"solutions": [
|
||||
"function convertToRoman(num) {\n var ref = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]];\n var res = [];\n ref.forEach(function(p) {\n while (num >= p[1]) {\n res.push(p[0]);\n num -= p[1];\n }\n });\n return res.join('');\n}"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>convertToRoman(2)</code> should return \"II\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(2), '<code>convertToRoman(2)</code> should return \"II\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(3)</code> should return \"III\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(3), '<code>convertToRoman(3)</code> should return \"III\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(4)</code> should return \"IV\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(4), '<code>convertToRoman(4)</code> should return \"IV\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(5)</code> should return \"V\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(5), '<code>convertToRoman(5)</code> should return \"V\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(9)</code> should return \"IX\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(9), '<code>convertToRoman(9)</code> should return \"IX\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(12)</code> should return \"XII\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(12), '<code>convertToRoman(12)</code> should return \"XII\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(16)</code> should return \"XVI\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(16), '<code>convertToRoman(16)</code> should return \"XVI\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(29)</code> should return \"XXIX\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(29), '<code>convertToRoman(29)</code> should return \"XXIX\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(44)</code> should return \"XLIV\".'",
|
||||
"testString": "assert.deepEqual(convertToRoman(44), '<code>convertToRoman(44)</code> should return \"XLIV\".')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(45)</code> should return \"XLV\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(45), '<code>convertToRoman(45)</code> should return \"XLV\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(68)</code> should return \"LXVIII\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(68), '<code>convertToRoman(68)</code> should return \"LXVIII\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(83)</code> should return \"LXXXIII\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(83), '<code>convertToRoman(83)</code> should return \"LXXXIII\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(97)</code> should return \"XCVII\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(97), '<code>convertToRoman(97)</code> should return \"XCVII\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(99)</code> should return \"XCIX\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(99), '<code>convertToRoman(99)</code> should return \"XCIX\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(400)</code> should return \"CD\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(400), '<code>convertToRoman(400)</code> should return \"CD\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(500)</code> should return \"D\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(500), '<code>convertToRoman(500)</code> should return \"D\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(501)</code> should return \"DI\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(501), '<code>convertToRoman(501)</code> should return \"DI\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(649)</code> should return \"DCXLIX\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(649), '<code>convertToRoman(649)</code> should return \"DCXLIX\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(798)</code> should return \"DCCXCVIII\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(798), '<code>convertToRoman(798)</code> should return \"DCCXCVIII\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(891)</code> should return \"DCCCXCI\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(891), '<code>convertToRoman(891)</code> should return \"DCCCXCI\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(1000)</code> should return \"M\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(1000), '<code>convertToRoman(1000)</code> should return \"M\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(1004)</code> should return \"MIV\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(1004), '<code>convertToRoman(1004)</code> should return \"MIV\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(1006)</code> should return \"MVI\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(1006), '<code>convertToRoman(1006)</code> should return \"MVI\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(1023)</code> should return \"MXXIII\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(1023), '<code>convertToRoman(1023)</code> should return \"MXXIII\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(2014)</code> should return \"MMXIV\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(2014), '<code>convertToRoman(2014)</code> should return \"MMXIV\"')"
|
||||
},
|
||||
{
|
||||
"text": "<code>convertToRoman(3999)</code> should return \"MMMCMXCIX\"'",
|
||||
"testString": "assert.deepEqual(convertToRoman(3999), '<code>convertToRoman(3999)</code> should return \"MMMCMXCIX\"')"
|
||||
}
|
||||
],
|
||||
"type": "bonfire",
|
||||
"MDNlinks": [
|
||||
"Roman Numerals",
|
||||
"Array.prototype.splice()",
|
||||
"Array.prototype.indexOf()",
|
||||
"Array.prototype.join()"
|
||||
],
|
||||
"isRequired": true,
|
||||
"challengeType": 5,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Convertior de números romanos",
|
||||
"description": [
|
||||
"Convierte el número dado en numeral romano.",
|
||||
"Todos los <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">numerales romanos</a> en las respuestas deben estar en mayúsculas.",
|
||||
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
|
||||
]
|
||||
},
|
||||
"fr": {
|
||||
"title": "Convertir en chiffres romains",
|
||||
"description": [
|
||||
"Convertis le nombre donné en chiffres romains.",
|
||||
"Tous les <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">chiffres romains</a> doivent être en lettres capitales.",
|
||||
"N'oublie pas d'utiliser <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Lire-Chercher-Demander</a> si tu es bloqué. Essaye de trouver un partenaire. Écris ton propre code."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function convertToRoman(num) {",
|
||||
" return num;",
|
||||
"}",
|
||||
"",
|
||||
"convertToRoman(36);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "56533eb9ac21ba0edf2244e2",
|
||||
"title": "Caesars Cipher",
|
||||
"description": [
|
||||
"One of the simplest and most widely known <dfn>ciphers</dfn> is a <code>Caesar cipher</code>, also known as a <code>shift cipher</code>. In a <code>shift cipher</code> the meanings of the letters are shifted by some set amount.",
|
||||
"A common modern use is the <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> cipher, where the values of the letters are shifted by 13 places. Thus 'A' ↔ 'N', 'B' ↔ 'O' and so on.",
|
||||
"Write a function which takes a <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> encoded string as input and returns a decoded string.",
|
||||
"All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.",
|
||||
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
|
||||
],
|
||||
"solutions": [
|
||||
"var lookup = {\n 'A': 'N','B': 'O','C': 'P','D': 'Q',\n 'E': 'R','F': 'S','G': 'T','H': 'U',\n 'I': 'V','J': 'W','K': 'X','L': 'Y',\n 'M': 'Z','N': 'A','O': 'B','P': 'C',\n 'Q': 'D','R': 'E','S': 'F','T': 'G',\n 'U': 'H','V': 'I','W': 'J','X': 'K',\n 'Y': 'L','Z': 'M' \n};\n\nfunction rot13(encodedStr) {\n var codeArr = encodedStr.split(\"\"); // String to Array\n var decodedArr = []; // Your Result goes here\n // Only change code below this line\n \n decodedArr = codeArr.map(function(letter) {\n if(lookup.hasOwnProperty(letter)) {\n letter = lookup[letter];\n }\n return letter;\n });\n\n // Only change code above this line\n return decodedArr.join(\"\"); // Array to String\n}"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>rot13(\"SERR PBQR PNZC\")</code> should decode to <code>FREE CODE CAMP</code>'",
|
||||
"testString": "assert(rot13(\"SERR PBQR PNZC\") === \"FREE CODE CAMP\", '<code>rot13(\"SERR PBQR PNZC\")</code> should decode to <code>FREE CODE CAMP</code>')"
|
||||
},
|
||||
{
|
||||
"text": "<code>rot13(\"SERR CVMMN!\")</code> should decode to <code>FREE PIZZA!</code>'",
|
||||
"testString": "assert(rot13(\"SERR CVMMN!\") === \"FREE PIZZA!\", '<code>rot13(\"SERR CVMMN!\")</code> should decode to <code>FREE PIZZA!</code>')"
|
||||
},
|
||||
{
|
||||
"text": "<code>rot13(\"SERR YBIR?\")</code> should decode to <code>FREE LOVE?</code>'",
|
||||
"testString": "assert(rot13(\"SERR YBIR?\") === \"FREE LOVE?\", '<code>rot13(\"SERR YBIR?\")</code> should decode to <code>FREE LOVE?</code>')"
|
||||
},
|
||||
{
|
||||
"text": "<code>rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\")</code> should decode to <code>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.</code>'",
|
||||
"testString": "assert(rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\") === \"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.\", '<code>rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\")</code> should decode to <code>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.</code>')"
|
||||
}
|
||||
],
|
||||
"type": "bonfire",
|
||||
"MDNlinks": [
|
||||
"String.prototype.charCodeAt()",
|
||||
"String.fromCharCode()"
|
||||
],
|
||||
"challengeType": 5,
|
||||
"isRequired": true,
|
||||
"releasedOn": "January 1, 2016",
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Cifrado César",
|
||||
"description": [
|
||||
"Uno de los <dfn>cifrados</dfn> más simples y ampliamente conocidos es el <code>cifrado César</code>, también llamado <code>cifrado por desplazamiento</code>. En un <code>cifrado por desplazamiento</code> los significados de las letras se desplazan por una cierta cantidad.",
|
||||
"Un uso moderno común es el cifrado <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> , donde los valores de las letras se desplazan 13 espacios. De esta forma 'A' ↔ 'N', 'B' ↔ 'O' y así.",
|
||||
"Crea una función que tome una cadena de texto cifrada en <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como argumento y que devuelva la cadena de texto decodificada.",
|
||||
"Todas las letras que se te pasen van a estar en mayúsculas. No transformes ningún caracter no-alfabético (por ejemplo: espacios, puntuación). Simplemente pásalos intactos.",
|
||||
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
|
||||
]
|
||||
},
|
||||
"pt-br": {
|
||||
"title": "Cifra de César",
|
||||
"description": [
|
||||
"Uma das mais simples e mais conhecidas <dfn>cifras</dfn> é a <code>cifra de César</code>, também conhecida como <code>cifra de troca</code>. Em uma <code>cifra de troca</code> os significados das letras são deslocados por um determinado valor.",
|
||||
"Um uso moderno comum é a cifra <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a>, aonde os valores das letras são deslocados por 13 lugares. Logo 'A' ↔ 'N', 'B' ↔ 'O' e assim por diante.",
|
||||
"Escreva uma função que recebe um texto criptografado com <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como entrada e retorna o texto desencriptado.",
|
||||
"Todas as letras serão maiúsculas. Não transforme nenhum caracter não alfanuméricos (como espaços, pontuação), mas passe-os adiante.",
|
||||
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function rot13(str) { // LBH QVQ VG!",
|
||||
" ",
|
||||
" return str;",
|
||||
"}",
|
||||
"",
|
||||
"// Change the inputs below to test",
|
||||
"rot13(\"SERR PBQR PNZC\");"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aff0395860f5d3034dc0bfc9",
|
||||
"title": "Telephone Number Validator",
|
||||
"description": [
|
||||
"Return <code>true</code> if the passed string looks like a valid US phone number.",
|
||||
"The user may fill out the form field any way they choose as long as it has the format of a valid US number. The following are examples of valid formats for US numbers (refer to the tests below for other variants):",
|
||||
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
|
||||
"For this challenge you will be presented with a string such as <code>800-692-7753</code> or <code>8oo-six427676;laskdjf</code>. Your job is to validate or reject the US phone number based on any combination of the formats provided above. The area code is required. If the country code is provided, you must confirm that the country code is <code>1</code>. Return <code>true</code> if the string is a valid US phone number; otherwise return <code>false</code>.",
|
||||
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
|
||||
],
|
||||
"solutions": [
|
||||
"var re = /^([+]?1[\\s]?)?((?:[(](?:[2-9]1[02-9]|[2-9][02-8][0-9])[)][\\s]?)|(?:(?:[2-9]1[02-9]|[2-9][02-8][0-9])[\\s.-]?)){1}([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2}[\\s.-]?){1}([0-9]{4}){1}$/;\n\nfunction telephoneCheck(str) {\n return re.test(str);\n}\n\ntelephoneCheck(\"555-555-5555\");"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"555-555-5555\")</code> should return a boolean.'",
|
||||
"testString": "assert(typeof telephoneCheck(\"555-555-5555\") === \"boolean\", '<code>telephoneCheck(\"555-555-5555\")</code> should return a boolean.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1 555-555-5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"1 555-555-5555\") === true, '<code>telephoneCheck(\"1 555-555-5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1 (555) 555-5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"1 (555) 555-5555\") === true, '<code>telephoneCheck(\"1 (555) 555-5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"5555555555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"5555555555\") === true, '<code>telephoneCheck(\"5555555555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"555-555-5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"555-555-5555\") === true, '<code>telephoneCheck(\"555-555-5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"(555)555-5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"(555)555-5555\") === true, '<code>telephoneCheck(\"(555)555-5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1(555)555-5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"1(555)555-5555\") === true, '<code>telephoneCheck(\"1(555)555-5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"555-5555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"555-5555\") === false, '<code>telephoneCheck(\"555-5555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"5555555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"5555555\") === false, '<code>telephoneCheck(\"5555555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1 555)555-5555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"1 555)555-5555\") === false, '<code>telephoneCheck(\"1 555)555-5555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1 555 555 5555\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"1 555 555 5555\") === true, '<code>telephoneCheck(\"1 555 555 5555\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"1 456 789 4444\")</code> should return true.'",
|
||||
"testString": "assert(telephoneCheck(\"1 456 789 4444\") === true, '<code>telephoneCheck(\"1 456 789 4444\")</code> should return true.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"123**&!!asdf#\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"123**&!!asdf#\") === false, '<code>telephoneCheck(\"123**&!!asdf#\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"55555555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"55555555\") === false, '<code>telephoneCheck(\"55555555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"(6054756961)\")</code> should return false'",
|
||||
"testString": "assert(telephoneCheck(\"(6054756961)\") === false, '<code>telephoneCheck(\"(6054756961)\")</code> should return false')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"2 (757) 622-7382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"2 (757) 622-7382\") === false, '<code>telephoneCheck(\"2 (757) 622-7382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"0 (757) 622-7382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"0 (757) 622-7382\") === false, '<code>telephoneCheck(\"0 (757) 622-7382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"-1 (757) 622-7382\")</code> should return false'",
|
||||
"testString": "assert(telephoneCheck(\"-1 (757) 622-7382\") === false, '<code>telephoneCheck(\"-1 (757) 622-7382\")</code> should return false')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"2 757 622-7382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"2 757 622-7382\") === false, '<code>telephoneCheck(\"2 757 622-7382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"10 (757) 622-7382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"10 (757) 622-7382\") === false, '<code>telephoneCheck(\"10 (757) 622-7382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"27576227382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"27576227382\") === false, '<code>telephoneCheck(\"27576227382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"(275)76227382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"(275)76227382\") === false, '<code>telephoneCheck(\"(275)76227382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"2(757)6227382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"2(757)6227382\") === false, '<code>telephoneCheck(\"2(757)6227382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"2(757)622-7382\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"2(757)622-7382\") === false, '<code>telephoneCheck(\"2(757)622-7382\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"555)-555-5555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"555)-555-5555\") === false, '<code>telephoneCheck(\"555)-555-5555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"(555-555-5555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"(555-555-5555\") === false, '<code>telephoneCheck(\"(555-555-5555\")</code> should return false.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>telephoneCheck(\"(555)5(55?)-5555\")</code> should return false.'",
|
||||
"testString": "assert(telephoneCheck(\"(555)5(55?)-5555\") === false, '<code>telephoneCheck(\"(555)5(55?)-5555\")</code> should return false.')"
|
||||
}
|
||||
],
|
||||
"type": "bonfire",
|
||||
"MDNlinks": [
|
||||
"RegExp"
|
||||
],
|
||||
"challengeType": 5,
|
||||
"isRequired": true,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Valida Números Telefónicos de los EEUU",
|
||||
"description": [
|
||||
"Haz que la función devuelva true (verdadero) si el texto introducido parece un número válido en los EEUU.",
|
||||
"El usuario debe llenar el campo del formulario de la forma que desee siempre y cuando tenga el formato de un número válido en los EEUU. Los números mostrados a continuación tienen formatos válidos en los EEUU:",
|
||||
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
|
||||
"Para esta prueba se te presentará una cadena de texto como por ejemplo: <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Tu trabajo consiste en validar o rechazar el número telefónico tomando como base cualquier combinación de los formatos anteriormente presentados. El código de área es requrido. Si el código de país es provisto, debes confirmar que este es <code>1</code>. La función debe devolver true si la cadena de texto es un número telefónico válido en los EEUU; de lo contrario, debe devolver false.",
|
||||
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
|
||||
]
|
||||
},
|
||||
"it": {
|
||||
"title": "Verifica i numeri telefonici degli Stati Uniti",
|
||||
"description": [
|
||||
"Ritorna <code>true</code> se la stringa passata come argomento è un numero valido negli Stati Uniti.",
|
||||
"L'utente può digitare qualunque stringa nel campo di inserimento, purchè sia un numero di telefono valido negli Stati Uniti. Qui sotto alcuni esempi di numeri di telefono validi negli Stati Uniti (fai riferimento ai test per le altre varianti):",
|
||||
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
|
||||
"In questo problema ti saranno presentate delle stringe come <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Il tuo obiettivo è di validare o rigettare il numero di telefono basato su una qualunque combinazione dei formati specificati sopra. Il prefisso di zona è obbligatorio. Se il prefisso nazionale è presente, devi confermare che corrisponda a <code>1</code>. Ritorna <code>true</code> se la stringa è un numero di telefono valido negli Stati Uniti; altrimenti ritorna <code>false</code>.",
|
||||
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
|
||||
]
|
||||
},
|
||||
"pt-br": {
|
||||
"title": "Valida números telefônicos dos EUA",
|
||||
"description": [
|
||||
"Retorna <code>true</code> se a string passada é um número telefônico válido nos EUA.",
|
||||
"O usuário pode preencher o campo de qualquer maneira com tanto que seja um número válido nos EUA. Os seguintes exemplos são formatos válidos para números de telefone nos EUA (baseie-se nos testes abaixo para outras variações):",
|
||||
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
|
||||
"Para esse desafio será dado a você uma string como <code>800-692-7753</code> ou <code>8oo-six427676;laskdjf</code>. Seu trabalho é validar ou rejeitar o número de telefone dos EUA baseado nos exmplos de formatos fornecidos acima. O código de área é obrigatório. Se o código do país for fornecido, você deve confirmar que o código do país é <code>1</code>. Retorne <code>true</code> se a string é um número válido nos EUA; caso contrário retorne <code>false</code>.",
|
||||
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código."
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function telephoneCheck(str) {",
|
||||
" // Good luck!",
|
||||
" return true;",
|
||||
"}",
|
||||
"",
|
||||
"telephoneCheck(\"555-555-5555\");"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aa2e6f85cab2ab736c9a9b24",
|
||||
"title": "Cash Register",
|
||||
"description": [
|
||||
"Design a cash register drawer function <code>checkCashRegister()</code> that accepts purchase price as the first argument (<code>price</code>), payment as the second argument (<code>cash</code>), and cash-in-drawer (<code>cid</code>) as the third argument.",
|
||||
"<code>cid</code> is a 2D array listing available currency.",
|
||||
"The <code>checkCashRegister()</code> function should always return an object with a <code>status</code> key and a <code>change</code> key.",
|
||||
"Return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code> if cash-in-drawer is less than the change due, or if you cannot return the exact change.",
|
||||
"Return <code>{status: \"CLOSED\", change: [...]}</code> with cash-in-drawer as the value for the key <code>change</code> if it is equal to the change due.",
|
||||
"Otherwise, return <code>{status: \"OPEN\", change: [...]}</code>, with the change due in coins and bills, sorted in highest to lowest order, as the value of the <code>change</code> key.",
|
||||
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code.",
|
||||
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
|
||||
],
|
||||
"solutions": [
|
||||
"var denom = [\n\t{ name: 'ONE HUNDRED', val: 100},\n\t{ name: 'TWENTY', val: 20},\n\t{ name: 'TEN', val: 10},\n\t{ name: 'FIVE', val: 5},\n\t{ name: 'ONE', val: 1},\n\t{ name: 'QUARTER', val: 0.25},\n\t{ name: 'DIME', val: 0.1},\n\t{ name: 'NICKEL', val: 0.05},\n\t{ name: 'PENNY', val: 0.01}\n];\n\nfunction checkCashRegister(price, cash, cid) {\n var output = {status: null, change: []};\n var change = cash - price;\n var register = cid.reduce(function(acc, curr) {\n acc.total += curr[1];\n acc[curr[0]] = curr[1];\n return acc;\n }, {total: 0});\n if(register.total === change) {\n output.status = 'CLOSED';\n output.change = cid;\n return output;\n }\n if(register.total < change) {\n output.status = 'INSUFFICIENT_FUNDS';\n return output;\n }\n var change_arr = denom.reduce(function(acc, curr) {\n var value = 0;\n while(register[curr.name] > 0 && change >= curr.val) {\n change -= curr.val;\n register[curr.name] -= curr.val;\n value += curr.val;\n change = Math.round(change * 100) / 100;\n }\n if(value > 0) {\n acc.push([ curr.name, value ]);\n }\n return acc;\n }, []);\n if(change_arr.length < 1 || change > 0) {\n output.status = 'INSUFFICIENT_FUNDS';\n return output;\n }\n output.status = 'OPEN';\n output.change = change_arr;\n return output;\n}"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return an object.'",
|
||||
"testString": "assert.deepEqual(Object.prototype.toString.call(checkCashRegister(19.5, '<code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return an object.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"QUARTER\", 0.5]]}</code>.'",
|
||||
"testString": "assert.deepEqual(checkCashRegister(19.5, '<code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"QUARTER\", 0.5]]}</code>.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]]}</code>.'",
|
||||
"testString": "assert.deepEqual(checkCashRegister(3.26, '<code>checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]]}</code>.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.'",
|
||||
"testString": "assert.deepEqual(checkCashRegister(19.5, '<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.'",
|
||||
"testString": "assert.deepEqual(checkCashRegister(19.5, '<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.')"
|
||||
},
|
||||
{
|
||||
"text": "<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"CLOSED\", change: [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]}</code>.'",
|
||||
"testString": "assert.deepEqual(checkCashRegister(19.5, '<code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"CLOSED\", change: [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]}</code>.')"
|
||||
}
|
||||
],
|
||||
"type": "bonfire",
|
||||
"isRequired": true,
|
||||
"MDNlinks": [
|
||||
"Global Object",
|
||||
"Floating Point Guide"
|
||||
],
|
||||
"challengeType": 5,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Cambio Exacto",
|
||||
"description": [
|
||||
"Crea una función que simule una caja registradora que acepte el precio de compra como el primer argumento, la cantidad recibida como el segundo argumento, y la cantidad de dinero disponible en la registradora (cid) como tercer argumento",
|
||||
"cid es un arreglo bidimensional que lista la cantidad de dinero disponible",
|
||||
"La función debe devolver la cadena de texto \"Insufficient Funds\" si el cid es menor al cambio requerido. También debe devolver \"Closed\" si el cid es igual al cambio",
|
||||
"De no ser el caso, devuelve el cambio en monedas y billetes, ordenados de mayor a menor denominación.",
|
||||
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
|
||||
]
|
||||
},
|
||||
"it": {
|
||||
"title": "Cambio Esatto",
|
||||
"description": [
|
||||
"Scrivi una funzione che simuli un registro di cassa chiamata <code>checkCashRegister()</code> che accetti il prezzo degli articoli come primo argomento (<code>price</code>), la somma pagata (<code>cash</code>), e la somma disponibile nel registratore di cassa (<code>cid</code>) come terzo argomento.",
|
||||
"<code>cid</code> è un array a due dimensioni che contiene la quantità di monete e banconote disponibili.",
|
||||
"Ritorna la stringa <code>\"Insufficient Funds\"</code> se la quantità di denaro disponibile nel registratore di cassa non è abbastanza per restituire il resto. Ritorna la stringa <code>\"Closed\"</code> se il denaro disponibile è esattamente uguale al resto.",
|
||||
"Altrimenti, ritorna il resto in monete e banconote, ordinate da quelle con valore maggiore a quelle con valore minore.",
|
||||
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
|
||||
]
|
||||
},
|
||||
"pt-br": {
|
||||
"title": "Troco Exato",
|
||||
"description": [
|
||||
"Crie uma função que simula uma caixa registradora chamada <code>checkCashRegister()</code> e aceita o valor da compra como primeiro argumento (<code>price</code>), pagamento como segundo argumento (<code>cash</code>), e o dinheiro na caixa registradora (<code>cid</code>) como terceiro argumento.",
|
||||
"<code>cid</code> é uma matriz bidimensional que lista o dinheiro disponível.",
|
||||
"Retorne a string <code>\"Insufficient Funds\"</code> se o dinheiro na caixa registradora é menor do que o troco ou se não é possível retornar o troco exato. Retorne a string <code>\"Closed\"</code> se o dinheiro na caixa é igual ao troco.",
|
||||
"Case cotrário, retorne o troco em moedas e notas, ordenado do maior para menor.",
|
||||
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código.",
|
||||
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexjs",
|
||||
"ext": "js",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"function checkCashRegister(price, cash, cid) {",
|
||||
" var change;",
|
||||
" // Here is your change, ma'am.",
|
||||
" return change;",
|
||||
"}",
|
||||
"",
|
||||
"// Example cash-in-drawer array:",
|
||||
"// [[\"PENNY\", 1.01],",
|
||||
"// [\"NICKEL\", 2.05],",
|
||||
"// [\"DIME\", 3.1],",
|
||||
"// [\"QUARTER\", 4.25],",
|
||||
"// [\"ONE\", 90],",
|
||||
"// [\"FIVE\", 55],",
|
||||
"// [\"TEN\", 20],",
|
||||
"// [\"TWENTY\", 60],",
|
||||
"// [\"ONE HUNDRED\", 100]]",
|
||||
"",
|
||||
"checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]);"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fileName": "02-javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects.json",
|
||||
"superBlock": "javascript-algorithms-and-data-structures",
|
||||
"superOrder": 2
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
65
packages/learn/seed/getChallenges.js
Normal file
65
packages/learn/seed/getChallenges.js
Normal file
@ -0,0 +1,65 @@
|
||||
/* eslint-disable no-self-compare */
|
||||
// no import here as this runs without babel
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
const hiddenFile = /(^(\.|\/\.))|(.md$)/g;
|
||||
function getFilesFor(dir) {
|
||||
return fs
|
||||
.readdirSync(path.join(__dirname, '/' + dir))
|
||||
.filter(file => !hiddenFile.test(file))
|
||||
.map(function(file) {
|
||||
let superBlock;
|
||||
if (fs.statSync(path.join(__dirname, dir + '/' + file)).isFile()) {
|
||||
return { file: file };
|
||||
}
|
||||
superBlock = file;
|
||||
return getFilesFor(dir + '/' + superBlock).map(function(data) {
|
||||
return {
|
||||
file: superBlock + '/' + data.file,
|
||||
superBlock: superBlock
|
||||
};
|
||||
});
|
||||
})
|
||||
.reduce(function(files, file) {
|
||||
if (!Array.isArray(file)) {
|
||||
files.push(file);
|
||||
return files;
|
||||
}
|
||||
return files.concat(file);
|
||||
}, []);
|
||||
}
|
||||
|
||||
function getSupOrder(filePath) {
|
||||
const order = parseInt((filePath || '').split('-')[0], 10);
|
||||
// check for NaN
|
||||
if (order !== order) {
|
||||
return 0;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
function getSupName(filePath) {
|
||||
const order = parseInt((filePath || '').split('-')[0], 10);
|
||||
// check for NaN
|
||||
if (order !== order) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
return (filePath || '')
|
||||
.split('-')
|
||||
.splice(1)
|
||||
.join('-');
|
||||
}
|
||||
|
||||
module.exports = function getChallenges() {
|
||||
return getFilesFor('challenges').map(function(data) {
|
||||
const challengeSpec = require('./challenges/' + data.file);
|
||||
challengeSpec.fileName = data.file;
|
||||
challengeSpec.superBlock = getSupName(data.superBlock);
|
||||
challengeSpec.superOrder = getSupOrder(data.superBlock);
|
||||
|
||||
return challengeSpec;
|
||||
});
|
||||
};
|
90
packages/learn/seed/schema/challengeSchema.js
Normal file
90
packages/learn/seed/schema/challengeSchema.js
Normal file
@ -0,0 +1,90 @@
|
||||
const Joi = require('joi');
|
||||
Joi.objectId = require('joi-objectid')(Joi);
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
block: Joi.string(),
|
||||
blockId: Joi.objectId(),
|
||||
challengeSeed: Joi.array().items(Joi.string().allow('')),
|
||||
challengeType: Joi.number()
|
||||
.min(0)
|
||||
.max(9)
|
||||
.required(),
|
||||
checksum: Joi.number(),
|
||||
dashedName: Joi.string(),
|
||||
description: Joi.array()
|
||||
.items(
|
||||
// classic/modern challenges
|
||||
Joi.string().allow(''),
|
||||
|
||||
// step challenges
|
||||
Joi.array()
|
||||
.items(Joi.string().allow(''))
|
||||
.length(4),
|
||||
|
||||
// quiz challenges
|
||||
Joi.object().keys({
|
||||
subtitle: Joi.string(),
|
||||
question: Joi.string(),
|
||||
choices: Joi.array(),
|
||||
answer: Joi.number(),
|
||||
explanation: Joi.string()
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
fileName: Joi.string(),
|
||||
files: Joi.object().pattern(
|
||||
/(jsx?|html|css|sass)$/,
|
||||
Joi.object().keys({
|
||||
key: Joi.string(),
|
||||
ext: Joi.string(),
|
||||
name: Joi.string(),
|
||||
head: Joi.string().allow(''),
|
||||
tail: Joi.string().allow(''),
|
||||
contents: Joi.string()
|
||||
})
|
||||
),
|
||||
guideUrl: Joi.string().uri({ scheme: 'https' }),
|
||||
head: Joi.array().items(Joi.string().allow('')),
|
||||
helpRoom: Joi.string(),
|
||||
id: Joi.objectId().required(),
|
||||
isBeta: Joi.bool(),
|
||||
isComingSoon: Joi.bool(),
|
||||
isLocked: Joi.bool(),
|
||||
isPrivate: Joi.bool(),
|
||||
isRequired: Joi.bool(),
|
||||
name: Joi.string(),
|
||||
order: Joi.number(),
|
||||
required: Joi.array().items(
|
||||
Joi.object().keys({
|
||||
link: Joi.string(),
|
||||
raw: Joi.bool(),
|
||||
src: Joi.string(),
|
||||
crossDomain: Joi.bool()
|
||||
})
|
||||
),
|
||||
solutions: Joi.array().items(Joi.string().optional()),
|
||||
superBlock: Joi.string(),
|
||||
superOrder: Joi.number(),
|
||||
suborder: Joi.number(),
|
||||
tail: Joi.array().items(Joi.string().allow('')),
|
||||
tests: Joi.array().items(
|
||||
Joi.string().min(2),
|
||||
Joi.object().keys({
|
||||
text: Joi.string().required(),
|
||||
testString: Joi.string()
|
||||
.allow('')
|
||||
.required()
|
||||
}),
|
||||
Joi.object().keys({
|
||||
id: Joi.objectId().required(),
|
||||
title: Joi.string().required()
|
||||
})
|
||||
),
|
||||
template: Joi.string(),
|
||||
time: Joi.string().allow(''),
|
||||
title: Joi.string().required()
|
||||
});
|
||||
|
||||
exports.validateChallenge = function validateChallenge(challenge) {
|
||||
return Joi.validate(challenge, schema);
|
||||
};
|
155
packages/learn/src/client/frame-runner.js
Normal file
155
packages/learn/src/client/frame-runner.js
Normal file
@ -0,0 +1,155 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var testTimeout = 5000;
|
||||
var Rx = document.Rx;
|
||||
var frameReady = document.__frameReady;
|
||||
var helpers = Rx.helpers;
|
||||
var chai = require('chai');
|
||||
var source = document.__source;
|
||||
var __getUserInput = document.__getUserInput || (x => x);
|
||||
var checkChallengePayload = document.__checkChallengePayload;
|
||||
|
||||
// Fake Deep Equal dependency
|
||||
/* eslint-disable no-unused-vars */
|
||||
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
// Hardcode Deep Freeze dependency
|
||||
var DeepFreeze = o => {
|
||||
Object.freeze(o);
|
||||
Object.getOwnPropertyNames(o).forEach(function(prop) {
|
||||
if (
|
||||
o.hasOwnProperty(prop) &&
|
||||
o[prop] !== null &&
|
||||
(typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
|
||||
!Object.isFrozen(o[prop])
|
||||
) {
|
||||
DeepFreeze(o[prop]);
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
if (document.Enzyme) {
|
||||
window.Enzyme = document.Enzyme;
|
||||
}
|
||||
|
||||
document.__getJsOutput = function getJsOutput() {
|
||||
if (window.__err) {
|
||||
return window.__err;
|
||||
}
|
||||
let output;
|
||||
try {
|
||||
/* eslint-disable no-eval */
|
||||
output = eval(source);
|
||||
/* eslint-enable no-eval */
|
||||
} catch (e) {
|
||||
output = e.message + '\n' + e.stack;
|
||||
window.__err = e;
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
document.__runTests = function runTests(tests = []) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const code = source;
|
||||
const editor = {
|
||||
getValue() {
|
||||
return source;
|
||||
}
|
||||
};
|
||||
/* eslint-enable no-unused-vars */
|
||||
if (window.__err) {
|
||||
return Rx.Observable.from(tests)
|
||||
.map(test => {
|
||||
/* eslint-disable */
|
||||
return Object.assign({}, test, {
|
||||
err: window.__err.message + '\n' + window.__err.stack,
|
||||
message: window.__err.message,
|
||||
stack: window.__err.stack
|
||||
});
|
||||
/* eslint-enable */
|
||||
})
|
||||
.toArray()
|
||||
.do(() => {
|
||||
window.__err = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Iterate through the test one at a time
|
||||
// on new stacks
|
||||
return (
|
||||
Rx.Observable.from(tests, null, null, Rx.Scheduler.default)
|
||||
// add delay here for firefox to catch up
|
||||
.delay(200)
|
||||
/* eslint-disable no-unused-vars */
|
||||
.flatMap(({ text, testString }) => {
|
||||
const assert = chai.assert;
|
||||
const getUserInput = __getUserInput;
|
||||
/* eslint-enable no-unused-vars */
|
||||
const newTest = { text, testString };
|
||||
let test;
|
||||
let __result;
|
||||
|
||||
// uncomment the following line to inspect
|
||||
// the framerunner as it runs tests
|
||||
// make sure the dev tools console is open
|
||||
// debugger;
|
||||
try {
|
||||
/* eslint-disable no-eval */
|
||||
// eval test string to actual JavaScript
|
||||
// This return can be a function
|
||||
// i.e. function() { assert(true, 'happy coding'); }
|
||||
test = eval(testString);
|
||||
/* eslint-enable no-eval */
|
||||
if (typeof test === 'function') {
|
||||
// all async tests must return a promise or observable
|
||||
// sync tests can return Any type
|
||||
__result = test(getUserInput);
|
||||
|
||||
if (helpers.isPromise(__result)) {
|
||||
// turn promise into an observable
|
||||
__result = Rx.Observable.fromPromise(__result);
|
||||
}
|
||||
}
|
||||
|
||||
if (!__result || typeof __result.subscribe !== 'function') {
|
||||
// make sure result is an observable
|
||||
__result = Rx.Observable.of(null);
|
||||
}
|
||||
} catch (e) {
|
||||
// something threw an uncaught error
|
||||
// we catch here and wrap it in an observable
|
||||
__result = Rx.Observable.throw(e);
|
||||
}
|
||||
return __result
|
||||
.timeout(testTimeout)
|
||||
.map(() => {
|
||||
// we don't need the result of a promise/observable/cb here
|
||||
// all data asserts should happen further up the chain
|
||||
// mark test as passing
|
||||
newTest.pass = true;
|
||||
return newTest;
|
||||
})
|
||||
.catch(err => {
|
||||
// we catch the error here to prevent the error from bubbling up
|
||||
// and collapsing the pipe
|
||||
let message = err.message || '';
|
||||
const assertIndex = message.indexOf(': expected');
|
||||
if (assertIndex !== -1) {
|
||||
message = message.slice(0, assertIndex);
|
||||
}
|
||||
message = message.replace(/<code>(.*?)<\/code>/g, '$1');
|
||||
newTest.err = err.message + '\n' + err.stack;
|
||||
newTest.stack = err.stack;
|
||||
newTest.message = message;
|
||||
// RxJS catch expects an observable as a return
|
||||
return Rx.Observable.of(newTest);
|
||||
});
|
||||
})
|
||||
// gather tests back into an array
|
||||
.toArray()
|
||||
);
|
||||
};
|
||||
|
||||
// notify that the window methods are ready to run
|
||||
frameReady.next({ checkChallengePayload });
|
||||
});
|
@ -4,25 +4,24 @@ import Link from 'gatsby-link';
|
||||
const Header = () => (
|
||||
<div
|
||||
style={{
|
||||
background: 'rebeccapurple',
|
||||
background: '#006400',
|
||||
marginBottom: '1.45rem'
|
||||
}}
|
||||
>
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
maxWidth: 960,
|
||||
padding: '1.45rem 1.0875rem'
|
||||
maxWidth: 960
|
||||
}}
|
||||
>
|
||||
>
|
||||
<h1 style={{ margin: 0 }}>
|
||||
<Link
|
||||
to="/"
|
||||
style={{
|
||||
color: 'white',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
to='/'
|
||||
>
|
||||
Gatsby
|
||||
</Link>
|
||||
</h1>
|
||||
|
38
packages/learn/src/components/Map/Map.js
Normal file
38
packages/learn/src/components/Map/Map.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
import SuperBlock from './components/SuperBlock';
|
||||
import Spacer from '../util/Spacer';
|
||||
|
||||
import './map.css';
|
||||
import { ChallengeNode } from '../../redux/propTypes';
|
||||
|
||||
const propTypes = {
|
||||
nodes: PropTypes.arrayOf(ChallengeNode)
|
||||
};
|
||||
|
||||
class ShowMap extends PureComponent {
|
||||
renderSuperBlocks = superBlocks => {
|
||||
const { nodes } = this.props;
|
||||
return superBlocks.map(superBlock => (
|
||||
<SuperBlock key={superBlock} nodes={nodes} superBlock={superBlock} />
|
||||
));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { nodes } = this.props;
|
||||
const superBlocks = uniq(nodes.map(({ superBlock }) => superBlock));
|
||||
return (
|
||||
<ul>
|
||||
{this.renderSuperBlocks(superBlocks)}
|
||||
<Spacer />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowMap.displayName = 'Map';
|
||||
ShowMap.propTypes = propTypes;
|
||||
|
||||
export default ShowMap;
|
55
packages/learn/src/components/Map/components/Block.js
Normal file
55
packages/learn/src/components/Map/components/Block.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import Link from 'gatsby-link';
|
||||
|
||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||
import Caret from '../../icons/Caret';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const expandedSelector = makeExpandedBlockSelector(ownProps.blockDashedName);
|
||||
|
||||
return createSelector(expandedSelector, isExpanded => ({ isExpanded }))(
|
||||
state
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ toggleBlock }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
blockDashedName: PropTypes.string,
|
||||
challenges: PropTypes.array,
|
||||
isExpanded: PropTypes.bool,
|
||||
toggleBlock: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class Block extends PureComponent {
|
||||
renderChallenges = challenges =>
|
||||
challenges.map(challenge => (
|
||||
<li className='map-challenge-title' key={challenge.dashedName}>
|
||||
<Link to={challenge.fields.slug}>{challenge.title}</Link>
|
||||
</li>
|
||||
));
|
||||
|
||||
render() {
|
||||
const { blockDashedName, challenges, isExpanded, toggleBlock } = this.props;
|
||||
const { blockName } = challenges[0].fields;
|
||||
return (
|
||||
<li className={`block ${isExpanded ? 'open' : ''}`}>
|
||||
<div className='map-title' onClick={() => toggleBlock(blockDashedName)}>
|
||||
<Caret />
|
||||
<h5>{blockName}</h5>
|
||||
</div>
|
||||
<ul>{isExpanded ? this.renderChallenges(challenges) : null}</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Block.displayName = 'Block';
|
||||
Block.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Block);
|
79
packages/learn/src/components/Map/components/SuperBlock.js
Normal file
79
packages/learn/src/components/Map/components/SuperBlock.js
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
import Block from './Block';
|
||||
|
||||
import { makeExpandedSuperBlockSelector, toggleSuperBlock } from '../redux';
|
||||
import Caret from '../../icons/Caret';
|
||||
import { ChallengeNode } from '../../../redux/propTypes';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const expandedSelector = makeExpandedSuperBlockSelector(ownProps.superBlock);
|
||||
|
||||
return createSelector(expandedSelector, isExpanded => ({ isExpanded }))(
|
||||
state
|
||||
);
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
toggleSuperBlock
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
isExpanded: PropTypes.bool,
|
||||
nodes: PropTypes.arrayOf(ChallengeNode),
|
||||
superBlock: PropTypes.string,
|
||||
toggleSuperBlock: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SuperBlock extends PureComponent {
|
||||
renderBlock = superBlock => {
|
||||
const { nodes } = this.props;
|
||||
const blocksForSuperBlock = nodes.filter(
|
||||
node => node.superBlock === superBlock
|
||||
);
|
||||
const blockDashedNames = uniq(
|
||||
blocksForSuperBlock.map(({ block }) => block)
|
||||
);
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{blockDashedNames.map(blockDashedName => (
|
||||
<Block
|
||||
blockDashedName={blockDashedName}
|
||||
challenges={blocksForSuperBlock.filter(
|
||||
node => node.block === blockDashedName
|
||||
)}
|
||||
key={blockDashedName}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
render() {
|
||||
const { superBlock, isExpanded, toggleSuperBlock } = this.props;
|
||||
return (
|
||||
<li className={`superblock ${isExpanded ? 'open' : ''}`}>
|
||||
<div className='map-title' onClick={() => toggleSuperBlock(superBlock)}>
|
||||
<Caret />
|
||||
<h4>{superBlock}</h4>
|
||||
</div>
|
||||
{isExpanded ? this.renderBlock(superBlock) : null}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SuperBlock.displayName = 'SuperBlock';
|
||||
SuperBlock.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SuperBlock);
|
1
packages/learn/src/components/Map/index.js
Normal file
1
packages/learn/src/components/Map/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Map.js';
|
23
packages/learn/src/components/Map/map.css
Normal file
23
packages/learn/src/components/Map/map.css
Normal file
@ -0,0 +1,23 @@
|
||||
aside#map {
|
||||
height: 100%;
|
||||
max-height: calc(100vh - (45px + 1.45rem));
|
||||
}
|
||||
|
||||
aside#map ul {
|
||||
list-style: none;
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.map-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.map-title > svg {
|
||||
width: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
li.open > .map-title svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
50
packages/learn/src/components/Map/redux/index.js
Normal file
50
packages/learn/src/components/Map/redux/index.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
|
||||
import { createTypes } from '../../../../utils/stateManagment';
|
||||
|
||||
const ns = 'map';
|
||||
|
||||
export const getNS = () => ns;
|
||||
|
||||
const initialState = {
|
||||
expandedState: {
|
||||
superBlock: {},
|
||||
block: {}
|
||||
}
|
||||
};
|
||||
|
||||
const types = createTypes(['toggleSuperBlock', 'toggleBlock'], ns);
|
||||
|
||||
export const toggleBlock = createAction(types.toggleBlock);
|
||||
export const toggleSuperBlock = createAction(types.toggleSuperBlock);
|
||||
|
||||
export const makeExpandedSuperBlockSelector = superBlock => state =>
|
||||
!!state[ns].expandedState.superBlock[superBlock];
|
||||
export const makeExpandedBlockSelector = block => state =>
|
||||
!!state[ns].expandedState.block[block];
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[types.toggleBlock]: (state, { payload }) => ({
|
||||
...state,
|
||||
expandedState: {
|
||||
...state.expandedState,
|
||||
block: {
|
||||
...state.expandedState.block,
|
||||
[payload]: !state.expandedState.block[payload]
|
||||
}
|
||||
}
|
||||
}),
|
||||
[types.toggleSuperBlock]: (state, { payload }) => ({
|
||||
...state,
|
||||
expandedState: {
|
||||
...state.expandedState,
|
||||
superBlock: {
|
||||
...state.expandedState.superBlock,
|
||||
[payload]: !state.expandedState.superBlock[payload]
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
initialState
|
||||
);
|
19
packages/learn/src/components/icons/Caret.js
Normal file
19
packages/learn/src/components/icons/Caret.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
function Caret() {
|
||||
return (
|
||||
<svg viewBox='0 0 100 100' width='25px'>
|
||||
<polygon
|
||||
points='-6.04047,17.1511 81.8903,58.1985 -3.90024,104.196'
|
||||
style={{ stroke: '#aaa', fill: '#aaa', strokeWidth: '1px' }}
|
||||
transform={
|
||||
'matrix(0.999729, 0.023281, -0.023281, 0.999729, 7.39321, -10.0425)'
|
||||
}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
Caret.displayName = 'Caret';
|
||||
|
||||
export default Caret;
|
11
packages/learn/src/components/util/Spacer.js
Normal file
11
packages/learn/src/components/util/Spacer.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import './spacer.css';
|
||||
|
||||
function Spacer() {
|
||||
return <div className='util-spacer' />;
|
||||
}
|
||||
|
||||
Spacer.displayName = 'Spacer';
|
||||
|
||||
export default Spacer;
|
3
packages/learn/src/components/util/spacer.css
Normal file
3
packages/learn/src/components/util/spacer.css
Normal file
@ -0,0 +1,3 @@
|
||||
.util-spacer {
|
||||
margin: 5px 0px;
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
font-family: 'Lato', sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
html, body, .full-height {
|
||||
min-height: 100% !important;
|
||||
height: 100%;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
@ -198,7 +202,7 @@ html {
|
||||
}
|
||||
body {
|
||||
color: hsla(0, 0%, 0%, 0.8);
|
||||
font-family: georgia, serif;
|
||||
font-family: 'Lato', georgia, serif;
|
||||
font-weight: normal;
|
||||
word-wrap: break-word;
|
||||
font-kerning: normal;
|
||||
@ -245,7 +249,7 @@ h2 {
|
||||
padding-top: 0;
|
||||
margin-bottom: 1.45rem;
|
||||
color: inherit;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeLegibility;
|
||||
@ -262,7 +266,7 @@ h3 {
|
||||
padding-top: 0;
|
||||
margin-bottom: 1.45rem;
|
||||
color: inherit;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeLegibility;
|
||||
@ -279,7 +283,7 @@ h4 {
|
||||
padding-top: 0;
|
||||
margin-bottom: 1.45rem;
|
||||
color: inherit;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeLegibility;
|
||||
@ -296,7 +300,7 @@ h5 {
|
||||
padding-top: 0;
|
||||
margin-bottom: 1.45rem;
|
||||
color: inherit;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeLegibility;
|
||||
@ -313,7 +317,7 @@ h6 {
|
||||
padding-top: 0;
|
||||
margin-bottom: 1.45rem;
|
||||
color: inherit;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-weight: bold;
|
||||
text-rendering: optimizeLegibility;
|
@ -1,35 +1,73 @@
|
||||
import React from 'react';
|
||||
/* global graphql */
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import Header from '../components/Header';
|
||||
import './index.css';
|
||||
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
|
||||
|
||||
const TemplateWrapper = ({ children }) => (
|
||||
<div>
|
||||
import { AllChallengeNode } from '../redux/propTypes';
|
||||
|
||||
import Header from '../components/Header';
|
||||
import Map from '../components/Map';
|
||||
|
||||
import '../../static/bootstrap3/css/bootstrap.min.css';
|
||||
import './global.css';
|
||||
import 'react-reflex/styles.css';
|
||||
import './layout.css';
|
||||
|
||||
const Layout = ({ children, data: { allChallengeNode: { edges } } }) => (
|
||||
<Fragment>
|
||||
<Helmet
|
||||
title="Gatsby Default Starter"
|
||||
meta={[
|
||||
{ name: 'description', content: 'Sample' },
|
||||
{ name: 'keywords', content: 'sample, something' }
|
||||
]}
|
||||
/>
|
||||
<Header />
|
||||
<div
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
maxWidth: 960,
|
||||
padding: '0px 1.0875rem 1.45rem',
|
||||
paddingTop: 0
|
||||
}}
|
||||
>
|
||||
{children()}
|
||||
</div>
|
||||
</div>
|
||||
<ReflexContainer className='app-wrapper' orientation='vertical'>
|
||||
<ReflexElement flex={0.2} minSize={100}>
|
||||
<aside id='map'>
|
||||
<Map
|
||||
nodes={edges
|
||||
.map(({ node }) => node)
|
||||
.filter(({ isPrivate }) => !isPrivate)}
|
||||
/>
|
||||
</aside>
|
||||
</ReflexElement>
|
||||
|
||||
<ReflexSplitter />
|
||||
|
||||
<ReflexElement>
|
||||
<main>{children()}</main>
|
||||
</ReflexElement>
|
||||
</ReflexContainer>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
TemplateWrapper.propTypes = {
|
||||
children: PropTypes.func
|
||||
Layout.propTypes = {
|
||||
children: PropTypes.func,
|
||||
data: AllChallengeNode
|
||||
};
|
||||
|
||||
export default TemplateWrapper;
|
||||
export default Layout;
|
||||
|
||||
export const query = graphql`
|
||||
query LayoutQuery {
|
||||
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
||||
edges {
|
||||
node {
|
||||
fields {
|
||||
slug
|
||||
blockName
|
||||
}
|
||||
block
|
||||
title
|
||||
isRequired
|
||||
isPrivate
|
||||
superBlock
|
||||
dashedName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
25
packages/learn/src/layouts/layout.css
Normal file
25
packages/learn/src/layouts/layout.css
Normal file
@ -0,0 +1,25 @@
|
||||
.app-wrapper {
|
||||
min-height: calc(100vh - (45px + 1.45rem));
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.reflex-layout.vertical > .reflex-splitter,
|
||||
.reflex-layout > .reflex-element {
|
||||
/*
|
||||
Note(Bouncey): removing height: 100% makes the element 100% high ¯\_(ツ)_/¯
|
||||
*/
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.reflex-layout.vertical > .reflex-splitter:hover {
|
||||
border-left: 2px solid #ccc !important;
|
||||
border-right: 2px solid #ccc !important;
|
||||
}
|
||||
|
||||
.reflex-layout.vertical > .reflex-splitter, {
|
||||
border-left: 2px solid #c6c6c6;
|
||||
border-right: 2px solid #c6c6c6;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Basic Node and Express Challenges
|
||||
block: Basic Node and Express
|
||||
---
|
||||
## Introduction to the Basic Node and Express Challenges
|
||||
|
||||
Node.js is a JavaScript tool that allows developers to write backend (server-side) programs in JavaScript. Node.js comes with a handful of built-in modules—small, independent programs—that help facilitate this purpose. Some of the core modules include:<br><br><ul><li>HTTP: a module that acts as a server</li><li>File System: a module that reads and modifies files</li><li>Path: a module for working with directory and file paths</li><li>Assertion Testing: a module that checks code against prescribed constraints</li></ul><br>Express, while not included with Node.js, is another module often used with it. Express runs between the server created by Node.js and the frontend pages of a web application. Express also handles an application's routing. Routing directs users to the correct page based on their interaction with the application.<br><br>While there are alternatives to using Express, its simplicity makes it a good place to begin when learning the interaction between a backend powered by Node.js and the frontend.
|
||||
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public Glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-express/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-express/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Managing Packages with npm Challenges
|
||||
block: Managing Packages with Npm
|
||||
---
|
||||
## Introduction to the Managing Packages with npm Challenges
|
||||
|
||||
The Node Package Manager (npm) is a command-line tool used by developers to share and control modules (or packages) of JavaScript code written for use with Node.js.<br><br>When starting a new project, npm generates a <code>package.json</code> file. This file lists the package dependencies for your project. Since npm packages are regularly updated, the <code>package.json</code> file allows you to set specific version numbers for each dependency. This ensures that updates to a package don't break your project.<br><br>npm saves packages in a folder named <code>node_modules</code>. These packages can be installed in two ways:<br><br><ol><li>globally in a root <code>node_modules</code> folder, accessible by all projects.</li><li>locally within a project's own <code>node_modules</code> folder, accessible only to that project.</li></ol><br>Most developers prefer to install packages local to each project to create a separation between the dependencies of different projects.
|
||||
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public Glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-npm'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-npm/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the MongoDB and Mongoose Challenges
|
||||
block: MongoDB and Mongoose
|
||||
---
|
||||
## Introduction to the MongoDB and Mongoose Challenges
|
||||
|
||||
MongoDB is a database that stores data records (documents) for use by an application. Mongo is a non-relational, "NoSQL" database. This means Mongo stores all data associated within one record, instead of storing it across many preset tables as in a SQL database. Some benefits of this storage model are:<br><br><ul><li>Scalability: by default, non-relational databases are split (or "sharded") across many systems instead of only one. This makes it easier to improve performance at a lower cost.</li><li>Flexibility: new datasets and properties can be added to a document without the need to make a new table for that data.</li><li>Replication: copies of the database run in parallel so if one goes down, one of the copies becomes the new primary data source.</li></ul><br>While there are many non-relational databases, Mongo's use of JSON as its document storage structure makes it a logical choice when learning backend JavaScript. Accessing documents and their properties is like accessing objects in JavaScript.<br><br>Mongoose.js is an npm module for Node.js that allows you to write objects for Mongo as you would in JavaScript. This can make is easier to construct documents for storage in Mongo.
|
||||
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-mongomongoose/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-mongomongoose/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Data Visualization with D3 Challenges
|
||||
block: Data Visualization with D3
|
||||
---
|
||||
## Introduction to the Data Visualization with D3 Challenges
|
||||
|
||||
D3.js, or D3, stands for Data Driven Documents. D3 is a JavaScript library to create dynamic and interactive data visualizations in the browser. It's built to work with common web standards, namely HTML, CSS, and Scalable Vector Graphics (SVG).<br><br>D3 takes input data and maps it into a visual representation of that data. It supports many different data formats. D3 lets you bind (or attach) the data to the Document Object Model (DOM). You use HTML or SVG elements with D3's built-in methods to transform the data into a visualization.<br><br>D3 gives you a lot of control over the presentation of data. This section covers the basic functionality and how to create visualizations with the D3 library.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the JSON APIs and AJAX Challenges
|
||||
block: JSON APIs and Ajax
|
||||
---
|
||||
## Introduction to the JSON APIs and AJAX Challenges
|
||||
|
||||
Similar to how User Interfaces help people use programs, Application Programming Interfaces (APIs) help programs interact with other programs. APIs are tools that computers use to communicate with one another, in part to send and receive data. You can use API functionality in your page once you understand how to make requests and process data from it. Programmers often use AJAX technologies when working with APIs.<br><br>The term AJAX originated as an acronym for Asynchronous JavaScript And XML. It refers to a group of technologies that make asynchronous requests to a server to transfer data, then load any returned data into the page. An asynchronous process has a couple key properties. The browser does not stop loading a page to wait for the server's response. Also, the browser inserts updated data into part of the page without having to refresh the entire page.<br><br>User experience benefits from asynchronous processes in several ways. Pages load faster since the browser isn't waiting for the server to respond in the middle of a page render. Requests and transfers happen in the background, without interrupting what the user is doing. When the browser receives new data, only the necessary area of the page refreshes. These qualities especially enhance the user experience for single page applications.<br><br>The data transferred between the browser and server is often in a format called JavaScript Object Notation (JSON). JSON resembles JavaScript object literal syntax, except that it's transferred as a string. Once received, it can be converted into an object and used in a script.<br><br>This section covers how to transfer and use data using AJAX technologies with a freeCodeCamp API.
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Bootstrap Challenges
|
||||
block: Bootstrap
|
||||
---
|
||||
## Introduction to the Bootstrap Challenges
|
||||
|
||||
Bootstrap is a front-end framework used to design responsive web pages and web applications. It takes a mobile-first approach to web development. Bootstrap includes pre-built CSS styles and classes, plus some JavaScript functionality.
|
||||
Bootstrap uses a responsive 12 column grid layout and has design templates for:<br><br><ul><li>buttons</li><li>images</li><li>tables</li><li>forms</li><li>navigation</li></ul><br>This section introduces some of the ways to use Bootstrap in your web projects.
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to jQuery
|
||||
block: jQuery
|
||||
---
|
||||
## Introduction to jQuery
|
||||
|
||||
jQuery is one of the many libraries for JavaScript. It is designed to simplify scripting done on the client side.
|
||||
jQuery's most recognizable characteristic is its dollar sign (<code>$</code>) syntax. With it, you can easily manipulate elements, create animations and handle input events.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Sass Challenges
|
||||
block: Sass
|
||||
---
|
||||
## Introduction to the Sass Challenges
|
||||
|
||||
Sass, or "Syntactically Awesome StyleSheets", is a language extension of CSS. It adds features that aren't available using basic CSS syntax. Sass makes it easier for developers to simplify and maintain the style sheets for their projects.<br><br>Sass can extend the CSS language because it is a preprocessor. It takes code written using Sass syntax, and converts it into basic CSS. This allows you to create variables, nest CSS rules into others, and import other Sass files, among other things. The result is more compact, easier to read code.<br><br>There are two syntaxes available for Sass. The first, known as SCSS (Sassy CSS) and used throughout these challenges, is an extension of the syntax of CSS. This means that every valid CSS stylesheet is a valid SCSS file with the same meaning. Files using this syntax have the .scss extension.<br><br>The second and older syntax, known as the indented syntax (or sometimes just "Sass"), uses indentation rather than brackets to indicate nesting of selectors, and newlines rather than semicolons to separate properties. Files using this syntax have the .sass extension.<br><br>This section introduces the basic features of Sass.
|
||||
|
7
packages/learn/src/pages/index.css
Normal file
7
packages/learn/src/pages/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
.index-page-wrapper {
|
||||
display: flex;
|
||||
/* width: calc(100vw - 340px); */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
}
|
@ -1,13 +1,62 @@
|
||||
/* global graphql */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Link from 'gatsby-link';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
const IndexPage = () => (
|
||||
<div>
|
||||
<h1>Hi people</h1>
|
||||
<p>Welcome to your new Gatsby site.</p>
|
||||
<p>Now go build something great.</p>
|
||||
<Link to="/page-2/">Go to page 2</Link>
|
||||
import { ChallengeNode } from '../redux/propTypes';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.shape({
|
||||
challengeNode: ChallengeNode
|
||||
})
|
||||
};
|
||||
|
||||
const IndexPage = ({
|
||||
data: { challengeNode: { title, fields: { slug, blockName } } }
|
||||
}) => (
|
||||
<div className='index-page-wrapper'>
|
||||
<Helmet title='Welcome to learn.freeCodeCamp!' />
|
||||
<h1>Welcome to learn.freeCodeCamp.org</h1>
|
||||
<p>
|
||||
Check out the lesson map on the left. We have thousands of coding lessons
|
||||
to help you improve your skills.
|
||||
</p>
|
||||
<p>
|
||||
You can earn verified certificates by completing certificate's 5 required
|
||||
projects.
|
||||
</p>
|
||||
<p>
|
||||
{'And yes - all of this is 100% free, thanks to the thousands of ' +
|
||||
'campers who '}
|
||||
<a href='https://donate.freecodecamp.org' target='_blank'>
|
||||
donate
|
||||
</a>{' '}
|
||||
to our nonprofit.
|
||||
</p>
|
||||
<h3>Not sure where to start?</h3>
|
||||
<p>
|
||||
We recommend you start at the beginning{' '}
|
||||
<Link to={slug}>{`${blockName} -> ${title}`}</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
IndexPage.displayName = 'IndexPage';
|
||||
IndexPage.propTypes = propTypes;
|
||||
|
||||
export default IndexPage;
|
||||
|
||||
export const query = graphql`
|
||||
query FirstChallenge {
|
||||
challengeNode(order: { eq: 0 }, suborder: { eq: 1 }) {
|
||||
title
|
||||
fields {
|
||||
slug
|
||||
blockName
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to Information Security with HelmetJS Challenges
|
||||
block: Information Security with HelmetJS
|
||||
---
|
||||
## Introduction to Information Security with HelmetJS Challenges
|
||||
|
||||
HelmetJS is a type of middleware for Express-based applications that automatically sets HTTP headers to prevent sensitive information from unintentially being passed between the server and client. While HelmetJS does not account for all situations, it does include support for common ones like Content Security Policy, XSS Filtering, and HTTP Strict Transport Security, among others. HelmetJS can be installed on an Express project from npm, after which each layer of protection can be configured to best fit the project.<br><br>Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-infosec/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-infosec/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Introduction to Objects
|
||||
block: Basic Data Structures
|
||||
---
|
||||
## Introduction to Objects
|
||||
|
||||
The next data structure we will discuss is the JavaScript <dfn>object</dfn>. Like arrays, objects are a fundamental part of JavaScript. However, it is probably safe to say that objects surpass arrays in flexibility, usefulness and in their overall importance to the language — in fact, you may have heard this line before: 'In JavaScript, everything is an object.'<br><br>While an understanding of objects is important to understand the inner workings of JavaScript functions or JavaScript's object-oriented capabilities, JavaScript objects at a basic level are actually just <dfn>key-value pair</dfn> stores, a commonly used data structure across almost all programming languages. Here, we will confine our discussion to JavaScript objects in this capacity.
|
||||
Key-value pair data structures go by different names depending on the language and the specific details of the data structure. The terms <dfn>dictionary</dfn>, <dfn>map</dfn>, and <dfn>hash table</dfn> all refer to the notion of a data structure in which specific keys, or properties, are mapped to specific values.<br><br>Objects, and other similar key-value pair data structures, offer some very useful benefits. One clear benefit is that they allow us to structure our data in an intuitive way; properties can be nested to an arbitrary depth, and values can be anything, including arrays and even other objects.<br><br>Largely due to this flexibility, objects are also the foundation for <dfn>JavaScript Object Notation</dfn>, or <dfn>JSON</dfn>, which is a widely used method of sending data across the web.
|
||||
Another powerful advantage of key-value pair data structures is <dfn>constant lookup time</dfn>. What this means, is that when you request the value of a specific property, you will get the value back in the same amount of time (theoretically) regardless of the number of entries in the object. If you had an object with 5 entries or one that held a collection of 1,000,000, you could still retrieve property values or check if a key exists in the same amount of time.<br><br>The reason for this fast lookup time, is that internally, the object is storing properties using a hashing mechanism which allows it to know exactly where it has stored different property values. If you want to learn more about this please take a look at the optional Advanced Data Structures challenges. All you should remember for now is that <strong>performant access to flexibly structured data make key-value stores very attractive data structures useful in a wide variety of settings</strong>.
|
||||
<br><br>In JavaScript, objects are written as comma-separated lists of key-value pairs, wrapped in curly brackets, with each key and its assigned value separated by a colon:<br> <code>{ key1: 'val-1', key2: 'val-2' }</code><br><br>In the next few challenges, we will examine JavaScript objects more closely, and take a look at <dfn>methods</dfn> and techniques that allow us to access, store, and manipulate an object's data.<br><br>Note that throughout the scope of this discussion, and in general when considering JavaScript objects, the terms <dfn>key</dfn> and <dfn>property</dfn> will be used interchangeably.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to JavaScript
|
||||
block: Basic JavaScript
|
||||
---
|
||||
## Introduction to JavaScript
|
||||
|
||||
JavaScript is a high-level programming language that all modern web browsers support. It is also one of the core technologies of the web, along with HTML and CSS that you may have learned previously. This section will cover basic programming concepts in JavaScript, which range from variables and arithemtic to objects and loops.
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Introduction to the Debugging Challenges
|
||||
block: Debugging
|
||||
---
|
||||
## Introduction to the Debugging Challenges
|
||||
|
||||
Debugging is a valuable and (unfortunately) necessary tool for programmers. It follows the testing phase of checking if your code works as intended, and discovering it does not. Debugging is the process of finding exactly what isn't working and fixing it.
|
||||
After spending time creating a brilliant block of code, it is tough realizing it may have errors. These issues generally come in three forms: 1) syntax errors that prevent a program from running, 2) runtime errors when code fails to execute or has unexpected behavior, and 3) semantic (or logical) errors when code doesn't do what it's meant to.<br><br>Modern code editors (and experience) can help identify syntax errors. Semantic and runtime errors are harder to find. They may cause your program to crash, make it run forever, or give incorrect output. Think of debugging as trying to understand why your code is behaving the way it is.<br><br>Example of a syntax error - often detected by the code editor:<br><br><blockquote>funtion willNotWork( {<br> console.log("Yuck");<br>}<br>// "function" keyword is misspelled and there's a missing parenthesis</blockquote><br><br>Here's an example of a runtime error - often detected while the program executes:<br><br><blockquote>function loopy() {<br> while(true) {<br> console.log("Hello, world!");<br> }<br>}<br>// Calling loopy starts an infinite loop, which may crash your browser</blockquote><br><br>Example of a semantic error - often detected after testing code output:<br><br><blockquote>function calcAreaOfRect(w, h) {<br> return w + h; // This should be w * h<br>}<br>let myRectArea = calcAreaOfRect(2, 3);<br>// Correct syntax and the program executes, but this gives the wrong answer</blockquote>
|
||||
Debugging is frustrating, but it helps to develop (and follow) a step-by-step approach to review your code. This means checking the intermediate values and types of variables to see if they are what they should be. You can start with a simple process of elimination.<br><br>For example, if function A works and returns what it's supposed to, then function B may have the issue. Or start checking values in a block of code from the middle to try to cut the search space in half. A problem in one spot indicates a bug in the first half of the code. If not, it's likely in the second.<br><br>This section will cover a couple helpful tools to find bugs, and some of the common forms they take. Fortunately, debugging is a learnable skill that just requires a little patience and practice to master.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the ES6 Challenges
|
||||
block: ES6
|
||||
---
|
||||
## Introduction to the ES6 Challenges
|
||||
|
||||
ECMAScript is a standardized version of JavaScript with the goal of unifying the language's specifications and features. As all major browsers and JavaScript-runtimes follow this specification, the term <i>ECMAScript</i> is interchangeable with the term <i>JavaScript</i>.<br><br>Most of the challenges on freeCodeCamp use the ECMAScript 5 (ES5) specification of the language, finalized in 2009. But JavaScript is an evolving programming language. As features are added and revisions are made, new versions of the language are released for use by developers.<br><br>The most recent standardized version is called ECMAScript 6 (ES6), released in 2015. This new version of the language adds some powerful features that will be covered in this section of challenges, including:<br><br><ul><li>Arrow functions</li><li>Classes</li><li>Modules</li><li>Promises</li><li>Generators</li><li><code>let</code> and <code>const</code></li></ul><br><br><strong>Note</strong><br>Not all browsers support ES6 features. If you use ES6 in your own projects, you may need to use a program (transpiler) to convert your ES6 code into ES5 until browsers support ES6.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Functional Programming Challenges
|
||||
block: Functional Programming
|
||||
---
|
||||
## Introduction to the Functional Programming Challenges
|
||||
|
||||
Functional programming is an approach to software development based around the evaluation of functions. Like mathematics, functions in programming map input to output to produce a result. You can combine basic functions in many ways to build more and more complex programs.<br><br>Functional programming follows a few core principles:<br><br><ul><li>Functions are independent from the state of the program or global variables. They only depend on the arguments passed into them to make a calculation</li><br><li>Functions try to limit any changes to the state of the program and avoid changes to the global objects holding data</li><br><li>Functions have minimal side effects in the program</li></ul><br><br>The functional programming software development approach breaks a program into small, testable parts. This section covers basic functional programming principles in JavaScript.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Object Oriented Programming Challenges
|
||||
block: Object Oriented Programming
|
||||
---
|
||||
## Introduction to the Object Oriented Programming Challenges
|
||||
|
||||
At its core, software development solves a problem or achieves a result with computation. The software development process first defines a problem, then presents a solution. Object oriented programming is one of several major approaches to the software development process.<br><br>As its name implies, object oriented programming organizes code into object definitions. These are sometimes called classes, and they group together data with related behavior. The data is an object's attributes, and the behavior (or functions) are methods.<br><br>The object structure makes it flexible within a program. Objects can transfer information by calling and passing data to another object's methods. Also, new classes can receive, or inherit, all the features from a base or parent class. This helps to reduce repeated code.<br><br>Your choice of programming approach depends on a few factors. These include the type of problem, as well as how you want to structure your data and algorithms. This section covers object oriented programming principles in JavaScript.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Regular Expression Challenges
|
||||
block: Regular Expressions
|
||||
---
|
||||
## Introduction to the Regular Expression Challenges
|
||||
|
||||
Regular expressions are special strings that represent a search pattern. Also known as "regex" or "regexp", they help programmers match, search, and replace text. Regular expressions can appear cryptic because a few characters have special meaning. The goal is to combine the symbols and text into a pattern that matches what you want, but only what you want. This section will cover the characters, a few shortcuts, and the common uses for writing regular expressions.
|
||||
|
@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import Link from 'gatsby-link';
|
||||
|
||||
const SecondPage = () => (
|
||||
<div>
|
||||
<h1>Hi from the second page</h1>
|
||||
<p>Welcome to page 2</p>
|
||||
<Link to="/">Go back to the homepage</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SecondPage;
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Introduction to the Applied Accessibility Challenges
|
||||
block: Applied Accessibility
|
||||
---
|
||||
## Introduction to the Applied Accessibility Challenges
|
||||
|
||||
"Accessibility" generally means having web content and a user interface that can be understood, navigated, and interacted with by a broad audience. This includes people with visual, auditory, mobility, or cognitive disabilities.
|
||||
Websites should be open and accessible to everyone, regardless of a user's abilities or resources. Some users rely on assistive technology such as a screen reader or voice recognition software. Other users may be able to navigate through a site only using a keyboard. Keeping the needs of various users in mind when developing your project can go a long way towards creating an open web.
|
||||
Here are three general concepts this section will explore throughout the following challenges:<br><br><ol><br><li>have well-organized code that uses appropriate markup</li><br><li>ensure text alternatives exist for non-text and visual content</li><br><li>create an easily-navigated page that's keyboard-friendly</li><br></ol><br><br>Having accessible web content is an ongoing challenge. A great resource for your projects going forward is the W3 Consortium's Web Content Accessibility Guidelines (WCAG). They set the international standard for accessibility and provide a number of criteria you can use to check your work.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the Applied Visual Design Challenges
|
||||
block: Applied Visual Design
|
||||
---
|
||||
## Introduction to the Applied Visual Design Challenges
|
||||
|
||||
Visual Design in web development is a broad topic. It combines typography, color theory, graphics, animation, and page layout to help deliver a site's message. The definition of good design is a well-discussed subject, with many books on the theme.<br><br>At a basic level, most web content provides a user with information. The visual design of the page can influence its presentation and a user's experience. In web development, HTML gives structure and semantics to a page's content, and CSS controls the layout and appearance of it.<br><br>This section covers some of the basic tools developers use to create their own visual designs.
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Introduction to Basic CSS
|
||||
block: Basic CSS
|
||||
---
|
||||
## Introduction to Basic CSS
|
||||
|
||||
Cascading Style Sheets (CSS) tell the browser how to display the text and other content that you write in HTML.<br><br>Note that CSS is case-sensitive so be careful with your capitalization.
|
||||
CSS has been adopted by all major browsers and allows you to control:<br><ul><li>color</li><li>fonts</li><li>positioning</li><li>spacing</li><li>sizing</li><li>decorations</li><li>transitions</li></ul>
|
||||
There are three main ways to apply CSS styling. You can apply inline styles directly to HTML elements with the <code>style</code> attribute. Alternatively, you can place CSS rules within <code>style</code> tags in an HTML document. Finally, you can write CSS rules in an external style sheet, then reference that file in the HTML document. Even though the first two options have their use cases, most developers prefer external style sheets because they keep the styles separate from the HTML elements. This improves the readability and reusability of your code.
|
||||
The idea behind CSS is that you can use a selector to target an HTML element in the DOM (Document Object Model) and then apply a variety of attributes to that element to change the way it is displayed on the page.<br><br>In this section, you'll see how adding CSS styles to the elements of your CatPhotoApp can change it from simple text to something more.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the CSS Flexbox Challenges
|
||||
block: CSS Flexbox
|
||||
---
|
||||
## Introduction to the CSS Flexbox Challenges
|
||||
|
||||
A website's User Interface ("UI") has two components. First, there are the visual elements, such as colors, fonts, and images. Second, there is the placement or positioning of those elements. In Responsive Web Design, a UI layout must accommodate many different browsers and devices accessing the content.<br><br>CSS3 introduced Flexible Boxes, or flexbox, to create page layouts for a dynamic UI. It is a layout mode that arranges elements in a predictable way for different screen sizes and browsers. While somewhat new, all popular modern browsers support flexbox. This section covers how to use flexbox and the different layout options it offers.
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Introduction to the CSS Grid Challenges
|
||||
block: CSS Grid
|
||||
---
|
||||
## Introduction to the CSS Grid Challenges
|
||||
|
||||
<dfn>CSS Grid</dfn> helps you easily build complex web designs. It works by turning an HTML element into a grid container with rows and columns for you to place children elements where you want within the grid.
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Introduction to the Responsive Web Design Challenges
|
||||
block: Responsive Web Design Principles
|
||||
---
|
||||
## Introduction to the Responsive Web Design Challenges
|
||||
|
||||
Today, there are many types of devices that can access the web. They range from large desktop computers to small mobile phones. These devices have different screen sizes, resolutions, and processing power.
|
||||
Responsive Web Design is an approach to designing web content that responds to the constraints of different devices. The page structure and CSS rules should be flexible to accommodate these differences.
|
||||
In general, design the page's CSS to your target audience. If you expect most of your traffic to be from mobile users, take a 'mobile-first' approach. Then add conditional rules for larger screen sizes. If your visitors are desktop users, then design for larger screens with conditional rules for smaller sizes.
|
||||
CSS gives you the tools to write different style rules, then apply them depending on the device displaying the page. This section will cover the basic ways to use CSS for Responsive Web Design.
|
||||
|
49
packages/learn/src/redux/propTypes.js
Normal file
49
packages/learn/src/redux/propTypes.js
Normal file
@ -0,0 +1,49 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const FileType = PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
ext: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
contents: PropTypes.string,
|
||||
head: PropTypes.string,
|
||||
tail: PropTypes.string
|
||||
});
|
||||
|
||||
export const ChallengeNode = PropTypes.shape({
|
||||
block: PropTypes.string,
|
||||
challengeType: PropTypes.number,
|
||||
dashedName: PropTypes.string,
|
||||
description: PropTypes.arrayOf(PropTypes.string),
|
||||
files: PropTypes.shape({
|
||||
indexhtml: FileType,
|
||||
indexjs: FileType
|
||||
}),
|
||||
fields: PropTypes.shape({
|
||||
slug: PropTypes.string,
|
||||
blockName: PropTypes.string
|
||||
}),
|
||||
guideUrl: PropTypes.string,
|
||||
head: PropTypes.arrayOf(PropTypes.string),
|
||||
helpRoom: PropTypes.string,
|
||||
suborder: PropTypes.number,
|
||||
isBeta: PropTypes.bool,
|
||||
isComingSoon: PropTypes.bool,
|
||||
isLocked: PropTypes.bool,
|
||||
isPrivate: PropTypes.bool,
|
||||
isRequired: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
order: PropTypes.number,
|
||||
superOrder: PropTypes.number,
|
||||
superBlock: PropTypes.string,
|
||||
tail: PropTypes.arrayOf(PropTypes.string),
|
||||
time: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
});
|
||||
|
||||
export const AllChallengeNode = PropTypes.shape({
|
||||
edges: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
node: ChallengeNode
|
||||
})
|
||||
)
|
||||
});
|
30
packages/learn/src/redux/store.js
Normal file
30
packages/learn/src/redux/store.js
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
createStore as reduxCreateStore,
|
||||
combineReducers,
|
||||
applyMiddleware
|
||||
} from 'redux';
|
||||
import { combineEpics, createEpicMiddleware } from 'redux-observable';
|
||||
import { routerReducer as router, routerMiddleware } from 'react-router-redux';
|
||||
|
||||
import { reducer as challenge, epics } from '../templates/Challenges/redux';
|
||||
import { reducer as map } from '../components/Map/redux';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
challenge,
|
||||
map,
|
||||
router
|
||||
});
|
||||
|
||||
const rootEpic = combineEpics(...epics);
|
||||
const epicMiddleware = createEpicMiddleware(rootEpic, {
|
||||
dependencies: {
|
||||
window: typeof window !== 'undefined' ? window : {},
|
||||
document: typeof window !== 'undefined' ? document : {}
|
||||
}
|
||||
});
|
||||
|
||||
export const createStore = history =>
|
||||
reduxCreateStore(
|
||||
rootReducer,
|
||||
applyMiddleware(epicMiddleware, routerMiddleware(history))
|
||||
);
|
57
packages/learn/src/templates/Challenges/icons/GreenPass.js
Normal file
57
packages/learn/src/templates/Challenges/icons/GreenPass.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
|
||||
const propTypes = {};
|
||||
|
||||
function GreenPass() {
|
||||
return (
|
||||
<svg
|
||||
height='50'
|
||||
viewBox='0 0 200 200'
|
||||
width='50'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<g>
|
||||
<title>Test Passed</title>
|
||||
<circle
|
||||
cx='100'
|
||||
cy='99'
|
||||
fill='#006400'
|
||||
r='95'
|
||||
stroke='#006400'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
/>
|
||||
<rect
|
||||
fill='#ffffff'
|
||||
height='30'
|
||||
stroke='#ffffff'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
transform='rotate(-45, 120, 106.321)'
|
||||
width='128.85878'
|
||||
x='55.57059'
|
||||
y='91.32089'
|
||||
/>
|
||||
<rect
|
||||
fill='#ffffff'
|
||||
height='30'
|
||||
stroke='#ffffff'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
transform='rotate(45, 66.75, 123.75)'
|
||||
width='80.66548'
|
||||
x='26.41726'
|
||||
y='108.75'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
GreenPass.displayName = 'GreenPass';
|
||||
GreenPass.propTypes = propTypes;
|
||||
|
||||
export default GreenPass;
|
57
packages/learn/src/templates/Challenges/icons/RedFail.js
Normal file
57
packages/learn/src/templates/Challenges/icons/RedFail.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
|
||||
const propTypes = {};
|
||||
|
||||
function RedFail() {
|
||||
return (
|
||||
<svg
|
||||
height='50'
|
||||
viewBox='0 0 200 200'
|
||||
width='50'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<g>
|
||||
<title>Test failed</title>
|
||||
<circle
|
||||
cx='100'
|
||||
cy='99'
|
||||
fill='#ff0000'
|
||||
r='95'
|
||||
stroke='#ff0000'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
/>
|
||||
<rect
|
||||
fill='#ffffff'
|
||||
height='30'
|
||||
stroke='#ffffff'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
transform='rotate(-45, 100, 103.321)'
|
||||
width='128.85878'
|
||||
x='35'
|
||||
y='88'
|
||||
/>
|
||||
<rect
|
||||
fill='#ffffff'
|
||||
height='30'
|
||||
stroke='#ffffff'
|
||||
strokeDasharray='null'
|
||||
strokeLinecap='null'
|
||||
strokeLinejoin='null'
|
||||
transform='rotate(45, 99.5, 104)'
|
||||
width='128.85878'
|
||||
x='35'
|
||||
y='88'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
RedFail.displayName = 'RedFail';
|
||||
RedFail.propTypes = propTypes;
|
||||
|
||||
export default RedFail;
|
102
packages/learn/src/templates/Challenges/rechallenge/builders.js
Normal file
102
packages/learn/src/templates/Challenges/rechallenge/builders.js
Normal file
@ -0,0 +1,102 @@
|
||||
import _ from 'lodash';
|
||||
import { Observable } from 'rxjs';
|
||||
import cond from 'lodash/cond';
|
||||
import flow from 'lodash/flow';
|
||||
import identity from 'lodash/identity';
|
||||
import matchesProperty from 'lodash/matchesProperty';
|
||||
import partial from 'lodash/partial';
|
||||
import stubTrue from 'lodash/stubTrue';
|
||||
|
||||
import { fetchScript, fetchLink } from '../utils/fetch-and-cache.js';
|
||||
import { compileHeadTail, setExt, transformContents } from '../utils/polyvinyl';
|
||||
|
||||
const htmlCatch = '\n<!--fcc-->\n';
|
||||
const jsCatch = '\n;/*fcc*/\n';
|
||||
|
||||
const defaultTemplate = ({ source }) => `
|
||||
<body style='margin:8px;'>
|
||||
<!-- fcc-start-source -->
|
||||
${source}
|
||||
<!-- fcc-end-source -->
|
||||
</body>
|
||||
`;
|
||||
|
||||
const wrapInScript = partial(
|
||||
transformContents,
|
||||
content => `${htmlCatch}<script>${content}${jsCatch}</script>`
|
||||
);
|
||||
const wrapInStyle = partial(
|
||||
transformContents,
|
||||
content => `${htmlCatch}<style>${content}</style>`
|
||||
);
|
||||
const setExtToHTML = partial(setExt, 'html');
|
||||
const padContentWithJsCatch = partial(compileHeadTail, jsCatch);
|
||||
const padContentWithHTMLCatch = partial(compileHeadTail, htmlCatch);
|
||||
|
||||
export const jsToHtml = cond([
|
||||
[
|
||||
matchesProperty('ext', 'js'),
|
||||
flow(padContentWithJsCatch, wrapInScript, setExtToHTML)
|
||||
],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
export const cssToHtml = cond([
|
||||
[
|
||||
matchesProperty('ext', 'css'),
|
||||
flow(padContentWithHTMLCatch, wrapInStyle, setExtToHTML)
|
||||
],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
// FileStream::concactHtml(
|
||||
// required: [ ...Object ],
|
||||
// template: String
|
||||
// ) => Observable[{ build: String, sources: Dictionary }]
|
||||
export function concactHtml(required, template) {
|
||||
const createBody = template ? _.template(template) : defaultTemplate;
|
||||
const source = this.shareReplay();
|
||||
const sourceMap = source.flatMap(files =>
|
||||
files.reduce((sources, file) => {
|
||||
sources[file.name] = file.source || file.contents;
|
||||
return sources;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const head = Observable.from(required)
|
||||
.flatMap(required => {
|
||||
if (required.src) {
|
||||
return fetchScript(required);
|
||||
}
|
||||
if (required.link) {
|
||||
return fetchLink(required);
|
||||
}
|
||||
return Observable.of('');
|
||||
})
|
||||
.reduce((head, required) => head + required, '')
|
||||
.map(head => `<head>${head}</head>`);
|
||||
|
||||
const body = source
|
||||
.flatMap(file =>
|
||||
file.reduce((body, file) => {
|
||||
return body + file.contents + file.tail + htmlCatch;
|
||||
}, '')
|
||||
)
|
||||
.map(source => ({ source }))
|
||||
.map(createBody);
|
||||
|
||||
return Observable.combineLatest(
|
||||
head,
|
||||
body,
|
||||
fetchScript({
|
||||
src: '/js/frame-runner.js',
|
||||
crossDomain: false,
|
||||
cacheBreaker: true
|
||||
}),
|
||||
sourceMap,
|
||||
(head, body, frameRunner, sources) => ({
|
||||
build: head + body + frameRunner,
|
||||
sources
|
||||
})
|
||||
);
|
||||
}
|
123
packages/learn/src/templates/Challenges/rechallenge/throwers.js
Normal file
123
packages/learn/src/templates/Challenges/rechallenge/throwers.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import cond from 'lodash/cond';
|
||||
import identity from 'lodash/identity';
|
||||
import stubTrue from 'lodash/stubTrue';
|
||||
import conforms from 'lodash/conforms';
|
||||
|
||||
import { castToObservable } from '../utils/polyvinyl';
|
||||
|
||||
const HTML$JSReg = /html|js/;
|
||||
|
||||
const testHTMLJS = conforms({ ext: ext => HTML$JSReg.test(ext) });
|
||||
// const testJS = matchesProperty('ext', 'js');
|
||||
const passToNext = [stubTrue, identity];
|
||||
|
||||
// Detect if a JS multi-line comment is left open
|
||||
const throwIfOpenComments = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function _checkForComments({ contents }) {
|
||||
const openingComments = contents.match(/\/\*/gi);
|
||||
const closingComments = contents.match(/\*\//gi);
|
||||
if (
|
||||
openingComments &&
|
||||
(!closingComments || openingComments.length > closingComments.length)
|
||||
) {
|
||||
throw new SyntaxError('Unfinished multi-line comment');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
// Nested dollar sign calls breaks browsers
|
||||
const nestedJQCallReg = /\$\s*?\(\s*?\$\s*?\)/gi;
|
||||
const throwIfNestedJquery = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (nestedJQCallReg.test(contents)) {
|
||||
throw new SyntaxError('Nested jQuery calls breaks browsers');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
const functionReg = /function/g;
|
||||
const functionCallReg = /function\s*?\(|function\s+\w+\s*?\(/gi;
|
||||
// lonely function keywords breaks browsers
|
||||
const ThrowIfUnfinishedFunction = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (functionReg.test(contents) && !functionCallReg.test(contents)) {
|
||||
throw new SyntaxError('Unsafe or unfinished function declaration');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
// console call stops tests scripts from running
|
||||
const unsafeConsoleCallReg = /if\s\(null\)\sconsole\.log\(1\);/gi;
|
||||
const throwIfUnsafeConsoleCall = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (unsafeConsoleCallReg.test(contents)) {
|
||||
throw new SyntaxError(
|
||||
'`if (null) console.log(1)` detected. This will break tests'
|
||||
);
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
// Code with the URL hyperdev.com should not be allowed to run,
|
||||
const goMixReg = /glitch\.(com|me)/gi;
|
||||
const throwIfGomixDetected = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (goMixReg.test(contents)) {
|
||||
throw new Error('Glitch.com or Glitch.me should not be in the code');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
const validators = [
|
||||
throwIfOpenComments,
|
||||
throwIfGomixDetected,
|
||||
throwIfNestedJquery,
|
||||
ThrowIfUnfinishedFunction,
|
||||
throwIfUnsafeConsoleCall
|
||||
];
|
||||
|
||||
export default function validate(file) {
|
||||
return (
|
||||
validators
|
||||
.reduce(
|
||||
(obs, validator) =>
|
||||
obs.flatMap(file => {
|
||||
try {
|
||||
return castToObservable(validator(file));
|
||||
} catch (err) {
|
||||
return Observable.throw(err);
|
||||
}
|
||||
}),
|
||||
Observable.of(file)
|
||||
)
|
||||
// if no error has occured map to the original file
|
||||
.switchMap(() => Observable.of(file))
|
||||
// if err add it to the file
|
||||
// and return file
|
||||
.catch(err => {
|
||||
file.error = err;
|
||||
return Observable.of(file);
|
||||
})
|
||||
);
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
import {
|
||||
attempt,
|
||||
cond,
|
||||
flow,
|
||||
identity,
|
||||
isError,
|
||||
matchesProperty,
|
||||
overSome,
|
||||
partial,
|
||||
stubTrue
|
||||
} from 'lodash';
|
||||
|
||||
import * as Babel from 'babel-standalone';
|
||||
import presetEs2015 from 'babel-preset-es2015';
|
||||
import presetReact from 'babel-preset-react';
|
||||
import { Observable } from 'rxjs';
|
||||
import protect from 'loop-protect';
|
||||
|
||||
import * as vinyl from '../utils/polyvinyl.js';
|
||||
|
||||
const { castToObservable } = vinyl;
|
||||
|
||||
const protectTimeout = 100;
|
||||
Babel.registerPlugin('loopProtection', protect(protectTimeout));
|
||||
|
||||
const babelOptions = {
|
||||
plugins: ['loopProtection'],
|
||||
presets: [presetEs2015, presetReact]
|
||||
};
|
||||
const babelTransformCode = code => Babel.transform(code, babelOptions).code;
|
||||
|
||||
// const sourceReg =
|
||||
// /(<!-- fcc-start-source -->)([\s\S]*?)(?=<!-- fcc-end-source -->)/g;
|
||||
const console$logReg = /(?:\b)console(\.log\S+)/g;
|
||||
const NBSPReg = new RegExp(String.fromCharCode(160), 'g');
|
||||
|
||||
const isJS = matchesProperty('ext', 'js');
|
||||
const testHTMLJS = overSome(isJS, matchesProperty('ext', 'html'));
|
||||
export const testJS$JSX = overSome(isJS, matchesProperty('ext', 'jsx'));
|
||||
|
||||
// work around the absence of multi-flile editing
|
||||
// this can be replaced with `matchesProperty('ext', 'sass')`
|
||||
// when the time comes
|
||||
const sassRE = /type='text\/sass'/i;
|
||||
const testSASS = file => sassRE.test(file.contents);
|
||||
// This can be done in the transformer when we have multi-file editing
|
||||
const browserSassCompiler = `
|
||||
<script>
|
||||
var styleTags = [ ...document.querySelectorAll('style') ];
|
||||
[].slice.call(styleTags, 1).forEach(
|
||||
function compileSass(tag) {
|
||||
var scss = tag.innerHTML;
|
||||
Sass.compile(scss, function(result) {
|
||||
tag.type = 'text/css';
|
||||
tag.innerHTML = result.text;
|
||||
});
|
||||
}
|
||||
)
|
||||
</script>
|
||||
`;
|
||||
// if shouldProxyConsole then we change instances of console log
|
||||
// to `window.__console.log`
|
||||
// this let's us tap into logging into the console.
|
||||
// currently we only do this to the main window and not the test window
|
||||
export const proxyLoggerTransformer = partial(
|
||||
vinyl.transformHeadTailAndContents,
|
||||
source =>
|
||||
source.replace(console$logReg, (match, methodCall) => {
|
||||
return 'window.__console' + methodCall;
|
||||
})
|
||||
);
|
||||
|
||||
// const addLoopProtect = partial(vinyl.transformContents, contents => {
|
||||
// /* eslint-disable import/no-unresolved */
|
||||
// const loopProtect = require('loop-protect');
|
||||
// /* eslint-enable import/no-unresolved */
|
||||
// loopProtect.hit = loopProtectHit;
|
||||
// return loopProtect(contents);
|
||||
// });
|
||||
|
||||
// export const addLoopProtectHtmlJsJsx = cond([
|
||||
// [
|
||||
// overEvery(
|
||||
// testHTMLJS,
|
||||
// partial(vinyl.testContents, contents =>
|
||||
// contents.toLowerCase().includes('<script>')
|
||||
// )
|
||||
// ),
|
||||
// addLoopProtect
|
||||
// ],
|
||||
// [testJS$JSX, addLoopProtect],
|
||||
// [stubTrue, identity]
|
||||
// ]);
|
||||
|
||||
export const replaceNBSP = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
partial(vinyl.transformContents, contents => contents.replace(NBSPReg, ' '))
|
||||
],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
function tryTransform(wrap = identity) {
|
||||
return function transformWrappedPoly(source) {
|
||||
const result = attempt(wrap, source);
|
||||
if (isError(result)) {
|
||||
const friendlyError = `${result}`
|
||||
.match(/[\w\W]+?\n/)[0]
|
||||
.replace(' unknown:', '');
|
||||
throw new Error(friendlyError);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export const babelTransformer = cond([
|
||||
[
|
||||
testJS$JSX,
|
||||
flow(
|
||||
partial(
|
||||
vinyl.transformHeadTailAndContents,
|
||||
tryTransform(babelTransformCode)
|
||||
),
|
||||
partial(vinyl.setExt, 'js')
|
||||
)
|
||||
],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
export const sassTransformer = cond([
|
||||
[testSASS, partial(vinyl.appendToTail, browserSassCompiler)],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
export const _transformers = [
|
||||
// addLoopProtectHtmlJsJsx,
|
||||
replaceNBSP,
|
||||
babelTransformer,
|
||||
sassTransformer
|
||||
];
|
||||
|
||||
export function applyTransformers(file, transformers = _transformers) {
|
||||
return transformers.reduce((obs, transformer) => {
|
||||
return obs.flatMap(file => castToObservable(transformer(file)));
|
||||
}, Observable.of(file));
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { ofType } from 'redux-observable';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { empty } from 'rxjs/observable/empty';
|
||||
|
||||
import { types, openModal } from './';
|
||||
|
||||
function challengeModalEpic(action$) {
|
||||
return action$.pipe(
|
||||
ofType(types.updateTests),
|
||||
switchMap(({ payload: tests }) => {
|
||||
const challengeComplete = tests.every(test => test.pass && !test.err);
|
||||
return challengeComplete ? of(openModal('completion')) : empty();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default challengeModalEpic;
|
175
packages/learn/src/templates/Challenges/redux/completion-epic.js
Normal file
175
packages/learn/src/templates/Challenges/redux/completion-epic.js
Normal file
@ -0,0 +1,175 @@
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { ofType } from 'redux-observable';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
import {
|
||||
// backendFormValuesSelector,
|
||||
// frontendProjectFormValuesSelector,
|
||||
// backendProjectFormValuesSelector,
|
||||
// challengeMetaSelector,
|
||||
// moveToNextChallenge,
|
||||
// submitChallengeComplete,
|
||||
// testsSelector,
|
||||
types,
|
||||
closeModal,
|
||||
challengeMetaSelector
|
||||
} from './';
|
||||
|
||||
// import {
|
||||
// challengeSelector,
|
||||
// createErrorObservable,
|
||||
// csrfSelector,
|
||||
// userSelector
|
||||
// } from '../../../redux';
|
||||
// import { filesSelector } from '../../../files';
|
||||
// import { backEndProject } from '../../../utils/challengeTypes.js';
|
||||
// import { makeToast } from '../../../Toasts/redux';
|
||||
// import { postJSON$ } from '../../../../utils/ajax-stream.js';
|
||||
|
||||
// function postChallenge(url, username, _csrf, challengeInfo) {
|
||||
// return Observable.if(
|
||||
// () => !!username,
|
||||
// Observable.defer(() => {
|
||||
// const body = { ...challengeInfo, _csrf };
|
||||
// const saveChallenge = postJSON$(url, body)
|
||||
// .retry(3)
|
||||
// .map(({ points, lastUpdated, completedDate }) =>
|
||||
// submitChallengeComplete(username, points, {
|
||||
// ...challengeInfo,
|
||||
// lastUpdated,
|
||||
// completedDate
|
||||
// })
|
||||
// )
|
||||
// .catch(createErrorObservable);
|
||||
// const challengeCompleted = Observable.of(moveToNextChallenge());
|
||||
// return Observable.merge(saveChallenge, challengeCompleted).startWith({
|
||||
// type: types.submitChallenge.start
|
||||
// });
|
||||
// }),
|
||||
// Observable.of(moveToNextChallenge())
|
||||
// );
|
||||
// }
|
||||
|
||||
// function submitModern(type, state) {
|
||||
// const tests = testsSelector(state);
|
||||
// if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
|
||||
// if (type === types.checkChallenge) {
|
||||
// return Observable.empty();
|
||||
// }
|
||||
|
||||
// if (type === types.submitChallenge.toString()) {
|
||||
// const { id } = challengeSelector(state);
|
||||
// const files = filesSelector(state);
|
||||
// const { username } = userSelector(state);
|
||||
// const csrfToken = csrfSelector(state);
|
||||
// return postChallenge(
|
||||
// '/modern-challenge-completed', username, csrfToken, {
|
||||
// id,
|
||||
// files
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// return Observable.of(makeToast({ message: 'Keep trying.' }));
|
||||
// }
|
||||
|
||||
// function submitProject(type, state) {
|
||||
// if (type === types.checkChallenge) {
|
||||
// return Observable.empty();
|
||||
// }
|
||||
// const {
|
||||
// solution: frontEndSolution = '' } = frontendProjectFormValuesSelector(
|
||||
// state
|
||||
// );
|
||||
// const {
|
||||
// solution: backendSolution = '',
|
||||
// githubLink = ''
|
||||
// } = backendProjectFormValuesSelector(state);
|
||||
// const solution = frontEndSolution ? frontEndSolution : backendSolution;
|
||||
// const { id, challengeType } = challengeSelector(state);
|
||||
// const { username } = userSelector(state);
|
||||
// const csrfToken = csrfSelector(state);
|
||||
// const challengeInfo = { id, challengeType, solution };
|
||||
// if (challengeType === backEndProject) {
|
||||
// challengeInfo.githubLink = githubLink;
|
||||
// }
|
||||
// return Observable.merge(
|
||||
// postChallenge('/project-completed', username, csrfToken, challengeInfo),
|
||||
// Observable.of(closeChallengeModal())
|
||||
// );
|
||||
// }
|
||||
|
||||
// function submitSimpleChallenge(type, state) {
|
||||
// const { id } = challengeSelector(state);
|
||||
// const { username } = userSelector(state);
|
||||
// const csrfToken = csrfSelector(state);
|
||||
// const challengeInfo = { id };
|
||||
// return postChallenge(
|
||||
// '/challenge-completed',
|
||||
// username,
|
||||
// csrfToken,
|
||||
// challengeInfo
|
||||
// );
|
||||
// }
|
||||
|
||||
// function submitBackendChallenge(type, state) {
|
||||
// const tests = testsSelector(state);
|
||||
// if (
|
||||
// type === types.checkChallenge &&
|
||||
// tests.length > 0 &&
|
||||
// tests.every(test => test.pass && !test.err)
|
||||
// ) {
|
||||
// /*
|
||||
// return Observable.of(
|
||||
// makeToast({
|
||||
// message: `${randomCompliment()} Go to your next challenge.`,
|
||||
// action: 'Submit',
|
||||
// actionCreator: 'submitChallenge',
|
||||
// timeout: 10000
|
||||
// })
|
||||
// );
|
||||
// */
|
||||
// return Observable.empty();
|
||||
// }
|
||||
// if (type === types.submitChallenge.toString()) {
|
||||
// const { id } = challengeSelector(state);
|
||||
// const { username } = userSelector(state);
|
||||
// const csrfToken = csrfSelector(state);
|
||||
// const { solution } = backendFormValuesSelector(state);
|
||||
// const challengeInfo = { id, solution };
|
||||
// return postChallenge(
|
||||
// '/backend-challenge-completed',
|
||||
// username,
|
||||
// csrfToken,
|
||||
// challengeInfo
|
||||
// );
|
||||
// }
|
||||
// return Observable.of(makeToast({ message: 'Keep trying.' }));
|
||||
// }
|
||||
|
||||
// const submitters = {
|
||||
// tests: submitModern,
|
||||
// backend: submitBackendChallenge,
|
||||
// step: submitSimpleChallenge,
|
||||
// video: submitSimpleChallenge,
|
||||
// quiz: submitSimpleChallenge,
|
||||
// 'project.frontEnd': submitProject,
|
||||
// 'project.backEnd': submitProject,
|
||||
// 'project.simple': submitSimpleChallenge
|
||||
// };
|
||||
|
||||
export default function completionEpic(action$, { getState }) {
|
||||
return action$.pipe(
|
||||
ofType(types.submitChallenge),
|
||||
switchMap(({ type }) => {
|
||||
const { nextChallengePath } = 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({ type: 'PONG' });
|
||||
})
|
||||
);
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
switchMap,
|
||||
map,
|
||||
filter,
|
||||
pluck,
|
||||
concat
|
||||
} from 'rxjs/operators';
|
||||
import { ofType, combineEpics } from 'redux-observable';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
types,
|
||||
challengeTestsSelector,
|
||||
initConsole,
|
||||
updateConsole,
|
||||
checkChallenge,
|
||||
updateTests
|
||||
} from './';
|
||||
import { buildFromFiles } from '../utils/build';
|
||||
import {
|
||||
runTestsInTestFrame,
|
||||
createTestFramer,
|
||||
createMainFramer
|
||||
} from '../utils/frame.js';
|
||||
|
||||
const executeDebounceTimeout = 750;
|
||||
|
||||
function updateMainEpic(actions, { getState }, { document }) {
|
||||
return Observable.of(document).pipe(
|
||||
filter(Boolean),
|
||||
switchMap(() => {
|
||||
const proxyLogger = new Subject();
|
||||
const frameMain = createMainFramer(document, getState, proxyLogger);
|
||||
const buildAndFrameMain = actions.pipe(
|
||||
ofType(types.updateFile, types.executeChallenge),
|
||||
debounceTime(executeDebounceTimeout),
|
||||
switchMap(() =>
|
||||
buildFromFiles(getState(), true)
|
||||
.map(frameMain)
|
||||
.ignoreElements()
|
||||
.catch(err => console.error(err))
|
||||
)
|
||||
);
|
||||
return Observable.merge(
|
||||
buildAndFrameMain,
|
||||
proxyLogger.map(updateConsole)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function executeChallengeEpic(action$, { getState }, { document }) {
|
||||
return Observable.of(document).pipe(
|
||||
filter(Boolean),
|
||||
switchMap(() => {
|
||||
const frameReady = new Subject();
|
||||
const frameTests = createTestFramer(document, getState, frameReady);
|
||||
const challengeResults = frameReady.pipe(
|
||||
pluck('checkChallengePayload'),
|
||||
map(checkChallengePayload => ({
|
||||
checkChallengePayload,
|
||||
tests: challengeTestsSelector(getState())
|
||||
})),
|
||||
switchMap(({ checkChallengePayload, tests }) => {
|
||||
const postTests = Observable.of(
|
||||
updateConsole('// tests completed'),
|
||||
checkChallenge(checkChallengePayload)
|
||||
).delay(250);
|
||||
// run the tests within the test iframe
|
||||
return runTestsInTestFrame(document, tests)
|
||||
.flatMap(tests => {
|
||||
return Observable.from(tests).pipe(
|
||||
map(({ message }) => message),
|
||||
filter(_.overEvery(_.isString, Boolean)),
|
||||
map(updateConsole),
|
||||
concat(Observable.of(updateTests(tests)))
|
||||
);
|
||||
})
|
||||
.concat(postTests);
|
||||
})
|
||||
);
|
||||
const buildAndFrameChallenge = action$.pipe(
|
||||
ofType(types.executeChallenge),
|
||||
debounceTime(executeDebounceTimeout),
|
||||
// if isCodeLocked do not run challenges
|
||||
// .filter(() => !codeLockedSelector(getState()))
|
||||
switchMap(() => {
|
||||
const state = getState();
|
||||
// const { challengeType } = challengeSelector(state);
|
||||
// if (challengeType === backend) {
|
||||
// return buildBackendChallenge(state)
|
||||
// .do(frameTests)
|
||||
// .ignoreElements()
|
||||
// .startWith(initOutput('// running test'))
|
||||
// .catch(createErrorObservable);
|
||||
// }
|
||||
return buildFromFiles(state, false)
|
||||
.do(frameTests)
|
||||
.ignoreElements()
|
||||
.startWith(initConsole('// running test'))
|
||||
.catch(err => console.log(err));
|
||||
})
|
||||
);
|
||||
return Observable.merge(buildAndFrameChallenge, challengeResults);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default combineEpics(updateMainEpic, executeChallengeEpic);
|
163
packages/learn/src/templates/Challenges/redux/index.js
Normal file
163
packages/learn/src/templates/Challenges/redux/index.js
Normal file
@ -0,0 +1,163 @@
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
|
||||
import { createTypes } from '../../../../utils/stateManagment';
|
||||
import { createPoly } from '../utils/polyvinyl';
|
||||
import challengeModalEpic from './challenge-modal-epic';
|
||||
import completionEpic from './completion-epic';
|
||||
import executeChallengeEpic from './execute-challenge-epic';
|
||||
|
||||
const ns = 'challenge';
|
||||
|
||||
const initialState = {
|
||||
challengeFiles: {},
|
||||
challengeMeta: {
|
||||
id: '',
|
||||
nextChallengePath: '/'
|
||||
},
|
||||
challengeTests: [],
|
||||
consoleOut: '',
|
||||
isJSEnabled: true,
|
||||
modal: {
|
||||
completion: false,
|
||||
help: false
|
||||
},
|
||||
successMessage: 'Happy Coding!'
|
||||
};
|
||||
|
||||
export const epics = [challengeModalEpic, completionEpic, executeChallengeEpic];
|
||||
|
||||
export const types = createTypes(
|
||||
[
|
||||
'createFiles',
|
||||
'initTests',
|
||||
'initConsole',
|
||||
'updateConsole',
|
||||
'updateChallengeMeta',
|
||||
'updateFile',
|
||||
'updateJSEnabled',
|
||||
'updateSuccessMessage',
|
||||
'updateTests',
|
||||
'disableJSOnError',
|
||||
|
||||
'closeModal',
|
||||
'openModal',
|
||||
|
||||
'checkChallenge',
|
||||
'executeChallenge',
|
||||
'submitChallenge'
|
||||
],
|
||||
ns
|
||||
);
|
||||
|
||||
export const createFiles = createAction(types.createFiles, challengeFiles =>
|
||||
Object.keys(challengeFiles)
|
||||
.filter(key => challengeFiles[key])
|
||||
.map(key => challengeFiles[key])
|
||||
.reduce(
|
||||
(challengeFiles, file) => ({
|
||||
...challengeFiles,
|
||||
[file.key]: {
|
||||
...createPoly(file),
|
||||
seed: file.contents.slice(0)
|
||||
}
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
export const initTests = createAction(types.initTests);
|
||||
export const updateTests = createAction(types.updateTests);
|
||||
|
||||
export const initConsole = createAction(types.initConsole);
|
||||
export const updateChallengeMeta = createAction(types.updateChallengeMeta);
|
||||
export const updateFile = createAction(types.updateFile);
|
||||
export const updateConsole = createAction(types.updateConsole);
|
||||
export const updateJSEnabled = createAction(types.updateJSEnabled);
|
||||
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
|
||||
|
||||
export const disableJSOnError = createAction(types.disableJSOnError, err => {
|
||||
console.error(err);
|
||||
return {};
|
||||
});
|
||||
|
||||
export const closeModal = createAction(types.closeModal);
|
||||
export const openModal = createAction(types.openModal);
|
||||
|
||||
export const checkChallenge = createAction(types.checkChallenge);
|
||||
export const executeChallenge = createAction(types.executeChallenge);
|
||||
export const submitChallenge = createAction(types.submitChallenge);
|
||||
|
||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||
|
||||
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
||||
|
||||
export const challengeTestsSelector = state => state[ns].challengeTests;
|
||||
|
||||
export const isJSEnabledSelector = state => state[ns].isJSEnabled;
|
||||
|
||||
export const consoleOutputSelector = state => state[ns].consoleOut;
|
||||
|
||||
export const isCompletionModalOpenSelector = state =>
|
||||
state[ns].modal.completion;
|
||||
export const successMessageSelector = state => state[ns].successMessage;
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[types.createFiles]: (state, { payload }) => ({
|
||||
...state,
|
||||
challengeFiles: payload
|
||||
}),
|
||||
[types.initTests]: (state, { payload }) => ({
|
||||
...state,
|
||||
challengeTests: payload
|
||||
}),
|
||||
[types.updateTests]: (state, { payload }) => ({
|
||||
...state,
|
||||
challengeTests: payload
|
||||
}),
|
||||
[types.initConsole]: (state, { payload }) => ({
|
||||
...state,
|
||||
consoleOut: payload
|
||||
}),
|
||||
[types.updateChallengeMeta]: (state, { payload }) => ({
|
||||
...state,
|
||||
challengeMeta: { ...payload }
|
||||
}),
|
||||
[types.updateConsole]: (state, { payload }) => ({
|
||||
...state,
|
||||
consoleOut: state.consoleOut + '\n' + payload
|
||||
}),
|
||||
[types.updateFile]: (state, { payload: { key, editorValue } }) => ({
|
||||
...state,
|
||||
challengeFiles: {
|
||||
...state.challengeFiles,
|
||||
[key]: {
|
||||
...state.challengeFiles[key],
|
||||
contents: editorValue
|
||||
}
|
||||
}
|
||||
}),
|
||||
[types.disableJSOnError]: state => ({
|
||||
...state,
|
||||
isJSEnabled: false
|
||||
}),
|
||||
[types.updateSuccessMessage]: (state, { payload }) => ({
|
||||
...state,
|
||||
successMessage: payload
|
||||
}),
|
||||
[types.closeModal]: (state, { payload }) => ({
|
||||
...state,
|
||||
modal: {
|
||||
...state.modal,
|
||||
[payload]: false
|
||||
}
|
||||
}),
|
||||
[types.openModal]: (state, { payload }) => ({
|
||||
...state,
|
||||
modal: {
|
||||
...state.modal,
|
||||
[payload]: true
|
||||
}
|
||||
})
|
||||
},
|
||||
initialState
|
||||
);
|
308
packages/learn/src/templates/Challenges/utils/ajax-stream.js
Normal file
308
packages/learn/src/templates/Challenges/utils/ajax-stream.js
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
* Microsoft Open Technologies would like to thank its contributors, a list
|
||||
* of whom are at http://rx.codeplex.com/wikipage?title=Contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
* may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*/
|
||||
|
||||
import debugFactory from 'debug';
|
||||
import { Observable, helpers } from 'rxjs';
|
||||
|
||||
const debug = debugFactory('fcc:ajax$');
|
||||
const root = typeof window !== 'undefined' ? window : {};
|
||||
|
||||
// Gets the proper XMLHttpRequest for support for older IE
|
||||
function getXMLHttpRequest() {
|
||||
if (root.XMLHttpRequest) {
|
||||
return new root.XMLHttpRequest();
|
||||
} else {
|
||||
var progId;
|
||||
try {
|
||||
var progIds = [
|
||||
'Msxml2.XMLHTTP',
|
||||
'Microsoft.XMLHTTP',
|
||||
'Msxml2.XMLHTTP.4.0'
|
||||
];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
try {
|
||||
progId = progIds[i];
|
||||
if (new root.ActiveXObject(progId)) {
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// purposely do nothing
|
||||
helpers.noop(e);
|
||||
}
|
||||
}
|
||||
return new root.ActiveXObject(progId);
|
||||
} catch (e) {
|
||||
throw new Error('XMLHttpRequest is not supported by your browser');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get CORS support even for older IE
|
||||
function getCORSRequest() {
|
||||
var xhr = new root.XMLHttpRequest();
|
||||
if ('withCredentials' in xhr) {
|
||||
return xhr;
|
||||
} else if (root.XDomainRequest) {
|
||||
return new XDomainRequest();
|
||||
} else {
|
||||
throw new Error('CORS is not supported by your browser');
|
||||
}
|
||||
}
|
||||
|
||||
function parseXhrResponse(responseType, xhr) {
|
||||
switch (responseType) {
|
||||
case 'json':
|
||||
if ('response' in xhr) {
|
||||
return xhr.responseType
|
||||
? xhr.response
|
||||
: JSON.parse(xhr.response || xhr.responseText || 'null');
|
||||
} else {
|
||||
return JSON.parse(xhr.responseText || 'null');
|
||||
}
|
||||
case 'xml':
|
||||
return xhr.responseXML;
|
||||
case 'text':
|
||||
default:
|
||||
return 'response' in xhr ? xhr.response : xhr.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAjaxSuccessEvent(e, xhr, settings) {
|
||||
return {
|
||||
response: parseXhrResponse(settings.responseType || xhr.responseType, xhr),
|
||||
status: xhr.status,
|
||||
responseType: xhr.responseType,
|
||||
xhr: xhr,
|
||||
originalEvent: e
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeAjaxErrorEvent(e, xhr, type) {
|
||||
return {
|
||||
type: type,
|
||||
status: xhr.status,
|
||||
xhr: xhr,
|
||||
originalEvent: e
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an observable for an Ajax request with either a settings object
|
||||
* with url, headers, etc or a string for a URL.
|
||||
*
|
||||
* @example
|
||||
* source = Rx.DOM.ajax('/products');
|
||||
* source = Rx.DOM.ajax( url: 'products', method: 'GET' });
|
||||
*
|
||||
* interface Options {
|
||||
* url: String, // URL of the request
|
||||
* body?: Object, // The body of the request
|
||||
* method? = 'GET' : 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||
* async? = true: Boolean, // Whether the request is async
|
||||
* headers?: Object, // optional headers
|
||||
* crossDomain?: true // if a cross domain request, else false
|
||||
* }
|
||||
*
|
||||
* ajax$(url?: String, options: Options) => Observable[XMLHttpRequest]
|
||||
*/
|
||||
export function ajax$(options) {
|
||||
var settings = {
|
||||
method: 'GET',
|
||||
crossDomain: false,
|
||||
async: true,
|
||||
headers: {},
|
||||
responseType: 'text',
|
||||
createXHR: function() {
|
||||
return this.crossDomain ? getCORSRequest() : getXMLHttpRequest();
|
||||
},
|
||||
normalizeError: normalizeAjaxErrorEvent,
|
||||
normalizeSuccess: normalizeAjaxSuccessEvent
|
||||
};
|
||||
|
||||
if (typeof options === 'string') {
|
||||
settings.url = options;
|
||||
} else {
|
||||
for (var prop in options) {
|
||||
if (hasOwnProperty.call(options, prop)) {
|
||||
settings[prop] = options[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var normalizeError = settings.normalizeError;
|
||||
var normalizeSuccess = settings.normalizeSuccess;
|
||||
|
||||
if (!settings.crossDomain && !settings.headers['X-Requested-With']) {
|
||||
settings.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
settings.hasContent = typeof settings.body !== 'undefined';
|
||||
|
||||
return new Observable.create(function(observer) {
|
||||
var isDone = false;
|
||||
var xhr;
|
||||
|
||||
var processResponse = function(xhr, e) {
|
||||
var status = xhr.status === 1223 ? 204 : xhr.status;
|
||||
if ((status >= 200 && status <= 300) || status === 0 || status === '') {
|
||||
try {
|
||||
observer.next(normalizeSuccess(e, xhr, settings));
|
||||
observer.complete();
|
||||
} catch (err) {
|
||||
observer.error(err);
|
||||
}
|
||||
} else {
|
||||
observer.error(normalizeError(e, xhr, 'error'));
|
||||
}
|
||||
isDone = true;
|
||||
};
|
||||
|
||||
try {
|
||||
xhr = settings.createXHR();
|
||||
} catch (err) {
|
||||
observer.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
if (settings.user) {
|
||||
xhr.open(
|
||||
settings.method,
|
||||
settings.url,
|
||||
settings.async,
|
||||
settings.user,
|
||||
settings.password
|
||||
);
|
||||
} else {
|
||||
xhr.open(settings.method, settings.url, settings.async);
|
||||
}
|
||||
|
||||
var headers = settings.headers;
|
||||
for (var header in headers) {
|
||||
if (hasOwnProperty.call(headers, header)) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!xhr.upload || (!('withCredentials' in xhr) && root.XDomainRequest)) {
|
||||
xhr.onload = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.next(e);
|
||||
settings.progressObserver.complete();
|
||||
}
|
||||
processResponse(xhr, e);
|
||||
};
|
||||
|
||||
if (settings.progressObserver) {
|
||||
xhr.onprogress = function(e) {
|
||||
settings.progressObserver.next(e);
|
||||
};
|
||||
}
|
||||
|
||||
xhr.onerror = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.error(e);
|
||||
}
|
||||
observer.error(normalizeError(e, xhr, 'error'));
|
||||
isDone = true;
|
||||
};
|
||||
|
||||
xhr.onabort = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.error(e);
|
||||
}
|
||||
observer.error(normalizeError(e, xhr, 'abort'));
|
||||
isDone = true;
|
||||
};
|
||||
} else {
|
||||
xhr.onreadystatechange = function(e) {
|
||||
if (xhr.readyState === 4) {
|
||||
processResponse(xhr, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
debug('ajax$ sending content', settings.hasContent && settings.body);
|
||||
xhr.send((settings.hasContent && settings.body) || null);
|
||||
} catch (err) {
|
||||
observer.error(err);
|
||||
}
|
||||
|
||||
return function() {
|
||||
if (!isDone && xhr.readyState !== 4) {
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Creates an observable sequence from an Ajax POST Request with the body.
|
||||
// post$(url: String, body: Object) => Observable[Any]
|
||||
export function post$(url, body) {
|
||||
try {
|
||||
body = JSON.stringify(body);
|
||||
} catch (e) {
|
||||
return Observable.throw(e);
|
||||
}
|
||||
|
||||
return ajax$({ url, body, method: 'POST' });
|
||||
}
|
||||
|
||||
// postJSON$(url: String, body: Object) => Observable[Object]
|
||||
export function postJSON$(url, body) {
|
||||
try {
|
||||
body = JSON.stringify(body);
|
||||
} catch (e) {
|
||||
return Observable.throw(e);
|
||||
}
|
||||
|
||||
return ajax$({
|
||||
url,
|
||||
body,
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
normalizeError: (e, xhr) => parseXhrResponse('json', xhr)
|
||||
}).map(({ response }) => response);
|
||||
}
|
||||
|
||||
// Creates an observable sequence from an Ajax GET Request with the body.
|
||||
// get$(url: String) => Obserable[Any]
|
||||
export function get$(url) {
|
||||
return ajax$({ url: url });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable sequence from JSON from an Ajax request
|
||||
*
|
||||
* @param {String} url The URL to GET
|
||||
* @returns {Observable} The observable sequence which contains the parsed JSON
|
||||
*/
|
||||
// getJSON$(url: String) => Observable[Object];
|
||||
export function getJSON$(url) {
|
||||
return ajax$({
|
||||
url: url,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
normalizeError: (e, xhr) => parseXhrResponse('json', xhr)
|
||||
}).map(({ response }) => response);
|
||||
}
|
74
packages/learn/src/templates/Challenges/utils/build.js
Normal file
74
packages/learn/src/templates/Challenges/utils/build.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import identity from 'lodash/identity';
|
||||
|
||||
// import { fetchScript } from './fetch-and-cache.js';
|
||||
import throwers from '../rechallenge/throwers';
|
||||
import {
|
||||
challengeFilesSelector,
|
||||
isJSEnabledSelector,
|
||||
disableJSOnError
|
||||
} from '../redux';
|
||||
import {
|
||||
applyTransformers,
|
||||
proxyLoggerTransformer,
|
||||
testJS$JSX
|
||||
} from '../rechallenge/transformers';
|
||||
import { cssToHtml, jsToHtml, concactHtml } from '../rechallenge/builders.js';
|
||||
import { createFileStream, pipe } from './polyvinyl';
|
||||
|
||||
const jQuery = {
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'
|
||||
};
|
||||
// const frameRunner = {
|
||||
// src: '/js/frame-runner.js',
|
||||
// crossDomain: false,
|
||||
// cacheBreaker: true
|
||||
// };
|
||||
const globalRequires = [
|
||||
{
|
||||
link:
|
||||
'https://cdnjs.cloudflare.com/' +
|
||||
'ajax/libs/normalize/4.2.0/normalize.min.css'
|
||||
},
|
||||
jQuery
|
||||
];
|
||||
|
||||
function filterJSIfDisabled(state) {
|
||||
const isJSEnabled = isJSEnabledSelector(state);
|
||||
return file => !(testJS$JSX(file) && !isJSEnabled);
|
||||
}
|
||||
|
||||
export function buildFromFiles(state, shouldProxyConsole) {
|
||||
const files = challengeFilesSelector(state);
|
||||
const required = [];
|
||||
/* challengeRequiredSelector(state);*/
|
||||
const finalRequires = [...globalRequires, ...required];
|
||||
const requiredFiles = Object.keys(files)
|
||||
.map(key => files[key])
|
||||
.filter(filterJSIfDisabled(state))
|
||||
.filter(Boolean);
|
||||
return createFileStream(requiredFiles)
|
||||
::pipe(throwers)
|
||||
::pipe(applyTransformers)
|
||||
::pipe(shouldProxyConsole ? proxyLoggerTransformer : identity)
|
||||
::pipe(jsToHtml)
|
||||
::pipe(cssToHtml)
|
||||
::concactHtml(
|
||||
finalRequires,
|
||||
false
|
||||
/* challengeTemplateSelector(state) */
|
||||
)
|
||||
.catch(err => of(disableJSOnError(err)));
|
||||
}
|
||||
|
||||
// export function buildBackendChallenge(state) {
|
||||
// const { solution: url } = backendFormValuesSelector(state);
|
||||
// return Observable.combineLatest(
|
||||
// fetchScript(frameRunner),
|
||||
// fetchScript(jQuery)
|
||||
// ).map(([frameRunner, jQuery]) => ({
|
||||
// build: jQuery + frameRunner,
|
||||
// sources: { url },
|
||||
// checkChallengePayload: { solution: url }
|
||||
// }));
|
||||
// }
|
@ -0,0 +1,69 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { ajax$ } from '../utils/ajax-stream';
|
||||
|
||||
// value used to break browser ajax caching
|
||||
const cacheBreakerValue = Math.random();
|
||||
|
||||
export function _fetchScript({
|
||||
src,
|
||||
cacheBreaker = false,
|
||||
crossDomain = true
|
||||
} = {}) {
|
||||
if (!src) {
|
||||
throw new Error('No source provided for script');
|
||||
}
|
||||
if (this.cache.has(src)) {
|
||||
return this.cache.get(src);
|
||||
}
|
||||
const url = cacheBreaker ? `${src}?cacheBreaker=${cacheBreakerValue}` : src;
|
||||
const script = ajax$({ url, crossDomain })
|
||||
.do(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Request errror: ' + res.status);
|
||||
}
|
||||
})
|
||||
.map(({ response }) => response)
|
||||
.map(script => `<script>${script}</script>`)
|
||||
.shareReplay();
|
||||
|
||||
this.cache.set(src, script);
|
||||
return script;
|
||||
}
|
||||
export const fetchScript = _fetchScript.bind({ cache: new Map() });
|
||||
|
||||
export function _fetchLink({
|
||||
link: href,
|
||||
raw = false,
|
||||
crossDomain = true
|
||||
} = {}) {
|
||||
if (!href) {
|
||||
return Observable.throw(new Error('No source provided for link'));
|
||||
}
|
||||
if (this.cache.has(href)) {
|
||||
return this.cache.get(href);
|
||||
}
|
||||
// css files with `url(...` may not work in style tags
|
||||
// so we put them in raw links
|
||||
if (raw) {
|
||||
const link = Observable.of(
|
||||
`<link href=${href} rel='stylesheet' />`
|
||||
).shareReplay();
|
||||
this.cache.set(href, link);
|
||||
return link;
|
||||
}
|
||||
const link = ajax$({ url: href, crossDomain })
|
||||
.do(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Request error: ' + res.status);
|
||||
}
|
||||
})
|
||||
.map(({ response }) => response)
|
||||
.map(script => `<style>${script}</style>`)
|
||||
.catch(() => Observable.of(''))
|
||||
.shareReplay();
|
||||
|
||||
this.cache.set(href, link);
|
||||
return link;
|
||||
}
|
||||
|
||||
export const fetchLink = _fetchLink.bind({ cache: new Map() });
|
147
packages/learn/src/templates/Challenges/utils/frame.js
Normal file
147
packages/learn/src/templates/Challenges/utils/frame.js
Normal file
@ -0,0 +1,147 @@
|
||||
import _ from 'lodash';
|
||||
import Rx, { Observable } from 'rxjs';
|
||||
import { ShallowWrapper, ReactWrapper } from 'enzyme';
|
||||
import Adapter15 from 'enzyme-adapter-react-15';
|
||||
import { isJSEnabledSelector } from '../redux';
|
||||
import 'chai';
|
||||
|
||||
// we use two different frames to make them all essentially pure functions
|
||||
// main iframe is responsible rendering the preview and is where we proxy the
|
||||
// console.log
|
||||
const mainId = 'fcc-main-frame';
|
||||
// the test frame is responsible for running the assert tests
|
||||
const testId = 'fcc-test-frame';
|
||||
|
||||
// base tag here will force relative links
|
||||
// within iframe to point to '/' instead of
|
||||
// append to the current challenge url
|
||||
// if an error occurs during initialization
|
||||
// the __err prop will be set
|
||||
// This is then picked up in client/frame-runner.js during
|
||||
// runTestsInTestFrame below
|
||||
const createHeader = (id = mainId) => `
|
||||
<base href='/' target='_blank'/>
|
||||
<script>
|
||||
window.__frameId = '${id}';
|
||||
window.onerror = function(msg, url, ln, col, err) {
|
||||
window.__err = err;
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
`;
|
||||
|
||||
export const runTestsInTestFrame = (document, tests) =>
|
||||
Observable.defer(() => {
|
||||
const { contentDocument: frame } = document.getElementById(testId);
|
||||
return frame.__runTests(tests);
|
||||
});
|
||||
|
||||
const createFrame = (document, getState, id) => ctx => {
|
||||
const isJSEnabled = isJSEnabledSelector(getState());
|
||||
const frame = document.createElement('iframe');
|
||||
frame.id = id;
|
||||
if (!isJSEnabled) {
|
||||
frame.sandbox = 'allow-same-origin';
|
||||
}
|
||||
return {
|
||||
...ctx,
|
||||
element: frame
|
||||
};
|
||||
};
|
||||
|
||||
const hiddenFrameClassname = 'hide-test-frame';
|
||||
const mountFrame = document => ({ element, ...rest }) => {
|
||||
const oldFrame = document.getElementById(element.id);
|
||||
if (oldFrame) {
|
||||
element.className = oldFrame.className || hiddenFrameClassname;
|
||||
oldFrame.parentNode.replaceChild(element, oldFrame);
|
||||
} else {
|
||||
element.className = hiddenFrameClassname;
|
||||
document.body.appendChild(element);
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
element,
|
||||
document: element.contentDocument,
|
||||
window: element.contentWindow
|
||||
};
|
||||
};
|
||||
|
||||
const addDepsToDocument = ctx => {
|
||||
ctx.document.Rx = Rx;
|
||||
|
||||
// using require here prevents nodejs issues as loop-protect
|
||||
// is added to the window object by webpack and not available to
|
||||
// us server side.
|
||||
/* eslint-disable import/no-unresolved */
|
||||
ctx.document.loopProtect = require('loop-protect');
|
||||
/* eslint-enable import/no-unresolved */
|
||||
return ctx;
|
||||
};
|
||||
|
||||
const buildProxyConsole = proxyLogger => ctx => {
|
||||
const oldLog = ctx.window.console.log.bind(ctx.window.console);
|
||||
ctx.window.__console = {};
|
||||
ctx.window.__console.log = function proxyConsole(...args) {
|
||||
proxyLogger.onNext(args);
|
||||
return oldLog(...args);
|
||||
};
|
||||
return ctx;
|
||||
};
|
||||
|
||||
const writeTestDepsToDocument = frameReady => ctx => {
|
||||
const { document: tests, sources, checkChallengePayload } = ctx;
|
||||
// add enzyme
|
||||
// TODO: do programatically
|
||||
// TODO: webpack lazyload this
|
||||
tests.Enzyme = {
|
||||
shallow: (node, options) =>
|
||||
new ShallowWrapper(node, null, {
|
||||
...options,
|
||||
adapter: new Adapter15()
|
||||
}),
|
||||
mount: (node, options) =>
|
||||
new ReactWrapper(node, null, {
|
||||
...options,
|
||||
adapter: new Adapter15()
|
||||
})
|
||||
};
|
||||
// default for classic challenges
|
||||
// should not be used for modern
|
||||
tests.__source = sources && 'index' in sources ? sources['index'] : '';
|
||||
// provide the file name and get the original source
|
||||
tests.__getUserInput = fileName => _.toString(sources[fileName]);
|
||||
tests.__checkChallengePayload = checkChallengePayload;
|
||||
tests.__frameReady = frameReady;
|
||||
return ctx;
|
||||
};
|
||||
|
||||
function writeToFrame(content, frame) {
|
||||
frame.open();
|
||||
frame.write(content);
|
||||
frame.close();
|
||||
return frame;
|
||||
}
|
||||
|
||||
const writeContentToFrame = ctx => {
|
||||
writeToFrame(createHeader(ctx.element.id) + ctx.build, ctx.document);
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export const createMainFramer = (document, getState, proxyLogger) =>
|
||||
_.flow(
|
||||
createFrame(document, getState, mainId),
|
||||
mountFrame(document),
|
||||
addDepsToDocument,
|
||||
buildProxyConsole(proxyLogger),
|
||||
writeContentToFrame
|
||||
);
|
||||
|
||||
export const createTestFramer = (document, getState, frameReady) =>
|
||||
_.flow(
|
||||
createFrame(document, getState, testId),
|
||||
mountFrame(document),
|
||||
addDepsToDocument,
|
||||
writeTestDepsToDocument(frameReady),
|
||||
writeContentToFrame
|
||||
);
|
17
packages/learn/src/templates/Challenges/utils/get-words.js
Normal file
17
packages/learn/src/templates/Challenges/utils/get-words.js
Normal file
@ -0,0 +1,17 @@
|
||||
import words from './words.json';
|
||||
|
||||
function randomItem(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
export function randomPhrase() {
|
||||
return randomItem(words.phrases);
|
||||
}
|
||||
|
||||
export function randomVerb() {
|
||||
return randomItem(words.verbs);
|
||||
}
|
||||
|
||||
export function randomCompliment() {
|
||||
return randomItem(words.compliments);
|
||||
}
|
217
packages/learn/src/templates/Challenges/utils/polyvinyl.js
Normal file
217
packages/learn/src/templates/Challenges/utils/polyvinyl.js
Normal file
@ -0,0 +1,217 @@
|
||||
// originally based off of https://github.com/gulpjs/vinyl
|
||||
import invariant from 'invariant';
|
||||
import { Observable } from 'rxjs';
|
||||
import { isPromise } from 'rxjs/util/isPromise';
|
||||
|
||||
export function castToObservable(maybe) {
|
||||
if (maybe instanceof Observable) {
|
||||
return maybe;
|
||||
}
|
||||
if (isPromise(maybe)) {
|
||||
return Observable.fromPromise(maybe);
|
||||
}
|
||||
return Observable.of(maybe);
|
||||
}
|
||||
|
||||
// createFileStream(
|
||||
// files: [...PolyVinyl]
|
||||
// ) => Observable[...Observable[...PolyVinyl]]
|
||||
export function createFileStream(files = []) {
|
||||
return Observable.of(Observable.from(files));
|
||||
}
|
||||
|
||||
// Observable::pipe(
|
||||
// project(
|
||||
// file: PolyVinyl
|
||||
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
|
||||
// ) => Observable[...Observable[...PolyVinyl]]
|
||||
export function pipe(project) {
|
||||
const source = this;
|
||||
return source.map(files =>
|
||||
files.flatMap(file => castToObservable(project(file)))
|
||||
);
|
||||
}
|
||||
|
||||
// interface PolyVinyl {
|
||||
// source: String,
|
||||
// contents: String,
|
||||
// name: String,
|
||||
// ext: String,
|
||||
// path: String,
|
||||
// key: String,
|
||||
// head: String,
|
||||
// tail: String,
|
||||
// history: [...String],
|
||||
// error: Null|Object|Error
|
||||
// }
|
||||
|
||||
// createPoly({
|
||||
// name: String,
|
||||
// ext: String,
|
||||
// contents: String,
|
||||
// history?: [...String],
|
||||
// }) => PolyVinyl, throws
|
||||
export function createPoly({ name, ext, contents, history, ...rest } = {}) {
|
||||
invariant(typeof name === 'string', 'name must be a string but got %s', name);
|
||||
|
||||
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
|
||||
|
||||
invariant(
|
||||
typeof contents === 'string',
|
||||
'contents must be a string but got %s',
|
||||
contents
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
history: Array.isArray(history) ? history : [name + ext],
|
||||
name,
|
||||
ext,
|
||||
path: name + '.' + ext,
|
||||
key: name + ext,
|
||||
contents,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
// isPoly(poly: Any) => Boolean
|
||||
export function isPoly(poly) {
|
||||
return (
|
||||
poly &&
|
||||
typeof poly.contents === 'string' &&
|
||||
typeof poly.name === 'string' &&
|
||||
typeof poly.ext === 'string' &&
|
||||
Array.isArray(poly.history)
|
||||
);
|
||||
}
|
||||
|
||||
// checkPoly(poly: Any) => Void, throws
|
||||
export function checkPoly(poly) {
|
||||
invariant(
|
||||
isPoly(poly),
|
||||
'function should receive a PolyVinyl, but got %s',
|
||||
poly
|
||||
);
|
||||
}
|
||||
|
||||
// isEmpty(poly: PolyVinyl) => Boolean, throws
|
||||
export function isEmpty(poly) {
|
||||
checkPoly(poly);
|
||||
return !!poly.contents;
|
||||
}
|
||||
|
||||
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
|
||||
// setContent will loose source if set
|
||||
export function setContent(contents, poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
contents,
|
||||
source: null
|
||||
};
|
||||
}
|
||||
|
||||
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function setExt(ext, poly) {
|
||||
checkPoly(poly);
|
||||
const newPoly = {
|
||||
...poly,
|
||||
ext,
|
||||
path: poly.name + '.' + ext,
|
||||
key: poly.name + ext
|
||||
};
|
||||
newPoly.history = [...poly.history, newPoly.path];
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function setName(name, poly) {
|
||||
checkPoly(poly);
|
||||
const newPoly = {
|
||||
...poly,
|
||||
name,
|
||||
path: name + '.' + poly.ext,
|
||||
key: name + poly.ext
|
||||
};
|
||||
newPoly.history = [...poly.history, newPoly.path];
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
||||
export function setError(error, poly) {
|
||||
invariant(
|
||||
typeof error === 'object',
|
||||
'error must be an object or null, but got %',
|
||||
error
|
||||
);
|
||||
checkPoly(poly);
|
||||
console.log('SET ERROR!!!!!!!!!!!!!!!!!');
|
||||
return {
|
||||
...poly,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
||||
export function clearHeadTail(poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
head: '',
|
||||
tail: ''
|
||||
};
|
||||
}
|
||||
|
||||
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function appendToTail(tail, poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
tail: poly.tail.concat(tail)
|
||||
};
|
||||
}
|
||||
|
||||
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function compileHeadTail(padding = '', poly) {
|
||||
return clearHeadTail(
|
||||
transformContents(
|
||||
() => [poly.head, poly.contents, poly.tail].join(padding),
|
||||
poly
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// transformContents(
|
||||
// wrap: (contents: String) => String,
|
||||
// poly: PolyVinyl
|
||||
// ) => PolyVinyl
|
||||
// transformContents will keep a copy of the original
|
||||
// code in the `source` property. If the original polyvinyl
|
||||
// already contains a source, this version will continue as
|
||||
// the source property
|
||||
export function transformContents(wrap, poly) {
|
||||
const newPoly = setContent(wrap(poly.contents), poly);
|
||||
// if no source exist, set the original contents as source
|
||||
newPoly.source = poly.source || poly.contents;
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// transformHeadTailAndContents(
|
||||
// wrap: (source: String) => String,
|
||||
// poly: PolyVinyl
|
||||
// ) => PolyVinyl
|
||||
export function transformHeadTailAndContents(wrap, poly) {
|
||||
return {
|
||||
...transformContents(wrap, poly),
|
||||
head: wrap(poly.head),
|
||||
tail: wrap(poly.tail)
|
||||
};
|
||||
}
|
||||
|
||||
export function testContents(predicate, poly) {
|
||||
return !!predicate(poly.contents);
|
||||
}
|
||||
|
||||
export function updateFileFromSpec(spec, poly) {
|
||||
return setContent(poly.contents, createPoly(spec));
|
||||
}
|
127
packages/learn/src/templates/Challenges/utils/words.json
Normal file
127
packages/learn/src/templates/Challenges/utils/words.json
Normal file
@ -0,0 +1,127 @@
|
||||
{
|
||||
"verbs": [
|
||||
"aced",
|
||||
"nailed",
|
||||
"rocked",
|
||||
"destroyed",
|
||||
"owned",
|
||||
"crushed",
|
||||
"conquered",
|
||||
"shredded",
|
||||
"demolished",
|
||||
"devoured",
|
||||
"banished",
|
||||
"wrangled"
|
||||
],
|
||||
"compliments": [
|
||||
"Over the top!",
|
||||
"Down the rabbit hole we go!",
|
||||
"Bring that rain!",
|
||||
"Target acquired!",
|
||||
"Feel that need for speed!",
|
||||
"You've got guts!",
|
||||
"We have liftoff!",
|
||||
"To infinity and beyond!",
|
||||
"Encore!",
|
||||
"Onward, ho!",
|
||||
"Challenge destroyed!",
|
||||
"It's on like Donkey Kong!",
|
||||
"Power level? It's over 9000!",
|
||||
"Coding spree!",
|
||||
"Code long and prosper.",
|
||||
"The crowd goes wild!",
|
||||
"One for the guinness book!",
|
||||
"Flawless victory!",
|
||||
"Most efficient!",
|
||||
"Party on, Wayne!",
|
||||
"You've got the touch!",
|
||||
"You're on fire!",
|
||||
"Don't hurt 'em, Hammer!",
|
||||
"The town is now red!",
|
||||
"To the nines!",
|
||||
"The world rejoices!",
|
||||
"That's the way it's done!",
|
||||
"You rock!",
|
||||
"Woo-hoo!",
|
||||
"We knew you could do it!",
|
||||
"Hyper Combo Finish!",
|
||||
"Nothing but net!",
|
||||
"Boom-shakalaka!",
|
||||
"You're a shooting star!",
|
||||
"You're unstoppable!",
|
||||
"Way cool!",
|
||||
"You're king of the world!",
|
||||
"Walk on that sunshine!",
|
||||
"Keep on trucking!",
|
||||
"Off the charts!",
|
||||
"There is no spoon!",
|
||||
"Cranked it up to 11!",
|
||||
"Escape velocity reached!",
|
||||
"You make this look easy!",
|
||||
"Passed with flying colors!",
|
||||
"You've got this!",
|
||||
"Happy, happy, joy, joy!",
|
||||
"Tomorrow, the world!",
|
||||
"Your powers combined!",
|
||||
"A winner is you!",
|
||||
"It's alive. It's alive!",
|
||||
"Sonic Boom!",
|
||||
"Here's looking at you, Code!",
|
||||
"Ride like the wind!",
|
||||
"Legen - wait for it - dary!",
|
||||
"Ludicrous Speed! Go!",
|
||||
"Yes we can!",
|
||||
"Most triumphant!",
|
||||
"One loop to rule them all!",
|
||||
"By the power of Grayskull!",
|
||||
"You did it!",
|
||||
"Storm that castle!",
|
||||
"Face-melting guitar solo!",
|
||||
"Checkmate!",
|
||||
"Bodacious!",
|
||||
"Tubular!",
|
||||
"You're outta sight!",
|
||||
"Keep calm and code on!",
|
||||
"Even sad panda smiles!",
|
||||
"Even grumpy cat approves!",
|
||||
"Kool Aid Man says oh yeah!",
|
||||
"Bullseye!",
|
||||
"Far out!",
|
||||
"You're heating up!",
|
||||
"Hasta la vista, challenge!",
|
||||
"Terminated.",
|
||||
"Off the hook!",
|
||||
"Thundercats, Hooo!",
|
||||
"Shiver me timbers!",
|
||||
"Raise the roof!",
|
||||
"We've underestimated you.",
|
||||
"I also live dangerously.",
|
||||
"Get to the choppa!",
|
||||
"Bingo!",
|
||||
"And you're all out of gum.",
|
||||
"Even honeybadger cares!",
|
||||
"Helm, Warp Nine. Engage!",
|
||||
"Gotta code 'em all!",
|
||||
"Spool up the FTL drive!",
|
||||
"Cool beans!",
|
||||
"They're in another castle.",
|
||||
"Power UP!",
|
||||
"Nuclear launch detected.",
|
||||
"Pikachu chooses you!",
|
||||
"We're gonna pump you up!",
|
||||
"I gotta have more cow bell."
|
||||
],
|
||||
"phrases": [
|
||||
"Shout it from on top of a mountain",
|
||||
"Tell everyone and their dogs",
|
||||
"Show them. Show them all!",
|
||||
"Inspire your friends",
|
||||
"Tell the world of your greatness",
|
||||
"Look accomplished on social media",
|
||||
"Share news of your grand endeavor",
|
||||
"Establish your alibi for the past two hours",
|
||||
"Prove to mom that computers aren't just for games",
|
||||
"With coding power comes sharing responsibility",
|
||||
"Have you told your friends of your coding powers?"
|
||||
]
|
||||
}
|
155
packages/learn/src/templates/Challenges/views/Modern/Editor.jsx
Normal file
155
packages/learn/src/templates/Challenges/views/Modern/Editor.jsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import Codemirror from 'react-codemirror';
|
||||
import NoSSR from 'react-no-ssr';
|
||||
import MouseTrap from 'mousetrap';
|
||||
|
||||
import ns from './ns.json';
|
||||
import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
|
||||
import {
|
||||
executeChallenge,
|
||||
modernEditorUpdated,
|
||||
challengeMetaSelector
|
||||
} from '../../redux';
|
||||
|
||||
import { themeSelector } from '../../../../redux';
|
||||
|
||||
import { createFileSelector } from '../../../../files';
|
||||
|
||||
const envProps = typeof window !== 'undefined' ? Object.keys(window) : [];
|
||||
const options = {
|
||||
lint: {
|
||||
esversion: 6,
|
||||
predef: envProps
|
||||
},
|
||||
lineNumbers: true,
|
||||
mode: 'javascript',
|
||||
runnable: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: [ 'CodeMirror-lint-markers' ]
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
createFileSelector((_, { fileKey }) => fileKey || ''),
|
||||
challengeMetaSelector,
|
||||
themeSelector,
|
||||
(
|
||||
file,
|
||||
{ mode },
|
||||
theme
|
||||
) => ({
|
||||
content: file.contents || '// Happy Coding!',
|
||||
file: file,
|
||||
mode: file.ext || mode || 'javascript',
|
||||
theme
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = {
|
||||
executeChallenge,
|
||||
modernEditorUpdated
|
||||
};
|
||||
|
||||
const propTypes = {
|
||||
content: PropTypes.string,
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
fileKey: PropTypes.string,
|
||||
mode: PropTypes.string,
|
||||
modernEditorUpdated: PropTypes.func.isRequired,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export class Editor extends PureComponent {
|
||||
createOptions = createSelector(
|
||||
state => state.executeChallenge,
|
||||
state => state.mode,
|
||||
state => state.cmTheme,
|
||||
(executeChallenge, mode, cmTheme) => ({
|
||||
...options,
|
||||
theme: cmTheme,
|
||||
mode,
|
||||
// JSHint only works with javascript
|
||||
// we will need to switch to eslint to make this work with jsx
|
||||
lint: mode === 'javascript' ? options.lint : false,
|
||||
extraKeys: {
|
||||
Esc() {
|
||||
document.activeElement.blur();
|
||||
},
|
||||
Tab(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
return cm.indentSelection('add');
|
||||
}
|
||||
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||
return cm.replaceSelection(spaces);
|
||||
},
|
||||
'Shift-Tab': function(cm) {
|
||||
return cm.indentSelection('subtract');
|
||||
},
|
||||
'Ctrl-Enter': function() {
|
||||
executeChallenge();
|
||||
return false;
|
||||
},
|
||||
'Cmd-Enter': function() {
|
||||
executeChallenge();
|
||||
return false;
|
||||
},
|
||||
'Ctrl-/': function(cm) {
|
||||
cm.toggleComment();
|
||||
},
|
||||
'Cmd-/': function(cm) {
|
||||
cm.toggleComment();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
MouseTrap.bind('e', () => {
|
||||
this.refs.editor.focus();
|
||||
}, 'keyup');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
MouseTrap.unbind('e', 'keyup');
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
content,
|
||||
modernEditorUpdated,
|
||||
executeChallenge,
|
||||
fileKey,
|
||||
mode
|
||||
} = this.props;
|
||||
const cmTheme = this.props.theme === 'default' ? 'default' : 'dracula';
|
||||
return (
|
||||
<div
|
||||
className={ `${ns}-editor` }
|
||||
role='main'
|
||||
>
|
||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
||||
<Codemirror
|
||||
onChange={ content => modernEditorUpdated(fileKey, content) }
|
||||
options={ this.createOptions({ executeChallenge, mode, cmTheme }) }
|
||||
ref='editor'
|
||||
value={ content }
|
||||
/>
|
||||
</NoSSR>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.displayName = 'Editor';
|
||||
Editor.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Editor);
|
111
packages/learn/src/templates/Challenges/views/Modern/Show.jsx
Normal file
111
packages/learn/src/templates/Challenges/views/Modern/Show.jsx
Normal file
@ -0,0 +1,111 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addNS } from 'berkeleys-redux-utils';
|
||||
|
||||
import ns from './ns.json';
|
||||
import Editor from './Editor.jsx';
|
||||
import ChildContainer from '../../Child-Container.jsx';
|
||||
import { showPreviewSelector, types } from '../../redux';
|
||||
import SidePanel from '../../Side-Panel.jsx';
|
||||
import Preview from '../../Preview.jsx';
|
||||
import _Map from '../../../../Map';
|
||||
import Panes from '../../../../Panes';
|
||||
import { filesSelector } from '../../../../files';
|
||||
|
||||
const createModernEditorToggleType = fileKey =>
|
||||
types.toggleModernEditor + `(${fileKey})`;
|
||||
|
||||
const getFirstFileKey = _.flow(_.values, _.first, _.property('key'));
|
||||
|
||||
const propTypes = {
|
||||
nameToFileKey: PropTypes.object
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
filesSelector,
|
||||
files => {
|
||||
if (Object.keys(files).length === 1) {
|
||||
return { nameToFileKey: { Editor: getFirstFileKey(files) }};
|
||||
}
|
||||
return {
|
||||
nameToFileKey: _.reduce(files, (map, file) => {
|
||||
map[file.name] = file.key;
|
||||
return map;
|
||||
}, {})
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const mapDispatchToProps = null;
|
||||
|
||||
export const mapStateToPanes = addNS(
|
||||
ns,
|
||||
createSelector(
|
||||
filesSelector,
|
||||
showPreviewSelector,
|
||||
(files, showPreview) => {
|
||||
// create panes map here
|
||||
// must include map
|
||||
// side panel
|
||||
// editors are created based on state
|
||||
// so pane component can have multiple panes based on state
|
||||
|
||||
const panesMap = {
|
||||
[types.toggleMap]: 'Map',
|
||||
[types.toggleSidePanel]: 'Lesson'
|
||||
};
|
||||
|
||||
// If there is more than one file show file name
|
||||
if (Object.keys(files).length > 1) {
|
||||
_.forEach(files, (file) => {
|
||||
panesMap[createModernEditorToggleType(file.fileKey)] = file.name;
|
||||
});
|
||||
} else {
|
||||
const key = getFirstFileKey(files);
|
||||
panesMap[createModernEditorToggleType(key)] = 'Editor';
|
||||
}
|
||||
|
||||
if (showPreview) {
|
||||
panesMap[types.togglePreview] = 'Preview';
|
||||
}
|
||||
|
||||
return panesMap;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const nameToComponent = {
|
||||
Map: _Map,
|
||||
Lesson: SidePanel,
|
||||
Preview: Preview
|
||||
};
|
||||
|
||||
export function ShowModern({ nameToFileKey }) {
|
||||
return (
|
||||
<ChildContainer isFullWidth={ true }>
|
||||
<Panes
|
||||
render={ name => {
|
||||
const Comp = nameToComponent[name];
|
||||
if (Comp) {
|
||||
return <Comp />;
|
||||
}
|
||||
if (nameToFileKey[name]) {
|
||||
return <Editor fileKey={ nameToFileKey[name] } />;
|
||||
}
|
||||
return <span>Could not find Component for { name }</span>;
|
||||
}}
|
||||
/>
|
||||
</ChildContainer>
|
||||
);
|
||||
}
|
||||
|
||||
ShowModern.displayName = 'ShowModern';
|
||||
ShowModern.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ShowModern);
|
@ -0,0 +1 @@
|
||||
export { default, mapStateToPanes } from './Show.jsx';
|
@ -0,0 +1 @@
|
||||
"modern"
|
@ -0,0 +1,184 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { createSelector } from 'reselect';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Row
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import ChallengeTitle from '../../Challenge-Title.jsx';
|
||||
import ChallengeDescription from '../../Challenge-Description.jsx';
|
||||
import SolutionInput from '../../Solution-Input.jsx';
|
||||
import TestSuite from '../../Test-Suite.jsx';
|
||||
import Output from '../../Output.jsx';
|
||||
import {
|
||||
executeChallenge,
|
||||
testsSelector,
|
||||
outputSelector
|
||||
} from '../../redux';
|
||||
import { descriptionRegex } from '../../utils';
|
||||
|
||||
import {
|
||||
createFormValidator,
|
||||
isValidURL,
|
||||
makeRequired
|
||||
} from '../../../../utils/form.js';
|
||||
import { challengeSelector } from '../../../../redux';
|
||||
|
||||
// provided by redux form
|
||||
const reduxFormPropTypes = {
|
||||
fields: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
resetForm: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool
|
||||
};
|
||||
|
||||
const propTypes = {
|
||||
description: PropTypes.arrayOf(PropTypes.string),
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
id: PropTypes.string,
|
||||
output: PropTypes.string,
|
||||
tests: PropTypes.array,
|
||||
title: PropTypes.string,
|
||||
...reduxFormPropTypes
|
||||
};
|
||||
|
||||
const fields = [ 'solution' ];
|
||||
|
||||
const fieldValidators = {
|
||||
solution: makeRequired(isValidURL)
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeSelector,
|
||||
outputSelector,
|
||||
testsSelector,
|
||||
(
|
||||
{
|
||||
id,
|
||||
title,
|
||||
description
|
||||
},
|
||||
output,
|
||||
tests
|
||||
) => ({
|
||||
id,
|
||||
title,
|
||||
tests,
|
||||
description,
|
||||
output
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToActions = {
|
||||
executeChallenge
|
||||
};
|
||||
|
||||
export class BackEnd extends PureComponent {
|
||||
renderDescription(description) {
|
||||
if (!Array.isArray(description)) {
|
||||
return null;
|
||||
}
|
||||
return description.map((line, index) => {
|
||||
if (descriptionRegex.test(line)) {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: line }}
|
||||
key={ line.slice(-6) + index }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p
|
||||
className='wrappable'
|
||||
dangerouslySetInnerHTML= {{ __html: line }}
|
||||
key={ line.slice(-6) + index }
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
description,
|
||||
executeChallenge,
|
||||
output,
|
||||
tests,
|
||||
title,
|
||||
// provided by redux-form
|
||||
fields: { solution },
|
||||
handleSubmit,
|
||||
submitting
|
||||
} = this.props;
|
||||
|
||||
const buttonCopy = submitting ?
|
||||
'Submit and go to my next challenge' :
|
||||
"I've completed this challenge";
|
||||
return (
|
||||
<Row>
|
||||
<Col
|
||||
xs={ 6 }
|
||||
xsOffset={ 3 }
|
||||
>
|
||||
<Row>
|
||||
<ChallengeTitle>
|
||||
{ title }
|
||||
</ChallengeTitle>
|
||||
<ChallengeDescription>
|
||||
{ this.renderDescription(description) }
|
||||
</ChallengeDescription>
|
||||
</Row>
|
||||
<Row>
|
||||
<form
|
||||
name='BackEndChallenge'
|
||||
onSubmit={ handleSubmit(executeChallenge) }
|
||||
>
|
||||
<SolutionInput
|
||||
placeholder='https://your-app.com'
|
||||
solution={ solution }
|
||||
/>
|
||||
<Button
|
||||
block={ true }
|
||||
bsStyle='primary'
|
||||
className='btn-big'
|
||||
onClick={ submitting ? null : null }
|
||||
type={ submitting ? null : 'submit' }
|
||||
>
|
||||
{ buttonCopy } (ctrl + enter)
|
||||
</Button>
|
||||
</form>
|
||||
</Row>
|
||||
<Row>
|
||||
<br/>
|
||||
<Output
|
||||
defaultOutput={
|
||||
`/**
|
||||
* Test output will go here
|
||||
*/`
|
||||
}
|
||||
output={ output }
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<TestSuite tests={ tests } />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BackEnd.displayName = 'BackEnd';
|
||||
BackEnd.propTypes = propTypes;
|
||||
|
||||
export default reduxForm(
|
||||
{
|
||||
form: 'BackEndChallenge',
|
||||
fields,
|
||||
validate: createFormValidator(fieldValidators)
|
||||
},
|
||||
mapStateToProps,
|
||||
mapDispatchToActions
|
||||
)(BackEnd);
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { addNS } from 'berkeleys-redux-utils';
|
||||
|
||||
import ChildContainer from '../../Child-Container.jsx';
|
||||
import BackEnd from './Back-End.jsx';
|
||||
import { types } from '../../redux';
|
||||
import Panes from '../../../../Panes';
|
||||
import _Map from '../../../../Map';
|
||||
|
||||
const propTypes = {};
|
||||
|
||||
export const mapStateToPanes = addNS(
|
||||
'backend',
|
||||
() => ({
|
||||
[types.toggleMap]: 'Map',
|
||||
[types.toggleMain]: 'Main'
|
||||
})
|
||||
);
|
||||
|
||||
const nameToComponent = {
|
||||
Map: _Map,
|
||||
Main: BackEnd
|
||||
};
|
||||
|
||||
const renderPane = name => {
|
||||
const Comp = nameToComponent[name];
|
||||
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
||||
};
|
||||
|
||||
export default function ShowBackEnd() {
|
||||
return (
|
||||
<ChildContainer isFullWidth={ true }>
|
||||
<Panes render={ renderPane } />
|
||||
</ChildContainer>
|
||||
);
|
||||
}
|
||||
|
||||
ShowBackEnd.displayName = 'ShowBackEnd';
|
||||
ShowBackEnd.propTypes = propTypes;
|
@ -0,0 +1 @@
|
||||
export { default, mapStateToPanes } from './Show.jsx';
|
107
packages/learn/src/templates/Challenges/views/classic/Editor.js
Normal file
107
packages/learn/src/templates/Challenges/views/classic/Editor.js
Normal file
@ -0,0 +1,107 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
|
||||
import { executeChallenge, updateFile } from '../../redux';
|
||||
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/material.css';
|
||||
|
||||
require('codemirror/mode/htmlmixed/htmlmixed');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
|
||||
const propTypes = {
|
||||
contents: PropTypes.string,
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
ext: PropTypes.string,
|
||||
fileKey: PropTypes.string,
|
||||
updateFile: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
executeChallenge,
|
||||
updateFile
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const modeMap = {
|
||||
html: 'htmlmixed',
|
||||
js: 'javascript',
|
||||
jsx: 'javascript'
|
||||
};
|
||||
|
||||
class Editor extends PureComponent {
|
||||
constructor(...props) {
|
||||
super(...props);
|
||||
|
||||
this._editor = null;
|
||||
}
|
||||
|
||||
handleChange = editorValue => {
|
||||
const { updateFile, fileKey } = this.props;
|
||||
updateFile({ key: fileKey, editorValue });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { contents, executeChallenge, ext } = this.props;
|
||||
|
||||
return (
|
||||
<div className='classic-editor editor'>
|
||||
<CodeMirror
|
||||
onBeforeChange={(editor, something, newValue) =>
|
||||
this.handleChange(newValue)
|
||||
}
|
||||
options={{
|
||||
mode: modeMap[ext],
|
||||
theme: 'material',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
extraKeys: {
|
||||
Esc() {
|
||||
document.activeElement.blur();
|
||||
},
|
||||
Tab(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
return cm.indentSelection('add');
|
||||
}
|
||||
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||
return cm.replaceSelection(spaces);
|
||||
},
|
||||
'Shift-Tab': function(cm) {
|
||||
return cm.indentSelection('subtract');
|
||||
},
|
||||
'Ctrl-Enter': function() {
|
||||
executeChallenge();
|
||||
return false;
|
||||
},
|
||||
'Cmd-Enter': function() {
|
||||
executeChallenge();
|
||||
return false;
|
||||
}
|
||||
// TODO: Not working in cm2
|
||||
// 'Ctrl-/': function(cm) {
|
||||
// cm.toggleComment();
|
||||
// },
|
||||
// 'Cmd-/': function(cm) {
|
||||
// cm.toggleComment();
|
||||
// }
|
||||
}
|
||||
}}
|
||||
value={contents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.displayName = 'Editor';
|
||||
Editor.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Editor);
|
160
packages/learn/src/templates/Challenges/views/classic/Show.js
Normal file
160
packages/learn/src/templates/Challenges/views/classic/Show.js
Normal file
@ -0,0 +1,160 @@
|
||||
/* global graphql */
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { connect } from 'react-redux';
|
||||
import Helmet from 'react-helmet';
|
||||
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
|
||||
|
||||
import Editor from './Editor';
|
||||
import Preview from '../components/Preview';
|
||||
import SidePanel from '../components/Side-Panel';
|
||||
import CompletionModal from '../components/CompletionModal';
|
||||
|
||||
import { challengeTypes } from '../../../../../utils/challengeTypes';
|
||||
import { ChallengeNode } from '../../../../redux/propTypes';
|
||||
import {
|
||||
createFiles,
|
||||
challengeFilesSelector,
|
||||
initTests,
|
||||
updateChallengeMeta
|
||||
} from '../../redux';
|
||||
|
||||
import './classic.css';
|
||||
|
||||
const mapStateToProps = createSelector(challengeFilesSelector, files => ({
|
||||
files
|
||||
}));
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ createFiles, initTests, updateChallengeMeta }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
createFiles: PropTypes.func.isRequired,
|
||||
data: PropTypes.shape({
|
||||
challengeNode: ChallengeNode
|
||||
}),
|
||||
files: PropTypes.shape({
|
||||
key: PropTypes.string
|
||||
}),
|
||||
initTests: PropTypes.func.isRequired,
|
||||
pathContext: PropTypes.shape({
|
||||
challengeMeta: PropTypes.shape({
|
||||
nextchallengePath: PropTypes.string
|
||||
})
|
||||
}),
|
||||
updateChallengeMeta: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class ShowClassic extends PureComponent {
|
||||
componentDidMount() {
|
||||
const {
|
||||
createFiles,
|
||||
initTests,
|
||||
updateChallengeMeta,
|
||||
data: { challengeNode: { files, fields: { tests } } },
|
||||
pathContext: { challengeMeta }
|
||||
} = this.props;
|
||||
createFiles(files);
|
||||
initTests(tests);
|
||||
updateChallengeMeta(challengeMeta);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challengeType,
|
||||
fields: { blockName },
|
||||
title,
|
||||
description,
|
||||
guideUrl
|
||||
}
|
||||
},
|
||||
files
|
||||
} = this.props;
|
||||
const editors = Object.keys(files)
|
||||
.map(key => files[key])
|
||||
.map((file, index) => (
|
||||
<Fragment key={file.key + index}>
|
||||
{index !== 0 && <ReflexSplitter />}
|
||||
<ReflexElement flex={1}>
|
||||
<Editor {...file} fileKey={file.key} />
|
||||
</ReflexElement>
|
||||
</Fragment>
|
||||
));
|
||||
|
||||
const showPreview = challengeType === challengeTypes.html;
|
||||
const blockNameTitle = `${blockName} - ${title}`;
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet title={`${blockNameTitle} | Learn freeCodeCamp}`} />
|
||||
<ReflexContainer orientation='vertical'>
|
||||
<ReflexElement flex={1}>
|
||||
<SidePanel
|
||||
className='full-height'
|
||||
description={description}
|
||||
guideUrl={guideUrl}
|
||||
title={blockNameTitle}
|
||||
/>
|
||||
</ReflexElement>
|
||||
<ReflexSplitter />
|
||||
<ReflexElement flex={1}>
|
||||
<ReflexContainer orientation='horizontal'>
|
||||
{editors}
|
||||
</ReflexContainer>
|
||||
</ReflexElement>
|
||||
{showPreview && <ReflexSplitter />}
|
||||
{showPreview && (
|
||||
<ReflexElement flex={0.3} maxSize={325}>
|
||||
<Preview className='full-height' />
|
||||
</ReflexElement>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
<CompletionModal />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowClassic.displayName = 'ShowClassic';
|
||||
ShowClassic.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowClassic);
|
||||
|
||||
export const query = graphql`
|
||||
query ClassicChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
title
|
||||
guideUrl
|
||||
description
|
||||
challengeType
|
||||
fields {
|
||||
blockName
|
||||
tests {
|
||||
text
|
||||
testString
|
||||
}
|
||||
}
|
||||
files {
|
||||
indexhtml {
|
||||
key
|
||||
ext
|
||||
name
|
||||
contents
|
||||
head
|
||||
tail
|
||||
}
|
||||
indexjs {
|
||||
key
|
||||
ext
|
||||
name
|
||||
contents
|
||||
head
|
||||
tail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
@ -0,0 +1,9 @@
|
||||
.editor,
|
||||
.react-codemirror2,
|
||||
.react-codemirror2 > .CodeMirror-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.instructions-panel {
|
||||
padding: 0 10px;
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './Show.js';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user